VIVO Documentation
Table of Contents |
---|
Suppose you want to do something more elaborate than just prohibit access to a page. For example, perhaps you want to have some profiles be accessible only to certain people.
This becomes a more interesting task, because all profiles are presented by the same controller. So how do you tell the controller that a person is authorized to view the page for one profile but not for another?
You must create a RequestedAction
that takes parameters, and then have your Policy
use those parameters in its decision.
Another issue is that there are several URLs that will lead to the same profile page. These URLs are equivalent:
Equivalent URLs for the same individual |
---|
http://vivo.mydomain.edu/individual/n4796 |
http://vivo.mydomain.edu/display/n4796 |
http://vivo.mydomain.edu/individual?uri=http%3A%2F%2Fvivo.mydomain.edu%2Findividual%2Fn4796 |
The IndividualController
is also responsible for handling Linked Open Data requests, and again there are a variety of URLs ways to request them. How will you handle all of these URLs that lead to the same page?
The RequestedAction
is how the Controller
asks the PolicyStack
whether an action is authorized. Each policy may:
AUTHORIZED
)UNAUTHORIZED
)(INCONCLUSIVE
)If all policies return INCONCLUSIVE
, the action is rejected.
Most policies are written to check the class of the RequestedAction
, and to ignore everything they don't understand, like this:
Code Block | ||
---|---|---|
| ||
if (!(whatToAuth instanceof DisplayDataPropertyStatement)) { return new BasicPolicyDecision(Authorization.INCONCLUSIVE, "Unrecognized action"); } |
The exception to this is RootUserPolicy, which approves every action if the root user is logged in. So,if you create your own class, its likely that only your policy will approve or reject it.
Something to remember: the Policy
objects do not have access to the current request. So your RequestedAction
must carry all of the information that the Policy
will require to make a decision. In this example, the Policy
needs to know who is logged in, and which profile page they are requesting.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
package edu.cornell.mannlib.vitro.webapp.controller.individual; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; /** * Ask for authorization to display this individual to this user. */ public class DisplayRestrictedIndividualAction extends RequestedAction { private final UserAccount user; private final Individual individual; public DisplayRestrictedIndividualAction(UserAccount user, Individual individual) { this.user = user; this.individual = individual; } public UserAccount getUser() { return user; } public Individual getIndividual() { return individual; } } |
So how does the controller request the action, and what does it do if the action is rejected?
There are a few ways to handle this. If your controller is a sub-class of FreemarkerHttpServlet
, and if you are willing to accept the default behavior, you can use the requestedActions()
method. Otherwise, you can use the FreemarkerHttpServlet.processRequest()
method, or just the doGet()
method.
Remember, the IndividualController
needs to deal with several different types of URLs and types of requests. However, it has a method that analyzes the request for you, and creates an IndividualRequestInfo
object. You can get the information you need from that, as shown in the examples below.
requestedActions()
methodThis method is a shortcut for subclasses of FreemarkerHttpServlet
. Just override this method so it returns an Actions
object. The framework will check to see if the policies approve this requested action. Here is an example.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
@Override protected Actions requiredActions(VitroRequest vreq) { IndividualRequestInfo requestInfo = analyzeTheRequest(vreq); Individual individual = requestInfo.getIndividual(); UserAccount user = LoginStatusBean.getCurrentUser(vreq); return new Actions(new DisplayRestrictedIndividualAction(user, individual)); } |
If the PolicyStack
rejects the action, one of two things will happen.
isAuthorizedToDisplayPage()
If your controller is not a sub-class of FreemarkerHttpServlet
, you can accomplish the same result by calling isAuthorizedToDisplayPage()
. This method takes one or more RequestedAction
objects, and behaves exactly the same as requestedActions()
does in a FreemarkerHttpServlet
.
You must control the code flow yourself, however. If the method returns false
, your code should immediately return. In that case, the framework has already set the HttpServletResponse
to redirect as described above.
Simply the code looks like this:
Code Block | ||
---|---|---|
| ||
public void doGet(HttpServletRequest request, HttpServletResponse response) { if (!isAuthorizedToDisplayPage(request, response, new MyRequestedAction())) { return; } ... |
If you search the VIVO code base, you will find this pattern in several controller classes.
The result is the same as with the requiredActions()
method.
In some cases, the default behavior is not wanted. For example, you may want to have your controller display one thing if the action is approved, but display another thing if the action is rejected. In neither case would you want to forward the user to a different page.
In that case, you can call the isAuthorizedForActions()
method on the PolicyHelper
class.
Code Block |
---|
if (PolicyHelper.isAuthorizedForActions(vreq, new MyRequestedAction())) { showAuthorizedResult(request, response); } else { showUnauthorizedResult(request, response); } |
That's completely up to you.
Let's return to the example with the IndividualController
and the DisplayRestrictedIndividualAction
. What might the policy look like? Here is a rather silly example. In all likelihood, the actual policy would certainly be more elaborate.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */ package edu.cornell.mannlib.vitro.webapp.controller.individual; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; 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.BasicPolicyDecision; import edu.cornell.mannlib.vitro.webapp.auth.policy.ServletPolicyList; 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; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; public class PermitProfilesPolicy implements PolicyIface { private static final Log log = LogFactory .getLog(PermitProfilesPolicy.class); @Override public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, RequestedAction whatToAuth) { if (!(whatToAuth instanceof DisplayRestrictedIndividualAction)) { return inconclusiveDecision("Only interested in displaying profiles"); } DisplayRestrictedIndividualAction action = (DisplayRestrictedIndividualAction) whatToAuth; UserAccount user = action.getUser(); Individual individual = action.getIndividual(); if (user == null) { return inconclusiveDecision("User is not logged in."); } if (individual == null) { return inconclusiveDecision("Not on a profile page."); } return isAuthorized(user, individual); } /** * This is totally bogus. Presumably you would have more sensible criteria. */ private PolicyDecision isAuthorized(UserAccount user, Individual individual) { if (individual.getURI().equals( "http://vivo.mydomain.edu/individual/n4526")) { log.debug("Permit access to " + individual.getLabel()); return authorizedDecision("I'll let anybody can see this guy."); } else { log.debug("Deny access to " + individual.getLabel()); return inconclusiveDecision("Some other policy might approve it, but I won't."); } } /** * An AUTHORIZED decision says "Go ahead. Don't need to ask anyone else". */ private PolicyDecision authorizedDecision(String message) { return new BasicPolicyDecision(Authorization.AUTHORIZED, getClass() .getSimpleName() + ": " + message); } /** * An INCONCLUSIVE decision says "Let someone else decide". */ private PolicyDecision inconclusiveDecision(String message) { return new BasicPolicyDecision(Authorization.INCONCLUSIVE, getClass() .getSimpleName() + ": " + message); } // ---------------------------------------------------------------------- // Setup class // ---------------------------------------------------------------------- public static class Setup implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletPolicyList.addPolicy(sce.getServletContext(), new PermitProfilesPolicy()); } @Override public void contextDestroyed(ServletContextEvent sce) { // Nothing to clean up. } } } |
As in the previous example (Creating a VIVO authorization policy - an example), the policy's Setup
class must be added to startup_listeners.txt