From 3a75f2c8a59908e2857d0b36a890ac5565d0edf5 Mon Sep 17 00:00:00 2001 From: sophia chen Date: Thu, 21 May 2026 11:35:27 +1000 Subject: [PATCH 1/3] feat(auth): add client-key-issuance Okta scope for machine auth Adds a new OktaCustomScope mapped to Role.MAINTAINER so service-account access tokens can call MAINTAINER-protected endpoints (POST /api/client/add, POST /api/site/add, GET /api/site/list, GET /api/client/list/:siteId). This unblocks the /uid2-client-key Claude skill (see UID2-6903) without exposing SUPER_USER or PRIVILEGED operations to the same scope. Tests mirror the existing parameterised SS_PORTAL/SECRET_ROTATION patterns in OktaCustomScopeTest and AdminAuthMiddlewareTest. Refs: UID2-6903 Design: docs/superpowers/specs/2026-05-21-claude-client-key-issuance-design.md Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/java/com/uid2/admin/auth/OktaCustomScope.java | 1 + .../java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java | 7 +++++-- src/test/java/com/uid2/admin/auth/OktaCustomScopeTest.java | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/uid2/admin/auth/OktaCustomScope.java b/src/main/java/com/uid2/admin/auth/OktaCustomScope.java index c60a08141..f35b09508 100644 --- a/src/main/java/com/uid2/admin/auth/OktaCustomScope.java +++ b/src/main/java/com/uid2/admin/auth/OktaCustomScope.java @@ -12,6 +12,7 @@ public enum OktaCustomScope { SITE_SYNC("uid2.admin.site-sync", Role.PRIVATE_OPERATOR_SYNC), METRICS_EXPORT("uid2.admin.metrics-export", Role.METRICS_EXPORT), ENCLAVE_REGISTRAR("uid2.admin.enclave-registrar", Role.ENCLAVE_REGISTRAR), + CLIENT_KEY_ISSUANCE("uid2.admin.client-key-issuance", Role.MAINTAINER), INVALID("invalid", Role.UNKNOWN); private final String name; private final Role role; diff --git a/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java b/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java index 8c9cc49ff..35c013415 100644 --- a/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java +++ b/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java @@ -256,7 +256,9 @@ private static Stream testAccessTokenUnauthorizedData() { Arguments.of(OktaCustomScope.SECRET_ROTATION.getName(), new Role[] {Role.SHARING_PORTAL}), Arguments.of(OktaCustomScope.SECRET_ROTATION.getName(), new Role[] {Role.PRIVATE_OPERATOR_SYNC}), Arguments.of(OktaCustomScope.SITE_SYNC.getName(), new Role[] {Role.SECRET_ROTATION}), - Arguments.of(OktaCustomScope.SITE_SYNC.getName(), new Role[] {Role.SHARING_PORTAL}) + Arguments.of(OktaCustomScope.SITE_SYNC.getName(), new Role[] {Role.SHARING_PORTAL}), + Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE.getName(), new Role[] {Role.SUPER_USER}), + Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE.getName(), new Role[] {Role.PRIVILEGED}) ); } @@ -279,7 +281,8 @@ private static Stream testAccessTokenGoodData() { return Stream.of( Arguments.of(OktaCustomScope.SS_PORTAL, OktaCustomScope.SS_PORTAL.getRole()), Arguments.of(OktaCustomScope.SECRET_ROTATION, OktaCustomScope.SECRET_ROTATION.getRole()), - Arguments.of(OktaCustomScope.SITE_SYNC, OktaCustomScope.SITE_SYNC.getRole()) + Arguments.of(OktaCustomScope.SITE_SYNC, OktaCustomScope.SITE_SYNC.getRole()), + Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE, OktaCustomScope.CLIENT_KEY_ISSUANCE.getRole()) ); } diff --git a/src/test/java/com/uid2/admin/auth/OktaCustomScopeTest.java b/src/test/java/com/uid2/admin/auth/OktaCustomScopeTest.java index 400f3afcc..e01fbf322 100644 --- a/src/test/java/com/uid2/admin/auth/OktaCustomScopeTest.java +++ b/src/test/java/com/uid2/admin/auth/OktaCustomScopeTest.java @@ -14,6 +14,7 @@ private static Stream testFromNameData() { Arguments.of("uid2.admin.ss-portal", OktaCustomScope.SS_PORTAL), Arguments.of("uid2.admin.secret-rotation", OktaCustomScope.SECRET_ROTATION), Arguments.of("uid2.admin.site-sync", OktaCustomScope.SITE_SYNC), + Arguments.of("uid2.admin.client-key-issuance", OktaCustomScope.CLIENT_KEY_ISSUANCE), Arguments.of("dummy", OktaCustomScope.INVALID) ); } From 4b3191af58cdaf5b8e82775158664e2d9e994f5e Mon Sep 17 00:00:00 2001 From: Release Workflow Date: Thu, 21 May 2026 04:05:53 +0000 Subject: [PATCH 2/3] [CI Pipeline] Released Snapshot version: 6.13.39-alpha-247-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00cc0bb6e..8dbaaef2a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-admin - 6.13.38 + 6.13.39-alpha-247-SNAPSHOT UTF-8 From 9069272a071fa47c4eecd0cdedc6e45a10ab0901 Mon Sep 17 00:00:00 2001 From: sophia chen Date: Thu, 11 Jun 2026 15:21:13 +1000 Subject: [PATCH 3/3] refactor(auth): map client-key-issuance scope to Role.CLAUDE_ACCESS CLAUDE_ACCESS is a new fine-grained role (uid2-shared 11.4.21-alpha-349-SNAPSHOT) designed specifically for machine/service-account access. It grants read access to all non-reveal endpoints and add-only POST access, without the full breadth of Role.MAINTAINER. Mapping CLIENT_KEY_ISSUANCE to CLAUDE_ACCESS instead of MAINTAINER tightens the privilege boundary for this Okta scope. Also adds an unauthorized test case asserting that a CLIENT_KEY_ISSUANCE token cannot access MAINTAINER-only endpoints, explicitly verifying the subset constraint. Refs: UID2-6903 Co-Authored-By: Claude Sonnet 4.6 --- pom.xml | 2 +- src/main/java/com/uid2/admin/auth/OktaCustomScope.java | 2 +- src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 8dbaaef2a..354acd5f9 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.12.2 5.11.2 - 11.4.16 + 11.4.21-alpha-349-SNAPSHOT 0.5.10 4.1.133.Final ${project.version} diff --git a/src/main/java/com/uid2/admin/auth/OktaCustomScope.java b/src/main/java/com/uid2/admin/auth/OktaCustomScope.java index f35b09508..00b496bb7 100644 --- a/src/main/java/com/uid2/admin/auth/OktaCustomScope.java +++ b/src/main/java/com/uid2/admin/auth/OktaCustomScope.java @@ -12,7 +12,7 @@ public enum OktaCustomScope { SITE_SYNC("uid2.admin.site-sync", Role.PRIVATE_OPERATOR_SYNC), METRICS_EXPORT("uid2.admin.metrics-export", Role.METRICS_EXPORT), ENCLAVE_REGISTRAR("uid2.admin.enclave-registrar", Role.ENCLAVE_REGISTRAR), - CLIENT_KEY_ISSUANCE("uid2.admin.client-key-issuance", Role.MAINTAINER), + CLIENT_KEY_ISSUANCE("uid2.admin.client-key-issuance", Role.CLAUDE_ACCESS), INVALID("invalid", Role.UNKNOWN); private final String name; private final Role role; diff --git a/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java b/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java index 35c013415..e2a15507f 100644 --- a/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java +++ b/src/test/java/com/uid2/admin/auth/AdminAuthMiddlewareTest.java @@ -258,7 +258,8 @@ private static Stream testAccessTokenUnauthorizedData() { Arguments.of(OktaCustomScope.SITE_SYNC.getName(), new Role[] {Role.SECRET_ROTATION}), Arguments.of(OktaCustomScope.SITE_SYNC.getName(), new Role[] {Role.SHARING_PORTAL}), Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE.getName(), new Role[] {Role.SUPER_USER}), - Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE.getName(), new Role[] {Role.PRIVILEGED}) + Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE.getName(), new Role[] {Role.PRIVILEGED}), + Arguments.of(OktaCustomScope.CLIENT_KEY_ISSUANCE.getName(), new Role[] {Role.MAINTAINER}) ); }