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

com.sap.cds.feature.auditlog.v2.CloudSdkCommunicator Maven / Gradle / Ivy

There is a newer version: 3.2.0
Show newest version
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.feature.auditlog.v2;

import java.time.Duration;
import java.util.Map;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;
import com.sap.cds.integration.cloudsdk.destination.HttpClientProvider;
import com.sap.cds.services.environment.CdsProperties.ConnectionPool;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cds.services.utils.environment.ServiceBindingUtils;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultOAuth2PropertySupplier;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2ServiceBindingDestinationLoader;
import com.sap.cloud.sdk.cloudplatform.connectivity.OnBehalfOf;
import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationLoader;
import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceIsolationMode;
import com.sap.cloud.sdk.cloudplatform.security.BasicCredentials;
import com.sap.cds.repackaged.audit.api.exception.AuditLogNotAvailableException;
import com.sap.cds.repackaged.audit.api.exception.AuditLogWriteException;
import com.sap.cds.repackaged.audit.client.impl.Communicator;

/**
 * Implementation for an audit log {@link Communicator} which provides
 * connectivity to the audit log api via the Cloud SDK.
 */
public class CloudSdkCommunicator implements Communicator {

	private static final Logger logger = LoggerFactory.getLogger(CloudSdkCommunicator.class);

	static {
		OAuth2ServiceBindingDestinationLoader.registerPropertySupplier(
				options -> ServiceBindingUtils.matches(options.getServiceBinding(), AuditLogV2Configuration.AUDITLOG),
				DefaultOAuth2PropertySupplier::new);
	}

	// resilience configuration
	private static final int NUMBER_RETRIES = 3;
	private static final Duration TIMEOUT_DURATION = Duration.ofMillis(30000);
	private static final String RESILIENCE_CONFIG_NAME = "auditlog";
	private final ResilienceConfiguration resilienceConfig;

	private final String serviceUrl;
	private final ServiceBinding binding;
	private final boolean oAuth2;
	private final HttpClientProvider clientProvider;

	private String uaaDomain;
	private boolean isX509;
	private String clientId;

	protected CloudSdkCommunicator(ServiceBinding binding, CdsRuntime runtime) {
		AuditLogV2Utils.validateBinding(binding);

		this.oAuth2 = AuditLogV2Utils.isOAuth2BasedServicePlan(binding);
		this.serviceUrl = AuditLogV2Utils.getServiceUrl(binding.getCredentials(), oAuth2);

		if (this.oAuth2) {
			@SuppressWarnings("unchecked")
			Map uaa = (Map) binding.getCredentials().get("uaa");
			this.uaaDomain = (String) uaa.get("uaadomain");
			this.isX509 = "x509".equals(uaa.get("credential-type"));
			this.clientId = (String) uaa.get("clientid");
		}
		this.binding = binding;

		ConnectionPool connectionPoolConfig = runtime.getEnvironment().getCdsProperties().getAuditLog().getConnectionPool();
		if (connectionPoolConfig.getCombinePools().isEnabled()) {
			logger.debug("Initializing Audit Log communicator with a global connection pool.");
		} else {
			logger.debug("Initializing Audit Log communicator with tenant-specific connection pools.");
		}

		// configure resilience patterns
		this.resilienceConfig = ResilienceConfiguration.empty(RESILIENCE_CONFIG_NAME);
		this.resilienceConfig.isolationMode(ResilienceIsolationMode.NO_ISOLATION);
		this.resilienceConfig.timeLimiterConfiguration(
				ResilienceConfiguration.TimeLimiterConfiguration.of().timeoutDuration(TIMEOUT_DURATION));
		this.resilienceConfig.retryConfiguration(ResilienceConfiguration.RetryConfiguration.of(NUMBER_RETRIES));

		// configure http clients
		HttpDestination destination;
		if (this.oAuth2) {
			logger.debug("Creating HttpClient for AuditLog Service with OAuth2 Authentication");
			destination = ServiceBindingDestinationLoader.defaultLoaderChain().getDestination(
					ServiceBindingDestinationOptions.forService(binding).onBehalfOf(OnBehalfOf.TECHNICAL_USER_CURRENT_TENANT).build());
		} else {
			logger.debug("Creating HttpClient for AuditLog Service with Basic Authentication");
			Map credentials = binding.getCredentials();
			BasicCredentials basic = new BasicCredentials((String) credentials.get("user"), (String) credentials.get("password"));
			destination = DefaultHttpDestination.builder(serviceUrl).name("auditlog").basicCredentials(basic).build();
		}
		this.clientProvider = new HttpClientProvider(destination, connectionPoolConfig, runtime);
	}

	@Override
	public String send(String message, String endpoint, String subscriberTokenIssuer)
			throws AuditLogNotAvailableException, AuditLogWriteException, UnsupportedOperationException {
		logger.debug("Sending request to audit log service");

		HttpClient httpClient = determineHttpClient();

		HttpPost request = new HttpPost(endpoint);
		StringEntity params = new StringEntity(message, ContentType.APPLICATION_JSON);
		request.setEntity(params);

		try {
			return ResilienceDecorator.executeCallable(() -> {
				HttpResponse response = httpClient.execute(request);

				int statusCode = response.getStatusLine().getStatusCode();
				if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_CREATED
						|| statusCode == HttpStatus.SC_NO_CONTENT) {
					String resultBody = EntityUtils.toString(response.getEntity());
					if (logger.isDebugEnabled()) {
						logger.debug("Request to Audit Log service sent successfully");
					}
					return resultBody;
				} else {
					//we need to consume the response to properly enable reuse of the connection
					EntityUtils.consume(response.getEntity());
					throw new ErrorStatusException(CdsErrorStatuses.AUDITLOG_UNEXPECTED_HTTP_STATUS, statusCode);
				}
			}, resilienceConfig);
		} catch (Exception e) {
			throw new AuditLogWriteException("Exception while calling to Audit Log service", e);
		}
	}

	@Override
	public String getServiceUrl() {
		return this.serviceUrl;
	}

	@Override
	public String getServicePlan() {
		return binding.getServicePlan().orElse(null);
	}

	@Override
	public String getUaaDomain() {
		return this.uaaDomain;
	}

	@Override
	public boolean isX509CredentialType() throws AuditLogWriteException {
		return isX509;
	}

	@VisibleForTesting
	HttpClient determineHttpClient() {
		return clientProvider.get();
	}

	@Override
	public String getClientId() {
		return this.clientId;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy