Skip to content

Permissions

Thomas Lynch edited this page Oct 1, 2024 · 2 revisions

How permissions work

The Permission class represents a set of permissions, allowing you to check, modify, and handle permission states. It utilizes bitwise operations to efficiently manage large numbers of permissions. It extends big-bitfield which provides the core functionality for manipulating the bitfield, and methods for checking whether a single bit is true, or for multiple bits whether all/any of the bits are true.

Permissions are stored in a Binary field in mongodb and a new instance of the Permission class is created inside a permission handling middleware.

Account, Org and Team permissions.

Permission binary data is stored in multiple places in mongodb. Account level permissions contain account-specific permissions. Organisations and Teams contain a mapping of account IDs to a permission binary. The bitfield has room for 255 bits. For simplicity, not all bits are used, and only certain subsets of bits are used by account/team/org permissions. The used bits for each subset do not overlap.

A visual representation:

+---------------------------------------------------------------------------------------+
| ACCOUNT LEVEL BITS (1-9) | ORG BITS (10-50) | TEAM BITS (51-100) | Unused bits        |
+---------------------------------------------------------------------------------------+
| 1 2 3 4 5 6 7 8 9 ...    | 10 11 ...     50 | 51 52 ...      100 | 101 ...       255  |
+---------------------------------------------------------------------------------------+

(Note: the actual bit ranges are defined in lib/permissions/bits)

This means that org level permissions such as the ability to create a team are contained within org bits, and team level permissions such as the ability to create an app are within team bits. An account can have separate permissions for each team and org.

When making a request, an authenticated users Permission is instantiated based on the Account permissions. Then, depending on the context of the request, the applicable org and team permission subsets are applied on top of this to create a single combined permission object that can be used easily throughout the rest of the controllers.

Inheritance and special cases

There are a few utility bits:

ORG_OWNER: if true, all ORG_BITS are set to true

TEAM_OWNER: if true, all TEAM_BITS are set to true

When calculating permissions, accounts whose id matches the org or team ownerId will have these bits set true, and in the Permission.applyInheritance() method, if those bits are true, the permission will be updated with all org or team bits, respectively.

There are almost no special cases of checking permission bits in the webapp, with one notable exception being the check of ownerId to prevent the editing of permission of team and org owners, so that their permission bits can never be removed. Therefore, users can grant the _OWNER permissions to other account and allow them to fully manage the org with similar authority to the owner, but without the ability to ever usurp ownership.

Permission metadata and parents

The permissions metadata in lib/permissions/metadata defines not only the name, title, description, etc of each permission, but also the parent property. The parent property defines what permission is required to set a permission, allowing you to e.g. only allow users with another bit e.g. TEAM_ADMIN or ORG_OWNER to edit certain permissions.

The permission parents are also used in the Permission.handleBody() method, which takes a body (permissions form data), the current permission, and the permission object that is being edited. This means you don't need to write any logic for updating permissions or checking parent bits manually, as it is all self-contained in the Permission class. When submitting a permission editing form, always use handleBody.

Roles

Roles are predefined sets of permissions. There are a few predefined roles such as ORG_OWNER and TEAM_OWNER that are used as the default permissions when an account is created and set in the permissions map. Users do not have a specific role. The roles shown in member lists on the org or team pages simply show which role matches the current permission of the user, else the users permission has been customised.

Querying by permissions

You can query permissions by their bits with mongodb bitwise operators.