Skip to content

Commit 249009a

Browse files
committed
General: Record a deactivation once and clear the keys beside it
One deactivation through the admin edit form wrote two DEACTIVATE_USER entries. The develop merge is where that came from: develop moved the audit call to after the credential revocation, this branch still had it before, and the merge kept both. It is the same defect that was fixed once already for the activation direction. The audit call now happens in one place, after the revocation, with the recovery-key clearing beside it. Both deactivation tests asserted with anySatisfy, which two entries satisfy just as well as one, so neither could see this. They count now.
1 parent ec89850 commit 249009a

2 files changed

Lines changed: 18 additions & 11 deletions

File tree

src/main/java/de/tum/cit/aet/artemis/account/service/user/UserCreationService.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -302,16 +302,6 @@ else if (!wasInternal || user.getPassword() == null) {
302302
log.debug("Changed Information for User: {}", user);
303303

304304
User savedUser = saveUser(user);
305-
// Only the deactivation is audited here. The admin edit form reaches that transition without going through
306-
// deactivateUser, so it would otherwise go unrecorded. The opposite direction needs no entry here: the only caller,
307-
// AdminUserResource.updateUser, follows an activating update with userService.activateUser, which audits it - doing
308-
// it in both places recorded a single activation twice.
309-
if (isBeingDeactivated) {
310-
auditAccountStateChange(savedUser, Constants.DEACTIVATE_USER);
311-
// Same reason as in deactivateUser. Deliberately not done for a plain administrative password change, which
312-
// also revokes credentials but must leave an administrator-created account's invitation keys intact.
313-
userRecoveryKeyService.clearAll(savedUser.getId());
314-
}
315305
boolean passwordChangedByAdministrator = user.isInternal() && updatedUserDTO.getPassword() != null;
316306
boolean credentialsRevoked = isBeingDeactivated || revokeCredentialsAfterPasswordChange;
317307
if (credentialsRevoked) {
@@ -324,6 +314,10 @@ else if (!wasInternal || user.getPassword() == null) {
324314
// AdminUserResource.updateUser, follows an activating update with userService.activateUser, which audits it - doing
325315
// it in both places recorded a single activation twice.
326316
if (isBeingDeactivated) {
317+
// Same reason as in deactivateUser: an outstanding activation or reset key would be a way back into an account
318+
// whose control has just been taken away. Deliberately not done for a plain administrative password change,
319+
// which revokes credentials too but must leave an administrator-created account's invitation keys intact.
320+
userRecoveryKeyService.clearAll(savedUser.getId());
327321
auditAccountStateChange(savedUser, Constants.DEACTIVATE_USER);
328322
}
329323
if (passwordChangedByAdministrator) {

src/test/java/de/tum/cit/aet/artemis/account/service/AccountCredentialRevocationIntegrationTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
import java.time.Instant;
66
import java.time.ZonedDateTime;
7+
import java.util.List;
78

89
import org.junit.jupiter.api.BeforeEach;
910
import org.junit.jupiter.api.Test;
1011
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.boot.actuate.audit.AuditEvent;
1113
import org.springframework.context.annotation.Conditional;
1214
import org.springframework.data.domain.Pageable;
1315
import org.springframework.http.HttpStatus;
@@ -471,11 +473,17 @@ void deactivatingAUserIsRecordedInTheAuditLog() {
471473
userCreationService.deactivateUser(user);
472474

473475
assertAccountStateAudited(Constants.DEACTIVATE_USER);
476+
assertThat(deactivationEvents()).as("one deactivation produces one audit entry").hasSize(1);
474477
}
475478

479+
/**
480+
* Once, not once per thing the deactivation does. {@code updateUser} both revokes the credentials and audits, and an
481+
* entry written on either side of the revocation reads the same in the log - so only counting catches the case where
482+
* both happen.
483+
*/
476484
@Test
477485
@WithMockUser(username = "admin", roles = "ADMIN")
478-
void deactivatingAUserThroughTheAdminUpdateIsRecordedInTheAuditLog() {
486+
void deactivatingAUserThroughTheAdminUpdateIsRecordedExactlyOnce() {
479487
securityAuditEventRepository.deleteAll();
480488

481489
User userWithAuthorities = userRepository.findOneWithAuthoritiesByLogin(user.getLogin()).orElseThrow();
@@ -485,6 +493,7 @@ void deactivatingAUserThroughTheAdminUpdateIsRecordedInTheAuditLog() {
485493
userCreationService.updateUser(userWithAuthorities, update);
486494

487495
assertAccountStateAudited(Constants.DEACTIVATE_USER);
496+
assertThat(deactivationEvents()).as("one deactivation produces one audit entry").hasSize(1);
488497
}
489498

490499
@Test
@@ -520,6 +529,10 @@ void activatingThroughTheAdminUpdateIsRecordedExactlyOnce() {
520529
.as("one activation produces one audit entry").hasSize(1);
521530
}
522531

532+
private List<AuditEvent> deactivationEvents() {
533+
return auditEventService.findAll(AuditLogType.SECURITY, Pageable.unpaged()).stream().filter(event -> Constants.DEACTIVATE_USER.equals(event.getType())).toList();
534+
}
535+
523536
/**
524537
* Read through AuditEventService rather than the repository, because it loads {@code data} through an entity graph
525538
* while the repository's findAll() leaves that collection lazy and unreadable outside a session. The log to read from

0 commit comments

Comments
 (0)