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>
|
<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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
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>
|
/// <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) {}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
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;
|
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; }
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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, _ => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
@@ -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; }
|
||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
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