# Feature Specification: Tenant RBAC v1 (Entra Login + Suite-Tenant Memberships) **Feature Branch**: `062-tenant-rbac-v1` **Created**: 2026-01-25 **Status**: Draft **Input**: User description: "# Feature 062 — Tenant RBAC v1 (Entra Login + Suite-Tenant Memberships + Optional Group/AppRole Mapping + Break-glass) **Stack:** Laravel 12 · PHP 8.4 · Filament v5 · Livewire v4 · Postgres **Scope:** Best-practice, scalable authorization for a multi-tenant SaaS/MSP suite: - Authentication via Microsoft Entra ID (OIDC) - Authorization via **suite-tenant** memberships in TenantAtlas (SoT) - Optional automation: Entra Groups / App Roles → memberships - Platform break-glass local superadmin (operator-safe, audited) **Goal:** Enable MSPs and customers to manage who can do what per Suite Tenant (customer + env), without mixing Microsoft tenant admin concepts into TenantAtlas authorization. --- ## 0. Key Concepts (avoid confusion) ### K-001 — Two different “tenants” 1) **Suite Tenant (TenantAtlas Tenant)**: a customer/environment container inside your app (e.g., “Customer A – PROD”, “Customer A – DEV”). 2) **Microsoft Tenant (Entra/Intune tenant)**: the target platform you connect to (Entra tenant ID GUID). TenantAtlas RBAC controls who can use TenantAtlas features per **Suite Tenant**. Microsoft (Entra/Intune) RBAC controls what the app is technically able to read/write. --- ## 1. Principles (Constitution-aligned) ### RBAC-001 — Capabilities-first, not role checks Feature code MUST check **capabilities/permissions** (Gates/Policies), never `if role == X`. Roles are a mapping layer only. ### RBAC-002 — Suite Tenant is the authorization scope All permissions are evaluated within the current Suite Tenant context. ### RBAC-003 — Audited changes All membership/role/permission changes must write an AuditLog entry (no secrets, no PII dumps). ### RBAC-004 — Safe defaults If a user is not a member of a Suite Tenant: deny access (404/403), no “implicit member”. ### RBAC-005 — Break-glass exists A local platform superadmin account exists to recover access when Entra integration fails. --- ## 2. Goals 1) Allow adding/removing members per Suite Tenant and setting their role via UI. 2) Support MSP reality: same person can have different roles across different Suite Tenants. 3) Keep authorization stable even if Entra groups change (source recorded). 4) Allow optional Entra group/app-role mapping to auto-provision memberships. 5) Support future fine-grained permissions without refactoring all features. --- ## 3. Non-Goals (v1) - Full local user management for all users (passwords etc.) — only break-glass. - Cross-tenant global RBAC (platform-wide “auditor across all tenants”) beyond superadmin. - Impersonation (future). - Per-resource row-level permissions beyond tenant scope (future). - Full SCIM provisioning (future). --- # 4. Role Model & Capabilities ## 4.1 Canonical roles (v1) - `owner` - `manager` - `operator` - `readonly` ## 4.2 Canonical capabilities (v1 baseline) Define these high-level permissions (more can be added later): **General** - `tenant.view` - `tenant.manage` (edit tenant metadata) **Providers (from Feature 061)** - `provider.view` - `provider.manage` (connections + credentials) - `provider.run` (check/snapshot/sync) **Operations** - `ops.view` (Monitoring → Operations) - `ops.run` (start ops) **Inventory** - `inventory.view` - `inventory.run` (run sync) **Policies** - `policy.view` - `policy.run` (sync, bulk ops) - `policy.restore` (if applicable) **Backups/Restore** - `backup.view` - `backup.run` - `restore.view` - `restore.execute` (high-risk) **Drift/Findings** - `drift.view` - `drift.run` ## 4.3 Role → capability mapping (v1 defaults) **Owner** - all capabilities for the Suite Tenant **Manager** - all except the highest-risk destructive actions (if you want a stricter split) - recommended v1: same as owner except `restore.execute` (optional) **Operator** - view + run operational tasks - cannot manage providers/credentials or tenant config - cannot execute destructive ops unless explicitly granted later **Readonly** - view-only across all tenant features ### Minimum enforced mapping for Provider v1 (must match Feature 061) - Owner/Manager: `provider.view`, `provider.manage`, `provider.run` - Operator: `provider.view`, `provider.run` - Readonly: `provider.view` --- # 5. Data Model ## 5.1 users Store Entra identity as stable keys: - `entra_tenant_id` (tid) - `entra_object_id` (oid) - `email` (optional/nullable; do not rely on it as identity) - `name` - timestamps Unique index: - `(entra_tenant_id, entra_object_id)`. ## 5.2 tenant_memberships (Suite Tenant authorization SoT) - `id` (uuid) - `tenant_id` (fk to suite tenant) - `user_id` (fk) - `role` (enum string) - `source` (`manual | entra_group | entra_app_role | break_glass`) - `source_ref` (nullable string; e.g., group id/app role id) - `created_by_user_id` (nullable; for manual changes) - `timestamps` Constraints: - unique `(tenant_id, user_id)` - index `(tenant_id, role)` ## 5.3 tenant_role_mappings (optional automation) Allows automatic provisioning based on Entra groups/app roles. - `id` (uuid) - `tenant_id` (fk) - `mapping_type` (`entra_group | entra_app_role`) - `external_id` (group GUID or appRole string) - `role` (enum string) - `is_enabled` (bool) - timestamps Constraints: - unique `(tenant_id, mapping_type, external_id)` ## 5.4 audit_logs (existing) Used for membership changes; must support: - actor - tenant - action_id - target - before/after (redacted) - timestamp --- # 6. Authentication & Provisioning ## 6.1 Entra login (OIDC) On successful login: 1) Upsert `users` by `(entra_tenant_id, entra_object_id)` 2) Determine accessible Suite Tenants by memberships 3) Set current tenant context via Filament tenant selection ## 6.2 Membership sources and precedence (v1) Membership evaluation per Suite Tenant: 1) Manual membership (`source=manual`) takes precedence over automated sources. 2) If no manual membership exists: - apply Entra mapping rules (groups/app roles) if configured. 3) If still no membership: deny. Record the source: - If created by mapping: `source=entra_group/app_role`, `source_ref=` ## 6.3 Entra group overage handling (v2-ready) v1 may rely on claims if available; if group claims are not present: - v1: treat mapping as optional; show “group overage not supported yet” - v2: add Graph call to resolve group membership (OperationRun-backed admin job, not render-time) --- # 7. Authorization (Policies/Gates) ## 7.1 Capability engine Implement a central `CapabilityResolver`: - input: user, tenant, membership role, optional overrides - output: `can($capability)` boolean All feature policies call `can()`. Forbidden: - direct role comparisons in feature code ## 7.2 Tenant scoping enforcement Every Filament resource query MUST scope to the current Suite Tenant and MUST enforce membership: - Non-members see 404 (deny-as-not-found) on tenant resources. --- # 8. UI Requirements (Filament) ## 8.1 Tenant “Members” management Add to **Settings → Tenants**: - Tab/Relation Manager: **Members** - Search existing users (by email/name) - Add member (select user) + role - Edit role - Remove member Permissions: - only Owner/Manager with `tenant.manage` can manage members - Operator/Readonly can view member list only if `tenant.view` (optional) Every change writes AuditLog. ## 8.2 Role mapping configuration (optional) Tenant detail page: - “Role Mappings” section - Add mapping: group/app-role → role - enable/disable mapping - show last sync attempt (if you add later) v1 can ship with UI but without group-membership resolution (if you choose). ## 8.3 Break-glass admin UX When logged in as break-glass: - Show a persistent banner “Break-glass account” - All actions are audited with source `break_glass` --- # 9. Break-glass Local Superadmin (Platform) ## 9.1 Purpose Allow platform operator to recover access if Entra login/mapping breaks. ## 9.2 Minimal implementation (v1) - local user table entry flagged `is_platform_superadmin = true` - password-based auth (local) OR separate guard - platform superadmin can: - view/manage all Suite Tenants - manage memberships - manage provider connections - must be audited and clearly indicated Constraints: - only one/few accounts - not used for day-to-day operations --- # 10. Tests & Guardrails ## 10.1 RBAC tests (required) - Membership required: non-member cannot access tenant resources (deny-as-not-found) - Role mapping: - Owner/Manager can manage members - Operator cannot manage credentials (ties into Feature 061) - Readonly cannot start operations ## 10.2 Capability resolver tests - role → capability mapping is stable - adding a new capability won’t break existing roles (safe defaults) ## 10.3 Audit tests - membership changes create audit log records - no secrets/PII in audit before/after ## 10.4 UI tests (Filament) - Members relation manager visible only to authorized roles - Role dropdown reflects canonical roles --- # 11. Migration & Backward Compatibility - Introduce membership enforcement gradually: - v1: create a default owner membership for the creator of a tenant (migration/seed) - existing dev tenants: backfill membership for current admin user - Ensure no tenant becomes inaccessible after deployment: - platform superadmin can always recover --- # 12. Task Plan (high level) ## Phase 0 — Discovery - Identify existing “tenant scoping” and any implicit access assumptions. - Locate current auth flow and where to hook Entra identity upsert. ## Phase 1 — Schema - `tenant_memberships` - `tenant_role_mappings` (optional) - user identity fields (`entra_tenant_id`, `entra_object_id`) if not present ## Phase 2 — Capability resolver + gates - Implement `CapabilityResolver` - Register Gates: `provider.manage`, `provider.run`, etc. ## Phase 3 — UI - Tenant Members manager (CRUD) - Optional role mapping UI ## Phase 4 — Break-glass - platform_superadmin account + banner + audit ## Phase 5 — Tests - RBAC + audit + UI tests --- # 13. Acceptance Criteria (DoD) 1) Users authenticate via Entra; user identities are stored by stable `(tid, oid)`. 2) A user’s access is determined by tenant_memberships for the current Suite Tenant. 3) Owner/Manager can add/remove members and set roles via UI; changes are audited. 4) Operator/Readonly restrictions are enforced across provider management and operations. 5) Optional: Entra group/app-role mappings can auto-provision memberships (at least config + data model). 6) Break-glass platform superadmin exists and can recover access; actions are audited and bannered. 7) No feature code uses direct role checks; only capabilities/gates/policies. --- ## Addendum — Bootstrap & Admin Responsibilities ### Context TenantAtlas operates across two distinct administrative planes: - **Microsoft tenant administration (Entra/Intune)** governs what the platform is technically allowed to do in the target Microsoft tenant (consent, permissions, RBAC assignments). - **TenantAtlas administration (Suite Tenant RBAC)** governs who is allowed to use TenantAtlas features for a given Suite Tenant (memberships, roles, operational actions). These roles may be held by the same person in small orgs, but must not be assumed to be the same in enterprise/MSP environments. ### FR-013 Admin Planes Are Distinct (No Assumptions) The system MUST NOT assume that: - a Microsoft tenant admin is automatically a TenantAtlas admin, or - a TenantAtlas admin is automatically a Microsoft tenant admin. TenantAtlas permissions are determined solely by `tenant_memberships` (and optional mappings), independent of Microsoft directory roles. **Acceptance:** - A user can be a TenantAtlas Owner/Manager without holding any Microsoft admin role. - A Microsoft admin can grant consent without being a TenantAtlas member (unless explicitly added). ### FR-014 Bootstrap Owner Assignment (First-Admin Rule) Each Suite Tenant MUST always have at least one Owner-capable administrator to prevent lockout. #### FR-014a Default Bootstrap Rule (v1) On Suite Tenant creation, the creator is assigned as `owner` membership (source: `manual`, created_by = creator). #### FR-014b Platform Recovery Rule (Break-glass) A platform superadmin MUST be able to assign/change the initial owner membership in case: - the creator no longer exists, - Entra login/mapping breaks, - the tenant was imported. #### FR-014c Non-Removable Last Owner Guard The system MUST prevent removing or demoting the last remaining `owner` membership for a Suite Tenant. **Acceptance:** - Attempting to remove the last Owner fails with a clear message. - Platform superadmin can recover by adding a new Owner first. ### FR-015 TenantAtlas Admin Responsibilities (v1) TenantAtlas admins (Owner/Manager) are responsible for: - managing Suite Tenant members and roles (tenant_memberships), - approving who can run operations and manage providers inside TenantAtlas, - reviewing audit logs for membership/role changes. Microsoft tenant admins are responsible for: - granting admin consent for the app, - ensuring required Graph permissions are granted, - maintaining any Microsoft-side RBAC/group prerequisites. **Acceptance:** - The UI communicates this split (e.g., small help text near membership management and provider setup links). ### FR-016 Audit Requirements for Bootstrap & Membership Management All bootstrap and membership management actions MUST be audited with canonical action_ids: - `tenant_membership.add` - `tenant_membership.role_change` - `tenant_membership.remove` - `tenant_membership.bootstrap_assign` (initial owner assignment) - `tenant_membership.bootstrap_recover` (platform superadmin recovery) Audit entries MUST include: - actor id - suite tenant id - target user id/email (minimal) - before/after role (redacted) - timestamp No secrets, no tokens, no Microsoft credentials. ### UI Requirements (Bootstrap-related) - Tenant creation flow MUST result in an Owner membership being present (visible in Tenant → Members). - Tenant → Members UI MUST show an “Owner” role and enforce “last owner cannot be removed/demoted”. - Platform superadmin UI MUST support adding an Owner membership if none exists or recovery is required.