TenantAtlas/app/Filament/Pages/BreakGlassRecovery.php
Ahmed Darrazi 3b1dd98f52 feat(rbac): Implement Tenant RBAC v1
This commit introduces a comprehensive Role-Based Access Control (RBAC) system for TenantAtlas.

- Implements authentication via Microsoft Entra ID (OIDC).
- Manages authorization on a per-Suite-Tenant basis using a  table.
- Follows a capabilities-first approach, using Gates and Policies.
- Includes a break-glass mechanism for platform superadmins.
- Adds policies for bootstrapping tenants and managing admin responsibilities.
2026-01-25 16:01:50 +01:00

98 lines
3.2 KiB
PHP

<?php
namespace App\Filament\Pages;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Auth\TenantMembershipManager;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use UnitEnum;
class BreakGlassRecovery extends Page
{
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-shield-exclamation';
protected static string|UnitEnum|null $navigationGroup = 'System';
protected static ?string $navigationLabel = 'Break-glass recovery';
protected static ?int $navigationSort = 999;
protected string $view = 'filament.pages.break-glass-recovery';
public static function canAccess(): bool
{
$user = auth()->user();
return $user instanceof User && $user->isPlatformSuperadmin();
}
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
return [
Action::make('bootstrap_recover')
->label('Assign owner (recovery)')
->color('danger')
->requiresConfirmation()
->modalHeading('Break-glass: assign owner')
->modalDescription('This grants Owner access to a tenant. Use for recovery only. This action is audited.')
->form([
Select::make('tenant_id')
->label('Tenant')
->required()
->searchable()
->options(fn (): array => Tenant::query()
->where('status', 'active')
->orderBy('name')
->pluck('name', 'id')
->all()),
Select::make('user_id')
->label('User')
->required()
->searchable()
->options(fn (): array => User::query()
->orderBy('name')
->pluck('name', 'id')
->all()),
])
->action(function (array $data, TenantMembershipManager $manager): void {
$actor = auth()->user();
if (! $actor instanceof User || ! $actor->isPlatformSuperadmin()) {
abort(403);
}
$tenant = Tenant::query()
->where('status', 'active')
->whereKey((int) $data['tenant_id'])
->first();
if (! $tenant instanceof Tenant) {
Notification::make()->title('Tenant not found')->danger()->send();
return;
}
$member = User::query()->whereKey((int) $data['user_id'])->first();
if (! $member instanceof User) {
Notification::make()->title('User not found')->danger()->send();
return;
}
$manager->bootstrapRecover($tenant, $actor, $member);
Notification::make()->title('Owner assigned')->success()->send();
}),
];
}
}