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

com.sap.cloud.sdk.cloudplatform.connectivity.XsuaaService Maven / Gradle / Ivy

Go to download

Implementation of the Cloud platform abstraction for general-purpose connectivity on the SAP Cloud Platform (Cloud Foundry).

The newest version!
/*
 * Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved.
 */

package com.sap.cloud.sdk.cloudplatform.connectivity;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import org.slf4j.Logger;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
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.cache.CacheKey;
import com.sap.cloud.sdk.cloudplatform.cache.CacheManager;
import com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.cloudplatform.security.AuthTokenAccessor;
import com.sap.cloud.sdk.cloudplatform.security.BasicCredentials;
import com.sap.cloud.sdk.cloudplatform.security.exception.TokenRequestDeniedException;
import com.sap.cloud.sdk.cloudplatform.security.exception.TokenRequestFailedException;
import com.sap.cloud.sdk.cloudplatform.security.user.exception.UserAccessException;
import com.sap.cloud.sdk.cloudplatform.security.user.exception.UserNotAuthenticatedException;
import com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantAccessException;
import com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantNotAvailableException;

import io.vavr.CheckedFunction0;
import io.vavr.control.Try;

class XsuaaService
{
    private static final Logger logger = CloudLoggerFactory.getSanitizedLogger(XsuaaService.class);

    static final String ERROR_BIND_XSUAA_SERVICE =
        "Please make sure to correctly bind your application to a XSUAA service instance.";

    private static final String SERVICE_CLIENT_ID = "clientid";
    private static final String SERVICE_CLIENT_SECRET = "clientsecret";

    static CacheBuilder tokenCacheBuilder =
        CacheBuilder.newBuilder().concurrencyLevel(10).maximumSize(100000).expireAfterAccess(60, TimeUnit.MINUTES);

    static Cache tokenCache = CacheManager.register(tokenCacheBuilder.build());

    private final SubdomainReplacer subdomainReplacer;

    XsuaaService()
    {
        this(new DefaultSubdomainReplacer());
    }

    XsuaaService( final SubdomainReplacer subdomainReplacer )
    {
        this.subdomainReplacer = subdomainReplacer;
    }

    private ScpCfCloudPlatform getCloudPlatform()
    {
        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.");
        }

        return (ScpCfCloudPlatform) cloudPlatform;
    }

    private URI getXsuaaUri()
        throws TokenRequestFailedException
    {
        final JsonObject xsuaaServiceCredentials =
            Try
                .of(
                    AuthTokenAccessor
                        .getCurrentToken()
                        .> map(
                            token -> () -> getCloudPlatform().getXsuaaServiceCredentials(token.getJwt()))
                        .orElseGet(() -> () -> getCloudPlatform().getXsuaaServiceCredentials()))
                .getOrElseThrow(TokenRequestFailedException::new);

        try {
            final JsonElement url = xsuaaServiceCredentials.get("url");

            if( url == null || !url.isJsonPrimitive() ) {
                throw new TokenRequestFailedException("Failed to get XSUAA service URI. " + ERROR_BIND_XSUAA_SERVICE);
            }

            return new URI(url.getAsString());
        }
        catch( final URISyntaxException e ) {
            throw new TokenRequestFailedException(e);
        }
    }

    private BasicCredentials getClientCredentials( final String serviceName )
        throws TokenRequestFailedException
    {
        final JsonObject serviceCredentials;
        try {
            serviceCredentials = getCloudPlatform().getServiceCredentials(serviceName);
        }
        catch( final CloudPlatformException e ) {
            throw new TokenRequestFailedException(e);
        }

        @Nullable
        final JsonElement clientIdElement = serviceCredentials.get(SERVICE_CLIENT_ID);

        @Nullable
        final JsonElement clientSecretElement = serviceCredentials.get(SERVICE_CLIENT_SECRET);

        if( clientIdElement == null
            || !clientIdElement.isJsonPrimitive()
            || clientSecretElement == null
            || !clientSecretElement.isJsonPrimitive() ) {
            throw new TokenRequestFailedException(
                "Failed to get "
                    + serviceName
                    + " service client identifier and secret. "
                    + "Please make sure to correctly bind your application to a "
                    + serviceName
                    + " service instance.");
        }

        final String clientId = clientIdElement.getAsString();
        final String clientSecret = clientSecretElement.getAsString();

        return new BasicCredentials(clientId, clientSecret);
    }

    private AccessToken requestAccessToken(
        final URI xsuaaUri,
        final BasicCredentials clientCredentials,
        final boolean propagateUser,
        final boolean useProviderTenant )
        throws TokenRequestFailedException,
            TokenRequestDeniedException
    {
        final TokenRequest tokenRequest = new TokenRequest(subdomainReplacer);

        if( propagateUser ) {
            return tokenRequest.requestTokenWithUserTokenGrant(xsuaaUri, clientCredentials, useProviderTenant);
        } else {
            return tokenRequest.requestTokenWithClientCredentialsGrant(xsuaaUri, clientCredentials, useProviderTenant);
        }
    }

    CacheKey getCacheKey( final String serviceName, final boolean propagateUser, final boolean useProviderTenant )
        throws TokenRequestFailedException
    {
        final CacheKey cacheKey;

        try {
            if( propagateUser ) {
                cacheKey = CacheKey.ofTenantAndUserIsolation();
            } else {
                cacheKey = CacheKey.ofTenantIsolation();
            }
        }
        catch( final
            TenantNotAvailableException
                | TenantAccessException
                | UserNotAuthenticatedException
                | UserAccessException e ) {
            throw new TokenRequestFailedException("Failed to determine cache key.", e);
        }

        return cacheKey.append(serviceName, useProviderTenant);
    }

    AccessToken
        getServiceToken( final String serviceName, final boolean propagateUser, final boolean useProviderTenant )
            throws TokenRequestFailedException,
                TokenRequestDeniedException
    {
        final CacheKey cacheKey = getCacheKey(serviceName, propagateUser, useProviderTenant);

        // Note: strict cache synchronization is not required here.
        // In the worst case, the token is fetched multiple times and the freshest one is cached.

        @Nullable
        final AccessToken accessToken = tokenCache.getIfPresent(cacheKey);

        if( accessToken != null && accessToken.isValid() ) {
            if( logger.isDebugEnabled() ) {
                logger.debug(
                    "Returning access token from cache (cache key: "
                        + cacheKey
                        + ", token expiry: "
                        + accessToken.getExpiry()
                        + ").");
            }
            return accessToken;
        }

        if( logger.isDebugEnabled() ) {
            logger.debug("Requesting fresh access token for cache key " + cacheKey + ".");
        }

        final URI xsuaaUri = getXsuaaUri();
        final BasicCredentials clientCredentials = getClientCredentials(serviceName);

        final AccessToken freshToken =
            requestAccessToken(xsuaaUri, clientCredentials, propagateUser, useProviderTenant);

        tokenCache.put(cacheKey, freshToken);
        return freshToken;
    }

    AccessToken getServiceTokenForSubscriber( final URI subscriberXsuaa )
    {
        // TODO use caching
        // TODO use debug messages
        final BasicCredentials credentials = getClientCredentials("xsuaa");

        final TokenRequest tokenRequest = new TokenRequest(subdomainReplacer);
        return tokenRequest.requestTokenWithClientCredentialsGrant(subscriberXsuaa, credentials, false);
    }

    AccessToken getServiceTokenForSubscriber( final String subscriberId )
    {
        final URI providerXsuaaUri = getXsuaaUri();
        final String subscriberXsuaaUri = String.format("https://%s.", subscriberId);
        final URI subscriberXsuaa = subdomainReplacer.replaceSubdomain(subscriberXsuaaUri, providerXsuaaUri);
        return getServiceTokenForSubscriber(subscriberXsuaa);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy