fix(user): findOrCreate Administrators group instead of blind-INSERT (#518) #519
@@ -67,11 +67,16 @@ public class UserDataInitializer {
|
|||||||
}
|
}
|
||||||
log.info("Kein Admin-User '{}' gefunden. Erstelle Default-Admin...", adminEmail);
|
log.info("Kein Admin-User '{}' gefunden. Erstelle Default-Admin...", adminEmail);
|
||||||
|
|
||||||
UserGroup adminGroup = UserGroup.builder()
|
// Reuse the Administrators group if it already exists (e.g. a
|
||||||
.name("Administrators")
|
// previous boot seeded the group but failed before creating
|
||||||
.permissions(Set.of("ADMIN", "READ_ALL", "WRITE_ALL", "ANNOTATE_ALL", "ADMIN_USER", "ADMIN_TAG", "ADMIN_PERMISSION"))
|
// the admin user, or the operator deleted just the user row
|
||||||
.build();
|
// to retry the seed with a new email). Blind-INSERTing would
|
||||||
groupRepository.save(adminGroup);
|
// violate user_groups_name_key and abort the context. See #518.
|
||||||
|
UserGroup adminGroup = groupRepository.findByName("Administrators")
|
||||||
|
.orElseGet(() -> groupRepository.save(UserGroup.builder()
|
||||||
|
.name("Administrators")
|
||||||
|
.permissions(Set.of("ADMIN", "READ_ALL", "WRITE_ALL", "ANNOTATE_ALL", "ADMIN_USER", "ADMIN_TAG", "ADMIN_PERMISSION"))
|
||||||
|
.build()));
|
||||||
|
|
||||||
AppUser admin = AppUser.builder()
|
AppUser admin = AppUser.builder()
|
||||||
.email(adminEmail)
|
.email(adminEmail)
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -78,6 +80,8 @@ class AdminSeedFailClosedTest {
|
|||||||
@Test
|
@Test
|
||||||
void allows_seed_when_both_values_are_set_and_profile_is_not_dev() throws Exception {
|
void allows_seed_when_both_values_are_set_and_profile_is_not_dev() throws Exception {
|
||||||
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
|
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.findByName("Administrators")).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.save(any(UserGroup.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||||
when(environment.matchesProfiles("dev", "test", "e2e")).thenReturn(false);
|
when(environment.matchesProfiles("dev", "test", "e2e")).thenReturn(false);
|
||||||
when(passwordEncoder.encode(anyString())).thenReturn("$2a$10$stub");
|
when(passwordEncoder.encode(anyString())).thenReturn("$2a$10$stub");
|
||||||
ReflectionTestUtils.setField(initializer, "adminEmail", "admin@archiv.raddatz.cloud");
|
ReflectionTestUtils.setField(initializer, "adminEmail", "admin@archiv.raddatz.cloud");
|
||||||
@@ -86,12 +90,14 @@ class AdminSeedFailClosedTest {
|
|||||||
CommandLineRunner runner = initializer.initAdminUser(passwordEncoder);
|
CommandLineRunner runner = initializer.initAdminUser(passwordEncoder);
|
||||||
runner.run();
|
runner.run();
|
||||||
|
|
||||||
verify(userRepository).save(org.mockito.ArgumentMatchers.any(AppUser.class));
|
verify(userRepository).save(any(AppUser.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void allows_seed_with_defaults_when_profile_is_dev() throws Exception {
|
void allows_seed_with_defaults_when_profile_is_dev() throws Exception {
|
||||||
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
|
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.findByName("Administrators")).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.save(any(UserGroup.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||||
when(environment.matchesProfiles("dev", "test", "e2e")).thenReturn(true);
|
when(environment.matchesProfiles("dev", "test", "e2e")).thenReturn(true);
|
||||||
when(passwordEncoder.encode(anyString())).thenReturn("$2a$10$stub");
|
when(passwordEncoder.encode(anyString())).thenReturn("$2a$10$stub");
|
||||||
ReflectionTestUtils.setField(initializer, "adminEmail", UserDataInitializer.DEFAULT_ADMIN_EMAIL);
|
ReflectionTestUtils.setField(initializer, "adminEmail", UserDataInitializer.DEFAULT_ADMIN_EMAIL);
|
||||||
@@ -100,7 +106,7 @@ class AdminSeedFailClosedTest {
|
|||||||
CommandLineRunner runner = initializer.initAdminUser(passwordEncoder);
|
CommandLineRunner runner = initializer.initAdminUser(passwordEncoder);
|
||||||
runner.run();
|
runner.run();
|
||||||
|
|
||||||
verify(userRepository).save(org.mockito.ArgumentMatchers.any(AppUser.class));
|
verify(userRepository).save(any(AppUser.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -120,4 +126,49 @@ class AdminSeedFailClosedTest {
|
|||||||
// Importantly, no IllegalStateException — re-deploys must not panic over
|
// Importantly, no IllegalStateException — re-deploys must not panic over
|
||||||
// historical default-seeded data they cannot retroactively fix.
|
// historical default-seeded data they cannot retroactively fix.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reuses_existing_Administrators_group_when_seeding_a_new_admin() throws Exception {
|
||||||
|
// Setup: admin user does not exist, but the Administrators group does
|
||||||
|
// (e.g. previous boot seeded the group then failed; operator deleted
|
||||||
|
// the bad user row to retry with a corrected APP_ADMIN_USERNAME). The
|
||||||
|
// re-seed must reuse the group, not blind-INSERT a duplicate. See #518.
|
||||||
|
UserGroup existingGroup = UserGroup.builder()
|
||||||
|
.name("Administrators")
|
||||||
|
.build();
|
||||||
|
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.findByName("Administrators")).thenReturn(Optional.of(existingGroup));
|
||||||
|
when(environment.matchesProfiles("dev", "test", "e2e")).thenReturn(false);
|
||||||
|
when(passwordEncoder.encode(anyString())).thenReturn("$2a$10$stub");
|
||||||
|
ReflectionTestUtils.setField(initializer, "adminEmail", "admin@archiv.raddatz.cloud");
|
||||||
|
ReflectionTestUtils.setField(initializer, "adminPassword", "a-real-strong-password");
|
||||||
|
|
||||||
|
CommandLineRunner runner = initializer.initAdminUser(passwordEncoder);
|
||||||
|
runner.run();
|
||||||
|
|
||||||
|
// Group must not be re-inserted — that would violate user_groups_name_key.
|
||||||
|
verify(groupRepository, never()).save(any(UserGroup.class));
|
||||||
|
// But the admin user IS created, with the existing group attached.
|
||||||
|
org.mockito.ArgumentCaptor<AppUser> captor = org.mockito.ArgumentCaptor.forClass(AppUser.class);
|
||||||
|
verify(userRepository).save(captor.capture());
|
||||||
|
assertThat(captor.getValue().getGroups()).containsExactly(existingGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void creates_Administrators_group_when_seeding_admin_on_a_fresh_database() throws Exception {
|
||||||
|
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.findByName("Administrators")).thenReturn(Optional.empty());
|
||||||
|
when(groupRepository.save(any(UserGroup.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||||
|
when(environment.matchesProfiles("dev", "test", "e2e")).thenReturn(false);
|
||||||
|
when(passwordEncoder.encode(anyString())).thenReturn("$2a$10$stub");
|
||||||
|
ReflectionTestUtils.setField(initializer, "adminEmail", "admin@archiv.raddatz.cloud");
|
||||||
|
ReflectionTestUtils.setField(initializer, "adminPassword", "a-real-strong-password");
|
||||||
|
|
||||||
|
CommandLineRunner runner = initializer.initAdminUser(passwordEncoder);
|
||||||
|
runner.run();
|
||||||
|
|
||||||
|
// Group should be inserted exactly once.
|
||||||
|
verify(groupRepository).save(any(UserGroup.class));
|
||||||
|
verify(userRepository).save(any(AppUser.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user