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

com.sap.cloud.sdk.cloudplatform.connectivity.ScpCfDestination 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.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

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

import org.apache.commons.io.FilenameUtils;
import org.apache.http.HttpHeaders;
import org.slf4j.Logger;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.json.JsonSanitizer;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import com.netflix.hystrix.exception.HystrixRuntimeException;
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.exception.DestinationAccessException;
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.AuthToken;
import com.sap.cloud.sdk.cloudplatform.security.AuthTokenAccessor;
import com.sap.cloud.sdk.cloudplatform.security.BasicCredentials;

import lombok.AccessLevel;
import lombok.Setter;

/**
 * Implementation of {@link Destination} on SAP Cloud Platform Cloud Foundry.
 *
 * @see ScpCfGenericDestination
 * @see ScpCfRfcDestination
 */
public class ScpCfDestination extends AbstractDestination
{
    private static final Logger logger = CloudLoggerFactory.getLogger(ScpCfDestination.class);

    private static final String ON_PREMISE_PROXY_HOST = "onpremise_proxy_host";
    private static final String ON_PREMISE_PROXY_PORT = "onpremise_proxy_port";

    private static final String SAP_CONNECTIVITY_SCC_LOCATION_ID_HEADER = "SAP-Connectivity-SCC-Location_ID";

    private static final String BEARER_PREFIX = "Bearer ";

    // @formatter:off
    private static final Map KEY_STORE_TYPE_BY_FILE_EXT = ImmutableMap.of(
        "pfx", "PKCS12",
        "p12", "PKCS12",
        "jks", "JKS",
        "cer", "CERT"
    );
    // @formatter:on

    @Nullable
    private final String cloudConnectorLocationId;

    private final transient AtomicReference destinationCache = new AtomicReference<>(null);
    private final transient AtomicReference> onPremiseProxyHeadersCache = new AtomicReference<>(null);
    private final transient AtomicReference trustStoreCache = new AtomicReference<>(null);
    private final transient AtomicReference keyStoreCache = new AtomicReference<>(null);

    @Nonnull
    private final XsuaaService xsuaaService;

    @Nonnull
    private final DestinationService destinationService;

    @Nonnull
    private final ConnectivityService connectivityService;

    @Setter( AccessLevel.PACKAGE )
    private boolean usingProviderTenant = false;

    /**
     * Creates a destination to be used on SAP Cloud Platform Cloud Foundry.
     * 
     * @param destinationAsJson
     *            The actual JSON object returned by the destination service.
     * @param xsuaaService
     *            The XSUAA service to be used to retrieve further destination information such as headers for
     *            on-premise connections.
     * @param destinationService
     *            The destinations service to be used to retrieve further destination information.
     * @param connectivityService
     *            The connectivity service to be used to retrieve further destination information regarding on-premise
     *            connections.
     * @param name
     *            The name of the destination.
     * @param description
     *            A description of this destination.
     * @param uri
     *            The uri of this destination.
     * @param authenticationType
     *            The {@code AuthenticationType} of this destination.
     * @param basicCredentials
     *            The credentials to be used if {@code authenticationType} is set to {@code BASIC_AUTHENTICATION}.
     * @param proxyType
     *            The type of proxy to be used for this destination.
     * @param proxyConfiguration
     *            The configuration of the proxy to be used (if given).
     * @param isTrustingAllCertificates
     *            Flag indicating whether all certificates should be accepted when communicating with the destination.
     * @param trustStoreLocation
     *            The name of the trust store to search for in the certificates obtained by the destination service.
     * @param trustStorePassword
     *            The password to access the trust store.
     * @param keyStoreLocation
     *            The name of the key store to search for in the certificates obtained by the destination service.
     * @param keyStorePassword
     *            The password to access the key store.
     * @param cloudConnectorLocationId
     *            The id to be used when communicating in {@code ON_PREMISE} {@code proxyType} with an on-premise
     *            system.
     * @param propertiesByName
     *            A map containing all additional properties.
     */
    public ScpCfDestination(
        @Nullable final JsonObject destinationAsJson,
        @Nonnull final XsuaaService xsuaaService,
        @Nonnull final DestinationService destinationService,
        @Nonnull final ConnectivityService connectivityService,
        @Nonnull final String name,
        @Nullable final String description,
        @Nonnull final URI uri,
        @Nonnull final AuthenticationType authenticationType,
        @Nullable final BasicCredentials basicCredentials,
        @Nonnull final ProxyType proxyType,
        @Nullable final ProxyConfiguration proxyConfiguration,
        final boolean isTrustingAllCertificates,
        @Nullable final String trustStoreLocation,
        @Nullable final String trustStorePassword,
        @Nullable final String keyStoreLocation,
        @Nullable final String keyStorePassword,
        @Nullable final String cloudConnectorLocationId,
        @Nonnull final Map propertiesByName )
    {
        this(
            destinationAsJson,
            xsuaaService,
            destinationService,
            connectivityService,
            name,
            description,
            uri.toString(),
            authenticationType,
            basicCredentials,
            proxyType,
            proxyConfiguration,
            isTrustingAllCertificates,
            trustStoreLocation,
            trustStorePassword,
            keyStoreLocation,
            keyStorePassword,
            cloudConnectorLocationId,
            propertiesByName);
    }

    /**
     * Creates a destination to be used on SAP Cloud Platform Cloud Foundry.
     *
     * @param destinationAsJson
     *            The actual JSON object returned by the destination service.
     * @param xsuaaService
     *            The XSUAA service to be used to retrieve further destination information such as headers for
     *            on-premise connections.
     * @param destinationService
     *            The destinations service to be used to retrieve further destination information.
     * @param connectivityService
     *            The connectivity service to be used to retrieve further destination information regarding on-premise
     *            connections.
     * @param name
     *            The name of the destination.
     * @param description
     *            A description of this destination.
     * @param uri
     *            The uri of this destination.
     * @param authenticationType
     *            The {@code AuthenticationType} of this destination.
     * @param basicCredentials
     *            The credentials to be used if {@code authenticationType} is set to {@code BASIC_AUTHENTICATION}.
     * @param proxyType
     *            The type of proxy to be used for this destination.
     * @param proxyConfiguration
     *            The configuration of the proxy to be used (if given).
     * @param isTrustingAllCertificates
     *            Flag indicating whether all certificates should be accepted when communicating with the destination.
     * @param trustStoreLocation
     *            The name of the trust store to search for in the certificates obtained by the destination service.
     * @param trustStorePassword
     *            The password to access the trust store.
     * @param keyStoreLocation
     *            The name of the key store to search for in the certificates obtained by the destination service.
     * @param keyStorePassword
     *            The password to access the key store.
     * @param cloudConnectorLocationId
     *            The id to be used when communicating in {@code ON_PREMISE} {@code proxyType} with an on-premise
     *            system.
     * @param propertiesByName
     *            A map containing all additional properties.
     */
    public ScpCfDestination(
        @Nullable final JsonObject destinationAsJson,
        @Nonnull final XsuaaService xsuaaService,
        @Nonnull final DestinationService destinationService,
        @Nonnull final ConnectivityService connectivityService,
        @Nonnull final String name,
        @Nullable final String description,
        @Nonnull final String uri,
        @Nonnull final AuthenticationType authenticationType,
        @Nullable final BasicCredentials basicCredentials,
        @Nonnull final ProxyType proxyType,
        @Nullable final ProxyConfiguration proxyConfiguration,
        final boolean isTrustingAllCertificates,
        @Nullable final String trustStoreLocation,
        @Nullable final String trustStorePassword,
        @Nullable final String keyStoreLocation,
        @Nullable final String keyStorePassword,
        @Nullable final String cloudConnectorLocationId,
        @Nonnull final Map propertiesByName )
    {
        super(
            name,
            description,
            uri,
            authenticationType,
            basicCredentials,
            proxyType,
            proxyConfiguration,
            isTrustingAllCertificates,
            trustStoreLocation,
            trustStorePassword,
            keyStoreLocation,
            keyStorePassword,
            propertiesByName);

        this.xsuaaService = xsuaaService;
        this.destinationService = destinationService;
        this.connectivityService = connectivityService;

        this.cloudConnectorLocationId = cloudConnectorLocationId;

        if( destinationAsJson != null ) {
            destinationCache.compareAndSet(null, destinationAsJson);
        }
    }

    /**
     * Creates a mocked {@link ScpCfDestination} returning an empty destination name.
     * 

* This no-arguments constructor is required to ensure compatibility with mocking frameworks such as Mockito. */ private ScpCfDestination() { this( null, new XsuaaService(), new DestinationService(), new ConnectivityService(), "", null, "", AuthenticationType.NO_AUTHENTICATION, null, ProxyType.INTERNET, null, false, null, null, null, null, null, Collections.emptyMap()); } @Nonnull @Override public Optional getTrustStore() throws DestinationAccessException { if( trustStoreLocation != null && trustStorePassword != null && trustStoreCache.get() == null ) { trustStoreCache.compareAndSet(null, getKeyStore(trustStoreLocation, trustStorePassword)); } return Optional.ofNullable(trustStoreCache.get()); } @Nonnull @Override public Optional getKeyStore() throws DestinationAccessException { if( keyStoreLocation != null && keyStorePassword != null && keyStoreCache.get() == null ) { keyStoreCache.compareAndSet(null, getKeyStore(keyStoreLocation, keyStorePassword)); } return Optional.ofNullable(keyStoreCache.get()); } /** * Getter for the location identifier used by the SAP Cloud Connector. * * @return The location identifier. */ @Nonnull public Optional getCloudConnectorLocationId() { return Optional.ofNullable(cloudConnectorLocationId); } private JsonObject fetchDestination() throws DestinationAccessException { final String servicePath = "destinations/" + getName(); final boolean propagateUser = authenticationTypeRequiresUserPropagation(); try { final String responsePayload = new DestinationServiceCommand( xsuaaService, propagateUser, destinationService, servicePath, usingProviderTenant).execute(); return new JsonParser().parse(JsonSanitizer.sanitize(responsePayload)).getAsJsonObject(); } catch( final HystrixRuntimeException | HystrixBadRequestException | IllegalStateException | JsonParseException e ) { throw new DestinationAccessException( "Failed to access the configuration of destination '" + getName() + "'.", e); } } private boolean authenticationTypeRequiresUserPropagation() { return (authenticationType == AuthenticationType.OAUTH2_SAML_BEARER_ASSERTION) || (authenticationType == AuthenticationType.OAUTH2_USER_TOKEN_EXCHANGE); } private JsonObject getOrFetchDestination() throws DestinationAccessException { if( destinationCache.get() == null ) { destinationCache.compareAndSet(null, fetchDestination()); } return destinationCache.get(); } private boolean hasCertificateType( @Nullable final JsonElement type ) { return type != null && type.isJsonPrimitive() && type.getAsString().equals("CERTIFICATE"); } private boolean hasCertificateName( @Nonnull final String keyStoreLocation, @Nullable final JsonElement name ) { return name != null && name.isJsonPrimitive() && name.getAsString().equals(keyStoreLocation); } private boolean hasCertificateContent( @Nullable final JsonElement content ) { return content != null && content.isJsonPrimitive(); } private KeyStore getKeyStore( @Nonnull final String keyStoreLocation, @Nullable final String password ) throws DestinationAccessException { @Nullable final JsonElement certificatesElement = getOrFetchDestination().get("certificates"); if( certificatesElement != null && certificatesElement.isJsonArray() ) { for( final JsonElement certificate : certificatesElement.getAsJsonArray() ) { if( certificate.isJsonObject() ) { final JsonObject cert = certificate.getAsJsonObject(); @Nullable final JsonElement type = cert.get("Type"); @Nullable final JsonElement name = cert.get("Name"); @Nullable final JsonElement content = cert.get("Content"); if( hasCertificateType(type) && hasCertificateName(keyStoreLocation, name) && hasCertificateContent(content) ) { final String fileExtension = FilenameUtils.getExtension(name.getAsString().toLowerCase()); @Nullable final String typeOfFileExt = KEY_STORE_TYPE_BY_FILE_EXT.get(fileExtension); final String keyStoreType; if( typeOfFileExt != null ) { keyStoreType = typeOfFileExt; if( logger.isDebugEnabled() ) { logger.debug( "Using key store type '" + keyStoreType + "' based on file extension '" + fileExtension + "'."); } } else { keyStoreType = KeyStore.getDefaultType(); if( logger.isDebugEnabled() ) { logger.debug("Using default key store type '" + keyStoreType + "'."); } } final KeyStore ks; try { ks = KeyStore.getInstance(keyStoreType); } catch( final KeyStoreException e ) { throw new DestinationAccessException("Failed to load key store.", e); } final byte[] bytes = Base64.getDecoder().decode(content.getAsString()); try( final ByteArrayInputStream is = new ByteArrayInputStream(bytes) ) { ks.load(is, password == null ? null : password.toCharArray()); return ks; } catch( final IOException | CertificateException | NoSuchAlgorithmException e ) { throw new DestinationAccessException("Failed to load key store.", e); } } } } } throw new DestinationAccessException("Failed to find key store '" + keyStoreLocation + "'."); } 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 ProxyConfiguration getOnPremiseProxyConfiguration() throws DestinationAccessException { String proxyHost = null; Integer proxyPort = null; final JsonObject connectivityServiceCredentials; try { connectivityServiceCredentials = getCloudPlatform().getConnectivityServiceCredentials(); } catch( final CloudPlatformException e ) { throw new DestinationAccessException(e); } @Nullable final JsonElement onPremiseHost = connectivityServiceCredentials.get(ON_PREMISE_PROXY_HOST); if( onPremiseHost != null && onPremiseHost.isJsonPrimitive() ) { proxyHost = onPremiseHost.getAsString(); } @Nullable final JsonElement onPremisePort = connectivityServiceCredentials.get(ON_PREMISE_PROXY_PORT); if( onPremisePort != null && onPremisePort.isJsonPrimitive() ) { try { proxyPort = Integer.valueOf(onPremisePort.getAsString()); } catch( final NumberFormatException e ) { if( logger.isWarnEnabled() ) { logger.warn("Failed to parse on-premise port.", e); } proxyPort = null; } } if( proxyHost == null || proxyPort == null ) { throw new DestinationAccessException( "Failed to configure on-premise proxy for destination '" + getName() + "'. Please make sure to correctly bind your application to a service instance."); } try { final URI uri = new URI("http://" + proxyHost + ":" + proxyPort); final ProxyConfiguration proxyConfiguration = new ProxyConfiguration(uri); if( logger.isDebugEnabled() ) { logger.debug("Using on-premise proxy configuration: " + proxyConfiguration + "."); } return proxyConfiguration; } catch( final URISyntaxException e ) { throw new DestinationAccessException("Invalid proxy URI.", e); } } @Nonnull @Override public Optional getProxyConfiguration() throws DestinationAccessException { if( getProxyType() == ProxyType.ON_PREMISE ) { return Optional.of(getOnPremiseProxyConfiguration()); } else { return Optional.ofNullable(proxyConfiguration); } } private List

getAppToAppSsoHeaders() throws DestinationAccessException { final List
result = new ArrayList<>(); final Optional currentJwt = AuthTokenAccessor.getCurrentToken(); if( currentJwt.isPresent() ) { final DecodedJWT jwt = currentJwt.get().getJwt(); result.add(new Header(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + jwt.getToken())); } else { logger.error( "Failed to add '" + HttpHeaders.AUTHORIZATION + "' header for app-to-app single sign-on: no JWT bearer found in '" + HttpHeaders.AUTHORIZATION + "' header of request. Continuing without header."); } return result; } private List
getAuthTokenHeaders() throws DestinationAccessException { final List
result = new ArrayList<>(); @Nullable final JsonElement authTokensElement = getOrFetchDestination().get("authTokens"); if( authTokensElement != null && authTokensElement.isJsonArray() ) { final JsonArray authTokens = authTokensElement.getAsJsonArray(); for( final JsonElement authToken : authTokens ) { if( authToken.isJsonObject() ) { final JsonObject token = authToken.getAsJsonObject(); @Nullable final JsonElement error = token.get("error"); if( error != null ) { throw new DestinationAccessException( "Failed to get authentication headers. " + "Destination service returned error" + (error.isJsonPrimitive() ? ": " + error.getAsString() + (error.getAsString().endsWith(".") ? "" : ".") : ".")); } @Nullable final JsonElement type = token.get("type"); @Nullable final JsonElement value = token.get("value"); if( type != null && type.isJsonPrimitive() && value != null && value.isJsonPrimitive() ) { result .add(new Header(HttpHeaders.AUTHORIZATION, type.getAsString() + " " + value.getAsString())); } } } } return result; } private List
getOnPremiseProxyHeaders() throws DestinationAccessException { if( onPremiseProxyHeadersCache.get() == null ) { final List
headers = new ArrayList<>(); if( !Strings.isNullOrEmpty(cloudConnectorLocationId) ) { headers.add(new Header(SAP_CONNECTIVITY_SCC_LOCATION_ID_HEADER, cloudConnectorLocationId)); if( logger.isDebugEnabled() ) { logger.debug( "Successfully added " + SAP_CONNECTIVITY_SCC_LOCATION_ID_HEADER + " header with location identifier '" + cloudConnectorLocationId + "'."); } } try { headers.addAll( new GetOnPremiseProxyHeadersCommand( xsuaaService, connectivityService, usingProviderTenant, authenticationTypeRequiresUserPropagation()).execute()); } catch( final HystrixRuntimeException | HystrixBadRequestException | IllegalStateException e ) { throw new DestinationAccessException( "Failed to get on-premise headers for destination '" + getName() + "'.", e); } onPremiseProxyHeadersCache.compareAndSet(null, headers); } return onPremiseProxyHeadersCache.get(); } @Nonnull @Override public List
getHeaders( @Nullable final URI requestUri ) throws DestinationAccessException { final List
headers = super.getHeaders(requestUri); final AuthenticationType authenticationType = getAuthenticationType(); if( authenticationType == AuthenticationType.APP_TO_APP_SSO ) { headers.addAll(getAppToAppSsoHeaders()); } else if( authenticationType != AuthenticationType.NO_AUTHENTICATION && authenticationType != AuthenticationType.BASIC_AUTHENTICATION ) { headers.addAll(getAuthTokenHeaders()); } if( ProxyType.ON_PREMISE == getProxyType() ) { headers.addAll(getOnPremiseProxyHeaders()); } return headers; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy