Files
Fociz/auth/callback_microsoft.php
T

153 lines
6.1 KiB
PHP

<?php
/**
* Microsoft 365 OAuth 2.0 Callback Handler
* Verwerkt de autorisatiecode, wisselt tokens uit en slaat de verbinding op.
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
start_secure_session();
// 1. CSRF State validatie & Sessieherstel
$receivedState = $_GET['state'] ?? '';
$stateUserId = !empty($receivedState) ? verify_oauth_state($receivedState) : null;
// Als de sessie verloren is gegaan tijdens de externe cross-site redirect vanaf Microsoft,
// herstellen we de gebruiker veilig aan de hand van de cryptografisch ondertekende OAuth state!
if (empty($_SESSION['user_id']) && $stateUserId) {
try {
$db = get_db_connection();
$stmtUser = $db->prepare("SELECT id, name, email, role, two_factor_enabled, created_at FROM users WHERE id = ?");
$stmtUser->execute([$stateUserId]);
$recoveredUser = $stmtUser->fetch();
if ($recoveredUser) {
$_SESSION['user_id'] = $recoveredUser['id'];
$_SESSION['user_name'] = $recoveredUser['name'];
$_SESSION['user_email'] = $recoveredUser['email'];
$_SESSION['user_role'] = $recoveredUser['role'];
$_SESSION['is_2fa_verified'] = true;
$_SESSION['auth_date'] = date('Y-m-d');
$_SESSION['session_expires_at'] = strtotime('today 23:59:59');
clear_remember_cookie();
}
} catch (Throwable $e) {
// Foutafhandeling
}
}
// 2. Controleer authenticatie
$user = get_logged_in_user();
if (!$user) {
header('Location: /auth/login.php?error=' . urlencode('Sessie verlopen tijdens Microsoft koppeling. Log opnieuw in en probeer het nogmaals.'));
exit;
}
$db = get_db_connection();
// 3. Foutafhandeling vanuit Microsoft (bijv. gebruiker weigerde machtiging)
if (!empty($_GET['error'])) {
$errorDescription = $_GET['error_description'] ?? $_GET['error'];
header('Location: /profile.php?error=' . urlencode("Microsoft melding: {$errorDescription}"));
exit;
}
// 3b. Afhandeling van beheerderstoestemming (Admin Consent)
// Microsoft stuurt bij het /adminconsent endpoint geen state mee, enkel ?admin_consent=True&tenant=...
if (!empty($_GET['admin_consent'])) {
header('Location: /admin.php?success=' . urlencode('Beheerdersgoedkeuring voor uw organisatie is succesvol verleend! U en uw collega\'s kunnen nu direct hun Outlook agenda en e-mail koppelen via Fociz.'));
exit;
}
// 4. Valideer de state (via HMAC handtekening of fallback sessiestate)
$expectedState = $_SESSION['ms_oauth_state'] ?? '';
unset($_SESSION['ms_oauth_state'], $_SESSION['ms_oauth_user_id']);
if (!$stateUserId && (empty($receivedState) || empty($expectedState) || !hash_equals($expectedState, $receivedState))) {
header('Location: /profile.php?error=' . urlencode('Ongeldige sessie of CSRF-controle mislukt. Probeer opnieuw in te loggen via Microsoft.'));
exit;
}
// 5. Autorisatiecode controleren
$code = $_GET['code'] ?? '';
if (empty($code)) {
header('Location: /profile.php?error=' . urlencode('Geen autorisatiecode ontvangen van Microsoft.'));
exit;
}
// 4. Centrale instellingen ophalen
$appConfig = microsoft_get_app_config();
$clientId = $appConfig['client_id'];
$clientSecret = $appConfig['client_secret'];
$tenantId = (!empty($appConfig['tenant_id']) && $appConfig['tenant_id'] !== 'common') ? $appConfig['tenant_id'] : 'planningsagenda.nl';
if (empty($clientId) || empty($clientSecret)) {
header('Location: /profile.php?error=' . urlencode('Microsoft 365 configuratie (Client ID of Secret) ontbreekt in Fociz. Neem contact op met de beheerder.'));
exit;
}
$redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
// 5. Code omwisselen voor access & refresh tokens
$tokenResult = microsoft_exchange_code($code, $clientId, $clientSecret, $redirectUri, $tenantId);
if (!$tokenResult['success']) {
header('Location: /profile.php?error=' . urlencode('Token uitwisseling mislukt: ' . $tokenResult['error']));
exit;
}
$accessToken = $tokenResult['access_token'];
$refreshToken = $tokenResult['refresh_token'];
$expiresIn = $tokenResult['expires_in'];
$expiresAt = time() + $expiresIn;
// 6. Microsoft gebruikersprofiel ophalen
$profile = microsoft_get_user_profile($accessToken);
$accountEmail = $profile['mail'] ?? ($profile['userPrincipalName'] ?? $user['email']);
$accountName = $profile['displayName'] ?? 'Microsoft 365 Account';
// 7. Opslaan in MariaDB
$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())
ON DUPLICATE KEY UPDATE
is_enabled = 1,
is_demo_mode = 0,
access_token = VALUES(access_token),
refresh_token = VALUES(refresh_token),
settings = VALUES(settings),
last_sync_at = NOW()
");
$saveStmt->execute([$user['id'], $accessToken, $refreshToken, json_encode($newSettings)]);
// 8. Succesvol terugkeren naar de gewenste pagina of het Persoonlijk Profiel
$returnTo = $_SESSION['ms_oauth_return_to'] ?? '/profile.php';
unset($_SESSION['ms_oauth_return_to']);
$redirectUrl = ($returnTo === '/mail.php')
? '/mail.php?success=' . urlencode("Microsoft Outlook 365 e-mail & agenda succesvol gekoppeld als {$accountEmail}!")
: '/profile.php?success=' . urlencode("Microsoft Outlook succesvol gekoppeld als {$accountEmail}!");
header('Location: ' . $redirectUrl);
exit;