Permissions and roles deep dive

November 14, 2023

This topic shows how to check for permissions in JSP templates, custom Java services, and actions, and describes permissions types. The topic also provides technical details on the permissions tree and permissions check algorithm, and on the ACL listener.

Note: For information on assigning permisisons to roles in Jahia>Administration, see Managing roles and permissions.

Implementing permission checks

Jahia provides ways to check for permissions in JSP templates, custom Java services, and actions.

JSP templates

With functions included in the <Jahia JCR> taglib template (see Downloading Javadoc and Taglib doc for Jahia), you can restrict access to particular areas of your templates. Here is a simple example that show the usage of such a function.

<c:if test="${jcr:hasPermission(currentNode,'jcr:write')}">
    The user is allowed to modify the node
</c:if>

Notice that the permission is used without the workspace name suffix. This is because Jahia automatically attaches the current workspace name when resolving the permission.

Java API

A permission is always checked for a user on a specific node. The JCR AccessControlManager API provides a method to check permissions on a node.

boolean AccessControlManager.hasPrivileges(String absPath, Privilege[] privileges)

A convenient method is also provided in JCRNodeWrapper to check a single permission.

boolean hasPermission(String permission)

Global permissions are checked on the root node. Site permissions can be checked on the site node.

Custom actions

If the  requiredPermission  property is set on an action, the Jahia controller checks the permission before trying to call the doExecute method. For example, the action for creating a new blog entry is defined with the setRequiredPermission method.

@Component(service = RateContent.class)
public class AddBlogEntryAction extends Action {
    JCRTemplate jcrTemplate;
    @Activate
    public void activate() {
        setName("addBlogEntry");
        setRequiredPermission("addBlogEntry");
    }
    @Reference
    public void setJcrTemplate(JCRTemplate jcrTemplate) {
        this.jcrTemplate = jcrTemplate;
    }
    @Override
    public ActionResult doExecute(HttpServletRequest req, RenderContext renderContext, final Resource resource, JCRSessionWrapper session, final Map<String, List<String>> parameters, URLResolver urlResolver) throws Exception {
        return (ActionResult) jcrTemplate.doExecuteWithSystemSession(null,session.getWorkspace().getName(),session.getLocale(),new JCRCallback<Object>() {
            public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
               // .... logic to add blog entry
            }
        });
    }
}

Views

The properties file associated with a view can contain a requirePermissions property. If this property is set, the RenderService checks that permission before calling the script. In the example module contact, the contactForm.properties file associated with the contactForm.jsp contains the following line.

requirePermissions=viewContacts

About permission sets

Permissions are stored as a tree of permission nodes. Top level permissions are aggregates of all subpermissions. For example, the Edit mode permission includes all the granular permissions related to the edit mode UI, including Edit mode access, Edit mode actionsEdit selector and more.

Basic permissions

The JCR defines a default set of permissions that match the low level operations available on any node. For example, jcr:read, jcr:addChildNodes, and jcr:modifyProperties are examples of JCR system permissions.

Jahia provides a distinct set of permissions for the default and live workspaces. A user who can read the live workspace may not be able to read the default (staging) workspace. Permissions are suffixed with the workspace name. For example, to add nodes in live workspace, a user requires the jcr:addChildNodes_live permission.

Jahia provides also distinct permission by language for updating properties. The jcr:modifyProperties is split in multiple subpermissions, one for each language. jcr:modifyProperties_default will allow users to change any property in the default workspace, but jcr:modifyProperties_default_en will allow only modification of internationalized properties in English.

In addition, Jahia provides a Publish permission which allows publishing content in live. Note that you may not need to give this permission to any user if you use the publication workflow to validate content before publishing.

Permissions on modules

A module with a complex application can define any number of specific permissions, which are checked by module specific actions, or directly by the JSPs.

Workflow permissions

Each workflow task is mapped to a permission. Any user who has the associated permission when a task is created will have the right to take and complete the task.

UI permissions

UI permissions are relative to the edit, contribute, or studio UIs. Every manager, selector tab, engine tab, or action item can have an associated permission that is required to be able to use it. All UI permissions are checked at the site level, meaning that an access to the edit mode cannot be limited to a specific section of the site.

Template and components permission

You can refine which template a user can use when they creates a new page by granting template permissions. You can also define which components a user can manipulate by giving components permissions. Like the UI permissions, these permissions are checked on the site level. You cannot constrain some templates on a site subsection.

Site administration

Site administration permissions are related to the site administration panel.

Server administration

Site administration permissions are related to the server administration panel.

Technical details

Permissions tree

Core permissions are stored in the /permissions node. Modules that define permissions have subnode permissions that contain them. They are only available when the module is deployed and started.

Although they are organized as a tree, a permission is always referred by its name. This means that there should never be multiple permissions with the same name, even in different modules. If a module declares a permission that already exists, it will be ignored.

Roles are all stored in the /roles node, organized in a tree. Roles can also have the jnt:externalPermissions subnode that contain additional scopes and associated permissions. An administrator can modify roles in Administration>Users and Roles>Roles and permissions. Roles and permissions are only visible in the default workspace and are never published.

For a role to be available to a user on a node, the node needs to have the jmix:accessControlled mixin type, which defines a j:acl subnode. The access control list contains a list of entries (jnt:ace), one for each user and group, that define the roles that are granted or denied for the user or group.

If a permission was granted through an external permission, the entry has the jnt:externalAce type and maintains a link to the associated ACE node that was set by the user.

Permission check algorithm

The permission check occurs in the JahiaAccessManager class. This class is called by Jackrabbit when checking a permission. It checks internal repository permissions, like read and write, when the JCR API is used. The method isGranted checks if the current user has a set permission for a given path. To do so, the method iterates on the node and its parent until all requested permissions find a match in the different ACEs. The iteration is done in the recurseOnACPs() method. Here’s the detailed algorithm.

def foundRoles
while (true)
  if (node has an acl) do
    for each (ace in acl) do
      if (ace matches current user) do
        for each (role in ace) do
          if (role is not foundRoles) do
            add role in foundRoles
            if (ace type is "granted") do
              remove all permission in role from permissions
              if (permissions is empty) do
                return true
              end if
            end if
          end if
        end for 
      end if
    end for  
    if (acl breaks all inheritance) do
      return false
    end if  
  end if
  if (node is root node)
    return false
  end if
  node = parent of node
end while

ACL listener

ACL listener is a JCR listener responsible for granting or removing privileged access when roles are granted or removed and for setting external permissions based on the roles that were granted. For example, if the editor role is granted to John, the listener does the following when:

  • the editor role is a privileged role
    the listener adds John to the site-privileged group if he’s not already in it.
  • the editor role has an external permission on the current site
    the listener checks if the current site already has an external ACE for the role editor and the user John. If not, it creates it with the external permission defined in the editor role.

When removing a role for a user, the listener checks if the user still has the role anywhere else on the site with a query before removing it from the site-privileged group and deleting the external ACE.

Defining role types

Roles types are defined in mod-rolesmanager.xml file in the Roles Manager module. For each role type, an entry defines if the roles are privileged, on which node types it can apply, what the predefined additional scopes are, and which permissions are available.