Chronologisches Protokoll der Systemereignisse als neue Admin-Seite (/admin/reports/activity) mit Filter nach Ereignistyp, Zeitraum und Suche. Erfasst ab jetzt fortlaufend: - Registrierungen inkl. Weg (Formular, Google, Magic-Link) - Anmeldungen inkl. Weg (Passwort, Google, Magic-Link, Impersonation) - PM-Statuswechsel mit Ergebnis/Begründung (zentral im Status-Log-Hook; KI-/Scheduler-Wechsel erscheinen als „System") - Zahlungseingänge (Rechnungsspiegelung) und Credit-Aufladungen - Formular-Eingänge: Kontakt, Rechtsanfrage, Widerruf, Kündigung, Newsletter-Anmeldung (Observer auf den Modellen) Schreiben ist Best-Effort über den ActivityLog-Service — ein fehlgeschlagener Verlaufseintrag bricht nie den fachlichen Ablauf. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
111 lines
3 KiB
PHP
111 lines
3 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\Admin;
|
|
|
|
use App\Listeners\RecordAuthActivity;
|
|
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()]);
|
|
|
|
request()->attributes->set(RecordAuthActivity::AUTH_METHOD_ATTRIBUTE, 'impersonation');
|
|
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;
|
|
}
|
|
|
|
request()->attributes->set(RecordAuthActivity::AUTH_METHOD_ATTRIBUTE, 'impersonation');
|
|
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);
|
|
}
|
|
}
|