diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java index e85c33d6d..40e7c5804 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java @@ -28,6 +28,7 @@ import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper.RequiresAuthori 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.auth.requestedAction.Actions; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; /** @@ -37,6 +38,25 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAct public class PolicyHelper { private static final Log log = LogFactory.getLog(PolicyHelper.class); + /** + * Are the actions that this servlet requires authorized for the current + * user by the current policies? + */ + public static boolean isAuthorizedForActions(HttpServletRequest req, + Actions actions) { + PolicyIface policy = ServletPolicyList.getPolicies(req); + IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(req); + return Actions.notNull(actions).isAuthorized(policy, ids); + } + + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // Obsolete ???????? + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + /** * A subclass of VitroHttpServlet may be annotated to say what actions * should be checked for authorization before permitting the user to view @@ -73,18 +93,6 @@ public class PolicyHelper { Or[] or() default @Or(); } - /** - * Does this servlet require authorization? - */ - public static boolean isServletRestricted(HttpServlet servlet) { - Class servletClass = servlet.getClass(); - try { - return !ActionClauses.forServletClass(servletClass).isEmpty(); - } catch (PolicyHelperException e) { - return true; - } - } - /** * Are the actions that this servlet requires authorized for the current * user by the current policies? @@ -108,20 +116,6 @@ public class PolicyHelper { } } - /** - * Are these action classes authorized for the current user by the current - * policies? - */ - public static boolean isAuthorizedForActions(HttpServletRequest req, - Collection> actionClasses) { - try { - return isAuthorizedForActionClauses(req, new ActionClauses( - actionClasses)); - } catch (PolicyHelperException e) { - return false; - } - } - /** * Is this action class authorized for the current user by the current * policies? diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java new file mode 100644 index 000000000..79dc01271 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java @@ -0,0 +1,114 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +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.auth.requestedAction.ifaces.RequestedAction; + +/** + * An immutable list of OR and AND relationships for the required + * authorizations. A group of AND relationships is a "clause", and the list of + * clauses are in an OR relationship. + * + * Authorization is successful if ALL of the actions in ANY of the clauses are + * authorized, or if there are NO clauses. + */ +public class Actions { + private static final Log log = LogFactory.getLog(Actions.class); + + public static Actions notNull(Actions actions) { + return (actions == null) ? new Actions() : actions; + } + + private final List> clauseList; + + public Actions(RequestedAction... actions) { + this(Arrays.asList(actions)); + } + + public Actions(Collection actions) { + this(Collections.> emptyList(), actions); + } + + private Actions(List> oldList, + Collection newActions) { + List> newList = new ArrayList>(); + newList.addAll(oldList); + + Set newActionSet = new HashSet( + newActions); + if (!newActionSet.isEmpty()) { + newList.add(Collections.unmodifiableSet(newActionSet)); + } + this.clauseList = Collections.unmodifiableList(newList); + } + + public Actions or(RequestedAction... newActions) { + return or(Arrays.asList(newActions)); + } + + public Actions or(Collection newActions) { + return new Actions(this.clauseList, newActions); + } + + public boolean isEmpty() { + for (Set clause : clauseList) { + if (!clause.isEmpty()) { + return false; + } + } + return true; + } + + /** No clauses means everything is authorized */ + public boolean isAuthorized(PolicyIface policy, IdentifierBundle ids) { + return clauseList.isEmpty() || isAuthorizedForClauseList(policy, ids); + } + + /** Any entire clause is good enough. */ + private boolean isAuthorizedForClauseList(PolicyIface policy, + IdentifierBundle ids) { + for (Set clause : clauseList) { + if (isAuthorizedForClause(policy, ids, clause)) { + return true; + } + } + return false; + } + + /** All actions in a clause must be authorized. */ + private static boolean isAuthorizedForClause(PolicyIface policy, + IdentifierBundle ids, Set clause) { + for (RequestedAction action : clause) { + if (!isAuthorizedForAction(policy, ids, action)) { + log.debug("not authorized"); + return false; + } + } + return true; + } + + /** Is this action authorized? */ + private static boolean isAuthorizedForAction(PolicyIface policy, + IdentifierBundle ids, RequestedAction action) { + PolicyDecision decision = policy.isAuthorized(ids, action); + log.debug("decision for '" + action.getClass().getName() + "' was: " + + decision); + return (decision != null) + && (decision.getAuthorized() == Authorization.AUTHORIZED); + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java index c62ab7f4b..a8e4e349c 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vedit.beans.LoginStatusBean; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage; import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector; @@ -40,15 +41,9 @@ public class VitroHttpServlet extends HttpServlet { public final static String HTML_MIMETYPE = "text/html"; public final static String RDFXML_MIMETYPE = "application/rdf+xml"; - public final static String N3_MIMETYPE = "text/n3"; // unofficial and - // unregistered - public final static String TTL_MIMETYPE = "text/turtle"; // unofficial and - // unregistered + public final static String N3_MIMETYPE = "text/n3"; // unofficial and unregistered + public final static String TTL_MIMETYPE = "text/turtle"; // unofficial and unregistered - /** - * Check that any required authorizations are satisfied before processing - * the request. - */ @Override public final void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException { @@ -61,22 +56,6 @@ public class VitroHttpServlet extends HttpServlet { dumpRequestHeaders(hreq); } - // Record restricted pages so we won't return to them on logout - if (PolicyHelper.isServletRestricted(this)) { - LogoutRedirector.recordRestrictedPageUri(hreq); - } - - // If the user isn't authorized for this servlet, don't show it. - if (!PolicyHelper.isAuthorizedForServlet(hreq, this)) { - if (LoginStatusBean.getBean(hreq).isLoggedIn()) { - redirectToInsufficientAuthorizationPage(hreq, hresp); - return; - } else { - redirectToLoginPage(hreq, hresp); - return; - } - } - // check to see if VitroRequestPrep filter was run if (hreq.getAttribute("appBean") == null || hreq.getAttribute("webappDaoFactory") == null) { @@ -114,6 +93,36 @@ public class VitroHttpServlet extends HttpServlet { doGet(request, response); } + /** + * Don't display a page that the user isn't authorized to see. + * + * @param actions + * the RequestedActions that need to be authorized. + */ + protected boolean isAuthorizedToDisplayPage(HttpServletRequest request, + HttpServletResponse response, Actions actions) { + // Record restricted pages so we won't return to them on logout + LogoutRedirector.recordRestrictedPageUri(request); + + if (PolicyHelper.isAuthorizedForActions(request, actions)) { + log.debug("Servlet '" + this.getClass().getSimpleName() + + "' is authorized for actions: " + actions); + return true; + } + + log.debug("Servlet '" + this.getClass().getSimpleName() + + "' is not authorized for actions: " + actions); + + LoginStatusBean statusBean = LoginStatusBean.getBean(request); + if (statusBean.isLoggedIn()) { + redirectToInsufficientAuthorizationPage(request, response); + return false; + } else { + redirectToLoginPage(request, response); + return false; + } + } + // ---------------------------------------------------------------------- // static utility methods for all Vitro servlets // ---------------------------------------------------------------------- diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java index 34cba764f..95218443b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java @@ -16,6 +16,7 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vedit.beans.LoginStatusBean; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; @@ -60,7 +61,8 @@ public class RequiresAuthorizationFor extends BodyTagSupport { if (classes == null) { return false; } - return PolicyHelper.isAuthorizedForActions(getRequest(), classes); + Set actionSet = getInstancesFromClasses(classes); + return PolicyHelper.isAuthorizedForActions(getRequest(), new Actions(actionSet)); } /** @@ -134,7 +136,7 @@ public class RequiresAuthorizationFor extends BodyTagSupport { return SKIP_PAGE; } - private int redirectToLoginPage() throws JspException { + private int redirectToLoginPage() { VitroHttpServlet.redirectToLoginPage(getRequest(), getResponse()); return SKIP_PAGE; } diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelperTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelperTest.java index d068de151..8a00fceeb 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelperTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelperTest.java @@ -22,6 +22,7 @@ import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper.RequiresAuthori 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.auth.requestedAction.Actions; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; @@ -49,6 +50,73 @@ public class PolicyHelperTest extends AbstractTestClass { req.setSession(session); } + @Test + public void authorizedForActionsNull() { + createPolicy(); + assertEquals("null actions", true, + PolicyHelper.isAuthorizedForActions(req, null)); + } + + @Test + public void authorizedForActionsEmpty() { + createPolicy(); + assertEquals("empty actions", true, + PolicyHelper.isAuthorizedForActions(req, new Actions())); + } + + @Test + public void authorizedForActionsOneClausePass() { + createPolicy(new Action1(), new Action2()); + assertEquals("one clause pass", true, + PolicyHelper.isAuthorizedForActions(req, new Actions( + new Action1(), new Action2()))); + } + + @Test + public void authorizedForActionsOneClauseFail() { + createPolicy(new Action2()); + assertEquals("one clause fail", false, + PolicyHelper.isAuthorizedForActions(req, new Actions( + new Action1(), new Action2()))); + } + + @Test + public void authorizedForActionsMultipleClausesPass() { + createPolicy(new Action3()); + assertEquals("multiple clauses pass", true, + PolicyHelper.isAuthorizedForActions(req, new Actions( + new Action1(), new Action2()).or(new Action3()))); + } + + @Test + public void authorizedForActionsMultipleClausesFail() { + createPolicy(new Action1()); + assertEquals("multiple clauses fail", false, + PolicyHelper.isAuthorizedForActions(req, new Actions( + new Action1(), new Action2()).or(new Action3()))); + } + + /** + *
+	 * actions is null, 
+	 * actions is empty
+	 * actions has one clause with multiple actions
+	 * 	   all pass
+	 *     some pass
+	 * action has multiple clauses
+	 *     one passes
+	 *     none pass (but partial passes)
+	 * 
+ */ + + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // Obsolete??? + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + @Test public void noAnnotation() { createPolicy();