Skip to content
ABRQ DATADocs Abrq DIP · latest
Product page Request a trial
On this page

Roles and Permissions (RBAC)

The two-tier model#

Authorization has two layers that compose:

  1. Global rolesadmin, editor, viewer — govern the instance: connectors, settings, and who may create projects.
  2. Project rolesViewer, Developer, Maintainer, Owner — govern each ETL project, per member, with awareness of protected environments.

Every decision flows through a single backend chokepoint, and an enforcement meta-test fails the build if any mutating ETL route ships without a guard.

Global roles#

Global role Grants
admin Instance administrator. Implicit Owner of every project, plus all settings surfaces (users, tokens, audit, providers, license).
editor May create projects (becoming their Owner) and manage instance-scoped connectors.
viewer Baseline read access only.

Any global role — including viewer — grants baseline read access across the instance, including all projects.

Note. Read endpoints are enforced by authentication plus this global-role baseline. The model defines view capabilities (project.view, task.view, connection.view, schedule.view) and resolves them for every project role, but in this release read endpoints check that the caller is authenticated rather than consulting per-project membership. Treat project read access as instance-wide for any signed-in user; project roles gate writes.

Project role × capability matrix#

The full capability vocabulary is 20 ids. Fifteen are resolved against the member's project role:

Capability Viewer Developer Maintainer Owner
project.view yes yes yes yes
task.view yes yes yes yes
connection.view yes yes yes yes
schedule.view yes yes yes yes
task.create unprotected envs only yes yes
task.update unprotected envs only yes yes
task.delete unprotected envs only yes yes
task.run unprotected envs only yes yes
connection.bind unprotected envs only yes yes
schedule.manage unprotected envs only yes yes
project.environment.manage yes yes
task.promote yes (any target) yes (any target)
project.update yes
project.delete yes
project.member.manage yes

The remaining five are instance-flag capabilities, resolved from the global role rather than any project role: project.create (global editor+), connection.create, connection.update, connection.delete, and instance.admin. In this release, connector management endpoints gate on the global editor role directly; instance.admin maps to the global admin role.

Environment gating#

Each project has named environments (only dev is auto-created; environments named prod or production default to protected). An environment can be marked protected, which changes what a Developer may do:

  • Exactly six write capabilities are environment-gated: task.create, task.update, task.delete, task.run, connection.bind, and schedule.manage.
  • In an unprotected environment, a Developer holds all six. In a protected environment, all six are removed from Developers — the denial carries reason protected_environment. Maintainers and Owners keep them everywhere.
  • Promotion is not environment-gated. task.promote requires Maintainer or Owner, and a Maintainer may promote into a protected environment — the role check is the control, not the target's protection flag. Protection gates direct edits, while promotion is the sanctioned, diff-and-confirm path into protected environments.

Warning. project.environment.manage (Maintainer+) includes toggling the protected flag itself — a Maintainer can un-protect an environment and then edit it directly. If that separation matters in your organization, grant Maintainer sparingly and treat Owner as the trust boundary.

Ownership rules#

  • Creator becomes Owner. Whoever creates a project is its first Owner.
  • Owner-only trio. project.update, project.delete, and project.member.manage require the Owner role (or global admin).
  • Last-Owner guard. Removing or demoting the last remaining Owner is rejected with HTTP 409 — a project can never be left ownerless.

Denial semantics — the 403 envelope#

Every permission denial returns a structured envelope:

{
  "code": "permission_denied",
  "reason": "protected_environment",
  "required_capability": "task.update",
  "project_id": "00000000-0000-0000-0000-000000000101",
  "environment_id": "00000000-0000-0000-0000-000000000201"
}

reason comes from a closed vocabulary:

Reason Meaning
unknown_capability The capability id is not in the vocabulary (denied, never allowed-by-accident).
instance_capability_required The action needs an instance flag (e.g. global editor), not a project role.
missing_project_context The request could not be tied to a project.
not_a_member The caller has no membership in this project.
protected_environment An environment-gated write hit a protected environment (Developer).
insufficient_role The member's project role is below what the capability requires.

Denied attempts are recorded in the audit log as etl_authz_denied events — including the capability, reason, and context.

Worked examples#

A Developer in dev vs prod. Dana is a Developer on the project. In the unprotected dev environment she creates, edits, runs, and schedules tasks freely. The same task.update request against the protected prod environment returns 403 with reason: "protected_environment". To change prod, she edits in dev and asks a Maintainer to promote.

A Maintainer and a protected target. Sam is a Maintainer. He promotes a task from dev into the protected prod environment — allowed, because task.promote checks role, not target protection. He can also flip the protected flag on prod via project.environment.manage; see the warning above about what that implies.

An Owner change that would strand the project. The only Owner tries to demote themselves to Maintainer. The request fails with 409 until a second Owner exists.