CluPilotCloud/app/Livewire/Admin/Roles.php

165 lines
5.2 KiB
PHP

<?php
namespace App\Livewire\Admin;
use App\Models\Operator;
use Illuminate\Support\Facades\Gate;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
/**
* Wer was darf — als Matrix, nicht als Migration.
*
* Bis hierher standen die Rechte einer Rolle in Migrationen und die Liste der
* zulässigen Rollennamen an DREI Stellen im Code. Das ist kein Schönheitsfehler
* gewesen: `Developer` existierte als Rolle und wurde vom Einladen-Formular nie
* angeboten, weil eine der drei Listen nicht mitgezogen worden war.
*
* Deshalb kommt hier alles aus der Datenbank. Eine Rolle, die es gibt, steht
* auf diesem Bildschirm — und was auf diesem Bildschirm steht, gilt.
*
* ZWEI Grenzen, beide mit Grund:
*
* - **Owner ist unantastbar.** Ein Klick, der dem Owner Rechte nimmt, sperrt
* den Betreiber aus seiner eigenen Installation aus, und es gibt niemanden
* mehr, der es zurücknehmen könnte.
* - **Alles läuft auf dem `operator`-Guard.** Genau diese Verwechslung hat
* `dpa.manage` zusätzlich auf `web` hinterlassen, wo es kein Betreiber je
* sieht — Operator löst gegen `operator` auf (siehe dessen `$guard_name`).
*/
#[Layout('layouts.admin')]
class Roles extends Component
{
public const GUARD = 'operator';
/** @var array<int, array<int, string>> Rollen-ID => angehakte Berechtigungen */
public array $granted = [];
public string $newRole = '';
public function mount(): void
{
abort_unless(Gate::allows('staff.manage'), 403);
$this->loadGrants();
}
public function save(int $roleId): void
{
$this->authorize('staff.manage');
$role = $this->role($roleId);
if ($role->name === 'Owner') {
$this->addError('granted.'.$roleId, __('roles.owner_locked'));
return;
}
// Nur bekannte Namen: die Liste kommt aus dem Browser.
$wanted = array_values(array_intersect(
$this->granted[$roleId] ?? [],
$this->allPermissions(),
));
$role->syncPermissions($wanted);
// Spatie hält die Rechte in einem Zwischenspeicher. Ohne das hier
// gölte die Änderung erst, wenn der Zwischenspeicher von selbst
// abläuft — und die Seite zeigte währenddessen das Neue an, während
// das Alte wirkt.
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->loadGrants();
$this->dispatch('notify', message: __('roles.saved', ['role' => $role->name]));
}
public function create(): void
{
$this->authorize('staff.manage');
$this->validate(['newRole' => ['required', 'string', 'max:64', 'regex:/^[\pL][\pL\pN \-]*$/u']]);
Role::findOrCreate(trim($this->newRole), self::GUARD);
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->newRole = '';
$this->loadGrants();
$this->dispatch('notify', message: __('roles.created'));
}
public function delete(int $roleId): void
{
$this->authorize('staff.manage');
$role = $this->role($roleId);
if ($role->name === 'Owner') {
$this->addError('newRole', __('roles.owner_locked'));
return;
}
// Eine Rolle zu löschen, die jemand hält, nähme diesem Menschen
// wortlos jeden Zugang — er stünde beim nächsten Anmelden ohne Rolle
// da und käme in die Konsole nicht mehr hinein.
if ($role->users()->exists()) {
$this->addError('newRole', __('roles.in_use', ['role' => $role->name]));
return;
}
$role->delete();
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->loadGrants();
$this->dispatch('notify', message: __('roles.deleted'));
}
private function role(int $roleId): Role
{
return Role::query()
->where('guard_name', self::GUARD)
->findOr($roleId, fn () => abort(404));
}
/** @return array<int, string> */
private function allPermissions(): array
{
return Permission::query()
->where('guard_name', self::GUARD)
->orderBy('name')
->pluck('name')
->all();
}
private function loadGrants(): void
{
$this->granted = Role::query()
->where('guard_name', self::GUARD)
->with('permissions')
->get()
->mapWithKeys(fn (Role $role) => [$role->id => $role->permissions->pluck('name')->all()])
->all();
}
public function render()
{
return view('livewire.admin.roles', [
'roles' => Role::query()
->where('guard_name', self::GUARD)
->withCount('users')
->orderBy('name')
->get(),
'permissions' => $this->allPermissions(),
// Wie viele Menschen an dieser Rolle hängen — damit niemand blind
// etwas wegnimmt, das gerade sechs Leute benutzen.
'protectedRole' => 'Owner',
'ownRoles' => Operator::find(auth('operator')->id())?->roles->pluck('name')->all() ?? [],
]);
}
}