mirror of
https://github.com/hmaxnl/SharpRSS.git
synced 2025-01-18 12:54:20 +01:00
Implemented simple authorization, with users, groups & sessions.
This commit is contained in:
parent
9338d8c106
commit
25ca94ae3a
|
@ -3,9 +3,9 @@ using System.Linq;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using SharpRSS.API.Contracts.Models;
|
using SharpRSS.API.Contracts.Models;
|
||||||
using SharpRSS.API.Data;
|
using SharpRSS.API.Data;
|
||||||
|
using SharpRSS.API.Models;
|
||||||
using ToolQit;
|
using ToolQit;
|
||||||
using ToolQit.Extensions;
|
using ToolQit.Extensions;
|
||||||
using ToolQit.Logging;
|
using ToolQit.Logging;
|
||||||
|
@ -23,8 +23,7 @@ namespace SharpRSS.API.Auth
|
||||||
|
|
||||||
private readonly ILog _log;
|
private readonly ILog _log;
|
||||||
private readonly bool _admin;
|
private readonly bool _admin;
|
||||||
|
public void OnAuthorization(AuthorizationFilterContext context)
|
||||||
public async void OnAuthorization(AuthorizationFilterContext context)
|
|
||||||
{
|
{
|
||||||
if (context.ActionDescriptor.EndpointMetadata.Any(obj => obj.GetType() == typeof(AllowAnonymousAttribute)))
|
if (context.ActionDescriptor.EndpointMetadata.Any(obj => obj.GetType() == typeof(AllowAnonymousAttribute)))
|
||||||
return;
|
return;
|
||||||
|
@ -43,19 +42,26 @@ namespace SharpRSS.API.Auth
|
||||||
context.Result = new UnauthorizedObjectResult(new Result("Invalid session ID"));
|
context.Result = new UnauthorizedObjectResult(new Result("Invalid session ID"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var authSet = await authService.ValidateSession(headerVal);
|
var authSetResult = authService.ValidateSession(headerVal).Result;
|
||||||
if (!authSet.Success || authSet.Value == null)
|
if (!authSetResult.Success || authSetResult.Value == null)
|
||||||
{
|
{
|
||||||
context.Result = new UnauthorizedResult();
|
context.Result = new UnauthorizedResult();
|
||||||
return;
|
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));
|
context.Result = new UnauthorizedObjectResult(new Result("Session expired", ResultStatus.Failed));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
authSet.Value.AdminRequired = _admin;
|
if (!authSetResult.Value.User.Active)
|
||||||
context.HttpContext.Items["auth"] = authSet.Value;
|
{
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
context.Result = new UnauthorizedResult();
|
context.Result = new UnauthorizedResult();
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
@ -15,7 +16,6 @@ using ToolQit.Logging;
|
||||||
namespace SharpRSS.API.Controllers
|
namespace SharpRSS.API.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[SessionAuthorize(false)]
|
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class AuthController : ControllerBase
|
public class AuthController : ControllerBase
|
||||||
{
|
{
|
||||||
|
@ -44,13 +44,29 @@ namespace SharpRSS.API.Controllers
|
||||||
|
|
||||||
// To update only fill the values that need to be updated.
|
// To update only fill the values that need to be updated.
|
||||||
[HttpPost("user")]
|
[HttpPost("user")]
|
||||||
[SessionAuthorize]
|
[SessionAuthorize(true)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ActionResult<ResultOr<User>>> InsertUser(InsertUser payload)
|
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);
|
var createdUserResult = await _authService.InsertUserAsync(payload);
|
||||||
return createdUserResult.Success ? Created("", createdUserResult) :
|
return createdUserResult.Success ? Created("", createdUserResult) :
|
||||||
createdUserResult.Status == ResultStatus.Failed ? BadRequest(createdUserResult) :
|
createdUserResult.Status == ResultStatus.Failed ? BadRequest(createdUserResult) :
|
||||||
|
@ -58,7 +74,7 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("user")]
|
[HttpDelete("user")]
|
||||||
[SessionAuthorize]
|
[SessionAuthorize(true)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
@ -72,7 +88,7 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("user")]
|
[HttpGet("user")]
|
||||||
[SessionAuthorize]
|
[SessionAuthorize(true)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
@ -83,7 +99,7 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("users")]
|
[HttpGet("users")]
|
||||||
[SessionAuthorize]
|
[SessionAuthorize(true)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
@ -98,7 +114,7 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("group")]
|
[HttpPost("group")]
|
||||||
[SessionAuthorize]
|
[SessionAuthorize(true)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
@ -112,7 +128,7 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("group")]
|
[HttpDelete("group")]
|
||||||
[SessionAuthorize]
|
[SessionAuthorize(true)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
@ -126,12 +142,13 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("groups")]
|
[HttpGet("groups")]
|
||||||
|
[SessionAuthorize(false)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
public async Task<ActionResult<ListResult<Group>>> GetGroups(string search = "", int results = 20, int skip = 0)
|
public async Task<ActionResult<ListResult<GroupItem>>> GetGroups(string search = "", int results = 20, int skip = 0)
|
||||||
{ //TODO: Change DTO to GroupItem!
|
{
|
||||||
var groupsResult = await _authService.GetGroupsAsync(results, skip, search);
|
var groupsResult = await _authService.GetGroupsAsync(results, skip, search);
|
||||||
return groupsResult.Success ? Ok(groupsResult) :
|
return groupsResult.Success ? Ok(groupsResult) :
|
||||||
groupsResult.Status == ResultStatus.Failed ? BadRequest(groupsResult) :
|
groupsResult.Status == ResultStatus.Failed ? BadRequest(groupsResult) :
|
||||||
|
@ -139,6 +156,7 @@ namespace SharpRSS.API.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("group")]
|
[HttpGet("group")]
|
||||||
|
[SessionAuthorize(false)]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
|
|
@ -61,9 +61,23 @@ namespace SharpRSS.API.Data
|
||||||
DbAccess access = new DbAccess(_configuration);
|
DbAccess access = new DbAccess(_configuration);
|
||||||
var dbSession = await access.Sessions.Where(s => s.Sid == sessionId).FirstOrDefaultAsync();
|
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 == 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();
|
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}'");
|
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();
|
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()
|
var set = new AuthorizationSet()
|
||||||
{
|
{
|
||||||
Session = dbSession,
|
Session = dbSession,
|
||||||
|
|
|
@ -2,9 +2,9 @@ namespace SharpRSS.API.Models
|
||||||
{
|
{
|
||||||
internal class AuthorizationSet
|
internal class AuthorizationSet
|
||||||
{
|
{
|
||||||
public DbSession? Session { get; set; }
|
public DbSession Session { get; set; }
|
||||||
public DbUser? User { get; set; }
|
public DbUser User { get; set; }
|
||||||
public DbGroup? Group { get; set; }
|
public DbGroup Group { get; set; }
|
||||||
public bool AdminRequired { get; set; }
|
public bool AdminRequired { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SharpRSS.API.Contracts.DTOs;
|
|
||||||
using SharpRSS.API.Contracts.DTOs.Sessions;
|
|
||||||
|
|
||||||
namespace SharpRSS.API.Models
|
namespace SharpRSS.API.Models
|
||||||
{
|
{
|
||||||
|
@ -20,21 +19,26 @@ namespace SharpRSS.API.Models
|
||||||
Uid = uid;
|
Uid = uid;
|
||||||
Created = DateTime.Now;
|
Created = DateTime.Now;
|
||||||
Expires = Created.AddMinutes(expiresMinutes);
|
Expires = Created.AddMinutes(expiresMinutes);
|
||||||
using SHA1 sha1 = SHA1.Create();
|
string uidHash = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(Uid)));
|
||||||
string uidHash = Convert.ToHexString(sha1.ComputeHash(Encoding.UTF8.GetBytes(Uid)));
|
string createdHash = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(Created.ToString(CultureInfo.CurrentCulture))));
|
||||||
Sid = $"{new DateTimeOffset(Created).ToUnixTimeMilliseconds()}.{uidHash}.{new DateTimeOffset(Expires).ToUnixTimeMilliseconds()}";
|
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 string Sid { get; set; } = string.Empty;
|
||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
public DateTime Expires { get; set; }
|
public DateTime Expires { get; set; }
|
||||||
|
|
||||||
public bool Expired => Expires < DateTime.Now;
|
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);
|
Uid = Uid,
|
||||||
return newSession;
|
Sid = Sid,
|
||||||
}
|
Created = Created,
|
||||||
|
Expires = DateTime.Now.AddMinutes(extendMinutes)
|
||||||
|
};
|
||||||
|
|
||||||
|
public static DbSession CreateSession(string uid, double expiresMinutes = 10080) => new DbSession(uid, expiresMinutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user