Rework Admin UI #6
@@ -54,6 +54,14 @@ public class ClubRoleClaimsTransformation : IClaimsTransformation
|
|||||||
return Task.FromResult(principal);
|
return Task.FromResult(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- NEW: Skip DB role lookup if user is a global admin ---
|
||||||
|
var realmAccess = principal.FindFirst("realm_access")?.Value;
|
||||||
|
if (!string.IsNullOrEmpty(realmAccess) && realmAccess.Contains("\"admin\"", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Task.FromResult(principal);
|
||||||
|
}
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
// Look up the user's role in the database for the requested tenant
|
// Look up the user's role in the database for the requested tenant
|
||||||
_httpContextAccessor.HttpContext!.Items["TenantId"] = tenantId;
|
_httpContextAccessor.HttpContext!.Items["TenantId"] = tenantId;
|
||||||
var memberRole = GetMemberRole(userIdClaim, tenantId);
|
var memberRole = GetMemberRole(userIdClaim, tenantId);
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ if (app.Environment.IsDevelopment())
|
|||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseMiddleware<TenantValidationMiddleware>();
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
app.UseMiddleware<TenantValidationMiddleware>();
|
||||||
app.UseMiddleware<MemberSyncMiddleware>();
|
app.UseMiddleware<MemberSyncMiddleware>();
|
||||||
|
|
||||||
app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
|
app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
|
using WorkClub.Domain.Enums;
|
||||||
|
using WorkClub.Application.Clubs.DTOs;
|
||||||
|
using WorkClub.Tests.Integration.Infrastructure;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace WorkClub.Tests.Integration.Clubs;
|
||||||
|
|
||||||
|
public class AdminClubEndpointsTests : IntegrationTestBase
|
||||||
|
{
|
||||||
|
public AdminClubEndpointsTests(CustomWebApplicationFactory<Program> factory) : base(factory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateClub_WithAdminRole_ReturnsCreated()
|
||||||
|
{
|
||||||
|
AuthenticateAsAdmin();
|
||||||
|
|
||||||
|
var request = new CreateClubRequest("New Admin Club", SportType.Tennis, "Desc");
|
||||||
|
var response = await Client.PostAsJsonAsync("/api/admin/clubs", request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateClub_WithoutAdminRole_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
AuthenticateAsNonAdmin();
|
||||||
|
|
||||||
|
var request = new CreateClubRequest("New Club", SportType.Tennis, "Desc");
|
||||||
|
var response = await Client.PostAsJsonAsync("/api/admin/clubs", request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AuthenticateAsAdmin()
|
||||||
|
{
|
||||||
|
Client.DefaultRequestHeaders.Remove("X-Test-Email");
|
||||||
|
Client.DefaultRequestHeaders.Add("X-Test-Email", "admin@workclub.com");
|
||||||
|
|
||||||
|
Client.DefaultRequestHeaders.Remove("X-Test-Realm-Access");
|
||||||
|
Client.DefaultRequestHeaders.Add("X-Test-Realm-Access", "{\"roles\":[\"admin\"]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AuthenticateAsNonAdmin()
|
||||||
|
{
|
||||||
|
Client.DefaultRequestHeaders.Remove("X-Test-Email");
|
||||||
|
Client.DefaultRequestHeaders.Add("X-Test-Email", "user@workclub.com");
|
||||||
|
|
||||||
|
Client.DefaultRequestHeaders.Remove("X-Test-Realm-Access");
|
||||||
|
Client.DefaultRequestHeaders.Add("X-Test-Realm-Access", "{\"roles\":[\"user\"]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,6 +67,8 @@ public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProg
|
|||||||
GRANT CONNECT ON DATABASE workclub_test TO rls_test_user;
|
GRANT CONNECT ON DATABASE workclub_test TO rls_test_user;
|
||||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO rls_test_user;
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO rls_test_user;
|
||||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO rls_test_user;
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO rls_test_user;
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_admin') THEN
|
||||||
|
CREATE ROLE app_admin;
|
||||||
END IF;
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
";
|
";
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions
|
|||||||
var emailClaim = Context.Request.Headers["X-Test-Email"].ToString();
|
var emailClaim = Context.Request.Headers["X-Test-Email"].ToString();
|
||||||
var userIdClaim = Context.Request.Headers["X-Test-UserId"].ToString();
|
var userIdClaim = Context.Request.Headers["X-Test-UserId"].ToString();
|
||||||
var clubRolesJson = Context.Request.Headers["X-Test-ClubRoles"].ToString();
|
var clubRolesJson = Context.Request.Headers["X-Test-ClubRoles"].ToString();
|
||||||
|
var realmAccessClaim = Context.Request.Headers["X-Test-Realm-Access"].ToString();
|
||||||
|
|
||||||
// If no test auth headers are present, return NoResult (unauthenticated)
|
// If no test auth headers are present, return NoResult (unauthenticated)
|
||||||
if (string.IsNullOrEmpty(emailClaim) && string.IsNullOrEmpty(userIdClaim) && string.IsNullOrEmpty(clubsClaim))
|
if (string.IsNullOrEmpty(emailClaim) && string.IsNullOrEmpty(userIdClaim) && string.IsNullOrEmpty(clubsClaim) && string.IsNullOrEmpty(realmAccessClaim))
|
||||||
{
|
{
|
||||||
return Task.FromResult(AuthenticateResult.NoResult());
|
return Task.FromResult(AuthenticateResult.NoResult());
|
||||||
}
|
}
|
||||||
@@ -46,6 +47,11 @@ public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions
|
|||||||
new Claim(ClaimTypes.Email, resolvedEmail),
|
new Claim(ClaimTypes.Email, resolvedEmail),
|
||||||
new Claim("preferred_username", resolvedEmail),
|
new Claim("preferred_username", resolvedEmail),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(realmAccessClaim))
|
||||||
|
{
|
||||||
|
claims.Add(new Claim("realm_access", realmAccessClaim, ClaimValueTypes.String));
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(clubsClaim))
|
if (!string.IsNullOrEmpty(clubsClaim))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ services:
|
|||||||
- "5001:8080"
|
- "5001:8080"
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "localhost:host-gateway"
|
- "localhost:host-gateway"
|
||||||
|
- "127.0.0.1:host-gateway"
|
||||||
|
- "keycloak:host-gateway"
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend:/app:cached
|
- ./backend:/app:cached
|
||||||
|
|||||||
Reference in New Issue
Block a user