diff --git a/cloudplatform/security/src/main/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractor.java b/cloudplatform/security/src/main/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractor.java index 4e4d399c1..02d7fbb04 100644 --- a/cloudplatform/security/src/main/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractor.java +++ b/cloudplatform/security/src/main/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractor.java @@ -16,6 +16,7 @@ class OidcAuthTokenPrincipalExtractor implements PrincipalExtractor { private static final String JWT_USER_UUID_CLAIM = "user_uuid"; + private static final String JWT_EMAIL_CLAIM = "email"; @Override @Nonnull @@ -43,11 +44,16 @@ private Try tryGetPrincipalId( @Nonnull final DecodedJWT jwt ) return Try.of(() -> { final Claim userUuidClaim = jwt.getClaim(JWT_USER_UUID_CLAIM); - if( userUuidClaim.isMissing() || userUuidClaim.isNull() ) { - throw new PrincipalAccessException("The current JWT does not contain the IAS user uuid."); + if( userUuidClaim != null && !userUuidClaim.isMissing() && !userUuidClaim.isNull() ) { + return userUuidClaim.asString(); } - return userUuidClaim.asString(); + final Claim emailClaim = jwt.getClaim(JWT_EMAIL_CLAIM); + if( emailClaim != null && !emailClaim.isMissing() && !emailClaim.isNull() ) { + return emailClaim.asString(); + } + + throw new PrincipalAccessException("The current JWT does not contain the IAS user uuid or an email."); }); } } diff --git a/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/AuthTokenDecoderIasTest.java b/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/AuthTokenDecoderIasTest.java index 071a0aadb..a3a43f8cb 100644 --- a/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/AuthTokenDecoderIasTest.java +++ b/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/AuthTokenDecoderIasTest.java @@ -202,4 +202,35 @@ void testCaseInsensitiveBearer() assertThat(jwt).isNotNull(); assertThat(jwt.getClaim("user_uuid").asString()).isEqualTo("5a21dfa1-fd90-45ff-a5d3-d1cfa446d25a"); } + + @Test + void testDecodeSucceedsWithEmailOnly( @Nonnull final WireMockRuntimeInfo wm ) + { + final Token tokenOnlyEmail = + JwtGenerator + .getInstance(Service.IAS, "2aba8ab2-edc3-4666-b2ed-90b2b47e60e3") + .withHeaderParameter(TokenHeader.KEY_ID, "testKey") + .withClaimValue("iss", wm.getHttpBaseUrl()) + .withClaimValues("aud", "1668e3ce-e2b8-4d27-a05b-3db47e7a4152", "2aba8ab2-edc3-4666-b2ed-90b2b47e60e3") + .withClaimValue("sub", "sub-only-email") + // intentionally omit user_uuid + .withClaimValue("email", "only.email@example.com") + .withExpiration(OffsetDateTime.now().plusDays(1).toInstant()) + .withPrivateKey(RSA_KEYS.getPrivate()) + .createToken(); + + final String tokenValue = tokenOnlyEmail.getTokenValue(); + + final RequestHeaderContainer headers = + DefaultRequestHeaderContainer + .fromSingleValueMap(Collections.singletonMap(HttpHeaders.AUTHORIZATION, "Bearer " + tokenValue)); + + final AuthToken authToken = new AuthTokenDecoderDefault().decode(headers).getOrNull(); + + assertThat(authToken).isNotNull(); + + final DecodedJWT jwt = authToken.getJwt(); + assertThat(jwt).isNotNull(); + assertThat(jwt.getClaim("email").asString()).isEqualTo("only.email@example.com"); + } } diff --git a/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractorTest.java b/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractorTest.java index 4709b6fb1..fba71f080 100644 --- a/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractorTest.java +++ b/cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractorTest.java @@ -56,6 +56,16 @@ void testReadPrincipal() assertThat(principal.getPrincipalId()).isEqualTo("principal id"); } + @Test + void testReadPrincipalFromEmailFallback() + { + mockAuthTokenFacade(JWT.create().withClaim("email", "fallback@example.com")); + + final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get(); + + assertThat(principal.getPrincipalId()).isEqualTo("fallback@example.com"); + } + private void mockAuthTokenFacadeWithMissingAuthToken() { AuthTokenAccessor.setAuthTokenFacade(() -> Try.failure(new AuthTokenAccessException("Auth token not mocked."))); diff --git a/release_notes.md b/release_notes.md index e62c759a7..c3ce28128 100644 --- a/release_notes.md +++ b/release_notes.md @@ -14,7 +14,7 @@ ### ✨ New Functionality -- +- Temporary: Use `email` as fallback principal id when `user_uuid` is missing. Will switch to using `sub` once IAS exposes `idtype` (tracked in [SCICAI-1323](https://jira.tools.sap/browse/SCICAI-1323)). ### 📈 Improvements