Reorganized folder structure
This commit is contained in:
15
src/HopFrame.Security/AdminPermissions.cs
Normal file
15
src/HopFrame.Security/AdminPermissions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace HopFrame.Security;
|
||||
|
||||
public static class AdminPermissions {
|
||||
public const string IsAdmin = "hopframe.admin";
|
||||
|
||||
public const string ViewUsers = "hopframe.admin.users.view";
|
||||
public const string EditUser = "hopframe.admin.users.edit";
|
||||
public const string DeleteUser = "hopframe.admin.users.delete";
|
||||
public const string AddUser = "hopframe.admin.users.add";
|
||||
|
||||
public const string ViewGroups = "hopframe.admin.groups.view";
|
||||
public const string EditGroup = "hopframe.admin.groups.edit";
|
||||
public const string DeleteGroup = "hopframe.admin.groups.delete";
|
||||
public const string AddGroup = "hopframe.admin.groups.add";
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace HopFrame.Security.Authentication;
|
||||
|
||||
public class HopFrameAuthentication<TDbContext>(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
TDbContext context,
|
||||
IPermissionService perms)
|
||||
: 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);
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
|
||||
var accessToken = Request.Cookies[ITokenContext.AccessTokenType];
|
||||
if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Headers[SchemeName];
|
||||
if (string.IsNullOrEmpty(accessToken)) accessToken = Request.Headers["Token"];
|
||||
if (string.IsNullOrEmpty(accessToken)) return AuthenticateResult.Fail("No Access Token provided");
|
||||
|
||||
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))
|
||||
return AuthenticateResult.Fail("The provided Access Token does not match any user");
|
||||
|
||||
var claims = new List<Claim> {
|
||||
new(HopFrameClaimTypes.AccessTokenId, accessToken),
|
||||
new(HopFrameClaimTypes.UserId, tokenEntry.UserId)
|
||||
};
|
||||
|
||||
var permissions = await perms.GetFullPermissions(tokenEntry.UserId);
|
||||
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
|
||||
|
||||
var principal = new ClaimsPrincipal();
|
||||
principal.AddIdentity(new ClaimsIdentity(claims, SchemeName));
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Services;
|
||||
using HopFrame.Security.Services.Implementation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
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 IServiceCollection AddHopFrameAuthentication<TDbContext>(this IServiceCollection service) where TDbContext : HopDbContextBase {
|
||||
service.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
service.AddScoped<ITokenContext, TokenContextImplementor<TDbContext>>();
|
||||
service.AddScoped<IPermissionService, PermissionService<TDbContext>>();
|
||||
service.AddScoped<IUserService, UserService<TDbContext>>();
|
||||
|
||||
service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {});
|
||||
service.AddAuthorization();
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
}
|
||||
19
src/HopFrame.Security/Authorization/AuthorizedAttribute.cs
Normal file
19
src/HopFrame.Security/Authorization/AuthorizedAttribute.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
public class AuthorizedAttribute : TypeFilterAttribute {
|
||||
|
||||
/// <summary>
|
||||
/// If this decorator is present, the endpoint is only accessible if the user provided a valid access token (is logged in)
|
||||
/// 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="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];
|
||||
}
|
||||
}
|
||||
32
src/HopFrame.Security/Authorization/AuthorizedFilter.cs
Normal file
32
src/HopFrame.Security/Authorization/AuthorizedFilter.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
public class AuthorizedFilter : IAuthorizationFilter {
|
||||
private readonly string[] _permissions;
|
||||
|
||||
public AuthorizedFilter(params string[] permissions) {
|
||||
_permissions = permissions;
|
||||
}
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context) {
|
||||
if (context.Filters.Any(item => item is IAllowAnonymousFilter)) return;
|
||||
|
||||
if (string.IsNullOrEmpty(context.HttpContext.User.GetAccessTokenId())) {
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_permissions.Length == 0) return;
|
||||
|
||||
var permissions = context.HttpContext.User.GetPermissions();
|
||||
|
||||
if (!_permissions.All(permission => PermissionValidator.IncludesPermission(permission, permissions))) {
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/HopFrame.Security/Authorization/PermissionValidator.cs
Normal file
25
src/HopFrame.Security/Authorization/PermissionValidator.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
public static class PermissionValidator {
|
||||
|
||||
public static bool IncludesPermission(string permission, string[] permissions) {
|
||||
var permLow = permission.ToLower();
|
||||
var permsLow = permissions.Select(perm => perm.ToLower()).ToArray();
|
||||
|
||||
if (permsLow.Any(perm =>
|
||||
perm == permLow ||
|
||||
(perm.Length > permLow.Length && perm.StartsWith(permLow) && perm.ToCharArray()[permLow.Length] == '.') ||
|
||||
perm == "*"))
|
||||
return true;
|
||||
|
||||
foreach (var perm in permsLow) {
|
||||
if (!perm.EndsWith(".*")) continue;
|
||||
|
||||
var permissionGroup = perm.Substring(0, perm.Length - 1);
|
||||
if (permLow.StartsWith(permissionGroup)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
21
src/HopFrame.Security/Claims/HopFrameClaimTypes.cs
Normal file
21
src/HopFrame.Security/Claims/HopFrameClaimTypes.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
public static class HopFrameClaimTypes {
|
||||
public const string AccessTokenId = "HopFrame.AccessTokenId";
|
||||
public const string UserId = "HopFrame.UserId";
|
||||
public const string Permission = "HopFrame.Permission";
|
||||
|
||||
public static string GetAccessTokenId(this ClaimsPrincipal principal) {
|
||||
return principal.FindFirstValue(AccessTokenId);
|
||||
}
|
||||
|
||||
public static string GetUserId(this ClaimsPrincipal principal) {
|
||||
return principal.FindFirstValue(UserId);
|
||||
}
|
||||
|
||||
public static string[] GetPermissions(this ClaimsPrincipal principal) {
|
||||
return principal.FindAll(Permission).Select(claim => claim.Value).ToArray();
|
||||
}
|
||||
}
|
||||
24
src/HopFrame.Security/Claims/ITokenContext.cs
Normal file
24
src/HopFrame.Security/Claims/ITokenContext.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
public interface ITokenContext {
|
||||
|
||||
public const string RefreshTokenType = "HopFrame.Security.RefreshToken";
|
||||
public const string AccessTokenType = "HopFrame.Security.AccessToken";
|
||||
|
||||
/// <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; }
|
||||
}
|
||||
15
src/HopFrame.Security/Claims/TokenContextImplementor.cs
Normal file
15
src/HopFrame.Security/Claims/TokenContextImplementor.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
internal sealed class TokenContextImplementor<TDbContext>(IHttpContextAccessor accessor, TDbContext context) : ITokenContext where TDbContext : HopDbContextBase {
|
||||
public bool IsAuthenticated => !string.IsNullOrEmpty(accessor.HttpContext?.User.GetAccessTokenId());
|
||||
|
||||
public User User => context.Users
|
||||
.SingleOrDefault(user => user.Id == accessor.HttpContext.User.GetUserId())?
|
||||
.ToUserModel(context);
|
||||
|
||||
public Guid AccessToken => Guid.Parse(accessor.HttpContext?.User.GetAccessTokenId() ?? Guid.Empty.ToString());
|
||||
}
|
||||
24
src/HopFrame.Security/EncryptionManager.cs
Normal file
24
src/HopFrame.Security/EncryptionManager.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace HopFrame.Security;
|
||||
|
||||
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,
|
||||
salt: salt,
|
||||
prf: method,
|
||||
iterationCount: 100000,
|
||||
numBytesRequested: 256 / 8
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
26
src/HopFrame.Security/HopFrame.Security.csproj
Normal file
26
src/HopFrame.Security/HopFrame.Security.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<RootNamespace>HopFrame.Security</RootNamespace>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HopFrame.Database\HopFrame.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="\"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
src/HopFrame.Security/Models/UserLogin.cs
Normal file
6
src/HopFrame.Security/Models/UserLogin.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace HopFrame.Security.Models;
|
||||
|
||||
public class UserLogin {
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
7
src/HopFrame.Security/Models/UserRegister.cs
Normal file
7
src/HopFrame.Security/Models/UserRegister.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace HopFrame.Security.Models;
|
||||
|
||||
public class UserRegister {
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
2
src/HopFrame.Security/README.md
Normal file
2
src/HopFrame.Security/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# HopFrame Security module
|
||||
this module contains all handlers for the login and register validation. It also checks the user permissions.
|
||||
48
src/HopFrame.Security/Services/IPermissionService.cs
Normal file
48
src/HopFrame.Security/Services/IPermissionService.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Security.Services;
|
||||
|
||||
/// <summary>
|
||||
/// permission system:<br/>
|
||||
/// - "*" -> all rights<br/>
|
||||
/// - "group.[name]" -> group member<br/>
|
||||
/// - "[namespace].[name]" -> single permission<br/>
|
||||
/// - "[namespace].*" -> all permissions in the namespace
|
||||
/// </summary>
|
||||
public interface IPermissionService {
|
||||
|
||||
Task<bool> HasPermission(string permission, Guid user);
|
||||
|
||||
Task<IList<PermissionGroup>> GetPermissionGroups();
|
||||
|
||||
Task<PermissionGroup> GetPermissionGroup(string name);
|
||||
|
||||
Task EditPermissionGroup(PermissionGroup group);
|
||||
|
||||
Task<IList<PermissionGroup>> GetUserPermissionGroups(User user);
|
||||
|
||||
Task RemoveGroupFromUser(User user, PermissionGroup group);
|
||||
|
||||
Task<PermissionGroup> CreatePermissionGroup(string name, bool isDefault = false, string description = null);
|
||||
|
||||
Task DeletePermissionGroup(PermissionGroup group);
|
||||
|
||||
Task<Permission> GetPermission(string name, IPermissionOwner owner);
|
||||
|
||||
/// <summary>
|
||||
/// 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="owner"></param>
|
||||
/// <param name="permission"></param>
|
||||
/// <returns></returns>
|
||||
Task AddPermission(IPermissionOwner owner, string permission);
|
||||
|
||||
Task RemovePermission(Permission permission);
|
||||
|
||||
Task<string[]> GetFullPermissions(string user);
|
||||
|
||||
}
|
||||
29
src/HopFrame.Security/Services/IUserService.cs
Normal file
29
src/HopFrame.Security/Services/IUserService.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Security.Models;
|
||||
|
||||
namespace HopFrame.Security.Services;
|
||||
|
||||
public interface IUserService {
|
||||
Task<IList<User>> GetUsers();
|
||||
|
||||
Task<User> GetUser(Guid userId);
|
||||
|
||||
Task<User> GetUserByEmail(string email);
|
||||
|
||||
Task<User> GetUserByUsername(string username);
|
||||
|
||||
Task<User> AddUser(UserRegister user);
|
||||
|
||||
/// <summary>
|
||||
/// IMPORTANT:<br/>
|
||||
/// This function does not add or remove any permissions to the user.
|
||||
/// For that please use <see cref="IPermissionService"/>
|
||||
/// </summary>
|
||||
Task UpdateUser(User user);
|
||||
|
||||
Task DeleteUser(User user);
|
||||
|
||||
Task<bool> CheckUserPassword(User user, string password);
|
||||
|
||||
Task ChangePassword(User user, string password);
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Security.Services.Implementation;
|
||||
|
||||
internal sealed 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);
|
||||
}
|
||||
|
||||
public async Task<IList<PermissionGroup>> GetPermissionGroups() {
|
||||
return await context.Groups
|
||||
.Select(group => group.ToPermissionGroup(context))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<PermissionGroup> GetPermissionGroup(string name) {
|
||||
return context.Groups
|
||||
.Where(group => group.Name == name)
|
||||
.Select(group => group.ToPermissionGroup(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task EditPermissionGroup(PermissionGroup group) {
|
||||
var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name);
|
||||
|
||||
if (orig is null) return;
|
||||
|
||||
var entity = context.Groups.Update(orig);
|
||||
|
||||
entity.Entity.Default = group.IsDefaultGroup;
|
||||
entity.Entity.Description = group.Description;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<PermissionGroup>> GetUserPermissionGroups(User user) {
|
||||
var groups = await context.Groups.ToListAsync();
|
||||
var perms = await GetFullPermissions(user.Id.ToString());
|
||||
|
||||
return groups
|
||||
.Where(group => perms.Contains(group.Name))
|
||||
.Select(group => group.ToPermissionGroup(context))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task RemoveGroupFromUser(User user, PermissionGroup group) {
|
||||
var entry = await context.Permissions
|
||||
.Where(perm => perm.PermissionText == group.Name && perm.UserId == user.Id.ToString())
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
context.Permissions.Remove(entry);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<PermissionGroup> CreatePermissionGroup(string name, bool isDefault = false, string description = null) {
|
||||
var group = new GroupEntry {
|
||||
Name = name,
|
||||
Description = description,
|
||||
Default = isDefault,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
|
||||
await context.Groups.AddAsync(group);
|
||||
|
||||
if (isDefault) {
|
||||
var users = await context.Users.ToListAsync();
|
||||
|
||||
foreach (var user in users) {
|
||||
await context.Permissions.AddAsync(new PermissionEntry {
|
||||
GrantedAt = DateTime.Now,
|
||||
PermissionText = group.Name,
|
||||
UserId = user.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return group.ToPermissionGroup(context);
|
||||
}
|
||||
|
||||
public async Task DeletePermissionGroup(PermissionGroup group) {
|
||||
var entry = await context.Groups.SingleOrDefaultAsync(entry => entry.Name == group.Name);
|
||||
context.Groups.Remove(entry);
|
||||
|
||||
var permissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == group.Name || perm.PermissionText == group.Name)
|
||||
.ToListAsync();
|
||||
|
||||
if (permissions.Count > 0) {
|
||||
context.Permissions.RemoveRange(permissions);
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<Permission> GetPermission(string name, IPermissionOwner owner) {
|
||||
var ownerId = (owner is User user) ? user.Id.ToString() : ((PermissionGroup)owner).Name;
|
||||
|
||||
return await context.Permissions
|
||||
.Where(perm => perm.PermissionText == name && perm.UserId == ownerId)
|
||||
.Select(perm => perm.ToPermissionModel())
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task AddPermission(IPermissionOwner owner, string permission) {
|
||||
var userId = owner is User user ? user.Id.ToString() : (owner as PermissionGroup)?.Name;
|
||||
|
||||
await context.Permissions.AddAsync(new PermissionEntry {
|
||||
UserId = userId,
|
||||
PermissionText = permission,
|
||||
GrantedAt = DateTime.Now
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemovePermission(Permission permission) {
|
||||
var entry = await context.Permissions.SingleOrDefaultAsync(entry => entry.RecordId == permission.Id);
|
||||
context.Permissions.Remove(entry);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public 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 = new List<string>();
|
||||
foreach (var group in groups) {
|
||||
var perms = await GetFullPermissions(group);
|
||||
groupPerms.AddRange(perms);
|
||||
}
|
||||
|
||||
permissions.AddRange(groupPerms);
|
||||
|
||||
return permissions.ToArray();
|
||||
}
|
||||
}
|
||||
128
src/HopFrame.Security/Services/Implementation/UserService.cs
Normal file
128
src/HopFrame.Security/Services/Implementation/UserService.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Security.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Security.Services.Implementation;
|
||||
|
||||
internal sealed class UserService<TDbContext>(TDbContext context) : IUserService where TDbContext : HopDbContextBase {
|
||||
public async Task<IList<User>> GetUsers() {
|
||||
return await context.Users
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<User> GetUser(Guid userId) {
|
||||
var id = userId.ToString();
|
||||
|
||||
return context.Users
|
||||
.Where(user => user.Id == id)
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public Task<User> GetUserByEmail(string email) {
|
||||
return context.Users
|
||||
.Where(user => user.Email == email)
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public Task<User> GetUserByUsername(string username) {
|
||||
return context.Users
|
||||
.Where(user => user.Username == username)
|
||||
.Select(user => user.ToUserModel(context))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<User> AddUser(UserRegister user) {
|
||||
if (await GetUserByEmail(user.Email) is not null) return null;
|
||||
if (await GetUserByUsername(user.Username) is not null) return null;
|
||||
|
||||
var entry = new UserEntry {
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Email = user.Email,
|
||||
Username = user.Username,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
entry.Password = EncryptionManager.Hash(user.Password, Encoding.Default.GetBytes(entry.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
await context.Users.AddAsync(entry);
|
||||
|
||||
var defaultGroups = await context.Groups
|
||||
.Where(group => group.Default)
|
||||
.Select(group => "group." + group.Name)
|
||||
.ToListAsync();
|
||||
|
||||
await context.Permissions.AddRangeAsync(defaultGroups.Select(group => new PermissionEntry {
|
||||
GrantedAt = DateTime.Now,
|
||||
PermissionText = group,
|
||||
UserId = entry.Id
|
||||
}));
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
return entry.ToUserModel(context);
|
||||
}
|
||||
|
||||
public async Task UpdateUser(User user) {
|
||||
var id = user.Id.ToString();
|
||||
var entry = await context.Users
|
||||
.SingleOrDefaultAsync(entry => entry.Id == id);
|
||||
if (entry is null) return;
|
||||
|
||||
entry.Email = user.Email;
|
||||
entry.Username = user.Username;
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteUser(User user) {
|
||||
var id = user.Id.ToString();
|
||||
var entry = await context.Users
|
||||
.SingleOrDefaultAsync(entry => entry.Id == id);
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
context.Users.Remove(entry);
|
||||
|
||||
var userTokens = await context.Tokens
|
||||
.Where(token => token.UserId == id)
|
||||
.ToArrayAsync();
|
||||
context.Tokens.RemoveRange(userTokens);
|
||||
|
||||
var userPermissions = await context.Permissions
|
||||
.Where(perm => perm.UserId == id)
|
||||
.ToArrayAsync();
|
||||
context.Permissions.RemoveRange(userPermissions);
|
||||
|
||||
context.OnUserDelete(entry);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> CheckUserPassword(User user, string password) {
|
||||
var id = user.Id.ToString();
|
||||
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var entry = await context.Users
|
||||
.Where(entry => entry.Id == id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
return entry.Password == hash;
|
||||
}
|
||||
|
||||
public async Task ChangePassword(User user, string password) {
|
||||
var entry = await context.Users
|
||||
.Where(entry => entry.Id == user.Id.ToString())
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (entry is null) return;
|
||||
|
||||
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
entry.Password = hash;
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user