netgescon-day0/app/Services/ProgramAclService.php

278 lines
8.5 KiB
PHP
Executable File

<?php
namespace App\Services;
use App\Models\ProgramAclRule;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Schema;
class ProgramAclService
{
/** @return array<string, array<string, mixed>> */
public function catalog(): array
{
$programs = config('program_acl.programs', []);
return is_array($programs) ? $programs : [];
}
/** @return array<string, string> */
public function programOptions(): array
{
$options = [];
foreach ($this->catalog() as $key => $definition) {
$label = trim((string) ($definition['label'] ?? $key));
$description = trim((string) ($definition['description'] ?? ''));
$options[$key] = $description !== '' ? ($label . ' - ' . $description) : $label;
}
return $options;
}
/** @return array<int, string> */
public function manageableRolesFor(User $actor): array
{
if ($actor->hasRole('super-admin')) {
return ['admin', 'amministratore', 'collaboratore', 'fornitore', 'condomino', 'inquilino', 'proprietario'];
}
if ($actor->hasRole('admin')) {
return ['amministratore', 'collaboratore', 'fornitore', 'condomino', 'inquilino', 'proprietario'];
}
if ($actor->hasRole('amministratore')) {
return ['collaboratore'];
}
return [];
}
public function canManageRole(User $actor, string $role): bool
{
return in_array($role, $this->manageableRolesFor($actor), true);
}
public function manageableUsersQuery(User $actor): Builder
{
$query = User::query()->with('roles')->orderBy('name');
if ($actor->hasAnyRole(['super-admin', 'admin'])) {
return $query;
}
if (! $actor->hasRole('amministratore')) {
return $query->whereRaw('1 = 0');
}
if (! Schema::hasColumn('users', 'amministratore_id')) {
return $query->whereRaw('1 = 0');
}
$amministratoreId = (int) ($actor->amministratore?->id ?? 0);
if ($amministratoreId <= 0) {
return $query->whereRaw('1 = 0');
}
return $query
->where('amministratore_id', $amministratoreId)
->whereHas('roles', static function (Builder $roleQuery): void {
$roleQuery->where('name', 'collaboratore');
});
}
/** @return array<int, string> */
public function manageableUserOptions(User $actor): array
{
return $this->manageableUsersQuery($actor)
->get(['id', 'name', 'email'])
->mapWithKeys(static function (User $user): array {
$label = trim((string) $user->name);
$email = trim((string) $user->email);
if ($label === '') {
$label = $email !== '' ? $email : ('Utente #' . (string) $user->id);
} elseif ($email !== '') {
$label .= ' <' . $email . '>';
}
return [(int) $user->id => $label];
})
->all();
}
public function canManageUser(User $actor, User $target): bool
{
if ($actor->hasAnyRole(['super-admin', 'admin'])) {
return true;
}
if (! $actor->hasRole('amministratore')) {
return false;
}
if (! $target->hasRole('collaboratore') || ! Schema::hasColumn('users', 'amministratore_id')) {
return false;
}
return (int) $target->amministratore_id === (int) ($actor->amministratore?->id ?? 0);
}
/** @return array{allow: array<int, string>, deny: array<int, string>} */
public function roleRules(string $role): array
{
if (! $this->hasRulesTable()) {
return ['allow' => [], 'deny' => []];
}
$rules = ProgramAclRule::query()
->forRole($role)
->get(['program_key', 'is_allowed']);
return [
'allow' => $rules->where('is_allowed', true)->pluck('program_key')->values()->all(),
'deny' => $rules->where('is_allowed', false)->pluck('program_key')->values()->all(),
];
}
/** @return array{allow: array<int, string>, deny: array<int, string>} */
public function userRules(int $userId): array
{
if (! $this->hasRulesTable()) {
return ['allow' => [], 'deny' => []];
}
$rules = ProgramAclRule::query()
->forUser($userId)
->get(['program_key', 'is_allowed']);
return [
'allow' => $rules->where('is_allowed', true)->pluck('program_key')->values()->all(),
'deny' => $rules->where('is_allowed', false)->pluck('program_key')->values()->all(),
];
}
/** @param array<int, string> $allow
* @param array<int, string> $deny */
public function replaceRoleRules(string $role, array $allow, array $deny): void
{
if (! $this->hasRulesTable()) {
return;
}
ProgramAclRule::query()->forRole($role)->delete();
$this->insertRules('role', $role, $allow, $deny);
}
/** @param array<int, string> $allow
* @param array<int, string> $deny */
public function replaceUserRules(int $userId, array $allow, array $deny): void
{
if (! $this->hasRulesTable()) {
return;
}
ProgramAclRule::query()->forUser($userId)->delete();
$this->insertRules('user', (string) $userId, $allow, $deny);
}
public function canAccessProgram(?User $user, string $programKey): bool
{
$catalog = $this->catalog();
if (! isset($catalog[$programKey]) || ! $user instanceof User) {
return false;
}
if ($user->hasRole('super-admin')) {
return true;
}
$roleNames = $user->getRoleNames()->values()->all();
if ($this->hasRulesTable()) {
$userRule = ProgramAclRule::query()
->forUser((int) $user->id)
->where('program_key', $programKey)
->first();
if ($userRule instanceof ProgramAclRule) {
return (bool) $userRule->is_allowed;
}
if ($roleNames !== []) {
$roleRules = ProgramAclRule::query()
->where('scope_type', 'role')
->whereIn('scope_key', $roleNames)
->where('program_key', $programKey)
->get(['is_allowed']);
if ($roleRules->contains(static fn(ProgramAclRule $rule): bool => $rule->is_allowed === false)) {
return false;
}
if ($roleRules->contains(static fn(ProgramAclRule $rule): bool => $rule->is_allowed === true)) {
return true;
}
}
}
$defaultRoles = $catalog[$programKey]['default_roles'] ?? [];
if (! is_array($defaultRoles)) {
return false;
}
foreach ($roleNames as $roleName) {
if (in_array($roleName, $defaultRoles, true)) {
return true;
}
}
return false;
}
/** @param array<int, string> $allow
* @param array<int, string> $deny */
private function insertRules(string $scopeType, string $scopeKey, array $allow, array $deny): void
{
if (! $this->hasRulesTable()) {
return;
}
$catalogKeys = array_keys($this->catalog());
$allow = array_values(array_unique(array_intersect($catalogKeys, $allow)));
$deny = array_values(array_unique(array_diff(array_intersect($catalogKeys, $deny), $allow)));
$rows = [];
foreach ($allow as $programKey) {
$rows[] = [
'scope_type' => $scopeType,
'scope_key' => $scopeKey,
'program_key' => $programKey,
'is_allowed' => true,
'created_at' => now(),
'updated_at' => now(),
];
}
foreach ($deny as $programKey) {
$rows[] = [
'scope_type' => $scopeType,
'scope_key' => $scopeKey,
'program_key' => $programKey,
'is_allowed' => false,
'created_at' => now(),
'updated_at' => now(),
];
}
if ($rows !== []) {
ProgramAclRule::query()->insert($rows);
}
}
private function hasRulesTable(): bool
{
return Schema::hasTable('program_acl_rules');
}
}