Authorization Specification: Owner-Based RBAC
Authorization Specification: Owner-Based RBAC
Section titled “Authorization Specification: Owner-Based RBAC”Overview
Section titled “Overview”Owner-based Role-Based Access Control (RBAC) for the Graph OLAP Platform. Three hierarchical roles govern access to all API endpoints. Resource ownership is assigned at creation time and is immutable.
Tier: 2 (referenced by all API specs)
Prerequisites
Section titled “Prerequisites”- api.common.spec.md - Authentication, middleware-injected headers (
X-Username,X-User-Role) - data.model.spec.md -
owner_usernamecolumn on resource tables
Role Definitions
Section titled “Role Definitions”| Role | Purpose | Summary |
|---|---|---|
| Analyst | Data users | Create, query, and analyze graphs. Modify own resources only. |
| Admin | Power users | Full data access across all users. Bulk operations. Schema management. |
| Ops | Platform operators | All Admin capabilities plus system configuration, cluster monitoring, and background jobs. |
Role Hierarchy
Section titled “Role Hierarchy”Analyst < Admin < OpsEach higher role is a strict superset of the one below it. An Ops user can do everything an Admin can do, and an Admin can do everything an Analyst can do. There is no concept of disjoint permissions between roles.
RBAC Permission Matrix
Section titled “RBAC Permission Matrix”Complete per-endpoint authorization. “Own” means resource.owner_username == user.username.
Data Resources
Section titled “Data Resources”| Endpoint | Analyst | Admin | Ops |
|---|---|---|---|
Mappings GET /api/mappings | List all | List all | List all |
Mappings POST /api/mappings | Create (owns result) | Create (owns result) | Create (owns result) |
Mappings GET /api/mappings/:id | Read any | Read any | Read any |
Mappings PUT /api/mappings/:id | Own only | Any | Any |
Mappings DELETE /api/mappings/:id | Own only | Any | Any |
Mappings POST /api/mappings/:id/copy | Create copy (owns result) | Create copy (owns result) | Create copy (owns result) |
Snapshots GET /api/snapshots | List all | List all | List all |
Snapshots POST /api/snapshots | Create (owns result) | Create (owns result) | Create (owns result) |
Snapshots GET /api/snapshots/:id | Read any | Read any | Read any |
Snapshots DELETE /api/snapshots/:id | Own only | Any | Any |
Instances GET /api/instances | List all | List all | List all |
Instances POST /api/instances | Create (owns result) | Create (owns result) | Create (owns result) |
Instances GET /api/instances/:id | Read any | Read any | Read any |
Instances DELETE /api/instances/:id | Own only | Any | Any |
Instances POST /query (Cypher, read-only) | Any instance | Any | Any |
| Instances algorithm endpoints | Own only | Any | Any |
Favorites * /api/favorites | Own only | Own only | Own only |
Export Jobs (Scoped)
Section titled “Export Jobs (Scoped)”| Endpoint | Analyst | Admin | Ops |
|---|---|---|---|
GET /api/export-jobs | Own snapshots only | All | All |
Analyst access is scoped via snapshot ownership: only export jobs for snapshots where snapshot.owner_username == user.username are returned.
Admin Endpoints
Section titled “Admin Endpoints”| Endpoint | Analyst | Admin | Ops |
|---|---|---|---|
Schema Admin POST /api/schema/admin/refresh | No access | Full access | Full access |
Schema Stats GET /api/schema/stats | No access | Full access | Full access |
Bulk Delete DELETE /api/admin/resources/bulk | No access | Full access | Full access |
E2E Cleanup DELETE /api/admin/e2e-cleanup | No access | Full access | Full access |
Ops-Only Endpoints
Section titled “Ops-Only Endpoints”| Endpoint | Analyst | Admin | Ops |
|---|---|---|---|
Config GET/PUT /api/config/* | No access | No access | Full access |
Cluster GET /api/cluster/* | No access | No access | Full access |
Jobs POST /api/ops/jobs/trigger | No access | No access | Full access |
Jobs GET /api/ops/jobs/status | No access | No access | Full access |
State GET /api/ops/state | No access | No access | Full access |
Export Jobs (debug) GET /api/ops/export-jobs | No access | No access | Full access |
Read-Only Endpoints (Any Authenticated User)
Section titled “Read-Only Endpoints (Any Authenticated User)”| Endpoint | Analyst | Admin | Ops |
|---|---|---|---|
Schema Browse GET /api/schema/catalogs, /schemas, /tables, /columns | Full access | Full access | Full access |
Schema Search GET /api/schema/search/* | Full access | Full access | Full access |
Ownership Model
Section titled “Ownership Model”- Assignment. The user who creates a resource owns it. The
owner_usernameis set from theX-Usernameheader at creation time. - Immutable. Ownership cannot be transferred after creation.
- No ACLs. There is no resource-level sharing or access control list. Visibility follows the role hierarchy.
- Copy creates new ownership. Copying a mapping (
POST /api/mappings/:id/copy) creates a new resource owned by the caller, not the original owner. - Storage. The
owner_usernamecolumn is indexed on every resource table (mappings,snapshots,instances). - Cypher queries are not gated by ownership. The wrapper’s
/queryendpoint is intentionally unauthenticated at the wrapper level. Any authenticated user can execute read-only Cypher against any instance they can reach. Mutating Cypher keywords (CREATE,SET,DELETE,MERGE,REMOVE,DROP) are rejected at the endpoint, so this is a read-catalogue model — consistent with the “Read any” rule for instance metadata. Algorithm execution, by contrast, is owner-gated viarequire_algorithm_permission→/api/internal/instances/:slug/authorizeon the control plane.
Authorization Enforcement
Section titled “Authorization Enforcement”Authorization is enforced at two layers:
Layer 1: Router-Level Role Gates
Section titled “Layer 1: Router-Level Role Gates”Router functions check the user’s role from X-User-Role before any business logic executes. Requests from users with insufficient roles are rejected immediately with 403 Forbidden.
# Ops-only endpointsdef require_ops_role(user: CurrentUser) -> None: if user.role != "ops": raise HTTPException(status_code=403, detail="Requires ops role")
# Admin-or-above endpointsdef require_admin_role(user: CurrentUser) -> None: if user.role not in ("admin", "ops"): raise HTTPException(status_code=403, detail="Requires admin role")Layer 2: Service-Layer Ownership Checks
Section titled “Layer 2: Service-Layer Ownership Checks”For data resource mutations (update, delete), the service layer checks ownership after loading the resource. Admin and Ops users bypass the ownership check.
# Analyst can only modify own resources; Admin/Ops can modify anyif user.role == "analyst" and resource.owner_username != user.username: raise HTTPException(status_code=403, detail="Only owner or admin can modify this resource")Both layers must pass for a request to succeed.
Error Responses
Section titled “Error Responses”| Status | Condition | Response |
|---|---|---|
| 401 Unauthorized | Missing or invalid authentication token | {"error": {"code": "UNAUTHORIZED", "message": "Authentication required"}} |
| 403 Forbidden | User’s role is below the endpoint minimum | {"error": {"code": "FORBIDDEN", "message": "Requires admin role"}} |
| 403 Forbidden | Analyst attempting to modify another user’s resource | {"error": {"code": "PERMISSION_DENIED", "message": "Only owner or admin can update this mapping", "details": {"owner_username": "...", "your_role": "analyst"}}} |
Related Documents
Section titled “Related Documents”| Document | Relevance |
|---|---|
| api.common.spec.md | Authentication middleware, X-Username / X-User-Role headers |
| api.admin-ops.spec.md | Admin and Ops endpoint definitions |
| api.mappings.spec.md | Mapping ownership and 403 responses |
| api.instances.spec.md | Instance ownership |
| api.snapshots.spec.md | Snapshot ownership |
| ADR-084 | Dual authentication paths |
| ADR-085 | JWT extraction middleware |