com.sap.cds.feature.auditlog.v2.CloudSdkCommunicator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-feature-auditlog-v2 Show documentation
Show all versions of cds-feature-auditlog-v2 Show documentation
Handler to send auditlog messages to AuditLog Service V2
/**************************************************************************
* (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;
}
}