Repository: SAP/cloud-security-xsuaa-integration
Pull request #636 (2021-08-20 16:55:12)

introduce new env project and deprecated XsuaaServicesParser (spring-xsuaa)
This is a preparation for K8S when spring-xsuaa uses Environments.getCurrent() for parsing xsuaa service binding.


Patch:
diff --git a/env/pom.xml b/env/pom.xml
new file mode 100644
index 00000000..0f7f4019
--- /dev/null
+++ b/env/pom.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+        xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.sap.cloud.security.xsuaa</groupId>
+        <artifactId>parent</artifactId>
+        <version>2.10.5</version>
+    </parent>
+
+    <groupId>com.sap.cloud.security</groupId>
+    <artifactId>env</artifactId>
+    <name>env</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sap.cloud.security</groupId>
+            <artifactId>java-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+        </dependency>
+
+        <!-- test utilities -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.vintage</groupId>
+            <artifactId>junit-vintage-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.stefanbirkner</groupId>
+            <artifactId>system-rules</artifactId>
+            <version>1.19.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>${maven.source.plugin.version}</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-pmd-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>findbugs-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <reuseForks>false</reuseForks>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/Environments.java b/env/src/main/java/com/sap/cloud/security/config/Environments.java
similarity index 100%
rename from java-security/src/main/java/com/sap/cloud/security/config/Environments.java
rename to env/src/main/java/com/sap/cloud/security/config/Environments.java
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java b/env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
similarity index 98%
rename from java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
rename to env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
index 3fcf8a8a..e66cc973 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
+++ b/env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
@@ -5,8 +5,6 @@
  */
 package com.sap.cloud.security.config;
 
-import com.sap.cloud.security.xsuaa.Assertions;
-
 import javax.annotation.Nonnull;
 import java.net.URI;
 import java.util.*;
@@ -35,7 +33,9 @@ public class OAuth2ServiceConfigurationBuilder {
 	 * @return this builder
 	 */
 	public static OAuth2ServiceConfigurationBuilder forService(@Nonnull Service service) {
-		Assertions.assertNotNull(service, "Service must not be null!");
+		if (service == null) {
+			throw new IllegalArgumentException("Service must not be null!");
+		}
 		OAuth2ServiceConfigurationBuilder instance = new OAuth2ServiceConfigurationBuilder();
 		instance.service = service;
 		return instance;
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java b/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
similarity index 96%
rename from java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
rename to env/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
index 7579f75c..dcbd3c22 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
+++ b/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
@@ -72,7 +72,8 @@ class CFEnvParser {
 					.getKeyValueMap();
 			OAuth2ServiceConfigurationBuilder builder = OAuth2ServiceConfigurationBuilder.forService(service)
 					.withProperties(serviceBindingCredentials)
-					.withProperty(SERVICE_PLAN, serviceBindingProperties.get(SERVICE_PLAN))
+					.withProperty(SERVICE_PLAN,
+							CFConstants.Plan.from(serviceBindingProperties.get(SERVICE_PLAN)).toString())
 					.runInLegacyMode(isLegacyMode);
 			if (Service.IAS == service) {
 				builder.withDomains(serviceJsonObject.getJsonObject(CREDENTIALS).getAsStringList(DOMAINS)
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java b/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
similarity index 99%
rename from java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
rename to env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
index e33c4c33..00f01df0 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
+++ b/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
@@ -10,6 +10,7 @@ import static com.sap.cloud.security.config.Service.XSUAA;
 import static com.sap.cloud.security.config.cf.CFConstants.VCAP_APPLICATION;
 import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import com.sap.cloud.security.config.Environment;
@@ -88,6 +89,7 @@ public class CFEnvironment implements Environment {
 	}
 
 	@Override
+	@Nonnull
 	public Type getType() {
 		return Type.CF;
 	}
diff --git a/java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java b/env/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
similarity index 99%
rename from java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
rename to env/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
index 9e1f33c7..b3cc8fbd 100644
--- a/java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
+++ b/env/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
@@ -198,7 +198,7 @@ public class DefaultJsonObject implements JsonObject {
 		return jsonObject;
 	}
 
-	@java.lang.SuppressWarnings("squid:S2139")
+	@SuppressWarnings("squid:S2139")
 	private JSONObject createJsonObject(String jsonString) {
 		try {
 			return new JSONObject(jsonString);
diff --git a/java-security/src/test/resources/vcapXsuaaServiceSingleBinding.json b/env/src/test/resources/vcapXsuaaServiceSingleBinding.json
similarity index 100%
rename from java-security/src/test/resources/vcapXsuaaServiceSingleBinding.json
rename to env/src/test/resources/vcapXsuaaServiceSingleBinding.json
diff --git a/java-security/src/test/resources/vcapXsuaaXsaSingleBinding.json b/env/src/test/resources/vcapXsuaaXsaSingleBinding.json
similarity index 100%
rename from java-security/src/test/resources/vcapXsuaaXsaSingleBinding.json
rename to env/src/test/resources/vcapXsuaaXsaSingleBinding.json
diff --git a/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java b/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java
index 40429426..d2e7bb32 100644
--- a/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java
+++ b/java-api/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java
@@ -60,8 +60,15 @@ public class CFConstants {
 		DEFAULT, BROKER, APPLICATION, SPACE, APIACCESS, SYSTEM;
 
 		public static Plan from(String planAsString) {
+			if(planAsString == null) {
+				return APPLICATION;
+			}
 			return Plan.valueOf(planAsString.toUpperCase());
 		}
 
+		@Override
+		public String toString() {
+			return super.toString().toLowerCase();
+		}
 	}
 }
diff --git a/java-security/pom.xml b/java-security/pom.xml
index 81bac68a..06771fd4 100644
--- a/java-security/pom.xml
+++ b/java-security/pom.xml
@@ -23,6 +23,11 @@
 			<artifactId>java-api</artifactId>
 			<version>${project.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>com.sap.cloud.security</groupId>
+			<artifactId>env</artifactId>
+			<version>${project.version}</version>
+		</dependency>
 		<dependency>
 			<groupId>commons-io</groupId>
 			<artifactId>commons-io</artifactId>
diff --git a/pom.xml b/pom.xml
index a983d967..3393d16d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
 		<junit-jupiter.version>5.7.2</junit-jupiter.version>
 		<maven-surefire-plugin>3.0.0-M5</maven-surefire-plugin>
 		<hamcrest.version>1.3</hamcrest.version>
-		<mockito.version>3.12.0</mockito.version>
+		<mockito.version>3.12.1</mockito.version>
 		<assertj.version>3.20.2</assertj.version>
 		<reactor.test.version>3.4.9</reactor.test.version>
 		<skipTests>false</skipTests>
@@ -86,8 +86,9 @@
 
 	<modules>
 		<module>api</module>
-		<module>token-client</module>
 		<module>java-api</module>
+		<module>env</module>
+		<module>token-client</module>
 		<module>java-security</module>
 		<module>spring-security</module>
 		<module>spring-security-starter</module>
@@ -97,7 +98,7 @@
 		<module>spring-xsuaa-mock</module>
 		<module>spring-xsuaa-starter</module>
 		<module>spring-xsuaa-it</module>
-	</modules>
+    </modules>
 
 	<dependencyManagement>
 		<dependencies>
diff --git a/spring-xsuaa/pom.xml b/spring-xsuaa/pom.xml
index 8f6b8ade..be10174c 100644
--- a/spring-xsuaa/pom.xml
+++ b/spring-xsuaa/pom.xml
@@ -61,10 +61,16 @@
 			<artifactId>java-api</artifactId>
 			<version>${project.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>com.sap.cloud.security</groupId>
+			<artifactId>env</artifactId>
+			<version>${project.version}</version>
+		</dependency>
 		<dependency>
 			<groupId>${project.groupId}</groupId>
 			<artifactId>token-client</artifactId>
 		</dependency>
+
 		<dependency>
 			<groupId>org.apache.httpcomponents</groupId>
 			<artifactId>httpclient</artifactId>
@@ -146,6 +152,12 @@
 			<artifactId>reactor-test</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>com.github.stefanbirkner</groupId>
+			<artifactId>system-rules</artifactId>
+			<version>1.19.0</version>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 
 	<build>
@@ -194,6 +206,9 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-surefire-plugin</artifactId>
+				<configuration>
+					<reuseForks>false</reuseForks>
+				</configuration>
 			</plugin>
 		</plugins>
 	</build>
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
index a68ff141..9bf2b4a8 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
@@ -5,15 +5,12 @@
  */
 package com.sap.cloud.security.xsuaa;
 
-import com.sap.cloud.security.config.ClientCertificate;
-import com.sap.cloud.security.config.ClientCredentials;
-import com.sap.cloud.security.config.ClientIdentity;
-import com.sap.cloud.security.config.CredentialType;
+import com.sap.cloud.security.config.*;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 
-import java.io.IOException;
 import java.net.URI;
+import java.util.Map;
 import java.util.Properties;
 
 @Configuration
@@ -106,10 +103,14 @@ public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfigurati
 
 	private Properties getVcapServiceProperties() {
 		if (vcapServiceProperties == null) {
-			try {
-				vcapServiceProperties = new XsuaaServicesParser().parseCredentials();
-			} catch (IOException e) {
-				vcapServiceProperties = new Properties();
+			vcapServiceProperties = new Properties();
+			if (Environments.getCurrent().getNumberOfXsuaaConfigurations() > 1) {
+				throw new IllegalStateException(
+						"Found more than one xsuaa bindings. Make use of Environments.getCurrent() directly.");
+			}
+			for (Map.Entry<String, String> property : Environments.getCurrent().getXsuaaConfiguration().getProperties()
+					.entrySet()) {
+				vcapServiceProperties.put(property.getKey(), property.getValue());
 			}
 		}
 		return vcapServiceProperties;
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
index 8ad49577..848a8807 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
@@ -5,11 +5,14 @@
  */
 package com.sap.cloud.security.xsuaa;
 
+import com.sap.cloud.security.config.Environment;
+import com.sap.cloud.security.config.Environments;
 import com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaAutoConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.env.PropertiesPropertySource;
 import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.InputStreamResource;
 import org.springframework.core.io.support.EncodedResource;
 import org.springframework.core.io.support.PropertySourceFactory;
 
@@ -53,15 +56,27 @@ public class XsuaaServicePropertySourceFactory implements PropertySourceFactory
 
 	@Override
 	public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
-		Properties properties;
-		XsuaaServicesParser vcapServicesParser;
-		if (resource != null && resource.getResource().getFilename() != null
-				&& !resource.getResource().getFilename().isEmpty()) {
-			vcapServicesParser = new XsuaaServicesParser(resource.getResource().getInputStream());
+		Properties properties = new Properties();
+		Environment environment;
+		if (resource.getResource() instanceof InputStreamResource
+				|| (resource.getResource().getFilename() != null && !resource.getResource().getFilename().isEmpty())) {
+			environment = Environments.readFromInput(resource.getResource().getInputStream());
 		} else {
-			vcapServicesParser = new XsuaaServicesParser();
+			environment = Environments.getCurrent();
+		}
+		if (environment.getNumberOfXsuaaConfigurations() > 1
+				&& environment.getXsuaaConfigurationForTokenExchange() != null) { // TODO check for number of xsuaa and
+																					// ignore api plan
+			throw new IllegalStateException(
+					"Found more than one xsuaa bindings. Please consider unified broker plan or use com.sap.cloud.security:spring-security client library.");
+		}
+		if (environment.getXsuaaConfiguration() != null) {
+			for (String key : XSUAA_ATTRIBUTES) {
+				if (environment.getXsuaaConfiguration().hasProperty(key)) {
+					properties.put(key, environment.getXsuaaConfiguration().getProperty(key));
+				}
+			}
 		}
-		properties = vcapServicesParser.parseCredentials();
 		logger.info("Parsed {} XSUAA properties.", properties.size());
 		return create(XSUAA_PROPERTIES_KEY, properties);
 	}
@@ -73,7 +88,7 @@ public class XsuaaServicePropertySourceFactory implements PropertySourceFactory
 	 *            of the propertySource. Use only "xsuaa" as name in case you like
 	 *            to overwrite/set all properties.
 	 * @param properties
-	 *            map of xsuaa properties.
+	 *            map of xsuaa properties
 	 * @return created @Code{PropertySource}
 	 */
 	public static PropertySource create(String name, Properties properties) {
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 60dfdfb9..96de11ab 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
@@ -13,6 +13,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.*;
 
+import com.sap.cloud.security.config.Environments;
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
 import org.json.JSONArray;
@@ -21,6 +22,10 @@ import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Deprecated in favor of {@link Environments#getCurrent()}.
+ */
+@Deprecated
 public class XsuaaServicesParser {
 
 	private static final Logger logger = LoggerFactory.getLogger(XsuaaServicesParser.class);
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java
index 89193522..fc7e0902 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java
@@ -23,7 +23,6 @@ import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
-import com.sap.cloud.security.xsuaa.XsuaaServicesParser;
 import com.sap.cloud.security.xsuaa.token.TokenClaims;
 
 /**
@@ -32,7 +31,7 @@ import com.sap.cloud.security.xsuaa.token.TokenClaims;
  */
 public class XsuaaAudienceValidator implements OAuth2TokenValidator<Jwt> {
 	private Map<String, String> appIdClientIdMap = new HashMap<>();
-	private final Logger logger = LoggerFactory.getLogger(XsuaaServicesParser.class);
+	private final Logger logger = LoggerFactory.getLogger(XsuaaAudienceValidator.class);
 
 	public XsuaaAudienceValidator(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
 		Assert.notNull(xsuaaServiceConfiguration, "'xsuaaServiceConfiguration' is required");


Test patch:
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java b/env/src/test/java/com/sap/cloud/security/config/EnvironmentsCFTest.java
similarity index 82%
rename from java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java
rename to env/src/test/java/com/sap/cloud/security/config/EnvironmentsCFTest.java
index 68ad3cd2..e3ece121 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java
+++ b/env/src/test/java/com/sap/cloud/security/config/EnvironmentsCFTest.java
@@ -7,7 +7,7 @@ package com.sap.cloud.security.config;
 
 import com.sap.cloud.security.config.cf.CFConstants;
 import org.apache.commons.io.IOUtils;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -16,17 +16,17 @@ import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 
-public class EnvironmentsTest {
+class EnvironmentsCFTest {
 
 	private final InputStream vcapMultipleXsuaa;
 
-	public EnvironmentsTest() throws IOException {
+	EnvironmentsCFTest() throws IOException {
 		vcapMultipleXsuaa = IOUtils.toInputStream(
 				IOUtils.resourceToString("/vcapXsuaaServiceMultipleBindings.json", UTF_8), UTF_8);
 	}
 
 	@Test
-	public void getCurrent_returnsOnlySingleInstance() {
+	void getCurrent_returnsOnlySingleCFInstance() {
 		Environment firstEnvironment = Environments.getCurrent();
 		Environment secondEnvironment = Environments.getCurrent();
 
@@ -34,14 +34,12 @@ public class EnvironmentsTest {
 	}
 
 	@Test
-	public void getCurrent_returnsCorrectEnvironment() {
-		// TODO 29.11.19 c5295400: extend test when more than one environment is
-		// supported
+	void getCurrent_returnsCFEnvironment() {
 		assertThat(Environments.getCurrent().getType()).isEqualTo(Environment.Type.CF);
 	}
 
 	@Test
-	public void readFromInputMultipleInstances() {
+	void readFromInputMultipleInstances() {
 		Environment cut = Environments.readFromInput(vcapMultipleXsuaa);
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(2);
@@ -60,7 +58,7 @@ public class EnvironmentsTest {
 	}
 
 	@Test
-	public void readFromInputDoesNotOverwriteCurrentEnvironment() {
+	void readFromInputDoesNotOverwriteCurrentEnvironment() {
 		Environment cut = Environments.readFromInput(vcapMultipleXsuaa);
 
 		assertThat(cut).isNotSameAs(Environments.getCurrent());
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java b/env/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
similarity index 99%
rename from java-security/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
rename to env/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
index bb7fdf7e..b23efea3 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
+++ b/env/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
@@ -12,8 +12,8 @@ import java.net.URI;
 import java.util.Collections;
 
 import static com.sap.cloud.security.config.cf.CFConstants.URL;
-import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.assertThat;
 
 public class OAuth2ServiceConfigurationBuilderTest {
 
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java b/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
similarity index 100%
rename from java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
rename to env/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserXsuaaTest.java b/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserXsuaaTest.java
similarity index 100%
rename from java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserXsuaaTest.java
rename to env/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserXsuaaTest.java
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java b/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
similarity index 91%
rename from java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
rename to env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
index d080eea1..8f146177 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
+++ b/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
@@ -13,6 +13,7 @@ import com.sap.cloud.security.json.JsonObject;
 
 import com.sap.cloud.security.json.JsonParsingException;
 import org.apache.commons.io.IOUtils;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -64,20 +65,18 @@ public class CFEnvironmentTest {
 		Map<String, String> xsuaaConfigMap = serviceJsonObject.getKeyValueMap();
 		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CREDENTIALS).getKeyValueMap();
 
-		assertThat(xsuaaConfigMap.size()).isEqualTo(4);
-		assertThat(credentialsMap.size()).isEqualTo(10);
-		assertThat(credentialsMap.get(CLIENT_SECRET)).isEqualTo("secret");
+		assertThat(xsuaaConfigMap).hasSize(4);
+		assertThat(credentialsMap).hasSize(10).containsEntry(CLIENT_SECRET, "secret");
 	}
 
 	@Test
 	public void getCorruptConfiguration_raisesException() {
 		String xsuaaBinding = "{\"xsuaa\": [{ \"credentials\": null }]}";
 
-		assertThatThrownBy(() -> {
-			cut = CFEnvironment.getInstance((str) -> xsuaaBinding, (str) -> null);
-		}).isInstanceOf(JsonParsingException.class).hasMessageContainingAll(
-				"The credentials of 'VCAP_SERVICES' can not be parsed for service 'XSUAA'",
-				"Please check the service binding.");
+		assertThatThrownBy(() -> cut = CFEnvironment.getInstance((str) -> xsuaaBinding, (str) -> null))
+				.isInstanceOf(JsonParsingException.class).hasMessageContainingAll(
+						"The credentials of 'VCAP_SERVICES' can not be parsed for service 'XSUAA'",
+						"Please check the service binding.");
 	}
 
 	@Test
@@ -87,7 +86,7 @@ public class CFEnvironmentTest {
 		assertThat(cut.getIasConfiguration().getService()).isEqualTo(Service.IAS);
 		assertThat(cut.getIasConfiguration().getClientId()).isEqualTo("T000310");
 		assertThat(cut.getIasConfiguration().getClientSecret()).startsWith("pCghfbrL");
-		assertThat(cut.getIasConfiguration().getUrl().toString()).isEqualTo("https://myauth.com");
+		assertThat(cut.getIasConfiguration().getUrl()).hasToString("https://myauth.com");
 		assertThat(cut.getIasConfiguration().isLegacyMode()).isFalse();
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
@@ -102,7 +101,7 @@ public class CFEnvironmentTest {
 		assertThat(cut.getXsuaaConfiguration().getClientSecret()).isEqualTo("secret");
 		assertThat(cut.getXsuaaConfiguration().getProperty(XSUAA.UAA_DOMAIN)).isEqualTo("auth.com");
 		assertThat(cut.getXsuaaConfiguration().getProperty(XSUAA.APP_ID)).isEqualTo("java-hello-world");
-		assertThat(cut.getXsuaaConfiguration().getUrl().toString()).isEqualTo("https://paastenant.auth.com");
+		assertThat(cut.getXsuaaConfiguration().getUrl()).hasToString("https://paastenant.auth.com");
 		assertThat(cut.getXsuaaConfiguration().isLegacyMode()).isFalse();
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(1);
@@ -123,8 +122,8 @@ public class CFEnvironmentTest {
 		assertThat(cut.getXsuaaConfiguration().getClientId()).isEqualTo("sb-java-hello-world!i1");
 		assertThat(cut.getXsuaaConfiguration().getProperty(XSUAA.APP_ID)).isEqualTo("java-hello-world!i1");
 		assertThat(cut.getXsuaaConfiguration().getClientSecret()).startsWith("fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpS");
-		assertThat(cut.getXsuaaConfiguration().getUrl().toString())
-				.isEqualTo("https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security");
+		assertThat(cut.getXsuaaConfiguration().getUrl())
+				.hasToString("https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security");
 		assertThat(cut.getXsuaaConfiguration().isLegacyMode()).isTrue();
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(1);
@@ -179,7 +178,7 @@ public class CFEnvironmentTest {
 		cut = CFEnvironment.getInstance((str) -> null, (str) -> null);
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
-		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(0);
+		assertThat(cut.getNumberOfXsuaaConfigurations()).isZero();
 		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isNull();
 		assertThat(cut.loadForServicePlan(Service.IAS, Plan.DEFAULT)).isNull();
 		assertThat(CFEnvironment.getInstance().getXsuaaConfiguration()).isNull();
diff --git a/java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java b/env/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
similarity index 95%
rename from java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
rename to env/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
index 2787d9ad..ad3d2410 100644
--- a/java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
+++ b/env/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
@@ -80,8 +80,7 @@ public class DefaultJsonObjectTest {
 
 		List<String> list = cut.getAsList(KEY_2, String.class);
 
-		assertThat(list).hasSize(3);
-		assertThat(list).first().isEqualTo("a");
+		assertThat(list).hasSize(3).first().isEqualTo("a");
 	}
 
 	@Test
@@ -100,8 +99,7 @@ public class DefaultJsonObjectTest {
 
 		List<String> list = cut.getAsStringList(KEY_2);
 
-		assertThat(list).hasSize(3);
-		assertThat(list).first().isEqualTo("a");
+		assertThat(list).hasSize(3).first().isEqualTo("a");
 	}
 
 	@Test
@@ -176,8 +174,7 @@ public class DefaultJsonObjectTest {
 
 		List<JsonObject> jsonObjects = cut.getJsonObjects(KEY_1);
 
-		assertThat(jsonObjects).isNotNull();
-		assertThat(jsonObjects).hasSize(1);
+		assertThat(jsonObjects).isNotNull().hasSize(1);
 		assertThat(jsonObjects.get(0).getAsString(KEY_1)).isNotNull();
 	}
 
@@ -206,8 +203,7 @@ public class DefaultJsonObjectTest {
 
 		List<JsonObject> jsonObjects = cut.getJsonObjects(KEY_1);
 
-		assertThat(jsonObjects).isNotNull();
-		assertThat(jsonObjects).isEmpty();
+		assertThat(jsonObjects).isNotNull().isEmpty();
 	}
 
 	@Test
diff --git a/java-security/src/test/resources/vcapIasServiceSingleBinding.json b/env/src/test/resources/vcapIasServiceSingleBinding.json
similarity index 100%
rename from java-security/src/test/resources/vcapIasServiceSingleBinding.json
rename to env/src/test/resources/vcapIasServiceSingleBinding.json
diff --git a/java-security/src/test/resources/vcapXsuaaServiceMultipleBindings.json b/env/src/test/resources/vcapXsuaaServiceMultipleBindings.json
similarity index 99%
rename from java-security/src/test/resources/vcapXsuaaServiceMultipleBindings.json
rename to env/src/test/resources/vcapXsuaaServiceMultipleBindings.json
index b5844444..7c8a72a3 100644
--- a/java-security/src/test/resources/vcapXsuaaServiceMultipleBindings.json
+++ b/env/src/test/resources/vcapXsuaaServiceMultipleBindings.json
@@ -58,5 +58,4 @@
       "volume_mounts": []
     }
   ]
-}
 }
\ No newline at end of file
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/CustomPropertySourceFactoryTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/CustomPropertySourceFactoryTest.java
index b4ddbb42..87b7d079 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/CustomPropertySourceFactoryTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/CustomPropertySourceFactoryTest.java
@@ -5,9 +5,13 @@
  */
 package com.sap.cloud.security.xsuaa;
 
-import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
 import java.util.Properties;
 
+import com.sap.cloud.security.config.Environment;
+import com.sap.cloud.security.config.Environments;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -79,13 +83,18 @@ class CustomConfiguration {
 }
 
 class CustomPropertySourceFactory implements PropertySourceFactory {
-	private String vcapJsonString = "{\"xsuaa\":[{\"credentials\":{\"xsappname\":\"customAppId!t2344\"},\"plan\": \"application\",\"tags\":[\"xsuaa\"]}]}";
+	private final String vcapJsonString = "{\"xsuaa\":[{\"credentials\":{\"xsappname\":\"customAppId!t2344\"},\"plan\": \"application\",\"tags\":[\"xsuaa\"]}]}";
 
 	@Override
 	public org.springframework.core.env.PropertySource<?> createPropertySource(String s,
-			EncodedResource encodedResource) throws IOException {
-
-		Properties properties = new XsuaaServicesParser(vcapJsonString).parseCredentials();
+			EncodedResource encodedResource) {
+
+		Properties properties = new Properties();
+		Environment environment = Environments
+				.readFromInput(new ByteArrayInputStream(vcapJsonString.getBytes(StandardCharsets.UTF_8)));
+		for (Map.Entry<String, String> property : environment.getXsuaaConfiguration().getProperties().entrySet()) {
+			properties.put(property.getKey(), property.getValue());
+		}
 
 		properties.put("clientid", "customClientId");
 		properties.put("clientsecret", "customClientSecret");
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefaultTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefaultTest.java
new file mode 100644
index 00000000..f8558a31
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefaultTest.java
@@ -0,0 +1,41 @@
+package com.sap.cloud.security.xsuaa;
+
+import com.sap.cloud.security.config.cf.CFConstants;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.EnvironmentVariables;
+
+import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
+import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class XsuaaServiceConfigurationDefaultTest {
+	XsuaaServiceConfigurationDefault cut;
+
+	@Rule
+	public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
+
+	@Before
+	public void setup() {
+		environmentVariables.set(VCAP_SERVICES,
+				"{\"xsuaa\":[{\"credentials\":{\"apiurl\":\"https://api.mydomain.com\",\"tenantid\":\"tenant-id\",\"subaccountid\":\"subaccount-id\",\"clientid\":\"client-id\"},\"tags\":[\"xsuaa\"]}]}");
+		cut = new XsuaaServiceConfigurationDefault();
+	}
+
+	@Test
+	public void getProperty() {
+		assertThat(cut.getProperty(API_URL)).isEqualTo("https://api.mydomain.com");
+		assertThat(cut.getProperty(SUBACCOUNT_ID)).isEqualTo("subaccount-id");
+		assertThat(cut.getProperty(TENANT_ID)).isEqualTo("tenant-id");
+		assertThat(cut.getProperty(CFConstants.CLIENT_ID)).isEqualTo("client-id");
+		assertThat(cut.getProperty("unknownProp")).isNull();
+	}
+
+	@Test
+	public void hasProperty() {
+		assertThat(cut.hasProperty(API_URL)).isTrue();
+		assertThat(cut.hasProperty(SUBACCOUNT_ID)).isTrue();
+		assertThat(cut.hasProperty("unknownProp")).isFalse();
+	}
+}
\ No newline at end of file
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactoryMultipleBindingsTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactoryMultipleBindingsTest.java
new file mode 100644
index 00000000..03fc71fa
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactoryMultipleBindingsTest.java
@@ -0,0 +1,48 @@
+package com.sap.cloud.security.xsuaa;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory.CLIENT_ID;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class XsuaaServicePropertySourceFactoryMultipleBindingsTest {
+	XsuaaServicePropertySourceFactory cut = new XsuaaServicePropertySourceFactory();
+
+	@Test
+	public void ignoreApiAccessPlan() throws IOException {
+		ResourceLoader resourceLoader = new DefaultResourceLoader();
+		Resource resource = resourceLoader.getResource("classpath:vcap_multipleBindings.json");
+		PropertySource propertySource = cut.createPropertySource(null, new EncodedResource(resource));
+		assertThat(propertySource.getProperty(CLIENT_ID)).isEqualTo("client-id");
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void doNotAllowBrokerAndApplicationPlan() throws IOException {
+		String vcapMultipleBindings = IOUtils.resourceToString("/vcap_multipleBindings.json", StandardCharsets.UTF_8);
+		vcapMultipleBindings = vcapMultipleBindings.replace("apiaccess", "broker");
+		Resource resource = new InputStreamResource(
+				new ByteArrayInputStream(vcapMultipleBindings.getBytes(StandardCharsets.UTF_8)));
+		cut.createPropertySource(null, new EncodedResource(resource));
+	}
+
+	@Test
+	public void acceptVcapServicesWithoutPlan() throws IOException {
+		String vcapMinimalWoPlan = "{\"xsuaa\":[{\"credentials\":{\"clientid\":\"client-id\",\"clientsecret\":\"client-secret\"},\"tags\":[\"xsuaa\"]}]}";
+		Resource resource = new InputStreamResource(
+				new ByteArrayInputStream(vcapMinimalWoPlan.getBytes(StandardCharsets.UTF_8)));
+		PropertySource propertySource = cut.createPropertySource(null, new EncodedResource(resource));
+		assertThat(propertySource.getProperty(CLIENT_ID)).isEqualTo("client-id");
+	}
+
+}
diff --git a/spring-xsuaa/src/test/resources/vcap_multipleBindings.json b/spring-xsuaa/src/test/resources/vcap_multipleBindings.json
index 74e8b9dc..5b724427 100644
--- a/spring-xsuaa/src/test/resources/vcap_multipleBindings.json
+++ b/spring-xsuaa/src/test/resources/vcap_multipleBindings.json
@@ -16,7 +16,10 @@
     {
       "credentials": {
         "clientid": "client-id",
-        "clientsecret": "client-secret"
+        "clientsecret": "client-secret",
+        "apiurl": "https://api.mydomain.com",
+        "tenantid": "tenant-id",
+        "subaccountid": "subaccount-id"
       },
       "instance_name": "xsuaa-main",
       "label": "xsuaa",