Repository: SAP/cloud-security-xsuaa-integration
Pull request #291 (2020-05-26 12:41:32)

Add requestTokenForUser to XSUserInfoAdapter
Add the `requestTokenForUser` to `XSUserInfoAdapter`.
This PR also includes a bug fix that has not been noticed yet.
Instead of a client credentials flow a user token flow was perform when `requestTokenForClient` was called. This is fixed and tested now!


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4b4c82f..25acdb54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 All notable changes to this project will be documented in this file.
 
+## 2.7.1
+- [java-security] rename Token claim "sap_uid" to „user_uuid“ 
+- Improve AudienceValidator logs
+
 ## 2.7.0
 - [token-client] By default requested tokens are now cached. You can disable the cache globally or per request as described [here](https://github.com/SAP/cloud-security-xsuaa-integration/tree/master/token-client).
 - [java-security]  
diff --git a/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java b/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java
index 9747256f..8438c090 100644
--- a/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java
+++ b/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java
@@ -314,6 +314,8 @@ public interface XSUserInfo {
 	boolean isInForeignMode() throws XSUserInfoException;
 
 	/**
+	 * Performs a client credentials token flow.
+	 *
 	 * @param clientId
 	 *            client id
 	 * @param clientSecret
@@ -328,6 +330,22 @@ public interface XSUserInfo {
 	 */
 	String requestTokenForClient(String clientId, String clientSecret, String uaaUrl) throws XSUserInfoException;
 
+	/**
+	 * Performs a user token flow.
+	 *
+	 * @param clientId
+	 *            client id
+	 * @param clientSecret
+	 *            client secret
+	 * @param uaaUrl
+	 *            the uaa url
+	 * @return the token
+	 * @deprecated can be replaced with token flows from the token-client library.
+	 * @throws XSUserInfoException
+	 * 				if an error occurs during token request
+	 */
+	String requestTokenForUser(String clientId, String clientSecret, String uaaUrl) throws XSUserInfoException;
+
 	/**
 	 * Exchange a token into a token from another service instance
 	 *
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
index 550b7268..4283d278 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
@@ -304,6 +304,11 @@ public class XSUserInfoAdapter implements XSUserInfo {
 
 	@Override
 	public String requestTokenForClient(String clientId, String clientSecret, String baseUaaUrl) {
+		return performTokenFlow(baseUaaUrl, XSTokenRequest.TYPE_CLIENT_CREDENTIALS_TOKEN, clientId, clientSecret, new HashMap<>());
+	}
+
+	@Override
+	public String requestTokenForUser(String clientId, String clientSecret, String baseUaaUrl) {
 		return performTokenFlow(baseUaaUrl, XSTokenRequest.TYPE_USER_TOKEN, clientId, clientSecret, new HashMap<>());
 	}
 
@@ -432,12 +437,19 @@ public class XSUserInfoAdapter implements XSUserInfo {
 		return getAttributeFromClaimAsString(EXTERNAL_ATTRIBUTE, attributeName);
 	}
 
+	/**
+	 * Getter for XsuaaTokenFlows object that can be overridden for testing purposes.
+	 */
+	XsuaaTokenFlows getXsuaaTokenFlows(String baseUaaUrl, ClientCredentials clientCredentials) {
+		return new XsuaaTokenFlows(getOrCreateOAuth2TokenService(),
+				new XsuaaDefaultEndpoints(baseUaaUrl), clientCredentials);
+	}
+
 	private String performTokenFlow(String baseUaaUrl, int tokenRequestType, String clientId, String clientSecret,
 			Map<String, String> additionalAuthAttributes) {
 		try {
 			ClientCredentials clientCredentials = new ClientCredentials(clientId, clientSecret);
-			XsuaaTokenFlows xsuaaTokenFlows = new XsuaaTokenFlows(getOrCreateOAuth2TokenService(),
-					new XsuaaDefaultEndpoints(baseUaaUrl), clientCredentials);
+			XsuaaTokenFlows xsuaaTokenFlows = getXsuaaTokenFlows(baseUaaUrl, clientCredentials);
 			return performRequest(xsuaaTokenFlows, tokenRequestType, additionalAuthAttributes);
 		} catch (RuntimeException e) {
 			throw new XSUserInfoException(e.getMessage());


Test patch:
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
index 73d2b63a..97ba9452 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
@@ -6,10 +6,15 @@ import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.token.*;
+import com.sap.cloud.security.xsuaa.client.ClientCredentials;
+import com.sap.cloud.security.xsuaa.client.OAuth2TokenResponse;
+import com.sap.cloud.security.xsuaa.tokenflows.ClientCredentialsTokenFlow;
+import com.sap.cloud.security.xsuaa.tokenflows.TokenFlowException;
+import com.sap.cloud.security.xsuaa.tokenflows.UserTokenFlow;
+import com.sap.cloud.security.xsuaa.tokenflows.XsuaaTokenFlows;
 import com.sap.xsa.security.container.XSUserInfoException;
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -280,6 +285,47 @@ public class XSUserInfoAdapterTest {
 		assertThat(cut.getOrigin()).isEqualTo("useridp");
 	}
 
+	@Test
+	public void requestTokenForClient() throws TokenFlowException {
+		String clientId = "theClientId";
+		String clientSecret = "theClientSecret";
+		String uaaBaseUrl = "http://oauth.base.url/oauth/token";
+
+		XsuaaTokenFlows xsuaaTokenFlowsMock = Mockito.mock(XsuaaTokenFlows.class);
+		doReturn(clientCredentialsTokenFlowMock()).when(xsuaaTokenFlowsMock).clientCredentialsTokenFlow();
+		cut = createComponentUnderTestSpy();
+		doReturn(xsuaaTokenFlowsMock).when(cut).getXsuaaTokenFlows(anyString(), any(ClientCredentials.class));
+
+		cut.requestTokenForClient(clientId, clientSecret, uaaBaseUrl);
+
+		verify(cut, times(1)).getXsuaaTokenFlows(eq(uaaBaseUrl), eq(new ClientCredentials(clientId, clientSecret)));
+		verify(xsuaaTokenFlowsMock, times(1)).clientCredentialsTokenFlow();
+	}
+
+
+	@Test
+	public void requestTokenForUser() throws TokenFlowException {
+		String clientId = "theClientId";
+		String clientSecret = "theClientSecret";
+		String uaaBaseUrl = "http://oauth.base.url/oauth/token";
+		String token = "token";
+
+		XsuaaTokenFlows xsuaaTokenFlowsMock = Mockito.mock(XsuaaTokenFlows.class);
+		UserTokenFlow userTokenFlowMock = userTokenFlowMock();
+		doReturn(userTokenFlowMock).when(xsuaaTokenFlowsMock).userTokenFlow();
+		cut = createComponentUnderTestSpy();
+		doReturn(xsuaaTokenFlowsMock).when(cut).getXsuaaTokenFlows(anyString(), any(ClientCredentials.class));
+		doReturn(token).when(cut).getAppToken();
+
+		cut.requestTokenForUser(clientId, clientSecret, uaaBaseUrl);
+
+		verify(cut, times(1)).getXsuaaTokenFlows(eq(uaaBaseUrl), eq(new ClientCredentials(clientId, clientSecret)));
+		verify(cut, times(1)).getAppToken();
+		verify(userTokenFlowMock, times(1)).token(token);
+		verify(xsuaaTokenFlowsMock, times(1)).userTokenFlow();
+	}
+
+
 	@Test
 	public void testGetOrigin_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
@@ -455,6 +501,7 @@ public class XSUserInfoAdapterTest {
 		assertThat(cut.isInForeignMode()).isTrue();
 	}
 
+
 	private XSUserInfoAdapter createComponentUnderTestSpy() throws XSUserInfoException {
 		return spy(new XSUserInfoAdapter(mock(XsuaaToken.class), mock(OAuth2ServiceConfiguration.class)));
 	}
@@ -482,4 +529,20 @@ public class XSUserInfoAdapterTest {
 		return mockToken;
 	}
 
+	private UserTokenFlow userTokenFlowMock() throws TokenFlowException {
+		UserTokenFlow userTokenFlowMock = mock(UserTokenFlow.class);
+		when(userTokenFlowMock.subdomain(any())).thenReturn(userTokenFlowMock);
+		when(userTokenFlowMock.attributes(any())).thenReturn(userTokenFlowMock);
+		when(userTokenFlowMock.token(any())).thenReturn(userTokenFlowMock);
+		when(userTokenFlowMock.execute()).thenReturn(Mockito.mock(OAuth2TokenResponse.class));
+		return userTokenFlowMock;
+	}
+
+	private ClientCredentialsTokenFlow clientCredentialsTokenFlowMock() throws TokenFlowException {
+		ClientCredentialsTokenFlow clientCredentialsTokenFlowMock = mock(ClientCredentialsTokenFlow.class);
+		when(clientCredentialsTokenFlowMock.subdomain(any())).thenReturn(clientCredentialsTokenFlowMock);
+		when(clientCredentialsTokenFlowMock.attributes(any())).thenReturn(clientCredentialsTokenFlowMock);
+		when(clientCredentialsTokenFlowMock.execute()).thenReturn(Mockito.mock(OAuth2TokenResponse.class));
+		return clientCredentialsTokenFlowMock;
+	}
 }
\ No newline at end of file