From 47c0098f5bd43fff342d28dad4329374ead29697 Mon Sep 17 00:00:00 2001 From: Daniel Hassler Date: Thu, 12 Sep 2019 11:21:35 +0200 Subject: [PATCH 1/3] SECAUTH-479: [Token-Clien] Support Password-token in OAuth2TokenService --- .../xsuaa/client/OAuth2TokenService.java | 24 +- .../client/OAuth2TokenServiceConstants.java | 4 + .../xsuaa/client/XsuaaOAuth2TokenService.java | 214 ++++++++++-------- .../XsuaaOAuth2TokenServicePasswordTest.java | 197 ++++++++++++++++ 4 files changed, 341 insertions(+), 98 deletions(-) create mode 100644 token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java 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 00c8421eec..ca9ce7adbf 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 @@ -23,7 +23,6 @@ public interface OAuth2TokenService { * supplying a subdomain (tenant). * @param optionalParameters * optional request parameters, can be null. - * * @return the OAuth2AccessToken. * @throws OAuth2ServiceException * in case of an error during the http request. @@ -50,7 +49,6 @@ OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(URI tokenEndpoi * supplying a subdomain (tenant). * @param optionalParameters * optional request parameters, can be null. - * * @return the OAuth2AccessToken. * @throws OAuth2ServiceException * in case of an error during the http request. @@ -82,4 +80,26 @@ OAuth2TokenResponse retrieveAccessTokenViaUserTokenGrant(URI tokenEndpointUri, */ OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(URI tokenEndpointUri, ClientCredentials clientCredentials, String refreshToken, @Nullable String subdomain) throws OAuth2ServiceException; + + /** + * @param tokenEndpointUri + * the token endpoint URI. + * @param clientCredentials + * the client id and secret of the OAuth client, the recipient of the + * token. + * @param username + * the username for the user trying to get a token + * @param password + * the password for the user trying to get a token + * @param subdomain + * optionally indicates what Identity Zone this request goes to by + * supplying a subdomain (tenant). + * @param optionalParameters + * optional request parameters, can be null. + * @return + * @throws OAuth2ServiceException + */ + OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(URI tokenEndpointUri, ClientCredentials clientCredentials, + String username, String password, String subdomain, Map optionalParameters) + throws OAuth2ServiceException; } diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java index 3fcd5bd420..bf41a3f0c9 100644 --- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java +++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java @@ -11,11 +11,15 @@ private OAuth2TokenServiceConstants() { public static final String REFRESH_TOKEN = "refresh_token"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; public static final String GRANT_TYPE = "grant_type"; public static final String GRANT_TYPE_USER_TOKEN = "user_token"; public static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; + public static final String GRANT_TYPE_PASSWORD = "password"; + public static final String TOKEN_TYPE_OPAQUE = "opaque"; public static final String PARAMETER_CLIENT_ID = "client_id"; diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java index c3ec85378d..5deb18c1e6 100644 --- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java +++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java @@ -1,21 +1,5 @@ package com.sap.cloud.security.xsuaa.client; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.CLIENT_ID; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.CLIENT_SECRET; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.EXPIRES_IN; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_CLIENT_CREDENTIALS; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_REFRESH_TOKEN; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_USER_TOKEN; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.PARAMETER_CLIENT_ID; -import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.REFRESH_TOKEN; - -import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; @@ -33,16 +17,99 @@ import org.springframework.web.util.UriBuilder; import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*; + public class XsuaaOAuth2TokenService implements OAuth2TokenService { - private final RestOperations restOperations; private static Logger logger = LoggerFactory.getLogger(XsuaaOAuth2TokenService.class); + private final RestOperations restOperations; public XsuaaOAuth2TokenService(@NonNull RestOperations restOperations) { Assert.notNull(restOperations, "restOperations is required"); this.restOperations = restOperations; } + /** + * Utility method that replaces the subdomain of the URI with the given + * subdomain. + * + * @param uri + * the URI to be replaced. + * @param subdomain + * of the tenant. + * @return the URI with the replaced subdomain or the passed URI in case a + * replacement was not possible. + */ + static URI replaceSubdomain(@NonNull URI uri, @Nullable String subdomain) { + Assert.notNull(uri, "the uri parameter must not be null"); + if (StringUtils.hasText(subdomain) && uri.getHost().contains(".")) { + UriBuilder builder = UriComponentsBuilder.newInstance().scheme(uri.getScheme()) + .host(subdomain + uri.getHost().substring(uri.getHost().indexOf('.'))).port(uri.getPort()) + .path(uri.getPath()); + return uri.resolve(builder.build()); + } + logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain); + return uri; + } + + /** + * Creates a copy of the given map or an new empty map of type MultiValueMap. + * + * @return a new @link{MultiValueMap} that contains all entries of the optional + * map. + */ + private static MultiValueMap copyIntoForm(Map parameters) { + MultiValueMap formData = new LinkedMultiValueMap(); + if (parameters != null) { + parameters.forEach(formData::add); + } + return formData; + } + + /** + * Creates the set of HTTP headers with client-credentials basic authentication + * header. + * + * @return the HTTP headers. + */ + private static HttpHeaders createHeadersWithoutAuthorization() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + return headers; + } + + /** + * Creates the set of HTTP headers with Authorization Bearer header. + * + * @return the HTTP headers. + */ + private static HttpHeaders createHeadersWithAuthorization(String token) { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + addAuthorizationBearerHeader(headers, token); + return headers; + } + + /** + * Adds the {@code Authorization: Bearer } header to the set of headers. + * + * @param headers + * - the set of headers to add the header to. + * @param token + * - the token which should be part of the header. + */ + static void addAuthorizationBearerHeader(HttpHeaders headers, String token) { + final String AUTHORIZATION_BEARER_TOKEN_FORMAT = "Bearer %s"; + headers.add(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_BEARER_TOKEN_FORMAT, token)); + } + @Override public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@NonNull URI tokenEndpointUri, @NonNull ClientCredentials clientCredentials, @@ -54,8 +121,7 @@ public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@NonNull // build parameters Map parameters = new HashMap<>(); parameters.put(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS); - parameters.put(CLIENT_ID, clientCredentials.getId()); - parameters.put(CLIENT_SECRET, clientCredentials.getSecret()); + addClientCredentials(clientCredentials, parameters); if (optionalParameters != null) { optionalParameters.forEach(parameters::putIfAbsent); } @@ -101,8 +167,7 @@ public OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(@NonNull URI token Map parameters = new HashMap<>(); parameters.put(GRANT_TYPE, GRANT_TYPE_REFRESH_TOKEN); parameters.put(REFRESH_TOKEN, refreshToken); - parameters.put(CLIENT_ID, clientCredentials.getId()); - parameters.put(CLIENT_SECRET, clientCredentials.getSecret()); + addClientCredentials(clientCredentials, parameters); // build header HttpHeaders headers = createHeadersWithoutAuthorization(); @@ -110,29 +175,41 @@ public OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(@NonNull URI token return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, copyIntoForm(parameters)); } - /** - * Utility method that replaces the subdomain of the URI with the given - * subdomain. - * - * @param uri - * the URI to be replaced. - * @param subdomain - * of the tenant. - * @return the URI with the replaced subdomain or the passed URI in case a - * replacement was not possible. - */ - static URI replaceSubdomain(@NonNull URI uri, @Nullable String subdomain) { - Assert.notNull(uri, "the uri parameter must not be null"); - if (StringUtils.hasText(subdomain) && uri.getHost().contains(".")) { - UriBuilder builder = UriComponentsBuilder.newInstance().scheme(uri.getScheme()) - .host(subdomain + uri.getHost().substring(uri.getHost().indexOf('.'))).port(uri.getPort()) - .path(uri.getPath()); - return uri.resolve(builder.build()); + @Override + public OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(@NonNull URI tokenEndpoint, + @NonNull ClientCredentials clientCredentials, @NonNull String username, @NonNull String password, + @Nullable String subdomain, @Nullable Map optionalParameters) + throws OAuth2ServiceException { + Assert.notNull(tokenEndpoint, "tokenEndpoint is required"); + Assert.notNull(clientCredentials, "clientCredentials are required"); + Assert.notNull(username, "username is required"); + Assert.notNull(password, "password is required"); + + Map parameters = new HashMap<>(); + parameters.put(GRANT_TYPE, GRANT_TYPE_PASSWORD); + parameters.put(USERNAME, username); + parameters.put(PASSWORD, password); + addClientCredentials(clientCredentials, parameters); + + if (optionalParameters != null) { + optionalParameters.forEach(parameters::putIfAbsent); } - logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain); - return uri; + + HttpHeaders headers = createHeadersWithoutAuthorization(); + + return requestAccessToken(replaceSubdomain(tokenEndpoint, subdomain), headers, copyIntoForm(parameters)); } + private void addClientCredentials(ClientCredentials clientCredentials, + Map parameters) { + parameters.put(CLIENT_ID, clientCredentials.getId()); + parameters.put(CLIENT_SECRET, clientCredentials.getSecret()); + } + + /** + * common utilities + **/ + private OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders headers, MultiValueMap parameters) throws OAuth2ServiceException { @@ -167,59 +244,4 @@ private OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders String refreshToken = accessTokenMap.get(REFRESH_TOKEN); return new OAuth2TokenResponse(accessToken, expiresIn, refreshToken); } - - /** - * Creates a copy of the given map or an new empty map of type MultiValueMap. - * - * @return a new @link{MultiValueMap} that contains all entries of the optional - * map. - */ - private static MultiValueMap copyIntoForm(Map parameters) { - MultiValueMap formData = new LinkedMultiValueMap(); - if (parameters != null) { - parameters.forEach(formData::add); - } - return formData; - } - - /** - * Creates the set of HTTP headers with client-credentials basic authentication - * header. - * - * @return the HTTP headers. - */ - private static HttpHeaders createHeadersWithoutAuthorization() { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - return headers; - } - - /** - * Creates the set of HTTP headers with Authorization Bearer header. - * - * @return the HTTP headers. - */ - private static HttpHeaders createHeadersWithAuthorization(String token) { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - addAuthorizationBearerHeader(headers, token); - return headers; - } - - /** common utilities **/ - - /** - * Adds the {@code Authorization: Bearer } header to the set of headers. - * - * @param headers - * - the set of headers to add the header to. - * @param token - * - the token which should be part of the header. - */ - static void addAuthorizationBearerHeader(HttpHeaders headers, String token) { - final String AUTHORIZATION_BEARER_TOKEN_FORMAT = "Bearer %s"; - headers.add(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_BEARER_TOKEN_FORMAT, token)); - } } diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java new file mode 100644 index 0000000000..6f20770cac --- /dev/null +++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java @@ -0,0 +1,197 @@ +package com.sap.cloud.security.xsuaa.client; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.*; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestOperations; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class XsuaaOAuth2TokenServicePasswordTest { + + private OAuth2TokenService cut; + + private String clientSecret = "test321"; + private String clientId = "theClientId"; + private String password = "test123"; + private String username = "bob"; + private String subdomain = "subdomain"; + private ClientCredentials clientCredentials = new ClientCredentials(clientId, clientSecret); + private URI tokenEndpoint = URI.create("https://subdomain.myauth.server.com/oauth/token"); + private Map optionalParameters; + private Map response; + + @Mock + private RestOperations mockRestOperations; + + @Before + public void setup() { + response = new HashMap(); + response.putIfAbsent(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0"); + response.putIfAbsent(EXPIRES_IN, "43199"); + when(mockRestOperations.postForEntity(any(), any(), any())) + .thenReturn(ResponseEntity.status(200).body(response)); + optionalParameters = new HashMap<>(); + cut = new XsuaaOAuth2TokenService(mockRestOperations); + } + + @Test(expected = OAuth2ServiceException.class) + public void retrieveToken_httpStatusUnauthorized_throwsException() throws OAuth2ServiceException { + throwExceptionOnPost(HttpStatus.UNAUTHORIZED); + + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + } + + @Test(expected = OAuth2ServiceException.class) + public void retrieveToken_httpStatusNotOk_throwsException() throws OAuth2ServiceException { + throwExceptionOnPost(HttpStatus.BAD_REQUEST); + + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + } + + @Test + public void retrieveToken_requiredParametersMissing_throwsException() { + assertThatThrownBy(() -> cut.retrieveAccessTokenViaPasswordGrant(null, clientCredentials, + username, password, subdomain, optionalParameters)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, null, + username, password, subdomain, optionalParameters)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + null, password, subdomain, optionalParameters)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, null, subdomain, optionalParameters)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void retrieveToken_callsTokenEndpoint() throws OAuth2ServiceException { + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + + Mockito.verify(mockRestOperations, times(1)) + .postForEntity(eq(tokenEndpoint), any(), any()); + } + + @Test + public void retrieveToken_setsCorrectGrantType() throws OAuth2ServiceException { + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + + ArgumentCaptor>> requestEntityCaptor = captureRequestEntity(); + + String actualGrantType = valueOfParameter(GRANT_TYPE, requestEntityCaptor); + assertThat(actualGrantType).isEqualTo(GRANT_TYPE_PASSWORD); + } + + @Test + public void retrieveToken_setsUsername() throws OAuth2ServiceException { + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + + ArgumentCaptor>> requestEntityCaptor = captureRequestEntity(); + + assertThat(valueOfParameter(USERNAME, requestEntityCaptor)).isEqualTo(username); + } + + @Test + public void retrieveToken_setsPassword() throws OAuth2ServiceException { + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + + ArgumentCaptor>> requestEntityCaptor = captureRequestEntity(); + + assertThat(valueOfParameter(PASSWORD, requestEntityCaptor)).isEqualTo(password); + } + + @Test + public void retrieveToken_setsClientCredentials() throws OAuth2ServiceException { + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + + ArgumentCaptor>> requestEntityCaptor = captureRequestEntity(); + + assertThat(valueOfParameter(CLIENT_ID, requestEntityCaptor)).isEqualTo(clientCredentials.getId()); + assertThat(valueOfParameter(CLIENT_SECRET, requestEntityCaptor)).isEqualTo(clientCredentials.getSecret()); + } + + @Test + public void retrieveToken_setsOptionalParameters() throws OAuth2ServiceException { + String tokenFormatParameterKey = "token_format"; + String tokenFormat = "opaque"; + String loginHintParameterKey = "login_hint"; + String loginHint = "origin"; + + optionalParameters.put(tokenFormatParameterKey, tokenFormat); + optionalParameters.put(loginHintParameterKey, loginHint); + + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, optionalParameters); + + ArgumentCaptor>> requestEntityCaptor = captureRequestEntity(); + assertThat(valueOfParameter(tokenFormatParameterKey, requestEntityCaptor)).isEqualTo(tokenFormat); + assertThat(valueOfParameter(loginHintParameterKey, requestEntityCaptor)).isEqualTo(loginHint); + } + + @Test + public void retrieveToken_setsCorrectHeaders() throws OAuth2ServiceException { + cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, optionalParameters); + + ArgumentCaptor>> requestEntityCaptor = captureRequestEntity(); + HttpHeaders headers = requestEntityCaptor.getValue().getHeaders(); + + assertThat(headers.getAccept()).containsExactly(MediaType.APPLICATION_JSON); + assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED); + } + + @Test + public void retrieveToken() throws OAuth2ServiceException { + OAuth2TokenResponse actualResponse = cut.retrieveAccessTokenViaPasswordGrant(tokenEndpoint, clientCredentials, + username, password, null, null); + + assertThat(actualResponse.getAccessToken()).isEqualTo(response.get(ACCESS_TOKEN)); + + assertThat(actualResponse.getExpiredAtDate()).isNotNull(); + } + + private ArgumentCaptor>> captureRequestEntity() { + ArgumentCaptor>> requestEntityCaptor = ArgumentCaptor + .forClass(HttpEntity.class); + Mockito.verify(mockRestOperations, times(1)) + .postForEntity( + eq(tokenEndpoint), + requestEntityCaptor.capture(), + eq(Map.class)); + return requestEntityCaptor; + } + + private String valueOfParameter( + String parameterKey, ArgumentCaptor>> requestEntityCaptor) { + MultiValueMap body = requestEntityCaptor.getValue().getBody(); + return body.getFirst(parameterKey); + } + + private void throwExceptionOnPost(HttpStatus unauthorized) { + when(mockRestOperations.postForEntity(any(), any(), any())) + .thenThrow(new HttpClientErrorException(unauthorized)); + } + +} \ No newline at end of file From b734bb0da605c876fa4fae75cf730cfa8cc4f760 Mon Sep 17 00:00:00 2001 From: Daniel Hassler Date: Thu, 12 Sep 2019 11:49:09 +0200 Subject: [PATCH 2/3] SECAUTH-479: [Token-Clien] Improved documentation, moved method down --- .../security/xsuaa/client/OAuth2TokenService.java | 6 ++++-- .../xsuaa/client/XsuaaOAuth2TokenService.java | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) 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 ca9ce7adbf..c1296ed17d 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 @@ -96,10 +96,12 @@ OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(URI tokenEndpointUri, Cli * supplying a subdomain (tenant). * @param optionalParameters * optional request parameters, can be null. - * @return + * @return the OAuth2AccessToken * @throws OAuth2ServiceException + * in case of an error during the http request. */ OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(URI tokenEndpointUri, ClientCredentials clientCredentials, - String username, String password, String subdomain, Map optionalParameters) + String username, String password, @Nullable String subdomain, + @Nullable Map optionalParameters) throws OAuth2ServiceException; } diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java index 5deb18c1e6..453e103a74 100644 --- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java +++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java @@ -200,12 +200,6 @@ public OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(@NonNull URI toke return requestAccessToken(replaceSubdomain(tokenEndpoint, subdomain), headers, copyIntoForm(parameters)); } - private void addClientCredentials(ClientCredentials clientCredentials, - Map parameters) { - parameters.put(CLIENT_ID, clientCredentials.getId()); - parameters.put(CLIENT_SECRET, clientCredentials.getSecret()); - } - /** * common utilities **/ @@ -244,4 +238,10 @@ private OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders String refreshToken = accessTokenMap.get(REFRESH_TOKEN); return new OAuth2TokenResponse(accessToken, expiresIn, refreshToken); } + + private void addClientCredentials(ClientCredentials clientCredentials, + Map parameters) { + parameters.put(CLIENT_ID, clientCredentials.getId()); + parameters.put(CLIENT_SECRET, clientCredentials.getSecret()); + } } From e5e1e7e44d8e79337e768dd5f155094a16ec9a61 Mon Sep 17 00:00:00 2001 From: Daniel Hassler Date: Thu, 12 Sep 2019 13:56:47 +0200 Subject: [PATCH 3/3] SECAUTH-479: [Token-Clien] Restructured code --- .../xsuaa/client/OAuth2TokenService.java | 4 +- .../xsuaa/client/XsuaaOAuth2TokenService.java | 164 +++++++++--------- 2 files changed, 83 insertions(+), 85 deletions(-) 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 c1296ed17d..242e8e84fc 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 @@ -102,6 +102,6 @@ OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(URI tokenEndpointUri, Cli */ OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(URI tokenEndpointUri, ClientCredentials clientCredentials, String username, String password, @Nullable String subdomain, - @Nullable Map optionalParameters) - throws OAuth2ServiceException; + @Nullable Map optionalParameters) throws OAuth2ServiceException; + } diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java index 453e103a74..d4714b58d2 100644 --- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java +++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java @@ -26,90 +26,14 @@ public class XsuaaOAuth2TokenService implements OAuth2TokenService { - private static Logger logger = LoggerFactory.getLogger(XsuaaOAuth2TokenService.class); private final RestOperations restOperations; + private static Logger logger = LoggerFactory.getLogger(XsuaaOAuth2TokenService.class); public XsuaaOAuth2TokenService(@NonNull RestOperations restOperations) { Assert.notNull(restOperations, "restOperations is required"); this.restOperations = restOperations; } - /** - * Utility method that replaces the subdomain of the URI with the given - * subdomain. - * - * @param uri - * the URI to be replaced. - * @param subdomain - * of the tenant. - * @return the URI with the replaced subdomain or the passed URI in case a - * replacement was not possible. - */ - static URI replaceSubdomain(@NonNull URI uri, @Nullable String subdomain) { - Assert.notNull(uri, "the uri parameter must not be null"); - if (StringUtils.hasText(subdomain) && uri.getHost().contains(".")) { - UriBuilder builder = UriComponentsBuilder.newInstance().scheme(uri.getScheme()) - .host(subdomain + uri.getHost().substring(uri.getHost().indexOf('.'))).port(uri.getPort()) - .path(uri.getPath()); - return uri.resolve(builder.build()); - } - logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain); - return uri; - } - - /** - * Creates a copy of the given map or an new empty map of type MultiValueMap. - * - * @return a new @link{MultiValueMap} that contains all entries of the optional - * map. - */ - private static MultiValueMap copyIntoForm(Map parameters) { - MultiValueMap formData = new LinkedMultiValueMap(); - if (parameters != null) { - parameters.forEach(formData::add); - } - return formData; - } - - /** - * Creates the set of HTTP headers with client-credentials basic authentication - * header. - * - * @return the HTTP headers. - */ - private static HttpHeaders createHeadersWithoutAuthorization() { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - return headers; - } - - /** - * Creates the set of HTTP headers with Authorization Bearer header. - * - * @return the HTTP headers. - */ - private static HttpHeaders createHeadersWithAuthorization(String token) { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - addAuthorizationBearerHeader(headers, token); - return headers; - } - - /** - * Adds the {@code Authorization: Bearer } header to the set of headers. - * - * @param headers - * - the set of headers to add the header to. - * @param token - * - the token which should be part of the header. - */ - static void addAuthorizationBearerHeader(HttpHeaders headers, String token) { - final String AUTHORIZATION_BEARER_TOKEN_FORMAT = "Bearer %s"; - headers.add(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_BEARER_TOKEN_FORMAT, token)); - } - @Override public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@NonNull URI tokenEndpointUri, @NonNull ClientCredentials clientCredentials, @@ -121,7 +45,7 @@ public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@NonNull // build parameters Map parameters = new HashMap<>(); parameters.put(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS); - addClientCredentials(clientCredentials, parameters); + addClientCredentialsToParameters(clientCredentials, parameters); if (optionalParameters != null) { optionalParameters.forEach(parameters::putIfAbsent); } @@ -167,7 +91,7 @@ public OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(@NonNull URI token Map parameters = new HashMap<>(); parameters.put(GRANT_TYPE, GRANT_TYPE_REFRESH_TOKEN); parameters.put(REFRESH_TOKEN, refreshToken); - addClientCredentials(clientCredentials, parameters); + addClientCredentialsToParameters(clientCredentials, parameters); // build header HttpHeaders headers = createHeadersWithoutAuthorization(); @@ -189,7 +113,7 @@ public OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(@NonNull URI toke parameters.put(GRANT_TYPE, GRANT_TYPE_PASSWORD); parameters.put(USERNAME, username); parameters.put(PASSWORD, password); - addClientCredentials(clientCredentials, parameters); + addClientCredentialsToParameters(clientCredentials, parameters); if (optionalParameters != null) { optionalParameters.forEach(parameters::putIfAbsent); @@ -201,8 +125,27 @@ public OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(@NonNull URI toke } /** - * common utilities - **/ + * Utility method that replaces the subdomain of the URI with the given + * subdomain. + * + * @param uri + * the URI to be replaced. + * @param subdomain + * of the tenant. + * @return the URI with the replaced subdomain or the passed URI in case a + * replacement was not possible. + */ + static URI replaceSubdomain(@NonNull URI uri, @Nullable String subdomain) { + Assert.notNull(uri, "the uri parameter must not be null"); + if (StringUtils.hasText(subdomain) && uri.getHost().contains(".")) { + UriBuilder builder = UriComponentsBuilder.newInstance().scheme(uri.getScheme()) + .host(subdomain + uri.getHost().substring(uri.getHost().indexOf('.'))).port(uri.getPort()) + .path(uri.getPath()); + return uri.resolve(builder.build()); + } + logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain); + return uri; + } private OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders headers, MultiValueMap parameters) throws OAuth2ServiceException { @@ -239,9 +182,64 @@ private OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders return new OAuth2TokenResponse(accessToken, expiresIn, refreshToken); } - private void addClientCredentials(ClientCredentials clientCredentials, + /** + * Creates a copy of the given map or an new empty map of type MultiValueMap. + * + * @return a new @link{MultiValueMap} that contains all entries of the optional + * map. + */ + private static MultiValueMap copyIntoForm(Map parameters) { + MultiValueMap formData = new LinkedMultiValueMap(); + if (parameters != null) { + parameters.forEach(formData::add); + } + return formData; + } + + /** + * Creates the set of HTTP headers with client-credentials basic authentication + * header. + * + * @return the HTTP headers. + */ + private static HttpHeaders createHeadersWithoutAuthorization() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + return headers; + } + + /** + * Creates the set of HTTP headers with Authorization Bearer header. + * + * @return the HTTP headers. + */ + private static HttpHeaders createHeadersWithAuthorization(String token) { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + addAuthorizationBearerHeader(headers, token); + return headers; + } + + private void addClientCredentialsToParameters(ClientCredentials clientCredentials, Map parameters) { parameters.put(CLIENT_ID, clientCredentials.getId()); parameters.put(CLIENT_SECRET, clientCredentials.getSecret()); } + + /** common utilities **/ + + /** + * Adds the {@code Authorization: Bearer } header to the set of headers. + * + * @param headers + * - the set of headers to add the header to. + * @param token + * - the token which should be part of the header. + */ + static void addAuthorizationBearerHeader(HttpHeaders headers, String token) { + final String AUTHORIZATION_BEARER_TOKEN_FORMAT = "Bearer %s"; + headers.add(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_BEARER_TOKEN_FORMAT, token)); + } }