diff --git a/pom.xml b/pom.xml
index 014aed45..59fc0859 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.uid2
uid2-admin
- 6.9.0
+ 6.9.1-alpha-202-SNAPSHOT
UTF-8
diff --git a/src/main/java/com/uid2/admin/AdminConst.java b/src/main/java/com/uid2/admin/AdminConst.java
index c4296d3e..c507cd58 100644
--- a/src/main/java/com/uid2/admin/AdminConst.java
+++ b/src/main/java/com/uid2/admin/AdminConst.java
@@ -6,4 +6,5 @@ public class AdminConst {
public static final String ROLE_OKTA_GROUP_MAP_PRIVILEGED = "role_okta_group_map_privileged";
public static final String ROLE_OKTA_GROUP_MAP_SUPER_USER = "role_okta_group_map_super_user";
public static final String ENABLE_SALT_ROTATION_REFRESH_FROM = "enable_salt_rotation_refresh_from";
+ public static final String ENABLE_SALT_ROTATION_CUSTOM_AGE_THRESHOLDS = "enable_salt_rotation_custom_age_thresholds";
}
diff --git a/src/main/java/com/uid2/admin/salt/SaltRotation.java b/src/main/java/com/uid2/admin/salt/SaltRotation.java
index adcdf151..cd2887d0 100644
--- a/src/main/java/com/uid2/admin/salt/SaltRotation.java
+++ b/src/main/java/com/uid2/admin/salt/SaltRotation.java
@@ -23,11 +23,17 @@ public class SaltRotation {
private final IKeyGenerator keyGenerator;
private final boolean isRefreshFromEnabled;
+ private final boolean isCustomAgeThresholdEnabled;
private static final Logger LOGGER = LoggerFactory.getLogger(SaltRotation.class);
public SaltRotation(JsonObject config, IKeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
this.isRefreshFromEnabled = config.getBoolean(AdminConst.ENABLE_SALT_ROTATION_REFRESH_FROM, false);
+ this.isCustomAgeThresholdEnabled = config.getBoolean(AdminConst.ENABLE_SALT_ROTATION_CUSTOM_AGE_THRESHOLDS, false);
+ }
+
+ public boolean isCustomAgeThresholdEnabled() {
+ return this.isCustomAgeThresholdEnabled;
}
public Result rotateSalts(
diff --git a/src/main/java/com/uid2/admin/vertx/service/SaltService.java b/src/main/java/com/uid2/admin/vertx/service/SaltService.java
index fe9c76f7..05862cda 100644
--- a/src/main/java/com/uid2/admin/vertx/service/SaltService.java
+++ b/src/main/java/com/uid2/admin/vertx/service/SaltService.java
@@ -21,15 +21,28 @@
import java.time.*;
import java.time.format.DateTimeFormatter;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
import static com.uid2.admin.vertx.Endpoints.*;
public class SaltService implements IService {
private static final Logger LOGGER = LoggerFactory.getLogger(SaltService.class);
+ private static final Duration[] SALT_ROTATION_AGE_THRESHOLDS = new Duration[]{
+ Duration.ofDays(30),
+ Duration.ofDays(60),
+ Duration.ofDays(90),
+ Duration.ofDays(120),
+ Duration.ofDays(150),
+ Duration.ofDays(180),
+ Duration.ofDays(210),
+ Duration.ofDays(240),
+ Duration.ofDays(270),
+ Duration.ofDays(300),
+ Duration.ofDays(330),
+ Duration.ofDays(360),
+ Duration.ofDays(390)
+ };
private final AdminAuthMiddleware auth;
private final WriteLock writeLock;
@@ -37,6 +50,7 @@ public class SaltService implements IService {
private final RotatingSaltProvider saltProvider;
private final SaltRotation saltRotation;
+
public SaltService(AdminAuthMiddleware auth,
WriteLock writeLock,
SaltStoreWriter storageManager,
@@ -117,8 +131,15 @@ private void handleSaltRotate(RoutingContext rc) {
try {
final Optional fraction = RequestUtil.getDouble(rc, "fraction");
if (fraction.isEmpty()) return;
- final Duration[] minAges = RequestUtil.getDurations(rc, "min_ages_in_seconds");
- if (minAges == null) return;
+
+ final Duration[] ageThresholds;
+ if (saltRotation.isCustomAgeThresholdEnabled()) {
+ ageThresholds = RequestUtil.getDurations(rc, "min_ages_in_seconds");
+ if (ageThresholds == null) return;
+ } else {
+ ageThresholds = SALT_ROTATION_AGE_THRESHOLDS;
+ }
+ LOGGER.info("Salt rotation age thresholds in seconds: {}", Arrays.stream(ageThresholds).map(Duration::toSeconds).collect(Collectors.toList()));
final TargetDate targetDate =
RequestUtil.getDate(rc, "target_date", DateTimeFormatter.ISO_LOCAL_DATE)
@@ -134,7 +155,7 @@ private void handleSaltRotate(RoutingContext rc) {
final List snapshots = saltProvider.getSnapshots();
final RotatingSaltProvider.SaltSnapshot lastSnapshot = snapshots.getLast();
- final SaltRotation.Result result = saltRotation.rotateSalts(lastSnapshot, minAges, fraction.get(), targetDate);
+ final SaltRotation.Result result = saltRotation.rotateSalts(lastSnapshot, ageThresholds, fraction.get(), targetDate);
if (!result.hasSnapshot()) {
ResponseUtil.error(rc, 200, result.getReason());
return;
diff --git a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java
index 7d4755f0..508d3ab2 100644
--- a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java
+++ b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java
@@ -11,7 +11,7 @@
import io.vertx.junit5.VertxTestContext;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
-
+import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
@@ -111,7 +111,7 @@ void rotateSaltsNoNewSnapshot(Vertx vertx, VertxTestContext testContext) throws
}
@Test
- void rotateSaltsWitnSpecificTargetDate(Vertx vertx, VertxTestContext testContext) throws Exception {
+ void rotateSaltsWithSpecificTargetDate(Vertx vertx, VertxTestContext testContext) throws Exception {
fakeAuth(Role.SUPER_USER);
final SaltSnapshotBuilder[] snapshots = {
SaltSnapshotBuilder.start().effective(daysEarlier(5)).expires(daysEarlier(4)).entries(10, daysEarlier(5)),
@@ -133,6 +133,75 @@ void rotateSaltsWitnSpecificTargetDate(Vertx vertx, VertxTestContext testContext
});
}
+ @Test
+ void rotateSaltsWithCustomAgeThresholdsEnabled(Vertx vertx, VertxTestContext testContext) throws Exception {
+ fakeAuth(Role.SUPER_USER);
+
+ when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(true);
+
+ final SaltSnapshotBuilder lastSnapshot = SaltSnapshotBuilder.start().effective(daysEarlier(1)).expires(daysLater(6)).entries(1, daysEarlier(1));
+ setSnapshots(lastSnapshot);
+
+ var result = SaltRotation.Result.fromSnapshot(SaltSnapshotBuilder.start().effective(targetDate()).expires(daysEarlier(7)).entries(1, targetDate()).build());
+
+ Duration[] expectedCustomAgeThresholds = new Duration[]{
+ Duration.ofSeconds(50),
+ Duration.ofSeconds(60),
+ Duration.ofSeconds(70)
+ };
+
+ when(saltRotation.rotateSalts(any(), eq(expectedCustomAgeThresholds), eq(0.2), eq(utcTomorrow))).thenReturn(result);
+
+ post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> {
+ verify(saltRotation).rotateSalts(any(), eq(expectedCustomAgeThresholds), eq(0.2), eq(utcTomorrow));
+ assertEquals(200, response.statusCode());
+ testContext.completeNow();
+ });
+ }
+
+ @Test
+ void rotateSaltsWithDefaultAgeThresholds(Vertx vertx, VertxTestContext testContext) throws Exception {
+ fakeAuth(Role.SUPER_USER);
+
+ when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(false);
+
+ final SaltSnapshotBuilder lastSnapshot = SaltSnapshotBuilder.start().effective(daysEarlier(1)).expires(daysLater(6)).entries(1, daysEarlier(1));
+ setSnapshots(lastSnapshot);
+
+ var result = SaltRotation.Result.fromSnapshot(SaltSnapshotBuilder.start().effective(targetDate()).expires(daysEarlier(7)).entries(1, targetDate()).build());
+
+ Duration[] expectedDefaultAgeThresholds = new Duration[]{
+ Duration.ofDays(30), Duration.ofDays(60), Duration.ofDays(90), Duration.ofDays(120),
+ Duration.ofDays(150), Duration.ofDays(180), Duration.ofDays(210), Duration.ofDays(240),
+ Duration.ofDays(270), Duration.ofDays(300), Duration.ofDays(330), Duration.ofDays(360),
+ Duration.ofDays(390)
+ };
+
+ when(saltRotation.rotateSalts(any(), eq(expectedDefaultAgeThresholds), eq(0.2), eq(utcTomorrow))).thenReturn(result);
+
+ post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> {
+ verify(saltRotation).rotateSalts(any(), eq(expectedDefaultAgeThresholds), eq(0.2), eq(utcTomorrow));
+ assertEquals(200, response.statusCode());
+ testContext.completeNow();
+ });
+ }
+
+ @Test
+ void rotateSaltsWithCustomAgeThresholdsEnabledButMissingParameter(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.SUPER_USER);
+
+ when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(true);
+
+ final SaltSnapshotBuilder lastSnapshot = SaltSnapshotBuilder.start().effective(daysEarlier(1)).expires(daysLater(6)).entries(1, daysEarlier(1));
+ setSnapshots(lastSnapshot);
+
+ post(vertx, testContext, "api/salt/rotate?fraction=0.2", "", response -> {
+ verify(saltRotation, never()).rotateSalts(any(), any(), anyDouble(), any());
+ assertEquals(400, response.statusCode());
+ testContext.completeNow();
+ });
+ }
+
private void checkSnapshotsResponse(SaltSnapshotBuilder[] expectedSnapshots, Object[] actualSnapshots) {
assertEquals(expectedSnapshots.length, actualSnapshots.length);
for (int i = 0; i < expectedSnapshots.length; ++i) {