From 384b9324428838241bb7c6f59d03cbe0d3a4d42d Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Thu, 15 Aug 2024 01:01:14 +0200 Subject: [PATCH 1/6] feat(backoffice): require authorization for pages --- .../Authentication/AuthorizationSettings.cs | 7 +++ .../Layout/NavMenu.razor | 5 +- .../Pages/DebugClaims.razor | 44 +++++++++++++++++ .../Pages/Home.razor | 3 +- .../Pages/Images.razor | 49 +++++++++++-------- .../Pages/KnowledgeBase.razor | 2 +- src/Eurofurence.App.Backoffice/Program.cs | 14 ++++++ .../wwwroot/appsettings.sample.json | 7 ++- 8 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 src/Eurofurence.App.Backoffice/Authentication/AuthorizationSettings.cs create mode 100644 src/Eurofurence.App.Backoffice/Pages/DebugClaims.razor diff --git a/src/Eurofurence.App.Backoffice/Authentication/AuthorizationSettings.cs b/src/Eurofurence.App.Backoffice/Authentication/AuthorizationSettings.cs new file mode 100644 index 00000000..f35dd3c4 --- /dev/null +++ b/src/Eurofurence.App.Backoffice/Authentication/AuthorizationSettings.cs @@ -0,0 +1,7 @@ +namespace Eurofurence.App.Backoffice.Authentication +{ + public class AuthorizationSettings + { + public List KnowledgeBaseEditor { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Eurofurence.App.Backoffice/Layout/NavMenu.razor b/src/Eurofurence.App.Backoffice/Layout/NavMenu.razor index 385acc52..6c9c2494 100644 --- a/src/Eurofurence.App.Backoffice/Layout/NavMenu.razor +++ b/src/Eurofurence.App.Backoffice/Layout/NavMenu.razor @@ -1,7 +1,8 @@  Home - - Knowledge Base + + Knowledge Base + Images \ No newline at end of file diff --git a/src/Eurofurence.App.Backoffice/Pages/DebugClaims.razor b/src/Eurofurence.App.Backoffice/Pages/DebugClaims.razor new file mode 100644 index 00000000..b28110f5 --- /dev/null +++ b/src/Eurofurence.App.Backoffice/Pages/DebugClaims.razor @@ -0,0 +1,44 @@ +@page "/debug-claims" +@using Microsoft.AspNetCore.Components.Authorization +@using System.Security.Claims +@inject AuthenticationStateProvider AuthenticationStateProvider + +

User Claims Debugging

+ +@if (claims != null && claims.Any()) +{ + +} +else +{ +

No claims found or user is not authenticated.

+} + +@code { + private IEnumerable claims = new List(); + + protected override async Task OnInitializedAsync() + { + // Get the authentication state + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + // Check if the user is authenticated + if (user?.Identity?.IsAuthenticated ?? false) + { + // Retrieve and print all claims + claims = user.Claims; + + // Log claims to the console (for browser console) + foreach (var claim in claims) + { + Console.WriteLine($"Claim Type: {claim.Type}, Claim Value: {claim.Value}"); + } + } + } +} diff --git a/src/Eurofurence.App.Backoffice/Pages/Home.razor b/src/Eurofurence.App.Backoffice/Pages/Home.razor index ba940e9f..8d356b55 100644 --- a/src/Eurofurence.App.Backoffice/Pages/Home.razor +++ b/src/Eurofurence.App.Backoffice/Pages/Home.razor @@ -10,7 +10,8 @@ - Welcome to the "Backoffice" of our Eurofurence mobile app. Here, you can manage data like knowledge base articles and images. + Welcome to the backoffice of the Eurofurence mobile app. Once authenticated, you will be able to perform + various tasks like managing the articles in our knowledge base, depending on your permissions. diff --git a/src/Eurofurence.App.Backoffice/Pages/Images.razor b/src/Eurofurence.App.Backoffice/Pages/Images.razor index d0c854b7..a8e80373 100644 --- a/src/Eurofurence.App.Backoffice/Pages/Images.razor +++ b/src/Eurofurence.App.Backoffice/Pages/Images.razor @@ -1,40 +1,44 @@ @page "/images" +@attribute [Authorize(Policy = "RequireKnowledgeBaseEditor")] @using Eurofurence.App.Backoffice.Services @using Microsoft.AspNetCore.Authorization @using Eurofurence.App.Domain.Model.Images @using Eurofurence.App.Backoffice.Components -@attribute [Authorize] @inject ISnackbar Snackbar @inject IImageService ImageService @inject IDialogService DialogService Images - - - - New image + + + + New image - + @if (!string.IsNullOrEmpty(@context.Item.Url)) { - + } else { - + } - - - - + + + + @if (context.Item?.FursuitBadgeIds.Count > 0) @@ -61,7 +65,8 @@ } - @if (context.Item?.DealerArtPreviewIds.Count > 0 || context.Item?.DealerArtistIds.Count > 0 || context.Item?.DealerArtistThumbnailIds.Count > 0) + @if (context.Item?.DealerArtPreviewIds.Count > 0 || context.Item?.DealerArtistIds.Count > 0 || + context.Item?.DealerArtistThumbnailIds.Count > 0) { @@ -85,14 +90,15 @@ @if (context.Item != null) { - - + + + } - + @@ -131,13 +137,14 @@ _dataGrid?.ReloadServerData(); } - private IEnumerable FilterImages(IEnumerable entries, string? searchString) + private IEnumerable FilterImages(IEnumerable entries, string? + searchString) { return string.IsNullOrEmpty(searchString) - ? entries - : entries.Where(entry => - entry.InternalReference.ToLower().Contains(searchString.ToLower()) - || entry.Id.ToString().ToLower().Contains(searchString.ToLower())); + ? entries + : entries.Where(entry => + entry.InternalReference.ToLower().Contains(searchString.ToLower()) + || entry.Id.ToString().ToLower().Contains(searchString.ToLower())); } private async Task AddImage() diff --git a/src/Eurofurence.App.Backoffice/Pages/KnowledgeBase.razor b/src/Eurofurence.App.Backoffice/Pages/KnowledgeBase.razor index 849f0409..84a04313 100644 --- a/src/Eurofurence.App.Backoffice/Pages/KnowledgeBase.razor +++ b/src/Eurofurence.App.Backoffice/Pages/KnowledgeBase.razor @@ -1,4 +1,5 @@ @page "/knowledgebase" +@attribute [Authorize(Policy = "RequireKnowledgeBaseEditor")] @using Eurofurence.App.Backoffice.Services @using Eurofurence.App.Domain.Model.Knowledge @using Microsoft.AspNetCore.Authorization @@ -6,7 +7,6 @@ @using Eurofurence.App.Domain.Model.Images @using Ganss.Xss @using Markdig -@attribute [Authorize] @inject ISnackbar Snackbar @inject IKnowledgeService KnowledgeService @inject IImageService ImageService diff --git a/src/Eurofurence.App.Backoffice/Program.cs b/src/Eurofurence.App.Backoffice/Program.cs index 37fbca50..9b0b7260 100644 --- a/src/Eurofurence.App.Backoffice/Program.cs +++ b/src/Eurofurence.App.Backoffice/Program.cs @@ -32,4 +32,18 @@ options.ProviderOptions.DefaultScopes.Add("profile"); }); +var authSettings = builder.Configuration.GetSection("Authorization").Get() ?? new AuthorizationSettings(); + +builder.Services.AddAuthorizationCore(config => +{ + config.AddPolicy("RequireKnowledgeBaseEditor", policy => + policy.RequireAssertion(context => + context.User.Claims.Any(c => + c.Type == "groups" && authSettings.KnowledgeBaseEditor.Any(group => c.Value.Contains(group)) + ) + ) + ); +} +); + await builder.Build().RunAsync(); \ No newline at end of file diff --git a/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json b/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json index 40a98500..68c58d4b 100644 --- a/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json +++ b/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json @@ -4,5 +4,8 @@ "Authority": "https://identity.eurofurence.org/", "ClientId": "" }, - "AllowedHosts": "*" -} + "AllowedHosts": "*", + "Authorization": { + "KnowledgeBaseEditor": [] + } +} \ No newline at end of file From 5964be3a2db663fdf5fbe9447c372e744b858637 Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Fri, 16 Aug 2024 00:42:51 +0200 Subject: [PATCH 2/6] feat(auth): fetch registration status for session --- .../Users/UserRegistrationClaims.cs | 12 +++++ .../Identity/RolesClaimsTransformation.cs | 48 ++++++++++++++----- 2 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 src/Eurofurence.App.Domain.Model/Users/UserRegistrationClaims.cs diff --git a/src/Eurofurence.App.Domain.Model/Users/UserRegistrationClaims.cs b/src/Eurofurence.App.Domain.Model/Users/UserRegistrationClaims.cs new file mode 100644 index 00000000..c41689c0 --- /dev/null +++ b/src/Eurofurence.App.Domain.Model/Users/UserRegistrationClaims.cs @@ -0,0 +1,12 @@ +namespace Eurofurence.App.Domain.Model.Users +{ + public static class UserRegistrationClaims + { + public static string Id = "RegSysId"; + public static string StatusPrefix = "RegSysStatus"; + public static string Status(string id) + { + return $"{StatusPrefix}:{id}"; + } + } +} \ No newline at end of file diff --git a/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs b/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs index 4efc4306..ec94fd06 100644 --- a/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs +++ b/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading.Tasks; using Eurofurence.App.Domain.Model.PushNotifications; +using Eurofurence.App.Domain.Model.Users; using Eurofurence.App.Infrastructure.EntityFramework; using IdentityModel.AspNetCore.OAuth2Introspection; using IdentityModel.Client; @@ -128,9 +129,10 @@ private async Task ReadRegSys(ClaimsIdentity identity, string token) { identity.AddClaim(new Claim(identity.RoleClaimType, "Attendee")); - foreach (var value in JsonSerializer.Deserialize>(cached)) + foreach (var registration in JsonSerializer.Deserialize>(cached)) { - identity.AddClaim(new Claim("RegSysId", value)); + identity.AddClaim(new Claim(UserRegistrationClaims.Id, registration.Key)); + identity.AddClaim(new Claim(UserRegistrationClaims.Status(registration.Key), registration.Value)); } return; @@ -151,23 +153,29 @@ private async Task ReadRegSys(ClaimsIdentity identity, string token) } var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); - var ids = json.RootElement.TryGetStringArray("ids").ToList(); + var registrations = (await Task.WhenAll( + json.RootElement.TryGetStringArray("ids").ToDictionary(id => id, async id => + { + var status = await GetRegistrationStatus(current.RegSysUrl, token, id); + + identity.AddClaim(new Claim(UserRegistrationClaims.Id, id)); + identity.AddClaim(new Claim(UserRegistrationClaims.Status(id), status)); + return status; + }).Select( + async registration => new { Id = registration.Key, Status = await registration.Value } + ) + )).ToDictionary(registration => registration.Id, registration => registration.Status); - if (ids.Count == 0) + if (registrations.Count == 0) { return; } identity.AddClaim(new Claim(identity.RoleClaimType, "Attendee")); - foreach (var id in ids) - { - identity.AddClaim(new Claim("RegSysId", id)); - } - if (identity.FindFirst("sub")?.Value is { Length: > 0 } identityId) { - await UpdateRegSysIdsInDb(ids, identityId); + await UpdateRegSysIdsInDb(registrations.Keys.ToList(), identityId); } var exp = identity.FindFirst(x => x.Type == "exp"); @@ -175,7 +183,7 @@ private async Task ReadRegSys(ClaimsIdentity identity, string token) { await cache.SetStringAsync( $"{token}_regsys", - JsonSerializer.Serialize(ids), + JsonSerializer.Serialize(registrations), new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.FromUnixTimeSeconds(seconds) @@ -184,6 +192,24 @@ await cache.SetStringAsync( } } + private async Task GetRegistrationStatus(string regSysUrl, string token, string id) + { + using var client = httpClientFactory.CreateClient(OAuth2IntrospectionDefaults.BackChannelHttpClientName); + + var statusRequest = new HttpRequestMessage(HttpMethod.Get, + new Uri(new Uri(regSysUrl), $"attsrv/api/rest/v1/attendees/{id}/status")); + statusRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + using var statusResponse = await client.SendAsync(statusRequest); + + if (statusResponse.IsSuccessStatusCode) + { + var statusJson = await JsonDocument.ParseAsync(await statusResponse.Content.ReadAsStreamAsync()); + return statusJson.RootElement.TryGetString("status"); + } + + return null; + } + private async Task UpdateRegSysIdsInDb(List ids, string identityId) { var newIds = new HashSet(ids); From a91eb9ca52e6cd68e57f40bbd38dfdc5aa9169b6 Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Fri, 16 Aug 2024 00:43:45 +0200 Subject: [PATCH 3/6] feat(users): endpoint to introspect token --- .../Users/UserRecord.cs | 12 ++++++ .../Users/UserRegistration.cs | 12 ++++++ .../Users/UserRegistrationStatus.cs | 14 +++++++ .../Controllers/UsersController.cs | 37 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 src/Eurofurence.App.Domain.Model/Users/UserRecord.cs create mode 100644 src/Eurofurence.App.Domain.Model/Users/UserRegistration.cs create mode 100644 src/Eurofurence.App.Domain.Model/Users/UserRegistrationStatus.cs create mode 100644 src/Eurofurence.App.Server.Web/Controllers/UsersController.cs diff --git a/src/Eurofurence.App.Domain.Model/Users/UserRecord.cs b/src/Eurofurence.App.Domain.Model/Users/UserRecord.cs new file mode 100644 index 00000000..7bf200b9 --- /dev/null +++ b/src/Eurofurence.App.Domain.Model/Users/UserRecord.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Eurofurence.App.Domain.Model.Users +{ + public class UserRecord + { + [DataMember] + public string[] Roles { get; set; } + [DataMember] + public UserRegistration[] Registrations { get; set; } + } +} \ No newline at end of file diff --git a/src/Eurofurence.App.Domain.Model/Users/UserRegistration.cs b/src/Eurofurence.App.Domain.Model/Users/UserRegistration.cs new file mode 100644 index 00000000..e469484f --- /dev/null +++ b/src/Eurofurence.App.Domain.Model/Users/UserRegistration.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Eurofurence.App.Domain.Model.Users +{ + public class UserRegistration + { + [DataMember] + public string Id { get; set; } + [DataMember] + public UserRegistrationStatus Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Eurofurence.App.Domain.Model/Users/UserRegistrationStatus.cs b/src/Eurofurence.App.Domain.Model/Users/UserRegistrationStatus.cs new file mode 100644 index 00000000..529af2e3 --- /dev/null +++ b/src/Eurofurence.App.Domain.Model/Users/UserRegistrationStatus.cs @@ -0,0 +1,14 @@ +namespace Eurofurence.App.Domain.Model.Users +{ + public enum UserRegistrationStatus + { + Unknown = 0, + New = 1, + Approved = 2, + PartiallyPaid = 3, + Paid = 4, + CheckedIn = 5, + Cancelled = 6, + Deleted = 7, + } +} \ No newline at end of file diff --git a/src/Eurofurence.App.Server.Web/Controllers/UsersController.cs b/src/Eurofurence.App.Server.Web/Controllers/UsersController.cs new file mode 100644 index 00000000..37d14c0f --- /dev/null +++ b/src/Eurofurence.App.Server.Web/Controllers/UsersController.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Security.Claims; +using Eurofurence.App.Domain.Model.Users; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Eurofurence.App.Server.Web.Controllers +{ + [Route("Api/[controller]")] + public class UsersController : BaseController + { + [Authorize] + [HttpGet(":self")] + [ProducesResponseType(typeof(UserRecord), 200)] + public UserRecord GetUsersSelf() + { + var result = new UserRecord(); + if (User.Identity is ClaimsIdentity identity) + { + result.Roles = identity.FindAll(identity.RoleClaimType).Select(c => c.Value).ToArray(); + result.Registrations = identity.FindAll(UserRegistrationClaims.Id).Select(c => + { + var statusString = identity.FindFirst(UserRegistrationClaims.Status(c.Value))?.Value.Replace(" ", ""); + Enum.TryParse(statusString, true, out UserRegistrationStatus registrationStatus); + var registration = new UserRegistration + { + Id = c.Value, + Status = registrationStatus + }; + return registration; + }).ToArray(); + } + return result; + } + } +} \ No newline at end of file From e2dfa89a49de4c1ffef7ed2715835093f68c2bff Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Fri, 16 Aug 2024 01:01:39 +0200 Subject: [PATCH 4/6] feat(auth): added virtual role AttendeeCheckedIn --- .../Identity/RolesClaimsTransformation.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs b/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs index ec94fd06..650f00e9 100644 --- a/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs +++ b/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs @@ -129,10 +129,17 @@ private async Task ReadRegSys(ClaimsIdentity identity, string token) { identity.AddClaim(new Claim(identity.RoleClaimType, "Attendee")); - foreach (var registration in JsonSerializer.Deserialize>(cached)) + var cachedRegistrations = JsonSerializer.Deserialize>(cached); + + foreach (var registration in cachedRegistrations) { identity.AddClaim(new Claim(UserRegistrationClaims.Id, registration.Key)); - identity.AddClaim(new Claim(UserRegistrationClaims.Status(registration.Key), registration.Value)); + identity.AddClaim(new Claim(UserRegistrationClaims.Status(registration.Key), registration.Value.ToString())); + } + + if (cachedRegistrations.Any(registrationStatus => registrationStatus.Value == UserRegistrationStatus.CheckedIn)) + { + identity.AddClaim(new Claim(identity.RoleClaimType, "AttendeeCheckedIn")); } return; @@ -159,7 +166,7 @@ private async Task ReadRegSys(ClaimsIdentity identity, string token) var status = await GetRegistrationStatus(current.RegSysUrl, token, id); identity.AddClaim(new Claim(UserRegistrationClaims.Id, id)); - identity.AddClaim(new Claim(UserRegistrationClaims.Status(id), status)); + identity.AddClaim(new Claim(UserRegistrationClaims.Status(id), status.ToString())); return status; }).Select( async registration => new { Id = registration.Key, Status = await registration.Value } @@ -192,7 +199,7 @@ await cache.SetStringAsync( } } - private async Task GetRegistrationStatus(string regSysUrl, string token, string id) + private async Task GetRegistrationStatus(string regSysUrl, string token, string id) { using var client = httpClientFactory.CreateClient(OAuth2IntrospectionDefaults.BackChannelHttpClientName); @@ -204,10 +211,11 @@ private async Task GetRegistrationStatus(string regSysUrl, string token, if (statusResponse.IsSuccessStatusCode) { var statusJson = await JsonDocument.ParseAsync(await statusResponse.Content.ReadAsStreamAsync()); - return statusJson.RootElement.TryGetString("status"); + Enum.TryParse(statusJson.RootElement.TryGetString("status"), true, out UserRegistrationStatus status); + return status; } - return null; + return UserRegistrationStatus.Unknown; } private async Task UpdateRegSysIdsInDb(List ids, string identityId) From 22fcd2b1b1de0f5a4ae1bf07c037faf261891bfb Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Sat, 17 Aug 2024 17:28:02 +0200 Subject: [PATCH 5/6] feat(backoffice): authorize via backend roles --- src/Eurofurence.App.Backoffice/App.razor | 2 +- .../BackendAccountClaimsFactory.cs | 41 +++++++++++++++++++ src/Eurofurence.App.Backoffice/Program.cs | 11 +++-- .../Services/IUsersService.cs | 9 ++++ .../Services/UsersService.cs | 17 ++++++++ .../wwwroot/appsettings.sample.json | 5 +-- .../wwwroot/index.html | 2 +- 7 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/Eurofurence.App.Backoffice/Authentication/BackendAccountClaimsFactory.cs create mode 100644 src/Eurofurence.App.Backoffice/Services/IUsersService.cs create mode 100644 src/Eurofurence.App.Backoffice/Services/UsersService.cs diff --git a/src/Eurofurence.App.Backoffice/App.razor b/src/Eurofurence.App.Backoffice/App.razor index d994055d..d07039a5 100644 --- a/src/Eurofurence.App.Backoffice/App.razor +++ b/src/Eurofurence.App.Backoffice/App.razor @@ -8,7 +8,7 @@ } else - { + {

You are not authorized to access this resource.

}
diff --git a/src/Eurofurence.App.Backoffice/Authentication/BackendAccountClaimsFactory.cs b/src/Eurofurence.App.Backoffice/Authentication/BackendAccountClaimsFactory.cs new file mode 100644 index 00000000..8e87345d --- /dev/null +++ b/src/Eurofurence.App.Backoffice/Authentication/BackendAccountClaimsFactory.cs @@ -0,0 +1,41 @@ +using System.Security.Claims; +using Eurofurence.App.Backoffice.Services; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; + +namespace Eurofurence.App.Backoffice.Authentication +{ + public class BackendAccountClaimsFactory : AccountClaimsPrincipalFactory + { + public IServiceProvider _serviceProvider { get; set; } + + public BackendAccountClaimsFactory(IServiceProvider serviceProvider, IAccessTokenProviderAccessor accessor) : base(accessor) + { + _serviceProvider = serviceProvider; + } + + public override async ValueTask CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options) + { + var userAccount = await base.CreateUserAsync(account, options); + + if (!(userAccount.Identity?.IsAuthenticated ?? false)) + { + return userAccount; + } + + if (userAccount.Identity is ClaimsIdentity identity) + { + var usersService = _serviceProvider.GetRequiredService(); + var userRecord = await usersService.GetUserSelf(); + foreach (var role in userRecord.Roles) + { + identity.AddClaim(new Claim(identity.RoleClaimType, role)); + } + } + + return userAccount; + + } + } + +} \ No newline at end of file diff --git a/src/Eurofurence.App.Backoffice/Program.cs b/src/Eurofurence.App.Backoffice/Program.cs index 9b0b7260..058c27fb 100644 --- a/src/Eurofurence.App.Backoffice/Program.cs +++ b/src/Eurofurence.App.Backoffice/Program.cs @@ -1,7 +1,9 @@ +using System.Security.Claims; using Eurofurence.App.Backoffice; using Eurofurence.App.Backoffice.Authentication; using Eurofurence.App.Backoffice.Services; using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; @@ -16,6 +18,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpClient("api", options => @@ -30,18 +33,14 @@ builder.Configuration.Bind("Oidc", options.ProviderOptions); options.ProviderOptions.ResponseType = "code"; options.ProviderOptions.DefaultScopes.Add("profile"); -}); +}).AddAccountClaimsPrincipalFactory(); var authSettings = builder.Configuration.GetSection("Authorization").Get() ?? new AuthorizationSettings(); builder.Services.AddAuthorizationCore(config => { config.AddPolicy("RequireKnowledgeBaseEditor", policy => - policy.RequireAssertion(context => - context.User.Claims.Any(c => - c.Type == "groups" && authSettings.KnowledgeBaseEditor.Any(group => c.Value.Contains(group)) - ) - ) + policy.RequireRole("KnowledgeBaseEditor") ); } ); diff --git a/src/Eurofurence.App.Backoffice/Services/IUsersService.cs b/src/Eurofurence.App.Backoffice/Services/IUsersService.cs new file mode 100644 index 00000000..6a0855f4 --- /dev/null +++ b/src/Eurofurence.App.Backoffice/Services/IUsersService.cs @@ -0,0 +1,9 @@ +using Eurofurence.App.Domain.Model.Users; + +namespace Eurofurence.App.Backoffice.Services +{ + public interface IUsersService + { + public Task GetUserSelf(); + } +} diff --git a/src/Eurofurence.App.Backoffice/Services/UsersService.cs b/src/Eurofurence.App.Backoffice/Services/UsersService.cs new file mode 100644 index 00000000..65dcdb26 --- /dev/null +++ b/src/Eurofurence.App.Backoffice/Services/UsersService.cs @@ -0,0 +1,17 @@ +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using System.Text.Json; +using Eurofurence.App.Domain.Model.Users; + +namespace Eurofurence.App.Backoffice.Services +{ + public class UsersService(HttpClient http) : IUsersService + { + public async Task GetUserSelf() + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + return await http.GetFromJsonAsync("Users/:self", options) ?? new UserRecord(); + } + } +} \ No newline at end of file diff --git a/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json b/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json index 68c58d4b..dc8346e7 100644 --- a/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json +++ b/src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json @@ -4,8 +4,5 @@ "Authority": "https://identity.eurofurence.org/", "ClientId": "" }, - "AllowedHosts": "*", - "Authorization": { - "KnowledgeBaseEditor": [] - } + "AllowedHosts": "*" } \ No newline at end of file diff --git a/src/Eurofurence.App.Backoffice/wwwroot/index.html b/src/Eurofurence.App.Backoffice/wwwroot/index.html index 766930a1..a20e25e1 100644 --- a/src/Eurofurence.App.Backoffice/wwwroot/index.html +++ b/src/Eurofurence.App.Backoffice/wwwroot/index.html @@ -4,7 +4,7 @@ - Eurofurence.App.Backoffice + Eurofurence App Backoffice From b4d3793252825c48f503ea3f34fdd283bf98f074 Mon Sep 17 00:00:00 2001 From: Fenrikur <3359222+Fenrikur@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:15:02 +0200 Subject: [PATCH 6/6] feat(auth): skip regsys if not configured --- .../Identity/AuthorizationOptions.cs | 6 ++++-- .../Identity/RolesClaimsTransformation.cs | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Eurofurence.App.Server.Web/Identity/AuthorizationOptions.cs b/src/Eurofurence.App.Server.Web/Identity/AuthorizationOptions.cs index d5e38049..bfa09569 100644 --- a/src/Eurofurence.App.Server.Web/Identity/AuthorizationOptions.cs +++ b/src/Eurofurence.App.Server.Web/Identity/AuthorizationOptions.cs @@ -5,14 +5,16 @@ namespace Eurofurence.App.Server.Web.Identity; public class AuthorizationOptions { public HashSet Admin { get; set; } = new(); + public HashSet Attendee { get; set; } = new(); + public HashSet AttendeeCheckedIn { get; set; } = new(); public HashSet KnowledgeBaseEditor { get; set; } = new(); public HashSet MapEditor { get; set; } = new(); public HashSet ArtShow { get; set; } = new(); - + public HashSet FursuitBadgeSystem { get; set; } = new(); - + public HashSet PrivateMessageSender { get; set; } = new(); } \ No newline at end of file diff --git a/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs b/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs index 650f00e9..870f1206 100644 --- a/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs +++ b/src/Eurofurence.App.Server.Web/Identity/RolesClaimsTransformation.cs @@ -75,6 +75,16 @@ public async Task TransformAsync(ClaimsPrincipal principal) { roles.Add("Admin"); } + + if (authorizationOptions.Value.Attendee.Contains(claim.Value)) + { + roles.Add("Attendee"); + } + + if (authorizationOptions.Value.AttendeeCheckedIn.Contains(claim.Value)) + { + roles.Add("AttendeeCheckedIn"); + } } foreach (var role in roles) @@ -125,6 +135,13 @@ await cache.SetStringAsync( private async Task ReadRegSys(ClaimsIdentity identity, string token) { + var current = identityOptions.CurrentValue; + + if (string.IsNullOrEmpty(current.RegSysUrl)) + { + return; + } + if (await cache.GetStringAsync($"{token}_regsys") is { Length: > 0 } cached) { identity.AddClaim(new Claim(identity.RoleClaimType, "Attendee")); @@ -145,7 +162,6 @@ private async Task ReadRegSys(ClaimsIdentity identity, string token) return; } - var current = identityOptions.CurrentValue; using var client = httpClientFactory.CreateClient(OAuth2IntrospectionDefaults.BackChannelHttpClientName); var request = new HttpRequestMessage(HttpMethod.Get,