PR Body Implements Spec 065 “Tenant RBAC v1” with capabilities-first RBAC, tenant membership scoping (Option 3), and consistent Filament action semantics. Key decisions / rules Tenancy Option 3: tenant switching is tenantless (ChooseTenant), tenant-scoped routes stay scoped, non-members get 404 (not 403). RBAC model: canonical capability registry + role→capability map + Gates for each capability (no role-string checks in UI logic). UX policy: for tenant members lacking permission → actions are visible but disabled + tooltip (avoid click→403). Security still enforced server-side. What’s included Capabilities foundation: Central capability registry (Capabilities::*) Role→capability mapping (RoleCapabilityMap) Gate registration + resolver/manager updates to support tenant-scoped authorization Filament enforcement hardening across the app: Tenant registration & tenant CRUD properly gated Backup/restore/policy flows aligned to “visible-but-disabled” where applicable Provider operations (health check / inventory sync / compliance snapshot) guarded and normalized Directory groups + inventory sync start surfaces normalized Policy version maintenance actions (archive/restore/prune/force delete) gated SpecKit artifacts for 065: spec.md, plan/tasks updates, checklists, enforcement hitlist Security guarantees Non-member → 404 via tenant scoping/membership guards. Member without capability → 403 on execution, even if UI is disabled. No destructive actions execute without proper authorization checks. Tests Adds/updates Pest coverage for: Tenant scoping & membership denial behavior Role matrix expectations (owner/manager/operator/readonly) Filament surface checks (visible/disabled actions, no side effects) Provider/Inventory/Groups run-start authorization Verified locally with targeted vendor/bin/sail artisan test --compact … Deployment / ops notes No new services required. Safe change: behavior is authorization + UI semantics; no breaking route changes intended. Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box> Reviewed-on: #79
400 lines
11 KiB
Markdown
400 lines
11 KiB
Markdown
---
|
||
description: "Tenant RBAC v1 — Capabilities-first authorization + Membership Management"
|
||
feature: "065-tenant-rbac-v1"
|
||
version: "1.0.0"
|
||
status: "draft"
|
||
---
|
||
|
||
# Spec 065 — Tenant RBAC v1 (Capabilities-first) + Membership Management
|
||
|
||
**Scope**: `/admin` Tenant Panel (Entra users)
|
||
|
||
**Depends on**:
|
||
- **063 Entra sign-in v1** (tenant users authenticate via Entra / OIDC)
|
||
- **064 Auth structure v1** (separate `/system` platform panel vs `/admin` tenant panel, cross-scope 404)
|
||
|
||
**Out of scope (v1)**:
|
||
- `/system` platform RBAC expansion (system console / global views)
|
||
- Entra group-to-role mapping (v2)
|
||
- SCIM provisioning (v2)
|
||
- Impersonation (v2)
|
||
- Custom per-feature roles UI (v2)
|
||
- “Invite token” email onboarding flows (optional v2, depending on your Entra setup)
|
||
|
||
---
|
||
|
||
## 0) Goals
|
||
|
||
1. **Enterprise-grade tenant authorization**: predictable, auditable, least privilege.
|
||
2. **Capabilities-first**: feature code checks only capabilities (Gates/Policies), never raw roles.
|
||
3. **Membership management** in tenant panel: tenant Owners manage members & roles.
|
||
4. **No regressions**: existing tenant features remain usable; RBAC enforcement becomes consistent.
|
||
5. **Testable**: every sensitive permission boundary has regression tests.
|
||
|
||
---
|
||
|
||
## 1) Non-goals
|
||
|
||
- This spec does **not** create a global “MSP console” across tenants.
|
||
- This spec does **not** implement Entra group claims ingestion or Graph-based membership resolution.
|
||
- This spec does **not** change provider connection credentials strategy (that stays in Provider foundation specs).
|
||
- This spec does **not** redesign UI pages; it adds management and enforcement.
|
||
|
||
---
|
||
|
||
## 2) Terms & Principles
|
||
|
||
### 2.1 Two planes (already established by 064)
|
||
|
||
- **Tenant plane**: `/admin/t/{tenant}` uses Entra users from `users`.
|
||
- **Platform plane**: `/system` uses platform operators from `platform_users`.
|
||
- Cross-plane access is deny-as-not-found (404). This spec does not change that.
|
||
|
||
### 2.2 Capabilities-first
|
||
|
||
- Roles exist for UX, but **code checks capabilities**.
|
||
- Capabilities are registered in a **central registry**.
|
||
- A **role → capability mapping** is the only place that references role names.
|
||
|
||
### 2.3 Least privilege
|
||
|
||
- Readonly is view-only.
|
||
- Operator can run operations but cannot manage configuration/members/credentials or delete.
|
||
- Manager can manage tenant configuration and run operations; cannot manage memberships.
|
||
- Owner can manage memberships and “danger zone”.
|
||
|
||
---
|
||
|
||
## 3) Requirements (Functional)
|
||
|
||
### FR-001 Membership source of truth
|
||
|
||
Authorization MUST be derived from a tenant membership record for the current (user_id, tenant_id).
|
||
|
||
### FR-002 Tenant membership management UI
|
||
|
||
Tenant Owners MUST be able to:
|
||
- add members
|
||
- change roles
|
||
- remove members
|
||
|
||
### FR-003 Last owner protection
|
||
|
||
The system MUST prevent removing or demoting the last remaining Owner for a tenant.
|
||
|
||
### FR-004 Capability registry
|
||
|
||
A canonical tenant capability registry MUST exist (single source of truth).
|
||
|
||
### FR-005 Role to capability mapping
|
||
|
||
Tenant roles MUST map to capability sets via a central mapper (no distributed role checks).
|
||
|
||
### FR-006 Enforcement in server-side authorization
|
||
|
||
All mutations MUST be protected by Policies/Gates. UI hiding is insufficient.
|
||
|
||
### FR-007 Operator constraints
|
||
|
||
Operators MUST NOT be able to:
|
||
- manage members
|
||
- manage provider connections/credentials
|
||
- change tenant settings
|
||
- perform destructive actions (delete/force delete)
|
||
|
||
### FR-008 Readonly constraints
|
||
|
||
Readonly MUST NOT be able to mutate data OR start tenant operations.
|
||
|
||
### FR-009 Operations start permissions
|
||
|
||
Starting a tenant operation (enqueue-only actions) MUST require the relevant capability.
|
||
|
||
### FR-010 Audit logging for access-control changes
|
||
|
||
Membership add/remove/role_change MUST write AuditLog entries with stable action IDs and redacted data.
|
||
|
||
### FR-011 Tenant switcher and route scoping
|
||
|
||
Only tenants where the user has membership MUST be listable/selectable; non-member tenant routes MUST 404.
|
||
|
||
Additionally, tenant-plane global search MUST be tenant-scoped (non-members MUST see no results, and any result URLs must still be deny-as-not-found when accessed directly).
|
||
|
||
### FR-012 Regression tests
|
||
|
||
RBAC boundaries MUST be covered by tests (positive + negative cases).
|
||
|
||
---
|
||
|
||
## 4) Requirements (Non-functional)
|
||
|
||
### NFR-001 Performance
|
||
|
||
Membership/capability evaluation MUST be O(1) per request after initial load (no N+1).
|
||
|
||
### NFR-002 Data minimization
|
||
|
||
No user secrets are stored; only Entra identifiers and minimal profile fields.
|
||
|
||
### NFR-003 DB-only render guarantee
|
||
|
||
RBAC UI surfaces (members listing) MUST be DB-only at render time (no outbound HTTP, no Graph).
|
||
|
||
### NFR-004 Observability
|
||
|
||
AuditLog and denied actions MUST be diagnosable without leaking secrets.
|
||
|
||
---
|
||
|
||
## 5) Data Model
|
||
|
||
### 5.1 Table: tenant_memberships
|
||
|
||
Note: The `tenant_memberships` table is already present in the repository (introduced by an earlier migration). This feature verifies the schema and treats it as the source of truth for tenant-plane authorization.
|
||
|
||
Columns:
|
||
- `id` (uuid, primary key)
|
||
- `tenant_id` (FK to tenants)
|
||
- `user_id` (FK to users)
|
||
- `role` (enum: `owner|manager|operator|readonly`)
|
||
- `source` (enum: `manual|entra_group|entra_app_role|break_glass`, default `manual`)
|
||
- `source_ref` (nullable string)
|
||
- `created_by_user_id` (nullable FK to `users`)
|
||
- `created_at`, `updated_at`
|
||
|
||
Constraints:
|
||
- unique `(tenant_id, user_id)`
|
||
- index `(tenant_id, role)`
|
||
- FK constraints (tenant_id/user_id/created_by_user_id)
|
||
|
||
### 5.2 Optional (deferred): tenant_invites
|
||
|
||
Not required for v1 unless you want email-based invites without the user existing in DB.
|
||
|
||
---
|
||
|
||
## 6) Capability Registry & Role Mapping
|
||
|
||
### 6.1 Naming convention
|
||
|
||
Capabilities are strings, and this repository’s canonical registry is `App\Support\Auth\Capabilities`.
|
||
|
||
Tenant-scoped capabilities are defined as `<namespace>.<action>`, with some namespaces using underscores (e.g. `tenant_membership.manage`).
|
||
|
||
### 6.2 Canonical capabilities (v1 baseline)
|
||
|
||
Minimum set (extendable, but these are the baseline contracts as of 2026-01-28):
|
||
|
||
Tenant:
|
||
- `tenant.view`
|
||
- `tenant.manage`
|
||
- `tenant.delete`
|
||
- `tenant.sync`
|
||
|
||
Membership:
|
||
- `tenant_membership.view`
|
||
- `tenant_membership.manage`
|
||
|
||
Tenant role mappings (optional in v1; no Graph resolution at render time):
|
||
- `tenant_role_mapping.view`
|
||
- `tenant_role_mapping.manage`
|
||
|
||
Providers:
|
||
- `provider.view`
|
||
- `provider.manage`
|
||
- `provider.run`
|
||
|
||
Audit:
|
||
- `audit.view`
|
||
|
||
Backup schedules:
|
||
- `tenant_backup_schedules.manage`
|
||
- `tenant_backup_schedules.run`
|
||
|
||
### 6.3 Role → capability mapping (v1)
|
||
|
||
Rules:
|
||
|
||
Readonly:
|
||
- `tenant.view`
|
||
- `tenant_membership.view`
|
||
- `tenant_role_mapping.view`
|
||
- `provider.view`
|
||
- `audit.view`
|
||
|
||
Operator:
|
||
- Readonly +
|
||
- `tenant.sync`
|
||
- `provider.run`
|
||
|
||
Manager:
|
||
- Operator +
|
||
- `tenant.manage`
|
||
- `provider.manage`
|
||
- NOT: `tenant_membership.manage` (Owner-only)
|
||
- NOT: `tenant_role_mapping.manage` (Owner-only in v1)
|
||
- Optional: `tenant.delete` if you explicitly decide managers can delete tenants (default: Owner-only)
|
||
|
||
Owner:
|
||
- Manager +
|
||
- `tenant_membership.manage`
|
||
- `tenant_role_mapping.manage`
|
||
- `tenant.delete`
|
||
|
||
---
|
||
|
||
## 7) Authorization Architecture
|
||
|
||
### 7.1 Membership resolution
|
||
|
||
Given current user + current tenant (Filament tenant):
|
||
- Load membership: `tenant_memberships` row for (user_id, tenant_id)
|
||
- If missing: tenant access is deny-as-not-found (404) (membership scoping rule).
|
||
|
||
### 7.2 Capability resolution
|
||
|
||
- Resolve role from membership
|
||
- Map role → capability set
|
||
- Cache in-request. Optional: short-lived cache keyed `(user_id, tenant_id)` max 60s (DB-only).
|
||
|
||
### 7.3 Gates and Policies
|
||
|
||
- Define per-capability Gates for all entries in `App\Support\Auth\Capabilities`.
|
||
- Resources MUST call Gate/Policies for:
|
||
- pages
|
||
- table actions
|
||
- bulk actions
|
||
- form actions
|
||
- relation manager actions
|
||
|
||
No feature code checks role strings directly (or uses role helper methods) outside the central mapping/resolver.
|
||
|
||
---
|
||
|
||
## 8) UI: Tenant Members Management (Admin Panel)
|
||
|
||
### 8.1 Location
|
||
|
||
Tenant-scoped settings section:
|
||
- `Settings → Members` (or `Tenants → View Tenant → Members` relation manager), consistent with your existing navigation style.
|
||
|
||
### 8.2 List view
|
||
|
||
Columns:
|
||
- user name
|
||
- user email
|
||
- role (badge)
|
||
- added_at
|
||
|
||
Actions:
|
||
- Add member (Owner only)
|
||
- Change role (Owner only)
|
||
- Remove member (Owner only)
|
||
|
||
### 8.3 Add member flow (v1 minimal, enterprise-safe)
|
||
|
||
Input:
|
||
- Entra email (UPN) or existing user picker
|
||
|
||
Behavior:
|
||
- If matching user exists (email match): create membership row.
|
||
- If not found:
|
||
- v1: Require the user to sign in first (cleaner), to avoid user identity conflicts.
|
||
|
||
### 8.4 Last owner protection
|
||
|
||
- If membership is last owner: remove/demote blocked with clear message.
|
||
- Emit AuditLog `tenant_membership.last_owner_blocked` (optional).
|
||
|
||
---
|
||
|
||
## 9) Audit Logging
|
||
|
||
### 9.1 Canonical action_ids
|
||
|
||
- `tenant_membership.add`
|
||
- `tenant_membership.role_change`
|
||
- `tenant_membership.remove`
|
||
- Optional: `tenant_membership.last_owner_blocked`
|
||
|
||
### 9.2 Minimal log payload
|
||
|
||
- actor_user_id
|
||
- tenant_id
|
||
- target_user_id
|
||
- before_role/after_role where relevant
|
||
- timestamp
|
||
- ip (optional)
|
||
|
||
No secrets, no tokens.
|
||
|
||
---
|
||
|
||
## 10) Repo-wide Enforcement Sweep (must-do)
|
||
|
||
### 10.1 Destructive actions policy
|
||
|
||
Any destructive action MUST:
|
||
- have server-side authorization (Policy/Gate)
|
||
- have `requiresConfirmation()`
|
||
- have at least one negative test (operator/readonly cannot)
|
||
|
||
### 10.2 “Operator cannot manage”
|
||
|
||
Specifically enforce:
|
||
- Provider connection delete/disable/credential rotate
|
||
- Tenant settings mutations
|
||
- Membership changes
|
||
- Force delete actions
|
||
|
||
---
|
||
|
||
## 11) Tests (Pest)
|
||
|
||
### 11.1 Unit tests
|
||
|
||
- Role → capability mapping invariants:
|
||
- readonly has no start/manage
|
||
- operator cannot manage members/settings/providers/credentials
|
||
- owner has members.manage
|
||
- Last owner guard logic
|
||
|
||
### 11.2 Feature tests
|
||
|
||
- Membership scoping:
|
||
- tenant list/switcher shows only memberships
|
||
- non-member route returns 404
|
||
- Membership management:
|
||
- owner can add/change/remove
|
||
- manager/operator/readonly cannot
|
||
- last owner cannot be removed/demoted
|
||
- Provider constraints:
|
||
- operator cannot delete provider connection or rotate credentials
|
||
- Operations starts:
|
||
- operator can start allowed operation (creates OperationRun) if capability exists
|
||
- readonly cannot start operations
|
||
|
||
### 11.3 Regression guard (optional but recommended)
|
||
|
||
- Architecture/grep test to flag role-string checks in `app/Filament/**` and `app/Jobs/**` (except in the central role→capability mapper).
|
||
|
||
---
|
||
|
||
## 12) Acceptance Criteria (Definition of Done)
|
||
|
||
- Tenant members management works; last owner rule enforced.
|
||
- Operator cannot manage/delete sensitive resources (tested).
|
||
- Readonly is view-only across tenant plane (tested).
|
||
- All new mutations are Policy/Gate enforced and audit logged.
|
||
- No outbound HTTP during render/hydration for tenant RBAC UI.
|
||
- No role-string checks exist outside the central mapper/registry.
|
||
|
||
---
|
||
|
||
## 13) v2 Roadmap (Explicit)
|
||
|
||
- Entra group-to-role mapping (scheduled sync, no render-time Graph calls)
|
||
- Invite tokens (email-based) if needed
|
||
- Custom roles per tenant
|
||
- Impersonation (audited, time-limited)
|
||
- System console global views (cross-tenant dashboards) |