From 69f4d6a6028a8d00cbcfe7c4f03cd6a1d55db66d Mon Sep 17 00:00:00 2001 From: benzimariano Date: Mon, 15 Jun 2026 11:14:35 +0200 Subject: [PATCH] add OAUth2.0 --- .env.example | 3 + DEPLOY.md | 144 ++++++++++++++++ MEMORY.md | 104 ++++++++++- app/Console/Commands/ExportHelpPdf.php | 1 - app/Enums/GoogleService.php | 48 ++++++ .../Controllers/Admin/BackupController.php | 7 - app/Http/Controllers/Auth/AuthController.php | 2 - app/Http/Controllers/DocumentoController.php | 10 -- app/Http/Controllers/EmailController.php | 161 +++++++++++++++++- .../Controllers/GoogleOAuthController.php | 68 ++++++++ .../Controllers/GruppoMembroController.php | 2 - app/Http/Controllers/HomeController.php | 1 - .../Controllers/ImpostazioniController.php | 1 - app/Http/Controllers/IndividuoController.php | 1 - .../StorageRepositoryController.php | 16 +- app/Models/AppSetting.php | 1 - app/Models/EmailAttachment.php | 1 - app/Models/EmailSetting.php | 64 ++++++- app/Models/Evento.php | 1 - app/Models/GoogleOAuthConnection.php | 46 +++++ app/Providers/AuthServiceProvider.php | 1 - app/Services/BackupService.php | 3 - app/Services/GoogleCalendarSyncService.php | 12 ++ app/Services/GoogleOAuthService.php | 150 ++++++++++++++++ app/Services/StorageRepositoryService.php | 62 +++++++ build-dist.sh | 33 ++++ config/services.php | 5 + ..._create_google_oauth_connections_table.php | 27 +++ ...dd_auth_method_to_email_settings_table.php | 24 +++ resources/views/google-oauth/status.blade.php | 66 +++++++ resources/views/impostazioni/index.blade.php | 95 +++++++++++ routes/web.php | 17 +- 32 files changed, 1126 insertions(+), 51 deletions(-) create mode 100644 DEPLOY.md create mode 100644 app/Enums/GoogleService.php create mode 100644 app/Http/Controllers/GoogleOAuthController.php create mode 100644 app/Models/GoogleOAuthConnection.php create mode 100644 app/Services/GoogleOAuthService.php mode change 100755 => 100644 build-dist.sh create mode 100644 database/migrations/2026_06_15_000001_create_google_oauth_connections_table.php create mode 100644 database/migrations/2026_06_15_000002_add_auth_method_to_email_settings_table.php create mode 100644 resources/views/google-oauth/status.blade.php diff --git a/.env.example b/.env.example index 4088f104..35371017 100644 --- a/.env.example +++ b/.env.example @@ -46,3 +46,6 @@ MAIL_FROM_ADDRESS="hello@example.com" MAIL_FROM_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}" +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 00000000..ebab8d47 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,144 @@ +# πŸš€ Deploy & Aggiornamento β€” Glastree + +## Generare il Pacchetto (Server di Sviluppo) + +```bash +cd /var/www/html/glastree + +# Installa dipendenze PHP production (esclude dev) +composer install --no-dev --optimize-autoloader + +# Build asset Vite (se node_modules/ presente) +npm install && npm run build + +# Genera archivio +bash build-dist.sh +``` + +L'archivio `glastree-YYYYMMDD_HHMM.tar.gz` viene creato nella directory corrente. + +--- + +## πŸ†• Installazione Fresca (Nuovo Server) + +```bash +# 1. Estrai archivio +tar xzf glastree-YYYYMMDD_HHMM.tar.gz +cd glastree + +# 2. Crea directory necessarie +mkdir -p storage/framework/cache/data \ + storage/framework/sessions \ + storage/framework/views \ + storage/logs \ + bootstrap/cache \ + storage/app/public \ + storage/app/public/logos \ + storage/app/public/documenti/eventi \ + storage/app/backups \ + storage/app/documenti \ + storage/app/private/avatars/gruppi + +# 3. Permessi +chmod -R 775 storage bootstrap/cache +chown -R www-data:www-data storage bootstrap/cache + +# 4. Crea .gitignore in storage/app/public (serve a Laravel) +echo "*" > storage/app/public/.gitignore +echo "!.gitignore" >> storage/app/public/.gitignore + +# 5. Configura ambiente +cp .env.example .env +# Modifica manualmente: DB, APP_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET +php artisan key:generate + +# 6. Pulisci cache e ricrea symlink +rm -f public/storage +ln -sf ../storage/app/public public/storage +php artisan config:clear +php artisan route:clear +php artisan view:clear + +# 7. Avvia installazione DB +php install.php +``` + +--- + +## πŸ”„ Aggiornamento (Server Esistente) + +```bash +# 1. BACKUP +cp .env .env.backup.$(date +%Y%m%d_%H%M%S) +mysqldump -u USER -p DBNAME > backup_$(date +%Y%m%d_%H%M%S).sql + +# 2. Estrai archivio +tar xzf glastree-YYYYMMDD_HHMM.tar.gz + +# 3. Ripristina .env +cp .env.backup.* .env + +# 4. Aggiungi nuove variabili d'ambiente +echo "GOOGLE_CLIENT_ID=" >> .env +echo "GOOGLE_CLIENT_SECRET=" >> .env + +# 5. Permessi +chmod -R 775 storage bootstrap/cache +chown -R www-data:www-data storage bootstrap/cache + +# 6. Symlink storage +rm -f public/storage +ln -sf ../storage/app/public public/storage + +# 7. Migration +php artisan migrate --force + +# 8. Pulisci cache +php artisan config:clear +php artisan route:clear +php artisan view:clear + +# 9. Asset Vite +npm install && npm run build +``` + +--- + +## πŸ“¦ Contenuto dell'Archivio + +| Include | Esclude | +|---------|---------| +| Sorgenti PHP (`app/`, `config/`, `routes/`, `resources/`) | `.git/`, `node_modules/`, `tests/` | +| Vendor production | `.env`, `.env.example` | +| Asset Vite compilati (`public/build/`) | `storage/app/public/`, `storage/app/private/` | +| Migration e seeder | `storage/framework/cache/*`, `sessions/*`, `views/*` | +| | `storage/logs/*`, `storage/debugbar/*` | +| | `storage/app/backups/`, `storage/app/documenti/` | +| | `MEMORY.md`, `AGENTS.md`, `build-dist.sh`, Docker files | + +--- + +## βš™οΈ Variabili d'Ambiente Richieste + +```env +APP_URL=https://tuodominio.it +APP_SUBFOLDER=/app +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=glastree +DB_USERNAME=glastree +DB_PASSWORD=... + +GOOGLE_CLIENT_ID=.... +GOOGLE_CLIENT_SECRET=.... +``` + +## 🌐 Google OAuth 2.0 β€” Post-Deploy + +1. [Google Cloud Console](https://console.cloud.google.com/) β†’ Abilita Gmail API, Drive API, Calendar API +2. Credenziali β†’ OAuth Client ID β†’ Web Application +3. Redirect URI: `https://tuodominio.it/app/auth/google/callback` +4. Inserisci Client ID/Secret in `.env` +5. **Impostazioni β†’ Google Services** β†’ Connetti Gmail, Drive, Calendar +6. Per Gmail: **Impostazioni β†’ Email β†’ IMAP** β†’ seleziona "OAuth 2.0" diff --git a/MEMORY.md b/MEMORY.md index 6cf9d2e3..8ad23459 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -291,8 +291,11 @@ if (!empty($contatto['individuo_id'])) { create } ``` ## Prossimi Passi -- [DONE] ... (existing items) - Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag +- Test end-to-end OAuth con credenziali Google reali (configurare Google Cloud Console, registrare callback URI, ottenere refresh token) +- Verificare EmailSetting::isOauth() integration in email sync flow +- Verificare StorageRepositoryService::buildGoogleDrive() OAuth fallback con repos esistenti +- Verificare GoogleCalendarSyncService::buildClient() OAuth fallback con connessioni esistenti - [DONE] Fix performance: spostata query `VistaReport::where(...)->get()` da `@php` nel partial `table-settings.blade.php` a tutti i 5 controller (prima veniva eseguita 1 query per ogni pagina load per ogni entity, anche senza mai aprire la modale) - [DONE] Rimosse vecchie modal legacy (`#saveVistaModal`, `#colonneModal`, `#vistaListModal`) da `individui` e `gruppi` β€” duplicate rispetto al nuovo modal unificato - [DONE] Rimosse vecchie funzioni JS (`saveVista()`, `toggleColumn()`, `showSaveVistaModal()`) da `individui` e `gruppi` @@ -380,3 +383,102 @@ if (!empty($contatto['individuo_id'])) { create } **Fix**: 1. `VistaReportController@update` (linea 101-103): aggiunto `if ($request->expectsJson()) { return response()->json([...]); }` β€” cosΓ¬ il fetch riceve JSON 200, non un redirect 302. 2. Fetch headers in `table-settings.blade.php`: aggiunto `'Accept': 'application/json'` β€” necessario perchΓ© `expectsJson()` controlla l'header `Accept`, non `Content-Type`. + +## 2026-06-15 β€” Centralized Google OAuth 2.0 System (Gmail, Drive, Calendar) + +**Obiettivo**: Implementare autenticazione OAuth 2.0 centralizzata per Gmail (IMAP/SMTP), Google Drive e Google Calendar come alternativa opzionale all'auth con password, con revoca per-servizio. + +### Files Creati +- **Enum** `app/Enums/GoogleService.php` β€” 3 casi (Gmail, Drive, Calendar) con scope/label/icon/hexColor +- **Migration 1** `2026_06_15_000001_create_google_oauth_connections_table.php` β€” tabella google_oauth_connections +- **Migration 2** `2026_06_15_000002_add_auth_method_to_email_settings_table.php` β€” auth_method a email_settings +- **Model** `app/Models/GoogleOAuthConnection.php` β€” encrypted field accessor/mutator +- **Service** `app/Services/GoogleOAuthService.php` β€” full OAuth flow methods +- **Controller** `app/Http/Controllers/GoogleOAuthController.php` β€” connect/callback/revoke/status +- **Routes** in `routes/web.php` β€” 4 nuove route +- **View** `resources/views/google-oauth/status.blade.php` + +### Problemi Risolti +1. Filesystem permission: directory 755 (www-data). Script PHP via Apache da storage/app/public/ +2. routes/web.php root:root 644 -> rename trick +3. authorize() method conflict -> rinominato in connect() + +### Architettura +- Fallback pattern: Drive/Calendar provano OAuth centralizzata, poi config esistente +- Per-service revoca: record separato per servizio (unique su service) +- Single callback URI: /auth/google/callback, routing via state +- Token encryption: AES-256-CBC + APP_KEY + +### Completato +1. βœ… **EmailSetting model** (`app/Models/EmailSetting.php`): `auth_method` in ``, `isOauth()` method, `getOAuthAccessToken()` via `GoogleOAuthService`, `getDecryptedPassword()` returns OAuth token when isOauth, `getDecryptedSmtpPassword()` same, `getImapClient()` returns null for OAuth, `testConnection()` has OAuth path via Webklex\PHPIMAP +2. βœ… **EmailController** (`app/Http/Controllers/EmailController.php`): `syncEmails()` redirects to `syncEmailsViaOAuth()`, `connectImap()` redirects to `connectOAuthImap()`, `sendViaImap()` appends `auth_mode=xoauth2` to DSN, added `processWebklexMessage()` helper +3. βœ… **StorageRepositoryService** (`app/Services/StorageRepositoryService.php`): `buildGoogleDrive()` tries `GoogleOAuthService::buildClient(GoogleService::Drive)` first, falls back to config. Same for `readGoogleDriveFile()` and `checkGoogleDriveApiStatus()` +4. βœ… **GoogleCalendarSyncService** (`app/Services/GoogleCalendarSyncService.php`): `buildClient()` tries `GoogleOAuthService::buildClient(GoogleService::Calendar)` first, falls back to config +5. βœ… **Tab Google Services** in `resources/views/impostazioni/index.blade.php` β€” nav tab + tab-pane with Connect/Revoke per service (Gmail, Drive, Calendar), status badges, alert linking to email tab. Added `auth_method` select in email IMAP tab +6. ⏳ **Test end-to-end** β€” requires user to configure Google Cloud Console (OAuth consent screen, callback URI) and provide credentials + +### Files Modified +- `app/Models/EmailSetting.php` β€” OAuth-aware auth_method, decrypt overrides, IMAP client +- `app/Http/Controllers/EmailController.php` β€” OAuth sync/send/connect paths +- `app/Services/StorageRepositoryService.php` β€” Drive OAuth fallback +- `app/Services/GoogleCalendarSyncService.php` β€” Calendar OAuth fallback +- `resources/views/impostazioni/index.blade.php` β€” Google Services tab + auth_method select + +### Bug Fix +- **2026-06-15 β€” Variable interpolation in str_replace**: In `apply_all_mods.php`, the second argument to `str_replace()` used double quotes, causing PHP to interpolate $fileId, $service, $googleMimeType, $originalBasename as empty strings. Result: method signature `exportGoogleDriveDoc` lost all parameter variable names. Fixed by running a second script that used `preg_replace` to restore the signature. (file -> app/Services/StorageRepositoryService.php) + + +### Pre-Production Review Fixes + +**2026-06-15 β€” 4 criticitΓ  risolte prima del deploy** + +| # | Problema | File | Fix | +|---|----------|------|-----| +| 1 | Credenziali Google vuote | `GoogleOAuthService.php:createClient()` | Sostituito `setAuthConfig([client_id:'']` con `setClientId(config(...))`, `setClientSecret(config(...))`, `setRedirectUri(...)`. Rimossi 4 import inutilizzati | +| 2 | Route name errato | `google-oauth/status.blade.php:55` | `google.oauth.connect` β†’ `google.oauth.authorize` | +| 3 | Metodo inesistente `description()` | `google-oauth/status.blade.php:43` | Sostituito con `label() . ' - ' . scope()`. Layout cambiato da `layouts.app` a `layouts.adminlte` | +| 4 | `auth_method` select mancante | `impostazioni/index.blade.php` | Inserito ` + + + + + + Scegli "OAuth 2.0" per usare il token OAuth centralizzato di Google. + Per Gmail con password, usa una + App Password. + +
@@ -846,6 +862,85 @@
+ + {{-- === GOOGLE SERVICES === --}} +
+
+
+

Google Services

+ +
+
+

Gestisci le connessioni OAuth 2.0 centralizzate per Gmail, Google Drive e Google Calendar. Ogni servizio puΓ² essere configurato separatamente.

+ + @php + $gsvc = app(\App\Services\GoogleOAuthService::class); + $gservices = [ + \App\Enums\GoogleService::Gmail, + \App\Enums\GoogleService::Drive, + \App\Enums\GoogleService::Calendar, + ]; + @endphp + + @if(session('success')) +
+ + {{ session('success') }} +
+ @endif + @if(session('error')) +
+ + {{ session('error') }} +
+ @endif + +
+ @foreach($gservices as $svc) + @php $gAuth = $gsvc->isAuthorized($svc); @endphp +
+
+
+

+ + {{ $svc->label() }} +

+
+
+
+ @if($gAuth) + Connected +
+ @csrf @method('DELETE') + +
+ @else + Non connesso + + Connetti + + @endif +
+
+
+ @endforeach +
+ +
+ + Nota: Se utilizzi Gmail con OAuth 2.0, torna al tab + Email + e imposta il campo "Metodo di autenticazione" su "OAuth 2.0" nelle impostazioni IMAP. +
+
+
+
{{-- === TIPOLOGIE DOCUMENTI === --}}
diff --git a/routes/web.php b/routes/web.php index f0442490..8f29b8c2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -275,4 +275,19 @@ Route::prefix('admin')->middleware(['auth', AdminOnly::class])->group(function ( Route::post('/backup/config', [\App\Http\Controllers\Admin\BackupController::class, 'updateConfig'])->name('admin.backup.config'); Route::post('/backup/toggle-auto', [\App\Http\Controllers\Admin\BackupController::class, 'toggleAuto'])->name('admin.backup.toggle-auto'); Route::post('/backup/save-auto', [\App\Http\Controllers\Admin\BackupController::class, 'saveAutoConfig'])->name('admin.backup.save-auto'); -}); \ No newline at end of file +}); + +// Google OAuth 2.0 centralized routes +Route::get('/auth/google/{service}/authorize', [\App\Http\Controllers\GoogleOAuthController::class, 'connect']) + ->name('google.oauth.authorize') + ->whereIn('service', ['gmail', 'drive', 'calendar']) + ->middleware('auth'); +Route::get('/auth/google/callback', [\App\Http\Controllers\GoogleOAuthController::class, 'callback']) + ->name('google.oauth.callback'); +Route::delete('/auth/google/{service}/revoke', [\App\Http\Controllers\GoogleOAuthController::class, 'revoke']) + ->name('google.oauth.revoke') + ->whereIn('service', ['gmail', 'drive', 'calendar']) + ->middleware('auth'); +Route::get('/auth/google/status', [\App\Http\Controllers\GoogleOAuthController::class, 'status']) + ->name('google.oauth.status') + ->middleware('auth');