feat: support server:-prefixed scopes and case-insensitive role mapping
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled

M2M scope mapping now accepts both 'server:admin' and 'admin' (case-
insensitive). OIDC user login role assignment strips the 'server:'
prefix before looking up SystemRole, so 'server:viewer' from the
id_token maps to VIEWER correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-06 10:05:13 +02:00
parent c757a0ea51
commit 9c2e6aacad
2 changed files with 15 additions and 5 deletions

View File

@@ -105,7 +105,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
/** /**
* Maps OAuth2 scopes to server RBAC roles. * Maps OAuth2 scopes to server RBAC roles.
* Scopes are defined on the Logto API Resource for this server. * Accepts both prefixed ({@code server:admin}) and bare ({@code admin}) scope names,
* case-insensitive. Scopes are defined on the Logto API Resource for this server.
*/ */
private List<String> extractRolesFromScopes(Jwt jwt) { private List<String> extractRolesFromScopes(Jwt jwt) {
String scopeStr = jwt.getClaimAsString("scope"); String scopeStr = jwt.getClaimAsString("scope");
@@ -113,12 +114,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
return List.of("VIEWER"); return List.of("VIEWER");
} }
List<String> scopes = List.of(scopeStr.split(" ")); List<String> scopes = List.of(scopeStr.split(" "));
if (scopes.contains("admin")) return List.of("ADMIN"); if (hasScope(scopes, "admin")) return List.of("ADMIN");
if (scopes.contains("operator")) return List.of("OPERATOR"); if (hasScope(scopes, "operator")) return List.of("OPERATOR");
if (scopes.contains("viewer")) return List.of("VIEWER"); if (hasScope(scopes, "viewer")) return List.of("VIEWER");
return List.of("VIEWER"); return List.of("VIEWER");
} }
private boolean hasScope(List<String> scopes, String role) {
return scopes.stream().anyMatch(s ->
s.equalsIgnoreCase(role) || s.equalsIgnoreCase("server:" + role));
}
private List<GrantedAuthority> toAuthorities(List<String> roles) { private List<GrantedAuthority> toAuthorities(List<String> roles) {
return roles.stream() return roles.stream()
.map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role)) .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role))

View File

@@ -170,7 +170,11 @@ public class OidcAuthController {
private void assignRolesForNewUser(String userId, List<String> oidcRoles, OidcConfig config) { private void assignRolesForNewUser(String userId, List<String> oidcRoles, OidcConfig config) {
List<String> roleNames = !oidcRoles.isEmpty() ? oidcRoles : config.defaultRoles(); List<String> roleNames = !oidcRoles.isEmpty() ? oidcRoles : config.defaultRoles();
for (String roleName : roleNames) { for (String roleName : roleNames) {
UUID roleId = SystemRole.BY_NAME.get(roleName.toUpperCase()); String normalized = roleName.toUpperCase();
if (normalized.startsWith("SERVER:")) {
normalized = normalized.substring("SERVER:".length());
}
UUID roleId = SystemRole.BY_NAME.get(normalized);
if (roleId != null) { if (roleId != null) {
rbacService.assignRoleToUser(userId, roleId); rbacService.assignRoleToUser(userId, roleId);
} }