Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
class OidcAuthTokenPrincipalExtractor implements PrincipalExtractor
{
private static final String JWT_SAP_ID_TYPE_CLAIM = "sap_id_type";
private static final String JWT_SAP_ID_TYPE_USER_VALUE = "user";
private static final String JWT_SUB_CLAIM = "sub";
private static final String JWT_USER_UUID_CLAIM = "user_uuid";
private static final String JWT_EMAIL_CLAIM = "email";

Expand Down Expand Up @@ -42,18 +45,39 @@ public Try<Principal> tryGetCurrentPrincipal()
private Try<String> tryGetPrincipalId( @Nonnull final DecodedJWT jwt )
{
return Try.of(() -> {
final Claim userUuidClaim = jwt.getClaim(JWT_USER_UUID_CLAIM);
// First, try to use the new sap_id_type and sub claims (preferred approach)
final String sapIdType = getClaimAsString(jwt, JWT_SAP_ID_TYPE_CLAIM);
if( JWT_SAP_ID_TYPE_USER_VALUE.equals(sapIdType) ) {
final String sub = getClaimAsString(jwt, JWT_SUB_CLAIM);
if( sub != null ) {
return sub;
}
}

if( userUuidClaim != null && !userUuidClaim.isMissing() && !userUuidClaim.isNull() ) {
return userUuidClaim.asString();
// Fallback to legacy user_uuid claim
final String userUuid = getClaimAsString(jwt, JWT_USER_UUID_CLAIM);
if( userUuid != null ) {
return userUuid;
}

final Claim emailClaim = jwt.getClaim(JWT_EMAIL_CLAIM);
if( emailClaim != null && !emailClaim.isMissing() && !emailClaim.isNull() ) {
return emailClaim.asString();
// Fallback to email claim
final String email = getClaimAsString(jwt, JWT_EMAIL_CLAIM);
if( email != null ) {
return email;
}

throw new PrincipalAccessException("The current JWT does not contain the IAS user uuid or an email.");
throw new PrincipalAccessException(
"The current JWT does not contain a valid principal identifier. "
+ "Expected one of: sap_id_type='user' with sub claim, user_uuid, or email.");
});
}

private String getClaimAsString( @Nonnull final DecodedJWT jwt, @Nonnull final String claimName )
{
final Claim claim = jwt.getClaim(claimName);
if( claim != null && !claim.isMissing() && !claim.isNull() ) {
return claim.asString();
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,56 @@ void testReadPrincipalFromEmailFallback()
assertThat(principal.getPrincipalId()).isEqualTo("fallback@example.com");
}

@Test
void testReadPrincipalFromSapIdTypeAndSub()
{
mockAuthTokenFacade(JWT.create().withClaim("sap_id_type", "user").withClaim("sub", "P123456"));

final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();

assertThat(principal.getPrincipalId()).isEqualTo("P123456");
}

@Test
void testSapIdTypeAndSubPreferredOverUserUuid()
{
mockAuthTokenFacade(
JWT
.create()
.withClaim("sap_id_type", "user")
.withClaim("sub", "preferred-id")
.withClaim("user_uuid", "legacy-uuid"));

final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();

assertThat(principal.getPrincipalId()).isEqualTo("preferred-id");
}

@Test
void testIgnoreSapIdTypeIfNotUser()
{
mockAuthTokenFacade(
JWT
.create()
.withClaim("sap_id_type", "app")
.withClaim("sub", "client-id")
.withClaim("user_uuid", "user-id"));

final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();

assertThat(principal.getPrincipalId()).isEqualTo("user-id");
}

@Test
void testIgnoreSapIdTypeIfSubMissing()
{
mockAuthTokenFacade(JWT.create().withClaim("sap_id_type", "user").withClaim("email", "user@example.com"));

final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();

assertThat(principal.getPrincipalId()).isEqualTo("user@example.com");
}

private void mockAuthTokenFacadeWithMissingAuthToken()
{
AuthTokenAccessor.setAuthTokenFacade(() -> Try.failure(new AuthTokenAccessException("Auth token not mocked.")));
Expand Down
2 changes: 1 addition & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

### ✨ New Functionality

-
- Added support for SAP Cloud Identity Services (SCI) `sap_id_type` and `sub` claims in OIDC principal extraction. When `sap_id_type=user`, the `sub` claim is now used as the [Subject Name Identifier](https://help.sap.com/docs/SAP_DATASPHERE/9f804b8efa8043539289f42f372c4862/fac3155d77154775b919ceba36ffc325.html) (User ID, Email, or Custom Attribute as configured in SCI).

### 📈 Improvements

Expand Down
Loading