mirror of
https://github.com/itflow-org/itflow
synced 2026-03-03 04:14:54 +00:00
Changes for M365 oAuth
- Added OAuth token lifecycle helpers (expiry check, refresh, persistence). - Updated SMTP XOAUTH2 send path to automatically refresh expired/missing access tokens for Microsoft/Google providers before sending queued mail.
This commit is contained in:
@@ -43,7 +43,7 @@ class StaticTokenProvider implements OAuthTokenProvider {
|
|||||||
* Load settings
|
* Load settings
|
||||||
* ======================================================================= */
|
* ======================================================================= */
|
||||||
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
|
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
|
||||||
$row = mysqli_fetch_assoc($sql_settings);
|
$row = mysqli_fetch_array($sql_settings);
|
||||||
|
|
||||||
$config_enable_cron = intval($row['config_enable_cron']);
|
$config_enable_cron = intval($row['config_enable_cron']);
|
||||||
|
|
||||||
@@ -93,10 +93,121 @@ if (file_exists($lock_file_path)) {
|
|||||||
file_put_contents($lock_file_path, "Locked");
|
file_put_contents($lock_file_path, "Locked");
|
||||||
|
|
||||||
/** =======================================================================
|
/** =======================================================================
|
||||||
* Mail sender function (defined inside this cron)
|
* Mail OAuth helpers + sender function
|
||||||
* - Handles standard SMTP and XOAUTH2 for Google/Microsoft
|
|
||||||
* - Reuses shared OAuth settings
|
|
||||||
* ======================================================================= */
|
* ======================================================================= */
|
||||||
|
function token_is_expired(?string $expires_at): bool {
|
||||||
|
if (empty($expires_at)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ts = strtotime($expires_at);
|
||||||
|
|
||||||
|
if ($ts === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($ts - 60) <= time();
|
||||||
|
}
|
||||||
|
|
||||||
|
function http_form_post(string $url, array $fields): array {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields, '', '&'));
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
|
||||||
|
$raw = curl_exec($ch);
|
||||||
|
$err = curl_error($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => ($raw !== false && $code >= 200 && $code < 300),
|
||||||
|
'body' => $raw,
|
||||||
|
'code' => $code,
|
||||||
|
'err' => $err,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function persist_mail_oauth_tokens(string $access_token, string $expires_at, ?string $refresh_token = null): void {
|
||||||
|
global $mysqli;
|
||||||
|
|
||||||
|
$access_token_esc = mysqli_real_escape_string($mysqli, $access_token);
|
||||||
|
$expires_at_esc = mysqli_real_escape_string($mysqli, $expires_at);
|
||||||
|
|
||||||
|
$refresh_sql = '';
|
||||||
|
if (!empty($refresh_token)) {
|
||||||
|
$refresh_token_esc = mysqli_real_escape_string($mysqli, $refresh_token);
|
||||||
|
$refresh_sql = ", config_mail_oauth_refresh_token = '{$refresh_token_esc}'";
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE settings SET config_mail_oauth_access_token = '{$access_token_esc}', config_mail_oauth_access_token_expires_at = '{$expires_at_esc}'{$refresh_sql} WHERE company_id = 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh_mail_oauth_access_token(string $provider, string $oauth_client_id, string $oauth_client_secret, string $oauth_tenant_id, string $oauth_refresh_token): ?array {
|
||||||
|
if (empty($oauth_client_id) || empty($oauth_client_secret) || empty($oauth_refresh_token)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($provider === 'google_oauth') {
|
||||||
|
$response = http_form_post('https://oauth2.googleapis.com/token', [
|
||||||
|
'client_id' => $oauth_client_id,
|
||||||
|
'client_secret' => $oauth_client_secret,
|
||||||
|
'refresh_token' => $oauth_refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
]);
|
||||||
|
} elseif ($provider === 'microsoft_oauth') {
|
||||||
|
if (empty($oauth_tenant_id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_url = "https://login.microsoftonline.com/" . rawurlencode($oauth_tenant_id) . "/oauth2/v2.0/token";
|
||||||
|
$response = http_form_post($token_url, [
|
||||||
|
'client_id' => $oauth_client_id,
|
||||||
|
'client_secret' => $oauth_client_secret,
|
||||||
|
'refresh_token' => $oauth_refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['ok']) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($response['body'], true);
|
||||||
|
|
||||||
|
if (!is_array($json) || empty($json['access_token'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'access_token' => $json['access_token'],
|
||||||
|
'expires_at' => $expires_at,
|
||||||
|
'refresh_token' => $json['refresh_token'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_mail_oauth_access_token(string $provider, string $oauth_client_id, string $oauth_client_secret, string $oauth_tenant_id, string $oauth_refresh_token, string $oauth_access_token, string $oauth_access_token_expires_at): ?string {
|
||||||
|
if (!empty($oauth_access_token) && !token_is_expired($oauth_access_token_expires_at)) {
|
||||||
|
return $oauth_access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens = refresh_mail_oauth_access_token($provider, $oauth_client_id, $oauth_client_secret, $oauth_tenant_id, $oauth_refresh_token);
|
||||||
|
|
||||||
|
if (!is_array($tokens) || empty($tokens['access_token']) || empty($tokens['expires_at'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_mail_oauth_tokens($tokens['access_token'], $tokens['expires_at'], $tokens['refresh_token'] ?? null);
|
||||||
|
|
||||||
|
return $tokens['access_token'];
|
||||||
|
}
|
||||||
|
|
||||||
function sendQueueEmail(
|
function sendQueueEmail(
|
||||||
string $provider,
|
string $provider,
|
||||||
string $host,
|
string $host,
|
||||||
@@ -153,27 +264,21 @@ function sendQueueEmail(
|
|||||||
$mail->AuthType = 'XOAUTH2';
|
$mail->AuthType = 'XOAUTH2';
|
||||||
$mail->Username = $username;
|
$mail->Username = $username;
|
||||||
|
|
||||||
// Pick/refresh access token
|
$access_token = resolve_mail_oauth_access_token(
|
||||||
$accessToken = trim($oauth_access_token);
|
$provider,
|
||||||
$needsRefresh = empty($accessToken);
|
trim($oauth_client_id),
|
||||||
if (!$needsRefresh && !empty($oauth_access_token_expires_at)) {
|
trim($oauth_client_secret),
|
||||||
$expTs = strtotime($oauth_access_token_expires_at);
|
trim($oauth_tenant_id),
|
||||||
if ($expTs && $expTs <= time() + 60) $needsRefresh = true;
|
trim($oauth_refresh_token),
|
||||||
}
|
trim($oauth_access_token),
|
||||||
|
trim($oauth_access_token_expires_at)
|
||||||
|
);
|
||||||
|
|
||||||
if ($needsRefresh) {
|
if (empty($access_token)) {
|
||||||
if ($provider === 'google_oauth' && function_exists('getGoogleAccessToken')) {
|
|
||||||
$accessToken = getGoogleAccessToken($username);
|
|
||||||
} elseif ($provider === 'microsoft_oauth' && function_exists('getMicrosoftAccessToken')) {
|
|
||||||
$accessToken = getMicrosoftAccessToken($username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($accessToken)) {
|
|
||||||
throw new Exception("Missing OAuth access token for XOAUTH2 SMTP.");
|
throw new Exception("Missing OAuth access token for XOAUTH2 SMTP.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$mail->setOAuth(new StaticTokenProvider($username, $accessToken));
|
$mail->setOAuth(new StaticTokenProvider($username, $access_token));
|
||||||
} else {
|
} else {
|
||||||
// Standard SMTP (with or without auth)
|
// Standard SMTP (with or without auth)
|
||||||
$mail->SMTPAuth = !empty($username);
|
$mail->SMTPAuth = !empty($username);
|
||||||
@@ -202,7 +307,7 @@ function sendQueueEmail(
|
|||||||
$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()");
|
$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()");
|
||||||
|
|
||||||
if (mysqli_num_rows($sql_queue) > 0) {
|
if (mysqli_num_rows($sql_queue) > 0) {
|
||||||
while ($rowq = mysqli_fetch_assoc($sql_queue)) {
|
while ($rowq = mysqli_fetch_array($sql_queue)) {
|
||||||
$email_id = (int)$rowq['email_id'];
|
$email_id = (int)$rowq['email_id'];
|
||||||
$email_from = $rowq['email_from'];
|
$email_from = $rowq['email_from'];
|
||||||
$email_from_name = $rowq['email_from_name'];
|
$email_from_name = $rowq['email_from_name'];
|
||||||
@@ -296,7 +401,7 @@ $sql_failed_queue = mysqli_query(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (mysqli_num_rows($sql_failed_queue) > 0) {
|
if (mysqli_num_rows($sql_failed_queue) > 0) {
|
||||||
while ($rowf = mysqli_fetch_assoc($sql_failed_queue)) {
|
while ($rowf = mysqli_fetch_array($sql_failed_queue)) {
|
||||||
$email_id = (int)$rowf['email_id'];
|
$email_id = (int)$rowf['email_id'];
|
||||||
$email_from = $rowf['email_from'];
|
$email_from = $rowf['email_from'];
|
||||||
$email_from_name = $rowf['email_from_name'];
|
$email_from_name = $rowf['email_from_name'];
|
||||||
|
|||||||
Reference in New Issue
Block a user