Skip to content

Commit 8aa4216

Browse files
[Security] Support new sap_id_typeclaim (#1124)
1 parent f96e819 commit 8aa4216

3 files changed

Lines changed: 82 additions & 8 deletions

File tree

cloudplatform/security/src/main/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractor.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
class OidcAuthTokenPrincipalExtractor implements PrincipalExtractor
1717
{
18+
private static final String JWT_SAP_ID_TYPE_CLAIM = "sap_id_type";
19+
private static final String JWT_SAP_ID_TYPE_USER_VALUE = "user";
20+
private static final String JWT_SUB_CLAIM = "sub";
1821
private static final String JWT_USER_UUID_CLAIM = "user_uuid";
1922
private static final String JWT_EMAIL_CLAIM = "email";
2023

@@ -42,18 +45,39 @@ public Try<Principal> tryGetCurrentPrincipal()
4245
private Try<String> tryGetPrincipalId( @Nonnull final DecodedJWT jwt )
4346
{
4447
return Try.of(() -> {
45-
final Claim userUuidClaim = jwt.getClaim(JWT_USER_UUID_CLAIM);
48+
// First, try to use the new sap_id_type and sub claims (preferred approach)
49+
final String sapIdType = getClaimAsString(jwt, JWT_SAP_ID_TYPE_CLAIM);
50+
if( JWT_SAP_ID_TYPE_USER_VALUE.equals(sapIdType) ) {
51+
final String sub = getClaimAsString(jwt, JWT_SUB_CLAIM);
52+
if( sub != null ) {
53+
return sub;
54+
}
55+
}
4656

47-
if( userUuidClaim != null && !userUuidClaim.isMissing() && !userUuidClaim.isNull() ) {
48-
return userUuidClaim.asString();
57+
// Fallback to legacy user_uuid claim
58+
final String userUuid = getClaimAsString(jwt, JWT_USER_UUID_CLAIM);
59+
if( userUuid != null ) {
60+
return userUuid;
4961
}
5062

51-
final Claim emailClaim = jwt.getClaim(JWT_EMAIL_CLAIM);
52-
if( emailClaim != null && !emailClaim.isMissing() && !emailClaim.isNull() ) {
53-
return emailClaim.asString();
63+
// Fallback to email claim
64+
final String email = getClaimAsString(jwt, JWT_EMAIL_CLAIM);
65+
if( email != null ) {
66+
return email;
5467
}
5568

56-
throw new PrincipalAccessException("The current JWT does not contain the IAS user uuid or an email.");
69+
throw new PrincipalAccessException(
70+
"The current JWT does not contain a valid principal identifier. "
71+
+ "Expected one of: sap_id_type='user' with sub claim, user_uuid, or email.");
5772
});
5873
}
74+
75+
private String getClaimAsString( @Nonnull final DecodedJWT jwt, @Nonnull final String claimName )
76+
{
77+
final Claim claim = jwt.getClaim(claimName);
78+
if( claim != null && !claim.isMissing() && !claim.isNull() ) {
79+
return claim.asString();
80+
}
81+
return null;
82+
}
5983
}

cloudplatform/security/src/test/java/com/sap/cloud/sdk/cloudplatform/security/principal/OidcAuthTokenPrincipalExtractorTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,56 @@ void testReadPrincipalFromEmailFallback()
6666
assertThat(principal.getPrincipalId()).isEqualTo("fallback@example.com");
6767
}
6868

69+
@Test
70+
void testReadPrincipalFromSapIdTypeAndSub()
71+
{
72+
mockAuthTokenFacade(JWT.create().withClaim("sap_id_type", "user").withClaim("sub", "P123456"));
73+
74+
final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();
75+
76+
assertThat(principal.getPrincipalId()).isEqualTo("P123456");
77+
}
78+
79+
@Test
80+
void testSapIdTypeAndSubPreferredOverUserUuid()
81+
{
82+
mockAuthTokenFacade(
83+
JWT
84+
.create()
85+
.withClaim("sap_id_type", "user")
86+
.withClaim("sub", "preferred-id")
87+
.withClaim("user_uuid", "legacy-uuid"));
88+
89+
final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();
90+
91+
assertThat(principal.getPrincipalId()).isEqualTo("preferred-id");
92+
}
93+
94+
@Test
95+
void testIgnoreSapIdTypeIfNotUser()
96+
{
97+
mockAuthTokenFacade(
98+
JWT
99+
.create()
100+
.withClaim("sap_id_type", "app")
101+
.withClaim("sub", "client-id")
102+
.withClaim("user_uuid", "user-id"));
103+
104+
final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();
105+
106+
assertThat(principal.getPrincipalId()).isEqualTo("user-id");
107+
}
108+
109+
@Test
110+
void testIgnoreSapIdTypeIfSubMissing()
111+
{
112+
mockAuthTokenFacade(JWT.create().withClaim("sap_id_type", "user").withClaim("email", "user@example.com"));
113+
114+
final Principal principal = new OidcAuthTokenPrincipalExtractor().tryGetCurrentPrincipal().get();
115+
116+
assertThat(principal.getPrincipalId()).isEqualTo("user@example.com");
117+
}
118+
69119
private void mockAuthTokenFacadeWithMissingAuthToken()
70120
{
71121
AuthTokenAccessor.setAuthTokenFacade(() -> Try.failure(new AuthTokenAccessException("Auth token not mocked.")));

release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
### ✨ New Functionality
1414

15-
-
15+
- 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).
1616

1717
### 📈 Improvements
1818

0 commit comments

Comments
 (0)