Repository: SAP/cloud-security-xsuaa-integration
Pull request #306 (2020-06-16 17:07:59)

[java-security] derive audiences from scopes
this is relevant for apps that migrate from java-security-container client lib and were called with a token of grant type "user_token", which does not provide `aud` claim.

nenaraab: @hassler-d 
thanks for your feedback!
I've incorporated them.

Patch:
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java
index 4ae7113b..12ace393 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java
@@ -3,6 +3,7 @@ package com.sap.cloud.security.token.validation.validators;
 import static com.sap.cloud.security.xsuaa.Assertions.assertHasText;
 
 import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.ValidationResults;
 import com.sap.cloud.security.token.validation.Validator;
@@ -10,9 +11,7 @@ import com.sap.cloud.security.token.validation.Validator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.LinkedHashSet;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Validates if the jwt access token is intended for the OAuth2 client of this
@@ -86,7 +85,7 @@ public class JwtAudienceValidator implements Validator<Token> {
 			if (audience.contains(".")) {
 				// CF UAA derives the audiences from the scopes.
 				// In case the scopes contains namespaces, these needs to be removed.
-				String aud = audience.substring(0, audience.indexOf(DOT)).trim();
+				String aud = extractAppId(audience);
 				if (!aud.isEmpty()) {
 					audiences.add(aud);
 				}
@@ -94,7 +93,25 @@ public class JwtAudienceValidator implements Validator<Token> {
 				audiences.add(audience);
 			}
 		}
+		// extract audience (app-id) from scopes
+		if (audiences.isEmpty()) {
+			for (String scope : token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES)) {
+				if (scope.contains(".")) {
+					audiences.add(extractAppId(scope));
+				}
+			}
+		}
 		return audiences;
 	}
 
+	/**
+	 * In case of audiences, the namespaces are trimmed.
+	 * In case of scopes, the namespaces and the scope names are trimmed.
+	 * @param scopeOrAudience
+	 * @return
+	 */
+	static String extractAppId(String scopeOrAudience) {
+		return scopeOrAudience.substring(0, scopeOrAudience.indexOf(DOT)).trim();
+	}
+
 }
diff --git a/spring-xsuaa/Migration_JavaContainerSecurityProjects.md b/spring-xsuaa/Migration_JavaContainerSecurityProjects.md
index 4eb53982..e6af583b 100644
--- a/spring-xsuaa/Migration_JavaContainerSecurityProjects.md
+++ b/spring-xsuaa/Migration_JavaContainerSecurityProjects.md
@@ -91,9 +91,9 @@ Instead the Xsuaa service instance adds audiences to the issued JSON Web Token (
 Whether the token is issued for your application or not is now validated by the [`XsuaaAudienceValidator`](/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java).
 
 ### Multiple XSUAA Bindings
-You can skip this section, in case your application is bound to only one Xsuaa service instance. The library does not support multiple XSUAA bindings of plan `application` and `broker`. **The Xsuaa service instance of plan `api` is ignored.**
+You can skip this section, in case your application is bound to only one Xsuaa service instance. The `xsuaa-spring-boot-starter` does not support multiple XSUAA bindings of plan `application` and `broker`. **The Xsuaa service instance of plan `api` is ignored.**
 
-Then you need to adapt your **Spring Security Configuration** as following:
+In case of multiple bindings you need to adapt your **Spring Security Configuration** as following:
 
 1. You need to get rid of  `XsuaaServicePropertySourceFactory`, because `XsuaaServicesParser` raises the error.
  * In case you make use of `xsuaa-spring-boot-starter` Spring Boot starter, you need to disable auto-configuration within your `*.properties` / or `*.yaml` file:


Test patch:
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java
index 4e6fb3ed..282e1fe1 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java
@@ -1,13 +1,16 @@
 package com.sap.cloud.security.token.validation.validators;
 
 import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import org.assertj.core.util.Sets;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Set;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -128,4 +131,25 @@ public class JwtAudienceValidatorTest {
 				.isEqualTo("Jwt token with audience [] is not issued for these clientIds: [any].");
 	}
 
+	@Test
+	public void extractAudiencesFromTokenScopes() {
+		ArrayList<String> scopes = new ArrayList();
+		scopes.add("client.read");
+		scopes.add("test1!t1.read");
+		scopes.add("client.write");
+		scopes.add("xsappid.namespace.ns.write");
+		scopes.add("openid");
+
+		// configures token audience
+		Mockito.when(token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES)).thenReturn(scopes);
+		Mockito.when(token.getAudiences()).thenReturn(Sets.newHashSet());
+
+		// configures audience validator with client-id from VCAP_SERVICES
+		Set audiences = JwtAudienceValidator.getAllowedAudiences(token);
+
+		assertThat(audiences.size()).isEqualTo(3);
+		assertThat(audiences).containsExactlyInAnyOrder("test1!t1", "client", "xsappid");
+	}
+
+
 }
\ No newline at end of file