Permissions overview¶
Authorization has two independent layers. Both must pass, and confusing them is the source of most "why can this role see that?" questions:
- The gate — may this caller call this endpoint at all? Enforced by
@RequiresPermissionon the controller method. - The scope — of the records the endpoint could return, which ones may this caller see? Enforced inside the service or query layer.
A permission string grants the first. It does not automatically grant the second: several permissions open a door without narrowing what is behind it. Which ones is documented in What is enforced.
Anatomy of a permission¶
- resource — what is being acted on (
assessments,vulnerabilities,users, …). Groups the permission in the Roles screen. - action — the verb:
read,edit,create,delete, plus a few resource-specific ones (download,comment,retest,complete,write). - scope — how wide the reach is:
all,org,team,owned,assigned,assessment,self,system. See Scopes.
Not every permission has all three segments. reporting:create and survey:complete are
unscoped, and a few carry a compound resource: sso:config:read is the SSO config resource
with a read action and no scope — not the sso resource scoped to read. When parsing a
permission string, read it from the right and treat a trailing segment as a scope only when it
is one of the known tiers.
How gates are enforced¶
@RequiresPermission({Permission.ASSESSMENTS_READ_ALL,
Permission.ASSESSMENTS_READ_TEAM,
Permission.ASSESSMENTS_READ_ASSIGNED})
- Any-of. The caller needs one of the listed permissions, not all of them.
- Exact string match. There is no hierarchy and no wildcard:
assessments:read:alldoes not implyassessments:read:team. An endpoint that lists only the:allvariant is unreachable for a:teamrole, however sensible the tier looks. This is why the gates enumerate every tier they mean to accept. super_adminis implied everywhere. It is checked before the annotation's list, so endpoints never list it — and must not.- Coverage is enforced by a test:
EndpointAuthorizationArchitectureTestfails the build for any endpoint with neither@RequiresPermissionnor@AuthenticatedOnly.
Where the catalog comes from¶
Permission.java is the single source of truth. GET /api/v1/permissions serves it grouped by
resource, and the Roles admin screen renders that response as a matrix of scope rows against
action columns. Adding an enum constant makes the permission assignable with no UI change.
Two consequences worth knowing:
super_adminis not in the catalog, so it has no checkbox and cannot be granted through the Roles screen. Duplicating a role in the UI copies only catalog permissions for the same reason.- A permission string stored on a role that no longer exists in the enum is dropped on startup
by
BootstrapService.migrateRolePermissions(), which also applies historical renames. A typo in a permission grants nothing rather than failing loudly, so grant from the UI, not by hand-editing role documents.
Roles¶
A role is a name plus a list of permission strings. Users hold roles; the authorities on their
token are the union of every role's permissions. Roles marked external are the ones
assignable to portal (client) users, and should hold only :org or :owned scoped permissions.
See Built-in roles for what ships out of the box.