diff --git a/auth/callback_microsoft.php b/auth/callback_microsoft.php index 673a757..e9aba2b 100644 --- a/auth/callback_microsoft.php +++ b/auth/callback_microsoft.php @@ -72,18 +72,15 @@ if (empty($code)) { exit; } -// 4. Instellingen ophalen -$stmt = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); -$stmt->execute([$user['id']]); -$row = $stmt->fetch(); -$savedSettings = json_decode($row['settings'] ?? '{}', true) ?: []; +// 4. Centrale instellingen ophalen +$appConfig = microsoft_get_app_config(); -$clientId = !empty($savedSettings['client_id']) ? $savedSettings['client_id'] : fociz_env('MICROSOFT_CLIENT_ID', ''); -$clientSecret = !empty($savedSettings['client_secret']) ? $savedSettings['client_secret'] : fociz_env('MICROSOFT_CLIENT_SECRET', ''); -$tenantId = !empty($savedSettings['tenant_id']) ? $savedSettings['tenant_id'] : fociz_env('MICROSOFT_TENANT_ID', 'common'); +$clientId = $appConfig['client_id']; +$clientSecret = $appConfig['client_secret']; +$tenantId = $appConfig['tenant_id']; if (empty($clientId) || empty($clientSecret)) { - header('Location: /integrations.php?error=' . urlencode('Client ID of Client Secret ontbreekt in Fociz. Vul deze eerst in bij de Outlook instellingen.')); + header('Location: /integrations.php?error=' . urlencode('Microsoft 365 configuratie (Client ID of Secret) ontbreekt in Fociz. Neem contact op met de beheerder.')); exit; } @@ -108,15 +105,24 @@ $accountEmail = $profile['mail'] ?? ($profile['userPrincipalName'] ?? $user['ema $accountName = $profile['displayName'] ?? 'Microsoft 365 Account'; // 7. Opslaan in MariaDB -$newSettings = array_merge($savedSettings, [ - 'client_id' => $clientId, - 'client_secret' => $clientSecret, - 'tenant_id' => $tenantId, +$stmtExisting = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); +$stmtExisting->execute([$user['id']]); +$existingRow = $stmtExisting->fetch(); +$existingSettings = json_decode($existingRow['settings'] ?? '{}', true) ?: []; + +$newSettings = array_merge($existingSettings, [ 'expires_at' => $expiresAt, 'account_email' => $accountEmail, 'account_name' => $accountName, ]); +// Als de ingelogde gebruiker Beheerder (ADMIN) is, bewaren we ook de centrale app credentials +if ($user['role'] === 'ADMIN') { + $newSettings['client_id'] = $clientId; + $newSettings['client_secret'] = $clientSecret; + $newSettings['tenant_id'] = $tenantId; +} + $saveStmt = $db->prepare(" INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, access_token, refresh_token, settings, last_sync_at) VALUES (?, 'OUTLOOK', 1, 0, ?, ?, ?, NOW()) diff --git a/auth/oauth_microsoft.php b/auth/oauth_microsoft.php index 17879f9..a0c6ecc 100644 --- a/auth/oauth_microsoft.php +++ b/auth/oauth_microsoft.php @@ -9,22 +9,17 @@ require_once __DIR__ . '/../includes/integrations/microsoft.php'; $user = require_auth(); $db = get_db_connection(); -// Haal eventueel opgeslagen client_id en tenant_id op uit database -$stmt = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); -$stmt->execute([$user['id']]); -$row = $stmt->fetch(); -$savedSettings = json_decode($row['settings'] ?? '{}', true) ?: []; +// Haal centrale Microsoft app configuratie op (env of admin instellingen) +$appConfig = microsoft_get_app_config(); -$clientId = !empty($_GET['client_id']) - ? trim($_GET['client_id']) - : (!empty($savedSettings['client_id']) ? $savedSettings['client_id'] : fociz_env('MICROSOFT_CLIENT_ID', '')); - -$tenantId = !empty($_GET['tenant_id']) - ? trim($_GET['tenant_id']) - : (!empty($savedSettings['tenant_id']) ? $savedSettings['tenant_id'] : fociz_env('MICROSOFT_TENANT_ID', 'common')); +$clientId = !empty($_GET['client_id']) ? trim($_GET['client_id']) : $appConfig['client_id']; +$tenantId = !empty($_GET['tenant_id']) ? trim($_GET['tenant_id']) : $appConfig['tenant_id']; if (empty($clientId)) { - header('Location: /integrations.php?error=' . urlencode('Vul eerst de Application (Client) ID en het Client Secret in bij de Outlook instellingen om te koppelen met Microsoft 365.')); + $err = ($user['role'] === 'ADMIN') + ? 'Vul eerst de Application (Client) ID en het Client Secret in bij de Outlook instellingen om te koppelen met Microsoft 365.' + : 'Microsoft 365 is nog niet geconfigureerd door de beheerder van uw organisatie. Vraag uw beheerder om de koppeling in te stellen.'; + header('Location: /integrations.php?error=' . urlencode($err)); exit; } diff --git a/includes/integrations/microsoft.php b/includes/integrations/microsoft.php index dea416c..10bea3d 100644 --- a/includes/integrations/microsoft.php +++ b/includes/integrations/microsoft.php @@ -12,6 +12,90 @@ const MS_OAUTH_TOKEN_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0 const MS_GRAPH_BASE_URL = 'https://graph.microsoft.com/v1.0'; const MS_DEFAULT_SCOPES = 'offline_access User.Read Calendars.ReadWrite'; +/** + * Haalt de centrale Microsoft App Registratie configuratie op (Client ID, Secret, Tenant ID) + * Prioriteit: + * 1. Environment variabelen (.env of server env) + * 2. Opgeslagen configuratie van een Beheerder (ADMIN) in de integrations tabel + * 3. Fallback naar integrations tabel waar settings client_id bevat + */ +function microsoft_get_app_config(): array { + $clientId = fociz_env('MICROSOFT_CLIENT_ID', ''); + $clientSecret = fociz_env('MICROSOFT_CLIENT_SECRET', ''); + $tenantId = fociz_env('MICROSOFT_TENANT_ID', ''); + + if (empty($clientId) || empty($clientSecret)) { + try { + $db = get_db_connection(); + // Zoek bij actieve beheerder(s) in de integrations tabel + $stmt = $db->query(" + SELECT i.settings + FROM integrations i + JOIN users u ON i.user_id = u.id + WHERE u.role = 'ADMIN' AND i.provider = 'OUTLOOK' AND i.settings IS NOT NULL + ORDER BY i.updated_at DESC + LIMIT 5 + "); + $rows = $stmt->fetchAll(); + foreach ($rows as $row) { + if (!empty($row['settings'])) { + $saved = json_decode($row['settings'], true) ?: []; + if (empty($clientId) && !empty($saved['client_id'])) { + $clientId = trim($saved['client_id']); + } + if (empty($clientSecret) && !empty($saved['client_secret'])) { + $clientSecret = trim($saved['client_secret']); + } + if (empty($tenantId) && !empty($saved['tenant_id'])) { + $tenantId = trim($saved['tenant_id']); + } + if (!empty($clientId) && !empty($clientSecret)) { + break; + } + } + } + + // Fallback: zoek een willekeurige OUTLOOK rij met ingevulde client_id + if (empty($clientId) || empty($clientSecret)) { + $stmtFallback = $db->query(" + SELECT settings + FROM integrations + WHERE provider = 'OUTLOOK' AND settings LIKE '%client_id%' + ORDER BY updated_at DESC + LIMIT 3 + "); + $fallbackRows = $stmtFallback->fetchAll(); + foreach ($fallbackRows as $fbRow) { + if (!empty($fbRow['settings'])) { + $saved = json_decode($fbRow['settings'], true) ?: []; + if (empty($clientId) && !empty($saved['client_id'])) { + $clientId = trim($saved['client_id']); + } + if (empty($clientSecret) && !empty($saved['client_secret'])) { + $clientSecret = trim($saved['client_secret']); + } + if (empty($tenantId) && !empty($saved['tenant_id'])) { + $tenantId = trim($saved['tenant_id']); + } + if (!empty($clientId) && !empty($clientSecret)) { + break; + } + } + } + } + } catch (Throwable $e) { + error_log("microsoft_get_app_config error: " . $e->getMessage()); + } + } + + return [ + 'client_id' => $clientId ?: '', + 'client_secret' => $clientSecret ?: '', + 'tenant_id' => !empty($tenantId) ? $tenantId : 'common', + 'is_configured' => !empty($clientId) && !empty($clientSecret), + ]; +} + /** * Genereert de Microsoft OAuth2 login URL */ @@ -117,9 +201,10 @@ function microsoft_get_valid_token(int $userId): ?string { // Token is verlopen of verloopt bijna; refresh met refresh_token $refreshToken = $integration['refresh_token'] ?? ''; - $clientId = $settings['client_id'] ?? fociz_env('MICROSOFT_CLIENT_ID', ''); - $clientSecret = $settings['client_secret'] ?? fociz_env('MICROSOFT_CLIENT_SECRET', ''); - $tenantId = $settings['tenant_id'] ?? fociz_env('MICROSOFT_TENANT_ID', 'common'); + $appConfig = microsoft_get_app_config(); + $clientId = !empty($settings['client_id']) ? $settings['client_id'] : $appConfig['client_id']; + $clientSecret = !empty($settings['client_secret']) ? $settings['client_secret'] : $appConfig['client_secret']; + $tenantId = !empty($settings['tenant_id']) ? $settings['tenant_id'] : $appConfig['tenant_id']; if (empty($refreshToken) || empty($clientId) || empty($clientSecret)) { return null; @@ -251,10 +336,33 @@ function microsoft_fetch_today_events(int $userId, ?string $date = null): array /** * Ontkoppelt Microsoft integratie voor een gebruiker + * Wist uitsluitend de persoonlijke tokens; behoudt de centrale Azure App gegevens indien aanwezig. */ function microsoft_disconnect(int $userId): bool { $db = get_db_connection(); - $stmt = $db->prepare("DELETE FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); - return $stmt->execute([$userId]); + $stmt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); + $stmt->execute([$userId]); + $integration = $stmt->fetch(); + + if (!$integration) { + return true; + } + + $settings = json_decode($integration['settings'] ?? '{}', true) ?: []; + // Verwijder gebruikers-specifieke token gegevens + unset($settings['account_email'], $settings['account_name'], $settings['expires_at']); + + // Als er centrale app credentials in staan (bijv. van beheerder), behouden we deze netjes + if (!empty($settings['client_id']) || !empty($settings['client_secret'])) { + $updateStmt = $db->prepare(" + UPDATE integrations + SET access_token = NULL, refresh_token = NULL, settings = ?, last_sync_at = NULL, is_enabled = 1, is_demo_mode = 0 + WHERE id = ? + "); + return $updateStmt->execute([json_encode($settings), $integration['id']]); + } else { + $delStmt = $db->prepare("DELETE FROM integrations WHERE id = ?"); + return $delStmt->execute([$integration['id']]); + } } diff --git a/integrations.php b/integrations.php index d809995..c95a988 100644 --- a/integrations.php +++ b/integrations.php @@ -43,54 +43,106 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 2. Outlook Opslaan of Koppelen elseif ($provider === 'OUTLOOK') { - $clientId = trim($_POST['client_id'] ?? ''); - $clientSecret = trim($_POST['client_secret'] ?? ''); - $tenantId = trim($_POST['tenant_id'] ?? 'common'); - if (empty($tenantId)) $tenantId = 'common'; - $isDemo = isset($_POST['is_demo_mode']) ? 1 : 0; + $isAdmin = ($user['role'] === 'ADMIN'); + $appConfig = microsoft_get_app_config(); - // Haal bestaande tokens op - $stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); - $stmtExisting->execute([$userId]); - $existing = $stmtExisting->fetch(); - $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; + // A. Koppelen via OAuth (zowel voor Collega als Beheerder) + if ($action === 'connect_oauth') { + // Als de beheerder vanaf het admin-formulier koppelt en eventueel gewijzigde velden meestuurt + if ($isAdmin && isset($_POST['client_id'])) { + $clientId = trim($_POST['client_id'] ?? ''); + $clientSecret = trim($_POST['client_secret'] ?? ''); + $tenantId = trim($_POST['tenant_id'] ?? 'common'); + if (empty($tenantId)) $tenantId = 'common'; + $isDemo = isset($_POST['is_demo_mode']) ? 1 : 0; - // Als geheim veld leeg is gelaten of bullet tekens bevat, behoud het bestaande geheim - if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') { - $clientSecret = $existingSettings['client_secret']; + $stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); + $stmtExisting->execute([$userId]); + $existing = $stmtExisting->fetch(); + $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; + + if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') { + $clientSecret = $existingSettings['client_secret']; + } + + $newSettings = array_merge($existingSettings, [ + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'tenant_id' => $tenantId, + ]); + + $stmt = $db->prepare(" + INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) + VALUES (?, 'OUTLOOK', 1, ?, ?, NOW()) + ON DUPLICATE KEY UPDATE + is_demo_mode = VALUES(is_demo_mode), + settings = VALUES(settings), + last_sync_at = NOW() + "); + $stmt->execute([$userId, $isDemo, json_encode($newSettings)]); + + if (empty($clientId)) { + $message = "Vul eerst de Application (Client) ID in van uw Azure App Registratie."; + $messageType = 'error'; + } elseif (empty($clientSecret)) { + $message = "Vul eerst het Client Secret in van uw Azure App Registratie."; + $messageType = 'error'; + } else { + header("Location: /auth/oauth_microsoft.php"); + exit; + } + } else { + // Collega of Beheerder zonder inline velden + if (!$appConfig['is_configured']) { + $message = "Microsoft 365 is nog niet geconfigureerd door de beheerder. Vraag een beheerder om de Application ID en Client Secret in te stellen."; + $messageType = 'error'; + } else { + header("Location: /auth/oauth_microsoft.php"); + exit; + } + } } - $newSettings = array_merge($existingSettings, [ - 'client_id' => $clientId, - 'client_secret' => $clientSecret, - 'tenant_id' => $tenantId, - ]); - - $stmt = $db->prepare(" - INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) - VALUES (?, 'OUTLOOK', 1, ?, ?, NOW()) - ON DUPLICATE KEY UPDATE - is_demo_mode = VALUES(is_demo_mode), - settings = VALUES(settings), - last_sync_at = NOW() - "); - $stmt->execute([$userId, $isDemo, json_encode($newSettings)]); - - // Als gebruiker direct op 'Koppel nu' klikte - if ($action === 'connect_oauth') { - if (empty($clientId)) { - $message = "Vul eerst de Application (Client) ID in van uw Azure App Registratie."; - $messageType = 'error'; - } elseif (empty($clientSecret)) { - $message = "Vul eerst het Client Secret in van uw Azure App Registratie."; + // B. Alleen Beheerders mogen de centrale Azure instellingen opslaan + elseif ($action === 'save') { + if (!$isAdmin) { + $message = "Alleen beheerders kunnen centrale integratie-instellingen wijzigen."; $messageType = 'error'; } else { - header("Location: /auth/oauth_microsoft.php?client_id=" . urlencode($clientId) . "&tenant_id=" . urlencode($tenantId)); - exit; + $clientId = trim($_POST['client_id'] ?? ''); + $clientSecret = trim($_POST['client_secret'] ?? ''); + $tenantId = trim($_POST['tenant_id'] ?? 'common'); + if (empty($tenantId)) $tenantId = 'common'; + $isDemo = isset($_POST['is_demo_mode']) ? 1 : 0; + + $stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); + $stmtExisting->execute([$userId]); + $existing = $stmtExisting->fetch(); + $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; + + if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') { + $clientSecret = $existingSettings['client_secret']; + } + + $newSettings = array_merge($existingSettings, [ + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'tenant_id' => $tenantId, + ]); + + $stmt = $db->prepare(" + INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) + VALUES (?, 'OUTLOOK', 1, ?, ?, NOW()) + ON DUPLICATE KEY UPDATE + is_demo_mode = VALUES(is_demo_mode), + settings = VALUES(settings), + last_sync_at = NOW() + "); + $stmt->execute([$userId, $isDemo, json_encode($newSettings)]); + + $message = "Centrale instellingen voor Microsoft Outlook zijn succesvol opgeslagen."; + $messageType = 'success'; } - } else { - $message = "Instellingen voor Microsoft Outlook zijn succesvol opgeslagen."; - $messageType = 'success'; } } @@ -193,6 +245,22 @@ $jiraIsDemo = !empty($jiraData['is_demo']); // Callback redirect URI $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; + +$appConfig = microsoft_get_app_config(); +$isAdmin = ($user['role'] === 'ADMIN'); + +// Voor beheerders tonen we eventueel centraal opgeslagen waarden als fallback in de inputvelden +if ($isAdmin) { + if (empty($outlookSettings['client_id']) && !empty($appConfig['client_id'])) { + $outlookSettings['client_id'] = $appConfig['client_id']; + } + if (empty($outlookSettings['client_secret']) && !empty($appConfig['client_secret'])) { + $outlookSettings['client_secret'] = $appConfig['client_secret']; + } + if (empty($outlookSettings['tenant_id']) && !empty($appConfig['tenant_id'])) { + $outlookSettings['tenant_id'] = $appConfig['tenant_id']; + } +} ?>
- Koppel Microsoft 365 Outlook (voor realtime agenda & Teams meetings) en Atlassian Jira Cloud (om direct uw toegewezen tickets in te plannen). + = $isAdmin + ? 'Beheer centrale organisatie-integraties en koppel uw persoonlijke Outlook agenda en Jira tickets.' + : 'Koppel uw persoonlijke Microsoft 365 Outlook agenda en Atlassian Jira tickets om direct uw dagplanning in Fociz te vullen.' ?>
1. Open Azure App Registraties en maak een nieuwe registratie.
-2. Kies Accounts in elke organisatiemap (...) en persoonlijke Microsoft-accounts.
-3. Voeg een Web Omleidings-URI toe:
-4. Maak een geheim aan onder Certificaten en geheimen.
-5. Voeg API-machtigingen toe: Calendars.ReadWrite, User.Read, offline_access.
6. Cruciaal voor organisaties: Klik direct boven de tabel met machtigingen op de knop "Beheerdersgoedkeuring verlenen voor [Organisatie]" (Grant admin consent) en bevestig met 'Ja'. Hiermee voorkomt u de melding "Beheerdersgoedkeuring vereist".
+1. Open Azure App Registraties en maak een nieuwe registratie.
+2. Kies Accounts in elke organisatiemap (...) en persoonlijke Microsoft-accounts.
+3. Voeg een Web Omleidings-URI toe:
+4. Maak een geheim aan onder Certificaten en geheimen.
+5. Voeg API-machtigingen toe: Calendars.ReadWrite, User.Read, offline_access.
6. Cruciaal voor organisaties: Klik direct boven de tabel met machtigingen op de knop "Beheerdersgoedkeuring verlenen voor [Organisatie]" (Grant admin consent) en bevestig met 'Ja'. Hiermee voorkomt u de melding "Beheerdersgoedkeuring vereist".
+ + + +