Repository: SAP/cloud-security-xsuaa-integration
Pull request #475 (2021-02-19 14:35:48)

Zone id support in clientCredentialsFlow


Patch:
diff --git a/.gitignore b/.gitignore
index 2fd0f336..d2f2f1b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,6 @@ vim.exe.stackdump
 
 __pycache__/
 /.metadata/
+
+### Misc ###
+samples/vars.yml
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenService.java
index 13653aaa..1dbbbab3 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenService.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenService.java
@@ -85,7 +85,7 @@ public abstract class AbstractOAuth2TokenService implements OAuth2TokenService,
 	@Override
 	public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@Nonnull URI tokenEndpointUri,
 			@Nonnull ClientCredentials clientCredentials,
-			@Nullable String subdomain, @Nullable Map<String, String> optionalParameters,
+			@Nullable String zoneId, @Nullable String subdomain, @Nullable Map<String, String> optionalParameters,
 			boolean disableCacheForRequest)
 			throws OAuth2ServiceException {
 		assertNotNull(tokenEndpointUri, "tokenEndpointUri is required");
@@ -98,6 +98,10 @@ public abstract class AbstractOAuth2TokenService implements OAuth2TokenService,
 				.buildAsMap();
 
 		HttpHeaders headers = HttpHeadersFactory.createWithoutAuthorizationHeader();
+		if (zoneId != null){
+			headers.withHeader(HttpHeaders.X_ZID,
+					zoneId);
+		}
 
 		return getOAuth2TokenResponse(tokenEndpointUri, headers, parameters, subdomain, disableCacheForRequest);
 	}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java
index da45f341..4fdd469f 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java
@@ -55,6 +55,7 @@ public class DefaultOAuth2TokenService extends AbstractOAuth2TokenService {
 	protected OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders headers,
 			Map<String, String> parameters) throws OAuth2ServiceException {
 		HttpPost httpPost = createHttpPost(tokenEndpointUri, headers, parameters);
+		LOGGER.debug("access token request {} - {}", headers, parameters);
 		return executeRequest(httpPost);
 	}
 
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java
index 35c161a5..96d8cb5e 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java
@@ -29,11 +29,48 @@ public interface OAuth2TokenService {
 	 * @return the OAuth2AccessToken.
 	 * @throws OAuth2ServiceException
 	 *             in case of an error during the http request.
+	 *
+	 * @deprecated gets removed in favor of
+	 *             {@link #retrieveAccessTokenViaClientCredentialsGrant(URI, ClientCredentials, String, String, Map, boolean)}
+	 *             with next major version 3.0.0
 	 */
-	OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@Nonnull URI tokenEndpointUri,
+	@Deprecated
+	default OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@Nonnull URI tokenEndpointUri,
 			@Nonnull ClientCredentials clientCredentials,
 			@Nullable String subdomain, @Nullable Map<String, String> optionalParameters,
 			boolean disableCacheForRequest)
+			throws OAuth2ServiceException {
+		return retrieveAccessTokenViaClientCredentialsGrant(tokenEndpointUri, clientCredentials, null, subdomain,
+				optionalParameters, disableCacheForRequest);
+	}
+
+	/**
+	 * Requests access token from OAuth Server with client credentials.
+	 *
+	 * @param tokenEndpointUri
+	 *            the token endpoint URI.
+	 * @param clientCredentials
+	 *            the client id and secret of the OAuth client, the recipient of the
+	 *            token.
+	 * @param zoneId
+	 *            Zone identifier - tenant discriminator
+	 * @param subdomain
+	 *            optionally indicates what Identity Zone this request goes to by
+	 *            supplying a subdomain (tenant).
+	 * @param optionalParameters
+	 *            optional request parameters, can be null.
+	 * @param disableCacheForRequest
+	 *            set to true disables the token cache for this request.
+	 * @return the OAuth2AccessToken.
+	 * @throws OAuth2ServiceException
+	 *             in case of an error during the http request.
+	 */
+	OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@Nonnull URI tokenEndpointUri,
+			@Nonnull ClientCredentials clientCredentials,
+			@Nullable String zoneId,
+			@Nullable String subdomain,
+			@Nullable Map<String, String> optionalParameters,
+			boolean disableCacheForRequest)
 			throws OAuth2ServiceException;
 
 	/**
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlow.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlow.java
index 38b961e1..0784eab3 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlow.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlow.java
@@ -80,6 +80,18 @@ public class ClientCredentialsTokenFlow {
 		return this;
 	}
 
+	/**
+	 * Sets the zone Id of the tenant<br>
+	 *
+	 * @param zoneId
+	 *            - the zoneId.
+	 * @return this builder.
+	 */
+	public ClientCredentialsTokenFlow zoneId(String zoneId) {
+		request.setZoneId(zoneId);
+		return this;
+	}
+
 	/**
 	 * Sets the scope attribute for the token request. This will restrict the scope
 	 * of the created token to the scopes provided. By default the scope is not
@@ -168,7 +180,7 @@ public class ClientCredentialsTokenFlow {
 			OAuth2TokenResponse accessToken = tokenService
 					.retrieveAccessTokenViaClientCredentialsGrant(request.getTokenEndpoint(),
 							new ClientCredentials(request.getClientId(), request.getClientSecret()),
-							request.getSubdomain(), requestParameter, disableCache);
+							request.getZoneId(), request.getSubdomain(), requestParameter, disableCache);
 			return accessToken;
 		} catch (OAuth2ServiceException e) {
 			throw new TokenFlowException(
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequest.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequest.java
index 34398057..acbab920 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequest.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequest.java
@@ -19,6 +19,7 @@ class XsuaaTokenFlowRequest implements XSTokenRequest {
 	private String clientId;
 	private URI tokenServiceEndpoint;
 	private String subdomain;
+	private String zoneId;
 	private String clientSecret;
 	private Map<String, String> additionalAuthorizationAttributes;
 	private static final String UNSUPPORTED_INTF_METHOD_INFO = "This XSTokenRequest method is no longer needed in context of new XsuaaTokenFlows API.";
@@ -140,4 +141,19 @@ class XsuaaTokenFlowRequest implements XSTokenRequest {
 		this.subdomain = subdomain;
 		return this;
 	}
+
+	/**
+	 * Get the Identity Zone
+	 *
+	 * @return zoneId or null in case zoneId is not present
+	 */
+	@Nullable
+	public String getZoneId() {
+		return zoneId;
+	}
+
+	public XSTokenRequest setZoneId(String zoneId) {
+		this.zoneId = zoneId;
+		return this;
+	}
 }


Test patch:
diff --git a/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/TokenBrokerTestConfiguration.java b/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/TokenBrokerTestConfiguration.java
index 1db329cb..695b7637 100644
--- a/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/TokenBrokerTestConfiguration.java
+++ b/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/TokenBrokerTestConfiguration.java
@@ -36,7 +36,7 @@ public class TokenBrokerTestConfiguration {
 		return new OAuth2TokenService() {
 			@Override
 			public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(URI tokenEndpointUri,
-					ClientCredentials clientCredentials, @Nullable String subdomain,
+					ClientCredentials clientCredentials, @Nullable String zoneId, @Nullable String subdomain,
 					@Nullable Map<String, String> optionalParameters, boolean disableCacheForRequest)
 					throws OAuth2ServiceException {
 				try {
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenServiceTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenServiceTest.java
index 1ca404aa..94327731 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenServiceTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/AbstractOAuth2TokenServiceTest.java
@@ -21,6 +21,7 @@ public class AbstractOAuth2TokenServiceTest {
 
 	public static final URI TOKEN_ENDPOINT_URI = URI.create("http://test.token.endpoint/oauth/token");
 	public static final String SUBDOMAIN = "subdomain";
+	public static final String ZONE_ID = "zone";
 	private static final Instant NOW = LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0).toInstant(UTC);
 	public static final TokenCacheConfiguration TEST_CACHE_CONFIGURATION = TokenCacheConfiguration
 			.defaultConfiguration();
@@ -250,7 +251,8 @@ public class AbstractOAuth2TokenServiceTest {
 	private OAuth2TokenResponse retrieveAccessTokenViaClientCredentials(ClientCredentials clientCredentials,
 			boolean disableCacheForRequest)
 			throws OAuth2ServiceException {
-		return cut.retrieveAccessTokenViaClientCredentialsGrant(TOKEN_ENDPOINT_URI, clientCredentials, SUBDOMAIN,
+		return cut.retrieveAccessTokenViaClientCredentialsGrant(TOKEN_ENDPOINT_URI, clientCredentials, ZONE_ID,
+				SUBDOMAIN,
 				null, disableCacheForRequest);
 	}
 
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlowTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlowTest.java
index d4fe0da5..78f6168a 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlowTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/ClientCredentialsTokenFlowTest.java
@@ -71,14 +71,14 @@ public class ClientCredentialsTokenFlowTest {
 		verifyThatDisableCacheAttributeIs(false);
 		verify(mockTokenService, times(1))
 				.retrieveAccessTokenViaClientCredentialsGrant(endpointsProvider.getTokenEndpoint(),
-						clientCredentials, null, emptyMap(), false);
+						clientCredentials, null, null, emptyMap(), false);
 	}
 
 	@Test
 	public void execute_throwsIfServiceRaisesException() throws OAuth2ServiceException {
 		when(mockTokenService
 				.retrieveAccessTokenViaClientCredentialsGrant(eq(TOKEN_ENDPOINT_URI), eq(clientCredentials),
-						isNull(), anyMap(), anyBoolean()))
+						isNull(), isNull(), anyMap(), anyBoolean()))
 								.thenThrow(new OAuth2ServiceException("exception executed REST call"));
 
 		assertThatThrownBy(() -> {
@@ -101,7 +101,7 @@ public class ClientCredentialsTokenFlowTest {
 		assertThat(response.getAccessToken()).isSameAs(accessToken.getAccessToken());
 		verify(mockTokenService, times(1))
 				.retrieveAccessTokenViaClientCredentialsGrant(eq(TOKEN_ENDPOINT_URI), eq(clientCredentials),
-						isNull(), optionalParametersCaptor.capture(), anyBoolean());
+						isNull(), isNull(), optionalParametersCaptor.capture(), anyBoolean());
 		Map<String, String> optionalParameters = optionalParametersCaptor.getValue();
 
 		assertThat(optionalParameters).containsKey(AUTHORITIES);
@@ -119,7 +119,7 @@ public class ClientCredentialsTokenFlowTest {
 
 		verify(mockTokenService, times(1))
 				.retrieveAccessTokenViaClientCredentialsGrant(eq(TOKEN_ENDPOINT_URI), eq(clientCredentials),
-						isNull(), optionalParametersCaptor.capture(), anyBoolean());
+						isNull(), isNull(), optionalParametersCaptor.capture(), anyBoolean());
 
 		Map<String, String> optionalParameters = optionalParametersCaptor.getValue();
 		assertThat(optionalParameters).containsKey(SCOPE);
@@ -146,17 +146,31 @@ public class ClientCredentialsTokenFlowTest {
 
 	}
 
+	@Test
+	public void execute_withZoneId() throws TokenFlowException, OAuth2ServiceException {
+		OAuth2TokenResponse accessToken = mockRetrieveAccessToken();
+		OAuth2TokenResponse response = cut.zoneId("zone").execute();
+
+		verify(mockTokenService, times(1))
+				.retrieveAccessTokenViaClientCredentialsGrant(eq(TOKEN_ENDPOINT_URI), eq(clientCredentials),
+						eq("zone"), isNull(), anyMap(), anyBoolean());
+
+		assertThat(response.getAccessToken()).isSameAs(accessToken.getAccessToken());
+
+	}
+
 	private void verifyThatDisableCacheAttributeIs(boolean disableCache) throws OAuth2ServiceException {
 		verify(mockTokenService, times(1))
 				.retrieveAccessTokenViaClientCredentialsGrant(eq(TOKEN_ENDPOINT_URI), eq(clientCredentials),
-						isNull(), anyMap(), eq(disableCache));
+						isNull(), isNull(), anyMap(), eq(disableCache));
 	}
 
 	private OAuth2TokenResponse mockRetrieveAccessToken() throws OAuth2ServiceException {
 		OAuth2TokenResponse accessToken = new OAuth2TokenResponse(JWT_ACCESS_TOKEN, 441231, null);
+
 		when(mockTokenService
 				.retrieveAccessTokenViaClientCredentialsGrant(eq(TOKEN_ENDPOINT_URI), eq(clientCredentials),
-						any(), any(), anyBoolean()))
+						any(), any(), any(), anyBoolean()))
 								.thenReturn(accessToken);
 		return accessToken;
 	}
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequestTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequestTest.java
index 888892a7..1ea91207 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequestTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/tokenflows/XsuaaTokenFlowRequestTest.java
@@ -1,7 +1,7 @@
 package com.sap.cloud.security.xsuaa.tokenflows;
 
 import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import java.net.URI;
 
@@ -14,12 +14,15 @@ public class XsuaaTokenFlowRequestTest {
 		XsuaaTokenFlowRequest request = new XsuaaTokenFlowRequest(URI.create("https://oauth.server.com/oauth/token"));
 		String clientId = "clientId";
 		String clientSecret = "clientSecret";
+		String zoneId = "zone";
 
 		request.setClientId(clientId);
 		request.setClientSecret(clientSecret);
+		request.setZoneId(zoneId);
 
 		assertThat(request.getTokenEndpoint().toString(), is("https://oauth.server.com/oauth/token"));
 		assertThat(request.getClientId(), is(clientId));
 		assertThat(request.getClientSecret(), is(clientSecret));
+		assertThat(request.getZoneId(), is(zoneId));
 	}
 }