netgescon-day0/app/Filament/Pages/SuperAdmin/ProgrammiAcl.php

397 lines
15 KiB
PHP
Executable File

<?php
namespace App\Filament\Pages\SuperAdmin;
use App\Models\User;
use App\Services\ProgramAclService;
use BackedEnum;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Support\Facades\Auth;
use UnitEnum;
class ProgrammiAcl extends Page implements HasForms
{
use InteractsWithForms;
protected static ?string $navigationLabel = 'ACL programmi';
protected static ?string $title = 'ACL programmi';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-shield-check';
protected static UnitEnum|string|null $navigationGroup = 'Super Admin';
protected static ?int $navigationSort = 4;
protected static ?string $slug = 'superadmin/acl-programmi';
protected string $view = 'filament.pages.super-admin.programmi-acl';
public ?array $data = [];
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore']);
}
public function mount(ProgramAclService $acl): void
{
$actor = Auth::user();
if (! $actor instanceof User) {
abort(403);
}
$roles = $acl->manageableRolesFor($actor);
$users = $acl->manageableUserOptions($actor);
$selectedRole = $this->resolveRequestedRole($acl, $actor, $roles);
$selectedUserId = $this->resolveRequestedUserId($acl, $actor, $users);
$this->getSchema('form')?->fill([
'selected_role' => $selectedRole,
'role_allow' => $selectedRole ? $acl->roleRules($selectedRole)['allow'] : [],
'role_deny' => $selectedRole ? $acl->roleRules($selectedRole)['deny'] : [],
'selected_user_id' => $selectedUserId,
'user_allow' => $selectedUserId ? $acl->userRules((int) $selectedUserId)['allow'] : [],
'user_deny' => $selectedUserId ? $acl->userRules((int) $selectedUserId)['deny'] : [],
]);
}
public function form(Schema $schema): Schema
{
/** @var User|null $actor */
$actor = Auth::user();
$acl = app(ProgramAclService::class);
$programOptions = $acl->programOptions();
$roleList = $acl->manageableRolesFor($actor instanceof User ? $actor : new User());
$roleOptions = array_combine($roleList, $roleList) ?: [];
$userOptions = $actor instanceof User ? $acl->manageableUserOptions($actor) : [];
return $schema
->statePath('data')
->components([
Section::make('Regole globali per ruolo')
->description('Il super admin gestisce i default globali. L amministratore puo operare solo sui collaboratori.')
->schema([
Select::make('selected_role')
->label('Ruolo')
->options($roleOptions)
->native(false),
Placeholder::make('role_hint')
->label('Note')
->content('Le regole ruolo valgono come default. Gli override utente hanno precedenza.'),
CheckboxList::make('role_allow')
->label('Programmi esplicitamente visibili')
->options($programOptions)
->columns(1),
CheckboxList::make('role_deny')
->label('Programmi esplicitamente nascosti')
->options($programOptions)
->columns(1),
]),
Section::make('Override per singolo utente')
->description('Usa gli override per eccezioni puntuali senza cambiare il default del gruppo.')
->schema([
Select::make('selected_user_id')
->label('Utente')
->options($userOptions)
->searchable()
->native(false),
Placeholder::make('user_hint')
->label('Perimetro')
->content($actor instanceof User && $actor->hasRole('amministratore')
? 'Puoi modificare solo i collaboratori collegati al tuo amministratore.'
: 'Puoi applicare override puntuali agli utenti gestibili dal tuo profilo.'),
CheckboxList::make('user_allow')
->label('Programmi resi visibili per questo utente')
->options($programOptions)
->columns(1),
CheckboxList::make('user_deny')
->label('Programmi nascosti per questo utente')
->options($programOptions)
->columns(1),
]),
]);
}
/** @param array<int, string> $roles */
private function resolveRequestedRole(ProgramAclService $acl, User $actor, array $roles): ?string
{
$requestedRole = trim((string) request()->query('selected_role', ''));
if ($requestedRole !== '' && $acl->canManageRole($actor, $requestedRole)) {
return $requestedRole;
}
return $roles[0] ?? null;
}
/** @param array<int, string> $users */
private function resolveRequestedUserId(ProgramAclService $acl, User $actor, array $users): ?int
{
$requestedUserId = (int) request()->query('selected_user_id', 0);
if ($requestedUserId > 0) {
$target = User::query()->find($requestedUserId);
if ($target instanceof User && $acl->canManageUser($actor, $target)) {
return (int) $target->id;
}
}
$firstKey = array_key_first($users);
return is_numeric($firstKey) ? (int) $firstKey : null;
}
public function loadRoleRules(ProgramAclService $acl): void
{
$state = $this->normalizedState();
$role = (string) ($state['selected_role'] ?? '');
$actor = Auth::user();
if (! $actor instanceof User || $role === '' || ! $acl->canManageRole($actor, $role)) {
Notification::make()->title('Ruolo non gestibile')->danger()->send();
return;
}
$rules = $acl->roleRules($role);
$this->fillState(array_merge($state, [
'role_allow' => $rules['allow'],
'role_deny' => $rules['deny'],
]));
}
public function saveRoleRules(ProgramAclService $acl): void
{
$state = $this->normalizedState();
$role = (string) ($state['selected_role'] ?? '');
$actor = Auth::user();
if (! $actor instanceof User || $role === '' || ! $acl->canManageRole($actor, $role)) {
Notification::make()->title('Ruolo non gestibile')->danger()->send();
return;
}
$acl->replaceRoleRules(
$role,
is_array($state['role_allow'] ?? null) ? $state['role_allow'] : [],
is_array($state['role_deny'] ?? null) ? $state['role_deny'] : [],
);
Notification::make()->title('ACL ruolo salvata')->success()->send();
$this->loadRoleRules($acl);
}
public function loadUserRules(ProgramAclService $acl): void
{
$state = $this->normalizedState();
$userId = (int) ($state['selected_user_id'] ?? 0);
$actor = Auth::user();
$target = $userId > 0 ? User::query()->find($userId) : null;
if (! $actor instanceof User || ! $target instanceof User || ! $acl->canManageUser($actor, $target)) {
Notification::make()->title('Utente non gestibile')->danger()->send();
return;
}
$rules = $acl->userRules((int) $target->id);
$this->fillState(array_merge($state, [
'user_allow' => $rules['allow'],
'user_deny' => $rules['deny'],
]));
}
public function saveUserRules(ProgramAclService $acl): void
{
$state = $this->normalizedState();
$userId = (int) ($state['selected_user_id'] ?? 0);
$actor = Auth::user();
$target = $userId > 0 ? User::query()->find($userId) : null;
if (! $actor instanceof User || ! $target instanceof User || ! $acl->canManageUser($actor, $target)) {
Notification::make()->title('Utente non gestibile')->danger()->send();
return;
}
$acl->replaceUserRules(
(int) $target->id,
is_array($state['user_allow'] ?? null) ? $state['user_allow'] : [],
is_array($state['user_deny'] ?? null) ? $state['user_deny'] : [],
);
Notification::make()->title('ACL utente salvata')->success()->send();
$this->loadUserRules($acl);
}
/** @return array<int, string> */
public function manageableRoles(): array
{
$actor = Auth::user();
return $actor instanceof User ? app(ProgramAclService::class)->manageableRolesFor($actor) : [];
}
/** @return array{roles: array<int, string>, programs: array<int, array<string, mixed>>} */
public function roleMatrixData(): array
{
$acl = app(ProgramAclService::class);
$roles = $this->manageableRoles();
$rows = [];
foreach ($acl->catalog() as $programKey => $definition) {
$label = trim((string) ($definition['label'] ?? $programKey));
$description = trim((string) ($definition['description'] ?? ''));
$defaultRoles = array_values(array_filter(array_map('strval', (array) ($definition['default_roles'] ?? []))));
$states = [];
foreach ($roles as $role) {
$rules = $acl->roleRules($role);
$source = 'default';
$isVisible = in_array($role, $defaultRoles, true);
if (in_array($programKey, $rules['deny'], true)) {
$source = 'deny';
$isVisible = false;
} elseif (in_array($programKey, $rules['allow'], true)) {
$source = 'allow';
$isVisible = true;
}
$states[$role] = [
'visible' => $isVisible,
'source' => $source,
];
}
$rows[] = [
'key' => $programKey,
'label' => $label,
'description' => $description,
'default_roles' => $defaultRoles,
'states' => $states,
];
}
return [
'roles' => $roles,
'programs' => $rows,
];
}
/** @return array<int, array<string, mixed>> */
public function rwMatrixData(): array
{
$roles = $this->manageableRoles();
$configRoles = (array) config('netgescon.user_roles', []);
$rows = [];
foreach ($configRoles as $role => $definition) {
if (! in_array($role, $roles, true)) {
continue;
}
$menus = (array) ($definition['menus'] ?? []);
foreach ($menus as $menuKey => $menuValue) {
$menuName = is_string($menuKey) ? $menuKey : (string) $menuValue;
$actions = is_array($menuValue)
? array_values(array_filter(array_map('strval', $menuValue)))
: array_values(array_filter(array_map('strval', (array) ($definition['permissions'] ?? []))));
$rows[] = [
'role' => $role,
'menu' => $menuName,
'actions' => $actions,
];
}
}
usort($rows, static function (array $left, array $right): int {
$roleCompare = strcmp((string) ($left['role'] ?? ''), (string) ($right['role'] ?? ''));
if ($roleCompare !== 0) {
return $roleCompare;
}
return strcmp((string) ($left['menu'] ?? ''), (string) ($right['menu'] ?? ''));
});
return $rows;
}
public function setRoleProgramVisibility(ProgramAclService $acl, string $role, string $programKey, bool $visible): void
{
$actor = Auth::user();
if (! $actor instanceof User || ! $acl->canManageRole($actor, $role) || ! array_key_exists($programKey, $acl->catalog())) {
Notification::make()->title('Regola non gestibile')->danger()->send();
return;
}
$rules = $acl->roleRules($role);
$allow = array_values(array_filter((array) $rules['allow'], static fn(string $key): bool => $key !== $programKey));
$deny = array_values(array_filter((array) $rules['deny'], static fn(string $key): bool => $key !== $programKey));
if ($visible) {
$allow[] = $programKey;
} else {
$deny[] = $programKey;
}
$acl->replaceRoleRules($role, $allow, $deny);
$state = $this->normalizedState();
if ((string) ($state['selected_role'] ?? '') === $role) {
$this->fillState(array_merge($state, [
'role_allow' => array_values(array_unique($allow)),
'role_deny' => array_values(array_unique($deny)),
]));
}
}
public function resetRoleProgramVisibility(ProgramAclService $acl, string $role, string $programKey): void
{
$actor = Auth::user();
if (! $actor instanceof User || ! $acl->canManageRole($actor, $role) || ! array_key_exists($programKey, $acl->catalog())) {
Notification::make()->title('Regola non gestibile')->danger()->send();
return;
}
$rules = $acl->roleRules($role);
$allow = array_values(array_filter((array) $rules['allow'], static fn(string $key): bool => $key !== $programKey));
$deny = array_values(array_filter((array) $rules['deny'], static fn(string $key): bool => $key !== $programKey));
$acl->replaceRoleRules($role, $allow, $deny);
$state = $this->normalizedState();
if ((string) ($state['selected_role'] ?? '') === $role) {
$this->fillState(array_merge($state, [
'role_allow' => $allow,
'role_deny' => $deny,
]));
}
}
/** @return array<string, mixed> */
private function normalizedState(): array
{
$state = $this->getSchema('form')?->getState() ?? [];
if (is_array($state['data'] ?? null)) {
return $state['data'];
}
return is_array($state) ? $state : (is_array($this->data) ? $this->data : []);
}
/** @param array<string, mixed> $state */
private function fillState(array $state): void
{
$this->getSchema('form')?->fill($state);
}
}