Restructured projects and created Services for permissions and users
This commit is contained in:
2
.idea/.idea.HopFrame/.idea/dataSources.xml
generated
2
.idea/.idea.HopFrame/.idea/dataSources.xml
generated
@@ -5,7 +5,7 @@
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<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>
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
</jdbc-additional-properties>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -9,8 +10,8 @@ namespace DatabaseTest.Controllers;
|
||||
public class TestController(ITokenContext userContext) : ControllerBase {
|
||||
|
||||
[HttpGet("permissions"), Authorized]
|
||||
public ActionResult<IList<string>> Permissions() {
|
||||
return new ActionResult<IList<string>>(userContext.User.Permissions);
|
||||
public ActionResult<IList<Permission>> Permissions() {
|
||||
return new ActionResult<IList<Permission>>(userContext.User.Permissions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,12 @@ using HopFrame.Api.Logic;
|
||||
using HopFrame.Api.Models;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Security;
|
||||
using HopFrame.Security.Authentication;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using HopFrame.Security.Models;
|
||||
using HopFrame.Security.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -15,32 +18,32 @@ namespace HopFrame.Api.Controller;
|
||||
|
||||
[ApiController]
|
||||
[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";
|
||||
|
||||
[HttpPut("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)
|
||||
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)));
|
||||
if (hashedPassword != user.Password)
|
||||
if (hashedPassword != await users.GetUserPassword(user))
|
||||
return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct");
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
|
||||
HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
@@ -60,41 +63,23 @@ public class SecurityController<TDbContext>(TDbContext context) : ControllerBase
|
||||
if (register.Password.Length < 8)
|
||||
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");
|
||||
|
||||
var user = new UserEntry {
|
||||
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 user = await users.AddUser(register);
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id
|
||||
UserId = user.Id.ToString()
|
||||
};
|
||||
|
||||
HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
@@ -163,26 +148,14 @@ public class SecurityController<TDbContext>(TDbContext context) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("delete"), Authorized]
|
||||
public async Task<ActionResult> Delete([FromBody] UserLogin login) {
|
||||
var token = HttpContext.User.GetAccessTokenId();
|
||||
var userId = (await context.Tokens.SingleOrDefaultAsync(t => t.Token == token && t.Type == TokenEntry.AccessTokenType))?.UserId;
|
||||
public async Task<ActionResult> Delete([FromBody] UserPasswordValidation validation) {
|
||||
var user = tokenContext.User;
|
||||
|
||||
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)));
|
||||
if (user.Password != password)
|
||||
var password = EncryptionManager.Hash(validation.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
if (await users.GetUserPassword(user) != password)
|
||||
return LogicResult.Forbidden("The provided password is not correct");
|
||||
|
||||
var tokens = await context.Tokens.Where(t => t.UserId == userId).ToArrayAsync();
|
||||
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();
|
||||
await users.DeleteUser(user);
|
||||
|
||||
HttpContext.Response.Cookies.Delete(RefreshTokenType);
|
||||
|
||||
|
||||
5
HopFrame.Api/Models/UserPasswordValidation.cs
Normal file
5
HopFrame.Api/Models/UserPasswordValidation.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace HopFrame.Api.Models;
|
||||
|
||||
public class UserPasswordValidation {
|
||||
public string Password { get; set; }
|
||||
}
|
||||
@@ -6,11 +6,7 @@ namespace HopFrame.Database;
|
||||
/// <summary>
|
||||
/// This class includes the basic database structure in order for HopFrame to work
|
||||
/// </summary>
|
||||
public class HopDbContextBase : DbContext {
|
||||
|
||||
public HopDbContextBase() {}
|
||||
|
||||
public HopDbContextBase(DbContextOptions options) : base(options) {}
|
||||
public abstract class HopDbContextBase : DbContext {
|
||||
|
||||
public virtual DbSet<UserEntry> Users { get; set; }
|
||||
public virtual DbSet<PermissionEntry> Permissions { get; set; }
|
||||
@@ -25,4 +21,12 @@ public class HopDbContextBase : DbContext {
|
||||
modelBuilder.Entity<TokenEntry>();
|
||||
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) {}
|
||||
}
|
||||
@@ -20,10 +20,37 @@ public static class ModelExtensions {
|
||||
|
||||
user.Permissions = contextBase.Permissions
|
||||
.Where(perm => perm.UserId == entry.Id)
|
||||
.Select(perm => perm.PermissionText)
|
||||
.Select(perm => perm.ToPermissionModel())
|
||||
.ToList();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
10
HopFrame.Database/Models/Permission.cs
Normal file
10
HopFrame.Database/Models/Permission.cs
Normal 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 {}
|
||||
9
HopFrame.Database/Models/PermissionGroup.cs
Normal file
9
HopFrame.Database/Models/PermissionGroup.cs
Normal 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; }
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public class User {
|
||||
public Guid Id { get; set; }
|
||||
public sealed class User : IPermissionOwner {
|
||||
public Guid Id { get; init; }
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public IList<string> Permissions { get; set; }
|
||||
public IList<Permission> Permissions { get; set; }
|
||||
}
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -17,7 +18,8 @@ public class HopFrameAuthentication<TDbContext>(
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
TDbContext context)
|
||||
TDbContext context,
|
||||
IPermissionService perms)
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock)
|
||||
where TDbContext : HopDbContextBase {
|
||||
|
||||
@@ -42,22 +44,7 @@ public class HopFrameAuthentication<TDbContext>(
|
||||
new(HopFrameClaimTypes.UserId, tokenEntry.UserId)
|
||||
};
|
||||
|
||||
var permissions = await context.Permissions
|
||||
.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);
|
||||
|
||||
var permissions = await perms.GetFullPermissions(tokenEntry.UserId);
|
||||
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
|
||||
|
||||
var principal = new ClaimsPrincipal();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -20,6 +21,8 @@ public static class HopFrameAuthenticationExtensions {
|
||||
service.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
service.AddScoped<ITokenContext, TokenContextImplementor<TDbContext>>();
|
||||
service.AddScoped<IPermissionService, PermissionService<TDbContext>>();
|
||||
service.AddScoped<IUserService, UserService<TDbContext>>();
|
||||
|
||||
return service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@ 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)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace HopFrame.Api;
|
||||
namespace HopFrame.Security;
|
||||
|
||||
public static class EncryptionManager {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace HopFrame.Api.Models;
|
||||
namespace HopFrame.Security.Models;
|
||||
|
||||
public struct UserRegister {
|
||||
public string Username { get; set; }
|
||||
@@ -1,43 +1,31 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
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);
|
||||
|
||||
Task<PermissionGroup> GetPermissionGroup(string name);
|
||||
|
||||
Task CreatePermissionGroup(string name, bool isDefault = false, string description = null);
|
||||
|
||||
Task DeletePermissionGroup(PermissionGroup group);
|
||||
|
||||
/// <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 DeletePermission(Permission permission);
|
||||
|
||||
internal Task<string[]> GetFullPermissions(string user);
|
||||
|
||||
}
|
||||
27
HopFrame.Security/Services/IUserService.cs
Normal file
27
HopFrame.Security/Services/IUserService.cs
Normal 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);
|
||||
}
|
||||
105
HopFrame.Security/Services/Implementation/PermissionService.cs
Normal file
105
HopFrame.Security/Services/Implementation/PermissionService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
110
HopFrame.Security/Services/Implementation/UserService.cs
Normal file
110
HopFrame.Security/Services/Implementation/UserService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user