From ac55760a5a0d54dade05207b69219c0f4cfa4727 Mon Sep 17 00:00:00 2001 From: j2blake Date: Mon, 25 Apr 2011 16:26:58 +0000 Subject: [PATCH] NIHVIVO-2509 Replace InformationResourceEditingPolicy with SelfEditorRelationshipPolicy, which is better structured and handles more special relationships. --- webapp/config/web.xml | 5 - .../InformationResourceEditingPolicy.java | 332 --------------- ...InformationResourceEditingPolicySetup.java | 62 --- .../AbstractRelationshipPolicy.java | 231 +++++++++++ .../SelfEditorRelationshipPolicy.java | 380 ++++++++++++++++++ .../SelfEditorRelationshipPolicyTest.java} | 106 ++--- .../SelfEditorRelationshipPolicyTest.n3} | 0 7 files changed, 668 insertions(+), 448 deletions(-) delete mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicy.java delete mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/InformationResourceEditingPolicySetup.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/AbstractRelationshipPolicy.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicy.java rename webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/{InformationResourceEditingPolicyTest.java => specialrelationships/SelfEditorRelationshipPolicyTest.java} (89%) rename webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/{resources/InformationResourceEditingPolicyTest.n3 => specialrelationships/SelfEditorRelationshipPolicyTest.n3} (100%) diff --git a/webapp/config/web.xml b/webapp/config/web.xml index db805f0df..ab1e91156 100644 --- a/webapp/config/web.xml +++ b/webapp/config/web.xml @@ -149,11 +149,6 @@ --> - - edu.cornell.mannlib.vitro.webapp.auth.identifier.UserToIndIdentifierFactorySetup diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicy.java deleted file mode 100644 index b0478d76a..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicy.java +++ /dev/null @@ -1,332 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.auth.policy; - -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.ServletContext; - -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.Property; -import com.hp.hpl.jena.rdf.model.RDFNode; -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.StmtIterator; -import com.hp.hpl.jena.shared.Lock; - -import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; -import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; -import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AbstractDataPropertyAction; -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AbstractObjectPropertyAction; -import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean.RoleLevel; -import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; - -/** - * Allows a self-editor to edit properties on any InformationResource of which - * he is an author or an editor. - */ -public class InformationResourceEditingPolicy extends BaseSelfEditingPolicy - implements PolicyIface { - private static final Log log = LogFactory - .getLog(InformationResourceEditingPolicy.class); - - private static final String NS_CORE = "http://vivoweb.org/ontology/core#"; - private static final String URI_INFORMATION_RESOURCE_TYPE = NS_CORE - + "InformationResource"; - private static final String URI_EDITOR_PROPERTY = "http://purl.org/ontology/bibo/editor"; - private static final String URI_IN_AUTHORSHIP_PROPERTY = NS_CORE - + "informationResourceInAuthorship"; - private static final String URI_LINKED_AUTHOR_PROPERTY = NS_CORE - + "linkedAuthor"; - private static final String URI_FEATURES_PROPERTY = NS_CORE + "features"; - - private final OntModel model; - - public InformationResourceEditingPolicy(ServletContext ctx, OntModel model) { - super(ctx, RoleLevel.SELF); - this.model = model; - } - - @Override - public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, - RequestedAction whatToAuth) { - if (whoToAuth == null) { - return inconclusiveDecision("whoToAuth was null"); - } - if (whatToAuth == null) { - return inconclusiveDecision("whatToAuth was null"); - } - - List userUris = getUrisOfSelfEditor(whoToAuth); - - if (userUris.isEmpty()) { - return inconclusiveDecision("Not self-editing."); - } - - if (whatToAuth instanceof AbstractObjectPropertyAction) { - return isAuthorizedForObjectPropertyAction(userUris, - (AbstractObjectPropertyAction) whatToAuth); - } - - if (whatToAuth instanceof AbstractDataPropertyAction) { - return isAuthorizedForDataPropertyAction(userUris, - (AbstractDataPropertyAction) whatToAuth); - } - - return inconclusiveDecision("Does not authorize " - + whatToAuth.getClass().getSimpleName() + " actions"); - } - - /** - * The user can edit a data property if it is not restricted and if it is - * about an information resource which he authored or edited, or in which he - * is featured. - */ - private PolicyDecision isAuthorizedForDataPropertyAction( - List userUris, AbstractDataPropertyAction action) { - String subject = action.getSubjectUri(); - String predicate = action.getPredicateUri(); - - if (!canModifyResource(subject)) { - return cantModifyResource(subject); - } - if (!canModifyPredicate(predicate)) { - return cantModifyPredicate(predicate); - } - - if (isInformationResource(subject)) { - if (anyUrisInCommon(userUris, getUrisOfEditors(subject))) { - return authorizedSubjectEditor(); - } - if (anyUrisInCommon(userUris, getUrisOfAuthors(subject))) { - return authorizedSubjectAuthor(); - } - if (anyUrisInCommon(userUris, getUrisOfFeatured(subject))) { - return authorizedSubjectFeatured(); - } - } - - return userNotAuthorizedToStatement(); - } - - /** - * The user can edit an object property if it is not restricted and if it is - * about an information resource which he authored or edited, or in which he - * is featured. - */ - private PolicyDecision isAuthorizedForObjectPropertyAction( - List userUris, AbstractObjectPropertyAction action) { - String subject = action.getUriOfSubject(); - String predicate = action.getUriOfPredicate(); - String object = action.getUriOfObject(); - - if (!canModifyResource(subject)) { - return cantModifyResource(subject); - } - if (!canModifyPredicate(predicate)) { - return cantModifyPredicate(predicate); - } - if (!canModifyResource(object)) { - return cantModifyResource(object); - } - - if (isInformationResource(subject)) { - if (anyUrisInCommon(userUris, getUrisOfEditors(subject))) { - return authorizedSubjectEditor(); - } - if (anyUrisInCommon(userUris, getUrisOfAuthors(subject))) { - return authorizedSubjectAuthor(); - } - if (anyUrisInCommon(userUris, getUrisOfFeatured(subject))) { - return authorizedSubjectFeatured(); - } - } - - if (isInformationResource(object)) { - if (anyUrisInCommon(userUris, getUrisOfEditors(object))) { - return authorizedObjectEditor(); - } - if (anyUrisInCommon(userUris, getUrisOfAuthors(object))) { - return authorizedObjectAuthor(); - } - if (anyUrisInCommon(userUris, getUrisOfFeatured(object))) { - return authorizedObjectFeatured(); - } - } - - return userNotAuthorizedToStatement(); - } - - private boolean isInformationResource(String uri) { - Selector selector = createSelector(uri, VitroVocabulary.RDF_TYPE, - URI_INFORMATION_RESOURCE_TYPE); - - StmtIterator stmts = null; - model.enterCriticalSection(Lock.READ); - try { - stmts = model.listStatements(selector); - if (stmts.hasNext()) { - return true; - } else { - return false; - } - } finally { - if (stmts != null) { - stmts.close(); - } - model.leaveCriticalSection(); - } - } - - private List getUrisOfEditors(String infoResourceUri) { - List list = new ArrayList(); - - Selector selector = createSelector(infoResourceUri, - URI_EDITOR_PROPERTY, null); - - StmtIterator stmts = null; - model.enterCriticalSection(Lock.READ); - try { - stmts = model.listStatements(selector); - while (stmts.hasNext()) { - list.add(stmts.next().getObject().toString()); - } - return list; - } finally { - if (stmts != null) { - stmts.close(); - } - model.leaveCriticalSection(); - } - } - - private List getUrisOfAuthors(String infoResourceUri) { - List list = new ArrayList(); - - Selector selector = createSelector(infoResourceUri, - URI_IN_AUTHORSHIP_PROPERTY, null); - - StmtIterator stmts = null; - - model.enterCriticalSection(Lock.READ); - try { - stmts = model.listStatements(selector); - while (stmts.hasNext()) { - RDFNode objectNode = stmts.next().getObject(); - if (objectNode.isResource()) { - log.debug("found authorship for '" + infoResourceUri - + "': " + objectNode); - list.addAll(getUrisOfAuthors(objectNode.asResource())); - } - } - log.debug("uris of authors for '" + infoResourceUri + "': " + list); - return list; - } finally { - if (stmts != null) { - stmts.close(); - } - model.leaveCriticalSection(); - } - } - - private List getUrisOfFeatured(String infoResourceUri) { - List list = new ArrayList(); - - Selector selector = createSelector(infoResourceUri, - URI_FEATURES_PROPERTY, null); - - StmtIterator stmts = null; - model.enterCriticalSection(Lock.READ); - try { - stmts = model.listStatements(selector); - while (stmts.hasNext()) { - list.add(stmts.next().getObject().toString()); - } - return list; - } finally { - if (stmts != null) { - stmts.close(); - } - model.leaveCriticalSection(); - } - } - - /** Note that we must already be in a critical section! */ - private List getUrisOfAuthors(Resource authorship) { - List list = new ArrayList(); - - Selector selector = createSelector(authorship, - URI_LINKED_AUTHOR_PROPERTY, null); - - StmtIterator stmts = null; - - try { - stmts = model.listStatements(selector); - while (stmts.hasNext()) { - list.add(stmts.next().getObject().toString()); - } - return list; - } finally { - if (stmts != null) { - stmts.close(); - } - } - } - - private Selector createSelector(String subjectUri, String predicateUri, - String objectUri) { - Resource subject = (subjectUri == null) ? null : model - .getResource(subjectUri); - return createSelector(subject, predicateUri, objectUri); - } - - private Selector createSelector(Resource subject, String predicateUri, - String objectUri) { - Property predicate = (predicateUri == null) ? null : model - .getProperty(predicateUri); - RDFNode object = (objectUri == null) ? null : model - .getResource(objectUri); - return new SimpleSelector(subject, predicate, object); - } - - private boolean anyUrisInCommon(List userUris, - List editorsOrAuthors) { - for (String userUri : userUris) { - if (editorsOrAuthors.contains(userUri)) { - return true; - } - } - return false; - } - - private PolicyDecision authorizedSubjectEditor() { - return authorizedDecision("User is editor of the subject of the statement"); - } - - private PolicyDecision authorizedObjectEditor() { - return authorizedDecision("User is editor of the object of the statement"); - } - - private PolicyDecision authorizedSubjectAuthor() { - return authorizedDecision("User is author of the subject of the statement"); - } - - private PolicyDecision authorizedObjectAuthor() { - return authorizedDecision("User is author of the object of the statement"); - } - - private PolicyDecision authorizedSubjectFeatured() { - return authorizedDecision("User is featured in the subject of the statement"); - } - - private PolicyDecision authorizedObjectFeatured() { - return authorizedDecision("User is featured in the object of the statement"); - } -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/InformationResourceEditingPolicySetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/InformationResourceEditingPolicySetup.java deleted file mode 100644 index 85548e7a9..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/InformationResourceEditingPolicySetup.java +++ /dev/null @@ -1,62 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.auth.policy.setup; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.ontology.OntModel; - -import edu.cornell.mannlib.vitro.webapp.auth.policy.InformationResourceEditingPolicy; -import edu.cornell.mannlib.vitro.webapp.auth.policy.ServletPolicyList; -import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup; - -/** - * Set up the InformationResourceEditingPolicy. This is tied to the SelfEditor - * identifier, but has enough of its own logic to merit its own policy class. - */ -public class InformationResourceEditingPolicySetup implements - ServletContextListener { - private static final Log log = LogFactory - .getLog(InformationResourceEditingPolicySetup.class); - - @Override - public void contextInitialized(ServletContextEvent sce) { - ServletContext ctx = sce.getServletContext(); - - if (AbortStartup.isStartupAborted(ctx)) { - return; - } - - try { - log.debug("Setting up InformationResourceEditingPolicy"); - - // need to make a policy and add it to the ServletContext - OntModel model = (OntModel) sce.getServletContext().getAttribute( - "jenaOntModel"); - InformationResourceEditingPolicy irep = new InformationResourceEditingPolicy( - ctx, model); - ServletPolicyList.addPolicy(ctx, irep); - - // don't need an IdentifierFactory if the SelfEditingPolicy is - // providing it. - - log.debug("Finished setting up InformationResourceEditingPolicy: " - + irep); - } catch (Exception e) { - log.error("could not run InformationResourceEditingPolicySetup: " - + e); - AbortStartup.abortStartup(ctx); - throw new RuntimeException(e); - } - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - // Nothing to do. - } -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/AbstractRelationshipPolicy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/AbstractRelationshipPolicy.java new file mode 100644 index 000000000..0fb8e1928 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/AbstractRelationshipPolicy.java @@ -0,0 +1,231 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy.specialrelationships; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; + +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.Property; +import com.hp.hpl.jena.rdf.model.RDFNode; +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.StmtIterator; +import com.hp.hpl.jena.shared.Lock; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.HasAssociatedIndividual; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.Identifier; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.policy.BasicPolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean.RoleLevel; +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; + +/** + * A collection of building-block methods so we can code a policy based on the + * relationship of the object being edited to the identity of the user doing the + * editing. + */ +public abstract class AbstractRelationshipPolicy implements PolicyIface { + private static final Log log = LogFactory + .getLog(AbstractRelationshipPolicy.class); + + protected final ServletContext ctx; + protected final OntModel model; + + public AbstractRelationshipPolicy(ServletContext ctx, OntModel model) { + this.ctx = ctx; + this.model = model; + } + + /** + * Check to see whether we are self-editing, and for which Individuals. + */ + protected List getUrisOfSelfEditor(IdentifierBundle ids) { + List uris = new ArrayList(); + if (ids != null) { + for (Identifier id : ids) { + if (id instanceof HasAssociatedIndividual) { + HasAssociatedIndividual selfEditId = (HasAssociatedIndividual) id; + if (!selfEditId.isBlacklisted()) { + uris.add(selfEditId.getAssociatedIndividualUri()); + } + } + } + } + return uris; + } + + protected boolean canModifyResource(String uri) { + return PropertyRestrictionPolicyHelper.getBean(ctx).canModifyResource( + uri, RoleLevel.SELF); + } + + protected boolean canModifyPredicate(String uri) { + return PropertyRestrictionPolicyHelper.getBean(ctx).canModifyPredicate( + uri, RoleLevel.SELF); + } + + protected boolean isResourceOfType(String resourceUri, String typeUri) { + Selector selector = createSelector(resourceUri, + VitroVocabulary.RDF_TYPE, typeUri); + + StmtIterator stmts = null; + model.enterCriticalSection(Lock.READ); + try { + stmts = model.listStatements(selector); + if (stmts.hasNext()) { + log.debug("resource '" + resourceUri + "' is of type '" + + typeUri + "'"); + return true; + } else { + log.debug("resource '" + resourceUri + "' is not of type '" + + typeUri + "'"); + return false; + } + } finally { + if (stmts != null) { + stmts.close(); + } + model.leaveCriticalSection(); + } + } + + protected List getObjectsOfProperty(String resourceUri, + String propertyUri) { + List list = new ArrayList(); + + Selector selector = createSelector(resourceUri, propertyUri, null); + + StmtIterator stmts = null; + model.enterCriticalSection(Lock.READ); + try { + stmts = model.listStatements(selector); + while (stmts.hasNext()) { + list.add(stmts.next().getObject().toString()); + } + log.debug("Objects of property '" + propertyUri + "' on resource '" + + resourceUri + "': " + list); + return list; + } finally { + if (stmts != null) { + stmts.close(); + } + model.leaveCriticalSection(); + } + } + + protected List getObjectsOfLinkedProperty(String resourceUri, + String linkUri, String propertyUri) { + List list = new ArrayList(); + + Selector selector = createSelector(resourceUri, linkUri, null); + + StmtIterator stmts = null; + + model.enterCriticalSection(Lock.READ); + try { + stmts = model.listStatements(selector); + while (stmts.hasNext()) { + RDFNode objectNode = stmts.next().getObject(); + if (objectNode.isResource()) { + log.debug("found authorship for '" + resourceUri + "': " + + objectNode); + list.addAll(getUrisOfAuthors(objectNode.asResource(), + propertyUri)); + } + } + log.debug("Objects of linked properties '" + linkUri + "' ==> '" + + propertyUri + "' on '" + resourceUri + "': " + list); + return list; + } finally { + if (stmts != null) { + stmts.close(); + } + model.leaveCriticalSection(); + } + } + + /** Note that we must already be in a critical section! */ + private List getUrisOfAuthors(Resource authorship, + String propertyUri) { + List list = new ArrayList(); + + Selector selector = createSelector(authorship, propertyUri, null); + + StmtIterator stmts = null; + try { + stmts = model.listStatements(selector); + while (stmts.hasNext()) { + list.add(stmts.next().getObject().toString()); + } + return list; + } finally { + if (stmts != null) { + stmts.close(); + } + } + } + + protected Selector createSelector(String subjectUri, String predicateUri, + String objectUri) { + Resource subject = (subjectUri == null) ? null : model + .getResource(subjectUri); + return createSelector(subject, predicateUri, objectUri); + } + + protected Selector createSelector(Resource subject, String predicateUri, + String objectUri) { + Property predicate = (predicateUri == null) ? null : model + .getProperty(predicateUri); + RDFNode object = (objectUri == null) ? null : model + .getResource(objectUri); + return new SimpleSelector(subject, predicate, object); + } + + protected boolean anyUrisInCommon(List userUris, + List editorsOrAuthors) { + for (String userUri : userUris) { + if (editorsOrAuthors.contains(userUri)) { + return true; + } + } + return false; + } + + protected PolicyDecision cantModifyResource(String uri) { + return inconclusiveDecision("No access to admin resources; cannot modify " + + uri); + } + + protected PolicyDecision cantModifyPredicate(String uri) { + return inconclusiveDecision("No access to admin predicates; cannot modify " + + uri); + } + + protected PolicyDecision userNotAuthorizedToStatement() { + return inconclusiveDecision("User has no access to this statement."); + } + + /** An INCONCLUSIVE decision with a message like "PolicyClass: message". */ + protected PolicyDecision inconclusiveDecision(String message) { + return new BasicPolicyDecision(Authorization.INCONCLUSIVE, getClass() + .getSimpleName() + ": " + message); + } + + /** An AUTHORIZED decision with a message like "PolicyClass: message". */ + protected PolicyDecision authorizedDecision(String message) { + return new BasicPolicyDecision(Authorization.AUTHORIZED, getClass() + .getSimpleName() + ": " + message); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicy.java new file mode 100644 index 000000000..ba0d984e1 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicy.java @@ -0,0 +1,380 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy.specialrelationships; + +import java.util.List; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ServletPolicyList; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AbstractDataPropertyAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AbstractObjectPropertyAction; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup; + +/** + * Permit self-editors to edit the properties of classes with which they share a + * special relationship. So for example: + * + * A self-editor may edit properties of an InformationResource for which he is + * an author, an editor, or in which he is featured. + * + * A self-editor may edit properties of a Project in which he plays a clinical + * role. + * + * Etc. + * + * NOTE: properties or resources which are restricted by namespace or by access + * setting will still not be editable, even if this special relationship + * applies. + * + * NOTE: This could be further generalized by building a list of authorizing + * relationships, where each relationship may specify a type of object, a + * relating property (or chain of properties), and a text message describing the + * relationship (to be used in the decision). We could go even farther and drive + * this from an XML config file, so site administrators could configure it + * themselves. A great tool for this is the one used to process the Tomcat + * server.xml file, see http://commons.apache.org/digester/ + */ +public class SelfEditorRelationshipPolicy extends AbstractRelationshipPolicy + implements PolicyIface { + private static final Log log = LogFactory + .getLog(SelfEditorRelationshipPolicy.class); + + private static final String NS_CORE = "http://vivoweb.org/ontology/core#"; + private static final String URI_INFORMATION_RESOURCE_TYPE = NS_CORE + + "InformationResource"; + private static final String URI_EDITOR_PROPERTY = "http://purl.org/ontology/bibo/editor"; + private static final String URI_FEATURES_PROPERTY = NS_CORE + "features"; + private static final String URI_IN_AUTHORSHIP_PROPERTY = NS_CORE + + "informationResourceInAuthorship"; + private static final String URI_LINKED_AUTHOR_PROPERTY = NS_CORE + + "linkedAuthor"; + + private static final String URI_GRANT_TYPE = NS_CORE + "Grant"; + private static final String URI_RELATED_ROLE_PROPERTY = NS_CORE + + "relatedRole"; + private static final String URI_PRINCIPAL_INVESTIGATOR_OF_PROPERTY = NS_CORE + + "principalInvestigatorRoleOf"; + private static final String URI_CO_PRINCIPAL_INVESTIGATOR_OF_PROPERTY = NS_CORE + + "co-PrincipalInvestigatorRoleOf"; + + private static final String URI_PROJECT_TYPE = NS_CORE + "Project"; + private static final String URI_SERVICE_TYPE = NS_CORE + "Service"; + private static final String URI_CLINICAL_ROLE_OF_PROPERTY = NS_CORE + + "clinicalRoleOf"; + + private static final String URI_PRESENTATION_TYPE = NS_CORE + + "Presentation"; + private static final String URI_PRESENTER_ROLE_OF_PROPERTY = NS_CORE + + "presenterRoleOf"; + + private static final String URI_COURSE_TYPE = NS_CORE + "Course"; + private static final String URI_TEACHER_ROLE_OF_PROPERTY = NS_CORE + + "teacherRoleOf"; + + private static final String URI_ADVISING_RELATIONSHIP_TYPE = NS_CORE + + "AdvisingRelationship"; + private static final String URI_ADVISOR_PROPERTY = NS_CORE + "advisor"; + + public SelfEditorRelationshipPolicy(ServletContext ctx, OntModel model) { + super(ctx, model); + } + + @Override + public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, + RequestedAction whatToAuth) { + PolicyDecision decision = null; + + if (whatToAuth == null) { + decision = inconclusiveDecision("whatToAuth was null"); + } else if (whatToAuth instanceof AbstractDataPropertyAction) { + decision = isAuthorized(whoToAuth, + distill((AbstractDataPropertyAction) whatToAuth)); + } else if (whatToAuth instanceof AbstractObjectPropertyAction) { + decision = isAuthorized(whoToAuth, + distill((AbstractObjectPropertyAction) whatToAuth)); + } else { + decision = inconclusiveDecision("Does not authorize " + + whatToAuth.getClass().getSimpleName() + " actions"); + } + + if (decision == null) { + return userNotAuthorizedToStatement(); + } else { + return decision; + } + } + + private DistilledAction distill(AbstractDataPropertyAction action) { + return new DistilledAction(action.getPredicateUri(), + action.getSubjectUri()); + } + + private DistilledAction distill(AbstractObjectPropertyAction action) { + return new DistilledAction(action.uriOfPredicate, action.uriOfSubject, + action.uriOfObject); + } + + private PolicyDecision isAuthorized(IdentifierBundle ids, + DistilledAction action) { + List userUris = getUrisOfSelfEditor(ids); + + if (userUris.isEmpty()) { + return inconclusiveDecision("Not self-editing."); + } + + if (!canModifyPredicate(action.predicateUri)) { + return cantModifyPredicate(action.predicateUri); + } + + for (String resourceUri : action.resourceUris) { + if (!canModifyResource(resourceUri)) { + return cantModifyResource(resourceUri); + } + } + + for (String resourceUri : action.resourceUris) { + if (isInformationResource(resourceUri)) { + if (anyUrisInCommon(userUris, getUrisOfEditors(resourceUri))) { + return authorizedEditor(resourceUri); + } + if (anyUrisInCommon(userUris, getUrisOfAuthors(resourceUri))) { + return authorizedAuthor(resourceUri); + } + if (anyUrisInCommon(userUris, getUrisOfFeatured(resourceUri))) { + return authorizedFeatured(resourceUri); + } + } + if (isGrant(resourceUri)) { + if (anyUrisInCommon(userUris, + getUrisOfPrincipalInvestigators(resourceUri))) { + return authorizedPI(resourceUri); + } + if (anyUrisInCommon(userUris, + getUrisOfCoPrincipalInvestigators(resourceUri))) { + return authorizedCoPI(resourceUri); + } + } + if (isProject(resourceUri) || isService(resourceUri)) { + if (anyUrisInCommon(userUris, + getUrisOfClinicalAgents(resourceUri))) { + return authorizedClinicalAgent(resourceUri); + } + } + if (isPresentation(resourceUri)) { + if (anyUrisInCommon(userUris, getUrisOfPresenters(resourceUri))) { + return authorizedPresenter(resourceUri); + } + } + if (isCourse(resourceUri)) { + if (anyUrisInCommon(userUris, getUrisOfTeachers(resourceUri))) { + return authorizedTeacher(resourceUri); + } + } + if (isAdvisingRelationship(resourceUri)) { + if (anyUrisInCommon(userUris, getUrisOfAdvisors(resourceUri))) { + return authorizedAdvisor(resourceUri); + } + } + } + + return userNotAuthorizedToStatement(); + } + + // ---------------------------------------------------------------------- + // methods for InformationResource + // ---------------------------------------------------------------------- + + private boolean isInformationResource(String resourceUri) { + return isResourceOfType(resourceUri, URI_INFORMATION_RESOURCE_TYPE); + } + + private List getUrisOfEditors(String resourceUri) { + return getObjectsOfProperty(resourceUri, URI_EDITOR_PROPERTY); + } + + private List getUrisOfFeatured(String resourceUri) { + return getObjectsOfProperty(resourceUri, URI_FEATURES_PROPERTY); + } + + private List getUrisOfAuthors(String resourceUri) { + return getObjectsOfLinkedProperty(resourceUri, + URI_IN_AUTHORSHIP_PROPERTY, URI_LINKED_AUTHOR_PROPERTY); + } + + private PolicyDecision authorizedEditor(String uri) { + return authorizedDecision("User is an editor of " + uri); + } + + private PolicyDecision authorizedAuthor(String uri) { + return authorizedDecision("User is author of " + uri); + } + + private PolicyDecision authorizedFeatured(String uri) { + return authorizedDecision("User is featured in " + uri); + } + + // ---------------------------------------------------------------------- + // methods for Grant + // ---------------------------------------------------------------------- + + private boolean isGrant(String resourceUri) { + return isResourceOfType(resourceUri, URI_GRANT_TYPE); + } + + private List getUrisOfPrincipalInvestigators(String resourceUri) { + return getObjectsOfLinkedProperty(resourceUri, + URI_RELATED_ROLE_PROPERTY, + URI_PRINCIPAL_INVESTIGATOR_OF_PROPERTY); + } + + private List getUrisOfCoPrincipalInvestigators(String resourceUri) { + return getObjectsOfLinkedProperty(resourceUri, + URI_RELATED_ROLE_PROPERTY, + URI_CO_PRINCIPAL_INVESTIGATOR_OF_PROPERTY); + } + + private PolicyDecision authorizedPI(String resourceUri) { + return authorizedDecision("User is Principal Investigator of " + + resourceUri); + } + + private PolicyDecision authorizedCoPI(String resourceUri) { + return authorizedDecision("User is Co-Principal Investigator of " + + resourceUri); + } + + // ---------------------------------------------------------------------- + // methods for Project or Service + // ---------------------------------------------------------------------- + + private boolean isProject(String resourceUri) { + return isResourceOfType(resourceUri, URI_PROJECT_TYPE); + } + + private boolean isService(String resourceUri) { + return isResourceOfType(resourceUri, URI_SERVICE_TYPE); + } + + private List getUrisOfClinicalAgents(String resourceUri) { + return getObjectsOfLinkedProperty(resourceUri, + URI_RELATED_ROLE_PROPERTY, URI_CLINICAL_ROLE_OF_PROPERTY); + } + + private PolicyDecision authorizedClinicalAgent(String resourceUri) { + return authorizedDecision("User has a Clinical Role on " + resourceUri); + } + + // ---------------------------------------------------------------------- + // methods for Presentation + // ---------------------------------------------------------------------- + + private boolean isPresentation(String resourceUri) { + return isResourceOfType(resourceUri, URI_PRESENTATION_TYPE); + } + + private List getUrisOfPresenters(String resourceUri) { + return getObjectsOfLinkedProperty(resourceUri, + URI_RELATED_ROLE_PROPERTY, URI_PRESENTER_ROLE_OF_PROPERTY); + } + + private PolicyDecision authorizedPresenter(String resourceUri) { + return authorizedDecision("User is a Presenter of " + resourceUri); + } + + // ---------------------------------------------------------------------- + // methods for Course + // ---------------------------------------------------------------------- + + private boolean isCourse(String resourceUri) { + return isResourceOfType(resourceUri, URI_COURSE_TYPE); + } + + private List getUrisOfTeachers(String resourceUri) { + return getObjectsOfLinkedProperty(resourceUri, + URI_RELATED_ROLE_PROPERTY, URI_TEACHER_ROLE_OF_PROPERTY); + } + + private PolicyDecision authorizedTeacher(String resourceUri) { + return authorizedDecision("User is a Teacher of " + resourceUri); + } + + // ---------------------------------------------------------------------- + // methods for AdvisingRelationship + // ---------------------------------------------------------------------- + + private boolean isAdvisingRelationship(String resourceUri) { + return isResourceOfType(resourceUri, URI_ADVISING_RELATIONSHIP_TYPE); + } + + private List getUrisOfAdvisors(String resourceUri) { + return getObjectsOfProperty(resourceUri, URI_ADVISOR_PROPERTY); + } + + private PolicyDecision authorizedAdvisor(String resourceUri) { + return authorizedDecision("User is an Advisor of " + resourceUri); + } + + // ---------------------------------------------------------------------- + // helper classes + // ---------------------------------------------------------------------- + + /** + * This allows us to treat data properties and object properties the same. + * It's just that object properties have more resourceUris. + */ + static class DistilledAction { + final String[] resourceUris; + final String predicateUri; + + public DistilledAction(String predicateUri, String... resourceUris) { + this.resourceUris = resourceUris; + this.predicateUri = predicateUri; + } + } + + /** + * When the system starts up, install the policy. This class must be a + * listener in web.xml + * + * The CommonIdentifierBundleFactory already creates the IDs we need. + */ + public static class Setup implements ServletContextListener { + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext ctx = sce.getServletContext(); + + if (AbortStartup.isStartupAborted(ctx)) { + return; + } + + try { + OntModel ontModel = (OntModel) sce.getServletContext() + .getAttribute("jenaOntModel"); + + ServletPolicyList.addPolicy(ctx, + new SelfEditorRelationshipPolicy(ctx, ontModel)); + } catch (Exception e) { + log.error("could not run " + this.getClass().getSimpleName() + + ": " + e); + AbortStartup.abortStartup(ctx); + throw new RuntimeException(e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { /* nothing */ + } + } +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicyTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicyTest.java similarity index 89% rename from webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicyTest.java rename to webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicyTest.java index 5ffeb6615..368c25d3f 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/InformationResourceEditingPolicyTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicyTest.java @@ -1,6 +1,6 @@ /* $This file is distributed under the terms of the license in /doc/license.txt$ */ -package edu.cornell.mannlib.vitro.webapp.auth.policy; +package edu.cornell.mannlib.vitro.webapp.auth.policy.specialrelationships; import static edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization.AUTHORIZED; import static edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization.INCONCLUSIVE; @@ -28,9 +28,8 @@ import com.hp.hpl.jena.rdf.model.StmtIterator; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import edu.cornell.mannlib.vitro.webapp.auth.identifier.ArrayIdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.HasAssociatedIndividual; import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; -import edu.cornell.mannlib.vitro.webapp.auth.identifier.SelfEditingIdentifierFactory; -import edu.cornell.mannlib.vitro.webapp.auth.identifier.SelfEditingIdentifierFactory.SelfEditing; import edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper; import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; @@ -39,15 +38,19 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAct import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AddDataPropStmt; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AddObjectPropStmt; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.resource.AddResource; -import edu.cornell.mannlib.vitro.webapp.beans.IndividualImpl; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; /** - * TODO + * Check the relationships in the SelfEditorRelationshipPolicy. + * + * This only checks the relationships that deal with InformationResources. + * Testing the others seems too redundant. If we generalize this to use + * configurable relationships, then we'll be able to make more general tests as + * well. */ -public class InformationResourceEditingPolicyTest extends AbstractTestClass { +public class SelfEditorRelationshipPolicyTest extends AbstractTestClass { private static final Log log = LogFactory - .getLog(InformationResourceEditingPolicyTest.class); + .getLog(SelfEditorRelationshipPolicyTest.class); /** Can edit properties or resources in this namespace. */ private static final String NS_PERMITTED = "http://vivo.mydomain.edu/individual/"; @@ -71,7 +74,8 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { /** * Where the model statements are stored for this test. */ - private static final String N3_DATA_FILENAME = "resources/InformationResourceEditingPolicyTest.n3"; + private static final String N3_DATA_FILENAME = "SelfEditorRelationship" + + "PolicyTest.n3"; /** * These URIs must match the data in the N3 file. @@ -96,7 +100,7 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { @BeforeClass public static void setupModel() throws IOException { - InputStream stream = InformationResourceEditingPolicyTest.class + InputStream stream = SelfEditorRelationshipPolicyTest.class .getResourceAsStream(N3_DATA_FILENAME); Model model = ModelFactory.createDefaultModel(); model.read(stream, null, "N3"); @@ -108,7 +112,7 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { dumpModel(); } - private InformationResourceEditingPolicy policy; + private SelfEditorRelationshipPolicy policy; private RequestedAction action; @Before @@ -118,7 +122,7 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { .getInstance(new String[] { NS_RESTRICTED }); PropertyRestrictionPolicyHelper.setBean(ctx, prph); - policy = new InformationResourceEditingPolicy(ctx, ontModel); + policy = new SelfEditorRelationshipPolicy(ctx, ontModel); } private IdentifierBundle idNobody; @@ -148,7 +152,7 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { } // ---------------------------------------------------------------------- - // the tests + // boilerplate tests // ---------------------------------------------------------------------- @Test @@ -189,12 +193,30 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { } @Test - public void dataPropSubjectIsNotInfoResource() { - action = new AddDataPropStmt(URI_PERMITTED_RESOURCE, - URI_PERMITTED_PREDICATE, "junk", null, null); + public void objectPropSubjectIsRestricted() { + action = new AddObjectPropStmt(URI_RESTRICTED_RESOURCE, + URI_PERMITTED_PREDICATE, URI_JOE_EDITED_IT); assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); } + @Test + public void objectPropPredicateIsRestricted() { + action = new AddObjectPropStmt(URI_PERMITTED_RESOURCE, + URI_RESTRICTED_PREDICATE, URI_JOE_EDITED_IT); + assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); + } + + @Test + public void objectPropObjectIsRestricted() { + action = new AddObjectPropStmt(URI_JOE_EDITED_IT, + URI_PERMITTED_PREDICATE, URI_RESTRICTED_RESOURCE); + assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); + } + + // ---------------------------------------------------------------------- + // InformationResource tests + // ---------------------------------------------------------------------- + @Test public void dataPropSubjectIsInfoResourceButNobodyIsSelfEditing() { action = new AddDataPropStmt(URI_JOE_WROTE_IT, URI_PERMITTED_PREDICATE, @@ -255,34 +277,6 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { assertDecision(AUTHORIZED, policy.isAuthorized(idBozoAndJoe, action)); } - @Test - public void objectPropSubjectIsRestricted() { - action = new AddObjectPropStmt(URI_RESTRICTED_RESOURCE, - URI_PERMITTED_PREDICATE, URI_JOE_EDITED_IT); - assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); - } - - @Test - public void objectPropPredicateIsRestricted() { - action = new AddObjectPropStmt(URI_PERMITTED_RESOURCE, - URI_RESTRICTED_PREDICATE, URI_JOE_EDITED_IT); - assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); - } - - @Test - public void objectPropObjectIsRestricted() { - action = new AddObjectPropStmt(URI_JOE_EDITED_IT, - URI_PERMITTED_PREDICATE, URI_RESTRICTED_RESOURCE); - assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); - } - - @Test - public void objectPropNeitherSubjectOrObjectIsInfoResource() { - action = new AddObjectPropStmt(URI_PERMITTED_RESOURCE, - URI_PERMITTED_PREDICATE, URI_PERMITTED_RESOURCE); - assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); - } - @Test public void objectPropSubjectIsInfoResourceButNobodyIsSelfEditing() { action = new AddObjectPropStmt(URI_JOE_EDITED_IT, @@ -403,16 +397,30 @@ public class InformationResourceEditingPolicyTest extends AbstractTestClass { assertDecision(AUTHORIZED, policy.isAuthorized(idBozoAndJoe, action)); } + // ---------------------------------------------------------------------- + // Other tests + // ---------------------------------------------------------------------- + + @Test + public void dataPropSubjectIsNotInfoResource() { + action = new AddDataPropStmt(URI_PERMITTED_RESOURCE, + URI_PERMITTED_PREDICATE, "junk", null, null); + assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); + } + + @Test + public void objectPropNeitherSubjectOrObjectIsInfoResource() { + action = new AddObjectPropStmt(URI_PERMITTED_RESOURCE, + URI_PERMITTED_PREDICATE, URI_PERMITTED_RESOURCE); + assertDecision(INCONCLUSIVE, policy.isAuthorized(idJoe, action)); + } + // ---------------------------------------------------------------------- // helper methods // ---------------------------------------------------------------------- - private SelfEditing makeSelfEditingId(String uri) { - IndividualImpl ind = new IndividualImpl(); - ind.setURI(uri); - SelfEditing selfEditing = new SelfEditing(ind, - SelfEditingIdentifierFactory.NOT_BLACKLISTED); - return selfEditing; + private HasAssociatedIndividual makeSelfEditingId(String uri) { + return new HasAssociatedIndividual(uri, null); } private void assertDecision(Authorization expected, PolicyDecision decision) { diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/resources/InformationResourceEditingPolicyTest.n3 b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicyTest.n3 similarity index 100% rename from webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/resources/InformationResourceEditingPolicyTest.n3 rename to webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/specialrelationships/SelfEditorRelationshipPolicyTest.n3