com.sap.cloud.sdk.cloudplatform.security.RefreshTokenRetrievalCommand Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of security-scp-cf Show documentation
Show all versions of security-scp-cf Show documentation
Implementation of the Cloud platform abstraction for security functionality
on the SAP Cloud Platform (Cloud Foundry).
/*
* Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.cloudplatform.security;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.json.JsonSanitizer;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.sap.cloud.sdk.cloudplatform.CloudPlatform;
import com.sap.cloud.sdk.cloudplatform.CloudPlatformAccessor;
import com.sap.cloud.sdk.cloudplatform.ScpCfCloudPlatform;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpEntityUtil;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import com.sap.cloud.sdk.cloudplatform.security.exception.TokenRequestDeniedException;
import com.sap.cloud.sdk.cloudplatform.security.exception.TokenRequestFailedException;
import com.sap.cloud.sdk.frameworks.hystrix.Command;
import com.sap.cloud.sdk.frameworks.hystrix.HystrixUtil;
import lombok.Data;
/**
* This command is used by the CloudFoundry security setup to retrieve a refresh token from a bound XSUAA instance.
* Refresh tokens can be used to refresh expired JWTs. NOTE: This is accepted duplicate code to
* connectivity-scp-cf:TokenRequest.
*/
class RefreshTokenRetrievalCommand extends Command
{
private final DecodedJWT jwt;
@Data
private static class CommandSetterBuilder
{
private final Class extends Command>> commandClass;
HystrixCommand.Setter build()
{
final String groupKey = HystrixUtil.getGlobalKey(commandClass);
final String commandKey = "refreshTokenRetrievalCommand";
final HystrixCommandProperties.Setter commandProperties =
HystrixCommandProperties
.Setter()
.withExecutionTimeoutInMilliseconds(6000)
.withCircuitBreakerEnabled(true)
.withCircuitBreakerSleepWindowInMilliseconds(6000)
.withFallbackEnabled(false);
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties
.Setter()
.withCoreSize(10)
.withQueueSizeRejectionThreshold(100)
.withMaxQueueSize(100))
.andCommandPropertiesDefaults(commandProperties);
}
}
RefreshTokenRetrievalCommand( final DecodedJWT jwt )
{
super(new CommandSetterBuilder(RefreshTokenRetrievalCommand.class).build());
this.jwt = jwt;
}
@Override
protected String run()
{
// get XSUAA credentials from environment variables
final CloudPlatform cloudPlatform = CloudPlatformAccessor.getCloudPlatform();
if( !(cloudPlatform instanceof ScpCfCloudPlatform) ) {
throw new ShouldNotHappenException(
"The current Cloud platform is not an instance of "
+ ScpCfCloudPlatform.class.getSimpleName()
+ ". Please make sure to specify a dependency to com.sap.cloud.s4hana.cloudplatform:core-scp-cf.");
}
final JsonObject xsuaaCreds = ((ScpCfCloudPlatform) cloudPlatform).getXsuaaServiceCredentials(jwt);
final String clientid = xsuaaCreds.get("clientid").getAsString();
final String xsuaaUrl = xsuaaCreds.get("url").getAsString();
// check for user.uaa scope
final List uaaUserScope =
jwt.getClaim("scope").asList(String.class).stream().filter(scope -> scope.equals("uaa.user")).collect(
Collectors.toList());
if( uaaUserScope.isEmpty() ) {
throw new TokenRequestDeniedException(
"Unable to get access token: "
+ "user does not have scope 'uaa.user'. "
+ "This is mandatory for the user token flow. "
+ "Please make sure to that this scope is assigned to the user.");
}
// assemble request
final String authorizationBearer = jwt.getToken();
final URI uri;
try {
uri = new URI((xsuaaUrl.endsWith("/") ? xsuaaUrl : xsuaaUrl + "/") + "oauth/token");
}
catch( final URISyntaxException e ) {
throw new TokenRequestFailedException(e);
}
final HttpPost tokenRequest = new HttpPost(uri);
tokenRequest.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
tokenRequest.setHeader("Authorization", "Bearer " + authorizationBearer);
tokenRequest.setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.toString());
try {
tokenRequest.setEntity(
new StringEntity(
"client_id=" + clientid + "&grant_type=user_token&token_format=token&response_type=token"));
}
catch( final UnsupportedEncodingException e ) {
throw new TokenRequestFailedException(e);
}
final HttpResponse response;
try {
response = HttpClientAccessor.getHttpClient().execute(tokenRequest);
}
catch( final IOException e ) {
throw new TokenRequestFailedException(e);
}
final int statusCode = response.getStatusLine().getStatusCode();
if( statusCode >= 400 && statusCode <= 599 ) {
throw new TokenRequestFailedException(
"Refresh token retrieval request failed with status code "
+ statusCode
+ ": "
+ response.getStatusLine().getReasonPhrase());
}
final String responseBody;
try {
responseBody = HttpEntityUtil.getResponseBody(response);
}
catch( final IOException e ) {
throw new TokenRequestFailedException(e);
}
final JsonObject responseBodyJson =
new JsonParser().parse(JsonSanitizer.sanitize(responseBody)).getAsJsonObject();
@Nullable
final JsonElement refreshTokenElement = responseBodyJson.get("refresh_token");
if( refreshTokenElement == null || !refreshTokenElement.isJsonPrimitive() ) {
throw new TokenRequestFailedException(
"Failed to get access token: no valid refresh token found in response of user token flow."
+ "Please make sure to correctly bind your application to a XSUAA service instance.");
}
return refreshTokenElement.getAsString();
}
}