CluPilotCloud/app/Http/Controllers/ImpersonationController.php

74 lines
2.6 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Customer;
use App\Models\Operator;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\URL;
/**
* Looking at a customer's portal as that customer.
*
* Not a shared cookie. Once the console has a hostname of its own, a session
* cookie set on the console host never reaches the portal host — SESSION_DOMAIN
* is null and cookies are host-bound. So the handover is a signed, single-use
* link: it works regardless of cookie scope, it expires, and it leaves a record
* of who looked at whom.
*/
class ImpersonationController extends Controller
{
/** Sixty seconds is a handover, not a credential. */
private const WINDOW = 60;
public function start(Customer $customer): RedirectResponse
{
$this->authorize('customers.impersonate');
$operator = Auth::guard('operator')->user();
// Addressed by CUSTOMER uuid, not user: `users` has no uuid column, and
// naming the integer key in a URL is what R11 forbids. The portal end
// resolves the account the same way this end would have.
return redirect()->away(URL::temporarySignedRoute(
'impersonate.enter',
now()->addSeconds(self::WINDOW),
['customer' => $customer->uuid, 'operator' => $operator->uuid],
));
}
/** The portal end of the handover. Signed, and good exactly once. */
public function enter(Request $request, string $customer, string $operator): RedirectResponse
{
abort_unless($request->hasValidSignature(), 403);
// Single use: the signature's own hash is the key, and it lives exactly
// as long as the link could. Redis, not the database — the marker is as
// short-lived as the link and should not need tidying up.
$marker = 'impersonate:'.hash('sha256', (string) $request->query('signature'));
abort_unless(Cache::add($marker, true, self::WINDOW), 403);
$target = Customer::where('uuid', $customer)->firstOrFail()->ensureUser();
$who = Operator::where('uuid', $operator)->firstOrFail();
Auth::guard('web')->login($target);
session(['impersonator_id' => $who->id]);
return redirect()->route('dashboard');
}
/** Back out of the customer's portal. The operator session was never touched. */
public function leave(): RedirectResponse
{
session()->forget('impersonator_id');
Auth::guard('web')->logout();
return redirect()->to(\App\Support\AdminArea::home());
}
}