feat: add user impersonation service
This commit is contained in:
197
app/Services/ImpersonationService.php
Normal file
197
app/Services/ImpersonationService.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user