com.sap.cloud.sdk.cloudplatform.auditlog.ScpNeoAuditLog Maven / Gradle / Ivy
/*
* Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.cloudplatform.auditlog;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.sap.cloud.auditlog.AuditedDataSubject;
import com.sap.cloud.auditlog.AuditedObject;
import com.sap.cloud.auditlog.ConfigurationChangeAuditMessage;
import com.sap.cloud.auditlog.ReadAccessAuditMessage;
import com.sap.cloud.auditlog.exception.AuditLogWriteException;
import com.sap.cloud.auditlog.extension.AuditLogMessageExtension;
import com.sap.cloud.auditlog.extension.AuditLogMessageExtensionFactory;
import com.sap.cloud.auditlog.extension.ConfigurationChangeAuditMessageExtension;
import com.sap.cloud.auditlog.extension.DataModificationAuditMessageExtension;
import com.sap.cloud.auditlog.extension.ReadAccessAuditMessageExtension;
import com.sap.cloud.auditlog.extension.SecurityEventAuditMessageExtension;
import com.sap.cloud.sdk.cloudplatform.auditlog.exception.AuditLogAccessException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.cloudplatform.servlet.Property;
import com.sap.cloud.sdk.cloudplatform.servlet.RequestContext;
import com.sap.cloud.sdk.cloudplatform.servlet.RequestContextAccessor;
import com.sap.cloud.sdk.cloudplatform.servlet.RequestContextExecutor;
import com.sap.cloud.sdk.cloudplatform.servlet.RequestContextServletFilter;
import com.sap.cloud.sdk.cloudplatform.servlet.exception.RequestContextPropertyException;
/**
* Implementation of audit logging that uses the SCP Neo library. Makes extensive use of custom attributes to fill in
* useful auditing information.
*
* Important: For performance reasons, consider to only use the audit log for logging of events such as:
*
* - security relevant events,
*
- read access to sensitive personal data,
*
- changes to configuration data,
*
- and changes to personal data.
*
*/
public class ScpNeoAuditLog implements AuditLog
{
private static final Logger logger = CloudLoggerFactory.getLogger(ScpNeoAuditLog.class);
static final String ACTION_SECURITY_EVENT_BEGIN = "securityEventBegin";
static final String ACTION_SECURITY_EVENT = "securityEvent";
static final String ACTION_SECURITY_EVENT_FAILED = "securityEventFailed";
static final String ATTRIBUTE_CALLER_CHANNEL = "caller_channel";
static final String ATTRIBUTE_LOG_MESSAGE = "message";
static final String ATTRIBUTE_ERROR_MESSAGE = "errorMessage";
static final String ATTRIBUTE_STACK_TRACE = "errorStackTrace";
static final String SUCCESSFUL_OPERATION = "[successfully modified]";
static final String FAILED_OPERATION = "[modification failed]";
private final AuditLogMessageExtensionFactory auditLogExtensionMessageFactory;
public ScpNeoAuditLog() throws AuditLogAccessException
{
auditLogExtensionMessageFactory = getAuditLogMessageExtensionFactory();
}
public ScpNeoAuditLog( @Nonnull final AuditLogMessageExtensionFactory auditLogExtensionMessageFactory )
{
this.auditLogExtensionMessageFactory = auditLogExtensionMessageFactory;
}
private AuditLogMessageExtensionFactory getAuditLogMessageExtensionFactory()
throws AuditLogAccessException
{
final Optional requestContext = RequestContextAccessor.getCurrentRequestContext();
if( !requestContext.isPresent() ) {
throw new AuditLogAccessException(
"Failed to get "
+ AuditLogMessageExtensionFactory.class.getSimpleName()
+ ": no "
+ RequestContext.class.getSimpleName()
+ " available."
+ " Have you correctly configured a "
+ RequestContextServletFilter.class.getSimpleName()
+ " or have you wrapped your logic in a "
+ RequestContextExecutor.class.getSimpleName()
+ " when executing background tasks that are not triggered by a request?");
}
try {
final Optional> property =
requestContext.get().getProperty(
ScpNeoAuditLogRequestContextListener.PROPERTY_AUDIT_LOG_MESSAGE_FACTORY);
if( !property.isPresent() ) {
throw new AuditLogAccessException(
"Failed to get "
+ AuditLogMessageExtensionFactory.class.getSimpleName()
+ ": "
+ RequestContext.class.getSimpleName()
+ " property \""
+ ScpNeoAuditLogRequestContextListener.PROPERTY_AUDIT_LOG_MESSAGE_FACTORY
+ "\" is not present. "
+ "Please ensure that "
+ ScpNeoAuditLogRequestContextListener.class.getSimpleName()
+ " is available on the class path.");
}
@Nullable
final Exception exception = property.get().getException();
if( exception != null ) {
throw new AuditLogAccessException(
"Failed to get " + AuditLogMessageExtensionFactory.class.getSimpleName() + ".",
exception);
}
return (AuditLogMessageExtensionFactory) property.get().getValue();
}
catch( final RequestContextPropertyException e ) {
throw new AuditLogAccessException(
"Failed to get "
+ AuditLogMessageExtensionFactory.class.getSimpleName()
+ ": failed to get "
+ RequestContext.class.getSimpleName()
+ " property.",
e);
}
}
@Override
public void logSecurityEventBeginning( @Nonnull final AccessRequester initiator, @Nullable final String message )
{
logSecurityEvent(true, initiator, message, null);
}
@Override
public void logSecurityEvent(
@Nonnull final AccessRequester initiator,
@Nullable final String message,
@Nullable final Throwable throwable )
{
logSecurityEvent(false, initiator, message, throwable);
}
private void logSecurityEvent(
final boolean isBeginning,
@Nonnull final AccessRequester initiator,
@Nullable final String message,
@Nullable final Throwable throwable )
{
final SecurityEventAuditMessageExtension auditLogEntry =
auditLogExtensionMessageFactory.createAuditLogMessageExtension(SecurityEventAuditMessageExtension.class);
if( throwable == null ) {
auditLogEntry.setAction(isBeginning ? ACTION_SECURITY_EVENT_BEGIN : ACTION_SECURITY_EVENT);
} else {
auditLogEntry.setAction(ACTION_SECURITY_EVENT_FAILED);
}
if( !Strings.isNullOrEmpty(message) ) {
auditLogEntry.setMessage(message);
}
fillCommonAttributesAndLog(auditLogEntry, initiator, throwable);
}
@Override
public void logConfigChangeBeginning(
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nullable final Iterable attributesAffected )
{
logConfigChange(true, initiator, object, attributesAffected, null);
}
@Override
public void logConfigChange(
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nullable final Iterable attributesAffected,
@Nullable final Throwable error )
{
logConfigChange(false, initiator, object, attributesAffected, error);
}
private void logConfigChange(
final boolean isBeginning,
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nullable final Iterable attributesAffected,
@Nullable final Throwable error )
{
final ConfigurationChangeAuditMessageExtension auditLogEntry =
auditLogExtensionMessageFactory
.createAuditLogMessageExtension(ConfigurationChangeAuditMessageExtension.class);
if( error == null ) {
auditLogEntry.setAction(
isBeginning
? ConfigurationChangeAuditMessage.ACTION_ABOUT_TO_UPDATE
: ConfigurationChangeAuditMessage.ACTION_UPDATE);
} else {
auditLogEntry.setAction(ConfigurationChangeAuditMessage.ACTION_UPDATE_FAILED);
}
auditLogEntry.setObject(convertAuditedObject(object));
if( attributesAffected != null ) {
for( final AccessedAttribute attribute : attributesAffected ) {
auditLogEntry.addChangedValues(
attribute.getIdentifier(),
String.valueOf(attribute.getOldValue()),
String.valueOf(attribute.getNewValue()));
}
}
fillCommonAttributesAndLog(auditLogEntry, initiator, error);
}
@Override
public void logDataReadAttempt(
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nonnull final com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject subject,
@Nullable final Iterable attributesAffected )
{
logDataRead(true, initiator, object, subject, attributesAffected, null);
}
@Override
public void logDataRead(
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nonnull final com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject subject,
@Nullable final Iterable attributesAffected,
@Nullable final Throwable error )
{
logDataRead(false, initiator, object, subject, attributesAffected, error);
}
private void logDataRead(
final boolean isAttempting,
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nonnull final com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject subject,
@Nullable final Iterable attributesAffected,
@Nullable final Throwable error )
{
final ReadAccessAuditMessageExtension auditLogEntry =
auditLogExtensionMessageFactory.createAuditLogMessageExtension(ReadAccessAuditMessageExtension.class);
if( error == null ) {
auditLogEntry.setAction(
isAttempting ? ReadAccessAuditMessage.ACTION_ABOUT_TO_READ : ReadAccessAuditMessage.ACTION_READ);
} else {
auditLogEntry.setAction(ReadAccessAuditMessage.ACTION_READ_ATTEMPT);
}
auditLogEntry.setObject(convertAuditedObject(object));
auditLogEntry.setDataSubject(convertAuditedSubject(subject));
if( attributesAffected != null ) {
for( final AccessedAttribute attribute : attributesAffected ) {
auditLogEntry.addObjectAttribute(attribute.getIdentifier(), String.valueOf(attribute.getOldValue()));
}
}
fillCommonAttributesAndLog(auditLogEntry, initiator, error);
}
@Override
public void logDataWriteAttempt(
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nonnull final com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject subject,
@Nullable final Iterable attributesAffected )
{
logDataWrite(true, initiator, object, subject, attributesAffected, null);
}
@Override
public void logDataWrite(
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nonnull final com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject subject,
@Nullable final Iterable attributesAffected,
@Nullable final Throwable error )
{
logDataWrite(false, initiator, object, subject, attributesAffected, error);
}
private void logDataWrite(
final boolean isAttempting,
@Nonnull final AccessRequester initiator,
@Nonnull final AuditedDataObject object,
@Nonnull final com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject subject,
@Nullable final Iterable attributesAffected,
@Nullable final Throwable error )
{
final DataModificationAuditMessageExtension auditLogEntry =
auditLogExtensionMessageFactory.createAuditLogMessageExtension(DataModificationAuditMessageExtension.class);
if( error == null ) {
auditLogEntry.setAction(
isAttempting
? DataModificationAuditMessageExtension.ACTION_ABOUT_TO_UPDATE
: DataModificationAuditMessageExtension.ACTION_UPDATE);
} else {
auditLogEntry.setAction(DataModificationAuditMessageExtension.ACTION_UPDATE_FAILED);
}
auditLogEntry.setObject(convertAuditedObject(object));
auditLogEntry.setDataSubject(convertAuditedSubject(subject));
if( attributesAffected != null ) {
for( final AccessedAttribute attribute : attributesAffected ) {
// don't log old/new values for security/privacy reasons, so just log the operation performed & result.
auditLogEntry.addModifiedValues(
attribute.getIdentifier(),
String.valueOf(attribute.getOperation()),
attribute.isOperationSuccessful() ? SUCCESSFUL_OPERATION : FAILED_OPERATION);
}
}
fillCommonAttributesAndLog(auditLogEntry, initiator, error);
}
@SuppressWarnings( "deprecation" )
private void fillCommonAttributesAndLog(
@Nonnull final AuditLogMessageExtension auditLogEntry,
@Nonnull final AccessRequester initiator,
@Nullable final Throwable throwable )
{
auditLogEntry.setCaller(
initiator.getIpAddress().orElse(null),
null,
initiator.getUserId().orElse(null),
null,
null,
null,
initiator.getTenantId().orElse(null));
auditLogEntry.addCustomAttribute(ATTRIBUTE_CALLER_CHANNEL, initiator.getChannel().orElse(null));
if( throwable != null ) {
auditLogEntry.addCustomAttribute(ATTRIBUTE_ERROR_MESSAGE, throwable.getMessage());
auditLogEntry.addCustomAttribute(ATTRIBUTE_STACK_TRACE, Throwables.getStackTraceAsString(throwable));
}
try {
auditLogEntry.log(getClass());
}
catch( final AuditLogWriteException e ) {
logger.error("Unable to write audit log entry. Entry contents: [" + auditLogEntry + "]", e);
}
}
private AuditedObject convertAuditedObject( @Nonnull AuditedDataObject sdkObject )
{
AuditedObject converted = auditLogExtensionMessageFactory.createAuditedObject();
converted.setType(sdkObject.getType());
for( Map.Entry identifier : sdkObject.getAllIdentifiers().entrySet() ) {
converted.addIdentifier(identifier.getKey(), identifier.getValue());
}
return converted;
}
private AuditedDataSubject convertAuditedSubject(
@Nonnull com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject sdkSubject )
{
AuditedDataSubject converted = auditLogExtensionMessageFactory.createAuditedDataSubject();
converted.setType(sdkSubject.getType());
converted.setRole(sdkSubject.getRole());
for( Map.Entry identifier : sdkSubject.getAllIdentifiers().entrySet() ) {
converted.addIdentifier(identifier.getKey(), identifier.getValue());
}
return converted;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy