From b8b0c686e8971bacc8068b714fa7d9591482d959 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:39:40 +0200 Subject: [PATCH] feat: replace manual Logto role check with @PreAuthorize in TenantController Remove LogtoManagementClient dependency from TenantController; gate listAll and create with @PreAuthorize("hasRole('platform-admin')"), relying on the JWT roles claim already mapped by JwtAuthenticationConverter. Update TenantControllerTest to supply the platform-admin role via jwt() on all POST requests that expect 201/409. Co-Authored-By: Claude Sonnet 4.6 --- .../cameleer/saas/tenant/TenantController.java | 16 +++++----------- .../saas/tenant/TenantControllerTest.java | 15 +++++++++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java index 90742c2..ea5c008 100644 --- a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java @@ -1,11 +1,11 @@ package net.siegeln.cameleer.saas.tenant; import jakarta.validation.Valid; -import net.siegeln.cameleer.saas.identity.LogtoManagementClient; import net.siegeln.cameleer.saas.tenant.dto.CreateTenantRequest; import net.siegeln.cameleer.saas.tenant.dto.TenantResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -22,30 +22,24 @@ import java.util.UUID; public class TenantController { private final TenantService tenantService; - private final LogtoManagementClient logtoClient; - public TenantController(TenantService tenantService, LogtoManagementClient logtoClient) { + public TenantController(TenantService tenantService) { this.tenantService = tenantService; - this.logtoClient = logtoClient; } @GetMapping - public ResponseEntity> listAll(Authentication authentication) { - String userId = authentication.getName(); - List roles = logtoClient.getUserRoles(userId); - if (!roles.contains("platform-admin")) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); - } + @PreAuthorize("hasRole('platform-admin')") + public ResponseEntity> listAll() { List tenants = tenantService.findAll().stream() .map(this::toResponse).toList(); return ResponseEntity.ok(tenants); } @PostMapping + @PreAuthorize("hasRole('platform-admin')") public ResponseEntity create(@Valid @RequestBody CreateTenantRequest request, Authentication authentication) { try { - // Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string) String sub = authentication.getName(); UUID actorId; try { diff --git a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java index ebdcc58..f72e350 100644 --- a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java @@ -38,7 +38,8 @@ class TenantControllerTest { mockMvc.perform(post("/api/tenants") .with(jwt().jwt(j -> j .claim("sub", "test-user") - .claim("organization_id", "test-org"))) + .claim("organization_id", "test-org") + .claim("roles", java.util.List.of("platform-admin")))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -53,13 +54,17 @@ class TenantControllerTest { var request = new CreateTenantRequest("First", slug, null); mockMvc.perform(post("/api/tenants") - .with(jwt().jwt(j -> j.claim("sub", "test-user"))) + .with(jwt().jwt(j -> j + .claim("sub", "test-user") + .claim("roles", java.util.List.of("platform-admin")))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()); mockMvc.perform(post("/api/tenants") - .with(jwt().jwt(j -> j.claim("sub", "test-user"))) + .with(jwt().jwt(j -> j + .claim("sub", "test-user") + .claim("roles", java.util.List.of("platform-admin")))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isConflict()); @@ -81,7 +86,9 @@ class TenantControllerTest { var request = new CreateTenantRequest("Get Test", slug, null); var createResult = mockMvc.perform(post("/api/tenants") - .with(jwt().jwt(j -> j.claim("sub", "test-user"))) + .with(jwt().jwt(j -> j + .claim("sub", "test-user") + .claim("roles", java.util.List.of("platform-admin")))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated())