diff --git a/.idea/.idea.HopFrame/.idea/discord.xml b/.idea/.idea.HopFrame/.idea/discord.xml new file mode 100644 index 0000000..912db82 --- /dev/null +++ b/.idea/.idea.HopFrame/.idea/discord.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index 86d9f80..eab4e1d 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -70,6 +70,7 @@ + \ No newline at end of file diff --git a/src/HopFrame.Database/Repositories/ITokenRepository.cs b/src/HopFrame.Database/Repositories/ITokenRepository.cs index 5f66769..2c1192c 100644 --- a/src/HopFrame.Database/Repositories/ITokenRepository.cs +++ b/src/HopFrame.Database/Repositories/ITokenRepository.cs @@ -6,5 +6,6 @@ public interface ITokenRepository { Task GetToken(string content); Task CreateToken(int type, User owner); Task DeleteUserTokens(User owner); + Task DeleteToken(Token token); Task CreateApiToken(User owner, DateTime expirationDate); } \ No newline at end of file diff --git a/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs b/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs index f80b0b8..6d55bc0 100644 --- a/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs +++ b/src/HopFrame.Database/Repositories/Implementation/PermissionRepository.cs @@ -69,6 +69,10 @@ internal sealed class PermissionRepository(TDbContext context, IGrou public async Task> GetFullPermissions(IPermissionOwner owner) { var permissions = new List(); + + if (owner is Token token && token.Type != Token.ApiTokenType) { + owner = token.Owner; + } if (owner is User user) { var perms = await context.Permissions @@ -86,11 +90,11 @@ internal sealed class PermissionRepository(TDbContext context, IGrou .ToListAsync(); permissions.AddRange(perms.Select(p => p.PermissionName)); - }else if (owner is Token token) { + }else if (owner is Token apiToken) { var perms = await context.Permissions .Include(p => p.Token) .Where(p => p.Token != null) - .Where(p =>p.Token.TokenId == token.TokenId) + .Where(p =>p.Token.TokenId == apiToken.TokenId) .ToListAsync(); permissions.AddRange(perms.Select(p => p.PermissionName)); diff --git a/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs b/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs index 927d080..b44dc43 100644 --- a/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs +++ b/src/HopFrame.Database/Repositories/Implementation/TokenRepository.cs @@ -39,6 +39,11 @@ internal sealed class TokenRepository(TDbContext context) : ITokenRe await context.SaveChangesAsync(); } + public async Task DeleteToken(Token token) { + context.Tokens.Remove(token); + await context.SaveChangesAsync(); + } + public async Task CreateApiToken(User owner, DateTime expirationDate) { var token = new Token { CreatedAt = expirationDate, diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs index 9f9af47..88a95c1 100644 --- a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs +++ b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs @@ -47,15 +47,7 @@ public class HopFrameAuthentication( new(HopFrameClaimTypes.UserId, tokenEntry.Owner.Id.ToString()) }; - IList permissions; - - if (tokenEntry.Type == Token.ApiTokenType) { - permissions = await perms.GetFullPermissions(tokenEntry); - } - else { - permissions = await perms.GetFullPermissions(tokenEntry.Owner); - } - + var permissions = await perms.GetFullPermissions(tokenEntry); claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); var principal = new ClaimsPrincipal(); diff --git a/src/HopFrame.Security/Claims/ITokenContext.cs b/src/HopFrame.Security/Claims/ITokenContext.cs index 6b5a590..6b052bc 100644 --- a/src/HopFrame.Security/Claims/ITokenContext.cs +++ b/src/HopFrame.Security/Claims/ITokenContext.cs @@ -21,4 +21,6 @@ public interface ITokenContext { /// The access token the user provided /// Token AccessToken { get; } + + IList ContextualPermissions { get; } } \ No newline at end of file diff --git a/src/HopFrame.Security/Claims/TokenContextImplementor.cs b/src/HopFrame.Security/Claims/TokenContextImplementor.cs index dd50a08..47fce76 100644 --- a/src/HopFrame.Security/Claims/TokenContextImplementor.cs +++ b/src/HopFrame.Security/Claims/TokenContextImplementor.cs @@ -4,10 +4,12 @@ using Microsoft.AspNetCore.Http; namespace HopFrame.Security.Claims; -internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, IUserRepository users, ITokenRepository tokens) : ITokenContext { +internal sealed class TokenContextImplementor(IHttpContextAccessor accessor, IUserRepository users, ITokenRepository tokens, IPermissionRepository permissions) : ITokenContext { public bool IsAuthenticated => !string.IsNullOrEmpty(accessor.HttpContext?.User.GetAccessTokenId()); public User User => users.GetUser(Guid.Parse(accessor.HttpContext?.User.GetUserId() ?? Guid.Empty.ToString())).GetAwaiter().GetResult(); public Token AccessToken => tokens.GetToken(accessor.HttpContext?.User.GetAccessTokenId()).GetAwaiter().GetResult(); + + public IList ContextualPermissions => permissions.GetFullPermissions(AccessToken).GetAwaiter().GetResult(); } \ No newline at end of file diff --git a/src/HopFrame.Web/AuthMiddleware.cs b/src/HopFrame.Web/AuthMiddleware.cs index ac5c954..b5fbc93 100644 --- a/src/HopFrame.Web/AuthMiddleware.cs +++ b/src/HopFrame.Web/AuthMiddleware.cs @@ -26,7 +26,7 @@ public sealed class AuthMiddleware(IAuthService auth, IPermissionRepository perm new(HopFrameClaimTypes.UserId, token.Owner.Id.ToString()) }; - var permissions = await perms.GetFullPermissions(token.Owner); + var permissions = await perms.GetFullPermissions(token); claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); context.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName)); diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 4876323..4e212b5 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -321,7 +321,7 @@ private async void Save() { if (_isEdit && _currentPage.Permissions.Update is not null) { - if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Update)) { + if (!await Permissions.HasPermission(Auth.AccessToken, _currentPage.Permissions.Update)) { await Alerts.FireAsync(new SweetAlertOptions { Title = "Unauthorized!", Text = "You don't have the required permissions to edit an entry!", @@ -330,7 +330,7 @@ return; } }else if (_currentPage.Permissions.Create is not null) { - if (!await Permissions.HasPermission(Auth.User, _currentPage.Permissions.Create)) { + if (!await Permissions.HasPermission(Auth.AccessToken, _currentPage.Permissions.Create)) { await Alerts.FireAsync(new SweetAlertOptions { Title = "Unauthorized!", Text = "You don't have the required permissions to add an entry!", diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index d796aeb..1086918 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -140,8 +140,8 @@ throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'"); _modelProvider = _pageData.LoadModelProvider(Provider); - _hasEditPermission = _pageData.Permissions.Update is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); - _hasDeletePermission = _pageData.Permissions.Delete is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); + _hasEditPermission = _pageData.Permissions.Update is null || await Permissions.HasPermission(Auth.AccessToken, _pageData.Permissions.Update); + _hasDeletePermission = _pageData.Permissions.Delete is null || await Permissions.HasPermission(Auth.AccessToken, _pageData.Permissions.Delete); await Reload(); } diff --git a/testing/HopFrame.Testing.Api/Controllers/TestController.cs b/testing/HopFrame.Testing.Api/Controllers/TestController.cs index fb39666..d097592 100644 --- a/testing/HopFrame.Testing.Api/Controllers/TestController.cs +++ b/testing/HopFrame.Testing.Api/Controllers/TestController.cs @@ -1,5 +1,7 @@ using HopFrame.Api.Logic; +using HopFrame.Api.Models; using HopFrame.Database.Models; +using HopFrame.Database.Repositories; using HopFrame.Security.Authorization; using HopFrame.Security.Claims; using HopFrame.Testing.Api.Models; @@ -10,11 +12,11 @@ namespace HopFrame.Testing.Api.Controllers; [ApiController] [Route("test")] -public class TestController(ITokenContext userContext, DatabaseContext context) : ControllerBase { +public class TestController(ITokenContext userContext, DatabaseContext context, ITokenRepository tokens, IPermissionRepository permissions) : ControllerBase { [HttpGet("permissions"), Authorized] - public ActionResult> Permissions() { - return new ActionResult>(userContext.User.Permissions); + public ActionResult> Permissions() { + return new ActionResult>(userContext.ContextualPermissions); } [HttpGet("generate")] @@ -50,5 +52,19 @@ public class TestController(ITokenContext userContext, DatabaseContext context) public async Task>> GetAddresses() { return LogicResult>.Ok(await context.Addresses.Include(e => e.Employee).ToListAsync()); } + + [HttpGet("token"), Authorized] + public async Task>> GetApiToken() { + var token = await tokens.CreateApiToken(userContext.User, DateTime.MaxValue); + await permissions.AddPermission(token, "hopframe.admin"); + await permissions.AddPermission(token, "hopframe.admin.users.read"); + return LogicResult>.Ok(token.TokenId.ToString()); + } + + [HttpDelete("token/{tokenId}")] + public async Task DeleteToken(string tokenId) { + var token = await tokens.GetToken(tokenId); + await tokens.DeleteToken(token); + } } \ No newline at end of file diff --git a/testing/HopFrame.Testing.Api/Program.cs b/testing/HopFrame.Testing.Api/Program.cs index b728eb3..948be0d 100644 --- a/testing/HopFrame.Testing.Api/Program.cs +++ b/testing/HopFrame.Testing.Api/Program.cs @@ -18,7 +18,7 @@ builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", - Name = "Authorization", + Name = "Token", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "Bearer"