package io.lumify.securegraph.model.audit;
import com.altamiracorp.bigtable.model.FlushFlag;
import com.altamiracorp.bigtable.model.ModelSession;
import com.altamiracorp.bigtable.model.Row;
import com.altamiracorp.bigtable.model.user.ModelUserContext;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import io.lumify.core.config.Configuration;
import io.lumify.core.model.PropertyJustificationMetadata;
import io.lumify.core.model.audit.*;
import io.lumify.core.model.ontology.OntologyProperty;
import io.lumify.core.model.ontology.OntologyRepository;
import io.lumify.core.model.properties.LumifyProperties;
import io.lumify.core.model.user.UserRepository;
import io.lumify.core.user.User;
import io.lumify.core.version.VersionService;
import io.lumify.web.clientapi.model.PropertyType;
import org.json.JSONObject;
import org.securegraph.*;
import org.securegraph.mutation.ElementMutation;
import org.securegraph.mutation.ExistingElementMutation;
import org.securegraph.type.GeoPoint;
import org.securegraph.util.IterableUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.lumify.core.model.properties.LumifyProperties.CONCEPT_TYPE;
import static io.lumify.core.model.properties.LumifyProperties.TITLE;
public class SecureGraphAuditRepository extends AuditRepository {
private final AuditBuilder auditBuilder = new AuditBuilder();
private final VersionService versionService;
private final Configuration configuration;
private final OntologyRepository ontologyRepository;
private final UserRepository userRepository;
@Inject
public SecureGraphAuditRepository(final ModelSession modelSession, final VersionService versionService,
final Configuration configuration, final OntologyRepository ontologyRepository,
final UserRepository userRepository) {
super(modelSession);
this.versionService = versionService;
this.configuration = configuration;
this.ontologyRepository = ontologyRepository;
this.userRepository = userRepository;
}
@Override
public Audit fromRow(Row row) {
return auditBuilder.fromRow(row);
}
@Override
public Row toRow(Audit audit) {
return audit;
}
@Override
public String getTableName() {
return auditBuilder.getTableName();
}
@Override
public Iterable<Audit> getAudits(String vertexId, String workspaceId, Authorizations authorizations) {
ModelUserContext modelUserContext = userRepository.getModelUserContext(authorizations, workspaceId);
return findByRowStartsWith(vertexId, modelUserContext);
}
public Audit createAudit(AuditAction auditAction, Object vertexId, String process, String comment, User user, Visibility visibility) {
checkNotNull(vertexId, "vertexId cannot be null");
checkNotNull(comment, "comment cannot be null");
checkNotNull(user, "user cannot be null");
checkNotNull(process, "process cannot be null");
Audit audit = new Audit(AuditRowKey.build(vertexId));
visibility = orVisibility(visibility);
audit.getAuditCommon()
.setUser(user, visibility)
.setAction(auditAction, visibility)
.setType(OntologyRepository.ENTITY_CONCEPT_IRI, visibility)
.setComment(comment, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
if (process.length() > 0) {
audit.getAuditCommon().setProcess(process, visibility);
}
return audit;
}
@Override
public Audit auditVertex(AuditAction auditAction, Object vertexId, String process, String comment, User user, Visibility visibility) {
Audit audit = createAudit(auditAction, vertexId, process, comment, user, visibility);
save(audit, FlushFlag.FLUSH);
return audit;
}
@Override
public Audit auditEntityProperty(
AuditAction action,
Object id,
String propertyKey,
String propertyName,
Object oldValue,
Object newValue,
String process,
String comment,
Metadata metadata,
User user,
Visibility visibility) {
checkNotNull(action, "action cannot be null");
checkNotNull(id, "id cannot be null");
checkNotNull(propertyName, "propertyName cannot be null");
checkArgument(propertyName.length() > 0, "property name cannot be empty");
checkNotNull(process, "process cannot be null");
checkNotNull(comment, "comment cannot be null");
checkNotNull(user, "user cannot be null");
Audit audit = new Audit(AuditRowKey.build(id));
visibility = orVisibility(visibility);
audit.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.TYPE_PROPERTY, visibility)
.setComment(comment, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
audit.getAuditProperty().setPropertyKey(propertyKey != null ? propertyKey : "", visibility);
if (oldValue != null) {
if (oldValue instanceof GeoPoint) {
String val = String.format("POINT(%f,%f)", ((GeoPoint) oldValue).getLatitude(), ((GeoPoint) oldValue).getLongitude());
audit.getAuditProperty().setPreviousValue(val, visibility);
} else {
String convertedValue = checkAndConvertForDateType(propertyName, oldValue);
audit.getAuditProperty().setPreviousValue(convertedValue != null ? convertedValue : oldValue.toString(), visibility);
}
}
if (action == AuditAction.DELETE) {
audit.getAuditProperty().setNewValue("", visibility);
} else {
if (newValue instanceof GeoPoint) {
String val = String.format("POINT(%f,%f)", ((GeoPoint) newValue).getLatitude(), ((GeoPoint) newValue).getLongitude());
audit.getAuditProperty().setNewValue(val, visibility);
} else {
String convertedValue = checkAndConvertForDateType(propertyName, newValue);
audit.getAuditProperty().setNewValue(convertedValue != null ? convertedValue : newValue.toString(), visibility);
}
}
audit.getAuditProperty().setPropertyName(propertyName, visibility);
if (metadata != null && !metadata.entrySet().isEmpty()) {
audit.getAuditProperty().setPropertyMetadata(jsonMetadata(metadata).toString(), visibility);
}
save(audit);
return audit;
}
@Override
public List<Audit> auditRelationship(AuditAction action, Vertex sourceVertex, Vertex destVertex, Edge edge, String process,
String comment, User user, Visibility visibility) {
checkNotNull(action, "action cannot be null");
checkNotNull(sourceVertex, "sourceVertex cannot be null");
checkNotNull(destVertex, "destVertex cannot be null");
checkNotNull(edge, "edge cannot be null");
checkNotNull(process, "process cannot be null");
checkNotNull(comment, "comment cannot be null");
checkNotNull(user, "user cannot be null");
Audit auditSourceDest = new Audit(AuditRowKey.build(sourceVertex.getId(), destVertex.getId()));
Audit auditDestSource = new Audit(AuditRowKey.build(destVertex.getId(), sourceVertex.getId()));
Audit auditEdge = new Audit(AuditRowKey.build(edge.getId()));
visibility = orVisibility(visibility);
List<Audit> audits = new ArrayList<>();
String displayLabel = ontologyRepository.getDisplayNameForLabel(edge.getLabel());
checkNotNull(displayLabel, "Could not find display name for label '" + edge.getLabel() + "' on edge " + edge.getId());
audits.add(auditRelationshipHelper(auditSourceDest, action, sourceVertex, destVertex, displayLabel, process, comment, user, visibility));
audits.add(auditRelationshipHelper(auditDestSource, action, sourceVertex, destVertex, displayLabel, process, comment, user, visibility));
auditEdge.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.TYPE_RELATIONSHIP, visibility)
.setComment(comment, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
Iterable<String> sourceTitleIterable = TITLE.getPropertyValues(sourceVertex);
String sourceTitle = "";
if (IterableUtils.count(sourceTitleIterable) != 0) {
sourceTitle = IterableUtils.toList(sourceTitleIterable).get(IterableUtils.count(sourceTitleIterable) - 1);
}
Iterable<String> destTitleIterable = TITLE.getPropertyValues(sourceVertex);
String destTitle = "";
if (IterableUtils.count(destTitleIterable) != 0) {
destTitle = IterableUtils.toList(destTitleIterable).get(IterableUtils.count(destTitleIterable) - 1);
}
auditEdge.getAuditRelationship()
.setSourceId(sourceVertex.getId(), visibility)
.setSourceType(CONCEPT_TYPE.getPropertyValue(sourceVertex), visibility)
.setSourceTitle(sourceTitle, visibility)
.setDestId(destVertex.getId(), visibility)
.setDestTitle(destTitle, visibility)
.setDestType(CONCEPT_TYPE.getPropertyValue(destVertex), visibility)
.setLabel(displayLabel, visibility);
audits.add(auditEdge);
saveMany(audits);
return audits;
}
@Override
public List<Audit> auditRelationshipProperty(AuditAction action, String sourceId, String destId, String propertyKey, String propertyName,
Object oldValue, Object newValue, Edge edge, String process, String comment, User user,
Visibility visibility) {
checkNotNull(action, "action cannot be null");
checkNotNull(sourceId, "sourceId cannot be null");
checkNotNull(sourceId.length() > 0, "sourceId cannot be empty");
checkNotNull(destId, "destId cannot be null");
checkNotNull(destId.length() > 0, "destId cannot be empty");
checkNotNull(propertyName, "propertyName cannot be null");
checkNotNull(propertyName.length() > 0, "propertyName cannot be empty");
checkNotNull(edge, "edge cannot be null");
checkNotNull(process, "process cannot be null");
checkNotNull(comment, "comment cannot be null");
checkNotNull(user, "user cannot be null");
Audit auditSourceDest = new Audit(AuditRowKey.build(sourceId, destId));
Audit auditDestSource = new Audit(AuditRowKey.build(destId, sourceId));
Audit auditEdge = new Audit(AuditRowKey.build(edge.getId()));
visibility = orVisibility(visibility);
auditSourceDest.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.TYPE_PROPERTY, visibility)
.setComment(comment, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
auditDestSource.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.TYPE_PROPERTY, visibility)
.setComment(comment, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
auditEdge.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.TYPE_PROPERTY, visibility)
.setComment(comment, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
propertyKey = propertyKey != null ? propertyKey : "";
auditEdge.getAuditProperty().setPropertyKey(propertyKey, visibility);
auditSourceDest.getAuditProperty().setPropertyKey(propertyKey, visibility);
auditDestSource.getAuditProperty().setPropertyKey(propertyKey, visibility);
if (oldValue != null && !oldValue.equals("")) {
String convertedValue = checkAndConvertForDateType(propertyName, oldValue);
if (convertedValue != null) {
oldValue = convertedValue;
}
auditDestSource.getAuditProperty().setPreviousValue(oldValue.toString(), visibility);
auditSourceDest.getAuditProperty().setPreviousValue(oldValue.toString(), visibility);
auditEdge.getAuditProperty().setPreviousValue(oldValue.toString(), visibility);
}
if (action == AuditAction.DELETE) {
auditDestSource.getAuditProperty().setNewValue("", visibility);
auditSourceDest.getAuditProperty().setNewValue("", visibility);
auditEdge.getAuditProperty().setNewValue("", visibility);
} else {
String convertedValue = checkAndConvertForDateType(propertyName, newValue);
if (convertedValue != null) {
newValue = convertedValue;
}
auditDestSource.getAuditProperty().setNewValue(newValue.toString(), visibility);
auditSourceDest.getAuditProperty().setNewValue(newValue.toString(), visibility);
auditEdge.getAuditProperty().setNewValue(newValue.toString(), visibility);
}
auditDestSource.getAuditProperty().setPropertyName(propertyName, visibility);
auditSourceDest.getAuditProperty().setPropertyName(propertyName, visibility);
auditEdge.getAuditProperty().setPropertyName(propertyName, visibility);
Metadata metadata = edge.getProperty(propertyKey, propertyName).getMetadata();
if (metadata != null && !metadata.entrySet().isEmpty()) {
auditDestSource.getAuditProperty().setPropertyMetadata(jsonMetadata(metadata).toString(), visibility);
auditSourceDest.getAuditProperty().setPropertyMetadata(jsonMetadata(metadata).toString(), visibility);
auditEdge.getAuditProperty().setPropertyMetadata(jsonMetadata(metadata).toString(), visibility);
}
List<Audit> audits = Lists.newArrayList(auditSourceDest, auditDestSource, auditEdge);
saveMany(audits);
return audits;
}
@Override
public Audit auditAnalyzedBy(AuditAction action, Vertex vertex, String process, User user, Visibility visibility) {
Audit audit = new Audit(AuditRowKey.build(vertex.getId()));
audit.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.ENTITY_CONCEPT_IRI, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
audit.getAuditEntity().setAnalyzedBy(process, visibility);
save(audit);
return audit;
}
private Audit auditRelationshipHelper(Audit audit, AuditAction action, Vertex sourceVertex, Vertex destVertex,
String label, String process, String comment, User user,
Visibility visibility) {
visibility = orVisibility(visibility);
audit.getAuditCommon()
.setUser(user, visibility)
.setAction(action, visibility)
.setType(OntologyRepository.TYPE_RELATIONSHIP, visibility)
.setComment(comment, visibility)
.setProcess(process, visibility)
.setUnixBuildTime(versionService.getUnixBuildTime() != null ? versionService.getUnixBuildTime() : -1L, visibility)
.setScmBuildNumber(versionService.getScmBuildNumber() != null ? versionService.getScmBuildNumber() : "", visibility)
.setVersion(versionService.getVersion() != null ? versionService.getVersion() : "", visibility);
Iterable<String> sourceTitleIterable = TITLE.getPropertyValues(sourceVertex);
String sourceTitle = "";
if (IterableUtils.count(sourceTitleIterable) != 0) {
sourceTitle = IterableUtils.toList(sourceTitleIterable).get(IterableUtils.count(sourceTitleIterable) - 1);
}
Iterable<String> destTitleIterable = TITLE.getPropertyValues(destVertex);
String destTitle = "";
if (IterableUtils.count(destTitleIterable) != 0) {
destTitle = IterableUtils.toList(destTitleIterable).get(IterableUtils.count(destTitleIterable) - 1);
}
String sourceVertexConceptType = CONCEPT_TYPE.getPropertyValue(sourceVertex);
checkNotNull(sourceVertexConceptType, "vertex " + sourceVertex.getId() + " has a null " + CONCEPT_TYPE.getPropertyName());
String destVertexConceptType = CONCEPT_TYPE.getPropertyValue(destVertex);
checkNotNull(destVertexConceptType, "vertex " + destVertex.getId() + " has a null " + CONCEPT_TYPE.getPropertyName());
audit.getAuditRelationship()
.setSourceId(sourceVertex.getId(), visibility)
.setSourceType(sourceVertexConceptType, visibility)
.setSourceTitle(sourceTitle, visibility)
.setDestId(destVertex.getId(), visibility)
.setDestTitle(destTitle, visibility)
.setDestType(destVertexConceptType, visibility)
.setLabel(label, visibility);
return audit;
}
@Override
public void auditVertexElementMutation(AuditAction action, ElementMutation<Vertex> vertexElementMutation, Vertex vertex, String process,
User user, Visibility visibility) {
if (vertexElementMutation instanceof ExistingElementMutation) {
Vertex oldVertex = (Vertex) ((ExistingElementMutation) vertexElementMutation).getElement();
for (Property property : vertexElementMutation.getProperties()) {
Object oldPropertyValue = oldVertex.getPropertyValue(property.getKey());
Object newPropertyValue = property.getValue();
checkNotNull(newPropertyValue, "new property (" + property + ") value cannot be null");
if (!newPropertyValue.equals(oldPropertyValue) || !oldVertex.getVisibility().getVisibilityString().equals(property.getVisibility().getVisibilityString())) {
auditEntityProperty(action, oldVertex.getId(), property.getKey(), property.getName(), oldPropertyValue,
newPropertyValue, process, "", property.getMetadata(), user, visibility);
}
}
} else {
auditVertexCreate(vertex.getId(), process, "", user, visibility);
for (Property property : vertexElementMutation.getProperties()) {
Object newPropertyValue = property.getValue();
checkNotNull(newPropertyValue, "new property (" + property + ") value cannot be null");
auditEntityProperty(
action,
vertex.getId(),
property.getKey(),
property.getName(),
null,
newPropertyValue,
process,
"",
property.getMetadata(),
user,
visibility
);
}
}
}
@Override
public void auditEdgeElementMutation(AuditAction action, ElementMutation<Edge> edgeElementMutation, Edge edge, Vertex sourceVertex, Vertex destVertex, String process,
User user, Visibility visibility) {
if (edgeElementMutation instanceof ExistingElementMutation) {
Edge oldEdge = (Edge) ((ExistingElementMutation) edgeElementMutation).getElement();
for (Property property : edgeElementMutation.getProperties()) {
Object oldPropertyValue = oldEdge.getPropertyValue(property.getKey());
Object newPropertyValue = property.getValue();
checkNotNull(newPropertyValue, "new property value cannot be null");
if (!newPropertyValue.equals(oldPropertyValue)) {
auditRelationshipProperty(action, sourceVertex.getId(), destVertex.getId(), property.getKey(),
property.getName(), oldPropertyValue, newPropertyValue, edge, process, "", user, visibility);
}
}
} else {
auditRelationship(AuditAction.CREATE, sourceVertex, destVertex, edge, process, "", user, visibility);
for (Property property : edgeElementMutation.getProperties()) {
Object newPropertyValue = property.getValue();
checkNotNull(newPropertyValue, "new property value cannot be null");
auditRelationshipProperty(action, sourceVertex.getId(), destVertex.getId(), property.getKey(),
property.getName(), null, newPropertyValue, edge, process, "", user, visibility);
}
}
}
@Override
public void updateColumnVisibility(Audit audit, Visibility originalEdgeVisibility, String visibilityString) {
getModelSession().alterColumnsVisibility(audit, orVisibility(originalEdgeVisibility).getVisibilityString(), visibilityString, FlushFlag.FLUSH);
}
private JSONObject jsonMetadata(Metadata metadata) {
JSONObject json = new JSONObject();
for (Metadata.Entry metadataEntry : metadata.entrySet()) {
if (metadataEntry.getKey().equals(LumifyProperties.JUSTIFICATION.getPropertyName())) {
PropertyJustificationMetadata propertyJustificationMetadata;
Object metadataEntryValue = metadataEntry.getValue();
if (metadataEntryValue instanceof PropertyJustificationMetadata) {
propertyJustificationMetadata = (PropertyJustificationMetadata) metadataEntryValue;
} else {
propertyJustificationMetadata = new PropertyJustificationMetadata(new JSONObject(metadataEntryValue.toString()));
}
json.put(LumifyProperties.JUSTIFICATION.getPropertyName(), (propertyJustificationMetadata).toJson());
} else {
json.put(metadataEntry.getKey(), metadataEntry.getValue());
}
}
return json;
}
private Visibility orVisibility(Visibility visibility) {
String auditVisibilityLabel = configuration.get(Configuration.AUDIT_VISIBILITY_LABEL, null);
if (auditVisibilityLabel != null && !visibility.toString().equals("")) {
if (visibility.toString().equals(auditVisibilityLabel)) {
return new Visibility(auditVisibilityLabel);
}
return new Visibility("(" + auditVisibilityLabel + "|" + visibility.toString() + ")");
}
return visibility;
}
private String checkAndConvertForDateType(String propertyName, Object value) {
OntologyProperty ontologyProperty = ontologyRepository.getPropertyByIRI(propertyName);
if (ontologyProperty != null && ontologyProperty.getDataType() == PropertyType.DATE) {
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyy");
try {
return String.valueOf(dateFormat.parse(value.toString()).getTime());
} catch (ParseException e) {
throw new RuntimeException("could not parse date");
}
}
return null;
}
private Audit auditVertexCreate(Object vertexId, String process, String comment, User user, Visibility visibility) {
return auditVertex(AuditAction.CREATE, vertexId, process, comment, user, visibility);
}
}