From 20b7f694d12ab471ef619e7ad9ea2f1eb525b6a3 Mon Sep 17 00:00:00 2001 From: j2blake Date: Tue, 20 Dec 2011 21:24:52 +0000 Subject: [PATCH] NIHVIVO-3523 Write the PermissionSetsLoader. --- .../permissions/PermissionSetsLoader.java | 328 ++++++++++++++++-- .../WEB-INF/resources/permission_config.n3 | 19 +- 2 files changed, 313 insertions(+), 34 deletions(-) diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java index 330383f50..2693c3196 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java @@ -4,6 +4,11 @@ package edu.cornell.mannlib.vitro.webapp.auth.permissions; import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.VITRO_AUTH; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -12,12 +17,24 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Selector; +import com.hp.hpl.jena.rdf.model.SimpleSelector; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.shared.Lock; +import com.hp.hpl.jena.util.iterator.ClosableIterator; +import com.hp.hpl.jena.vocabulary.RDF; +import com.hp.hpl.jena.vocabulary.RDFS; -import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.dao.jena.ModelContext; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; @@ -26,12 +43,14 @@ import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; * * The UserAccounts model must be created before this runs. * - * For now, we just use the four hard-coded "roles". + * The PermissionRegistry must be created before this runs. */ public class PermissionSetsLoader implements ServletContextListener { private static final Log log = LogFactory .getLog(PermissionSetsLoader.class); + public static final String FILE_OF_PERMISSION_SETS_INFO = "/WEB-INF/resources/permission_config.n3"; + public static final String URI_SELF_EDITOR = VITRO_AUTH + "SELF_EDITOR"; public static final String URI_EDITOR = VITRO_AUTH + "EDITOR"; public static final String URI_CURATOR = VITRO_AUTH + "CURATOR"; @@ -43,19 +62,10 @@ public class PermissionSetsLoader implements ServletContextListener { StartupStatus ss = StartupStatus.getBean(ctx); try { - String ns = ConfigurationProperties.getBean(ctx).getProperty( - "Vitro.defaultNamespace"); - - OntModel model = ModelContext.getBaseOntModelSelector(ctx) - .getUserAccountsModel(); - - ModelWrapper wrapper = new ModelWrapper(model); - wrapper.createPermissionSet(URI_SELF_EDITOR, "Self Editor"); - wrapper.createPermissionSet(URI_EDITOR, "Editor"); - wrapper.createPermissionSet(URI_CURATOR, "Curator"); - wrapper.createPermissionSet(URI_DBA, "Site Admin"); + new Loader(this, ctx, ss).load(); + new SmokeTester(this, ctx, ss).test(); } catch (Exception e) { - ss.fatal(this, "could not run PermissionSetsLoader" + e); + ss.fatal(this, "Failed to load the PermissionSets", e); } } @@ -64,32 +74,284 @@ public class PermissionSetsLoader implements ServletContextListener { // Nothing to tear down. } - private static class ModelWrapper { - private final OntModel model; + // ---------------------------------------------------------------------- + // Loader class + // ---------------------------------------------------------------------- - private final Property typeProperty; - private final Property labelProperty; - private final Resource permissionSet; + private static class Loader { + private static final int MAX_STATEMENTS_IN_WARNING = 5; - public ModelWrapper(OntModel model) { - this.model = model; + private ServletContextListener listener; + private final ServletContext ctx; + private final StartupStatus ss; + + private final OntModel userAccountsModel; + private final Property permissionSetType; + + private Model modelFromFile; + private Model filteredModel; + + private int howManyNewPermissionSets; + private int howManyOldPermissionSets; + + public Loader(ServletContextListener listener, ServletContext ctx, + StartupStatus ss) { + this.listener = listener; + this.ctx = ctx; + this.ss = ss; + + this.userAccountsModel = ModelContext.getBaseOntModelSelector(ctx) + .getUserAccountsModel(); + this.permissionSetType = this.userAccountsModel + .getProperty(VitroVocabulary.PERMISSIONSET); - typeProperty = model.createProperty(VitroVocabulary.RDF_TYPE); - labelProperty = model.createProperty(VitroVocabulary.LABEL); - permissionSet = model.createResource(VitroVocabulary.PERMISSIONSET); } - public void createPermissionSet(String uri, String label) { - model.enterCriticalSection(Lock.WRITE); + public void load() { try { - Resource r = model.createResource(uri); - model.add(r, typeProperty, permissionSet); - model.add(r, labelProperty, label); - log.debug("Created permission set: '" + uri + "', '" + label - + "'"); - } finally { - model.leaveCriticalSection(); + createModelFromFile(); + filterModelFromFile(); + checkForLeftoverStatements(); + removeExistingPermissionSetsFromUserAccountsModel(); + addNewStatementsToUserAccountsModel(); + + ss.info(listener, buildInfoMessage()); + } catch (LoaderException e) { + Throwable cause = e.getCause(); + if (cause == null) { + ss.warning(listener, e.getMessage()); + } else { + ss.warning(listener, e.getMessage(), e.getCause()); + } } } + + private void createModelFromFile() throws LoaderException { + InputStream stream = ctx + .getResourceAsStream(FILE_OF_PERMISSION_SETS_INFO); + + if (stream == null) { + throw new LoaderException("The permission sets config file " + + "doesn't exist in the servlet context: '" + + FILE_OF_PERMISSION_SETS_INFO + "'"); + } + + try { + modelFromFile = ModelFactory.createDefaultModel(); + modelFromFile.read(stream, null, "N3"); + } finally { + try { + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + log.debug("Loaded " + modelFromFile.size() + " statements"); + } + + /** + * Move all statements that relate to PermissionSets from the loaded + * model to the filtered model. + */ + private void filterModelFromFile() { + filteredModel = ModelFactory.createDefaultModel(); + + for (Resource r : iterable(modelFromFile.listResourcesWithProperty( + RDF.type, permissionSetType))) { + moveStatementsToFilteredModel(r); + howManyNewPermissionSets++; + } + + log.debug("Filtered " + filteredModel.size() + " statements for " + + howManyNewPermissionSets + " PermissionSets; " + + modelFromFile.size() + " extraneous statements."); + } + + /** + * Move the statements about this PermissionSet from the loaded model to + * the filtered model. + */ + private void moveStatementsToFilteredModel(Resource ps) { + Selector sel = new SimpleSelector(ps, null, (String) null); + for (Statement stmt : iterable(modelFromFile.listStatements(sel))) { + filteredModel.add(stmt); + modelFromFile.remove(stmt); + } + } + + /** + * Complain about any statements that were not moved to the filtered + * model. + */ + private void checkForLeftoverStatements() { + List list = iterable(modelFromFile.listStatements()); + if (list.isEmpty()) { + return; + } + + String message = "The PermissionSets configuration file contained " + + list.size() + + " statements that didn't describe PermissionSets: "; + for (int i = 0; i < Math + .min(list.size(), MAX_STATEMENTS_IN_WARNING); i++) { + Statement stmt = list.get(i); + message += "(" + stmt.asTriple() + ") "; + } + if (list.size() > MAX_STATEMENTS_IN_WARNING) { + message += ", ..."; + } + + ss.warning(listener, message); + } + + private void removeExistingPermissionSetsFromUserAccountsModel() { + userAccountsModel.enterCriticalSection(Lock.WRITE); + try { + for (Resource r : iterable(userAccountsModel + .listResourcesWithProperty(RDF.type, permissionSetType))) { + Selector sel = new SimpleSelector(r, null, (String) null); + StmtIterator stmts = userAccountsModel.listStatements(sel); + userAccountsModel.remove(stmts); + howManyOldPermissionSets++; + } + } finally { + userAccountsModel.leaveCriticalSection(); + } + + log.debug("Deleted " + howManyOldPermissionSets + + " old PermissionSets from the user model."); + } + + private void addNewStatementsToUserAccountsModel() { + userAccountsModel.enterCriticalSection(Lock.WRITE); + try { + userAccountsModel.add(filteredModel); + } finally { + userAccountsModel.leaveCriticalSection(); + } + } + + private String buildInfoMessage() { + String message = "Loaded " + howManyNewPermissionSets + + " PermissionSets: "; + Selector sel = new SimpleSelector(null, RDFS.label, (String) null); + for (Statement stmt : iterable(filteredModel.listStatements(sel))) { + String label = stmt.getObject().asLiteral().getString(); + message += "'" + label + "' "; + } + return message; + } + + private List iterable(ClosableIterator iterator) { + List list = new ArrayList(); + try { + while (iterator.hasNext()) { + list.add(iterator.next()); + } + } finally { + iterator.close(); + } + return list; + } + + } + + // ---------------------------------------------------------------------- + // SmokeTester class + // ---------------------------------------------------------------------- + + private static class SmokeTester { + private ServletContextListener listener; + private final ServletContext ctx; + private final StartupStatus ss; + + private final UserAccountsDao uaDao; + + public SmokeTester(ServletContextListener listener, ServletContext ctx, + StartupStatus ss) { + this.listener = listener; + this.ctx = ctx; + this.ss = ss; + + WebappDaoFactory wadf = (WebappDaoFactory) ctx + .getAttribute("webappDaoFactory"); + if (wadf == null) { + throw new IllegalStateException( + "No webappDaoFactory on the servlet context"); + } + this.uaDao = wadf.getUserAccountsDao(); + } + + public void test() { + checkForPermissionSetsWithoutLabels(); + checkForReferencesToNonexistentPermissionSets(); + checkForReferencesToNonexistentPermissions(); + warnIfNoDefaultPermissionSetsForNewUsers(); + } + + private void checkForPermissionSetsWithoutLabels() { + for (PermissionSet ps : uaDao.getAllPermissionSets()) { + if (ps.getLabel().isEmpty()) { + ss.warning(listener, "This PermissionSet has no label: " + + ps.getUri()); + } + } + } + + private void checkForReferencesToNonexistentPermissionSets() { + for (UserAccount user : uaDao.getAllUserAccounts()) { + for (String psUri : user.getPermissionSetUris()) { + if (uaDao.getPermissionSetByUri(psUri) == null) { + ss.warning(listener, "The user '" + user.getFirstName() + + " " + user.getLastName() + + "' has the PermissionSet '" + psUri + + "', but the PermissionSet doesn't exist."); + } + } + } + } + + private void checkForReferencesToNonexistentPermissions() { + PermissionRegistry registry = PermissionRegistry.getRegistry(ctx); + for (PermissionSet ps : uaDao.getAllPermissionSets()) { + for (String pUri : ps.getPermissionUris()) { + if (!registry.isPermission(pUri)) { + ss.warning(listener, + "The PermissionSet '" + ps.getLabel() + + "' has the Permission '" + pUri + + "', but the Permission " + + "is not found in the registry."); + } + } + } + } + + private void warnIfNoDefaultPermissionSetsForNewUsers() { + for (PermissionSet ps : uaDao.getAllPermissionSets()) { + if (ps.isDefaultForNewUsers()) { + return; + } + } + ss.warning(listener, "No PermissionSet has been declared to be a " + + "Default PermissionSet for new users."); + } + + } + + // ---------------------------------------------------------------------- + // Handy dandy exception. + // ---------------------------------------------------------------------- + + private static class LoaderException extends Exception { + + public LoaderException(String message) { + super(message); + } + + public LoaderException(String message, Throwable cause) { + super(message, cause); + } + } } diff --git a/webapp/web/WEB-INF/resources/permission_config.n3 b/webapp/web/WEB-INF/resources/permission_config.n3 index 485474eab..2664fbe4f 100644 --- a/webapp/web/WEB-INF/resources/permission_config.n3 +++ b/webapp/web/WEB-INF/resources/permission_config.n3 @@ -1,10 +1,27 @@ # $This file is distributed under the terms of the license in /doc/license.txt$ +@prefix rdfs: . @prefix auth: . -@prefix simplePermission: . +@prefix simplePermission: . auth:ADMIN a auth:PermissionSet ; + rdfs:label "Site Admin" ; auth:hasPermission simplePermission:ManageMenus ; . +auth:CURATOR + a auth:PermissionSet ; + rdfs:label "Curator" ; + . + +auth:EDITOR + a auth:PermissionSet ; + rdfs:label "Editor" ; + . + +auth:SELF_EDITOR + a auth:PermissionSet ; + a auth:DefaultPermissionSetForNewUsers ; + rdfs:label "Self Editor" ; + .