Implements platform feature branch `285-workspace-rbac-environment-access`. Summary: - switch managed environment authorization to workspace-first role resolution with explicit environment-scope narrowing - rewire Filament pages, resources, policies, and user tenant access helpers to the shared access-scope resolver - add Spec 285 coverage across unit, feature, and browser tests plus full spec artifacts Validation: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Verification/ProviderExecutionReauthorizationTest.php tests/Feature/ProviderConnections/ProviderConnectionHealthCheckStartSurfaceTest.php tests/Feature/Tenants/TenantProviderBackedActionStartTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Audit/TenantMembershipAuditLogTest.php tests/Feature/Filament/TenantMembersTest.php tests/Feature/TenantRBAC/TenantMembershipCrudTest.php tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` Target branch: `platform-dev`. Follow-up integration path after merge: - `platform-dev` -> `dev`. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #344
200 lines
6.5 KiB
Markdown
200 lines
6.5 KiB
Markdown
# Data Model: Workspace-first RBAC & Environment Access Scoping
|
|
|
|
## Overview
|
|
|
|
Feature `285` does not add a new persisted source of truth. It clarifies and narrows responsibility across the existing membership data.
|
|
|
|
## Entities and responsibilities
|
|
|
|
### 1. WorkspaceMembership
|
|
|
|
**Status**: existing, canonical, role-bearing
|
|
|
|
**Responsibility**:
|
|
|
|
- defines whether a user belongs to a workspace
|
|
- defines the single role-bearing authority for capability resolution
|
|
- remains the source for last-owner protection and workspace membership audit
|
|
|
|
**Core attributes**:
|
|
|
|
- `workspace_id`
|
|
- `user_id`
|
|
- `role`
|
|
- `created_by_user_id`
|
|
- timestamps
|
|
|
|
**Rules**:
|
|
|
|
- every access decision starts here
|
|
- if no `WorkspaceMembership` exists, the user is out of scope for every workspace-owned and managed-environment-owned surface in that workspace
|
|
|
|
### 2. ManagedEnvironmentAccessScope
|
|
|
|
**Status**: logical successor to the current role-bearing `ManagedEnvironmentMembership` semantics
|
|
|
|
**Responsibility**:
|
|
|
|
- optionally narrows which managed environments a workspace member may open
|
|
- never grants capabilities by itself
|
|
- never stores a second role authority
|
|
|
|
**Expected backing**:
|
|
|
|
- the current `managed_environment_memberships` persistence reused or renamed in place
|
|
- no dual-write and no compatibility read path
|
|
|
|
**Required data meaning**:
|
|
|
|
- `managed_environment_id`
|
|
- `user_id`
|
|
- audit metadata such as creator and timestamps
|
|
|
|
**Forbidden data meaning**:
|
|
|
|
- role-bearing semantics
|
|
- capability-bearing semantics
|
|
- owner semantics separate from workspace ownership
|
|
|
|
**Rules**:
|
|
|
|
- scope rows are valid only for users who already have a `WorkspaceMembership` in the same workspace
|
|
- if no scope rows exist for a workspace member, environment visibility is inherited across the currently selectable managed environments in that workspace
|
|
- if scope rows exist for a workspace member, they form an allowlist
|
|
|
|
### 3. ManagedEnvironment
|
|
|
|
**Status**: existing, workspace-owned child resource
|
|
|
|
**Responsibility**:
|
|
|
|
- anchors provider connections, runs, findings, review artifacts, and other environment-owned resources to `workspace_id`
|
|
- provides the workspace boundary that the scope overlay must match
|
|
|
|
**Rules**:
|
|
|
|
- every managed-environment access decision must confirm that the environment belongs to the current workspace
|
|
- scope rows may only reference environments within the member's workspace
|
|
|
|
### 4. Environment-owned Resources
|
|
|
|
**Status**: existing derived subjects of the access contract
|
|
|
|
**In-scope examples**:
|
|
|
|
- `ProviderConnection`
|
|
- `OperationRun`
|
|
- `Finding`
|
|
- `EvidenceSnapshot`
|
|
- `ReviewPack`
|
|
- `TenantReview`
|
|
- onboarding drafts and sessions tied to a managed environment
|
|
|
|
**Responsibility**:
|
|
|
|
- reuse the shared access contract instead of implementing local role logic
|
|
|
|
## Derived access decision shapes
|
|
|
|
### Workspace membership summary
|
|
|
|
Used by membership-management surfaces and policy helpers.
|
|
|
|
**Fields**:
|
|
|
|
- `workspace_id`
|
|
- `user_id`
|
|
- `workspace_member` boolean
|
|
- `workspace_role` string or `null`
|
|
- `owner_guarded` boolean
|
|
|
|
### Managed-environment access decision
|
|
|
|
Used by `User::canAccessTenant()`, `WorkspaceContext`, selection flows, and environment-owned policies.
|
|
|
|
**Fields**:
|
|
|
|
- `workspace_id`
|
|
- `managed_environment_id`
|
|
- `user_id`
|
|
- `workspace_role` string or `null`
|
|
- `workspace_member` boolean
|
|
- `explicit_scope_rows_present` boolean
|
|
- `managed_environment_allowed` boolean
|
|
- `failed_boundary` string or `null`
|
|
- `required_capability` string or `null`
|
|
- `capability_allowed` boolean
|
|
- `denial_http_status` integer or `null`
|
|
- `provider_capability_context` string or `null`
|
|
|
|
**Decision order**:
|
|
|
|
1. confirm workspace membership
|
|
2. confirm the environment belongs to the workspace
|
|
3. if explicit scope rows exist, require the environment to be in the allowlist
|
|
4. evaluate required capability from the workspace role
|
|
5. hand off to downstream provider-capability or operability gates only after local access passes
|
|
|
|
### OperationRun access decision
|
|
|
|
Used by run drilldowns and monitoring surfaces.
|
|
|
|
**Fields**:
|
|
|
|
- `operation_run_id`
|
|
- `workspace_id`
|
|
- `managed_environment_id` nullable
|
|
- `user_id`
|
|
- `workspace_role` string or `null`
|
|
- `workspace_member` boolean
|
|
- `managed_environment_allowed` boolean
|
|
- `failed_boundary` string or `null`
|
|
- `required_capability` string or `null`
|
|
- `capability_allowed` boolean
|
|
- `denial_http_status` integer or `null`
|
|
|
|
**Decision order**:
|
|
|
|
- if the run is workspace-bound, use workspace membership plus required capability
|
|
- if the run is managed-environment-bound, apply the managed-environment access decision first and then required capability
|
|
|
|
## Invariants
|
|
|
|
- `WorkspaceMembership` is the only role-bearing source of truth.
|
|
- no managed-environment scope record may grant access without workspace membership.
|
|
- no managed-environment scope record may point outside the workspace of the current member.
|
|
- deleting workspace membership removes or invalidates any managed-environment scope rows for that user in the same workspace.
|
|
- non-membership or out-of-scope access resolves to `404`.
|
|
- in-scope members missing the required capability resolve to `403`.
|
|
- provider capability and operability checks do not run until local access passes.
|
|
|
|
## Audit expectations
|
|
|
|
- workspace role changes keep using the existing workspace membership audit path
|
|
- managed-environment scope mutations write audit records that describe scope narrowing or widening, not role reassignment
|
|
- last-owner protection remains anchored to workspace ownership only
|
|
|
|
## Denied-access diagnostic context
|
|
|
|
Denied-access logging remains derived rather than persisted.
|
|
|
|
**Expected diagnostic fields**:
|
|
|
|
- `workspace_id`
|
|
- `managed_environment_id` nullable
|
|
- `user_id`
|
|
- `failed_boundary` as a descriptive string such as `workspace_membership`, `managed_environment_scope`, or `capability`
|
|
- `required_capability` nullable
|
|
|
|
**Rules**:
|
|
|
|
- diagnostics must be sufficient to explain whether the denial happened at workspace membership, environment scope, or capability evaluation
|
|
- diagnostics must not include raw provider payloads, secrets, or implementation-only provider evidence
|
|
- the shared access decision is the only source from which denial diagnostics are derived
|
|
|
|
## Migration notes for implementation
|
|
|
|
- runtime implementation may repurpose the current `ManagedEnvironmentMembership` model or replace it in place with a clearer successor, but it must preserve the logical rules above
|
|
- if the current table name is retained temporarily, the implementation must still remove the old role-bearing meaning in the same slice
|
|
- no parallel legacy projection is allowed beyond what is strictly required to land the cutover atomically
|