1
0

add SSHA+Argon2 hashed password to accounts profile and validate profile activation (#203)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/203
Reviewed-by: Marc Sandlus <hsh-marcsandlus@noreply.dev.hostsharing.net>
This commit is contained in:
Michael Hoennig
2025-09-17 12:14:47 +02:00
parent bae13d5503
commit 4994341232
32 changed files with 786 additions and 128 deletions
@@ -0,0 +1,86 @@
package net.hostsharing.hsadminng.hash;
import lombok.val;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class Base64UtilsUnitTest {
@Test
void testNullInput() {
val given = (String) null;
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Null input should not be valid Base64").isFalse();
}
@Test
void testEmptyInput() {
val given = "";
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Empty string should not be valid Base64").isFalse();
}
@Test
void testValidBase64WithoutPadding() {
val given = "U29tZU5vbmRLZXk"; // 'SomeNonKey' in Base64
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Valid Base64 string without padding should be identified as valid").isTrue();
}
@Test
void testValidBase64WithPadding() {
val given = "U29tZSBLZXk="; // 'Some Key' in Base64
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Valid Base64 string with padding should be identified as valid").isTrue();
}
@Test
void testValidBase64WithDoublePadding() {
val given = "U29tZQ=="; // 'Some' in Base64
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Valid Base64 string with double padding should be identified as valid").isTrue();
}
@Test
void testInvalidBase64LengthNotDivisibleByFour() {
val given = "U29tZQ="; // Invalid length for Base64 with padding
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Input with invalid length not divisible by 4 should not be valid Base64").isFalse();
}
@Test
void testInvalidBase64WithSpecialCharacters() {
val given = "U29tZQ$$"; // Contains invalid characters
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Input containing invalid characters should not be valid Base64").isFalse();
}
@Test
void testInvalidBase64WithWhitespace() {
val given = "U29tZ SBs"; // Contains whitespace
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Input containing whitespace should not be valid Base64").isFalse();
}
@Test
void testInvalidBase64WithExcessivePadding() {
val given = "U29tZQ==="; // Too many padding characters
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Input with excessive padding should not be valid Base64").isFalse();
}
@Test
void testEdgeCaseBase64SingleCharacter() {
val given = "U"; // Single valid Base64 character
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Single-character input should not be valid Base64").isFalse();
}
@Test
void testValidBase64EdgeCaseMinimalLengthWithPadding() {
val given = "U2="; // Minimal valid Base64 with padding maps to one byte
val actual = Base64Utils.isBase64(given);
assertThat(actual).as("Base64 input length not divisible by 4 should fail even if contains padding.").isFalse();
}
}
@@ -1,10 +1,15 @@
package net.hostsharing.hsadminng.hash;
import lombok.val;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.charset.Charset;
import java.util.Base64;
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LDAP_ARGON2;
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LDAP_SSHA;
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_SHA512;
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_YESCRYPT;
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.MYSQL_NATIVE;
@@ -12,6 +17,7 @@ import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.SCRAM_SHA25
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
@ExtendWith(MockitoExtension.class)
class HashGeneratorUnitTest {
final String GIVEN_PASSWORD = "given password";
@@ -27,6 +33,89 @@ class HashGeneratorUnitTest {
// SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'test';
final String GIVEN_POSTGRESQL_GENERATED_SCRAM_SHA256_HASH = "SCRAM-SHA-256$4096:m8M12fdSTsKH+ywthTx1Zw==$4vsB1OddRNdsej9NPAFh91MPdtbOPjkQ85LQZS5lV0Q=:NsVpQNx4Ic/8Sqj1dxfBzUAxyF4FCTMpIsI+bOZCTfA=";
@Test
void fetchesHashGeneratorFromEnvVarDefault() {
{
val hash = HashGenerator.fromEnv("NON_EXISTING_ENV_VAR", "{SSHA}").withRandomSalt().hash(GIVEN_PASSWORD);
LdapSshaHash.verifyHash(hash, GIVEN_PASSWORD); // throws exception if wrong
}
{
val hash = HashGenerator.fromEnv("NON_EXISTING_ENV_VAR", "{ARGON2}").withRandomSalt().hash(GIVEN_PASSWORD);
LdapArgon2Hash.verifyHash(hash, GIVEN_PASSWORD); // throws exception if wrong
}
}
@Test
void verifiesPasswordAgainstGeneratedArgon2Hash() {
val hash = HashGenerator.using(LDAP_ARGON2).withSalt(null).hash(GIVEN_PASSWORD);
LdapArgon2Hash.verifyHash(hash, GIVEN_PASSWORD); // throws exception if wrong
}
@Test
void rejectsInvalidPasswordAgainstGeneratedArgon2Hash() {
val hash = HashGenerator.using(LDAP_ARGON2).withSalt(null).hash(GIVEN_PASSWORD);
final var throwable = catchThrowable(() ->
LdapArgon2Hash.verifyHash(hash, GIVEN_PASSWORD+"x") // throws exception if wrong
);
assertThat(throwable).hasMessage("invalid password");
}
@Test
void currentArgon2AdapterIgnoresExplicitSalt() {
val hash = HashGenerator.using(LDAP_ARGON2).withRandomSalt().hash(GIVEN_PASSWORD);
LdapArgon2Hash.verifyHash(hash, GIVEN_PASSWORD); // throws exception if wrong
}
@Test
void avoidsDoubleHashingArgon2AHashPassword() {
val hashedPassword = "{ARGON2}$argon2id$v=19$m=65536,t=3,p=1$pEabRksh7EJQV+OwPR5n7Q$83qQtZe2J8+fteWm7g/uvXksfhJKGsipZFsuAaJtBjs";
val hash = HashGenerator.using(LDAP_ARGON2).hash(hashedPassword);
assertThat(hash).isEqualTo(hashedPassword);
}
@Test
void hashesPasswordWhichLooksLikeArgon2AHashButIsNot() {
val password = "{ARGON2}$argon2id$das-ist-kein-base64-hash";
val hash = HashGenerator.using(LDAP_ARGON2).hash(password);
LdapArgon2Hash.verifyHash(hash, password); // throws exception if wrong
}
@Test
void verifiesPasswordAgainstGeneratedSshaHash() {
val hash = HashGenerator.using(LDAP_SSHA).withRandomSalt().hash(GIVEN_PASSWORD);
LdapSshaHash.verifyHash(hash, GIVEN_PASSWORD); // throws exception if wrong
}
@Test
void avoidsDoubleHashingSshaHashPassword() {
val hashedPassword = "{SSHA}SNBnIh5QomfgrvDLDwBR+JOcc8Y17H+4";
val hash = HashGenerator.using(LDAP_SSHA).withRandomSalt().hash(hashedPassword);
assertThat(hash).isEqualTo(hashedPassword);
}
@Test
void hashesPasswordWhichLooksLikeSshaHashButIsNot() {
val password = "{SSHA}das-ist-kein-base64-hash";
val hash = HashGenerator.using(LDAP_SSHA).withRandomSalt().hash(password);
LdapSshaHash.verifyHash(hash, password); // throws exception if wrong
}
@Test
void verifiesPasswordAgainstRawSshaHashFromOpenLdap() {
val sha512HashFromOpenLdap = "{SSHA}SNBnIh5QomfgrvDLDwBR+JOcc8Y17H+4";
LdapSshaHash.verifyHash(sha512HashFromOpenLdap, "QpoGyCeuC1m5X6ew"); // throws exception if wrong
}
@Test
void rejectsInvalidPasswordAgainstGeneratedSshaHash() {
val hash = HashGenerator.using(LDAP_SSHA).withRandomSalt().hash(GIVEN_PASSWORD);
final var throwable = catchThrowable(() ->
LdapSshaHash.verifyHash(hash, GIVEN_PASSWORD+"x") // throws exception if wrong
);
assertThat(throwable).hasMessage("invalid password");
}
@Test
void verifiesLinuxPasswordAgainstSha512HashFromMkpasswd() {
LinuxEtcShadowHashGenerator.verify(GIVEN_LINUX_GENERATED_SHA512_HASH, GIVEN_PASSWORD); // throws exception if wrong
@@ -9,6 +9,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository;
import net.hostsharing.hsadminng.rbac.subject.RealSubjectEntity;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.jetbrains.annotations.NotNull;
@@ -52,7 +53,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
Context context;
@Autowired
RbacSubjectRepository subjectRepo;
RbacSubjectRepository rbacSubjectRepo;
@Autowired
HsOfficePersonRealRepository realPersonRepo;
@@ -361,9 +362,59 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.body("message", containsString("die eigenen hsadmin-Profile dürfen nicht entfernt werden"));
// @formatter:on
}
@Test
void shouldRejectActivatingProfileForNormalUser() {
// given
context.define("selfregistered-user-drew@hostsharing.org");
val drewProfile = profileRepo.findByCurrentSubject().stream().findFirst().orElseThrow();
val inactiveProfileUuid = createNewInactiveProfile(drewProfile.getPerson()).getSubject().getUuid();
RestAssured // @formatter:off
.given()
.header("Authorization", bearer("selfregistered-user-drew@hostsharing.org"))
.header("Accept-Language", "de")
.contentType(ContentType.JSON)
.body("""
{
"active": true
}
""")
.port(port)
.when()
.patch("http://localhost/api/hs/accounts/profiles/" + inactiveProfileUuid)
.then().log().all().assertThat()
.statusCode(403)
.contentType("application/json")
.body("message", containsString("Only global admins are allowed to activate an inactive profile"));
// @formatter:on
}
}
// Helper methods
private HsProfileEntity createNewInactiveProfile(final HsOfficePersonRealEntity person) {
return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
// only RbacSubject entities can be created
val rbacSubjectEntity = rbacSubjectRepo.create(RbacSubjectEntity.builder()
.name("some-inactive-profile")
.build());
// but we need the RealSubjectEntity to be attached to the profile entity
val realSubjectEntity = em.find(RealSubjectEntity.class, rbacSubjectEntity.getUuid());
val inactiveCopy = HsProfileEntity.builder()
.person(person)
.subject(realSubjectEntity)
.active(false).build();
em.persist(inactiveCopy);
em.flush();
return toCleanup(inactiveCopy);
}).assertSuccessful().returnedValue();
}
private HsOfficePersonRealEntity givenLegalPerson(final String executingSubjectName) {
return jpaAttempt.transacted(() -> {
context.define(executingSubjectName);
@@ -418,16 +469,17 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
) {
return jpaAttempt.transacted(() -> {
context.define(executingSubjectName);
final RbacSubjectEntity rbacSubjectEntity = RbacSubjectEntity.builder()
// only RbacSubject entities can be created
val subject = rbacSubjectRepo.create(RbacSubjectEntity.builder()
.name(newSubjectName)
.build();
val subject = subjectRepo.create(rbacSubjectEntity);
.build());
context.define(subject.getName());
val attachedPerson = em.find(HsOfficePersonRealEntity.class, person.getUuid());
val profileBuilder = HsProfileEntity.builder()
.person(attachedPerson)
.subject(subjectRepo.findByUuid(subject.getUuid()))
.subject(em.find(RealSubjectEntity.class, subject.getUuid()))
.scopes(Set.of());
modifier.accept(profileBuilder);
return toCleanup(profileRepo.save(profileBuilder.build()));
@@ -0,0 +1,79 @@
package net.hostsharing.hsadminng.hs.accounts;
import lombok.val;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.rbac.subject.RealSubjectEntity;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class HsProfileEntityUnitTest {
static final HsProfileEntity GIVEN_PROFILE_ENTITY = HsProfileEntity.builder()
.uuid(UUID.fromString("11111111-1111-1111-1111-111111111111"))
.subject(
RealSubjectEntity.builder().uuid(UUID.randomUUID()).name("testSubject").build())
.person(
HsOfficePersonRealEntity.builder()
.personType(HsOfficePersonType.NATURAL_PERSON)
.familyName("Miller")
.givenName("John")
.build()
)
.emailAddress("john.miller@example.com")
.smsNumber("+49 1234567890")
.globalUid(10001)
.globalUid(20002)
.phonePassword("hello world")
.totpSecrets(List.of("secret1", "secret2"))
.active(true)
.build();
@Test
void toShortStringContainsJustTypeAndQualifier() {
assertThat(GIVEN_PROFILE_ENTITY.toShortString()).isEqualTo("true:john.miller@example.com:20002");
}
@Test
void toStringContainsAllPropertiesExceptUuidAndPasswordHash() {
assertThat(GIVEN_PROFILE_ENTITY.toString()).isEqualTo("profile(true, john.miller@example.com, [secret1, secret2], hello world, +49 1234567890)");
}
@Test
void setPasswordSetsPasswordHash() {
val profile = HsProfileEntity.builder().build();
profile.setPassword("my password");
assertThat(profile.getPasswordHash()).startsWith("{SSHA}");
}
@Test
void acceptsValidSshaPasswordHash() {
val givenSshaHash = "{SSHA}SNBnIh5QomfgrvDLDwBR+JOcc8Y17H+4";
val profile = HsProfileEntity.builder().build();
profile.setPasswordHash(givenSshaHash);
assertThat(profile.getPasswordHash()).isEqualTo(givenSshaHash);
}
@Test
void acceptsValidArgon2PasswordHash() {
val givenArgon2Hash = "{ARGON2}$argon2id$v=19$m=65536,t=3,p=1$pEabRksh7EJQV+OwPR5n7Q$83qQtZe2J8+fteWm7g/uvXksfhJKGsipZFsuAaJtBjs";
val profile = HsProfileEntity.builder().build();
profile.setPasswordHash(givenArgon2Hash);
assertThat(profile.getPasswordHash()).isEqualTo(givenArgon2Hash);
}
@Test
void rejectInvalidPasswordHash() {
val profile = HsProfileEntity.builder().build();
val throwable = assertThrows(
ValidationException.class,
() -> profile.setPasswordHash("{whatever} but not a valid hash"));
assertThat(throwable.getMessage()).isEqualTo("passwordHash must be SSHA or ARGON2 hash valid for LDAP");
}
}
@@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.hs.accounts;
import lombok.val;
import net.hostsharing.hsadminng.rbac.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
@@ -7,6 +8,8 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository;
import net.hostsharing.hsadminng.rbac.subject.RealSubjectEntity;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.hibernate.TransientObjectException;
@@ -48,6 +51,9 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@MockitoBean
HttpServletRequest request;
@Autowired
private RbacSubjectRepository rbacSubjectRepo;
@Autowired
private HsOfficePersonRealRepository personRepo;
@@ -58,9 +64,9 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
private HsProfileScopeRealRepository scopeRealRepo;
// fetched UUIDs from test-data
private RbacSubjectEntity alexSubject;
private RbacSubjectEntity drewSubject;
private RbacSubjectEntity testUserSubject;
private RealSubjectEntity alexSubject;
private RealSubjectEntity drewSubject;
private RealSubjectEntity testUserSubject;
private HsOfficePersonRealEntity drewPerson;
private HsOfficePersonRealEntity testUserPerson;
@@ -106,11 +112,13 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
givenRelation(REPRESENTATIVE)
.withAnchorPersonLike(firstGmbHPerson)
.withHolder(drewPerson)
.withContact("some test contact");
.withContact("some test contact")
.inDatabase();
givenProfile()
.forSubject("first-gmbh")
.forPerson(firstGmbHPerson)
.withEMailAddress("first-gmbh@example.com");
.withEMailAddress("first-gmbh@example.com")
.inDatabase();
// when
final var foundProfile = attempt(
@@ -263,13 +271,13 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
}
private RbacSubjectEntity fetchSubjectByName(final String name) {
final String jpql = "SELECT s FROM RbacSubjectEntity s WHERE s.name = :name";
final Query query = em.createQuery(jpql, RbacSubjectEntity.class);
private RealSubjectEntity fetchSubjectByName(final String name) {
final String jpql = "SELECT s FROM RealSubjectEntity s WHERE s.name = :name";
final Query query = em.createQuery(jpql, RealSubjectEntity.class);
query.setParameter("name", name);
try {
context(SUPERUSER_ALEX_SUBJECT_NAME);
return notNull((RbacSubjectEntity) query.getSingleResult());
return notNull((RealSubjectEntity) query.getSingleResult());
} catch (final NoResultException e) {
throw new AssertionError(
"Failed to find subject with name '" + name + "'. Ensure test data is present.", e);
@@ -331,10 +339,14 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
return this;
}
public HsOfficeRelationRealEntity withContact(String caption) {
public RelationBuilder withContact(String caption) {
this.contact = HsOfficeContactRealEntity.builder()
.caption(caption)
.build();
return this;
}
public HsOfficeRelationRealEntity inDatabase() {
em.persist(contact);
final var relation = HsOfficeRelationRealEntity.builder()
@@ -350,15 +362,17 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
}
private class ProfileBuilder {
private RbacSubjectEntity subject;
private RealSubjectEntity subject;
private HsOfficePersonRealEntity person;
private String emailAddress;
public ProfileBuilder forSubject(String subjectName) {
this.subject = RbacSubjectEntity.builder()
// only the RbacSubject can be created
val rbacSubject = toCleanup(rbacSubjectRepo.create(RbacSubjectEntity.builder()
.name(subjectName)
.build();
em.persist(subject);
toCleanup(subject);
.build()));
// but we need the RealSubject
this.subject = em.find(RealSubjectEntity.class, rbacSubject.getUuid());
return this;
}
@@ -367,7 +381,19 @@ class HsProfileRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
return this;
}
public HsProfileEntity withEMailAddress(String emailAddress) {
public ProfileBuilder withEMailAddress(String emailAddress) {
this.emailAddress = emailAddress;
final var profile = HsProfileEntity.builder()
.uuid(subject.getUuid())
.subject(subject)
.person(em.find(HsOfficePersonRealEntity.class, person.getUuid()))
.emailAddress(emailAddress)
.active(true)
.build();
return this;
}
public HsProfileEntity inDatabase() {
final var profile = HsProfileEntity.builder()
.uuid(subject.getUuid())
@@ -35,13 +35,14 @@ public class CreateProfile extends BaseProfileUseCase<CreateProfile> {
{
"person.uuid": ${Person: %{personGivenName} %{personFamilyName}},
"nickname": ${nickname},
"active": %{active},
"totpSecrets": @{totpSecrets},
"emailAddress": ${emailAddress},
"phonePassword": ${phonePassword},
"smsNumber": ${smsNumber},
"password": ${password},
"totpSecrets": @{totpSecrets},
"phonePassword": ${phonePassword},
"globalUid": %{globalUid},
"globalGid": %{globalGid},
"active": %{active},
"scopes": @{resolvedScopes}
}
"""))
@@ -74,13 +74,14 @@ class ProfileScenarioTests extends ScenarioTest {
// a login name, to be stored in the new RBAC subject
.given("nickname", "firby-susan")
// initial profile
.given("active", true)
.given("totpSecrets", Array.of("initialSecret"))
.given("emailAddress", "susan.firby@example.com")
.given("phonePassword", "securePass123")
.given("smsNumber", "+49123456789")
.given("password", "my raw password")
.given("totpSecrets", Array.of("initialSecret"))
.given("phonePassword", "securePass123")
.given("globalUid", 21011)
.given("globalGid", 21011)
.given("active", true)
.given(
"scopes", Array.of(
Pair.of("HSADMIN", "prod")
@@ -100,6 +101,7 @@ class ProfileScenarioTests extends ScenarioTest {
.given("active", false)
.given("totpSecrets", Array.of("initialSecret", "additionalSecret"))
.given("emailAddress", "susan.firby@example.org")
.given("password", "my new raw password")
.given("phonePassword", "securePass987")
.given("smsNumber", "+49987654321")
.given(
@@ -24,8 +24,8 @@ import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.UUID;
import static java.util.UUID.randomUUID;
import static net.hostsharing.hsadminng.config.JwtFakeBearer.bearer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
@@ -491,7 +491,8 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
return jpaAttempt.transacted(() -> {
final String newUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
context(null);
return rbacSubjectRepository.create(new RbacSubjectEntity(UUID.randomUUID(), newUserName));
return rbacSubjectRepository.create(
RbacSubjectEntity.builder().uuid(randomUUID()).name(newUserName).build());
}).returnedValue();
}
@@ -322,13 +322,13 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
return jpaAttempt.transacted(() -> {
final var newUserName = "test-user-" + System.currentTimeMillis() + "@example.com";
context(null);
return rbacSubjectRepository.create(new RbacSubjectEntity(null, newUserName));
return rbacSubjectRepository.create(RbacSubjectEntity.builder().name(newUserName).build());
}).assumeSuccessful().returnedValue();
}
private RbacSubjectEntity createNewUser() {
return rbacSubjectRepository.create(
new RbacSubjectEntity(null, "test-user-" + System.currentTimeMillis() + "@example.com"));
RbacSubjectEntity.builder().name("test-user-" + System.currentTimeMillis() + "@example.com").build());
}
void exactlyTheseRbacGrantsAreReturned(final List<RbacGrantEntity> actualResult, final String... expectedGrant) {
@@ -16,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
import static java.util.UUID.randomUUID;
import static net.hostsharing.hsadminng.config.JwtFakeBearer.bearer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.allOf;
@@ -454,7 +455,8 @@ class RbacSubjectControllerAcceptanceTest {
final var givenUserName = "test-user-" + System.currentTimeMillis() + "@example.com";
final var givenUser = jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
return rbacSubjectRepository.create(new RbacSubjectEntity(UUID.randomUUID(), givenUserName));
return rbacSubjectRepository.create(
RbacSubjectEntity.builder().uuid(randomUUID()).name(givenUserName).build());
}).assumeSuccessful().returnedValue();
assertThat(rbacSubjectRepository.findByName(givenUser.getName())).isNotNull();
return givenUser;
@@ -4,14 +4,14 @@ import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class RbacSubjectEntityUnitTest {
RbacSubjectEntity givenUser = new RbacSubjectEntity(UUID.randomUUID(), "test@example.org");
RbacSubjectEntity givenUser = RbacSubjectEntity.builder().uuid(randomUUID()).name("test@example.org").build();
@Test
void generatedAccessCodeMatchesDefinedPattern() {
@@ -56,7 +56,7 @@ class RbacSubjectRepositoryIntegrationTest extends ContextBasedTest {
// when:
final var result = jpaAttempt.transacted(() -> {
context(null);
return rbacSubjectRepository.create(new RbacSubjectEntity(givenUuid, newUserName));
return rbacSubjectRepository.create(RbacSubjectEntity.builder().uuid(givenUuid).name(newUserName).build());
});
// then:
@@ -395,7 +395,8 @@ class RbacSubjectRepositoryIntegrationTest extends ContextBasedTest {
final var givenUserName = "test-user-" + System.currentTimeMillis() + "@example.com";
final var givenUser = jpaAttempt.transacted(() -> {
context(null);
return rbacSubjectRepository.create(new RbacSubjectEntity(UUID.randomUUID(), givenUserName));
return rbacSubjectRepository.create(
RbacSubjectEntity.builder().uuid(UUID.randomUUID()).name(givenUserName).build());
}).assumeSuccessful().returnedValue();
assertThat(rbacSubjectRepository.findByName(givenUser.getName())).isNotNull();
return givenUser;
@@ -9,6 +9,6 @@ public class TestRbacSubject {
static final RbacSubjectEntity userBbb = rbacRole("customer-admin@bbb.example.com");
static public RbacSubjectEntity rbacRole(final String userName) {
return new RbacSubjectEntity(randomUUID(), userName);
return RbacSubjectEntity.builder().uuid(randomUUID()).name(userName).build();
}
}