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']; + } +} ?>
@@ -208,12 +276,17 @@ $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; Integraties Hub

- Koppel Microsoft 365 Outlook (voor realtime agenda & Teams meetings) en Atlassian Jira Cloud (om direct uw toegewezen tickets in te plannen). +

+ + +
Outlook: @@ -291,151 +364,217 @@ $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
- -
- - - - Azure App Registratie Instructies - - - -
-

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:

-
- - + + +
+ +
+ Centrale Beheerdersinstellingen + Deze Azure App gegevens gelden voor alle collega's in de organisatie. Gewone gebruikers zien deze technische details niet en hoeven alleen op 'Koppelen' te klikken.
-

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".

+
+ + +
+ + + + Azure App Registratie Instructies + + + +
+

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".

+ + + +
+
+ + + +
+ +
+ Configuratie vereist door beheerder + + Uw beheerder heeft de Microsoft 365 integratie nog niet geconfigureerd voor uw organisatie. Vraag uw beheerder om de Azure App gegevens in te stellen. + +
+
+ +
+ +
+ Gereed voor Koppeling + + Uw beheerder heeft Microsoft 365 klaargezet voor uw organisatie. Koppel uw persoonlijke werkagenda om al uw meetings en Teams-afspraken automatisch in uw dagplanning te laden. + +
+
+ + +
+ + + +
+ + +
+ + +

Te vinden op de Overzichtspagina (Overview) van uw Azure App Registratie.

+
+ +
+ +
+ + +
+

Aangemaakt onder Certificaten en geheimen > Nieuw clientgeheim (kopieer de Waarde / Value).

+
+ +
+
+ + Standaard: common +
+ +
+

+ 💡 Wat moet hier staan? +

+

• Voor uw organisatie: vul uw domein in (bijv. planningsagenda.nl) of uw Tenant GUID.

+

• Voor multi-tenant apps: vul organizations of common in.

+
+ -
- - - Direct Beheerdersgoedkeuring verlenen via Microsoft → - +
+ +
+ Krijgt u "Beheerdersgoedkeuring vereist"? + Stuur deze officiële Microsoft goedkeuringslink naar uw IT-beheerder om Fociz met 1 klik goed te keuren voor : +
+ + +
+ +
-
-
- - - - -
-
-
- -
- - + +
-

Aangemaakt onder Certificaten en geheimen > Nieuw clientgeheim (kopieer de Waarde / Value).

-
-
-
- - Standaard: common -
- -
-

- 💡 Wat moet hier staan? -

-

• Voor uw organisatie: vul uw domein in (bijv. planningsagenda.nl) of uw Tenant GUID.

-

• Voor multi-tenant apps: vul organizations of common in.

-
- - -
- -
- Krijgt u "Beheerdersgoedkeuring vereist"? - Stuur deze officiële Microsoft goedkeuringslink naar uw IT-beheerder om Fociz met 1 klik goed te keuren voor : -
- - -
- -
-
+ + + + + + +
+ + + +
+ + + +
+ +
+ + +
- - - -
- - - -
- - - - - +