Implemented simple authorization, with users, groups & sessions.

This commit is contained in:
Max 2023-10-09 21:05:38 +02:00
parent 9338d8c106
commit 25ca94ae3a
5 changed files with 72 additions and 30 deletions

View File

@ -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();

View File

@ -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<ActionResult<ResultOr<User>>> 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<ActionResult<ListResult<Group>>> GetGroups(string search = "", int results = 20, int skip = 0)
{ //TODO: Change DTO to GroupItem!
public async Task<ActionResult<ListResult<GroupItem>>> 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)]

View File

@ -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<AuthorizationSet>.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<AuthorizationSet>.Failed($"Could not get user: '{dbSession.Uid}'");
var dbGroup = await access.Groups.Where(g => g.Gid == dbUser.Gid).FirstOrDefaultAsync();
if (dbGroup == null) return ResultOr<AuthorizationSet>.Failed($"Could not get group: '{dbUser.Gid}'");
var set = new AuthorizationSet()
{
Session = dbSession,

View File

@ -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; }
}
}

View File

@ -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);
}
}