Repository: SAP/cloud-security-xsuaa-integration
Pull request #278 (2020-05-08 20:19:26)

BugFix: NullpointerException when VCAPServices provides no 'plan' 
### Related Github issue
#277 


Patch:
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicesParser.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicesParser.java
index fe9784bc..e62c9404 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicesParser.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicesParser.java
@@ -139,9 +139,9 @@ public class XsuaaServicesParser {
 			JSONObject binding = (JSONObject) jsonArray.get(i);
 			JSONArray tags = (JSONArray) binding.get(TAGS);
 
+			Optional<String> planName = Optional.ofNullable(binding.getAsString("plan"));
+			boolean isApiAccessPlan = (planName.isPresent() && planName.get().equals("apiaccess")) ? true : false;
 			for (int j = 0; j < tags.size(); j++) {
-				boolean isApiAccessPlan = binding.getAsString("plan").equals("apiaccess");
-
 				if (tags.get(j).equals(tag) && !isApiAccessPlan) {
 					if (xsuaaBinding == null) {
 						xsuaaBinding = binding;
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractor.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractor.java
index e8a6f937..366e8040 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractor.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractor.java
@@ -2,6 +2,8 @@ package com.sap.cloud.security.xsuaa.extractor;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -12,30 +14,34 @@ import com.sap.cloud.security.xsuaa.token.XsuaaToken;
 
 public class LocalAuthoritiesExtractor implements AuthoritiesExtractor {
 
-	protected String appId;
+	protected Set<String> appIds = new HashSet<>();
 
-	public LocalAuthoritiesExtractor(String appId) {
-		this.appId = appId;
+	public LocalAuthoritiesExtractor(String... appIds) {
+		Collections.addAll(this.appIds, appIds);
 	}
 
 	@Override
 	public Collection<GrantedAuthority> getAuthorities(XsuaaToken jwt) {
-		Collection<String> scopeAuthorities = getScopes(jwt);
+		Set<String> scopeAuthorities = new HashSet<>();
+
+		appIds.stream().forEach((appId) -> {
+			scopeAuthorities.addAll(getScopes(jwt, appId));
+		});
 
 		Stream<String> authorities = Stream.of(scopeAuthorities).flatMap(Collection::stream);
 
 		return authorities.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
 	}
 
-	protected Collection<String> getScopes(XsuaaToken jwt) {
+	protected Set<String> getScopes(XsuaaToken jwt, String appId) {
 		Collection<String> scopes = jwt.getScopes();
 		if (scopes == null) {
-			return Collections.emptyList();
+			return Collections.emptySet();
 		}
 		return scopes.stream()
 				.filter(scope -> scope.startsWith(appId + "."))
 				.map(scope -> scope.replaceFirst(appId + ".", ""))
-				.collect(Collectors.toList());
+				.collect(Collectors.toSet());
 
 	}
 


Test patch:
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java
index 5ee59258..6631ddfe 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java
@@ -26,4 +26,12 @@ public class XsuaaServicesParserTest {
 		XsuaaServicesParser cut = new XsuaaServicesParser(vcapMultipleBindings);
 		cut.parseCredentials();
 	}
+
+	@Test
+	public void acceptVcapServicesWithoutPlan() throws IOException {
+		String vcapMinimalWoPlan = "{\"xsuaa\":[{\"credentials\":{\"clientid\":\"client-id\",\"clientsecret\":\"client-secret\"},\"tags\":[\"xsuaa\"]}]}";
+		XsuaaServicesParser cut = new XsuaaServicesParser(vcapMinimalWoPlan);
+		Properties properties = cut.parseCredentials();
+		assertThat(properties.getProperty("clientid")).isEqualTo("client-id");
+	}
 }
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractorTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractorTest.java
new file mode 100644
index 00000000..221bcf8f
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/extractor/LocalAuthoritiesExtractorTest.java
@@ -0,0 +1,55 @@
+package com.sap.cloud.security.xsuaa.extractor;
+
+
+import com.sap.cloud.security.xsuaa.token.XsuaaToken;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LocalAuthoritiesExtractorTest {
+    LocalAuthoritiesExtractor cut;
+    XsuaaToken token;
+    Collection<String> scopes = new HashSet<>();
+
+    @Before
+    public void setup() {
+        cut = new LocalAuthoritiesExtractor("appId!1234");
+
+        token = Mockito.mock(XsuaaToken.class);
+        scopes.add("appId!1234.Scope1");
+        scopes.add("appId!1234.Scope2");
+        scopes.add("appId2!888.Scope1");
+        scopes.add("appId2!777.Scope3");
+        Mockito.when(token.getScopes()).thenReturn(scopes);
+    }
+
+    @Test
+    public void extractLocalScopes() {
+        assertThat(cut.getAuthorities(token)).hasSize(2);
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope1"));
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope2"));
+    }
+
+    @Test
+    public void extractLocalScopesOfTwoApps() {
+        cut = new LocalAuthoritiesExtractor("appId!1234", "appId2!777");
+        assertThat(cut.getAuthorities(token)).hasSize(3);
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope1"));
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope1"));
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope3"));
+    }
+
+    @Test
+    public void extractLocalScopesOfTwoApps_SameScopeName() {
+        cut = new LocalAuthoritiesExtractor("appId!1234", "appId2!888");
+        assertThat(cut.getAuthorities(token)).hasSize(2);
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope1"));
+        assertThat(cut.getAuthorities(token)).contains(new SimpleGrantedAuthority("Scope2"));
+    }
+}
\ No newline at end of file
diff --git a/spring-xsuaa/src/test/resources/vcap_multipleBindings.json b/spring-xsuaa/src/test/resources/vcap_multipleBindings.json
index fe1c6501..74e8b9dc 100644
--- a/spring-xsuaa/src/test/resources/vcap_multipleBindings.json
+++ b/spring-xsuaa/src/test/resources/vcap_multipleBindings.json
@@ -28,4 +28,3 @@
     }
   ]
 }
-}