71 lines
3.7 KiB
PHP
71 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use App\Models\User;
|
|
use App\Shared\Context\CompanyContext;
|
|
use App\Shared\Context\RequireModuleContext;
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Session\ArraySessionHandler;
|
|
use Illuminate\Session\Store;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
final class AuthenticateSgcInvoice
|
|
{
|
|
public function handle(Request $request, Closure $next)
|
|
{
|
|
abort_unless(config('sgc_invoice.enabled') === true, 404);
|
|
// The installed native adapter is QA-only. This transport cannot remove that boundary.
|
|
abort_unless(app()->environment('budget-testing'), 403);
|
|
abort_unless($request->isSecure() || in_array($request->ip(), ['127.0.0.1', '::1'], true), 403);
|
|
abort_if(strlen($request->getContent()) > 131072, 413);
|
|
if ($request->isMethod('GET')) abort_if($request->query->count() || strlen($request->getContent()), 422);
|
|
else abort_unless($request->isJson(), 415);
|
|
$client = (string)$request->header('X-SGC-Client');
|
|
$principals = config('sgc_invoice.principals', []);
|
|
$principal = preg_match('/^[a-zA-Z0-9_-]{1,64}$/D', $client) ? ($principals[$client] ?? null) : null;
|
|
$token = $request->bearerToken();
|
|
abort_unless(is_array($principal) && ($principal['enabled'] ?? false) === true
|
|
&& is_string($token) && strlen($token) >= 32 && strlen($token) <= 512
|
|
&& preg_match('/^[a-f0-9]{64}$/D', $principal['token_sha256'] ?? '')
|
|
&& hash_equals($principal['token_sha256'], hash('sha256', $token)), 401);
|
|
foreach (['user_id', 'auth_version', 'tenant_id', 'company_id', 'unit_id'] as $key) {
|
|
abort_unless(is_int($principal[$key] ?? null) && $principal[$key] > 0, 403);
|
|
}
|
|
$version = $principal['context_version'] ?? null;
|
|
abort_unless(is_string($version) && preg_match('/^[a-f0-9]{64}$/D', $version)
|
|
&& hash_equals($version, (string)$request->header('X-SGC-Context')), 409);
|
|
$user = User::find($principal['user_id']);
|
|
abort_unless($user && $user->auth_version === $principal['auth_version'], 401);
|
|
$context = app(CompanyContext::class);
|
|
$guard = Auth::getDefaultDriver();
|
|
$oldUser = Auth::guard($guard)->user();
|
|
$oldResolver = $request->getUserResolver();
|
|
$oldSession = $request->hasSession() ? $request->session() : null;
|
|
try {
|
|
// Context and person come exclusively from the server-side principal, never JSON/cookies.
|
|
$context->selectFor($user->id, $principal['tenant_id'], $principal['company_id'], $principal['unit_id']);
|
|
Auth::guard($guard)->setUser($user);
|
|
$request->setUserResolver(fn () => $user);
|
|
$session = new Store('sgc_invoice_ephemeral', new ArraySessionHandler(10));
|
|
$session->start();
|
|
$session->put('company_context', ['tenant_id'=>$principal['tenant_id'], 'company_id'=>$principal['company_id'],
|
|
'unit_id'=>$principal['unit_id'], 'module_id'=>'purchasing', 'token'=>$version]);
|
|
$request->setLaravelSession($session);
|
|
$request->headers->set('X-SGF-Context', $version);
|
|
app(RequireModuleContext::class)->check($request, $context);
|
|
$response = $next($request);
|
|
$response->headers->set('Cache-Control', 'no-store, private');
|
|
return $response;
|
|
} finally {
|
|
$context->clear();
|
|
if (isset($session)) $session->flush();
|
|
Auth::forgetGuards(); Auth::shouldUse($guard);
|
|
if ($oldUser) Auth::guard($guard)->setUser($oldUser);
|
|
$request->setUserResolver($oldResolver);
|
|
if ($oldSession) $request->setLaravelSession($oldSession);
|
|
}
|
|
}
|
|
}
|