Old Release

This documentation relates to an old version of VIVO, version 1.9.x. Looking for another version? See all documentation.

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

The RequestedAction is how the Controller asks the PolicyStack whether an action is authorized. Each policy may:

  • approve the action (AUTHORIZED)
  • reject the action (UNAUTHORIZED)
  • let another policy decide (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:

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.

The RequestedAction class
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;
    }
}

The Controller

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.

The requestedActions() method

This 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.

Overriding the requestedActions method
@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));
}

What happens if the Policy does not authorize the Action?

If the PolicyStack rejects the action, one of two things will happen.

  • If the user is not logged in, they will be sent to the login screen. No explanation is offered, but after they log in, the request is repeated.
  • If the user is logged in, they will be sent to the home page. A message will appear, like this:

Calling 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:

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.

What happens if the Policy does not authorize the Action?

The result is the same as with the requiredActions() method.

For finer control,

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.

if (PolicyHelper.isAuthorizedForActions(vreq, new MyRequestedAction())) {
    showAuthorizedResult(request, response);
} else {
    showUnauthorizedResult(request, response);
}

What happens if the Policy does not authorize the Action?

That's completely up to you.

The Policy

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.

The policy class
/* $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

 

  • No labels