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

com.sap.cloud.sdk.cloudplatform.connectivity.ScpCfService 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.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.slf4j.Logger;

import com.google.common.annotations.Beta;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.sap.cloud.sdk.cloudplatform.ScpCfServiceDesignator;
import com.sap.cloud.sdk.cloudplatform.ScpCfServiceInfo;
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.NoServiceBindingException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
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.tenant.exception.TenantAccessException;
import com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantNotAvailableException;

/**
 * A facilitation of access to the SAP CP services with OAuth auth listed in VCAP_SERVICES.
 *
 * 

* Be aware that this class is in Beta (as indicated by the annotation) and therefore subject to breaking changes. *

*/ @Beta public class ScpCfService { private static final Logger logger = CloudLoggerFactory.getSanitizedLogger(ScpCfService.class); private static final String AUTHORIZATION_HEADER = HttpHeaders.AUTHORIZATION; private static final String BEARER_PREFIX = "Bearer "; private static final CacheBuilder tokenCacheBuilder = CacheBuilder.newBuilder().concurrencyLevel(10).maximumSize(100000).expireAfterAccess(60, TimeUnit.MINUTES); private static final Cache tokenCache = CacheManager.register(tokenCacheBuilder.build()); private final ScpCfServiceInfo serviceInfo; private final URI authUri; private final String serviceLocationInfo; private final BasicCredentials clientCredentials; private final boolean useProviderTenant; private final SubdomainReplacer subdomainReplacer; /** * Constructor open for extension. * * @param serviceInfo * json from the VCAP_SERVICES * @param authUri * OAuth authentication service * @param clientCredentials * credentials for authentications to OAuth service * @param serviceLocationInfo * url or host of the service represented by this object */ public ScpCfService( @Nonnull final ScpCfServiceInfo serviceInfo, @Nonnull final URI authUri, @Nonnull final BasicCredentials clientCredentials, @Nonnull final String serviceLocationInfo ) { this(serviceInfo, authUri, clientCredentials, serviceLocationInfo, false); } /** * Constructor open for extension. * * @param serviceInfo * json from the VCAP_SERVICES * @param authUri * OAuth authentication service * @param clientCredentials * credentials for authentications to OAuth service * @param serviceLocationInfo * url or host of the service represented by this object * @param useProviderTenant * for applications that other tenants can subscribe to, this flag controls whether to always use the * service with the credentials of the provider tenant. */ public ScpCfService( @Nonnull final ScpCfServiceInfo serviceInfo, @Nonnull final URI authUri, @Nonnull final BasicCredentials clientCredentials, @Nonnull final String serviceLocationInfo, final boolean useProviderTenant ) { this( serviceInfo, authUri, clientCredentials, serviceLocationInfo, useProviderTenant, new DefaultSubdomainReplacer()); } /** * Constructor open for extension. * * @param serviceInfo * json from the VCAP_SERVICES * @param authUri * OAuth authentication service * @param clientCredentials * credentials for authentications to OAuth service * @param serviceLocationInfo * url or host of the service represented by this object * @param useProviderTenant * for applications that other tenants can subscribe to, this flag controls whether to always use the * service with the credentials of the provider tenant. * @param subdomainReplacer * if you want to override the subdomain-replacing behavior (useful for testing) */ public ScpCfService( @Nonnull final ScpCfServiceInfo serviceInfo, @Nonnull final URI authUri, @Nonnull final BasicCredentials clientCredentials, @Nonnull final String serviceLocationInfo, final boolean useProviderTenant, final SubdomainReplacer subdomainReplacer ) { this.serviceInfo = serviceInfo; this.authUri = authUri; this.serviceLocationInfo = serviceLocationInfo; this.clientCredentials = clientCredentials; this.useProviderTenant = useProviderTenant; this.subdomainReplacer = subdomainReplacer; } /** * Factory method reading information about service from VCAP_SERVICES. Takes paths in the form of * 'credentials/clientId' to make information extraction generic. * * Doesn't validate the connection to the auth server or service itself yet. * * @param designator * service designator indicatig the service information to use * @param authUrlJsonPath * path to the OAuth server URL. * @param clientIdJsonPath * path to client id * @param clientSecretJsonPath * path to client secret * @param serviceLocationJsonPath * path to the service's url or host information or similar * * @return The created service. * * @throws CloudPlatformException * On problems like the service type not being present in VCAP_SERVICES. * @throws ScpCfServiceCreationFailedException * If the service creation is otherwise incomplete or incorrect. */ @Nonnull public static ScpCfService of( @Nonnull final ScpCfServiceDesignator designator, @Nonnull final String authUrlJsonPath, @Nonnull final String clientIdJsonPath, @Nonnull final String clientSecretJsonPath, @Nonnull final String serviceLocationJsonPath ) throws CloudPlatformException, ScpCfServiceCreationFailedException { return ofMultiTenant( designator, authUrlJsonPath, clientIdJsonPath, clientSecretJsonPath, serviceLocationJsonPath, false, new DefaultSubdomainReplacer()); } /** * Factory method for testing. */ static ScpCfService of( @Nonnull final String serviceType, @Nullable final String servicePlan, @Nonnull final String authUrlJsonPath, @Nonnull final String clientIdJsonPath, @Nonnull final String clientSecretJsonPath, @Nonnull final String serviceLocationJsonPath, @Nonnull final SubdomainReplacer subdomainReplacer ) throws CloudPlatformException { return ofMultiTenant( ScpCfServiceDesignator.builder().serviceType(serviceType).servicePlan(servicePlan).build(), authUrlJsonPath, clientIdJsonPath, clientSecretJsonPath, serviceLocationJsonPath, false, subdomainReplacer); } /** * Factory method reading information about service from VCAP_SERVICES. Takes paths in the form of * 'credentials/clientId' to make information extraction generic. * * Doesn't validate the connection to the auth server or service itself yet. * * @param serviceType * classification of services * @param servicePlan * optional service plan identifier to distinguish different instances of the same service type. * @param authUrlJsonPath * path to the OAuth server URL. * @param clientIdJsonPath * path to client id * @param clientSecretJsonPath * path to client secret * @param serviceLocationJsonPath * path to the service's url or host information or similar * * @return The created service. * * @throws CloudPlatformException * On problems like the service type not being present in VCAP_SERVICES. * @throws ScpCfServiceCreationFailedException * If the service creation is otherwise incomplete or incorrect. */ @Nonnull public static ScpCfService of( @Nonnull final String serviceType, @Nullable final String servicePlan, @Nonnull final String authUrlJsonPath, @Nonnull final String clientIdJsonPath, @Nonnull final String clientSecretJsonPath, @Nonnull final String serviceLocationJsonPath ) throws CloudPlatformException, ScpCfServiceCreationFailedException { return ofMultiTenant( serviceType, servicePlan, authUrlJsonPath, clientIdJsonPath, clientSecretJsonPath, serviceLocationJsonPath, false); } /** * Factory method reading information about service from VCAP_SERVICES. Takes paths in the form of * 'credentials/clientId' to make information extraction generic. * * Doesn't validate the connection to the auth server or service itself yet. * * This one takes into account multi-tenant subscription environments and allows for always using the provider * tenant when accessing the service. * * @param serviceType * classification of services * @param servicePlan * optional service plan identifier to distinguish different instances of the same service type. * @param authUrlJsonPath * path to the OAuth server URL. * @param clientIdJsonPath * path to client id * @param clientSecretJsonPath * path to client secret * @param serviceLocationJsonPath * path to the service's url or host information or similar * @param useProviderTenant * for applications that other tenants can subscribe to, this flag controls whether to always use the * service with the credentials ofMultiTenant the provider tenant. * @return the created service. * @throws CloudPlatformException * On problems like the service type not being present in VCAP_SERVICES. * @throws ScpCfServiceCreationFailedException * If the service creation is otherwise incomplete or incorrect. */ @Nonnull public static ScpCfService ofMultiTenant( @Nonnull final String serviceType, @Nullable final String servicePlan, @Nonnull final String authUrlJsonPath, @Nonnull final String clientIdJsonPath, @Nonnull final String clientSecretJsonPath, @Nonnull final String serviceLocationJsonPath, final boolean useProviderTenant ) throws CloudPlatformException, ScpCfServiceCreationFailedException { return ofMultiTenant( ScpCfServiceDesignator.builder().serviceType(serviceType).servicePlan(servicePlan).build(), authUrlJsonPath, clientIdJsonPath, clientSecretJsonPath, serviceLocationJsonPath, useProviderTenant, new DefaultSubdomainReplacer()); } /** * Factory method reading information about service from VCAP_SERVICES. Takes paths in the form of * 'credentials/clientId' to make information extraction generic. * * Doesn't validate the connection to the auth server or service itself yet. * * This one takes into account multi-tenant subscription environments and allows for always using the provider * tenant when accessing the service. * * @param designator * service designator indicatig the service information to use * @param authUrlJsonPath * path to the OAuth server URL. * @param clientIdJsonPath * path to client id * @param clientSecretJsonPath * path to client secret * @param serviceLocationJsonPath * path to the service's url or host information or similar * @param useProviderTenant * for applications that other tenants can subscribe to, this flag controls whether to always use the * service with the credentials ofMultiTenant the provider tenant. * @return the created service. * @throws CloudPlatformException * On problems like the service type not being present in VCAP_SERVICES and if the service information * is otherwise incomplete or incorrect. */ @Nonnull public static ScpCfService ofMultiTenant( @Nonnull final ScpCfServiceDesignator designator, @Nonnull final String authUrlJsonPath, @Nonnull final String clientIdJsonPath, @Nonnull final String clientSecretJsonPath, @Nonnull final String serviceLocationJsonPath, final boolean useProviderTenant, @Nonnull final SubdomainReplacer subdomainReplacer ) throws CloudPlatformException { try { final List serviceInstances = ScpCfServiceInfo.createFor(designator); if( serviceInstances.isEmpty() ) { throw new NoServiceBindingException(String.format("No service designated by %s bound.", designator)); } final ScpCfServiceInfo serviceInfo = serviceInstances.get(0); if( serviceInstances.size() > 1 ) { logger.warn( "Found more than one instance of {}, using the first: {}", createForServiceDesignatorString(designator), serviceInfo.getServiceName()); } else { logger.debug("Setting up service {}", serviceInfo.getServiceName()); } final String serviceLocationInfo = serviceInfo.getServiceInfoPathNonEmptyStringOrThrow(serviceLocationJsonPath, "Service location info"); final BasicCredentials clientCredentials = new BasicCredentials( serviceInfo.getServiceInfoPathNonEmptyStringOrThrow(clientIdJsonPath, "Client id"), serviceInfo.getServiceInfoPathNonEmptyStringOrThrow(clientSecretJsonPath, "Client secret")); final URI authUrl = new URI(serviceInfo.getServiceInfoPathNonEmptyStringOrThrow(authUrlJsonPath, "Auth url")); return new ScpCfService( serviceInfo, authUrl, clientCredentials, serviceLocationInfo, useProviderTenant, subdomainReplacer); } catch( final URISyntaxException e ) { throw new ScpCfServiceCreationFailedException( String.format( "Failed to setup access to %s: %s", createForServiceDesignatorString(designator), e.getMessage()), e); } } @Nonnull private static String createForServiceDesignatorString( @Nonnull final ScpCfServiceDesignator designator ) { return String.format("service designated by %s", designator); } /** * Provides access to the rest of the service info. * * @return an nice wrapper for the json. */ @Nonnull public ScpCfServiceInfo getServiceInfo() { return serviceInfo; } /** * Service access. * * @return the URL or host name or similar - if present */ @Nullable public String getServiceLocationInfo() { return serviceLocationInfo; } /** * The oauth server url from the service config. * * @return the url */ @Nonnull public URI getAuthUri() { return authUri; } /** * * @return the client credentials from the service info */ @Nonnull public BasicCredentials getClientCredentials() { return clientCredentials; } /** * Configures a provided request to the service to bear required authentication token. Requests authentication from * the oauth server or uses a cached token. * * @param request * to add authentication token to. * @return the request for chaining. * @throws ScpCfServiceAuthorizationFailedException * if the request was forbidden or failed */ @Nonnull public HttpRequest addBearerTokenHeader( @Nonnull final HttpRequest request ) throws ScpCfServiceAuthorizationFailedException { try { request.setHeader( ScpCfService.AUTHORIZATION_HEADER, ScpCfService.BEARER_PREFIX + getServiceToken().getValue()); return request; } catch( final TokenRequestFailedException e ) { throw new ScpCfServiceAuthorizationFailedException( "Authorization token request failed: " + e.getMessage(), e); } catch( final TokenRequestDeniedException e ) { throw new ScpCfServiceAuthorizationFailedException( "Authorization token request was denied: " + e.getMessage(), e); } } private AccessToken requestAccessToken( final URI xsuaaUri, final BasicCredentials clientCredentials ) throws TokenRequestFailedException, TokenRequestDeniedException { final TokenRequest tokenRequest = new TokenRequest(subdomainReplacer); return tokenRequest.requestTokenWithClientCredentialsGrant(xsuaaUri, clientCredentials, useProviderTenant); } @Nonnull private CacheKey getCacheKey( @Nonnull final String serviceName, @Nullable final String servicePlan ) throws TokenRequestFailedException { final CacheKey cacheKey; try { cacheKey = CacheKey.ofTenantIsolation(); } catch( final TenantNotAvailableException | TenantAccessException e ) { throw new TokenRequestFailedException("Failed to determine cache key.", e); } cacheKey.append(serviceName); if( servicePlan != null ) { cacheKey.append(servicePlan); } return cacheKey; } private AccessToken getServiceToken() throws TokenRequestFailedException, TokenRequestDeniedException { final CacheKey cacheKey = getCacheKey(getServiceInfo().getServiceType(), getServiceInfo().getServicePlan()); // 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() ) { logger.debug( "Returning access token from cache (cache key: {}, token expiry: {}).", cacheKey, accessToken.getExpiry()); return accessToken; } logger.debug("Requesting fresh access token for cache key {}.", cacheKey); final AccessToken freshToken = requestAccessToken(authUri, clientCredentials); tokenCache.put(cacheKey, freshToken); return freshToken; } /** * {@inheritDoc} */ @Override @Nonnull public String toString() { return "ScpCfService{" + "serviceInfo='" + getServiceInfo() + '\'' + ", serviceLocationInfo=" + serviceLocationInfo + ", authUri=" + authUri + '}'; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy