/*
* Copyright (c) 2010-2016 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.audit.api;
import java.beans.Transient;
import java.text.SimpleDateFormat;
import java.util.*;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.schema.DeltaConversionOptions;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordPropertyType;
import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectDeltaOperationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.ItemDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.RawType;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.NotNull;
/**
* Audit event record describes a single event (usually data change) in a format suitable for audit.
*
* @author Radovan Semancik
*
*/
public class AuditEventRecord implements DebugDumpable {
private Long repoId;
/**
* Timestamp when the event occured.
* Timestamp in millis.
*/
private Long timestamp;
/**
* Unique identification of the event.
*/
private String eventIdentifier;
/**
* Identitification of (interactive) session in which the event occured.
*/
private String sessionIdentifier;
// channel???? (e.g. web gui, web service, ...)
// task ID (not OID!)
private String taskIdentifier;
private String taskOID;
// host ID
private String hostIdentifier;
// initiator (subject, event "owner"): store OID, type(implicit?), name
private PrismObject<UserType> initiator;
/**
* (primary) target (object, the thing acted on): store OID, type, name.
* This is reference instead of full object, because sometimes we have just
* the OID of the object and reading full object would be too expensive.
* The reference can store OID but it can also store whole object if
* that is available.
* OPTIONAL
*/
private PrismReferenceValue target;
// user that the target "belongs to"????: store OID, name
private PrismObject<UserType> targetOwner;
// event type
private AuditEventType eventType;
// event stage (request, execution)
private AuditEventStage eventStage;
// delta
private final Collection<ObjectDeltaOperation<? extends ObjectType>> deltas = new ArrayList<>();
// delta order (primary, secondary)
private String channel;
// outcome (success, failure)
private OperationResultStatus outcome;
// result (e.g. number of entries, returned object, business result of workflow task or process instance - approved, rejected)
private String result;
private String parameter;
private String message;
private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private final Map<String, Set<String>> properties = new HashMap<>();
private final Map<String, Set<AuditReferenceValue>> references = new HashMap<>();
public AuditEventRecord() {
}
public AuditEventRecord(AuditEventType eventType) {
this.eventType = eventType;
}
public AuditEventRecord(AuditEventType eventType, AuditEventStage eventStage) {
this.eventType = eventType;
this.eventStage = eventStage;
}
public Long getTimestamp() {
return timestamp;
}
public void clearTimestamp() {
timestamp = null;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public String getEventIdentifier() {
return eventIdentifier;
}
public void setEventIdentifier(String eventIdentifier) {
this.eventIdentifier = eventIdentifier;
}
public String getSessionIdentifier() {
return sessionIdentifier;
}
public void setSessionIdentifier(String sessionIdentifier) {
this.sessionIdentifier = sessionIdentifier;
}
public String getTaskIdentifier() {
return taskIdentifier;
}
public void setTaskIdentifier(String taskIdentifier) {
this.taskIdentifier = taskIdentifier;
}
public String getTaskOID() {
return taskOID;
}
public void setTaskOID(String taskOID) {
this.taskOID = taskOID;
}
public String getHostIdentifier() {
return hostIdentifier;
}
public void setHostIdentifier(String hostIdentifier) {
this.hostIdentifier = hostIdentifier;
}
public PrismObject<UserType> getInitiator() {
return initiator;
}
public void setInitiator(PrismObject<UserType> initiator) {
this.initiator = initiator;
}
public PrismReferenceValue getTarget() {
return target;
}
public void setTarget(PrismReferenceValue target) {
this.target = target;
}
// Compatibility and convenience
public void setTarget(PrismObject<?> targetObject) {
if (targetObject != null) {
this.target = ObjectTypeUtil.createObjectRef((ObjectType) targetObject.asObjectable()).asReferenceValue();
} else {
this.target = null;
}
}
public PrismObject<UserType> getTargetOwner() {
return targetOwner;
}
public void setTargetOwner(PrismObject<UserType> targetOwner) {
this.targetOwner = targetOwner;
}
public AuditEventType getEventType() {
return eventType;
}
public void setEventType(AuditEventType eventType) {
this.eventType = eventType;
}
public AuditEventStage getEventStage() {
return eventStage;
}
public void setEventStage(AuditEventStage eventStage) {
this.eventStage = eventStage;
}
public Collection<ObjectDeltaOperation<? extends ObjectType>> getDeltas() {
return deltas;
}
public void addDelta(ObjectDeltaOperation<? extends ObjectType> delta) {
deltas.add(delta);
}
public void addDeltas(Collection<ObjectDeltaOperation<? extends ObjectType>> deltasToAdd) {
deltas.addAll(deltasToAdd);
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public void clearDeltas() {
deltas.clear();
}
public OperationResultStatus getOutcome() {
return outcome;
}
public void setOutcome(OperationResultStatus outcome) {
this.outcome = outcome;
}
public void setResult(String result) {
this.result = result;
}
public String getResult() {
return result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getParameter() {
return parameter;
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
@Transient
public Long getRepoId() {
return repoId;
}
public void setRepoId(Long repoId) {
this.repoId = repoId;
}
public Map<String, Set<String>> getProperties() {
return properties;
}
public Set<String> getPropertyValues(String name) {
return properties.get(name);
}
public Map<String, Set<AuditReferenceValue>> getReferences() {
return references;
}
public Set<AuditReferenceValue> getReferenceValues(String name) {
return references.get(name);
}
public void addPropertyValue(String key, String value) {
properties.computeIfAbsent(key, k -> new HashSet<>()).add(value);
}
public void addPropertyValueIgnoreNull(String key, Object value) {
if (value != null) {
addPropertyValue(key, String.valueOf(value));
}
}
public void addReferenceValueIgnoreNull(String key, ObjectReferenceType value) {
if (value != null) {
addReferenceValue(key, value.asReferenceValue());
}
}
public void addReferenceValue(String key, @NotNull AuditReferenceValue value) {
Validate.notNull(value, "Reference value must not be null");
references.computeIfAbsent(key, k -> new HashSet<>()).add(value);
}
public void addReferenceValue(String key, @NotNull PrismReferenceValue prv) {
Validate.notNull(prv, "Reference value must not be null");
addReferenceValue(key, new AuditReferenceValue(prv));
}
public void addReferenceValues(String key, @NotNull List<ObjectReferenceType> values) {
values.forEach(v -> addReferenceValue(key, v.asReferenceValue()));
}
public void checkConsistence() {
if (initiator != null) {
initiator.checkConsistence();
}
if (target != null && target.getObject() != null) {
target.getObject().checkConsistence();
}
if (targetOwner != null) {
targetOwner.checkConsistence();
}
ObjectDeltaOperation.checkConsistence(deltas);
// //TODO: should this be here?
// if (result != null && result.getStatus() != null) {
// if (result.getStatus() != outcome) {
// throw new IllegalStateException("Status in result (" + result.getStatus() + ") differs from outcome (" + outcome + ")");
// }
// }
}
public AuditEventRecordType createAuditEventRecordType(){
return createAuditEventRecordType(false);
}
public AuditEventRecordType createAuditEventRecordType(boolean tolerateInconsistencies) {
AuditEventRecordType auditRecordType = new AuditEventRecordType();
auditRecordType.setChannel(channel);
auditRecordType.setEventIdentifier(eventIdentifier);
auditRecordType.setEventStage(AuditEventStage.fromAuditEventStage(eventStage));
auditRecordType.setEventType(AuditEventType.fromAuditEventType(eventType));
auditRecordType.setHostIdentifier(hostIdentifier);
auditRecordType.setInitiatorRef(ObjectTypeUtil.createObjectRef(initiator, true));
auditRecordType.setMessage(message);
auditRecordType.setOutcome(OperationResultStatus.createStatusType(outcome));
auditRecordType.setParameter(parameter);
auditRecordType.setResult(result);
auditRecordType.setSessionIdentifier(sessionIdentifier);
auditRecordType.setTargetOwnerRef(ObjectTypeUtil.createObjectRef(targetOwner, true));
auditRecordType.setTargetRef(ObjectTypeUtil.createObjectRef(target, true));
auditRecordType.setTaskIdentifier(taskIdentifier);
auditRecordType.setTaskOID(taskOID);
auditRecordType.setTimestamp(MiscUtil.asXMLGregorianCalendar(timestamp));
for (ObjectDeltaOperation delta : deltas) {
ObjectDeltaOperationType odo = new ObjectDeltaOperationType();
try {
DeltaConvertor.toObjectDeltaOperationType(delta, odo, DeltaConversionOptions.createSerializeReferenceNames());
auditRecordType.getDelta().add(odo);
} catch (Exception e) {
if (tolerateInconsistencies){
if (delta.getExecutionResult() != null){
// TODO does this even work? [med]
delta.getExecutionResult().setMessage("Could not show audit record, bad data in delta: " + delta.getObjectDelta());
} else {
OperationResult result = new OperationResult("Create audit event record type");
result.setMessage("Could not show audit record, bad data in delta: " + delta.getObjectDelta());
odo.setExecutionResult(result.createOperationResultType());
}
continue;
} else {
throw new SystemException(e.getMessage(), e);
}
}
}
for (Map.Entry<String, Set<String>> propertyEntry : properties.entrySet()) {
AuditEventRecordPropertyType propertyType = new AuditEventRecordPropertyType();
propertyType.setName(propertyEntry.getKey());
propertyType.getValue().addAll(propertyEntry.getValue());
auditRecordType.getProperty().add(propertyType);
}
for (Map.Entry<String, Set<AuditReferenceValue>> referenceEntry : references.entrySet()) {
AuditEventRecordReferenceType referenceType = new AuditEventRecordReferenceType();
referenceType.setName(referenceEntry.getKey());
referenceEntry.getValue().forEach(v -> referenceType.getValue().add(v.toXml()));
auditRecordType.getReference().add(referenceType);
}
return auditRecordType;
}
public static AuditEventRecord createAuditEventRecord(AuditEventRecordType auditEventRecordType) {
AuditEventRecord auditRecord = new AuditEventRecord();
auditRecord.setChannel(auditEventRecordType.getChannel());
auditRecord.setEventIdentifier(auditEventRecordType.getEventIdentifier());
auditRecord.setEventStage(AuditEventStage.toAuditEventStage(auditEventRecordType.getEventStage()));
auditRecord.setEventType(AuditEventType.toAuditEventType(auditEventRecordType.getEventType()));
auditRecord.setHostIdentifier(auditEventRecordType.getHostIdentifier());
auditRecord.setInitiator(getObjectFromObjectReferenceType(auditEventRecordType.getInitiatorRef()));
auditRecord.setMessage(auditEventRecordType.getMessage());
auditRecord.setOutcome(OperationResultStatus.parseStatusType(auditEventRecordType.getOutcome()));
auditRecord.setParameter(auditEventRecordType.getParameter());
auditRecord.setResult(auditEventRecordType.getResult());
auditRecord.setSessionIdentifier(auditEventRecordType.getSessionIdentifier());
auditRecord.setTarget(getReferenceValueFromObjectReferenceType(auditEventRecordType.getTargetRef()));
auditRecord.setTargetOwner(getObjectFromObjectReferenceType(auditEventRecordType.getTargetOwnerRef()));
auditRecord.setTaskIdentifier(auditEventRecordType.getTaskIdentifier());
auditRecord.setTaskOID(auditEventRecordType.getTaskOID());
auditRecord.setTimestamp(MiscUtil.asLong(auditEventRecordType.getTimestamp()));
for (AuditEventRecordPropertyType propertyType : auditEventRecordType.getProperty()) {
propertyType.getValue().forEach(v -> auditRecord.addPropertyValue(propertyType.getName(), v));
}
for (AuditEventRecordReferenceType referenceType : auditEventRecordType.getReference()) {
referenceType.getValue().forEach(v -> auditRecord.addReferenceValue(referenceType.getName(), AuditReferenceValue.fromXml(v)));
}
return auditRecord;
}
private static PrismReferenceValue getReferenceValueFromObjectReferenceType(ObjectReferenceType refType) {
if (refType == null) {
return null;
}
PrismReferenceValue refVal = new PrismReferenceValue(refType.getOid());
refVal.setTargetType(refType.getType());
refVal.setTargetName(refType.getTargetName());
return refVal;
}
private static PrismObject getObjectFromObjectReferenceType(ObjectReferenceType ref){
if (ref == null){
return null;
}
PrismReferenceValue prismRef = ref.asReferenceValue();
return prismRef.getObject();
}
public AuditEventRecord clone() {
AuditEventRecord clone = new AuditEventRecord();
clone.channel = this.channel;
clone.deltas.addAll(MiscSchemaUtil.cloneObjectDeltaOperationCollection(this.deltas));
clone.eventIdentifier = this.eventIdentifier;
clone.eventStage = this.eventStage;
clone.eventType = this.eventType;
clone.hostIdentifier = this.hostIdentifier;
clone.initiator = this.initiator;
clone.outcome = this.outcome;
clone.sessionIdentifier = this.sessionIdentifier;
clone.target = this.target;
clone.targetOwner = this.targetOwner;
clone.taskIdentifier = this.taskIdentifier;
clone.taskOID = this.taskOID;
clone.timestamp = this.timestamp;
clone.result = this.result;
clone.parameter = this.parameter;
clone.message = this.message;
clone.properties.putAll(properties); // TODO deep clone?
clone.references.putAll(references); // TODO deep clone?
return clone;
}
@Override
public String toString() {
return "AUDIT[" + formatTimestamp(timestamp) + " eid=" + eventIdentifier
+ " sid=" + sessionIdentifier + ", tid=" + taskIdentifier
+ " toid=" + taskOID + ", hid=" + hostIdentifier + ", I=" + formatObject(initiator)
+ ", T=" + formatReference(target) + ", TO=" + formatObject(targetOwner) + ", et=" + eventType
+ ", es=" + eventStage + ", D=" + deltas + ", ch="+ channel +", o=" + outcome + ", r=" + result + ", p=" + parameter
+ ", m=" + message
+ ", prop=" + properties
+ ", ref=" + references + "]";
}
private String formatResult(OperationResult result) {
if (result == null || result.getReturns() == null || result.getReturns().isEmpty()) {
return "nothing";
}
StringBuilder sb = new StringBuilder();
for (String key : result.getReturns().keySet()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(key);
sb.append("=");
sb.append(result.getReturns().get(key));
}
return sb.toString();
}
private static String formatTimestamp(Long timestamp) {
if (timestamp == null) {
return "null";
}
return TIMESTAMP_FORMAT.format(new java.util.Date(timestamp));
}
private static String formatObject(PrismObject<? extends ObjectType> object) {
if (object == null) {
return "null";
}
return object.toString();
}
private String formatReference(PrismReferenceValue refVal) {
if (refVal == null) {
return "null";
}
if (refVal.getObject() != null) {
return formatObject(refVal.getObject());
}
return refVal.toString();
}
@Override
public String debugDump() {
return debugDump(0);
}
@Override
public String debugDump(int indent) {
StringBuilder sb = new StringBuilder();
DebugUtil.indentDebugDump(sb, indent);
sb.append("AUDIT");
sb.append("\n");
DebugUtil.debugDumpWithLabelToStringLn(sb, "Timestamp", formatTimestamp(timestamp), indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Event Identifier", eventIdentifier, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Session Identifier", sessionIdentifier, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Task Identifier", taskIdentifier, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Task OID", taskOID, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Host Identifier", hostIdentifier, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Initiator", formatObject(initiator), indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Target", formatReference(target), indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Target Owner", formatObject(targetOwner), indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Event Type", eventType, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Event Stage", eventStage, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Channel", channel, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Outcome", outcome, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Result", result, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Parameter", parameter, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Message", message, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "Properties", properties, indent + 1);
DebugUtil.debugDumpWithLabelToStringLn(sb, "References", references, indent + 1);
DebugUtil.debugDumpLabel(sb, "Deltas", indent + 1);
if (deltas.isEmpty()) {
sb.append(" none");
} else {
sb.append(" ").append(deltas.size()).append(" deltas\n");
DebugUtil.debugDump(sb, deltas, indent + 2, false);
}
return sb.toString();
}
// a bit of hack - TODO place appropriately
public static void adopt(AuditEventRecordType record, PrismContext prismContext) throws SchemaException {
for (ObjectDeltaOperationType odo : record.getDelta()) {
adopt(odo.getObjectDelta(), prismContext);
}
}
public static void adopt(ObjectDeltaType delta, PrismContext prismContext) throws SchemaException {
if (delta == null) {
return;
}
if (delta.getObjectToAdd() != null) {
prismContext.adopt(delta.getObjectToAdd().asPrismObject());
}
for (ItemDeltaType itemDelta : delta.getItemDelta()) {
adopt(itemDelta, prismContext);
}
}
private static void adopt(ItemDeltaType itemDelta, PrismContext prismContext) throws SchemaException {
for (RawType value : itemDelta.getValue()) {
if (value != null) {
value.revive(prismContext);
}
}
for (RawType value : itemDelta.getEstimatedOldValue()) {
if (value != null) {
value.revive(prismContext);
}
}
}
}