We’re making changes to our server and Data Center products, including the end of sale for new server licenses on February 2, 2021 and the end of support for server on February 2, 2024. Learn what this means for you.
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
Permissions checking overview
In Confluence, a permission check is a question like does user U have permission to do action A to content C? The way we answer that question deserves a brief overview of the logical operations:
First, Confluence checks that the user is allowed to access the application. This involves user and group checks for user U against the defined global permissions.
Second, Confluence checks space permissions. This involves user and group checks for user U against the space permissions for action A for the space containing content C.
Finally, Confluence checks content level restrictions like page permissions. This involves user and group checks for user U against the content level permissions for action A on content C.
If all three checks succeed, user U is permitted to do action A to content C by Confluence.
The logical operations involved in a "user and group check for user U" look like this, taking space permissions as an example:
First, Confluence retrieves all the space permissions for the space containing content C from the database.
Next, it gets the groups that user U is a member of and checks if each of the group has permission required to do action A.
Next, it checks whether user U is one of the individual users that has been granted the permissions required to do action A.
If the membership status of user U and the group isn't cached already, Confluence determines which user repository (database, LDAP, Crowd) owns the group. Confluence checks in the user repository whether user U is a member of the group, and caches the result for subsequent checks.
If either check succeeds - that is, if either user U or one of their groups has permission for the action - user U is permitted to do action A to content C by Confluence.
The API used for performing all these checks is described in more detail below.
The PermissionManager API
The core API for checking permissions in Confluence is through the PermissionManager (javadoc). The two most important methods on this interface are:
Permissions are defined as constants on the Permission interface (javadoc). They are VIEW, EDIT, EXPORT, REMOVE, SET_PERMISSIONS and ADMINISTER.
If the supplied user is null, the anonymous permission is checked
For the purpose of checking create permissions, the "containing" object is not the same as the parent. You test if a page can be created in a space, and a comment within a page, not within its parent page or comment.
There is a special object - PermissionManager.TARGET_APPLICATION - that represents Confluence itself and is used for checking global permissions
Some permission checks don't make sense, for example checking if you can REMOVETARGET_APPLICATION, or checking if you can administer a page. Checking a nonsensical permission will result in an IllegalStateException
Similarly, if you check permissions against a type of object that the PermissionManager doesn't know how to check permissions against (i.e. it doesn't have a delegate for that class, see below), it will throw an IllegalArgumentException.
The system does not cater for any inheritance of permissions. having Permission.ADMINISTER against an object does not imply that you also have Permission.EDIT.
However, certain permissions are considered "guard permissions". For example, permission to VIEWTARGET_APPLICATION is required to do anything in Confluence (it's generally referred to as "Use Confluence" permission). Similarly, permission to VIEW a particular space is required to do anything else in that space. If you are modifying Confluence permissions through the UI, removing a guard permission from a user or group will also remove any dependent permissions that user/group might have. If you are modifying Confluence permissions programatically, you are responsible for making sure they end up in a sensible state w.r.t guard permissions.
The PermissionManager always checks to ensure a user is not deactivated, and that a user has the "Use Confluence" guard permission.
The PermissionManager does not check if the user is a member of the super-user confluence-administrators group. If you want super-users to override your permission check, you have to do it manually.
For every type of target object (or container in the case of create permissions) there is a corresponding PermissionDelegate (javadoc) that performs the actual checks. The code should be reasonably self-explanatory
Getting all viewable/editable spaces for a user
Finding all spaces for which the user has a particular permission is a common, and reasonably expensive operation in instances with large numbers of spaces. For this reason we have a number of shortcut methods on SpaceManager that go straight to the database:
Note: These operations are still not cheap, especially in situations where the user being checked may be a member of a large number of groups.
Searching / Lucene
The Lucene index contains enough information for searches to determine if particular results are visible to the user performing the search. So long as you're not going direct to the Lucene index yourself, and use one of Confluence's search APIs to find content, the content returned should not require any more tests for VIEW permission.
Checking Permissions from Velocity
It might be difficult (or even impossible) to construct a required PermissionManager call from velocity code, especially for calls to the hasCreatePermission() method. For this reason there is an object called permissionHelper (javadoc) in the default velocity context with a number of helper methods to perform common permission checks.
If you can not find an appropriate method on the PermissionHelper, your best course of action is to write a Velocity context plugin to encapsulate your permission checking code (or if you're an Atlassian developer, obviously, just add it to the helper).
Other Permission-related APIs
The PermissionCheckDispatcher allows you to check if a particular user has access to a certain Confluence URL. It will only work if the target of the URL is a WebWork action (it works by instantiating the action referred to by that URL, filling in all the relevant form values, and calling isPermitted on the action).
The PermissionCheckDispatcher used to be the preferred way of testing whether or not to display a link in the web UI. However, its use is being phased out because it can be very slow. Do not use the PermissionCheckDispatcher for new code. Instead, use the PermissionManager directly. If you are in UI code, use the PermissionHelper (javadoc), a convenience class that is placed in the Velocity context to make permission checks more Velocity-friendly.
The SpacePermissionManager is a low-level API for directly manipulating user permissions. You should not use the SpacePermissionManager for checking permissions, because it tightly couples your permission check to the internal representation of permissions in the database. Use the PermissionManager for all permission checks.
The SpacePermissionManager should only be used:
By a PermissionDelegate to translate between a logical permission check, and the back-end implementation of that permission
By permissions management code (i.e. granting, revoking or displaying user permissions)
Adding New Permissionable Objects
To make it possible to use a new type of object as the subject of a permissions check, you will need to:
write a PermissionDelegate for that object's class.
instantiate the delegate in Spring (delegates are defined in securityContext.xml)
add that object to DefaultPermissionManager's delegates property in securityContext.xml
Adding New Permissions
Ask if this permission is really necessary? For example a lot of things that look like they should be permissions are really "create" permissions (like "can comment on page" is really "can create (comment, page)"
Add a new method to the PermissionDelegate interface.
For each existing PermissionDelegate, implement your new method. Throw IllegalStateException if the permission is not relevant to that delegate's object
Add a new constant to the Permission interface to represent your permission (see the existing examples)
Permissions checking on labels (add, remove) are broken, and still being done via page permission checks
We should probably throw UnsupportedOperationException for bogus checks instead of IllegalStateException
Currently create permissions are tested against container, not parent. a hasCreatePermission(user, container, parent, klass) could be useful