Deploying Access

Developers perform this task.

Overview

When you deploy the Access plugin, Brightspot reviews every request for content as indicated in the following diagram.

../../../_images/access-request-flow.svg

Access request flow

Referring to the previous diagram, Brightspot reviews every request as follows:

  • If the content is not restricted, retrieve and send it to the client.
  • If the content is restricted:
    1. Brightspot checks if the user associated with the request has the required credentials.
    2. If so, Brightspot retrieves the content and returns it to the client.
    3. If not, Brightspot returns substitute content (such as a pay wall) to the client.

Note

Brightspot checks for access control at the content type requested by a client. Suppose a client requests a parent item that is unrestricted, and that item contains a child item that is restricted. The client receives the restricted child item.

Create UI Form for Assigning Access Control

In a typical scenario, Brightspot administrators use a form on the UI to assign access control to various content types (see Configuring Content Substitutions). You provide that form by instantiating a class that implements the interface Access.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package access;

import com.psddev.access.Access;
import com.psddev.cms.db.Content;
import com.psddev.cms.db.ToolUi;
import com.psddev.dari.db.CompoundPredicate;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.Predicate;
import com.psddev.dari.db.PredicateParser;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ContentAccess extends Content implements Access {

    private String accessLabel;

    @ToolUi.DropDown
    @Where("groups = content.article.Article OR groups = content.page.Page")
    private Set<ObjectType> contentTypes;

    public Set<ObjectType> getTypes() {
        if (contentTypes == null) {
            contentTypes = new HashSet<>();
        }
        return contentTypes;
    }

    @Override
    public Predicate itemsPredicate() {
        List<Predicate> predicateList = new ArrayList<>();

        if (!getTypes().isEmpty()) {
            predicateList.add(PredicateParser.Static.parse("_type = ?", getTypes()));
        }

        return new CompoundPredicate(PredicateParser.AND_OPERATOR, predicateList);
    }
}

In the previous snippet—

  • Line 16 declares this class implements the interface Access, which requires overriding the method itemsPredicate.
  • Line 18 provides a label for the access control rule that appears in the UI.
  • Line 21 uses the annotation @Where to determine which content types an administrator can select for access control. (For more information about this annotation, see @Recordable.Where.) If the content types listed in this annotation contain child content types that also require access control, include those child content types as well.
  • Line 22 displays a drop-down list containing the items listed in the @Where annotation.
  • Lines 31–40 implement the method itemsPredicate, which returns a list of conditions (Predicates) that a request must satisfy to be under access control. At run time, Brightspot iterates over all of the Accesses associated with the requested content. For each such Access, Brightspot uses this method to retrieve all of the conditions requiring access control. In this example, the conditions may be of the form WHERE content-type IN (article, author, page).

The following illustration is an example of a class implementing Access in the content edit form.

../../../_images/access-content-edit-form.png

Checking for Access to Requested Content

Checking for access requires two steps:

  • Find the accesses assigned to the requested resource that were established by implementing Access as described in Create UI Form for Assigning Access Control. This step is implementation dependent, and usually involves determining the content type requested from the incoming query string and then checking if the content type is under access control.
  • Determine if the user associated with the request has the required access. This step is also very much implementation dependent: some Brightspot projects have an API to the publisher’s subscription system, and other projects have internal subscription tables.

For an overview of this process, see the diagram Access request flow.

You perform both of these steps by declaring a class that implements the interface AccessProvider.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package access;

import com.psddev.access.Access;
import com.psddev.access.AccessProvider;
import com.psddev.cms.db.PageFilter;
import com.psddev.cms.db.Site;
import com.psddev.dari.db.Predicate;
import com.psddev.dari.db.PredicateParser;

import javax.servlet.http.HttpServletRequest;

public class ContentAccessProvider implements AccessProvider {

    @Override
    public boolean requiresAccess(HttpServletRequest request) {
        Site site = PageFilter.Static.getSite(request);
        Object mainObject = PageFilter.Static.getMainObject(request);

        if (mainObject == null) {
            return false;
        }

        List<SubscriptionPlan> plans = site.getSubscriptionPlans();

        for (SubscriptionPlan plan : plans) {
            Set<Access> accesses = plan.getAccesses();

            for (Access access : accesses) {
                Predicate predicate = access.itemsPredicate();
                if (PredicateParser.Static.evaluate(mainObject, predicate)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public Collection<? extends Access> findAccesses(HttpServletRequest request) {
        Set<Access> accesses = new HashSet<>();

        ExampleUser user = ExampleAuthFilter.getUser(request);

        if (user != null) {
            for (ExampleSubscription subscription : user.getSubscriptions()) {
                accesses.addAll(subscription.getAccesses());
            }
        }

        return accesses;
    }
}

In the previous snippet—

  • Line 12 indicates this class implements AccessProvider, so Brightspot runs this class each time a request arrives for content.
  • Lines 14–36 override the method requiresAccess and return a boolean true if the requested content type is under access control, false otherwise. The code within this method is usually implementation dependent. The following list describes a basic implementation.
    • Lines 16–17 extract the site and content type associated with the incoming request.
    • Line 19–21 is a null check that returns false if there is no content type associated with the request.
    • Line 23 retrieves all the subscription plans associated with the site.
    • Lines 25–34 examine each of the retrieved subscription plans. If any of them contain the requested content type, return true.
    • Line 35 returns false if the requested content type is not included in any subscription plan. As a result, Brightspot retrieves the requested content to the client.
  • Lines 38–51 override the method findAccesses and return a HashSet<Access> containing a set of accesses associated with the user who sent the request. Brightspot runs this method if requiresAccess returns true.
    • Line 42 retrieves the user associated with the incoming request.
    • Lines 45–47 retrieve all of the subscriptions associated with the user.

Brightspot denies access to an item when requiresAccess returns true and one of the following is also true:

  • findAccesses returns an empty set of accesses.
  • findAccesses returns a non-empty set of accesses, none of which match those associated with the requested content.