The New Custom User Group Implementation
A CUG as it is known in the context of AEM consists of the following steps:
- Restrict read access on the tree that must be protected and only allow read for principals that are either listed with a given CUG instance or excluded from the CUG evaluation altogether. This is called the authorization element.
- Enforce authentication on a given tree and optionally specify a dedicated login page for that tree that is then excluded. This is called the authentication element.
The new implementation has been designed to draw a line between the authentication and the authorization elements. As of AEM 6.3, it is possible to restrict read access without explicitly adding an authentication requirement. For example, if a given instance requires authentication altogether or a given tree already resides in a subtree that requires authentication already.
Equally, a given tree can be marked with an authentication requirement without changing the effective permission setup. The combinations and results are listed in the Combining CUG Policies and the Authentication Requirement section.
Overview
Authorization: Restricting Read Access
The key feature of a CUG is restricting read access on a given tree in the content repository for everyone except selected principals. Instead of manipulating the default access control content on the fly the new implementation takes a different approach by defining a dedicated type of access control policy that represents a CUG.
Access Control Policy for CUG
This new type of policy has the following characteristics:
- Access control policy of type org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy (defined by the Apache Jackrabbit API);
- PrincipalSetPolicy grants privileges to a modifiable set of principals;
- The privileges granted and the scope of the policy are an implementation detail.
The implementation of PrincipalSetPolicy used to represent CUGs in addition defines that:
- CUG policies only grant read access to regular JCR items (for example, access control content is excluded);
- The scope is defined by the access-controlled node that holds the CUG policy;
- CUG policies can be nested, a nested CUG starts a new CUG without inheriting the principal set of the ‘parent’ CUG;
- The effect of the policy, if evaluation is enabled, is inherited to the whole subtree down to the next nested CUG.
These CUG policies are deployed to an AEM instance through a separate authorization module called oak-authorization-cug. This module comes with its own access control management and permission evaluation. In other words, the default AEM setup ships an Oak content repository configuration that combines multiple authorization mechanisms. For more info, see this page on the Apache Oak Documentation.
In this composite setup, a new CUG does not replace the existing access control content attached to the target node. Instead, it is a supplement which can also be removed later on without affecting the original access control, that by default in AEM would be an access control list.
In contrast to the former implementation, the new CUG policies are always recognized and treated as access control content. This implies that they are created and edited using the JCR access control management API. For more info, see the Managing CUG Policies section.
Permission Evaluation of CUG Policies
Apart from a dedicated access control management for CUGs, the new authorization model lets you conditionally enable permission evaluation for its policies. This lets you set up CUG policies in a staging environment, and only enables evaluation of the effective permissions once replicated to the production environment.
Permission evaluation for CUG policies and the interaction with the default or any additional authorization model follows the pattern designed for multiple authorization mechanisms in Apache Jackrabbit Oak. That is, a given set of permissions is granted if and only if all models grant access. See the Jackrabbit Oak Documentation for more details.
The following characteristics apply for the permission evaluation associated with the authorization model designed to handle and evaluate CUG policies:
- It only handles read permissions for regular nodes and properties, but not reading access control content
- It does not handle write permissions nor any kind of permissions required for modification of protected JCR content (access control, node type information, versioning, locking, or user management among others). These permissions are not affected by a CUG policy and will not be evaluated by the associated authorization model. Whether these permissions are granted depends on the other models configured in the security setup.
The effect of a single CUG policy upon permission evaluation can be summarized as follows:
- Read access is denied for everyone except for subjects containing excluded principals or principals listed in the policy;
- The policy takes effect on the access-controlled node which holds the policy and its properties;
- The effect is also inherited down the hierarchy - that is, the item tree defined by the access-controlled node;
- However, it does neither affect siblings nor ancestors of the access-controlled node;
- The inheritance of a given CUG stops at a nested CUG.
Best Practices
The following best practices should account for defining restricted read access through CUGs:
-
Make a conscious decision on whether your need for a CUG is about restricting read access or an authentication requirement. If the latter, or if there is a need for both, consult the section on Best Practices for details regarding the Authentication requirement
-
Create a threat model for the data or content that must be protected to identify threat boundaries and get a clear picture about the sensitivity of the data and the roles associated with authorized access
-
Model the repository content and CUGs keeping general authorization-related aspects and best practices in mind:
- Remember that read permission is only granted if a given CUG and the evaluation of other modules deployed in the setup grant allow a given subject to read a given repository item
- Avoid creating redundant CUGs where read access is already restricted by other authorization modules
- Excessive need for nested CUGs may potentially highlight issues in the content design
- Excessive need for CUGs (for example, on every page) may indicate the need for a custom authorization model potentially better suited to match the specific security needs of the application and content at hand.
-
Limit the paths supported for CUG policies to a few trees in the repository to allow for optimized performance. For example, only allow CUGs below the /content node as shipped as the default value since AEM 6.3.
-
CUG policies are designed to grant read access to a small set of principals. The need for a huge number of principals may highlight issues in the content or application design and should be reconsidered.
Authentication: Defining the Auth Requirement
The authentication-related parts of the CUG feature let you mark trees that require authentication and optionally specify a dedicated login page. In accordance to the previous version, the new implementation lets you mark trees that require authentication in the content repository. It also conditionally enables synchronization with the Sling org.apache.sling.api.auth.Authenticator
responsible for ultimately enforcing the requirement and redirecting to a login resource.
These requirements are registered with the Authenticator by an OSGi service that provides the sling.auth.requirements
registration property. These properties are then used to dynamically extend the authentication requirements. For more details, consult the Sling documentation.
Defining the Authentication Requirement with A Dedicated Mixin Type
For security reasons, the new implementation replaces the usage of a residual JCR property by a dedicated mixin type called granite:AuthenticationRequired
, which defines a single optional property of type STRING for the login path granite:loginPath
. Only content changes related to this mixin type leads to an update of the requirements registered with Apache Sling Authenticator. The modifications are tracked upon persisting any transient modifications and thus require a javax.jcr.Session.save()
call to become effective.
The same applies for the granite:loginPath
property. It is only respected if it is defined by the auth-requirement related mixin type. Adding a residual property with this very name at an unstructured JCR node does not show the desired effect and the property is ignored by the handler responsible for updating the OSGi registration.
Registering the Authentication Requirement and Login Path With the Sling Authenticator
Since this type of authentication requirement is expected to be limited to certain run modes and to a small subset of trees within the content repository, tracking of the requirement mixin type and the login path properties is conditional. And, it is bound to a corresponding configuration that defines the supported paths (see Configuration Options below). Therefore, only changes within the scope of these supported paths trigger an update of the OSGi registration, elsewhere both the mixin type and the property are ignored.
The default AEM setup now makes use of this configuration by allowing to set the mixin in the author run mode but only have it take effect upon replication to the publish instance. See the Sling Authentication - Framework documentation for details how Sling enforces the authentication requirement.
Adding the granite:AuthenticationRequired
mixin type within the configured supported paths cause the OSGi registration of the responsible handler to be updated containing a new, additional entry with the sling.auth.requirements
property. If a given authentication requirement specifies the optional granite:loginPath
property, the value is also registered with the Authenticator with a ‘-’ prefix to be excluded from authentication requirement.
Evaluation and Inheritance of the Authentication Requirement
Apache Sling authentication requirements are inherited through the page or node hierarchy. The very details of the inheritance and the evaluation of the authentication requirements such as order and precedence are considered an implementation detail and will not be documented in this article.
Evaluation of Login Path
The evaluation of the login path and redirect to the corresponding resource upon authentication is an implementation detail of the Adobe Granite Login Selector Authentication Handler ( com.day.cq.auth.impl.LoginSelectorHandler
), which is an Apache Sling AuthenticationHandler configured with AEM by default.
Upon calling AuthenticationHandler.requestCredentials
this handler attempts to determine the mapping login page to which the user is redirected. This includes the following steps:
-
Distinguish between expired password and need for regular login as reason for the redirect;
-
If a regular login, tests if a login path can be obtained in the following order:
- from the LoginPathProvider as implemented by the new
com.adobe.granite.auth.requirement.impl.RequirementService
, - from the old, deprecated CUG implementation,
- from the Login Page Mappings, as defined with the
LoginSelectorHandler
, - and finally, fall back to the Default Login Page, as defined with the
LoginSelectorHandler
.
- from the LoginPathProvider as implemented by the new
-
When a valid login path was obtained through the calls listed above, the user’s request is redirected to that page.
The target of this documentation is the evaluation of the login path as exposed by the internal LoginPathProvider
interface. The implementation shipped since AEM 6.3 behaves as follows:
-
Registration of login paths depends on distinguishing between expired password and need for regular login as reason for the redirect
-
If regular login, tests if a login path can be obtained in the following order:
- from the
LoginPathProvider
as implemented by the newcom.adobe.granite.auth.requirement.impl.RequirementService
, - from the old, deprecated CUG implementation,
- from the Login Page Mappings as defined with the
LoginSelectorHandler
, - and finally fall back to the Default Login Page as defined with the
LoginSelectorHandler
.
- from the
-
When a valid login path was obtained through the calls listed above, the user’s request is redirected to that page.
The LoginPathProvider
as implemented by the new auth-requirement support in Granite exposes login paths as defined by the granite:loginPath
properties, which in turn are defined by the mixin type as described above. The mapping of the resource path holding the login path and the property value itself is kept in memory and is evaluated to find a suitable login path for other nodes in the hierarchy.
Best Practices
The following best practices should be taken into account when defining authentication requirements:
-
Avoid nesting authentication requirements: placing a single auth-requirement marker at the start of a tree should be sufficient and be inherited to the whole subtree defined by the target node. Additional authentication requirements within that tree should be considered redundant and may lead to performance issues while evaluating the authentication requirement within Apache Sling. With the separation of authorization and authentication-related CUG areas, it is possible to restrict read access by CUG or other types of policies while enforcing authentication for the whole tree.
-
Model repository content such that authentication requirements apply for the whole tree without the need to exclude nested subtrees from requirement again.
-
To avoid specifying, and then registering redundant login paths:
- rely on inheritance and avoid defining nested login paths,
- do not set the optional login path to a value that corresponds to the default or an inherited value,
- application developers should identify which login paths should be configured in the global login-path configurations (both default and mappings) associated with the
LoginSelectorHandler
.