From f42e6279e6b7efaee09800a965b3d0a52714caec Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:49:34 +0100 Subject: [PATCH] fix: null safety in role/group creation, add user create/update endpoints - RoleAdminController.createRole: default null description to "" and null scope to "custom" - RoleAdminController.updateRole: pass null audit details to avoid NPE when name is null - GroupAdminController.updateGroup: pass null audit details to avoid NPE when name is null - UserAdminController: add POST / createUser endpoint with default VIEWER role assignment - UserAdminController: add PUT /{userId} updateUser endpoint for displayName/email updates --- .../app/controller/GroupAdminController.java | 2 +- .../app/controller/RoleAdminController.java | 6 ++- .../app/controller/UserAdminController.java | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/GroupAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/GroupAdminController.java index 3def54ba..94ec2fdd 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/GroupAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/GroupAdminController.java @@ -111,7 +111,7 @@ public class GroupAdminController { groupRepository.update(id, request.name(), request.parentGroupId()); auditService.log("update_group", AuditCategory.RBAC, id.toString(), - Map.of("name", request.name()), AuditResult.SUCCESS, httpRequest); + null, AuditResult.SUCCESS, httpRequest); return ResponseEntity.ok().build(); } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RoleAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RoleAdminController.java index 612ca04b..7b05a0c5 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RoleAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RoleAdminController.java @@ -69,7 +69,9 @@ public class RoleAdminController { @ApiResponse(responseCode = "200", description = "Role created") public ResponseEntity> createRole(@RequestBody CreateRoleRequest request, HttpServletRequest httpRequest) { - UUID id = roleRepository.create(request.name(), request.description(), request.scope()); + String desc = request.description() != null ? request.description() : ""; + String sc = request.scope() != null ? request.scope() : "custom"; + UUID id = roleRepository.create(request.name(), desc, sc); auditService.log("create_role", AuditCategory.RBAC, id.toString(), Map.of("name", request.name()), AuditResult.SUCCESS, httpRequest); return ResponseEntity.ok(Map.of("id", id)); @@ -93,7 +95,7 @@ public class RoleAdminController { } roleRepository.update(id, request.name(), request.description(), request.scope()); auditService.log("update_role", AuditCategory.RBAC, id.toString(), - Map.of("name", request.name()), AuditResult.SUCCESS, httpRequest); + null, AuditResult.SUCCESS, httpRequest); return ResponseEntity.ok().build(); } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java index 45e00b48..0cb9abe5 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java @@ -4,7 +4,9 @@ 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.rbac.UserDetail; +import com.cameleer3.server.core.security.UserInfo; import com.cameleer3.server.core.security.UserRepository; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -16,9 +18,12 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.UUID; @@ -63,6 +68,43 @@ public class UserAdminController { return ResponseEntity.ok(detail); } + @PostMapping + @Operation(summary = "Create a local user") + @ApiResponse(responseCode = "200", description = "User created") + public ResponseEntity createUser(@RequestBody CreateUserRequest request, + HttpServletRequest httpRequest) { + String userId = "user:" + request.username(); + UserInfo user = new UserInfo(userId, "local", + request.email() != null ? request.email() : "", + request.displayName() != null ? request.displayName() : request.username(), + Instant.now()); + userRepository.upsert(user); + rbacService.assignRoleToUser(userId, SystemRole.VIEWER_ID); + auditService.log("create_user", AuditCategory.USER_MGMT, userId, + Map.of("username", request.username()), AuditResult.SUCCESS, httpRequest); + return ResponseEntity.ok(rbacService.getUser(userId)); + } + + @PutMapping("/{userId}") + @Operation(summary = "Update user display name or email") + @ApiResponse(responseCode = "200", description = "User updated") + @ApiResponse(responseCode = "404", description = "User not found") + public ResponseEntity updateUser(@PathVariable String userId, + @RequestBody UpdateUserRequest request, + HttpServletRequest httpRequest) { + var existing = userRepository.findById(userId); + if (existing.isEmpty()) return ResponseEntity.notFound().build(); + var user = existing.get(); + var updated = new UserInfo(user.userId(), user.provider(), + request.email() != null ? request.email() : user.email(), + request.displayName() != null ? request.displayName() : user.displayName(), + user.createdAt()); + userRepository.upsert(updated); + auditService.log("update_user", AuditCategory.USER_MGMT, userId, + null, AuditResult.SUCCESS, httpRequest); + return ResponseEntity.ok().build(); + } + @PostMapping("/{userId}/roles/{roleId}") @Operation(summary = "Assign a role to a user") @ApiResponse(responseCode = "200", description = "Role assigned") @@ -122,4 +164,7 @@ public class UserAdminController { null, AuditResult.SUCCESS, httpRequest); return ResponseEntity.noContent().build(); } + + public record CreateUserRequest(String username, String displayName, String email) {} + public record UpdateUserRequest(String displayName, String email) {} }