Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cloud.sdk.cloudplatform.connectivity.ScpCfService Maven / Gradle / Ivy
/*
* Copyright (c) 2018 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.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.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;
/**
* A facilitation of access to the SCP 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 ";
static CacheBuilder tokenCacheBuilder =
CacheBuilder.newBuilder().concurrencyLevel(10).maximumSize(100000).expireAfterAccess(60, TimeUnit.MINUTES);
static 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;
/**
* 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 = serviceInfo;
this.authUri = authUri;
this.serviceLocationInfo = serviceLocationInfo;
this.clientCredentials = clientCredentials;
this.useProviderTenant = useProviderTenant;
}
/**
* 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
*/
@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
{
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
*/
@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
{
try {
final List serviceInstances = ScpCfServiceInfo.createFor(serviceType, servicePlan);
if( serviceInstances == null || serviceInstances.size() < 1 ) {
throw new NoServiceBindingException(
String.format(
"No service of type \"%s\"%s bound.",
serviceType,
createForServicePlanString(servicePlan)));
}
final ScpCfServiceInfo serviceInfo = serviceInstances.get(0);
if( serviceInstances.size() > 1 ) {
logger.warn(
"Found more than one instance of service type {}{}, using the first: {}",
serviceType,
createForServicePlanString(servicePlan),
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 String authUrlPrefix =
serviceInfo.getServiceInfoPathNonEmptyStringOrThrow(authUrlJsonPath, "Auth url");
final URI authUrl;
if( !authUrlPrefix.endsWith("token") ) {
authUrl = new URI(authUrlPrefix + "/oauth/token").normalize(); // ensure it ends with oAuth token endpoint
} else {
authUrl = new URI(authUrlPrefix);
}
return new ScpCfService(serviceInfo, authUrl, clientCredentials, serviceLocationInfo, useProviderTenant);
}
catch( final CloudPlatformException | URISyntaxException e ) {
throw new ScpCfServiceCreationFailedException(
String.format(
"Failed to setup access to service for type %s%s: %s",
serviceType,
createForServicePlanString(servicePlan),
e.getMessage()),
e);
}
}
@Nonnull
private static String createForServicePlanString( @Nullable final String servicePlan )
{
return servicePlan == null ? "" : String.format(" for service plan \"%s\"", servicePlan);
}
/**
* 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 TokenRequestFailedException
* if a technical problem made the request fail
* @throws TokenRequestDeniedException
* if the authentication or authorization was not successful
*/
@Nonnull
public HttpRequest addBearerTokenHeader( @Nonnull final HttpRequest request )
throws TokenRequestFailedException,
TokenRequestDeniedException
{
request.setHeader(
ScpCfService.AUTHORIZATION_HEADER,
ScpCfService.BEARER_PREFIX + getServiceToken(false).getValue());
return request;
}
private
AccessToken
requestAccessToken( final URI xsuaaUri, final BasicCredentials clientCredentials, final boolean propagateUser )
throws TokenRequestFailedException,
TokenRequestDeniedException
{
final TokenRequest tokenRequest = new TokenRequest();
if( propagateUser ) {
return tokenRequest.requestTokenWithUserTokenGrant(xsuaaUri, clientCredentials, useProviderTenant);
} else {
return tokenRequest.requestTokenWithClientCredentialsGrant(xsuaaUri, clientCredentials, useProviderTenant);
}
}
@Nonnull
CacheKey getCacheKey(
@Nonnull final String serviceName,
@Nullable final String servicePlan,
final boolean propagateUser )
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);
}
cacheKey.append(serviceName);
if( servicePlan != null ) {
cacheKey.append(servicePlan);
}
return cacheKey;
}
AccessToken getServiceToken( final boolean propagateUser )
throws TokenRequestFailedException,
TokenRequestDeniedException
{
final CacheKey cacheKey =
getCacheKey(getServiceInfo().getServiceType(), getServiceInfo().getServicePlan(), false);
// 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, propagateUser);
tokenCache.put(cacheKey, freshToken);
return freshToken;
}
/**
* {@inheritDoc}
*/
@Override
@Nonnull
public String toString()
{
return "ScpCfService{"
+ "serviceInfo='"
+ getServiceInfo()
+ '\''
+ ", serviceLocationInfo="
+ serviceLocationInfo
+ ", authUri="
+ authUri
+ '}';
}
}