Added PermissionService and added docs to all methods with potential user contact
This commit is contained in:
@@ -4,6 +4,13 @@ namespace HopFrame.Api;
|
||||
|
||||
public static class EncryptionManager {
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the given string with the specified hash method
|
||||
/// </summary>
|
||||
/// <param name="input">The raw string that should be hashed</param>
|
||||
/// <param name="salt">The "password" for the hash</param>
|
||||
/// <param name="method">The preferred hash method</param>
|
||||
/// <returns></returns>
|
||||
public static string Hash(string input, byte[] salt, KeyDerivationPrf method = KeyDerivationPrf.HMACSHA256) {
|
||||
return Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||
password: input,
|
||||
|
||||
@@ -7,6 +7,11 @@ namespace HopFrame.Api.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions {
|
||||
|
||||
/// <summary>
|
||||
/// Adds all HopFrame endpoints and the HopFrame security layer to the WebApplication
|
||||
/// </summary>
|
||||
/// <param name="services">The service provider to add the services to</param>
|
||||
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
|
||||
public static void AddHopFrame<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
|
||||
services.AddMvcCore().UseSpecificControllers(typeof(SecurityController<TDbContext>));
|
||||
services.AddHopFrameAuthentication<TDbContext>();
|
||||
|
||||
@@ -3,6 +3,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Database;
|
||||
|
||||
/// <summary>
|
||||
/// This class includes the basic database structure in order for HopFrame to work
|
||||
/// </summary>
|
||||
public class HopDbContextBase : DbContext {
|
||||
|
||||
public HopDbContextBase() {}
|
||||
|
||||
@@ -4,6 +4,12 @@ namespace HopFrame.Database.Models;
|
||||
|
||||
public static class ModelExtensions {
|
||||
|
||||
/// <summary>
|
||||
/// Converts the database model to a friendly user model
|
||||
/// </summary>
|
||||
/// <param name="entry">the database model</param>
|
||||
/// <param name="contextBase">the data source for the permissions and users</param>
|
||||
/// <returns></returns>
|
||||
public static User ToUserModel(this UserEntry entry, HopDbContextBase contextBase) {
|
||||
var user = new User {
|
||||
Id = Guid.Parse(entry.Id),
|
||||
|
||||
@@ -12,29 +12,29 @@ using Microsoft.Extensions.Options;
|
||||
|
||||
namespace HopFrame.Security.Authentication;
|
||||
|
||||
public class HopFrameAuthentication<TDbContext> : AuthenticationHandler<AuthenticationSchemeOptions> where TDbContext : HopDbContextBase {
|
||||
public class HopFrameAuthentication<TDbContext>(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
TDbContext context)
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock)
|
||||
where TDbContext : HopDbContextBase {
|
||||
|
||||
public const string SchemeName = "HopCore.Authentication";
|
||||
public static readonly TimeSpan AccessTokenTime = new(0, 0, 5, 0);
|
||||
public static readonly TimeSpan RefreshTokenTime = new(30, 0, 0, 0);
|
||||
|
||||
private readonly TDbContext _context;
|
||||
|
||||
public HopFrameAuthentication(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger,
|
||||
UrlEncoder encoder, ISystemClock clock, TDbContext context) : base(options, logger, encoder, clock) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
|
||||
var accessToken = Request.Headers["Authorization"].ToString();
|
||||
if (string.IsNullOrEmpty(accessToken)) return AuthenticateResult.Fail("No Access Token provided");
|
||||
|
||||
var tokenEntry = await _context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
|
||||
var tokenEntry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
|
||||
|
||||
if (tokenEntry is null) return AuthenticateResult.Fail("The provided Access Token does not exist");
|
||||
if (tokenEntry.CreatedAt + AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
|
||||
|
||||
if (!(await _context.Users.AnyAsync(user => user.Id == tokenEntry.UserId)))
|
||||
if (!(await context.Users.AnyAsync(user => user.Id == tokenEntry.UserId)))
|
||||
return AuthenticateResult.Fail("The provided Access Token does not match any user");
|
||||
|
||||
var claims = new List<Claim> {
|
||||
@@ -42,7 +42,7 @@ public class HopFrameAuthentication<TDbContext> : AuthenticationHandler<Authenti
|
||||
new(HopFrameClaimTypes.UserId, tokenEntry.UserId)
|
||||
};
|
||||
|
||||
var permissions = await _context.Permissions
|
||||
var permissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == tokenEntry.UserId)
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToListAsync();
|
||||
@@ -51,7 +51,7 @@ public class HopFrameAuthentication<TDbContext> : AuthenticationHandler<Authenti
|
||||
.Where(perm => perm.StartsWith("group."))
|
||||
.ToList();
|
||||
|
||||
var groupPerms = await _context.Permissions
|
||||
var groupPerms = await context.Permissions
|
||||
.Where(perm => groups.Contains(perm.UserId))
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToListAsync();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -9,9 +10,16 @@ namespace HopFrame.Security.Authentication;
|
||||
|
||||
public static class HopFrameAuthenticationExtensions {
|
||||
|
||||
/// <summary>
|
||||
/// Configures the WebApplication to use the authentication and authorization of the HopFrame API
|
||||
/// </summary>
|
||||
/// <param name="service">The service provider to add the services to</param>
|
||||
/// <typeparam name="TDbContext">The database object that saves all entities that are important for the security api</typeparam>
|
||||
/// <returns></returns>
|
||||
public static AuthenticationBuilder AddHopFrameAuthentication<TDbContext>(this IServiceCollection service) where TDbContext : HopDbContextBase {
|
||||
service.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
service.AddScoped<ITokenContext, TokenContextImplementor<TDbContext>>();
|
||||
service.AddScoped<IPermissionService, PermissionService<TDbContext>>();
|
||||
return service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,12 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
public class AuthorizedAttribute : TypeFilterAttribute {
|
||||
public AuthorizedAttribute(params string[] permission) : base(typeof(AuthorizedFilter)) {
|
||||
Arguments = new object[] { permission };
|
||||
|
||||
/// <summary>
|
||||
/// If this decorator is present, the endpoint is only accessible if the user provided a valid access token (is logged in)
|
||||
/// </summary>
|
||||
/// <param name="permissions">specifies the permissions the user needs to have in order to access this endpoint</param>
|
||||
public AuthorizedAttribute(params string[] permissions) : base(typeof(AuthorizedFilter)) {
|
||||
Arguments = [permissions];
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,7 @@
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
internal static class PermissionValidator {
|
||||
|
||||
/// <summary>
|
||||
/// Checks for the user to have the specified permission<br/>
|
||||
/// Permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
/// <param name="permission">The permission the user needs</param>
|
||||
/// <param name="permissions">All the permissions the user has (includes group permissions)</param>
|
||||
/// <returns></returns>
|
||||
|
||||
public static bool IncludesPermission(string permission, string[] permissions) {
|
||||
if (permission == "*") return true;
|
||||
if (permissions.Contains(permission)) return true;
|
||||
|
||||
@@ -3,7 +3,19 @@ using HopFrame.Database.Models;
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
public interface ITokenContext {
|
||||
|
||||
/// <summary>
|
||||
/// This field specifies that a valid user is accessing the endpoint
|
||||
/// </summary>
|
||||
bool IsAuthenticated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user that is accessing the endpoint
|
||||
/// </summary>
|
||||
User User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The access token the user provided
|
||||
/// </summary>
|
||||
Guid AccessToken { get; }
|
||||
}
|
||||
43
HopFrame.Security/Services/IPermissionService.cs
Normal file
43
HopFrame.Security/Services/IPermissionService.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace HopFrame.Security.Services;
|
||||
|
||||
public interface IPermissionService {
|
||||
|
||||
/// <summary>
|
||||
/// Checks for the user to have the specified permission
|
||||
/// Permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
/// <param name="permission">The permission the user needs</param>
|
||||
/// <returns>rather the user has the permission or not</returns>
|
||||
Task<bool> HasPermission(string permission);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user has all the specified permissions
|
||||
/// </summary>
|
||||
/// <param name="permissions">list of the permissions</param>
|
||||
/// <returns>rather the user has all the permissions or not</returns>
|
||||
Task<bool> HasPermissions(params string[] permissions);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user has any of the specified permissions
|
||||
/// </summary>
|
||||
/// <param name="permissions">list of the permissions</param>
|
||||
/// <returns>rather the user has any permission or not</returns>
|
||||
Task<bool> HasAnyPermission(params string[] permissions);
|
||||
|
||||
/// <summary>
|
||||
/// Checks for the user to have the specified permission
|
||||
/// Permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
/// <param name="permission">The permission the user needs</param>
|
||||
/// <param name="user">The user who gets checked</param>
|
||||
/// <returns>rather the user has the permission or not</returns>
|
||||
Task<bool> HasPermission(string permission, Guid user);
|
||||
}
|
||||
60
HopFrame.Security/Services/PermissionService.cs
Normal file
60
HopFrame.Security/Services/PermissionService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Security.Services;
|
||||
|
||||
internal class PermissionService<TDbContext>(TDbContext context, ITokenContext current) : IPermissionService where TDbContext : HopDbContextBase {
|
||||
public async Task<bool> HasPermission(string permission) {
|
||||
return await HasPermission(permission, current.User.Id);
|
||||
}
|
||||
|
||||
public async Task<bool> HasPermissions(params string[] permissions) {
|
||||
var user = current.User.Id.ToString();
|
||||
var perms = await GetFullPermissions(user);
|
||||
|
||||
foreach (var permission in permissions) {
|
||||
if (!PermissionValidator.IncludesPermission(permission, perms)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> HasAnyPermission(params string[] permissions) {
|
||||
var user = current.User.Id.ToString();
|
||||
var perms = await GetFullPermissions(user);
|
||||
|
||||
foreach (var permission in permissions) {
|
||||
if (PermissionValidator.IncludesPermission(permission, perms)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> HasPermission(string permission, Guid user) {
|
||||
var permissions = await GetFullPermissions(user.ToString());
|
||||
|
||||
return PermissionValidator.IncludesPermission(permission, permissions);
|
||||
}
|
||||
|
||||
private async Task<string[]> GetFullPermissions(string user) {
|
||||
var permissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == user)
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToListAsync();
|
||||
|
||||
var groups = permissions
|
||||
.Where(perm => perm.StartsWith("group."))
|
||||
.ToList();
|
||||
|
||||
var groupPerms = await context.Permissions
|
||||
.Where(perm => groups.Contains(user))
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToListAsync();
|
||||
|
||||
permissions.AddRange(groupPerms);
|
||||
|
||||
return permissions.ToArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user