diff --git a/src/main/java/com/uid2/admin/auth/AdminAuthMiddleware.java b/src/main/java/com/uid2/admin/auth/AdminAuthMiddleware.java index 73542c34..be6031fd 100644 --- a/src/main/java/com/uid2/admin/auth/AdminAuthMiddleware.java +++ b/src/main/java/com/uid2/admin/auth/AdminAuthMiddleware.java @@ -1,27 +1,23 @@ package com.uid2.admin.auth; - import com.okta.jwt.*; import com.uid2.admin.AdminConst; import com.uid2.shared.auth.Role; import io.vertx.core.Handler; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.uid2.shared.audit.AuditParams; import com.uid2.shared.audit.Audit; import java.util.*; public class AdminAuthMiddleware { - private static final Logger LOGGER = LoggerFactory.getLogger(AdminAuthMiddleware.class); + private final Map> roleToOktaGroups = new EnumMap<>(Role.class); private final AuthProvider authProvider; private final String environment; private final boolean isAuthDisabled; private final Audit audit; - final Map> roleToOktaGroups = new EnumMap<>(Role.class); public AdminAuthMiddleware(AuthProvider authProvider, JsonObject config) { this.authProvider = authProvider; this.environment = config.getString("environment", "local"); @@ -59,7 +55,6 @@ public Handler handle(Handler handler, Role... r return this.handle(handler, new AuditParams(), roles); } - private Handler logAndHandle(Handler handler, AuditParams params) { return ctx -> { ctx.addBodyEndHandler(v -> this.audit.log(ctx, params)); @@ -73,6 +68,7 @@ private static class AdminAuthHandler { private final Set allowedRoles; private final Map> roleToOktaGroups; private final AuthProvider authProvider; + private AdminAuthHandler(Handler handler, AuthProvider authProvider, Set allowedRoles, String environment, Map> roleToOktaGroups) { this.environment = environment; @@ -96,6 +92,7 @@ public static String extractBearerToken(String headerValue) { } } } + private boolean isAuthorizedUser(List userAssignedGroups) { for (Role role : allowedRoles) { if (roleToOktaGroups.containsKey(role)) { @@ -109,6 +106,7 @@ private boolean isAuthorizedUser(List userAssignedGroups) { } return false; } + private boolean isAuthorizedService(List scopes) { for (String scope : scopes) { if (allowedRoles.contains(OktaCustomScope.fromName(scope).getRole())) { @@ -117,13 +115,14 @@ private boolean isAuthorizedService(List scopes) { } return false; } + public void handle(RoutingContext rc) { // human user String idToken = null; - if(rc.user() != null && rc.user().principal() != null) { + if (rc.user() != null && rc.user().principal() != null) { idToken = rc.user().principal().getString("id_token"); } - if(idToken != null) { + if (idToken != null) { validateIdToken(rc, idToken); return; } @@ -131,7 +130,7 @@ public void handle(RoutingContext rc) { // machine user String authHeaderValue = rc.request().getHeader("Authorization"); String accessToken = extractBearerToken(authHeaderValue); - if(accessToken == null) { + if (accessToken == null) { rc.response().putHeader("REQUIRES_AUTH", "1").setStatusCode(401).end(); return; } @@ -146,7 +145,7 @@ private void validateAccessToken(RoutingContext rc, String accessToken) { rc.response().setStatusCode(401).end(); return; } - if(jwt.getClaims().get("environment") == null || !jwt.getClaims().get("environment").toString().equals(environment)) { + if (jwt.getClaims().get("environment") == null || !jwt.getClaims().get("environment").toString().equals(environment)) { rc.response().setStatusCode(401).end(); return; } @@ -155,7 +154,7 @@ private void validateAccessToken(RoutingContext rc, String accessToken) { serviceAccountDetails.put("scope", scopes); serviceAccountDetails.put("client_id", jwt.getClaims().get("client_id")); rc.put("user_details", serviceAccountDetails); - if(isAuthorizedService(scopes)) { + if (isAuthorizedService(scopes)) { innerHandler.handle(rc); } else { rc.response().setStatusCode(401).end(); @@ -171,7 +170,7 @@ private void validateIdToken(RoutingContext rc, String idToken) { rc.response().putHeader("REQUIRES_AUTH", "1").setStatusCode(401).end(); return; } - if(jwt.getClaims().get("environment") == null || !jwt.getClaims().get("environment").toString().equals(environment)) { + if (jwt.getClaims().get("environment") == null || !jwt.getClaims().get("environment").toString().equals(environment)) { rc.response().setStatusCode(401).end(); return; } @@ -181,7 +180,7 @@ private void validateIdToken(RoutingContext rc, String idToken) { userDetails.put("email", jwt.getClaims().get("email")); userDetails.put("sub", jwt.getClaims().get("sub")); rc.put("user_details", userDetails); - if(isAuthorizedUser(groups)) { + if (isAuthorizedUser(groups)) { innerHandler.handle(rc); } else { rc.response().setStatusCode(401).end(); diff --git a/src/main/java/com/uid2/admin/salt/SaltRotation.java b/src/main/java/com/uid2/admin/salt/SaltRotation.java index 693b24eb..3cb4cfa0 100644 --- a/src/main/java/com/uid2/admin/salt/SaltRotation.java +++ b/src/main/java/com/uid2/admin/salt/SaltRotation.java @@ -19,7 +19,7 @@ public class SaltRotation { private static final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis(); private static final double MAX_SALT_PERCENTAGE = 0.8; - private final boolean ENABLE_V4_RAW_UID; + private final boolean enableV4RawUid; private final IKeyGenerator keyGenerator; @@ -27,7 +27,7 @@ public class SaltRotation { public SaltRotation(IKeyGenerator keyGenerator, JsonObject config) { this.keyGenerator = keyGenerator; - this.ENABLE_V4_RAW_UID = config.getBoolean(AdminConst.ENABLE_V4_RAW_UID, false); + this.enableV4RawUid = config.getBoolean(AdminConst.ENABLE_V4_RAW_UID, false); } public Result rotateSalts( @@ -143,10 +143,9 @@ private long calculateRefreshFrom(SaltEntry bucket, TargetDate targetDate) { private String calculateCurrentSalt(SaltEntry bucket, boolean shouldRotate) throws Exception { if (shouldRotate) { - if (ENABLE_V4_RAW_UID) { + if (enableV4RawUid) { return null; - } - else { + } else { return this.keyGenerator.generateRandomKeyString(32); } } @@ -165,10 +164,10 @@ private String calculatePreviousSalt(SaltEntry bucket, boolean shouldRotate, Tar private SaltEntry.KeyMaterial calculateCurrentKeySalt(SaltEntry bucket, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception { if (shouldRotate) { - if (ENABLE_V4_RAW_UID) { + if (enableV4RawUid) { return new SaltEntry.KeyMaterial( keyIdGenerator.getNextKeyId(), - this.keyGenerator.generateRandomKeyString(32), + this.keyGenerator.generateRandomKeyString(24), this.keyGenerator.generateRandomKeyString(32) ); } else { @@ -253,7 +252,6 @@ private void logSaltAges(String saltCountType, TargetDate targetDate, Collection } } - /** Logging to monitor migration of buckets from salts (old format - v2/v3) to encryption keys (new format - v4) **/ private void logBucketFormatCount(TargetDate targetDate, SaltEntry[] postRotationBuckets) { int totalKeys = 0, totalSalts = 0, totalPreviousKeys = 0, totalPreviousSalts = 0; 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 b1595dcd..527f19c0 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SaltService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SaltService.java @@ -73,11 +73,11 @@ public void setupRoutes(Router router) { } }, new AuditParams(List.of(), Collections.emptyList()), Role.MAINTAINER)); - router.post(API_SALT_ROTATE.toString()).blockingHandler(auth.handle((ctx) -> { + router.post(API_SALT_ROTATE.toString()).blockingHandler(auth.handle(ctx -> { synchronized (writeLock) { this.handleSaltRotate(ctx); } - }, new AuditParams(List.of("fraction", "min_ages_in_seconds", "target_date"), Collections.emptyList()), Role.SUPER_USER, Role.SECRET_ROTATION)); + }, new AuditParams(List.of("fraction", "target_date"), Collections.emptyList()), Role.SUPER_USER, Role.SECRET_ROTATION)); } private void handleSaltSnapshots(RoutingContext rc) { diff --git a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java index d6f6ceb2..b3c2224f 100644 --- a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java +++ b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java @@ -20,10 +20,13 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -public class SaltServiceTest extends ServiceTestBase { +class SaltServiceTest extends ServiceTestBase { private final TargetDate utcTomorrow = TargetDate.now().plusDays(1); - @Mock RotatingSaltProvider saltProvider; - @Mock SaltRotation saltRotation; + + @Mock + private RotatingSaltProvider saltProvider; + @Mock + private SaltRotation saltRotation; @Override protected IService createService() { @@ -77,7 +80,7 @@ void rotateSalts(Vertx vertx, VertxTestContext testContext) throws Exception { var result = SaltRotation.Result.fromSnapshot(addedSnapshots[0].build()); when(saltRotation.rotateSalts(any(), any(), eq(0.2), eq(utcTomorrow))).thenReturn(result); - post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> { + post(vertx, testContext, "api/salt/rotate?fraction=0.2", "", response -> { assertEquals(200, response.statusCode()); checkSnapshotsResponse(addedSnapshots, new Object[]{response.bodyAsJsonObject()}); verify(saltStoreWriter).upload(any()); @@ -100,7 +103,7 @@ void rotateSaltsNoNewSnapshot(Vertx vertx, VertxTestContext testContext) throws var result = SaltRotation.Result.noSnapshot("test"); when(saltRotation.rotateSalts(any(), any(), eq(0.2), eq(utcTomorrow))).thenReturn(result); - post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> { + post(vertx, testContext, "api/salt/rotate?fraction=0.2", "", response -> { assertEquals(200, response.statusCode()); JsonObject jo = response.bodyAsJsonObject(); assertFalse(jo.containsKey("effective")); @@ -127,7 +130,7 @@ void rotateSaltsWithSpecificTargetDate(Vertx vertx, VertxTestContext testContext var result = SaltRotation.Result.fromSnapshot(addedSnapshots[0].build()); when(saltRotation.rotateSalts(any(), any(), eq(0.2), eq(targetDate()))).thenReturn(result); - post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2&target_date=2025-01-01", "", response -> { + post(vertx, testContext, "api/salt/rotate?fraction=0.2&target_date=2025-01-01", "", response -> { assertEquals(200, response.statusCode()); testContext.completeNow(); }); @@ -135,23 +138,23 @@ void rotateSaltsWithSpecificTargetDate(Vertx vertx, VertxTestContext testContext @Test void rotateSaltsWithDefaultAgeThresholds(Vertx vertx, VertxTestContext testContext) throws Exception { - fakeAuth(Role.SUPER_USER); + fakeAuth(Role.SUPER_USER); 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) + 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 -> { + post(vertx, testContext, "api/salt/rotate?fraction=0.2", "", response -> { verify(saltRotation).rotateSalts(any(), eq(expectedDefaultAgeThresholds), eq(0.2), eq(utcTomorrow)); assertEquals(200, response.statusCode()); testContext.completeNow(); diff --git a/webroot/adm/salt.html b/webroot/adm/salt.html index 77f37a08..01bf2535 100644 --- a/webroot/adm/salt.html +++ b/webroot/adm/salt.html @@ -41,14 +41,6 @@

UID2 Env - Salt Management

defaultValue: defaultTargetDate }; - const minAgesMultilineInput = { - name: 'minAges', - label: 'Min Ages (seconds)', - required: true, - defaultValue: '2592000,5184000,7776000,10368000,12960000,15552000,18144000,20736000,23328000,25920000,28512000,31104000,33696000', - type: 'multi-line' - }; - const operationConfig = { read: [ { @@ -81,16 +73,14 @@

UID2 Env - Salt Management

role: 'superuser', inputs: [ fractionInput, - targetDateInput, - minAgesMultilineInput + targetDateInput ], apiCall: { method: 'POST', getUrl: (inputs) => { - const minAges = encodeURIComponent(inputs.minAges); const fraction = encodeURIComponent(inputs.fraction); const targetDate = encodeURIComponent(inputs.targetDate); - return `/api/salt/rotate?min_ages_in_seconds=${minAges}&fraction=${fraction}&target_date=${targetDate}`; + return `/api/salt/rotate?fraction=${fraction}&target_date=${targetDate}`; } } }