diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntity.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntity.java index 77899361..2b936431 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntity.java @@ -3,7 +3,12 @@ package net.hostsharing.hsadminng.rbac.rbacuser; import lombok.*; import org.springframework.data.annotation.Immutable; -import javax.persistence.*; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.UUID; @Entity @@ -16,8 +21,35 @@ import java.util.UUID; @AllArgsConstructor public class RbacUserEntity { + private static final int MAX_VALIDITY_DAYS = 21; + private static DateTimeFormatter DATE_FORMAT_WITH_FULLHOUR = DateTimeFormatter.ofPattern("MM-dd-yyyy HH"); + @Id private UUID uuid; private String name; + + public String generateAccessCode() { + return generateAccessCode(LocalDateTime.now()); + } + + public boolean isValidAccessCode(final String accessCode, final int validityHours) { + if (validityHours > 24 * MAX_VALIDITY_DAYS) { + throw new IllegalArgumentException("Max validity (%s days) exceeded.".formatted(MAX_VALIDITY_DAYS)); + } + if (generateAccessCode(LocalDateTime.now().minus(validityHours, ChronoUnit.HOURS)).equals(accessCode)) { + return true; + } + if (validityHours < 0) { + return false; + } + return isValidAccessCode(accessCode, validityHours - 1); + } + + String generateAccessCode(final LocalDateTime timestamp) { + final var compound = name + ":" + uuid + ":" + timestamp.format(DATE_FORMAT_WITH_FULLHOUR); + final var code = String.valueOf(1000000 + Math.abs(compound.hashCode()) % 100000); + return code.substring(1, 4) + ":" + code.substring(4, 7); + } + } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntityUnitTest.java new file mode 100644 index 00000000..b1cff68e --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserEntityUnitTest.java @@ -0,0 +1,69 @@ +package net.hostsharing.hsadminng.rbac.rbacuser; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RbacUserEntityUnitTest { + + RbacUserEntity givenUser = new RbacUserEntity(UUID.randomUUID(), "test@example.org"); + + @Test + void generatedAccessCodeMatchesDefinedPattern() { + final var givenAccessCode = givenUser.generateAccessCode(); + + final var actual = givenAccessCode.matches("[0-9]{3}:[0-9]{3}"); + + assertThat(actual).isTrue(); + } + + @Test + void freshAccessCodeIsValid() { + final var givenAccessCode = givenUser.generateAccessCode(); + + final var actual = givenUser.isValidAccessCode(givenAccessCode, 4); + + assertThat(actual).isTrue(); + } + + @Test + void recentEnoughAccessCodeIsValid() { + final var givenAccessCode = givenUser.generateAccessCode(LocalDateTime.now().minus(4, ChronoUnit.HOURS)); + + final var actual = givenUser.isValidAccessCode(givenAccessCode, 4); + + assertThat(actual).isTrue(); + } + + @Test + void outdatedEnoughAccessCodeIsNotValid() { + final var givenAccessCode = givenUser.generateAccessCode(LocalDateTime.now().minus(5, ChronoUnit.HOURS)); + + final var actual = givenUser.isValidAccessCode(givenAccessCode, 4); + + assertThat(actual).isFalse(); + } + + @Test + void noExceptionIsThrowIfMaxValidityIsNotExceeded() { + final var givenAccessCode = givenUser.generateAccessCode(); + + givenUser.isValidAccessCode(givenAccessCode, 24 * 21); + } + + @Test + void illegalArgumentExceptionIsThrowIfMaxValidityIsExceeded() { + final var givenAccessCode = givenUser.generateAccessCode(); + + assertThatThrownBy(() -> { + givenUser.isValidAccessCode(givenAccessCode, 24 * 21 + 1); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Max validity (21 days) exceeded."); + } +}