Overview

Domain Permission

The Domain Permission defines a behavior or an action on the target resource within specific application domain. In other words, a domain permission define "what" the application can do on "which" domain object.

ACL

Internally Play Auth relies on the concept of an Access Control List. The ACL holds a list of Access Control Entries (ACEs) which are used to make access decisions. Each ACE connects an authority (party or role) with a domain object, and a set of permitted actions. In order to achieve the level of indirection, in the ACL system the authorities and domain objects are represented through SecurityIdentity and ObjectIdentity objects correspondingly. 

 

 

Defining Permissions

The base interface for all permissions is ch.insign.playauth.authz.DomainPermission, but most often you'll use it's more convenient extension, the ch.insign.playauth.authz.DomainPermissionEnum:

import ch.insign.playauth.authz.DomainPermissionEnum;
 
public enum PartyPermission implements DomainPermissionEnum<Party> {
    BROWSE, READ, @Allows("READ") EDIT, ADD, DELETE, IMPERSONATE;
}

 The PartyPermission enum defines permissions that are applicable only within the "Party" domain, this means that only Party's subclasses & their instances are only valid targets. For instance, you cannot grant or revoke PartyPermission.READ with ch.insign.playauth.party.PartyRole as a target because Party and PartyRole are not assignable from each other.

PlayAuth also allows to define a permissions which imply another permissions.  In the example above the PartyPermission.EDIT permission is annotated with @Allows("READ") which tells PlayAuth to also allow READing to those who are allowed EDITing.

The PlayAuthPlugin will scan for such definitions during startup and add them to the PermissionManager, so it is possible to retrieve all defined permissions and build the UI for managing them:

playAuthApi.getPermissionManager().getDefinedPermissions()
    .stream()
    .collect(Collectors.groupingBy(DomainPermission::domain))
    .forEach((domain, permissions) -> {
         System.out.println([" + domain + "]");
         permissions.forEach(p -> System.out.println("- " + playAuthApi.getPermissionManager().getQualifiedName(p)));
    });
 
// Console output:
// [ch.insign.playauth.party.Party]
// - ch.insign.playauth.party.Party.BROWSE
// - ch.insign.playauth.party.Party.READ
// ...
// - ch.insign.playauth.party.Party.IMPERSONATE

It's also easy to add permissions to the existing definition by defining another enum with the same domain type:

// add few more items to the list of existing Party permisions
public enum MyPartyPermission implements DomainPermissionEnum<Party> {
    EXPORT, RESET_PASSWORD;
}

 

Permission Inheritance

import ch.insign.playauth.authz.WithParent;
 
@WithParent("getParentNode")
public class Node {
    private Node parent;
    public Node getParentNode() { return parent; }
}

 TODO.

Granting / Revoking Permissions

The AccessControlManager provides methods to grant permissions on specific domain objects, on the domain objects of a specific type or whole permission domain at once.

// allow "partyOrRole" to READ the "specificBlock" instance
playAuthApi.getAccessControlManager().grantPermission(partyOrRole, CmsBlockPermission.READ, specificBlock);
 
// deny "partyOrRole" to MODIFY all instances of PageBlock
playAuthApi.getAccessControlManager().revokePermission(partyOrRole, CmsBlockPermission.MODIFY, PageBlock.class);
 
// allow "partyOrRole" to IMPERSONATE any other Party
playAuthApi.getAccessControlManager().grantPermission(partyOrRole, PartyPermission.IMPERSONATE);

 

Checking permissions

The AccessControlManager provides isPermitted and requirePermission methods to perform security checks. It is also possible to perform class-wide or even domain-wide permission checks, but most of the time access control check is done with specific domain object.

// returns true if current party is allowed to IMPERSONATE "someParty"
playAuthApi.isPermitted(PartyPermission.IMPERSONATE, someParty);
 
// throws AuthorizationException if current party is NOT allowed to MODIFY the "specificBlock"
playAuthApi.requirePermission(CmsBlockPermission.MODIFY, specificBlock);
 
// returns true if "Admin" role is allowed to IMPERSONATE "someParty"
PartyRole admin = playAuthApi.getPartyRoleManager().findOneByName("Admin");
playAuthApi.getAccessControlManager().isPermitted(admin, PartyPermission.IMPERSONATE, someParty);

 

 

Custom Authorizers

For any complex authorization logic that conains conditions we provide ability to override default authorization process by creating own Authorizer:

@WithAuthorizer(PageAuthorizer.class)
public class Page {
    /* implementation */
}
 
public class PageAuthorizer implements ch.insign.playauth.authz.Authorizer {
    @Override
    public boolean isPermitted(Object authority, DomainPermission<?> permission) {
 
        /* custom authorization logic */
 
        // IMPORTANT! Methods PlayAuth.isPermitted or PlayAuth.getAccessControlManager.isPermitted
        // should not be called from here, because it will cause infinite recursion.
        // The method PlayAuth.getAuthorizer().isPermitted should be used instead.
    }
}
 
PlayAuth.isPermitted(PagePermission.EDIT); // is now delegated to PageAuthorizer.isPermitted

  

Protecting Controllers

PlayAuth.isPermitted and PlayAuth.requirePermission are used to protect any code including controllers, there are also controller specific annotations, but they're limited to certain use cases. Usually the controllers are operating with some domain objects, so you just add permission checks on those domain objects inside controllers:

public Result deleteParty(String id) {
    Optional<Party> maybeParty = partyRepository.findById(id);
    if (maybeParty.isEmpty()) {
        return notFound("Sorry, there is no such Party.");
    }

    Party party = maybeParty.get();
 
    // this check throws AuthorizationException if current party is not authorized to remove 'party',
    // the AuthorizationException is handled by play-auth so that the end user will see "forbidden" error page.
    playAuthApi.requirePermission(PartyPermission.DELETE, party.);
 
    partyRepository.delete(party);
 
    return ok("Rest in peace " + party.getName());
}

 

But sometimes controllers do not interact with domain objects, but display some general stuff, so which permissions to check in such cases? The "clean" solution is to define "virtual domain" — an empty interface with a recognizable name like this:

/** A tagging interface that represents a virtual domain "backend" */
public interface Backend { }

 then define corresponding permissions on the domain:

public enum BackendPermission implements DomainPermisionEnum<Backend> {
    BROWSE;
}

 and use it like usual:

public class BackendController extends Controller {
    public Result dashboard() {
        playAuthApi.requirePermission(BackendPermission.BROWSE);
        ...
    }
}

  

Controller Annotations

There are several annotations to perform authentication checks:

...
import ch.insign.playauth.controllers.actions.RequireAuthentication;
import ch.insign.playauth.controllers.actions.RequireUser;
import ch.insign.playauth.controllers.actions.RequireAnonymous;
 
public class BackendController extends Controller {
 
    @RequireAuthentication
    public Result dashboard() { /* ... */ }
}