397 lines
15 KiB
PHP
Executable File
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);
|
|
}
|
|
}
|