TenantAtlas/app/Policies/ProviderConnectionPolicy.php
ahmido bab01f07a9 feat: standardize platform provider identity (#166)
## Summary
- standardize Microsoft provider connections around explicit platform vs dedicated identity modes
- centralize admin-consent URL and runtime identity resolution so platform flows no longer fall back to tenant-local credentials
- add migration classification, richer consent and verification state handling, dedicated override management, and focused regression coverage

## Validation
- focused repo test coverage was added across provider identity, onboarding, audit, policy, guard, and migration flows
- latest explicit passing run in the workspace: `vendor/bin/sail artisan test --compact tests/Feature/AdminConsentCallbackTest.php tests/Feature/Audit/ProviderConnectionConsentAuditTest.php`

## Notes
- branch includes the full Spec 137 artifact set under `specs/137-platform-provider-identity/`
- target base branch: `dev`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #166
2026-03-13 16:29:08 +00:00

282 lines
8.3 KiB
PHP

<?php
namespace App\Policies;
use App\Models\ProviderConnection;
use App\Models\Tenant;
use App\Models\TenantMembership;
use App\Models\User;
use App\Models\Workspace;
use App\Support\Auth\Capabilities;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;
class ProviderConnectionPolicy
{
use HandlesAuthorization;
public function viewAny(User $user): Response|bool
{
$workspace = $this->currentWorkspace($user);
if (! $workspace instanceof Workspace) {
return Response::denyAsNotFound();
}
$entitledTenants = Tenant::query()
->select('tenants.*')
->join('tenant_memberships as policy_memberships', function ($join) use ($user): void {
$join->on('policy_memberships.tenant_id', '=', 'tenants.id')
->where('policy_memberships.user_id', '=', (int) $user->getKey());
})
->where('tenants.workspace_id', (int) $workspace->getKey())
->get();
if ($entitledTenants->isEmpty()) {
return true;
}
foreach ($entitledTenants as $tenant) {
if (Gate::forUser($user)->allows(Capabilities::PROVIDER_VIEW, $tenant)) {
return true;
}
}
return false;
}
public function view(User $user, ProviderConnection $connection): Response|bool
{
$workspace = $this->currentWorkspace($user);
if (! $workspace instanceof Workspace) {
return Response::denyAsNotFound();
}
$tenant = $this->tenantForConnection($connection);
if (! $tenant instanceof Tenant || (int) $tenant->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound();
}
if (! $this->isTenantMember($user, $tenant)) {
return Response::denyAsNotFound();
}
if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_VIEW, $tenant)) {
return false;
}
if ((int) $connection->tenant_id !== (int) $tenant->getKey()) {
return Response::denyAsNotFound();
}
if ((int) $connection->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound();
}
return true;
}
public function create(User $user): Response|bool
{
$workspace = $this->currentWorkspace($user);
if (! $workspace instanceof Workspace) {
return Response::denyAsNotFound();
}
$tenant = $this->resolveCreateTenant($workspace);
if (! $tenant instanceof Tenant || ! $this->isTenantMember($user, $tenant)) {
return Response::denyAsNotFound();
}
if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE, $tenant)) {
return false;
}
return true;
}
public function update(User $user, ProviderConnection $connection): Response|bool
{
$workspace = $this->currentWorkspace($user);
if (! $workspace instanceof Workspace) {
return Response::denyAsNotFound();
}
$tenant = $this->tenantForConnection($connection);
if (! $tenant instanceof Tenant || (int) $tenant->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound();
}
if (! $this->isTenantMember($user, $tenant)) {
return Response::denyAsNotFound();
}
if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE, $tenant)) {
return false;
}
if ((int) $connection->tenant_id !== (int) $tenant->getKey()) {
return Response::denyAsNotFound();
}
if ((int) $connection->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound();
}
return true;
}
public function delete(User $user, ProviderConnection $connection): Response|bool
{
$workspace = $this->currentWorkspace($user);
if (! $workspace instanceof Workspace) {
return Response::denyAsNotFound();
}
$tenant = $this->tenantForConnection($connection);
if (! $tenant instanceof Tenant || (int) $tenant->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound();
}
if (! $this->isTenantMember($user, $tenant)) {
return Response::denyAsNotFound();
}
if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE, $tenant)) {
return false;
}
if ((int) $connection->tenant_id !== (int) $tenant->getKey()) {
return Response::denyAsNotFound();
}
if ((int) $connection->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound();
}
return true;
}
public function manageDedicated(User $user, ProviderConnection $connection): Response|bool
{
$baseAccess = $this->update($user, $connection);
if ($baseAccess !== true) {
return $baseAccess;
}
$tenant = $this->tenantForConnection($connection);
if (! $tenant instanceof Tenant) {
return Response::denyAsNotFound();
}
return Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE_DEDICATED, $tenant);
}
public function changeConnectionType(User $user, ProviderConnection $connection): Response|bool
{
return $this->manageDedicated($user, $connection);
}
public function manageDedicatedCredential(User $user, ProviderConnection $connection): Response|bool
{
return $this->manageDedicated($user, $connection);
}
public function deleteDedicatedCredential(User $user, ProviderConnection $connection): Response|bool
{
return $this->manageDedicated($user, $connection);
}
private function currentWorkspace(User $user): ?Workspace
{
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
if (! is_int($workspaceId)) {
$filamentTenant = Filament::getTenant();
if ($filamentTenant instanceof Tenant) {
$workspaceId = (int) $filamentTenant->workspace_id;
}
}
if (! is_int($workspaceId)) {
return null;
}
$workspace = Workspace::query()->whereKey($workspaceId)->first();
if (! $workspace instanceof Workspace) {
return null;
}
if (! app(WorkspaceContext::class)->isMember($user, $workspace)) {
return null;
}
return $workspace;
}
private function resolveCreateTenant(Workspace $workspace): ?Tenant
{
$tenantExternalId = request()->query('tenant_id');
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
$lastTenantId = app(WorkspaceContext::class)->lastTenantId(request());
if (is_int($lastTenantId)) {
return Tenant::query()
->whereKey($lastTenantId)
->where('workspace_id', (int) $workspace->getKey())
->first();
}
$filamentTenant = Filament::getTenant();
if ($filamentTenant instanceof Tenant && (int) $filamentTenant->workspace_id === (int) $workspace->getKey()) {
return $filamentTenant;
}
return null;
}
return Tenant::query()
->where('external_id', $tenantExternalId)
->where('workspace_id', (int) $workspace->getKey())
->first();
}
private function tenantForConnection(ProviderConnection $connection): ?Tenant
{
if ($connection->relationLoaded('tenant') && $connection->tenant instanceof Tenant) {
return $connection->tenant;
}
if (is_int($connection->tenant_id) || is_numeric($connection->tenant_id)) {
return Tenant::query()->whereKey((int) $connection->tenant_id)->first();
}
return null;
}
private function isTenantMember(User $user, Tenant $tenant): bool
{
return TenantMembership::query()
->where('user_id', (int) $user->getKey())
->where('tenant_id', (int) $tenant->getKey())
->exists();
}
}