From 3aa4ff2b7b95248995ad32df594ba3cb182a8bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 27 Jun 2025 17:30:05 +0200 Subject: [PATCH 1/3] Resolve names of role assignment principals --- .../rbac-principal.component.html | 3 +- .../rbac-principal.component.ts | 23 +++++++++- .../Principals/GetPrincipalNameEndpoint.cs | 44 +++++++++++++++++++ .../ApiKey/IApiKeyRepository.cs | 2 + src/Turnierplan.Core/User/IUserRepository.cs | 2 + .../Repositories/ApiKeyRepository.cs | 5 +++ .../Repositories/UserRepository.cs | 5 +++ 7 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs diff --git a/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.html b/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.html index d3d7f345..6696266a 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.html @@ -6,5 +6,4 @@ } } - -{{ principal.principalId }} +{{ displayName$ | async }} diff --git a/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts index 63a450ba..4cdba553 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts @@ -1,14 +1,33 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { PrincipalDto, PrincipalKind } from '../../../api'; +import { PrincipalsService } from '../../../api/services/principals.service'; +import { catchError, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; @Component({ selector: 'tp-rbac-principal', standalone: false, templateUrl: './rbac-principal.component.html' }) -export class RbacPrincipalComponent { +export class RbacPrincipalComponent implements OnInit { protected readonly PrincipalKind = PrincipalKind; + protected displayName$?: Observable; + @Input() public principal!: PrincipalDto; + + constructor(private readonly principalsService: PrincipalsService) {} + + public ngOnInit(): void { + this.displayName$ = this.principalsService + .getPrincipalName({ + principalKind: this.principal.kind, + principalId: this.principal.principalId + }) + .pipe( + map((result) => result.name), + catchError(() => of(this.principal.principalId)) + ); + } } diff --git a/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs b/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs new file mode 100644 index 00000000..e2b3edf1 --- /dev/null +++ b/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using Turnierplan.Core.ApiKey; +using Turnierplan.Core.RoleAssignment; +using Turnierplan.Core.User; + +namespace Turnierplan.App.Endpoints.Principals; + +internal sealed class GetPrincipalNameEndpoint : EndpointBase +{ + protected override HttpMethod Method => HttpMethod.Post; + + protected override string Route => "/api/principals/name"; + + protected override Delegate Handler => Handle; + + private static async Task Handle( + [FromQuery] PrincipalKind principalKind, + [FromQuery] Guid principalId, + IApiKeyRepository apiKeyRepository, + IUserRepository userRepository) + { + string? name; + + switch (principalKind) + { + case PrincipalKind.ApiKey: + var apiKey = await apiKeyRepository.GetByPrincipalIdAsync(principalId).ConfigureAwait(false); + name = apiKey?.Name; + break; + case PrincipalKind.User: + var user = await userRepository.GetByPrincipalIdAsync(principalId).ConfigureAwait(false); + name = user?.Name; + break; + default: + return Results.BadRequest("Invalid principal kind specified."); + } + + return name is not null + ? Results.Ok(new GetPrincipalNameEndpointResponse(name)) + : Results.NotFound(); + } + + public sealed record GetPrincipalNameEndpointResponse(string Name); +} diff --git a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs index 6bd5c7d5..b468ece4 100644 --- a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs +++ b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs @@ -4,5 +4,7 @@ namespace Turnierplan.Core.ApiKey; public interface IApiKeyRepository : IRepositoryWithPublicId { + Task GetByPrincipalIdAsync(Guid id); + Task> GetRequestsInTimeRange(ApiKey apiKey, DateTime start, DateTime end); } diff --git a/src/Turnierplan.Core/User/IUserRepository.cs b/src/Turnierplan.Core/User/IUserRepository.cs index 7b94f56a..e3703563 100644 --- a/src/Turnierplan.Core/User/IUserRepository.cs +++ b/src/Turnierplan.Core/User/IUserRepository.cs @@ -8,5 +8,7 @@ public interface IUserRepository : IRepository Task GetByIdAsync(Guid id); + Task GetByPrincipalIdAsync(Guid id); + Task GetByEmailAsync(string email); } diff --git a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs index 608f65d9..ff3d68c1 100644 --- a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs +++ b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs @@ -23,6 +23,11 @@ public ApiKeyRepository(TurnierplanContext context) .FirstOrDefaultAsync(); } + public Task GetByPrincipalIdAsync(Guid id) + { + return DbSet.Where(x => x.PrincipalId == id).FirstOrDefaultAsync(); + } + public Task> GetRequestsInTimeRange(ApiKey apiKey, DateTime start, DateTime end) { return _requests.Where(x => x.ApiKey == apiKey && x.Timestamp >= start && x.Timestamp <= end) diff --git a/src/Turnierplan.Dal/Repositories/UserRepository.cs b/src/Turnierplan.Dal/Repositories/UserRepository.cs index 3fb65403..d4b4deac 100644 --- a/src/Turnierplan.Dal/Repositories/UserRepository.cs +++ b/src/Turnierplan.Dal/Repositories/UserRepository.cs @@ -16,6 +16,11 @@ public Task> GetAllUsers() return DbSet.Where(x => x.Id.Equals(id)).FirstOrDefaultAsync(); } + public Task GetByPrincipalIdAsync(Guid id) + { + return DbSet.Where(x => x.PrincipalId == id).FirstOrDefaultAsync(); + } + public Task GetByEmailAsync(string email) { var normalizedEMail = User.NormalizeEmail(email); From 044562e330b1cdf5399c3d2d21c44c5491c12eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 27 Jun 2025 17:33:06 +0200 Subject: [PATCH 2/3] fix http method --- .../Endpoints/Principals/GetPrincipalNameEndpoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs b/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs index e2b3edf1..256d0a62 100644 --- a/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs @@ -7,7 +7,7 @@ namespace Turnierplan.App.Endpoints.Principals; internal sealed class GetPrincipalNameEndpoint : EndpointBase { - protected override HttpMethod Method => HttpMethod.Post; + protected override HttpMethod Method => HttpMethod.Get; protected override string Route => "/api/principals/name"; From 79461a054e5c5e257a5ba686662feb79d630d7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 27 Jun 2025 17:35:59 +0200 Subject: [PATCH 3/3] Inherited "small" --- .../components/rbac-offcanvas/rbac-offcanvas.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html b/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html index 758ef58d..c1a85dfd 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html @@ -41,7 +41,7 @@ } @if (assignment.isInherited) { -
+