Skip to content

Commit

Permalink
Audit module (Change Tracking) (vivo-project#390)
Browse files Browse the repository at this point in the history
* merged changeset_user_info

* changed default graph uri

* audit code

* Modified audit controller

* Audit controller added user information

* Add listener to RDFService configuration models

* added some filters for audit history

* More filtering options for audit controller

* added user id in audit history table

* Allow only admins to access audit page

* fix for prev commit

* Set default end date to be the next day to avoid empty results

* Removed not implemented AuditDaoFS implementation, added tests for AuditDatTDB

* added example configuration

* Fixed some typos

* Don't show empty changes on audit history page

* fix: release dataset before close

* remove empty test file

* chore: fixed formatting

* Don't delete TDB dataset from temporary directory as it results in test errors on Windows and the directory will be deleted at some point anyway.

* refactored and improved sparql queries

fixes

* style fixes

* fixes

* one more fix

---------

Co-authored-by: Georgy Litvinov <georgy.litvinov@tib.eu>
  • Loading branch information
litvinovg and litvinovg committed Dec 22, 2023
1 parent d3903af commit 440d4e8
Show file tree
Hide file tree
Showing 39 changed files with 2,276 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import edu.cornell.mannlib.vitro.webapp.audit.AuditModule;
import org.apache.jena.ontology.OntDocumentManager;

import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
Expand Down Expand Up @@ -41,6 +42,7 @@ public class ApplicationImpl implements Application {
private SearchIndexer searchIndexer;
private ImageProcessor imageProcessor;
private FileStorage fileStorage;
private AuditModule auditModule;
private ContentTripleSource contentTripleSource;
private ConfigurationTripleSource configurationTripleSource;
private TBoxReasonerModule tboxReasonerModule;
Expand Down Expand Up @@ -103,6 +105,15 @@ public void setFileStorage(FileStorage fs) {
fileStorage = fs;
}

@Override
public AuditModule getAuditModule() {
return auditModule;
}

@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasAuditModule", minOccurs = 0, maxOccurs = 1)
public void setAuditModule(AuditModule am) {
auditModule = am;
}
@Override
public ContentTripleSource getContentTripleSource() {
return contentTripleSource;
Expand Down Expand Up @@ -165,6 +176,11 @@ public void contextInitialized(ServletContextEvent sce) {
fileStorage.startup(app, css);
ss.info(this, "Started the FileStorage system: " + fileStorage);

AuditModule auditModule = app.getAuditModule();
if (auditModule != null) {
auditModule.startup(app, css);
}

ContentTripleSource contentTripleSource = app
.getContentTripleSource();
contentTripleSource.startup(app, css);
Expand Down Expand Up @@ -213,6 +229,11 @@ public void contextDestroyed(ServletContextEvent sce) {
app.getFileStorage().shutdown(app);
app.getImageProcessor().shutdown(app);
app.getSearchEngine().shutdown(app);

AuditModule auditModule = app.getAuditModule();
if (auditModule != null) {
auditModule.shutdown(app);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vitro.webapp.audit;

import java.util.List;

import freemarker.ext.beans.StringModel;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;

/**
* Base helper method for Freemarker
*/
public abstract class AbstractListStatementsMethod implements TemplateMethodModelEx {
@Override
public Object exec(List arguments) throws TemplateModelException {
// We expect two arguments
// 1 - an AuditChangeSet
// 2 - a graph URI
if (arguments.size() == 2) {
Object arg1 = arguments.get(0);
Object arg2 = arguments.get(1);

// This looks odd, but the AuditChangeSet is wrapped in a StringModel
if (arg1 instanceof StringModel) {
arg1 = ((StringModel) arg1).getWrappedObject();
}

if (arg1 instanceof AuditChangeSet && arg2 instanceof SimpleScalar) {
AuditChangeSet dataset = (AuditChangeSet) arg1;
String graphUri = ((SimpleScalar) arg2).getAsString();

// Get the statements from the changeset for the named graph
return getStatements(dataset, graphUri);
}
}

throw new TemplateModelException("Wrong arguments");
}

/**
* Abstract method to be implemented for Added / Removed statements
*
* @param dataset
* @param graphUri
* @return
*/
protected abstract Object getStatements(AuditChangeSet dataset, String graphUri);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vitro.webapp.audit;

import edu.cornell.mannlib.vitro.webapp.audit.storage.AuditDAOFactory;
import edu.cornell.mannlib.vitro.webapp.audit.storage.AuditVocabulary;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jena.rdf.listeners.StatementListener;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelChangedListener;

/**
* Listener for changes in the RDFService
*/
public class AuditChangeListener extends StatementListener implements ModelChangedListener, ChangeListener {
private static final Log log = LogFactory.getLog(AuditChangeListener.class);

@Override
public void notifyModelChange(ModelChange modelChange) {

// Convert the serialized statements into a Jena Model
Model changes =
RDFServiceUtils.parseModel(modelChange.getSerializedModel(), modelChange.getSerializationFormat());

// Get the changeset for the current request
AuditChangeSet auditChangeset = new AuditChangeSet();
Model additions = auditChangeset.getAddedModel(modelChange.getGraphURI());

String userId = modelChange.getUserId();
if (StringUtils.isBlank(userId)) {
Exception e = new Exception();
log.debug("User id is not provided.", e);
userId = AuditVocabulary.RESOURCE_UNKNOWN;
}
auditChangeset.setUserId(userId);

// Is the change adding or removing statements?
if (modelChange.getOperation() == ModelChange.Operation.REMOVE) {
// If we are removing statements, make sure we don't retain them in the additions
additions.remove(changes);

// Record all of the changes in the Model of removed statements
Model removed = auditChangeset.getRemovedModel(modelChange.getGraphURI());
removed.add(changes);
} else {
// Record all of the changes in the Model of added statements
additions.add(changes);
}
if (!auditChangeset.isEmpty()) {
// Write the changes to the audit store
AuditDAOFactory.getAuditDAO().write(auditChangeset);
}
}

@Override
public void notifyEvent(String graphURI, Object event) {
}
}
Loading

0 comments on commit 440d4e8

Please sign in to comment.