netgescon-day0/app/Http/Controllers/Integrations/GoogleOAuthController.php

268 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers\Integrations;
use App\Http\Controllers\Controller;
use App\Models\Amministratore;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\Stabile;
use App\Support\GoogleAccountStore;
use App\Models\User;
use App\Support\StabileContext;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;
class GoogleOAuthController extends Controller
{
private const GOOGLE_SCOPES = [
...GoogleAccountStore::SCOPES,
];
public function redirect(): RedirectResponse
{
$amministratore = $this->resolveAmministratore();
if (! $amministratore instanceof Amministratore) {
abort(403, 'Amministratore non disponibile per questa sessione.');
}
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
$store = app(GoogleAccountStore::class);
$accountKey = $store->normalizeAccountKey((string) request()->query('account_key', ''));
$accountLabel = trim((string) request()->query('label', ''));
$account = $store->get($amministratore, $accountKey !== '' ? $accountKey : null);
$returnTo = trim((string) request()->query('return_to', ''));
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
$redirectUri = (string) ($google['redirect_uri'] ?? config('services.google.redirect'));
if ($clientId === '' || $clientSecret === '' || $redirectUri === '') {
return redirect('/admin-filament/impostazioni/scheda-amministratore')
->with('error', 'Configura Client ID, Client Secret e Redirect URI in Scheda amministratore.');
}
$state = bin2hex(random_bytes(20));
session()->put('google_oauth_context', [
'state' => $state,
'account_key' => $accountKey,
'label' => $accountLabel,
'return_to' => str_starts_with($returnTo, '/') ? $returnTo : null,
]);
$queryParams = [
'client_id' => $clientId,
'redirect_uri' => $redirectUri,
'response_type' => 'code',
'scope' => implode(' ', self::GOOGLE_SCOPES),
'access_type' => 'offline',
'include_granted_scopes' => 'true',
'state' => $state,
];
$refreshToken = trim((string) ($account['refresh_token'] ?? ''));
if ($refreshToken === '') {
$queryParams['prompt'] = 'consent';
}
if (! empty($account['email'])) {
$queryParams['login_hint'] = (string) $account['email'];
}
$query = http_build_query($queryParams);
return redirect()->away('https://accounts.google.com/o/oauth2/v2/auth?' . $query);
}
public function callback(): RedirectResponse
{
$amministratore = $this->resolveAmministratore();
if (! $amministratore instanceof Amministratore) {
abort(403, 'Amministratore non disponibile per questa sessione.');
}
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
$store = app(GoogleAccountStore::class);
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
$redirectUri = (string) ($google['redirect_uri'] ?? config('services.google.redirect'));
try {
$oauthContext = (array) session()->pull('google_oauth_context', []);
$incomingState = (string) request()->query('state', '');
$sessionState = (string) ($oauthContext['state'] ?? '');
if ($incomingState === '' || $sessionState === '' || ! hash_equals($sessionState, $incomingState)) {
return redirect('/admin-filament/impostazioni/scheda-amministratore')
->with('error', 'Stato OAuth non valido. Riprova il collegamento Google.');
}
$code = (string) request()->query('code', '');
if ($code === '') {
return redirect('/admin-filament/impostazioni/scheda-amministratore')
->with('error', 'Google non ha restituito il codice di autorizzazione.');
}
$tokenResponse = Http::asForm()->post('https://oauth2.googleapis.com/token', [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'redirect_uri' => $redirectUri,
'grant_type' => 'authorization_code',
'code' => $code,
]);
if (! $tokenResponse->successful()) {
$message = (string) (Arr::get($tokenResponse->json(), 'error_description') ?: Arr::get($tokenResponse->json(), 'error.message') ?: 'token_exchange_failed');
return redirect('/admin-filament/impostazioni/scheda-amministratore')
->with('error', 'Errore scambio token Google: ' . $message);
}
$payload = $tokenResponse->json();
$accessToken = (string) Arr::get($payload, 'access_token', '');
$refreshToken = (string) Arr::get($payload, 'refresh_token', '');
$expiresIn = (int) Arr::get($payload, 'expires_in', 3600);
$profileResponse = Http::withToken($accessToken)
->acceptJson()
->get('https://openidconnect.googleapis.com/v1/userinfo');
$profile = $profileResponse->successful() ? $profileResponse->json() : [];
$googleUserId = (string) Arr::get($profile, 'sub', '');
$email = (string) Arr::get($profile, 'email', '');
$name = (string) Arr::get($profile, 'name', '');
$requestedKey = $store->normalizeAccountKey((string) ($oauthContext['account_key'] ?? ''));
$accountKey = $requestedKey !== ''
? $requestedKey
: $store->normalizeAccountKey($googleUserId !== '' ? $googleUserId : ($email !== '' ? $email : 'primary'));
$accountLabel = trim((string) ($oauthContext['label'] ?? '')) ?: ($name !== '' ? $name : $email);
$existing = $store->get($amministratore, $accountKey);
if ($refreshToken === '') {
$refreshToken = trim((string) ($existing['refresh_token'] ?? ''));
}
$store->upsertAccount(
$amministratore,
$accountKey,
[
'connected' => true,
'label' => $accountLabel,
'email' => $email,
'name' => $name,
'google_user_id' => $googleUserId,
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => $expiresIn,
'connected_at' => (string) ($existing['connected_at'] ?? now()->toDateTimeString()),
'refreshed_at' => now()->toDateTimeString(),
'scopes' => self::GOOGLE_SCOPES,
],
$existing === null
);
$redirectTo = (string) ($oauthContext['return_to'] ?? '');
return redirect($redirectTo !== '' ? $redirectTo : '/admin-filament/impostazioni/scheda-amministratore')
->with('status', 'Account Google collegato con successo.');
} catch (Throwable $e) {
Log::error('Google OAuth callback failed', [
'admin_id' => $amministratore->id,
'error' => $e->getMessage(),
]);
return redirect('/admin-filament/impostazioni/scheda-amministratore')
->with('error', 'Errore durante il collegamento Google: ' . $e->getMessage());
}
}
public function disconnect(): RedirectResponse
{
$amministratore = $this->resolveAmministratore();
if (! $amministratore instanceof Amministratore) {
abort(403, 'Amministratore non disponibile per questa sessione.');
}
app(GoogleAccountStore::class)->disconnectAccount(
$amministratore,
(string) request()->input('account_key', request()->query('account_key', ''))
);
$returnTo = trim((string) request()->input('return_to', request()->query('return_to', '')));
return redirect($returnTo !== '' && str_starts_with($returnTo, '/') ? $returnTo : '/admin-filament/impostazioni/scheda-amministratore')
->with('status', 'Collegamento Google rimosso.');
}
private function resolveAmministratore(): ?Amministratore
{
$user = Auth::user();
if (! $user instanceof User) {
return null;
}
$amministratore = $user->amministratore;
if (! $amministratore instanceof Amministratore && $user->hasAnyRole(['super-admin', 'admin'])) {
$stabile = StabileContext::getActiveStabile($user);
if ($stabile instanceof Stabile && $stabile->amministratore instanceof Amministratore) {
$amministratore = $stabile->amministratore;
}
}
if (! $amministratore instanceof Amministratore && $user->hasRole('fornitore')) {
$fornitore = $this->resolveCurrentUserSupplier($user);
if ($fornitore instanceof Fornitore && $fornitore->amministratore instanceof Amministratore) {
$amministratore = $fornitore->amministratore;
}
}
return $amministratore instanceof Amministratore ? $amministratore : null;
}
private function resolveCurrentUserSupplier(User $user): ?Fornitore
{
$email = mb_strtolower(trim((string) $user->email));
if ($email === '') {
return $this->resolveCurrentUserSupplierFromCollaboratore($user);
}
$fornitore = Fornitore::query()
->whereRaw('LOWER(email) = ?', [$email])
->first();
if ($fornitore instanceof Fornitore) {
return $fornitore;
}
return $this->resolveCurrentUserSupplierFromCollaboratore($user);
}
private function resolveCurrentUserSupplierFromCollaboratore(User $user): ?Fornitore
{
$dipendente = FornitoreDipendente::query()
->where('attivo', true)
->where(function ($query) use ($user): void {
$query->where('user_id', (int) $user->id);
$email = mb_strtolower(trim((string) $user->email));
if ($email !== '') {
$query->orWhereRaw('LOWER(email) = ?', [$email]);
}
})
->latest('id')
->first();
if (! $dipendente instanceof FornitoreDipendente) {
return null;
}
return Fornitore::query()->find((int) $dipendente->fornitore_id);
}
}