From ab971c3d3b4a56b6051d4c4343397494b0579eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C4=ABga?= <72249435+liga-oz@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:07:35 +0100 Subject: [PATCH] Add scim_id attribute for oidc tokens (#1010) --- CHANGELOG.md | 2 ++ .../sap/cloud/security/token/TokenClaims.java | 6 +---- .../sap/cloud/security/test/JwtGenerator.java | 11 +++++---- .../cloud/security/test/JwtGeneratorTest.java | 24 ++++++++++--------- .../authentication/HybridJwtDecoderTest.java | 2 +- .../authentication/IasJwtDecoderTest.java | 2 +- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db2791cbb7..839e3609a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## 2.13.4 - [spring-xsuaa][spring-security] - Patches [CVE-2022-31692](https://nvd.nist.gov/vuln/detail/CVE-2022-31692) vulnerability in spring security dependency. +- [java-security-test] + - `scim_id` added as default attribute for identity token Jwt generator #### Dependency upgrades * Bump spring.security.version from 5.7.3 to 5.7.5 diff --git a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java index af6e523303..be69597a98 100644 --- a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java +++ b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java @@ -39,6 +39,7 @@ private TokenClaims() { * true if the user physically exists in the IAS user store and IAS is not a * pure proxy which simply forwards all information from the corporate IdP. */ + public static final String SAP_GLOBAL_SCIM_ID = "scim_id"; public static final String SAP_GLOBAL_USER_ID = "user_uuid"; public static final String SAP_GLOBAL_ZONE_ID = "zone_uuid"; // tenant GUID public static final String GROUPS = "groups"; // scim groups @@ -70,9 +71,4 @@ private XSUAA() { public static final String TRUSTED_CLIENT_ID_SUFFIX = "trustedclientidsuffix"; } - // SAP User token - /* - * public final class SAP_ID { private SAP_ID() { } // service_instance_id } - */ - } diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java index 142bf79865..463fb0ce0f 100644 --- a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java +++ b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java @@ -44,8 +44,8 @@ public class JwtGenerator { private final JSONObject jsonHeader = new JSONObject(); private final JSONObject jsonPayload = new JSONObject(); - private SignatureCalculator signatureCalculator; - private Service service; + private final SignatureCalculator signatureCalculator; + private final Service service; private JwtSignatureAlgorithm signatureAlgorithm; private PrivateKey privateKey = RSAKeys.generate().getPrivate(); @@ -149,9 +149,10 @@ private void setDefaultsForNewToken(String azp) { jsonPayload.put(TokenClaims.AUDIENCE, azp); jsonPayload.put(TokenClaims.SAP_GLOBAL_ZONE_ID, DEFAULT_ZONE_ID); jsonPayload.put(TokenClaims.SAP_GLOBAL_USER_ID, DEFAULT_USER_ID); + jsonPayload.put(TokenClaims.SAP_GLOBAL_SCIM_ID, DEFAULT_USER_ID); } else { withClaimValue(TokenClaims.XSUAA.CLIENT_ID, azp); // Client Id left for backward compatibility - jsonPayload.put(TokenClaims.AUDIENCE, Arrays.asList(azp)); + jsonPayload.put(TokenClaims.AUDIENCE, Collections.singletonList(azp)); jsonPayload.put(TokenClaims.XSUAA.ZONE_ID, DEFAULT_ZONE_ID); jsonPayload.put(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE, createJsonObject("{\"enhancer\" : \"XSUAA\"} ")); } @@ -266,14 +267,14 @@ public JwtGenerator withClaimValues(String claimName, String... values) { * * @throws JsonParsingException * if the file does not contain a valid json object. - * @throws IOException + * @throws IllegalArgumentException * when the file cannot be read or does not exist. * @param claimsJsonResource * the resource path to the file containing the claims in json * format, e.g. "/claims.json" * @return the builder object. */ - public JwtGenerator withClaimsFromFile(String claimsJsonResource) throws IOException { + public JwtGenerator withClaimsFromFile(String claimsJsonResource) throws IllegalArgumentException { String claimsJson = read(claimsJsonResource); JSONObject jsonObject = createJsonObject(claimsJson); copyJsonProperties(jsonObject, jsonPayload); diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java index 0419d1b738..aa9bdec7ee 100644 --- a/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java +++ b/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java @@ -89,6 +89,7 @@ public void createIasToken_isNotNull() { .withClaimValue("last_name", "doe") .withClaimValue("email", "john.doe@email.org") .withClaimValue(SAP_GLOBAL_USER_ID, "1234567890") + .withClaimValue(SAP_GLOBAL_SCIM_ID, "scim-1234567890") .withPrivateKey(keys.getPrivate()); Token token = cut.createToken(); @@ -100,6 +101,7 @@ public void createIasToken_isNotNull() { assertThat(token.getClientId()).isEqualTo("T000310"); assertThat(token.getExpiration()).isEqualTo(JwtGenerator.NO_EXPIRE_DATE); assertThat(token.getClaimAsString(SAP_GLOBAL_USER_ID)).isEqualTo("1234567890"); + assertThat(token.getClaimAsString(SAP_GLOBAL_SCIM_ID)).isEqualTo("scim-1234567890"); assertThat(token.getPrincipal().getName()).isEqualTo("1234567890"); String encodedModulusN = Base64.getUrlEncoder() .encodeToString(((RSAPublicKey) keys.getPublic()).getModulus().toByteArray()); @@ -297,7 +299,7 @@ public void loadClaimsFromFile_doesNotContainValidJson_throwsException() throws } @Test - public void loadClaimsFromFile_containsStringClaims() throws IOException { + public void loadClaimsFromFile_containsStringClaims() { final Token token = cut.withClaimsFromFile("/claims.json").createToken(); assertThat(token.getClaimAsString(EMAIL)).isEqualTo("test@uaa.org"); @@ -306,14 +308,14 @@ public void loadClaimsFromFile_containsStringClaims() throws IOException { } @Test - public void loadClaimsFromFile_containsExpirationClaim() throws IOException { + public void loadClaimsFromFile_containsExpirationClaim() { final Token token = cut.withClaimsFromFile("/claims.json").createToken(); assertThat(token.getExpiration()).isEqualTo(Instant.ofEpochSecond(1542416800)); } @Test - public void loadClaimsFromFile_containsJsonObjectClaims() throws IOException { + public void loadClaimsFromFile_containsJsonObjectClaims() { final Token token = cut.withClaimsFromFile("/claims.json").createToken(); JsonObject externalAttributes = token.getClaimAsJsonObject("ext_attr"); @@ -324,7 +326,7 @@ public void loadClaimsFromFile_containsJsonObjectClaims() throws IOException { } @Test - public void loadClaimsFromFile_containsListClaims() throws IOException { + public void loadClaimsFromFile_containsListClaims() { final Token token = cut.withClaimsFromFile("/claims.json").createToken(); assertThat(token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES)) @@ -333,7 +335,7 @@ public void loadClaimsFromFile_containsListClaims() throws IOException { } @Test - public void getInstanceFromFile_overridesTokenPropertiesForTesting() throws IOException { + public void getInstanceFromFile_overridesTokenPropertiesForTesting() { Token token = JwtGenerator.getInstanceFromFile(XSUAA, "/token.json") .createToken(); @@ -342,7 +344,7 @@ public void getInstanceFromFile_overridesTokenPropertiesForTesting() throws IOEx } @Test - public void getInstanceFromFile_loadsJsonData() throws IOException { + public void getInstanceFromFile_loadsJsonData() { Token token = JwtGenerator.getInstanceFromFile(XSUAA, "/token.json") .createToken(); @@ -355,7 +357,7 @@ public void getInstanceFromFile_loadsJsonData() throws IOException { } @Test - public void getInstanceFromFile_noHeader_noErrorAndReadsPayload() throws IOException { + public void getInstanceFromFile_noHeader_noErrorAndReadsPayload() { Token token = JwtGenerator.getInstanceFromFile(XSUAA, "/token_no_header.json") .createToken(); @@ -363,7 +365,7 @@ public void getInstanceFromFile_noHeader_noErrorAndReadsPayload() throws IOExcep } @Test - public void getInstanceFromFile_invalidAlg_throwsException() throws IOException { + public void getInstanceFromFile_invalidAlg_throwsException() { assertThatThrownBy(() -> JwtGenerator.getInstanceFromFile(XSUAA, "/token_invalid_alg.json")) .isInstanceOf(UnsupportedOperationException.class); } @@ -385,7 +387,7 @@ public void createToken_signatureCalculation_NoSuchAlgorithmExceptionTurnedIntoR JwtGenerator instance = JwtGenerator.getInstance(XSUAA, (key, alg, data) -> { throw new NoSuchAlgorithmException(); }); - assertThatThrownBy(() -> instance.createToken()).isInstanceOf(RuntimeException.class); + assertThatThrownBy(instance::createToken).isInstanceOf(RuntimeException.class); } @Test @@ -393,7 +395,7 @@ public void createToken_signatureCalculation_SignatureExceptionTurnedIntoRuntime JwtGenerator instance = JwtGenerator.getInstance(XSUAA, (key, alg, data) -> { throw new SignatureException(); }); - assertThatThrownBy(() -> instance.createToken()).isInstanceOf(RuntimeException.class); + assertThatThrownBy(instance::createToken).isInstanceOf(RuntimeException.class); } @Test @@ -401,7 +403,7 @@ public void createToken_signatureCalculation_InvalidKeyExceptionTurnedIntoRuntim JwtGenerator instance = JwtGenerator.getInstance(XSUAA, (key, alg, data) -> { throw new InvalidKeyException(); }); - assertThatThrownBy(() -> instance.createToken()).isInstanceOf(RuntimeException.class); + assertThatThrownBy(instance::createToken).isInstanceOf(RuntimeException.class); } } diff --git a/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java b/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java index 9f8beacffd..a3cc1490fd 100644 --- a/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java +++ b/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java @@ -42,7 +42,7 @@ void parseJwt() { Jwt jwt = HybridJwtDecoder.parseJwt(jwtGenerator.createToken()); assertEquals(2, jwt.getHeaders().size()); - assertEquals(6, jwt.getClaims().size()); + assertEquals(7, jwt.getClaims().size()); assertEquals(1, jwt.getExpiresAt().compareTo(Instant.now())); assertEquals("theClientId", jwt.getClaim(TokenClaims.AUTHORIZATION_PARTY)); } diff --git a/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/IasJwtDecoderTest.java b/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/IasJwtDecoderTest.java index 49908d34b4..3a6601014c 100644 --- a/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/IasJwtDecoderTest.java +++ b/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/IasJwtDecoderTest.java @@ -42,7 +42,7 @@ void parseJwt() { Jwt jwt = IasJwtDecoder.parseJwt(jwtGenerator.createToken()); assertEquals(2, jwt.getHeaders().size()); - assertEquals(6, jwt.getClaims().size()); + assertEquals(7, jwt.getClaims().size()); assertEquals(1, jwt.getExpiresAt().compareTo(Instant.now())); assertEquals("theClientId", jwt.getClaim(TokenClaims.AUTHORIZATION_PARTY)); }