feat: replace flat users.roles with relational RBAC model
New package com.cameleer3.server.core.rbac with SystemRole constants, detail/summary records, GroupRepository, RoleRepository, RbacService. Remove roles field from UserInfo. Implement PostgresGroupRepository, PostgresRoleRepository, RbacServiceImpl with inheritance computation. Update UiAuthController, OidcAuthController, AgentRegistrationController to assign roles via user_roles table. JWT populated from effective system roles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ import com.cameleer3.server.app.security.BootstrapTokenValidator;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
import com.cameleer3.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer3.server.core.agent.AgentState;
|
||||
import com.cameleer3.server.core.rbac.RbacService;
|
||||
import com.cameleer3.server.core.rbac.SystemRole;
|
||||
import com.cameleer3.server.core.security.Ed25519SigningService;
|
||||
import com.cameleer3.server.core.security.InvalidTokenException;
|
||||
import com.cameleer3.server.core.security.JwtService;
|
||||
@@ -50,17 +52,20 @@ public class AgentRegistrationController {
|
||||
private final BootstrapTokenValidator bootstrapTokenValidator;
|
||||
private final JwtService jwtService;
|
||||
private final Ed25519SigningService ed25519SigningService;
|
||||
private final RbacService rbacService;
|
||||
|
||||
public AgentRegistrationController(AgentRegistryService registryService,
|
||||
AgentRegistryConfig config,
|
||||
BootstrapTokenValidator bootstrapTokenValidator,
|
||||
JwtService jwtService,
|
||||
Ed25519SigningService ed25519SigningService) {
|
||||
Ed25519SigningService ed25519SigningService,
|
||||
RbacService rbacService) {
|
||||
this.registryService = registryService;
|
||||
this.config = config;
|
||||
this.bootstrapTokenValidator = bootstrapTokenValidator;
|
||||
this.jwtService = jwtService;
|
||||
this.ed25519SigningService = ed25519SigningService;
|
||||
this.rbacService = rbacService;
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
@@ -97,6 +102,9 @@ public class AgentRegistrationController {
|
||||
request.agentId(), request.name(), group, request.version(), routeIds, capabilities);
|
||||
log.info("Agent registered: {} (name={}, group={})", request.agentId(), request.name(), group);
|
||||
|
||||
// Assign AGENT role via RBAC
|
||||
rbacService.assignRoleToUser(request.agentId(), SystemRole.AGENT_ID);
|
||||
|
||||
// Issue JWT tokens with AGENT role
|
||||
List<String> roles = List.of("AGENT");
|
||||
String accessToken = jwtService.createAccessToken(request.agentId(), group, roles);
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.cameleer3.server.app.controller;
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import com.cameleer3.server.core.rbac.RbacService;
|
||||
import com.cameleer3.server.core.rbac.SystemRole;
|
||||
import com.cameleer3.server.core.security.UserInfo;
|
||||
import com.cameleer3.server.core.security.UserRepository;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Admin endpoints for user management.
|
||||
@@ -34,10 +37,13 @@ public class UserAdminController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final AuditService auditService;
|
||||
private final RbacService rbacService;
|
||||
|
||||
public UserAdminController(UserRepository userRepository, AuditService auditService) {
|
||||
public UserAdminController(UserRepository userRepository, AuditService auditService,
|
||||
RbacService rbacService) {
|
||||
this.userRepository = userRepository;
|
||||
this.auditService = auditService;
|
||||
this.rbacService = rbacService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -67,7 +73,20 @@ public class UserAdminController {
|
||||
if (userRepository.findById(userId).isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
userRepository.updateRoles(userId, request.roles());
|
||||
// Remove all existing direct roles, then assign the new ones
|
||||
List<String> currentRoles = rbacService.getSystemRoleNames(userId);
|
||||
for (String roleName : currentRoles) {
|
||||
UUID roleId = SystemRole.BY_NAME.get(roleName);
|
||||
if (roleId != null) {
|
||||
rbacService.removeRoleFromUser(userId, roleId);
|
||||
}
|
||||
}
|
||||
for (String roleName : request.roles()) {
|
||||
UUID roleId = SystemRole.BY_NAME.get(roleName.toUpperCase());
|
||||
if (roleId != null) {
|
||||
rbacService.assignRoleToUser(userId, roleId);
|
||||
}
|
||||
}
|
||||
auditService.log("update_roles", AuditCategory.USER_MGMT, userId,
|
||||
Map.of("roles", request.roles()), AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.ok().build();
|
||||
|
||||
Reference in New Issue
Block a user