198 lines
5.1 KiB
PHP
198 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\enum\UserLevel;
|
|
use App\Models\ImpersonationLog;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Session;
|
|
|
|
final class ImpersonationService
|
|
{
|
|
private const IMPERSONATION_SESSION_KEY = 'impersonation';
|
|
|
|
private const IMPERSONATION_START_TIME_KEY = 'impersonation_start_time';
|
|
|
|
private const ORIGINAL_USER_KEY = 'original_admin_user_id';
|
|
|
|
private const IMPERSONATION_TIMEOUT = 30; // minutes
|
|
|
|
public function canImpersonate(User $admin, User $target): bool
|
|
{
|
|
// Only SUPERADMIN can impersonate
|
|
if ($admin->level !== UserLevel::SUPERADMIN) {
|
|
return false;
|
|
}
|
|
|
|
// Cannot impersonate other SUPERADMIN users
|
|
if ($target->level === UserLevel::SUPERADMIN) {
|
|
return false;
|
|
}
|
|
|
|
// Cannot impersonate self
|
|
if ($admin->id === $target->id) {
|
|
return false;
|
|
}
|
|
|
|
// Can only impersonate NORMALUSER
|
|
if ($target->level !== UserLevel::NORMALUSER) {
|
|
return false;
|
|
}
|
|
|
|
// Check if admin is already impersonating someone
|
|
if ($this->isImpersonating()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function isImpersonating(): bool
|
|
{
|
|
return Session::has(self::IMPERSONATION_SESSION_KEY) &&
|
|
Session::has(self::ORIGINAL_USER_KEY);
|
|
}
|
|
|
|
public function getCurrentImpersonator(): ?User
|
|
{
|
|
if (! $this->isImpersonating()) {
|
|
return null;
|
|
}
|
|
|
|
$originalUserId = Session::get(self::ORIGINAL_USER_KEY);
|
|
|
|
return User::find($originalUserId);
|
|
}
|
|
|
|
public function getImpersonationStartTime(): ?Carbon
|
|
{
|
|
if (! $this->isImpersonating()) {
|
|
return null;
|
|
}
|
|
|
|
return Session::get(self::IMPERSONATION_START_TIME_KEY);
|
|
}
|
|
|
|
public function isImpersonationExpired(): bool
|
|
{
|
|
if (! $this->isImpersonating()) {
|
|
return false;
|
|
}
|
|
|
|
$startTime = $this->getImpersonationStartTime();
|
|
|
|
if (! $startTime) {
|
|
return true;
|
|
}
|
|
|
|
return $startTime->diffInMinutes(now()) > self::IMPERSONATION_TIMEOUT;
|
|
}
|
|
|
|
public function startImpersonation(User $admin, User $target, Request $request): bool
|
|
{
|
|
if (! $this->canImpersonate($admin, $target)) {
|
|
return false;
|
|
}
|
|
|
|
// Store original admin user info
|
|
Session::put(self::ORIGINAL_USER_KEY, $admin->id);
|
|
Session::put(self::IMPERSONATION_START_TIME_KEY, now());
|
|
|
|
// Log the impersonation start
|
|
$impersonationLog = ImpersonationLog::create([
|
|
'admin_id' => $admin->id,
|
|
'target_user_id' => $target->id,
|
|
'start_time' => now(),
|
|
'ip_address' => $request->ip(),
|
|
'user_agent' => $request->userAgent(),
|
|
'status' => 'active',
|
|
]);
|
|
|
|
Session::put(self::IMPERSONATION_SESSION_KEY, $impersonationLog->id);
|
|
|
|
// Log out the admin and log in as target user
|
|
Auth::logout();
|
|
Auth::login($target);
|
|
|
|
// Regenerate session for security
|
|
Session::regenerate(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
public function stopImpersonation(Request $request): bool
|
|
{
|
|
if (! $this->isImpersonating()) {
|
|
return false;
|
|
}
|
|
|
|
$admin = $this->getCurrentImpersonator();
|
|
$target = Auth::user();
|
|
$impersonationLogId = Session::get(self::IMPERSONATION_SESSION_KEY);
|
|
|
|
// Update the impersonation log
|
|
if ($impersonationLogId) {
|
|
$log = ImpersonationLog::find($impersonationLogId);
|
|
if ($log) {
|
|
$log->update([
|
|
'end_time' => now(),
|
|
'status' => 'completed',
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Clear impersonation session data
|
|
Session::forget([
|
|
self::IMPERSONATION_SESSION_KEY,
|
|
self::IMPERSONATION_START_TIME_KEY,
|
|
self::ORIGINAL_USER_KEY,
|
|
]);
|
|
|
|
// Log out target user and log back in as admin
|
|
Auth::logout();
|
|
|
|
if ($admin) {
|
|
Auth::login($admin);
|
|
}
|
|
|
|
// Regenerate session for security
|
|
Session::regenerate(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
public function forceStopAllImpersonations(): void
|
|
{
|
|
// Force end any active impersonation sessions in the database
|
|
ImpersonationLog::where('status', 'active')
|
|
->whereNull('end_time')
|
|
->update([
|
|
'end_time' => now(),
|
|
'status' => 'force_terminated',
|
|
]);
|
|
}
|
|
|
|
public function getRemainingMinutes(): int
|
|
{
|
|
if (! $this->isImpersonating()) {
|
|
return 0;
|
|
}
|
|
|
|
$startTime = $this->getImpersonationStartTime();
|
|
|
|
if (! $startTime) {
|
|
return 0;
|
|
}
|
|
|
|
$elapsed = $startTime->diffInMinutes(now());
|
|
$remaining = self::IMPERSONATION_TIMEOUT - $elapsed;
|
|
|
|
return (int) max(0, $remaining);
|
|
}
|
|
}
|