This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.

ACL

*.acl files are used to control access to certain resources (for example entities).

Folder structure

The files are contained in the resources/acl folder of the module:

../../_images/acl-folder-structure.png

The structure is the same for all modules.

The main file that will be loaded is called module.acl, but usually the ACL rules are split up into different files (entity.acl, action.acl and so on).

Note

When a new *.acl file is created it needs to be included into the module.acl file:

include 'entity.acl';
include 'action.acl';

The Access Control Language

For defining the access control rules, there is a language, called ACL (Access Control Language). The top-level structure 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

The selector defines the security domain (in this example entityManager) and the objects (in this example the entity model MyEntity) that are affected by the acl rules.

The following figure shows the syntax for the rules:

../../_images/acl-rule-syntax.png

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 (...)) and 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.

Example:

entity(User):
    deny access(write) to usermanager if deleted and stop;

This rule applies to all User entities.

  • When grant is used access will allowed; when deny is used access will be forbidden if the rule matches

  • access(write) is the name of the permission. The different security domains offer different permissions.

  • to usermanager defines that the rule should only be applied to principals with a certain role.

  • if deleted is the condition. The rule is only applied if the secured objects matches the condition. Not every security domain supports conditions.

  • and stop marks this rule as final (see above).

Note

All security features are disabled when the privileged() invoker of the SecurityManager is used.

Keywords

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

Security domains

Different objects are secured with different SecurityDomains. A security domain defines which permissions are available and how or if conditions can be applied to a rule.

entityManager

The target of this domain is an EntityModel. It provides the create permission, which allows creating a new instance of an entity. Conditions are not supported.

entityManager(User):
    grant create to usermanager;

This permission is checked when a new entity instance is created:

context.getEntityManager("User").create();

entity

This domain controls the read and write access to persisted entities and provides the access and delete permissions. The access permission takes an optional parameter to specify whether the rule targets read or write permission. If no parameter is given both read and write permissions are affected.

For example this rule affects the write operation only:

entity(User):
    deny access(write);

Note

The combinations deny access(read) and grant access(write) are not allowed, because it does not make sense to deny only read access (but allow write access) or to grant only write access (but deny read access).

Conditions

This domain supports conditions to restrict the affected entities with a TQL like syntax. For example:

entity(User):
    deny access(write) if exists(relAddress) and enabled;

Using the keyword principal.user_id the primary key of the currently logged in user can be used in the condition.

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.

The applicable conditions are added to every query for a given entity model, so that only readable entities are returned from the database.

Updating relations

When an entity is added to or removed from a Relation the permissions of the both sides of the relation are combined:

  • GRANT + GRANT = GRANT

  • GRANT + NO_RULE = GRANT

  • GRANT + DENY = DENY

  • NO_RULE + DENY = DENY

  • NO_RULE + NO_RULE = NO_RULE

  • DENY + DENY = DENY

If the combined result is not GRANT the relation may not be changed.

This has the following implications:

  • A relation may be updated through its reverse side unless there is an explicit DENY rule.

  • For new entities GRANT is assumed, so new entities may always be added to a relation unless there is an explicit DENY rule.

Note

The rules of this security domain do not affect entities which are in state CONCEPTION.

entityPath

The entityPath domain is related to the entity domain as it also affects entity instances and provides the same access permission (but no delete permission). But while the latter affects entities as a whole, the former affects access to single fields or relations of an entity.

The same Conditions are supported as for the entity domain (obviously referencing the entity containing the field and not the field itself).

entityPath(User, email):
    deny access(write);

This denies write access only to a single field of an entity (email in this case).

These rules are checked whenever getValue(), requireValue() or setValue() (or a similar method like getString() / requireString()) is called on the Entity.

Note

When a user has been granted access to an entity (through the entity security domain) and there are no specific rules for a field, the entity rules are also applicable for the field.

Otherwise the two security domains are evaluated independently and the entityPath domain can override the rules of the entity domain for specific fields.

Additional domains

adminMenu

Controls the visibility of menu items in the admin menu:

adminMenu(settings,
            settings-address,
                settings-address-address,
                    settings-address-address-region):
    grant display to configurator;

netuiactions

Controls whether actions may be executed:

netuiactions("nice2.businessunit.CopyBusinessUnitValuesAction"):
    grant netuiPerform to configurator;

infoBoxes

Controls to whom infoboxes are displayed on the home page:

infoBoxes(welcomebox, sysreqinfobox, securityinfobox):
    grant boxDisplay;

reports

Controls whether a specific report may be generated:

reports("report.schooling"):
    grant generateReport to eventmanager, eventguest;

Policy

All ACL rules are compiled into a security Policy. The rules are applied in the order they were defined in the *.acl files (and depending on module dependencies). So it is always possible to override an earlier rule (unless and stop was defined on a rule).

Because the full policy (stored in the SecurityManager) for all possible principals and all possible objects may get very big for a full-scale application, this policy 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 (stored in the SecurityContext) 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 (stored in the Guard).

Therefore, in practice, the policy for a concrete object will finally be relatively compact.

Checking permissions manually

Normally the permissions are checked automatically when querying or updating data. But sometimes it is necessary to check permissions manually. This can be done by obtaining a Guard from the SecurityContext. The Guard instance can then be used to evaluate permissions.

Roles

For more info see Initial Values

Roles are loaded into the database via a yaml file:

Role:
    localized:
        label: entities.Role.tipguest.label
        description: entities.Role.tipguest.description
    single-relations:
        relRole_type: nice_role_type WHERE unique_id = 'guest'
    fields:
        unique_id: tipguest
        force_two_factor_auth: false
    config:
        update-mode: keep_changes
        identifier: unique_id
---
Role:
    localized:
        label: entities.Role.tipmanager.label
        description: entities.Role.tipmanager.description
    single-relations:
        relRole_type: nice_role_type WHERE unique_id = 'manager'
    fields:
        unique_id: tipmanager
        force_two_factor_auth: false
    config:
        update-mode: keep_changes
        identifier: unique_id
  • relRole_type set an already defined role type from nice_role_type to this new role.

  • unique_id the id that can then be used in acl and other places.

  • force_two_factor_auth true or false if 2FA should be forced.

ACL for (public) widgets

Some widgets will only fully work if a customer ACL rule is added as the permission depends on the use-case of the customer. As an example in the membership registration widget the section dropdown is empty for anonymous users. Here some guidelines on how to write ACL rules for such cases:

  • Only grant access to the minimal number of entities (e.g. use a flag or status)

  • You cannot only grant access to some paths. You must grant access to the entity and deny access to all paths which should not be available. As an example normally relResource_entitydocs and relFolder_entitydocs contain sensible data which should not be public available

  • If you write an ACL for extranet users (without a role) add except &anonymous if the entity must not be public available

As an example for the membership registration widget if the publish flag on the Address should be used, the constriction must be adjusted and the ACL rules added:

entity(Address):
    grant access(read) to &anonymous if publish;

entityPath(Address, relResource_entitydocs, relFolder_entitydocs, ...):
    deny access to &anonymous;