Repository: SAP/cloud-security-xsuaa-integration (java)

Issue:
XsuaaServiceConfigurationDefault breaking change since 2.11.1
Hi colleagues,

It seems that from version 2.11.1 of XSUAA Spring library, the `XsuaaServiceConfigurationDefault` class has breaking changes that affect the ability to get XSUAA VCAP properties that are not mapped in the class and do not have any getters. 

The `getProperty()` and `hasProperty()` methods were deleted.
For example, properties like API URL and tenant ID cannot be fetched now directly.

How can these properties be fetched via the XSUAA Spring library?

As far as I can see, the change is from here:
https://github.com/SAP/cloud-security-xsuaa-integration/pull/698/files#diff-578760e442614f47823e1a3c83ed22a0a8cab50f5005861b9417764499473606L115

Best regards,
Momchil

Pull request:
XsuaaServiceConfigurationDefault supports access to other credentials
relates to #627
closes #802 

Patch:
diff --git a/java-api/src/main/java/com/sap/cloud/security/x509/Certificate.java b/java-api/src/main/java/com/sap/cloud/security/x509/Certificate.java
index 6ec360b4..609baca1 100644
--- a/java-api/src/main/java/com/sap/cloud/security/x509/Certificate.java
+++ b/java-api/src/main/java/com/sap/cloud/security/x509/Certificate.java
@@ -5,7 +5,6 @@
  */
 package com.sap.cloud.security.x509;
 
-
 import java.util.Map;
 
 /**
@@ -22,8 +21,8 @@ public interface Certificate {
 	 *
 	 * @return the thumbprint
 	 * @throws InvalidCertificateException
-	 *             if error occurs while encoding X509 certificate
-	 *             or when a particular cryptographic algorithm is not supported
+	 *             if error occurs while encoding X509 certificate or when a
+	 *             particular cryptographic algorithm is not supported
 	 */
 	String getThumbprint() throws InvalidCertificateException;
 
@@ -37,8 +36,9 @@ public interface Certificate {
 	/**
 	 * Gets certificate subject DN map.
 	 *
-	 * @return the subject DN map with entries such as ["CN"]["common name"], or ["OU"]["1234-5678"]
+	 * @return the subject DN map with entries such as ["CN"]["common name"], or
+	 *         ["OU"]["1234-5678"]
 	 */
-	Map<String,String> getSubjectDNMap();
+	Map<String, String> getSubjectDNMap();
 
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java
index 321feb70..b77ff7ac 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java
@@ -63,7 +63,8 @@ class JwtSignatureValidator implements Validator<Token> {
 		String jwksUri;
 		String keyId;
 
-		if (Service.IAS == configuration.getService() && !token.getIssuer().equals("" + configuration.getUrl()) && token.getZoneId() == null) { // lgtm[java/dereferenced-value-may-be-null]
+		if (Service.IAS == configuration.getService() && !token.getIssuer().equals("" + configuration.getUrl())
+				&& token.getZoneId() == null) { // lgtm[java/dereferenced-value-may-be-null]
 			return createInvalid("Error occurred during signature validation: OIDC token must provide zone_uuid.");
 		}
 		try {
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java
index fe19d0f0..2bb39ea5 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java
@@ -9,6 +9,7 @@ import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.ValidationResults;
 import com.sap.cloud.security.token.validation.Validator;
 import com.sap.cloud.security.x509.Certificate;
+import com.sap.cloud.security.xsuaa.Assertions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,11 +30,10 @@ import javax.annotation.Nullable;
 public class JwtX5tValidator implements Validator<Token> {
 
 	private static final Logger LOGGER = LoggerFactory.getLogger(JwtX5tValidator.class);
-	private final OAuth2ServiceConfiguration config;
-
+	static final String VALIDATION_FAILED = "Certificate thumbprint validation failed";
 
 	public JwtX5tValidator(OAuth2ServiceConfiguration config) {
-		this.config = config;
+		Assertions.assertNotNull(config, "Service configuration must not be null");
 	}
 
 	/**
@@ -51,26 +51,23 @@ public class JwtX5tValidator implements Validator<Token> {
 	 */
 	@Override
 	public ValidationResult validate(Token token) {
+		if (token == null) {
+			return ValidationResults.createInvalid("No token passed to validate certificate thumbprint");
+		}
 		String tokenX5t = extractCnfThumbprintFromToken(token);
 		LOGGER.debug("Token 'cnf' thumbprint: {}", tokenX5t);
-		if (tokenX5t != null) {
-			Certificate clientCertificate = SecurityContext.getClientCertificate();
-			if (clientCertificate == null) {
-				LOGGER.error("Client certificate missing from SecurityContext");
-				return ValidationResults.createInvalid("Certificate validation failed");
-			}
-			String clientCertificateX5t = clientCertificate.getThumbprint();
-			if (clientCertificateX5t.equals(tokenX5t)) {
-				if (token.getAudiences().size() == 1 && token.getAudiences().contains(config.getClientId())) {
-					return ValidationResults.createValid();
-				}
-				LOGGER.error("Audience validation failed -> \"aud\": {} != \"clientid\": \"{}\"", token.getAudiences(), config.getClientId());
-			} else {
-				LOGGER.error("Thumbprint validation failed -> x5t from token: \"{}\" != thumbprint from client certificate: \"{}\"", tokenX5t,
-						clientCertificateX5t);
-			}
+		if (tokenX5t == null) {
+			return ValidationResults.createInvalid("Token doesn't contain certificate thumbprint confirmation method");
+		}
+		Certificate clientCertificate = SecurityContext.getClientCertificate();
+		if (clientCertificate == null) {
+			return ValidationResults.createInvalid("Client certificate missing from SecurityContext");
+		}
+		String clientCertificateX5t = clientCertificate.getThumbprint();
+		if (clientCertificateX5t.equals(tokenX5t)) {
+			return ValidationResults.createValid();
 		}
-		return ValidationResults.createInvalid("Certificate validation failed");
+		return ValidationResults.createInvalid(VALIDATION_FAILED);
 	}
 
 	/**
diff --git a/spring-security/src/main/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfiguration.java b/spring-security/src/main/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfiguration.java
index 8f7318e9..aac753fd 100644
--- a/spring-security/src/main/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfiguration.java
+++ b/spring-security/src/main/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfiguration.java
@@ -59,7 +59,7 @@ public class XsuaaTokenFlowAutoConfiguration {
 	}
 
 	@Bean
-	@Conditional({ PropertyConditions.class })
+	@Conditional(PropertyConditions.class)
 	public XsuaaTokenFlows xsuaaTokenFlows(RestOperations xsuaaRestOperations) {
 		logger.debug("auto-configures XsuaaTokenFlows using {} based restOperations",
 				xsuaaConfig.getClientIdentity().isCertificateBased() ? "certificate" : "client secret");
@@ -76,7 +76,7 @@ public class XsuaaTokenFlowAutoConfiguration {
 	 * @return the {@link RestOperations} instance.
 	 */
 	@Bean
-	@Conditional({ OnNotX509CredentialTypeCondition.class })
+	@Conditional({ OnSecretCredentialTypeCondition.class, NoClientCertificateCondition.class })
 	@ConditionalOnMissingBean
 	public RestOperations restOperations() {
 		logger.warn("In productive environment provide a well configured client secret based RestOperations bean");
@@ -90,7 +90,6 @@ public class XsuaaTokenFlowAutoConfiguration {
 	 * @return the {@link RestOperations} instance.
 	 */
 	@Bean
-	@ConditionalOnProperty(prefix = "sap.security.services.xsuaa", name = "credential-type", havingValue = "x509")
 	@ConditionalOnClass(name = "org.apache.http.impl.client.CloseableHttpClient")
 	@ConditionalOnMissingBean
 	public RestOperations mtlsRestOperations(XsuaaServiceConfiguration xsuaaConfig) {
@@ -98,7 +97,7 @@ public class XsuaaTokenFlowAutoConfiguration {
 		return SpringHttpClient.getInstance().create(xsuaaConfig.getClientIdentity());
 	}
 
-	private static class OnNotX509CredentialTypeCondition implements Condition {
+	private static class OnSecretCredentialTypeCondition implements Condition {
 		@Override
 		public boolean matches(ConditionContext context, @Nonnull AnnotatedTypeMetadata metadata) {
 			CredentialType credentialType = CredentialType
@@ -108,17 +107,25 @@ public class XsuaaTokenFlowAutoConfiguration {
 		}
 	}
 
+	private static class NoClientCertificateCondition implements Condition {
+		@Override
+		public boolean matches(ConditionContext context, @Nonnull AnnotatedTypeMetadata metadata) {
+			return context.getEnvironment().getProperty("sap.security.services.xsuaa.certificate") == null &&
+					context.getEnvironment().getProperty("sap.security.services.xsuaa[0].certificate") == null;
+		}
+	}
+
 	private static class PropertyConditions extends AnyNestedCondition {
 
 		public PropertyConditions() {
 			super(ConfigurationPhase.REGISTER_BEAN);
 		}
 
-		@ConditionalOnProperty(prefix = "sap.security.services", name = "xsuaa[0].clientsecret")
+		@ConditionalOnProperty(prefix = "sap.security.services", name = "xsuaa[0].clientid")
 		static class MultipleBindingsCondition {
 		}
 
-		@ConditionalOnProperty(prefix = "sap.security.services", name = "xsuaa.clientsecret")
+		@ConditionalOnProperty(prefix = "sap.security.services", name = "xsuaa.clientid")
 		static class SingleBindingCondition {
 		}
 	}
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 111eadfa..69df32b0 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,18 +5,18 @@
  */
 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.net.URI;
+import java.util.Objects;
 
 @Configuration
 public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfiguration {
 
+	static final String VCAP_SERVICES_CREDENTIALS = "xsuaa credentials from VCAP_SERVICES/secret must not be null";
+
 	@Value("${xsuaa.clientid:}")
 	private String clientId;
 
@@ -101,4 +101,31 @@ public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfigurati
 		return identity;
 	}
 
+	/**
+	 * This only supports read from VCAP_SERVICES in cf environment or read from
+	 * secrets in kubernetes environment.
+	 * 
+	 * @param name
+	 *            of the credential property
+	 * @return the property value or null if not found
+	 */
+	@Override
+	public String getProperty(String name) {
+		return Objects.requireNonNull(Environments.getCurrent().getXsuaaConfiguration(), VCAP_SERVICES_CREDENTIALS)
+				.getProperty(name);
+	}
+
+	/**
+	 * This only supports VCAP_SERVICES in cf environment or read from secrets in
+	 * kubernetes environment.
+	 * 
+	 * @param name
+	 *            of the credential property
+	 * @return false if property doesn't exist
+	 */
+	@Override
+	public boolean hasProperty(String name) {
+		return Objects.requireNonNull(Environments.getCurrent().getXsuaaConfiguration(), VCAP_SERVICES_CREDENTIALS)
+				.hasProperty(name);
+	}
 }
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
index 0eced4a5..b4fd7e80 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
@@ -81,7 +81,7 @@ public class XsuaaAutoConfiguration {
 	 * @return the {@link RestOperations} instance.
 	 */
 	@Bean
-	@Conditional({ OnNotX509CredentialTypeCondition.class })
+	@Conditional({ OnSecretCredentialTypeCondition.class, NoClientCertificateCondition.class })
 	@ConditionalOnMissingBean
 	@ConditionalOnBean(XsuaaServiceConfiguration.class)
 	public RestOperations xsuaaRestOperations() {
@@ -96,7 +96,6 @@ public class XsuaaAutoConfiguration {
 	 * @return the {@link RestOperations} instance.
 	 */
 	@Bean
-	@ConditionalOnProperty(prefix = "xsuaa", name = "credential-type", havingValue = "x509")
 	@ConditionalOnMissingBean
 	@ConditionalOnClass(name = "org.apache.http.impl.client.CloseableHttpClient")
 	@ConditionalOnBean(XsuaaServiceConfiguration.class)
@@ -105,7 +104,7 @@ public class XsuaaAutoConfiguration {
 		return SpringHttpClient.getInstance().create(xsuaaServiceConfiguration.getClientIdentity());
 	}
 
-	private static class OnNotX509CredentialTypeCondition implements Condition {
+	private static class OnSecretCredentialTypeCondition implements Condition {
 		@Override
 		public boolean matches(ConditionContext context, @Nonnull AnnotatedTypeMetadata metadata) {
 			CredentialType credentialType = CredentialType
@@ -115,4 +114,12 @@ public class XsuaaAutoConfiguration {
 		}
 	}
 
+	private static class NoClientCertificateCondition implements Condition {
+		@Override
+		public boolean matches(ConditionContext context, @Nonnull AnnotatedTypeMetadata metadata) {
+			return context.getEnvironment().getProperty("xsuaa.certificate") == null &&
+					context.getEnvironment().getProperty("xsuaa.key") == null;
+		}
+	}
+
 }
\ No newline at end of file
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenKeyService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenKeyService.java
index c6a1ee18..13bd738b 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenKeyService.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenKeyService.java
@@ -10,7 +10,8 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 /**
- * Service that targets Identity service (xsuaa and identity) to request Json Web Keys.
+ * Service that targets Identity service (xsuaa and identity) to request Json
+ * Web Keys.
  */
 public interface OAuth2TokenKeyService {
 


Test patch:
diff --git a/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java
index fac26fed..46ba1d7f 100644
--- a/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java
+++ b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java
@@ -80,14 +80,14 @@ class IasTokenAuthenticatorX509Test {
 		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
 
 		assertThat(response.getUnauthenticatedReason())
-				.contains("Error during token validation: Certificate validation failed");
+				.contains("Error during token validation: Client certificate missing");
 		assertThat(response.isAuthenticated()).isFalse();
 
 		when(httpRequest.getHeader(FWD_CLIENT_CERT_HEADER)).thenReturn("");
 		TokenAuthenticationResult response2 = cut.validateRequest(httpRequest, HTTP_RESPONSE);
 
 		assertThat(response2.getUnauthenticatedReason())
-				.contains("Error during token validation: Certificate validation failed");
+				.contains("Error during token validation: Client certificate missing");
 		assertThat(response2.isAuthenticated()).isFalse();
 
 	}
@@ -100,7 +100,7 @@ class IasTokenAuthenticatorX509Test {
 		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
 
 		assertThat(response.getUnauthenticatedReason())
-				.contains("Error during token validation: Certificate validation failed");
+				.contains("Error during token validation: Client certificate missing");
 		assertThat(response.isAuthenticated()).isFalse();
 	}
 
@@ -112,7 +112,7 @@ class IasTokenAuthenticatorX509Test {
 		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
 
 		assertThat(response.getUnauthenticatedReason())
-				.contains("Error during token validation: Certificate validation failed");
+				.contains("Error during token validation: Certificate thumbprint validation failed");
 		assertThat(response.isAuthenticated()).isFalse();
 	}
 
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java
index f0f92e09..99590205 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java
@@ -23,10 +23,9 @@ import org.mockito.internal.util.collections.Sets;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static com.sap.cloud.security.token.validation.validators.JwtX5tValidator.VALIDATION_FAILED;
+import static org.junit.jupiter.api.Assertions.*;
 
 class JwtX5tValidatorTest {
 
@@ -36,7 +35,6 @@ class JwtX5tValidatorTest {
 	private static X509Certificate x509;
 	private static final Token TOKEN = Mockito.mock(Token.class);
 	private static final JsonObject JSON_MOCK = Mockito.mock(JsonObject.class);
-	private static final String INVALID_MESSAGE = "Certificate validation failed";
 	private static final String X5T_VALIDATOR_DISABLED = "X5tValidator is not enabled";
 
 	@BeforeAll
@@ -50,94 +48,72 @@ class JwtX5tValidatorTest {
 				.withClientId("myClientId")
 				.build();
 		CUT = new JwtX5tValidator(configuration);
-		Mockito.when(JSON_MOCK.getAsString(TokenClaims.CNF_X5T)).thenReturn("fU-XoQlhMTpQsz9ArXl6zHIpMGuRO4ExLKdLRTc5VjM");
+		Mockito.when(JSON_MOCK.getAsString(TokenClaims.CNF_X5T))
+				.thenReturn("fU-XoQlhMTpQsz9ArXl6zHIpMGuRO4ExLKdLRTc5VjM");
 	}
 
 	@Test
-	void validateToken_ValidToken_validX509() {
-		Token token = Token.create(tokenWithX5t);
-		SecurityContext.setClientCertificate(x509);
-		ValidationResult result = CUT.validate(token);
-		assertTrue(result.isValid());
+	void given_NoConfig() {
+		assertThrows(
+				IllegalArgumentException.class,
+				() -> new JwtX5tValidator(null),
+				"Service configuration must not be null");
 	}
 
 	@Test
-	void validateToken_ValidToken_invalidX509() {
-		Token token = Token.create(tokenWithX5t);
-		SecurityContext.setClientCertificate(null);
-		ValidationResult result = CUT.validate(token);
+	void given_NoToken() {
+		ValidationResult result = CUT.validate(null);
 		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+		assertEquals("No token passed to validate certificate thumbprint", result.getErrorDescription());
 	}
 
 	@Test
-	void validateToken_validCnf_validX509_MultipleAud() {
+	void given_ValidToken_validX509() {
+		Token token = Token.create(tokenWithX5t);
 		SecurityContext.setClientCertificate(x509);
-		Mockito.when(TOKEN.getAudiences()).thenReturn(Sets.newSet("aud1", "aud2"));
-		Mockito.when(TOKEN.getClaimAsJsonObject(TokenClaims.CNF)).thenReturn(JSON_MOCK);
-		ValidationResult result = CUT.validate(TOKEN);
-		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+		ValidationResult result = CUT.validate(token);
+		assertTrue(result.isValid());
 	}
 
 	@Test
-	void validateToken_validCnf_validX509_EmptyAud() {
-		SecurityContext.setClientCertificate(x509);
-		Mockito.when(TOKEN.getAudiences()).thenReturn(Collections.emptySet());
-		Mockito.when(TOKEN.getClaimAsJsonObject(TokenClaims.CNF)).thenReturn(JSON_MOCK);
-		ValidationResult result = CUT.validate(TOKEN);
+	void given_ValidToken_invalidX509() {
+		Token token = Token.create(tokenWithX5t);
+		SecurityContext.setClientCertificate(null);
+		ValidationResult result = CUT.validate(token);
 		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+		assertEquals("Client certificate missing from SecurityContext", result.getErrorDescription());
 	}
 
 	@Test
-	void validateToken_InvalidCnf_validX509() {
+	void given_InvalidCnf_validX509() {
 		Token token = Token.create(tokenWithInvalidX5t);
 		SecurityContext.setClientCertificate(x509);
 		ValidationResult result = CUT.validate(token);
 		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+		assertEquals(VALIDATION_FAILED, result.getErrorDescription());
 	}
 
 	@Test
-	void validateToken_InvalidCnf_invalidX509() {
+	void given_InvalidCnf_invalidX509() {
 		Token token = Token.create(tokenWithInvalidX5t);
 		SecurityContext.setClientCertificate(null);
 		ValidationResult result = CUT.validate(token);
 		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+		assertEquals("Client certificate missing from SecurityContext", result.getErrorDescription());
 	}
 
 	@Test
-	void validateToken_NoCnf_validAud() {
+	void given_NoCnf_validAud() {
 		Mockito.when(JSON_MOCK.getAsString(TokenClaims.CNF_X5T)).thenReturn(null);
 		Mockito.when(TOKEN.getAudiences()).thenReturn(Sets.newSet("myClientId"));
 		ValidationResult result = CUT.validate(TOKEN);
 		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
-	}
-
-	@Test
-	void validateToken_NoCnf_MultipleAud() {
-		Mockito.when(JSON_MOCK.getAsString(TokenClaims.CNF_X5T)).thenReturn(null);
-		Mockito.when(TOKEN.getAudiences()).thenReturn(Sets.newSet("aud1", "aud2"));
-		ValidationResult result = CUT.validate(TOKEN);
-		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
-	}
-
-	@Test
-	void validateToken_NoCnf_EmptyAud() {
-		Mockito.when(JSON_MOCK.getAsString(TokenClaims.CNF_X5T)).thenReturn(null);
-		Mockito.when(TOKEN.getAudiences()).thenReturn(Collections.emptySet());
-		ValidationResult result = CUT.validate(TOKEN);
-		assertTrue(result.isErroneous());
-		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+		assertEquals("Token doesn't contain certificate thumbprint confirmation method", result.getErrorDescription());
 	}
 
-	@Disabled("until proofOfPossesion validator subchain is implemented")
+	@Disabled("until proofOfPossession validator subchain is implemented")
 	@Test
-	void validateToken_validatorDisabled() {
+	void given_validatorDisabled() {
 		Token token = Token.create(tokenWithX5t);
 		ValidationResult result = CUT.validate(token);
 		assertTrue(result.isErroneous());
diff --git a/java-security/src/test/java/com/sap/cloud/security/x509/X509CertificateTest.java b/java-security/src/test/java/com/sap/cloud/security/x509/X509CertificateTest.java
index a3638381..965035de 100644
--- a/java-security/src/test/java/com/sap/cloud/security/x509/X509CertificateTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/x509/X509CertificateTest.java
@@ -41,13 +41,16 @@ class X509CertificateTest {
 
 	@Test
 	void getSubjectDN() {
-		assertThat(cut.getSubjectDN()).isEqualTo("CN=bdcd300c-b202-4a7a-bb95-2a7e6d15fe47/2b585405-d391-4986-b76d-b4f24685f3c8, L=aoxk2addh.accounts400.ondemand.com, OU=8e1affb2-62a1-43cc-a687-2ba75e4b3d84, OU=Canary, OU=SAP Cloud Platform Clients, O=SAP SE, C=DE");
+		assertThat(cut.getSubjectDN()).isEqualTo(
+				"CN=bdcd300c-b202-4a7a-bb95-2a7e6d15fe47/2b585405-d391-4986-b76d-b4f24685f3c8, L=aoxk2addh.accounts400.ondemand.com, OU=8e1affb2-62a1-43cc-a687-2ba75e4b3d84, OU=Canary, OU=SAP Cloud Platform Clients, O=SAP SE, C=DE");
 	}
 
 	@Test
 	void getSubjectDNMap() {
 		assertThat(cut.getSubjectDNMap().size()).isEqualTo(5);
-		assertThat(cut.getSubjectDNMap().get("CN")).isEqualTo("bdcd300c-b202-4a7a-bb95-2a7e6d15fe47/2b585405-d391-4986-b76d-b4f24685f3c8");
-		assertThat(cut.getSubjectDNMap().get("OU")).isEqualTo("8e1affb2-62a1-43cc-a687-2ba75e4b3d84,Canary,SAP Cloud Platform Clients");
+		assertThat(cut.getSubjectDNMap().get("CN"))
+				.isEqualTo("bdcd300c-b202-4a7a-bb95-2a7e6d15fe47/2b585405-d391-4986-b76d-b4f24685f3c8");
+		assertThat(cut.getSubjectDNMap().get("OU"))
+				.isEqualTo("8e1affb2-62a1-43cc-a687-2ba75e4b3d84,Canary,SAP Cloud Platform Clients");
 	}
 }
\ No newline at end of file
diff --git a/spring-security/src/test/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfigurationTest.java b/spring-security/src/test/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfigurationTest.java
index f9eed0dd..785c3c80 100644
--- a/spring-security/src/test/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfigurationTest.java
+++ b/spring-security/src/test/java/com/sap/cloud/security/spring/autoconfig/XsuaaTokenFlowAutoConfigurationTest.java
@@ -72,9 +72,11 @@ class XsuaaTokenFlowAutoConfigurationTest {
 
 	@Test
 	void configures_xsuaaMtlsRestTemplate() {
-		runner
-				.withPropertyValues("sap.spring.security.xsuaa.flows.auto:true")
-				.withPropertyValues("sap.security.services.xsuaa.credential-type:x509")
+		runner = new ApplicationContextRunner()
+				.withConfiguration(AutoConfigurations.of(HybridIdentityServicesAutoConfiguration.class,
+						XsuaaTokenFlowAutoConfiguration.class))
+				.withPropertyValues("sap.security.services.xsuaa.certurl:https://domain.cert.auth.com")
+				.withPropertyValues("sap.security.services.xsuaa.clientid:cid")
 				.withPropertyValues("sap.security.services.xsuaa.certificate:" + cert)
 				.withPropertyValues("sap.security.services.xsuaa.key:" + key)
 				.run((context) -> {
@@ -90,7 +92,7 @@ class XsuaaTokenFlowAutoConfigurationTest {
 	}
 
 	@Test
-	void autoConfigurationDisabledWhenNoClientSecretIsGiven() {
+	void autoConfigurationDisabledWhenNoClientIdIsGiven() {
 		WebApplicationContextRunner mt_runner;
 
 		mt_runner = new WebApplicationContextRunner()
@@ -105,7 +107,7 @@ class XsuaaTokenFlowAutoConfigurationTest {
 		WebApplicationContextRunner mt_runner;
 
 		List<String> mt_properties = new ArrayList<>(properties);
-		mt_properties.add("sap.security.services.xsuaa[0].url:http://localhost");
+		mt_properties.add("sap.security.services.xsuaa[0].certurl:https://domain.cert.auth.com");
 		mt_properties.add("sap.security.services.xsuaa[0].clientid:cid");
 		mt_properties.add("sap.security.services.xsuaa[0].clientsecret:pwd");
 		mt_properties.add("sap.security.services.xsuaa[1].clientid:cid");
@@ -118,6 +120,26 @@ class XsuaaTokenFlowAutoConfigurationTest {
 		mt_runner.run(context -> assertTrue(context.containsBean("xsuaaTokenFlows")));
 	}
 
+	@Test
+	void configures_xsuaaMtlsRestTemplateForMultipleXsuaaServicesAsPrimary() {
+		WebApplicationContextRunner mt_runner;
+
+		List<String> mt_properties = new ArrayList<>(properties);
+		mt_properties.add("sap.security.services.xsuaa[0].url:http://localhost");
+		mt_properties.add("sap.security.services.xsuaa[0].clientid:cid");
+		mt_properties.add("sap.security.services.xsuaa[0].certificate:cert");
+		mt_properties.add("sap.security.services.xsuaa[0].key:key");
+		mt_properties.add("sap.security.services.xsuaa[1].clientid:cid");
+
+		mt_runner = new WebApplicationContextRunner()
+				.withPropertyValues(mt_properties.toArray(new String[0]))
+				.withConfiguration(AutoConfigurations.of(HybridIdentityServicesAutoConfiguration.class,
+						XsuaaTokenFlowAutoConfiguration.class));
+
+		mt_runner.run(context -> assertTrue(context.containsBean("xsuaaTokenFlows")));
+		mt_runner.run(context -> assertTrue(context.containsBean("mtlsRestOperations")));
+	}
+
 	@Test
 	void userConfigurationCanOverrideDefaultBeans() {
 		runner.withUserConfiguration(UserConfiguration.class)
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
index 164d4e74..8342a996 100644
--- 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
@@ -10,6 +10,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
 import java.net.URI;
 
+import static com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault.VCAP_SERVICES_CREDENTIALS;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -52,8 +53,20 @@ public class XsuaaServiceConfigurationDefaultTest {
 
 	@Test
 	public void unsupportedMethods() {
-		assertThatThrownBy(() -> cut.hasProperty("")).isInstanceOf(UnsupportedOperationException.class);
-		assertThatThrownBy(() -> cut.getProperty("")).isInstanceOf(UnsupportedOperationException.class);
 		assertThatThrownBy(() -> cut.getProperties()).isInstanceOf(UnsupportedOperationException.class);
 	}
+
+	@Test
+	public void hasPropertyRequiresEnvironmentVar() {
+		assertThatThrownBy(() -> cut.hasProperty("clientid"))
+				.isInstanceOf(NullPointerException.class)
+				.hasMessageContaining(VCAP_SERVICES_CREDENTIALS);
+	}
+
+	@Test
+	public void getPropertyRequiresEnvironmentVar() {
+		assertThatThrownBy(() -> cut.getProperty("clientid"))
+				.isInstanceOf(NullPointerException.class)
+				.hasMessageContaining(VCAP_SERVICES_CREDENTIALS);
+	}
 }
\ No newline at end of file
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationFromEnvTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationFromEnvTest.java
new file mode 100644
index 00000000..091ff536
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationFromEnvTest.java
@@ -0,0 +1,41 @@
+package com.sap.cloud.security.xsuaa;
+
+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.CLIENT_ID;
+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 XsuaaServiceConfigurationFromEnvTest {
+	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(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/autoconfiguration/XsuaaAutoConfigurationTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfigurationTest.java
index b128af29..5a36537e 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfigurationTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfigurationTest.java
@@ -70,7 +70,7 @@ public class XsuaaAutoConfigurationTest {
 	}
 
 	@Test
-	public void configures_xsuaaMtlsRestTemplate() {
+	public void configures_xsuaaMtlsRestTemplateWithCredType() {
 		contextRunner
 				.withPropertyValues("spring.xsuaa.flows.auto:true")
 				.withPropertyValues("xsuaa.credential-type:x509")
@@ -84,6 +84,19 @@ public class XsuaaAutoConfigurationTest {
 				});
 	}
 
+	@Test
+	public void configures_xsuaaMtlsRestTemplate() {
+		contextRunner
+				.withPropertyValues("xsuaa.clientid:client")
+				.withPropertyValues("xsuaa.certificate:" + cert)
+				.withPropertyValues("xsuaa.key:" + key)
+				.withPropertyValues("xsuaa.certurl:https://domain.cert.authentication.sap.com")
+				.run((context) -> {
+					assertThat(context).hasSingleBean(RestOperations.class);
+					assertThat(context).hasBean("xsuaaMtlsRestOperations");
+				});
+	}
+
 	@Test
 	public void configures_xsuaaServiceConfiguration_withProperties() {
 		contextRunner.withClassLoader(new FilteredClassLoader(CloseableHttpClient.class))