presseportale/app/Actions/Admin/UserImpersonation.php
Kevin Adametz 40783192d0 security: Rechteausweitung schließen & Admin-Panel feingranular gaten (K1, H1, H2, M8)
- users:manage/roles:manage auf allen User-/Rollen-Masken erzwungen;
  is_super_admin aus $fillable entfernt und nur per autorisiertem
  forceFill setzbar; zentraler Gate::before-Super-Admin-Kurzschluss (K1/M8).
- Nicht-Super-Admins können Super-Admin-Konten nicht mehr bearbeiten (Fix#2);
  Impersonation mit Rangprüfung, keine Token-Erstellung während Impersonation (H1).
- Destruktive/verwaltende Admin-Aktionen (CRM/Billing/Compliance/Struktur)
  hängen an neuen admin-only Permissions; Editor behält reine Content-Rechte (H2).
- Kanonischer Permission-Katalog (App\Authorization\Permission) als Single
  Source of Truth für Seeder und Monitoring.

Deployment: `php artisan db:seed --class=RolesAndPermissionsSeeder` auf allen
Umgebungen ausführen (neue Permissions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:48:50 +00:00

108 lines
2.8 KiB
PHP

<?php
namespace App\Actions\Admin;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Auth;
class UserImpersonation
{
public const SessionKey = 'impersonate_from';
public function canInitiate(User $user): bool
{
return $user->is_super_admin || $user->can('users:manage');
}
public function canStart(User $admin, User $target): bool
{
return $this->canInitiate($admin)
&& ! $admin->is($target)
&& ! $this->isActive()
&& $this->canBeImpersonated($target)
&& $this->mayImpersonateRank($admin, $target);
}
public function canBeImpersonated(User $target): bool
{
return $target->canAccessCustomer();
}
/**
* Rang-Schutz: Ein Super-Admin darf nur von einem Super-Admin im Panel
* angemeldet werden. Verhindert, dass ein Admin (users:manage) das mächtigere
* Super-Admin-Konto übernimmt und in dessen Namen z. B. API-Token erzeugt.
*/
public function mayImpersonateRank(User $admin, User $target): bool
{
return $admin->is_super_admin || ! $target->is_super_admin;
}
/**
* @throws AuthorizationException
*/
public function start(User $admin, User $target): void
{
if (! $this->canInitiate($admin)) {
throw new AuthorizationException(__('Du darfst keine Benutzer-Impersonation starten.'));
}
if ($admin->is($target)) {
return;
}
if ($this->isActive()) {
throw new AuthorizationException(__('Verschachtelte Benutzer-Impersonation ist nicht erlaubt.'));
}
if (! $this->canBeImpersonated($target)) {
throw new AuthorizationException(__('Dieser Benutzer kann nicht im Panel angemeldet werden.'));
}
if (! $this->mayImpersonateRank($admin, $target)) {
throw new AuthorizationException(__('Super-Admins können nur von Super-Admins im Panel angemeldet werden.'));
}
session([self::SessionKey => $admin->getKey()]);
Auth::login($target);
}
public function stop(): ?User
{
$adminUserId = session(self::SessionKey);
session()->forget(self::SessionKey);
if (! is_numeric($adminUserId)) {
return null;
}
$admin = User::query()->find((int) $adminUserId);
if (! $admin?->canAccessAdmin()) {
return null;
}
Auth::login($admin);
return $admin;
}
public function isActive(): bool
{
return session()->has(self::SessionKey);
}
public function impersonator(): ?User
{
$adminUserId = session(self::SessionKey);
if (! is_numeric($adminUserId)) {
return null;
}
return User::query()->find((int) $adminUserId);
}
}