com.sap.cloud.security.ams.logging.PolicyEvaluationV2AuditLogger Maven / Gradle / Ivy
/************************************************************************
* © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cloud.security.ams.logging;
import com.sap.cloud.security.ams.api.Principal;
import com.sap.cloud.security.ams.dcl.client.pdp.PolicyEvaluationResult;
import com.sap.cloud.security.ams.dcl.client.pdp.PolicyEvaluationResultSerializer;
import com.sap.xs.audit.api.exception.AuditLogNotAvailableException;
import com.sap.xs.audit.api.exception.AuditLogWriteException;
import com.sap.xs.audit.api.v2.AuditLogMessageFactory;
import com.sap.xs.audit.api.v2.AuditedDataSubject;
import com.sap.xs.audit.api.v2.AuditedObject;
import com.sap.xs.audit.api.v2.DataAccessAuditMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static com.sap.cloud.security.ams.dcl.client.pdp.PolicyEvaluationKind.TECHNICAL;
import static com.sap.cloud.security.ams.dcl.client.pdp.PolicyEvaluationResultSerializer.*;
/**
* In case the business application does not have the option to write the
* decision logs to AMS server, it requires some other reliable logs to analyze
* WHETHER and WHY a user was able to access / manipulate
* (sensitive/configuration) data. This policy evaluation log should be written
* in a separate transaction to make sure that the log is written, even in case
* the operation wasn't successful and rolled back.
*
* Furthermore, the audit log shall be written independent whether the policy
* evaluation was triggered via request or via event.
*/
public class PolicyEvaluationV2AuditLogger implements Consumer {
protected static final Logger LOGGER = LoggerFactory.getLogger(PolicyEvaluationV2AuditLogger.class);
private final AuditedDataSubject auditedDataSubject;
protected AuditLogMessageFactory auditLogFactory;
protected static final String TYPE_VALUE = "AmsPolicyEvaluation";
public static final String MDC_SAP_PASSPORT = "sap-passport";
protected static final String CHANNEL_VALUE = "web";
protected static final String CONDITIONAL_ACCESS = "conditionalAccess";
/**
* Constructs an object.
*
* @param logFactory
* the {@link AuditLogMessageFactory} implementation
* @param dataSubject
* the audited data subject, which is the owner of the modified
* personal data, audit logged with this event. A data subject (id
* and type) is mandatory for writing an audit log entry. In this
* case this might be the application's ams service instance guid.
* Example:
* {@code "data_subject": {"type": "ams-service","id": {"guid":
* "28c17dfe-dc96-47fe-83ad-54f52d4bbd57"}}}
*/
public PolicyEvaluationV2AuditLogger(AuditLogMessageFactory logFactory, AuditedDataSubject dataSubject) {
if (logFactory == null || dataSubject == null) {
throw new IllegalArgumentException("requires logFactory and dataSubject");
}
this.auditLogFactory = logFactory;
this.auditedDataSubject = dataSubject;
}
/**
* Is always called when the
* {@link com.sap.cloud.security.ams.dcl.client.pdp.PolicyDecisionPoint} returns
* with a result.
*
* @param result
* the result of the policy evaluation containing the input
*/
@Override
public void accept(PolicyEvaluationResult result) {
if (result.getKind().equals(TECHNICAL)) { // result and kind can never be null
return;
}
Principal principal = Principal.create();
DataAccessAuditMessage message = auditLogFactory.createDataAccessAuditMessage();
message.setChannel(getDataAccessChannel());
message.setTenant(principal.getAppTid());
message.setUser(principal.getId());
AuditedObject auditedObject = auditLogFactory.createAuditedObject();
auditedObject.setType(TYPE_VALUE);
auditedObject.addIdentifier(MDC_SAP_PASSPORT, MDC.get(MDC_SAP_PASSPORT));
BiConsumer enhanceMessage = auditedObject::addIdentifier;
switch (result.getResultType()) {
case DENY:
case ERROR:
message.addAttribute(ACCESS, false);
break;
case GRANT:
message.addAttribute(ACCESS, true);
break;
case EXPRESSION:
message.addAttribute(CONDITIONAL_ACCESS, true);
break;
default:
LOGGER.error("This resultType {} is not yet supported. Audit Log can't be written without attributes.",
result.getResultType());
return;
}
PolicyEvaluationResultSerializer.getInstance().process(result, enhanceMessage);
message.setObject(auditedObject);
message.setDataSubject(auditedDataSubject);
log(message);
}
/**
* Provides the type of the data access channel such as RFC, web service, IDOC,
* file based interfaces, user interface, spool, printing etc.
*
* @return by default "web"
*/
protected String getDataAccessChannel() {
return CHANNEL_VALUE;
}
/**
* Handles the provided message.
*
* @param message
* the message to be logged
*/
protected void log(DataAccessAuditMessage message) {
try {
message.log();
} catch (AuditLogNotAvailableException e) {
LOGGER.warn("Unable to audit log Policy Evaluation: {}", e.getMessage());
} catch (AuditLogWriteException e) {
LOGGER.error("Error during audit logging Policy Evaluation: {}", e.getErrors());
}
}
}