# Actionable Tasks for Tenant RBAC v1 **Feature**: Tenant RBAC v1 **Branch**: `062-tenant-rbac-v1` **Plan**: `specs/062-tenant-rbac-v1/plan.md` This task list is dependency-ordered and test-driven. It implements: - Entra (OIDC) identity (no Entra credentials stored) - Suite-tenant authorization via `tenant_memberships` (SoT) - Capabilities-first gates/policies (no role checks in feature code) - Tenant switcher + direct route enforcement (non-members = 404) - Audit logging with canonical action_ids - Break-glass platform superadmin recovery --- ## Phase 0 — Discovery / Fit Check - [x] T001 [P] Confirm existing auth entrypoints (where OIDC callback/upsert happens) and Filament tenancy resolver (where current tenant is set). - [x] T002 [P] Confirm existing `User` / `Tenant` models and current schema (do NOT create duplicates). Identify required columns for Entra identity: `entra_tenant_id (tid)`, `entra_object_id (oid)`. - [x] T003 [P] Identify existing AuditLog service/model and how to write audit entries (target format + redaction). --- ## Phase 1 — Schema (RBAC source of truth) - [x] T004 Create migration `create_tenant_memberships_table` with: - `tenant_id`, `user_id`, `role` (`owner|manager|operator|readonly`) - `source` (`manual|entra_group|entra_app_role|break_glass`) - `source_ref` (nullable) - `created_by_user_id` (nullable) - unique `(tenant_id, user_id)` and index `(tenant_id, role)` - [ ] T005 (Optional, but supported) Create migration `create_tenant_role_mappings_table` with: - `tenant_id`, `mapping_type` (`entra_group|entra_app_role`), `external_id`, `role`, `is_enabled` - unique `(tenant_id, mapping_type, external_id)` - [x] T006 Add/adjust `users` columns if missing: `entra_tenant_id` (tid), `entra_object_id` (oid) + unique index `(entra_tenant_id, entra_object_id)`. - [x] T007 Run migrations. --- ## Phase 2 — Models + Capability Registry (capabilities-first) - [x] T008 Create `app/Support/Auth/Capabilities.php` as the canonical allowlist (constants/enum) of capability strings. - [x] T009 Create `app/Services/Auth/RoleCapabilityMap.php` (single source of truth) mapping roles → capabilities. - [x] T010 Create `app/Services/Auth/CapabilityResolver.php`: - resolves membership for (user, tenant) once per request (no N+1) - answers `can($capability)` using the registry + map - [x] T011 Register Gates in `app/Providers/AuthServiceProvider.php` using `CapabilityResolver` (no direct role checks). - [x] T012 Add model `TenantMembership` and (if used) `TenantRoleMapping` with relationships: - `Tenant::memberships()`, `User::tenantMemberships()` - [x] T013 Unit tests: - `CapabilitiesRegistryTest`: role map only references registry entries - `CapabilityResolverTest`: Owner/Manager/Operator/Readonly mapping works and is deterministic --- ## Phase 3 — Tenant Isolation (switcher + deny-as-not-found) - [x] T014 Enforce tenant switcher scoping: only tenants with a membership are listable/selectable for a user. - [x] T015 Enforce route-level deny-as-not-found: - direct access to `/t/{tenant}` and tenant-scoped resources returns 404 when user is not a member. - member without capability returns 403. - [x] T016 Feature tests: - `TenantSwitcherScopeTest`: only membership tenants appear - `TenantRouteDenyAsNotFoundTest`: non-member gets 404 for direct URL --- ## Phase 4 — Suite Tenant Membership Management UI (Tenant → Members) - [x] T017 Add a Filament Relation Manager (or equivalent) under `Settings → Tenants` to manage memberships: - list members + role - add member (select existing user) + role - edit member role - remove member - [x] T018 Implement **Last Owner Guard**: - prevent removing/demoting last `owner` membership (clear UI message) - [x] T019 Implement **Bootstrap assign**: - on tenant creation, creator becomes Owner (action_id `tenant_membership.bootstrap_assign`) - [x] T020 Implement **Bootstrap recover** (platform superadmin path): - add/assign Owner when needed (action_id `tenant_membership.bootstrap_recover`) - [x] T021 Feature tests: - `TenantMembershipCrudTest` - `LastOwnerGuardTest` - `TenantBootstrapAssignTest` --- ## Phase 5 — Audit Logging (canonical action_ids) - [x] T022 Add audit logging for membership and mapping changes with canonical action_ids: - `tenant_membership.add` - `tenant_membership.role_change` - `tenant_membership.remove` - `tenant_membership.bootstrap_assign` - `tenant_membership.bootstrap_recover` - `tenant_role_mapping.create|update|delete` (if mappings are enabled) Audit entries must be redacted (no secrets; minimal identity data). - [x] T023 Feature test `MembershipAuditLogTest` ensures audit entries are written on add/change/remove and contain no sensitive fields. --- ## Phase 6 — Break-glass Platform Superadmin (recovery) - [x] T024 Implement (or confirm existing) local platform superadmin authentication separate from Entra users. - [x] T025 Add a persistent UI banner when authenticated as break-glass. - [x] T026 Ensure platform superadmin can manage memberships across all tenants for recovery (at least to add an Owner). - [x] T027 Feature test `BreakGlassRecoveryTest`: - can assign owner to tenant - actions are audited with bootstrap_recover --- ## Phase 7 — Optional: Entra Mapping (deferred execution in v1) - [ ] T028 (Optional) Add UI to manage `tenant_role_mappings` (no Graph calls for resolution in v1). - [ ] T029 (Optional) Test that mapping records are tenant-scoped and audited on create/update/delete. --- ## Phase 8 — Quality Gates - [x] T030 Run formatting: `./vendor/bin/sail php ./vendor/bin/pint --dirty` - [x] T031 Run focused tests: `./vendor/bin/sail artisan test tests/Feature/TenantRBAC --stop-on-failure` --- ## Notes / Guardrails - Non-member access = **404** (deny-as-not-found). Member without capability = **403**. - No feature code may use `role == ...` checks. Always gates/capabilities. - Do not add any render-time Graph calls (group/app-role resolution is deferred unless explicitly scheduled as a job in a later feature).