feat(audit): emit GROUP_MEMBERSHIP_CHANGED when admin updates user groups

Adds actorId param to adminUpdateUser(), captures beforeGroups before
mutation, computes added/removed group names, emits logAfterCommit only
when the group set actually changes. Payload contains group names, not
permission strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-26 14:54:42 +02:00
committed by marcel
parent a736b7399a
commit eb8f9d4dc4
3 changed files with 102 additions and 18 deletions

View File

@@ -86,9 +86,11 @@ public class UserController {
@PutMapping("/users/{id}")
@RequirePermission(Permission.ADMIN_USER)
public ResponseEntity<AppUser> adminUpdateUser(@PathVariable UUID id,
public ResponseEntity<AppUser> adminUpdateUser(Authentication authentication,
@PathVariable UUID id,
@RequestBody AdminUpdateUserRequest dto) {
AppUser updated = userService.adminUpdateUser(id, dto);
AppUser actor = userService.findByEmail(authentication.getName());
AppUser updated = userService.adminUpdateUser(actor.getId(), id, dto);
updated.setPassword(null);
return ResponseEntity.ok(updated);
}

View File

@@ -28,6 +28,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static java.util.stream.Collectors.toSet;
@Service
@RequiredArgsConstructor
@Slf4j
@@ -156,7 +158,7 @@ public class UserService {
}
@Transactional
public AppUser adminUpdateUser(UUID id, AdminUpdateUserRequest dto) {
public AppUser adminUpdateUser(UUID actorId, UUID id, AdminUpdateUserRequest dto) {
AppUser user = getById(id);
if (dto.getEmail() != null && !dto.getEmail().isBlank()) {
@@ -181,8 +183,22 @@ public class UserService {
}
if (dto.getGroupIds() != null) {
Set<UserGroup> groups = new HashSet<>(groupRepository.findAllById(dto.getGroupIds()));
user.setGroups(groups);
Set<UUID> beforeIds = user.getGroups().stream().map(UserGroup::getId).collect(toSet());
Set<UserGroup> beforeGroups = new HashSet<>(user.getGroups());
Set<UserGroup> newGroups = new HashSet<>(groupRepository.findAllById(dto.getGroupIds()));
user.setGroups(newGroups);
Set<UUID> afterIds = newGroups.stream().map(UserGroup::getId).collect(toSet());
if (!beforeIds.equals(afterIds)) {
List<String> added = newGroups.stream()
.filter(g -> !beforeIds.contains(g.getId()))
.map(UserGroup::getName).toList();
List<String> removed = beforeGroups.stream()
.filter(g -> !afterIds.contains(g.getId()))
.map(UserGroup::getName).toList();
auditService.logAfterCommit(AuditKind.GROUP_MEMBERSHIP_CHANGED, actorId, null,
Map.of("userId", id.toString(), "email", user.getEmail(),
"addedGroups", added, "removedGroups", removed));
}
}
return userRepository.save(user);