Repository: SAP/cloud-security-xsuaa-integration
Pull request #148 (2019-10-10 06:45:22)

Refactoring XsuaaOAuth2TokenService
Refactoring as preparation for the implementation of _SECAUTH-516_ to additionally support the apache rest client.
I have moved almost all of the logic from `XsuaaOAuth2TokenService` into separate classes that can in the next step be reused by the apache implementation.


Patch:
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/RequestParameterBuilder.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/RequestParameterBuilder.java
new file mode 100644
index 00000000..4e8bb763
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/RequestParameterBuilder.java
@@ -0,0 +1,54 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*;
+
+public class RequestParameterBuilder {
+
+	private final Map<String, String> parameters = new HashMap<>();
+
+	public RequestParameterBuilder withGrantType(String grantType) {
+		parameters.put(GRANT_TYPE, grantType);
+		return this;
+	}
+
+	public RequestParameterBuilder withClientId(String clientId) {
+		parameters.put(PARAMETER_CLIENT_ID, clientId);
+		return this;
+	}
+
+	public RequestParameterBuilder withRefreshToken(String refreshToken) {
+		parameters.put(REFRESH_TOKEN, refreshToken);
+		return this;
+	}
+
+	public RequestParameterBuilder withClientCredentials(ClientCredentials clientCredentials) {
+		parameters.put(CLIENT_ID, clientCredentials.getId());
+		parameters.put(CLIENT_SECRET, clientCredentials.getSecret());
+		return this;
+	}
+
+	public RequestParameterBuilder withOptionalParameters(Map<String, String> optionalParameters) {
+		Optional.ofNullable(optionalParameters).orElse(Collections.emptyMap())
+				.forEach((key, value) -> parameters.putIfAbsent(key, value));
+		return this;
+	}
+
+	public RequestParameterBuilder withUsername(String username) {
+		parameters.put(USERNAME, username);
+		return this;
+	}
+
+	public RequestParameterBuilder withPassword(String password) {
+		parameters.put(PASSWORD, password);
+		return this;
+	}
+
+	public Map<String, String> buildAsMap() {
+		return parameters;
+	}
+}
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 0e625b38..7a3fc8ff 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,26 +1,23 @@
 package com.sap.cloud.security.xsuaa.client;
 
+import com.sap.cloud.security.xsuaa.Assertions;
+import com.sap.cloud.security.xsuaa.http.HttpHeaders;
+import com.sap.cloud.security.xsuaa.http.HttpHeadersFactory;
+import com.sap.cloud.security.xsuaa.util.UriUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
-import org.springframework.lang.NonNull;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
-import org.springframework.util.StringUtils;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.HttpServerErrorException;
 import org.springframework.web.client.RestOperations;
-import org.springframework.web.util.UriBuilder;
 import org.springframework.web.util.UriComponentsBuilder;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 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.*;
@@ -29,135 +26,108 @@ public class XsuaaOAuth2TokenService implements OAuth2TokenService {
 
 	private final RestOperations restOperations;
 	private static Logger logger = LoggerFactory.getLogger(XsuaaOAuth2TokenService.class);
+	private final HttpHeadersFactory httpHeadersFactory;
 
-	public XsuaaOAuth2TokenService(@NonNull RestOperations restOperations) {
-		Assert.notNull(restOperations, "restOperations is required");
+	public XsuaaOAuth2TokenService(@Nonnull RestOperations restOperations) {
+		Assertions.assertNotNull(restOperations, "restOperations is required");
 		this.restOperations = restOperations;
+		this.httpHeadersFactory = new HttpHeadersFactory();
 	}
 
 	@Override
-	public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@NonNull URI tokenEndpointUri,
-			@NonNull ClientCredentials clientCredentials,
+	public OAuth2TokenResponse retrieveAccessTokenViaClientCredentialsGrant(@Nonnull URI tokenEndpointUri,
+			@Nonnull ClientCredentials clientCredentials,
 			@Nullable String subdomain, @Nullable Map<String, String> optionalParameters)
 			throws OAuth2ServiceException {
-		Assert.notNull(tokenEndpointUri, "tokenEndpointUri is required");
-		Assert.notNull(clientCredentials, "clientCredentials is required");
-
-		// build parameters
-		Map<String, String> parameters = new HashMap<>();
-		parameters.put(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS);
-		addClientCredentialsToParameters(clientCredentials, parameters);
-		if (optionalParameters != null) {
-			optionalParameters.forEach(parameters::putIfAbsent);
-		}
+		Assertions.assertNotNull(tokenEndpointUri, "tokenEndpointUri is required");
+		Assertions.assertNotNull(clientCredentials, "clientCredentials is required");
+
+		Map<String, String> parameters = new RequestParameterBuilder()
+				.withGrantType(GRANT_TYPE_CLIENT_CREDENTIALS)
+				.withClientCredentials(clientCredentials)
+				.withOptionalParameters(optionalParameters)
+				.buildAsMap();
 
-		// build header
-		HttpHeaders headers = createHeadersWithoutAuthorization();
+		HttpHeaders headers = httpHeadersFactory.createWithoutAuthorizationHeader();
 
-		return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, copyIntoForm(parameters));
+		return requestAccessToken(UriUtil.replaceSubdomain(tokenEndpointUri, subdomain), headers, parameters);
 	}
 
 	@Override
-	public OAuth2TokenResponse retrieveAccessTokenViaUserTokenGrant(@NonNull URI tokenEndpointUri,
-			@NonNull ClientCredentials clientCredentials, @NonNull String token, @Nullable String subdomain,
+	public OAuth2TokenResponse retrieveAccessTokenViaUserTokenGrant(@Nonnull URI tokenEndpointUri,
+			@Nonnull ClientCredentials clientCredentials, @Nonnull String token, @Nullable String subdomain,
 			@Nullable Map<String, String> optionalParameters)
 			throws OAuth2ServiceException {
-		Assert.notNull(tokenEndpointUri, "tokenEndpointUri is required");
-		Assert.notNull(clientCredentials, "clientCredentials is required");
-		Assert.notNull(token, "token is required");
-
-		// build parameters
-		Map<String, String> parameters = new HashMap<>();
-		parameters.put(GRANT_TYPE, GRANT_TYPE_USER_TOKEN);
-		parameters.put(PARAMETER_CLIENT_ID, clientCredentials.getId());
-		if (optionalParameters != null) {
-			optionalParameters.forEach(parameters::putIfAbsent);
-		}
+		Assertions.assertNotNull(tokenEndpointUri, "tokenEndpointUri is required");
+		Assertions.assertNotNull(clientCredentials, "clientCredentials is required");
+		Assertions.assertNotNull(token, "token is required");
+
+		Map<String, String> parameters = new RequestParameterBuilder()
+				.withGrantType(GRANT_TYPE_USER_TOKEN)
+				.withClientId(clientCredentials.getId())
+				.withOptionalParameters(optionalParameters)
+				.buildAsMap();
 
-		// build header
-		HttpHeaders headers = createHeadersWithAuthorization(token);
+		HttpHeaders headers = httpHeadersFactory.createWithAuthorizationBearerHeader(token);
 
-		return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, copyIntoForm(parameters));
+		return requestAccessToken(UriUtil.replaceSubdomain(tokenEndpointUri, subdomain), headers, parameters);
 	}
 
 	@Override
-	public OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(@NonNull URI tokenEndpointUri,
-			@NonNull ClientCredentials clientCredentials,
-			@NonNull String refreshToken, String subdomain) throws OAuth2ServiceException {
-		Assert.notNull(tokenEndpointUri, "tokenEndpointUri is required");
-		Assert.notNull(clientCredentials, "clientCredentials is required");
-		Assert.notNull(refreshToken, "refreshToken is required");
-
-		// build parameters
-		Map<String, String> parameters = new HashMap<>();
-		parameters.put(GRANT_TYPE, GRANT_TYPE_REFRESH_TOKEN);
-		parameters.put(REFRESH_TOKEN, refreshToken);
-		addClientCredentialsToParameters(clientCredentials, parameters);
-
-		// build header
-		HttpHeaders headers = createHeadersWithoutAuthorization();
-
-		return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, copyIntoForm(parameters));
+	public OAuth2TokenResponse retrieveAccessTokenViaRefreshToken(@Nonnull URI tokenEndpointUri,
+			@Nonnull ClientCredentials clientCredentials,
+			@Nonnull String refreshToken, String subdomain) throws OAuth2ServiceException {
+		Assertions.assertNotNull(tokenEndpointUri, "tokenEndpointUri is required");
+		Assertions.assertNotNull(clientCredentials, "clientCredentials is required");
+		Assertions.assertNotNull(refreshToken, "refreshToken is required");
+
+		Map<String, String> parameters = new RequestParameterBuilder()
+				.withGrantType(GRANT_TYPE_REFRESH_TOKEN)
+				.withRefreshToken(refreshToken)
+				.withClientCredentials(clientCredentials)
+				.buildAsMap();
+
+		HttpHeaders headers = httpHeadersFactory.createWithoutAuthorizationHeader();
+
+		return requestAccessToken(UriUtil.replaceSubdomain(tokenEndpointUri, subdomain), headers, parameters);
 	}
 
 	@Override
-	public OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(@NonNull URI tokenEndpoint,
-			@NonNull ClientCredentials clientCredentials, @NonNull String username, @NonNull String password,
+	public OAuth2TokenResponse retrieveAccessTokenViaPasswordGrant(@Nonnull URI tokenEndpoint,
+			@Nonnull ClientCredentials clientCredentials, @Nonnull String username, @Nonnull String password,
 			@Nullable String subdomain, @Nullable Map<String, String> 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<String, String> parameters = new HashMap<>();
-		parameters.put(GRANT_TYPE, GRANT_TYPE_PASSWORD);
-		parameters.put(USERNAME, username);
-		parameters.put(PASSWORD, password);
-		addClientCredentialsToParameters(clientCredentials, parameters);
-
-		if (optionalParameters != null) {
-			optionalParameters.forEach(parameters::putIfAbsent);
-		}
-
-		HttpHeaders headers = createHeadersWithoutAuthorization();
-
-		return requestAccessToken(replaceSubdomain(tokenEndpoint, 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());
-		}
-		logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain);
-		return uri;
+		Assertions.assertNotNull(tokenEndpoint, "tokenEndpoint is required");
+		Assertions.assertNotNull(clientCredentials, "clientCredentials are required");
+		Assertions.assertNotNull(username, "username is required");
+		Assertions.assertNotNull(password, "password is required");
+
+		Map<String, String> parameters = new RequestParameterBuilder()
+				.withGrantType(GRANT_TYPE_PASSWORD)
+				.withUsername(username)
+				.withPassword(password)
+				.withClientCredentials(clientCredentials)
+				.withOptionalParameters(optionalParameters)
+				.buildAsMap();
+
+		HttpHeaders headers = httpHeadersFactory.createWithoutAuthorizationHeader();
+
+		return requestAccessToken(UriUtil.replaceSubdomain(tokenEndpoint, subdomain), headers, parameters);
 	}
 
 	private OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders headers,
-			MultiValueMap<String, String> parameters) throws OAuth2ServiceException {
+			Map<String, String> parameters) throws OAuth2ServiceException {
 
 		// Create URI
 		UriComponentsBuilder builder = UriComponentsBuilder.fromUri(tokenEndpointUri);
 		URI requestUri = builder.build().encode().toUri();
 
-		// Create entity
-		HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parameters, headers);
+		org.springframework.http.HttpHeaders springHeaders = new org.springframework.http.HttpHeaders();
+		headers.getHeaders().forEach(h -> springHeaders.add(h.getName(), h.getValue()));
 
+		// Create entity
+		HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(copyIntoForm(parameters),
+				springHeaders);
 		@SuppressWarnings("rawtypes")
 		ResponseEntity<Map> responseEntity = null;
 		try {
@@ -198,50 +168,4 @@ public class XsuaaOAuth2TokenService implements OAuth2TokenService {
 		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<String, String> parameters) {
-		parameters.put(CLIENT_ID, clientCredentials.getId());
-		parameters.put(CLIENT_SECRET, clientCredentials.getSecret());
-	}
-
-	/** common utilities **/
-
-	/**
-	 * Adds the {@code  Authorization: Bearer <token>} 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/main/java/com/sap/cloud/security/xsuaa/http/HttpHeader.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeader.java
new file mode 100644
index 00000000..1c939043
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeader.java
@@ -0,0 +1,20 @@
+package com.sap.cloud.security.xsuaa.http;
+
+public class HttpHeader {
+
+	private final String name;
+	private final String value;
+
+	public HttpHeader(String name, String value) {
+		this.name = name;
+		this.value = value;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public String getName() {
+		return name;
+	}
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeaders.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeaders.java
new file mode 100644
index 00000000..3cedead6
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeaders.java
@@ -0,0 +1,20 @@
+package com.sap.cloud.security.xsuaa.http;
+
+import java.util.List;
+
+public class HttpHeaders {
+
+	public static final String AUTHORIZATION = "Authorization";
+	public static final String ACCEPT = "Accept";
+	public static final String CONTENT_TYPE = "Content-Type";
+
+	private final List<HttpHeader> headers;
+
+	public HttpHeaders(List<HttpHeader> headers) {
+		this.headers = headers;
+	}
+
+	public List<HttpHeader> getHeaders() {
+		return headers;
+	}
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeadersFactory.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeadersFactory.java
new file mode 100644
index 00000000..1f081b08
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/HttpHeadersFactory.java
@@ -0,0 +1,49 @@
+package com.sap.cloud.security.xsuaa.http;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class HttpHeadersFactory {
+
+	private final Map<String, String> headers;
+
+	public HttpHeadersFactory() {
+		headers = new HashMap<>();
+		headers.put(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON.value());
+		headers.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED.value());
+	}
+
+	/**
+	 * Adds the {@code  Authorization: Bearer <token>} header to the set of headers.
+	 *
+	 * @param token
+	 *            - the token which should be part of the header.
+	 * @return the builder instance.
+	 */
+	public HttpHeaders createWithAuthorizationBearerHeader(String token) {
+		final String AUTHORIZATION_BEARER_TOKEN_FORMAT = "Bearer %s";
+		headers.put(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_BEARER_TOKEN_FORMAT, token));
+		return createFromHeaders();
+	}
+
+	/**
+	 * Creates the set of HTTP headers with client-credentials basic authentication
+	 * header.
+	 *
+	 * @return the HTTP headers.
+	 */
+	public HttpHeaders createWithoutAuthorizationHeader() {
+		return createFromHeaders();
+	}
+
+	private HttpHeaders createFromHeaders() {
+		List<HttpHeader> httpHeaders = this.headers.entrySet()
+				.stream()
+				.map(header -> new HttpHeader(header.getKey(), header.getValue()))
+				.collect(Collectors.toList());
+		return new HttpHeaders(httpHeaders);
+	}
+
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/MediaType.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/MediaType.java
new file mode 100644
index 00000000..72b9ad06
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/http/MediaType.java
@@ -0,0 +1,15 @@
+package com.sap.cloud.security.xsuaa.http;
+
+public enum MediaType {
+	APPLICATION_JSON("application/json"), APPLICATION_FORM_URLENCODED("application/x-www-form-urlencoded");
+
+	private final String value;
+
+	MediaType(String value) {
+		this.value = value;
+	}
+
+	public String value() {
+		return value;
+	}
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/util/UriUtil.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/util/UriUtil.java
new file mode 100644
index 00000000..bc91a09f
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/util/UriUtil.java
@@ -0,0 +1,51 @@
+package com.sap.cloud.security.xsuaa.util;
+
+import com.sap.cloud.security.xsuaa.Assertions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Optional;
+
+public class UriUtil {
+
+	private static final Logger logger = LoggerFactory.getLogger(UriUtil.class);
+
+	/**
+	 * 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.
+	 */
+	public static URI replaceSubdomain(@Nonnull URI uri, @Nullable String subdomain) {
+		Assertions.assertNotNull(uri, "the uri parameter must not be null");
+		if (hasText(subdomain) && hasSubdomain(uri)) {
+			String newHost = subdomain + uri.getHost().substring(uri.getHost().indexOf('.'));
+			try {
+				return uri.resolve(new URI(uri.getScheme(), uri.getUserInfo(), newHost, uri.getPort(), uri.getPath(),
+						uri.getQuery(), uri.getFragment()));
+			} catch (URISyntaxException e) {
+				logger.error("Could not replace subdomain {} in given uri {}", subdomain, uri);
+				throw new IllegalArgumentException(e);
+			}
+		}
+		logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain);
+		return uri;
+	}
+
+	private static boolean hasSubdomain(URI uri) {
+		return uri.getHost().contains(".");
+	}
+
+	private static boolean hasText(String string) {
+		return Optional.ofNullable(string).filter(str -> !str.trim().isEmpty()).isPresent();
+	}
+}


Test patch:
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/util/UriUtilTest.java
similarity index 55%
rename from token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceTest.java
rename to token-client/src/test/java/com/sap/cloud/security/xsuaa/util/UriUtilTest.java
index 8aa3575d..e9e1b953 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/util/UriUtilTest.java
@@ -1,4 +1,4 @@
-package com.sap.cloud.security.xsuaa.client;
+package com.sap.cloud.security.xsuaa.util;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -6,33 +6,32 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import java.net.URI;
 
 import org.junit.Test;
-import org.springframework.web.client.RestTemplate;
 
-public class XsuaaOAuth2TokenServiceTest {
-	private XsuaaOAuth2TokenService cut = new XsuaaOAuth2TokenService(new RestTemplate());
+public class UriUtilTest {
+
 	private URI tokenEndpointUri = URI.create("https://subdomain.myauth.com/mypath");
 
 	@Test
 	public void replaceSubdomain_replacesNothingWhenSubdomainIsNull() {
-		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(tokenEndpointUri, null);
+		URI replacedURI = UriUtil.replaceSubdomain(tokenEndpointUri, null);
 		assertThat(replacedURI, is(tokenEndpointUri));
 	}
 
 	@Test
 	public void replaceSubdomain() {
-		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(tokenEndpointUri, "newsubdomain");
+		URI replacedURI = UriUtil.replaceSubdomain(tokenEndpointUri, "newsubdomain");
 		assertThat(replacedURI.toString(), is("https://newsubdomain.myauth.com/mypath"));
 	}
 
 	@Test
 	public void replaceSubdomain_replacesNothingWhenSubdomainIsEmpty() {
-		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(tokenEndpointUri, "");
+		URI replacedURI = UriUtil.replaceSubdomain(tokenEndpointUri, "");
 		assertThat(replacedURI, is(tokenEndpointUri));
 	}
 
 	@Test
 	public void replaceSubdomain_replacesNothingWhenUrlContainsNoSubdomain() {
-		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(URI.create("http://localhost"), "newsubdomain");
+		URI replacedURI = UriUtil.replaceSubdomain(URI.create("http://localhost"), "newsubdomain");
 		assertThat(replacedURI.toString(), is("http://localhost"));
 	}
 }