diff --git a/SharpRSS.API/Auth/SessionAuthorizeAttribute.cs b/SharpRSS.API/Auth/SessionAuthorizeAttribute.cs index 942d98e..20b68b0 100644 --- a/SharpRSS.API/Auth/SessionAuthorizeAttribute.cs +++ b/SharpRSS.API/Auth/SessionAuthorizeAttribute.cs @@ -3,9 +3,9 @@ using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Primitives; using SharpRSS.API.Contracts.Models; using SharpRSS.API.Data; +using SharpRSS.API.Models; using ToolQit; using ToolQit.Extensions; using ToolQit.Logging; @@ -23,8 +23,7 @@ namespace SharpRSS.API.Auth private readonly ILog _log; private readonly bool _admin; - - public async void OnAuthorization(AuthorizationFilterContext context) + public void OnAuthorization(AuthorizationFilterContext context) { if (context.ActionDescriptor.EndpointMetadata.Any(obj => obj.GetType() == typeof(AllowAnonymousAttribute))) return; @@ -43,19 +42,26 @@ namespace SharpRSS.API.Auth context.Result = new UnauthorizedObjectResult(new Result("Invalid session ID")); return; } - var authSet = await authService.ValidateSession(headerVal); - if (!authSet.Success || authSet.Value == null) + var authSetResult = authService.ValidateSession(headerVal).Result; + if (!authSetResult.Success || authSetResult.Value == null) { context.Result = new UnauthorizedResult(); return; } - if (authSet.Value.Session is { Expired: true }) + if (authSetResult.Value.Session is { Expired: true }) { context.Result = new UnauthorizedObjectResult(new Result("Session expired", ResultStatus.Failed)); return; } - authSet.Value.AdminRequired = _admin; - context.HttpContext.Items["auth"] = authSet.Value; + if (!authSetResult.Value.User.Active) + { + context.Result = new UnauthorizedObjectResult(new Result( + "User is not active, contact your administrator to enable this account!", ResultStatus.Failed)); + return; + } + + authSetResult.Value.AdminRequired = _admin; + context.RouteData.Values.Add(nameof(AuthorizationSet), authSetResult.Value); return; } context.Result = new UnauthorizedResult(); diff --git a/SharpRSS.API/Controllers/AuthController.cs b/SharpRSS.API/Controllers/AuthController.cs index b41bce7..8497a1d 100644 --- a/SharpRSS.API/Controllers/AuthController.cs +++ b/SharpRSS.API/Controllers/AuthController.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -15,7 +16,6 @@ using ToolQit.Logging; namespace SharpRSS.API.Controllers { [ApiController] - [SessionAuthorize(false)] [Route("api/[controller]")] public class AuthController : ControllerBase { @@ -44,13 +44,29 @@ namespace SharpRSS.API.Controllers // To update only fill the values that need to be updated. [HttpPost("user")] - [SessionAuthorize] + [SessionAuthorize(true)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task>> InsertUser(InsertUser payload) { + object? authSetObj = RouteData.Values[nameof(AuthorizationSet)]; + if (authSetObj is AuthorizationSet authSet) + { + if (!authSet.Group.Administrator) + { + if (payload.Uid == authSet.User.Uid) // User can self change own information, but not the group property! + payload.GroupId = string.Empty; + else + return new UnauthorizedObjectResult(new Result( + $"User '{authSet.User.Uid}' in group '{authSet.Group.DisplayName}' does not has the right permission to change user '{payload.Uid}'!")); + } + } + else + return StatusCode(StatusCodes.Status500InternalServerError, new Result("Failed to get the authorization data!", ResultStatus.InternalFail)); + var createdUserResult = await _authService.InsertUserAsync(payload); return createdUserResult.Success ? Created("", createdUserResult) : createdUserResult.Status == ResultStatus.Failed ? BadRequest(createdUserResult) : @@ -58,7 +74,7 @@ namespace SharpRSS.API.Controllers } [HttpDelete("user")] - [SessionAuthorize] + [SessionAuthorize(true)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -72,7 +88,7 @@ namespace SharpRSS.API.Controllers } [HttpGet("user")] - [SessionAuthorize] + [SessionAuthorize(true)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -83,7 +99,7 @@ namespace SharpRSS.API.Controllers } [HttpGet("users")] - [SessionAuthorize] + [SessionAuthorize(true)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -98,7 +114,7 @@ namespace SharpRSS.API.Controllers } [HttpPost("group")] - [SessionAuthorize] + [SessionAuthorize(true)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -112,7 +128,7 @@ namespace SharpRSS.API.Controllers } [HttpDelete("group")] - [SessionAuthorize] + [SessionAuthorize(true)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -126,12 +142,13 @@ namespace SharpRSS.API.Controllers } [HttpGet("groups")] + [SessionAuthorize(false)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetGroups(string search = "", int results = 20, int skip = 0) - { //TODO: Change DTO to GroupItem! + public async Task>> GetGroups(string search = "", int results = 20, int skip = 0) + { var groupsResult = await _authService.GetGroupsAsync(results, skip, search); return groupsResult.Success ? Ok(groupsResult) : groupsResult.Status == ResultStatus.Failed ? BadRequest(groupsResult) : @@ -139,6 +156,7 @@ namespace SharpRSS.API.Controllers } [HttpGet("group")] + [SessionAuthorize(false)] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] diff --git a/SharpRSS.API/Data/AuthService.cs b/SharpRSS.API/Data/AuthService.cs index fdcc1cc..1b80032 100644 --- a/SharpRSS.API/Data/AuthService.cs +++ b/SharpRSS.API/Data/AuthService.cs @@ -61,9 +61,23 @@ namespace SharpRSS.API.Data DbAccess access = new DbAccess(_configuration); var dbSession = await access.Sessions.Where(s => s.Sid == sessionId).FirstOrDefaultAsync(); if (dbSession == null) return ResultOr.Failed($"Could not get session: '{sessionId}'"); + if (!dbSession.Expired && dbSession.Expires - DateTime.Now <= TimeSpan.FromDays(1)) + { + DbSession extended = dbSession.Extend(); + access.Sessions.Update(extended); + bool sessionExtended = await access.SaveChangesAsync() > 0; + if (sessionExtended) + { + dbSession = extended; + _log.Information("Extended session: '{SessionId}' to '{SessionExpirationDate}'", dbSession.Sid, dbSession.Expires); + } + else + _log.Warning("Failed to extend session: '{SessionId}'", dbSession.Sid); + } var dbUser = await access.Users.Where(u => u.Uid == dbSession.Uid).FirstOrDefaultAsync(); if (dbUser == null) return ResultOr.Failed($"Could not get user: '{dbSession.Uid}'"); var dbGroup = await access.Groups.Where(g => g.Gid == dbUser.Gid).FirstOrDefaultAsync(); + if (dbGroup == null) return ResultOr.Failed($"Could not get group: '{dbUser.Gid}'"); var set = new AuthorizationSet() { Session = dbSession, diff --git a/SharpRSS.API/Models/AuthorizationSet.cs b/SharpRSS.API/Models/AuthorizationSet.cs index 76ce812..2fc718b 100644 --- a/SharpRSS.API/Models/AuthorizationSet.cs +++ b/SharpRSS.API/Models/AuthorizationSet.cs @@ -2,9 +2,9 @@ namespace SharpRSS.API.Models { internal class AuthorizationSet { - public DbSession? Session { get; set; } - public DbUser? User { get; set; } - public DbGroup? Group { get; set; } + public DbSession Session { get; set; } + public DbUser User { get; set; } + public DbGroup Group { get; set; } public bool AdminRequired { get; set; } } } \ No newline at end of file diff --git a/SharpRSS.API/Models/DbSession.cs b/SharpRSS.API/Models/DbSession.cs index 1d3b567..b0e7878 100644 --- a/SharpRSS.API/Models/DbSession.cs +++ b/SharpRSS.API/Models/DbSession.cs @@ -1,9 +1,8 @@ using System; +using System.Globalization; using System.Security.Cryptography; using System.Text; using Serilog; -using SharpRSS.API.Contracts.DTOs; -using SharpRSS.API.Contracts.DTOs.Sessions; namespace SharpRSS.API.Models { @@ -20,21 +19,26 @@ namespace SharpRSS.API.Models Uid = uid; Created = DateTime.Now; Expires = Created.AddMinutes(expiresMinutes); - using SHA1 sha1 = SHA1.Create(); - string uidHash = Convert.ToHexString(sha1.ComputeHash(Encoding.UTF8.GetBytes(Uid))); - Sid = $"{new DateTimeOffset(Created).ToUnixTimeMilliseconds()}.{uidHash}.{new DateTimeOffset(Expires).ToUnixTimeMilliseconds()}"; + string uidHash = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(Uid))); + string createdHash = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(Created.ToString(CultureInfo.CurrentCulture)))); + Sid = Convert.ToHexString(SHA512.HashData(Encoding.UTF8.GetBytes($"{createdHash}.{uidHash}"))); } - public string Uid { get; set; } + + public string Uid { get; set; } = string.Empty; public string Sid { get; set; } = string.Empty; public DateTime Created { get; set; } public DateTime Expires { get; set; } public bool Expired => Expires < DateTime.Now; - public static DbSession CreateSession(string uid, double expiresMinutes = 10080) + public DbSession Extend(double extendMinutes = 10080) => new DbSession() { - DbSession newSession = new DbSession(uid, expiresMinutes); - return newSession; - } + Uid = Uid, + Sid = Sid, + Created = Created, + Expires = DateTime.Now.AddMinutes(extendMinutes) + }; + + public static DbSession CreateSession(string uid, double expiresMinutes = 10080) => new DbSession(uid, expiresMinutes); } } \ No newline at end of file