Package ch.tocco.nice2.security.api


package ch.tocco.nice2.security.api
The APIs for the security sub-system.

The Policy

The policy can be thought of as a table of security rules. Each row denotes one rule. An example:
 | Selector                  | Action       | Permission              | Subject                   | Condition                |
 |---------------------------|--------------|-------------------------|---------------------------|--------------------------|
 | `entityManager(MyEntity)` | `grant`      | `create`                | `someGroup, anotherGroup` |                          |
 | `entityManager(MyEntity)` | `deny`       | `create`                | `group3`                  |                          |
 | `entity(MyEntity)`        | `grant`      | `access(read|write, *)` |                           | `principal.key == owner` |
 | `entity(MyEntity)`        | `deny`       | `access(write, *)`      | `anonymous`               |                          |
 
For each secured action, this table will be processed top-down, resulting in a conclusion: deny or grant. It first assumes deny, then changes to the specified action for each rule where all conditions are met. A rule may be declared final, in which case processing stops if that rule applies, further rules will be ignored. The selector defines the general type of the object affected by the action. The permission the exact permission, that's asked for. The currently logged in user is the principal, and finally, there may be some generic condition.

Table Reduction

Because the full table for all possible principals and all possible objects may get very big for a full-scale application, this table will be reduced whenever possible. There are two points, where some important facts get known that allow to filter out rules that won't apply anyway:
  *  After login, the exact principal is known. At this point, a new policy will be
     generated for that user that doesn't contain any rules anymore that don't affect
     that principal.

  *  When a guard is needed, the exact object is known. At this point, all rules that
     don't affect this object will be filtered out using the selector.
 
Therefore, in practice, the policy for a concrete object will finally be relatively compact.

The Access Control Language (ACL)

The define the policy, a language called "*ACL*" is introduced. The top-level structural element of this language is the section. A section defines the selector for the following rules, until a new section is started. The section header looks like this:
 entityManager(MyEntity):
     // the rules
 
So, each rule starts with the keyword grant or deny, optionally followed by a list of permissions, optionally followed by the keyword to and a list of subjects (role name or '&' + principal name), optionally followed by the keyword if and a condition (or unless and a condition, which is a shortcut for if not (...)). It is finally terminated by a semicolon. Optionally, a rule may end with and stop, which declares a rule final. If a final rule applies, processing of rules stops at this point. This is the table above written as ACL:
 entityManager(myEntity):
   grant create
     to someGroup, anotherGroup;
   deny create
     to group3;

 entity(myEntity):
   grant access(read|write, *)
     if principal.key == owner;
   deny access(write, *)
     to anonymous;
 

Keywords

The following words are ACL keywords: grant, deny, include, to, if, unless, principal, null, true, false, or, and, not, role. Also, the keyword permission is currently reserved, but unused.

Selectors and Permissions

Selectors and permissions are written very similarily: identifier [(arguments)]. The identifier is the one as contributed to the security system:

 @Bean
 public SecurityDomainContribution infoboxSecurityDomainContribution(MyDomain domain) {
     SecurityDomainContribution contribution = new SecurityDomainContribution();
     contribution.setName("myDomain");
     contribution.setImplementation(domain);
     return contribution;
 }

 @Bean
 public PermissionContribution infoboxDisplayPermission() {
     PermissionContribution contribution = new PermissionContribution();
     contribution.setDomain("myDomain");
     contribution.setName("myPermission");
     contribution.setId("myPermission");
     contribution.setPermissionClass(my.pkg.MyPermission.class);
     return contribution;
 }
 
This defines the domain myDomain as provided by the service MyDomain, and assigns the permission myPermission imlemented by the class my.pkg.MyPermission to this domain. The argument list will be mapped to method signatures for domains and constructors for permissions. See the documentation of ch.tocco.nice2.security.spi.SecurityDomain#getSelectorFactory() and Permission for more information.

Method and Constructor Matching

-------------------------------
 Primitive Types
 :   Numeric types are matched straight-forward. All Java rules apply for Integers and
     Doubles. Supported primitive types are: Integer, Long, Short, Byte, Double and
     Boolean, all in their primitive and wrapped version.

 Strings
 :   Strings may be written in three ways: myString, 'myString' and "myString".
     The two quoted variants support all Java escapes, the unquoted variant must follow
     Java's rules for identifiers. If a string matches a keyword, it *must* be quoted.

 Enumerations
 :   Enumeration types are written just like strings, however, these Strings are
     converted: All letters to upper case, '-' will be replaced by '_', i.e
     'my-enum-value' refers to the enumeration value MY_ENUM_VALUE.

 Arrays of Enumerations
 :   Arrays of enumerations can be written by separating the enum values with '|'. A
     typical example is read/write access: read|write.

 Varargs
 :   If the last argument of a method or constructor is an array, it will be interpreted
     as Varargs. If no varargs or the wildcard are passed, the method will be called
     with null for the array. Enumeration types are never treated as varargs. For all
     other types, arrays are only allowed as the last argument.

 Wildcards
 :   Wildcards are written as '*' and result in null being passed to the method.
     They're allowed both for arrays of enumerations or varargs.
 

Conditions

Conditions are simple boolean critera and logical operations (and, or, not, you know that stuff). Supported operators are `==`, `!=`, `<`, `<=`, `>`, `>=` and `~=`. Identifiers and paths thereof (a.b.c is a path) will be resolved depending on the object being checked. For example, in entities, relations and fields will be used. The special identifier principal may only occur as the first path element and refers to the principal of the current security context. The second path element will be resolved using the Principal's attributes, except name (the Principal's username) and key (the Principal's primary key). All further path elements will be resolved using the bean introspection.

Date Conditions

The keyword now specifies the current date/time. It's fields are actually operators which operate on that object. These can be combined freely, e.g. now.yesterday.date means yesterday at 0:00. The following "path elements" (operators) are available:
 date
 :   Keep the date as-is, set the time to 0:00.

 time
 :   Keep the time as-is, set the date to January 1st, 1970

 tomorrow
 :   Plus one day.

 yesterday
 :   Minus one day.
 

Includes

ACL files may include other ACL files. The syntax for this is:

 include 'another-resource.acl';
 
Important: An include statement will end the current section! This is an error:
 entity(*):
 grant access(*, *);

 include 'another-resource.acl';

 deny access(write) to anonymous; // ERROR: No section header