Restructured projects and created Services for permissions and users

This commit is contained in:
2024-07-14 12:17:49 +02:00
parent 54ec3b4f52
commit 01978d30ce
19 changed files with 363 additions and 169 deletions

View File

@@ -5,7 +5,7 @@
<driver-ref>sqlite.xerial</driver-ref> <driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver> <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:C:\Users\Remote\Documents\Projekte\HopFrame\DatabaseTest\test.db</jdbc-url> <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/DatabaseTest/bin/Debug/net8.0/test.db</jdbc-url>
<jdbc-additional-properties> <jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" /> <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties> </jdbc-additional-properties>

View File

@@ -1,3 +1,4 @@
using HopFrame.Database.Models;
using HopFrame.Security.Authorization; using HopFrame.Security.Authorization;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -9,8 +10,8 @@ namespace DatabaseTest.Controllers;
public class TestController(ITokenContext userContext) : ControllerBase { public class TestController(ITokenContext userContext) : ControllerBase {
[HttpGet("permissions"), Authorized] [HttpGet("permissions"), Authorized]
public ActionResult<IList<string>> Permissions() { public ActionResult<IList<Permission>> Permissions() {
return new ActionResult<IList<string>>(userContext.User.Permissions); return new ActionResult<IList<Permission>>(userContext.User.Permissions);
} }
} }

View File

@@ -4,9 +4,12 @@ using HopFrame.Api.Logic;
using HopFrame.Api.Models; using HopFrame.Api.Models;
using HopFrame.Database; using HopFrame.Database;
using HopFrame.Database.Models.Entries; using HopFrame.Database.Models.Entries;
using HopFrame.Security;
using HopFrame.Security.Authentication; using HopFrame.Security.Authentication;
using HopFrame.Security.Authorization; using HopFrame.Security.Authorization;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using HopFrame.Security.Models;
using HopFrame.Security.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -15,32 +18,32 @@ namespace HopFrame.Api.Controller;
[ApiController] [ApiController]
[Route("authentication")] [Route("authentication")]
public class SecurityController<TDbContext>(TDbContext context) : ControllerBase where TDbContext : HopDbContextBase { public class SecurityController<TDbContext>(TDbContext context, IUserService users, ITokenContext tokenContext) : ControllerBase where TDbContext : HopDbContextBase {
private const string RefreshTokenType = "HopFrame.Security.RefreshToken"; private const string RefreshTokenType = "HopFrame.Security.RefreshToken";
[HttpPut("login")] [HttpPut("login")]
public async Task<ActionResult<SingleValueResult<string>>> Login([FromBody] UserLogin login) { public async Task<ActionResult<SingleValueResult<string>>> Login([FromBody] UserLogin login) {
var user = await context.Users.SingleOrDefaultAsync(user => user.Email == login.Email); var user = await users.GetUserByEmail(login.Email);
if (user is null) if (user is null)
return LogicResult<SingleValueResult<string>>.NotFound("The provided email address was not found"); return LogicResult<SingleValueResult<string>>.NotFound("The provided email address was not found");
var hashedPassword = EncryptionManager.Hash(login.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture))); var hashedPassword = EncryptionManager.Hash(login.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
if (hashedPassword != user.Password) if (hashedPassword != await users.GetUserPassword(user))
return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct"); return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct");
var refreshToken = new TokenEntry { var refreshToken = new TokenEntry {
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Token = Guid.NewGuid().ToString(), Token = Guid.NewGuid().ToString(),
Type = TokenEntry.RefreshTokenType, Type = TokenEntry.RefreshTokenType,
UserId = user.Id UserId = user.Id.ToString()
}; };
var accessToken = new TokenEntry { var accessToken = new TokenEntry {
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Token = Guid.NewGuid().ToString(), Token = Guid.NewGuid().ToString(),
Type = TokenEntry.AccessTokenType, Type = TokenEntry.AccessTokenType,
UserId = user.Id UserId = user.Id.ToString()
}; };
HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions { HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions {
@@ -59,42 +62,24 @@ public class SecurityController<TDbContext>(TDbContext context) : ControllerBase
public async Task<ActionResult<SingleValueResult<string>>> Register([FromBody] UserRegister register) { public async Task<ActionResult<SingleValueResult<string>>> Register([FromBody] UserRegister register) {
if (register.Password.Length < 8) if (register.Password.Length < 8)
return LogicResult<SingleValueResult<string>>.Conflict("Password needs to be at least 8 characters long"); return LogicResult<SingleValueResult<string>>.Conflict("Password needs to be at least 8 characters long");
if (await context.Users.AnyAsync(user => user.Username == register.Username || user.Email == register.Email)) var allUsers = await users.GetUsers();
if (allUsers.Any(user => user.Username == register.Username || user.Email == register.Email))
return LogicResult<SingleValueResult<string>>.Conflict("Username or Email is already registered"); return LogicResult<SingleValueResult<string>>.Conflict("Username or Email is already registered");
var user = new UserEntry { var user = await users.AddUser(register);
CreatedAt = DateTime.Now,
Email = register.Email,
Username = register.Username,
Id = Guid.NewGuid().ToString()
};
user.Password = EncryptionManager.Hash(register.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
await context.Users.AddAsync(user);
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 = user.Id
}));
var refreshToken = new TokenEntry { var refreshToken = new TokenEntry {
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Token = Guid.NewGuid().ToString(), Token = Guid.NewGuid().ToString(),
Type = TokenEntry.RefreshTokenType, Type = TokenEntry.RefreshTokenType,
UserId = user.Id UserId = user.Id.ToString()
}; };
var accessToken = new TokenEntry { var accessToken = new TokenEntry {
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
Token = Guid.NewGuid().ToString(), Token = Guid.NewGuid().ToString(),
Type = TokenEntry.AccessTokenType, Type = TokenEntry.AccessTokenType,
UserId = user.Id UserId = user.Id.ToString()
}; };
HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions { HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions {
@@ -163,26 +148,14 @@ public class SecurityController<TDbContext>(TDbContext context) : ControllerBase
} }
[HttpDelete("delete"), Authorized] [HttpDelete("delete"), Authorized]
public async Task<ActionResult> Delete([FromBody] UserLogin login) { public async Task<ActionResult> Delete([FromBody] UserPasswordValidation validation) {
var token = HttpContext.User.GetAccessTokenId(); var user = tokenContext.User;
var userId = (await context.Tokens.SingleOrDefaultAsync(t => t.Token == token && t.Type == TokenEntry.AccessTokenType))?.UserId;
if (string.IsNullOrEmpty(userId))
return LogicResult.NotFound("Access token does not match any user");
var user = await context.Users.SingleAsync(user => user.Id == userId);
var password = EncryptionManager.Hash(login.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture))); var password = EncryptionManager.Hash(validation.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
if (user.Password != password) if (await users.GetUserPassword(user) != password)
return LogicResult.Forbidden("The provided password is not correct"); return LogicResult.Forbidden("The provided password is not correct");
var tokens = await context.Tokens.Where(t => t.UserId == userId).ToArrayAsync(); await users.DeleteUser(user);
var permissions = await context.Permissions.Where(perm => perm.UserId == userId).ToArrayAsync();
context.Tokens.RemoveRange(tokens);
context.Permissions.RemoveRange(permissions);
context.Users.Remove(user);
await context.SaveChangesAsync();
HttpContext.Response.Cookies.Delete(RefreshTokenType); HttpContext.Response.Cookies.Delete(RefreshTokenType);

View File

@@ -0,0 +1,5 @@
namespace HopFrame.Api.Models;
public class UserPasswordValidation {
public string Password { get; set; }
}

View File

@@ -6,11 +6,7 @@ namespace HopFrame.Database;
/// <summary> /// <summary>
/// This class includes the basic database structure in order for HopFrame to work /// This class includes the basic database structure in order for HopFrame to work
/// </summary> /// </summary>
public class HopDbContextBase : DbContext { public abstract class HopDbContextBase : DbContext {
public HopDbContextBase() {}
public HopDbContextBase(DbContextOptions options) : base(options) {}
public virtual DbSet<UserEntry> Users { get; set; } public virtual DbSet<UserEntry> Users { get; set; }
public virtual DbSet<PermissionEntry> Permissions { get; set; } public virtual DbSet<PermissionEntry> Permissions { get; set; }
@@ -25,4 +21,12 @@ public class HopDbContextBase : DbContext {
modelBuilder.Entity<TokenEntry>(); modelBuilder.Entity<TokenEntry>();
modelBuilder.Entity<GroupEntry>(); modelBuilder.Entity<GroupEntry>();
} }
/// <summary>
/// Gets executed when a user is deleted through the IUserService from the
/// HopFrame.Security package. You can override this method to also delete
/// related user specific entries in the database
/// </summary>
/// <param name="user"></param>
public virtual void OnUserDelete(UserEntry user) {}
} }

View File

@@ -20,10 +20,37 @@ public static class ModelExtensions {
user.Permissions = contextBase.Permissions user.Permissions = contextBase.Permissions
.Where(perm => perm.UserId == entry.Id) .Where(perm => perm.UserId == entry.Id)
.Select(perm => perm.PermissionText) .Select(perm => perm.ToPermissionModel())
.ToList(); .ToList();
return user; return user;
} }
public static Permission ToPermissionModel(this PermissionEntry entry) {
Guid.TryParse(entry.UserId, out var userId);
return new Permission {
Owner = userId,
PermissionName = entry.PermissionText,
GrantedAt = entry.GrantedAt,
Id = entry.RecordId
};
}
public static PermissionGroup ToPermissionGroup(this GroupEntry entry, HopDbContextBase contextBase) {
var group = new PermissionGroup {
Name = entry.Name,
IsDefaultGroup = entry.Default,
Description = entry.Description,
CreatedAt = entry.CreatedAt
};
group.Permissions = contextBase.Permissions
.Where(perm => perm.UserId == group.Name)
.Select(perm => perm.ToPermissionModel())
.ToList();
return group;
}
} }

View File

@@ -0,0 +1,10 @@
namespace HopFrame.Database.Models;
public sealed class Permission {
public long Id { get; init; }
public string PermissionName { get; set; }
public Guid Owner { get; set; }
public DateTime GrantedAt { get; set; }
}
public interface IPermissionOwner {}

View File

@@ -0,0 +1,9 @@
namespace HopFrame.Database.Models;
public sealed class PermissionGroup : IPermissionOwner {
public string Name { get; init; }
public bool IsDefaultGroup { get; set; }
public string Description { get; set; }
public DateTime CreatedAt { get; set; }
public IList<Permission> Permissions { get; set; }
}

View File

@@ -1,9 +1,9 @@
namespace HopFrame.Database.Models; namespace HopFrame.Database.Models;
public class User { public sealed class User : IPermissionOwner {
public Guid Id { get; set; } public Guid Id { get; init; }
public string Username { get; set; } public string Username { get; set; }
public string Email { get; set; } public string Email { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public IList<string> Permissions { get; set; } public IList<Permission> Permissions { get; set; }
} }

View File

@@ -2,6 +2,7 @@ using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using HopFrame.Database; using HopFrame.Database;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using HopFrame.Security.Services;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -17,7 +18,8 @@ public class HopFrameAuthentication<TDbContext>(
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder, UrlEncoder encoder,
ISystemClock clock, ISystemClock clock,
TDbContext context) TDbContext context,
IPermissionService perms)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock) : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock)
where TDbContext : HopDbContextBase { where TDbContext : HopDbContextBase {
@@ -42,22 +44,7 @@ public class HopFrameAuthentication<TDbContext>(
new(HopFrameClaimTypes.UserId, tokenEntry.UserId) new(HopFrameClaimTypes.UserId, tokenEntry.UserId)
}; };
var permissions = await context.Permissions var permissions = await perms.GetFullPermissions(tokenEntry.UserId);
.Where(perm => perm.UserId == tokenEntry.UserId)
.Select(perm => perm.PermissionText)
.ToListAsync();
var groups = permissions
.Where(perm => perm.StartsWith("group."))
.ToList();
var groupPerms = await context.Permissions
.Where(perm => groups.Contains(perm.UserId))
.Select(perm => perm.PermissionText)
.ToListAsync();
permissions.AddRange(groupPerms);
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
var principal = new ClaimsPrincipal(); var principal = new ClaimsPrincipal();

View File

@@ -1,6 +1,7 @@
using HopFrame.Database; using HopFrame.Database;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using HopFrame.Security.Services; using HopFrame.Security.Services;
using HopFrame.Security.Services.Implementation;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -20,6 +21,8 @@ public static class HopFrameAuthenticationExtensions {
service.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); service.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
service.AddScoped<ITokenContext, TokenContextImplementor<TDbContext>>(); service.AddScoped<ITokenContext, TokenContextImplementor<TDbContext>>();
service.AddScoped<IPermissionService, PermissionService<TDbContext>>(); service.AddScoped<IPermissionService, PermissionService<TDbContext>>();
service.AddScoped<IUserService, UserService<TDbContext>>();
return service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {}); return service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {});
} }

View File

@@ -6,6 +6,11 @@ public class AuthorizedAttribute : TypeFilterAttribute {
/// <summary> /// <summary>
/// If this decorator is present, the endpoint is only accessible if the user provided a valid access token (is logged in) /// 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> /// </summary>
/// <param name="permissions">specifies the permissions the user needs to have in order to access this endpoint</param> /// <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)) { public AuthorizedAttribute(params string[] permissions) : base(typeof(AuthorizedFilter)) {

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Cryptography.KeyDerivation; using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace HopFrame.Api; namespace HopFrame.Security;
public static class EncryptionManager { public static class EncryptionManager {

View File

@@ -1,4 +1,4 @@
namespace HopFrame.Api.Models; namespace HopFrame.Security.Models;
public struct UserRegister { public struct UserRegister {
public string Username { get; set; } public string Username { get; set; }

View File

@@ -1,43 +1,31 @@
using HopFrame.Database.Models;
namespace HopFrame.Security.Services; namespace HopFrame.Security.Services;
public interface IPermissionService { public interface IPermissionService {
/// <summary> Task<bool> HasPermission(string permission, Guid user);
/// Checks for the user to have the specified permission
/// Permission system:<br/> Task<PermissionGroup> GetPermissionGroup(string name);
/// - "*" -> all rights<br/>
/// - "group.[name]" -> group member<br/> Task CreatePermissionGroup(string name, bool isDefault = false, string description = null);
/// - "[namespace].[name]" -> single permission<br/>
/// - "[namespace].*" -> all permissions in the namespace Task DeletePermissionGroup(PermissionGroup group);
/// </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> /// <summary>
/// Checks for the user to have the specified permission /// permission system:<br/>
/// Permission system:<br/>
/// - "*" -> all rights<br/> /// - "*" -> all rights<br/>
/// - "group.[name]" -> group member<br/> /// - "group.[name]" -> group member<br/>
/// - "[namespace].[name]" -> single permission<br/> /// - "[namespace].[name]" -> single permission<br/>
/// - "[namespace].*" -> all permissions in the namespace /// - "[namespace].*" -> all permissions in the namespace
/// </summary> /// </summary>
/// <param name="permission">The permission the user needs</param> /// <param name="owner"></param>
/// <param name="user">The user who gets checked</param> /// <param name="permission"></param>
/// <returns>rather the user has the permission or not</returns> /// <returns></returns>
Task<bool> HasPermission(string permission, Guid user); Task AddPermission(IPermissionOwner owner, string permission);
Task DeletePermission(Permission permission);
internal Task<string[]> GetFullPermissions(string user);
} }

View File

@@ -0,0 +1,27 @@
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<string> GetUserPassword(User user);
}

View File

@@ -0,0 +1,105 @@
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 Task<PermissionGroup> GetPermissionGroup(string name) {
return context.Groups
.Where(group => group.Name == name)
.Select(group => group.ToPermissionGroup(context))
.SingleOrDefaultAsync();
}
public async Task 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);
await context.SaveChangesAsync();
}
public async Task DeletePermissionGroup(PermissionGroup group) {
var entry = await context.Groups.SingleOrDefaultAsync(entry => entry.Name == group.Name);
context.Groups.Remove(entry);
await context.SaveChangesAsync();
}
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 DeletePermission(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();
}
}

View File

@@ -0,0 +1,110 @@
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) {
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 Task<string> GetUserPassword(User user) {
var id = user.Id.ToString();
return context.Users
.Where(entry => entry.Id == id)
.Select(entry => entry.Password)
.SingleOrDefaultAsync();
}
}

View File

@@ -1,60 +0,0 @@
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();
}
}