netgescon-day0/app/Support/GoogleAccountStore.php

336 lines
12 KiB
PHP

<?php
namespace App\Support;
use App\Models\Amministratore;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class GoogleAccountStore
{
public const SCOPES = [
'openid',
'email',
'profile',
'https://www.googleapis.com/auth/calendar.events',
'https://www.googleapis.com/auth/contacts',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/gmail.readonly',
];
/**
* @return array<string, array<string, mixed>>
*/
public function all(Amministratore $amministratore): array
{
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
$accounts = [];
foreach ((array) Arr::get($google, 'accounts', []) as $rawKey => $account) {
if (! is_array($account)) {
continue;
}
$key = $this->normalizeAccountKey((string) $rawKey);
if ($key === '') {
$key = $this->normalizeAccountKey(
(string) ($account['email'] ?? $account['google_user_id'] ?? $account['label'] ?? 'google-account')
);
}
$accounts[$key] = $this->normalizeAccountPayload($account, $key);
}
$legacyOauth = Arr::get($google, 'oauth', []);
if (is_array($legacyOauth) && $this->hasUsableOauthPayload($legacyOauth)) {
$legacyKey = $this->normalizeAccountKey(
(string) (Arr::get($google, 'default_account_key')
?: Arr::get($legacyOauth, 'google_user_id')
?: Arr::get($legacyOauth, 'email')
?: 'primary')
);
if (! isset($accounts[$legacyKey])) {
$accounts[$legacyKey] = $this->normalizeAccountPayload($legacyOauth, $legacyKey);
}
}
$defaultKey = $this->normalizeAccountKey((string) Arr::get($google, 'default_account_key', ''));
if ($defaultKey === '' && $accounts !== []) {
$defaultKey = (string) array_key_first($accounts);
}
foreach ($accounts as $key => $account) {
$accounts[$key]['is_default'] = $key === $defaultKey;
}
return $accounts;
}
/**
* @return array<string, string>
*/
public function options(Amministratore $amministratore): array
{
$options = [];
foreach ($this->all($amministratore) as $key => $account) {
$label = trim((string) ($account['label'] ?? ''));
$email = trim((string) ($account['email'] ?? ''));
$text = $label !== '' ? $label : $key;
if ($email !== '') {
$text .= ' (' . $email . ')';
}
if (! empty($account['is_default'])) {
$text .= ' · predefinito';
}
$options[$key] = $text;
}
return $options;
}
/**
* @return array<string, mixed>|null
*/
public function get(Amministratore $amministratore, ?string $accountKey = null): ?array
{
$accounts = $this->all($amministratore);
if ($accounts === []) {
return null;
}
$resolvedKey = $this->normalizeAccountKey((string) $accountKey);
if ($resolvedKey !== '' && isset($accounts[$resolvedKey])) {
return $accounts[$resolvedKey];
}
foreach ($accounts as $account) {
if (! empty($account['is_default'])) {
return $account;
}
}
return reset($accounts) ?: null;
}
public function normalizeAccountKey(?string $value): string
{
$raw = trim((string) $value);
if ($raw === '') {
return '';
}
return (string) Str::of($raw)->lower()->replace('@', '-at-')->slug('-');
}
/**
* @param array<string, mixed> $payload
*/
public function upsertAccount(Amministratore $amministratore, string $accountKey, array $payload, bool $setDefault = false): string
{
$key = $this->normalizeAccountKey($accountKey) ?: 'primary';
$impostazioni = $amministratore->impostazioni ?? [];
$google = Arr::get($impostazioni, 'google', []);
$accounts = (array) Arr::get($google, 'accounts', []);
$existing = isset($accounts[$key]) && is_array($accounts[$key]) ? $accounts[$key] : [];
$account = $this->normalizeAccountPayload(array_replace($existing, $payload), $key);
if (empty($account['scopes'])) {
$account['scopes'] = self::SCOPES;
}
$accounts[$key] = $account;
$google['accounts'] = $accounts;
$defaultKey = $this->normalizeAccountKey((string) Arr::get($google, 'default_account_key', ''));
if ($setDefault || $defaultKey === '') {
$defaultKey = $key;
}
$google['default_account_key'] = $defaultKey;
if ($defaultKey === $key) {
$google['oauth'] = $this->toLegacyOauthPayload($account);
if (trim((string) ($account['email'] ?? '')) !== '') {
$google['workspace_email'] = trim((string) $account['email']);
}
}
$impostazioni['google'] = $google;
$amministratore->impostazioni = $impostazioni;
$amministratore->save();
return $key;
}
public function disconnectAccount(Amministratore $amministratore, ?string $accountKey = null): void
{
$account = $this->get($amministratore, $accountKey);
if (! is_array($account)) {
return;
}
$key = (string) ($account['key'] ?? 'primary');
$impostazioni = $amministratore->impostazioni ?? [];
$google = Arr::get($impostazioni, 'google', []);
$accounts = (array) Arr::get($google, 'accounts', []);
$existing = isset($accounts[$key]) && is_array($accounts[$key]) ? $accounts[$key] : $account;
$accounts[$key] = array_replace($existing, [
'connected' => false,
'access_token' => '',
'refresh_token' => '',
'expires_in' => null,
'refreshed_at' => null,
'disconnected_at' => now()->toDateTimeString(),
]);
$google['accounts'] = $accounts;
if ($this->normalizeAccountKey((string) Arr::get($google, 'default_account_key', '')) === $key) {
$google['oauth'] = [
'connected' => false,
'disconnected_at' => now()->toDateTimeString(),
];
}
$impostazioni['google'] = $google;
$amministratore->impostazioni = $impostazioni;
$amministratore->save();
}
public function resolveAccessToken(Amministratore $amministratore, ?string $accountKey = null, bool $forceRefresh = false): ?string
{
$account = $this->get($amministratore, $accountKey);
if (! is_array($account)) {
return null;
}
$accessToken = trim((string) ($account['access_token'] ?? ''));
$refreshToken = trim((string) ($account['refresh_token'] ?? ''));
if (! $forceRefresh && $accessToken !== '' && ! $this->isExpired($account)) {
return $accessToken;
}
if ($refreshToken === '') {
return $accessToken !== '' ? $accessToken : null;
}
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
$clientId = (string) ($google['client_id'] ?? config('services.google.client_id'));
$clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret'));
if ($clientId === '' || $clientSecret === '') {
return $accessToken !== '' ? $accessToken : null;
}
$response = Http::asForm()->post('https://oauth2.googleapis.com/token', [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token',
]);
if (! $response->successful()) {
return $accessToken !== '' ? $accessToken : null;
}
$payload = $response->json();
$newAccessToken = trim((string) Arr::get($payload, 'access_token', ''));
if ($newAccessToken === '') {
return $accessToken !== '' ? $accessToken : null;
}
$this->upsertAccount($amministratore, (string) ($account['key'] ?? 'primary'), array_filter([
'connected' => true,
'label' => $account['label'] ?? null,
'email' => $account['email'] ?? null,
'name' => $account['name'] ?? null,
'google_user_id'=> $account['google_user_id'] ?? null,
'access_token' => $newAccessToken,
'refresh_token' => trim((string) Arr::get($payload, 'refresh_token', '')) ?: $refreshToken,
'expires_in' => (int) Arr::get($payload, 'expires_in', $account['expires_in'] ?? 3600),
'refreshed_at' => now()->toDateTimeString(),
'scopes' => $account['scopes'] ?? self::SCOPES,
], static fn ($value) => $value !== null), ! empty($account['is_default']));
return $newAccessToken;
}
/**
* @param array<string, mixed> $account
* @return array<string, mixed>
*/
private function normalizeAccountPayload(array $account, string $key): array
{
$label = trim((string) ($account['label'] ?? $account['name'] ?? $account['email'] ?? $key));
return [
'key' => $key,
'label' => $label,
'connected' => (bool) ($account['connected'] ?? false),
'email' => trim((string) ($account['email'] ?? '')),
'name' => trim((string) ($account['name'] ?? '')),
'google_user_id' => trim((string) ($account['google_user_id'] ?? '')),
'access_token' => trim((string) ($account['access_token'] ?? '')),
'refresh_token' => trim((string) ($account['refresh_token'] ?? '')),
'expires_in' => (int) ($account['expires_in'] ?? 0),
'connected_at' => $account['connected_at'] ?? null,
'refreshed_at' => $account['refreshed_at'] ?? null,
'disconnected_at'=> $account['disconnected_at'] ?? null,
'scopes' => array_values(array_filter((array) ($account['scopes'] ?? []))),
];
}
/**
* @param array<string, mixed> $account
* @return array<string, mixed>
*/
private function toLegacyOauthPayload(array $account): array
{
return [
'connected' => (bool) ($account['connected'] ?? false),
'email' => (string) ($account['email'] ?? ''),
'name' => (string) ($account['name'] ?? ''),
'google_user_id' => (string) ($account['google_user_id'] ?? ''),
'access_token' => (string) ($account['access_token'] ?? ''),
'refresh_token' => (string) ($account['refresh_token'] ?? ''),
'expires_in' => (int) ($account['expires_in'] ?? 0),
'connected_at' => $account['connected_at'] ?? null,
'refreshed_at' => $account['refreshed_at'] ?? null,
'scopes' => $account['scopes'] ?? self::SCOPES,
];
}
/**
* @param array<string, mixed> $account
*/
private function isExpired(array $account): bool
{
$refreshedAt = trim((string) ($account['refreshed_at'] ?? $account['connected_at'] ?? ''));
$expiresIn = (int) ($account['expires_in'] ?? 0);
if ($refreshedAt === '' || $expiresIn <= 0) {
return false;
}
$timestamp = strtotime($refreshedAt);
if ($timestamp === false) {
return false;
}
return ($timestamp + max(60, $expiresIn - 120)) <= time();
}
/**
* @param array<string, mixed> $oauth
*/
private function hasUsableOauthPayload(array $oauth): bool
{
return (bool) ($oauth['connected'] ?? false)
|| trim((string) ($oauth['refresh_token'] ?? '')) !== ''
|| trim((string) ($oauth['access_token'] ?? '')) !== '';
}
}