All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.sdk.cloudplatform.security.RefreshTokenRetrievalCommand Maven / Gradle / Ivy

Go to download

Implementation of the Cloud platform abstraction for security functionality on the SAP Cloud Platform (Cloud Foundry).

There is a newer version: 2.28.0
Show newest version
/*
 * 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> 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();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy