From cceda1db1eae40bcc08a8a96f436c9d61a521a06 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 28 Jul 2025 09:06:36 -0400 Subject: [PATCH] add OAuth support to Notification Job (#5372) --- .../Infrastructure/Jobs/NotificationJob.cs | 260 +++++++++++------- 1 file changed, 154 insertions(+), 106 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index e1d3e17a..6b755e89 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Threading.Tasks; using MailKit.Net.Smtp; - using Microsoft.Extensions.DependencyInjection; - +using Microsoft.Identity.Client; using MimeKit; - using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; +using MailKit.Security; namespace Oqtane.Infrastructure { @@ -27,7 +26,7 @@ namespace Oqtane.Infrastructure } // job is executed for each tenant in installation - public override string ExecuteJob(IServiceProvider provider) + public async override Task ExecuteJobAsync(IServiceProvider provider) { string log = ""; @@ -48,126 +47,175 @@ namespace Oqtane.Infrastructure if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True") { - if (settingRepository.GetSettingValue(settings, "SMTPHost", "") != "" && - settingRepository.GetSettingValue(settings, "SMTPPort", "") != "" && - settingRepository.GetSettingValue(settings, "SMTPSender", "") != "") + bool valid = true; + if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic") + { + // basic + if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPSender", "") == "") + { + log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "
"; + valid = false; + } + } + else + { + // oauth + if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPAuthority", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPClientId", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPClientSecret", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPScopes", "") == "" || + settingRepository.GetSettingValue(settings, "SMTPSender", "") == "") + { + log += "SMTP Not Configured Properly In Site Settings - Host, Port, Authority, Client ID, Client Secret, Scopes, And Sender Are All Required" + "
"; + valid = false; + } + } + + + if (valid) { // construct SMTP Client using var client = new SmtpClient(); - client.Connect(host: settingRepository.GetSettingValue(settings, "SMTPHost", ""), - port: int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), - options: bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) ? MailKit.Security.SecureSocketOptions.StartTls : MailKit.Security.SecureSocketOptions.None); + await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""), + int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), + bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) ? SecureSocketOptions.StartTls : SecureSocketOptions.None); - if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "") + if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic") { - client.Authenticate(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), - settingRepository.GetSettingValue(settings, "SMTPPassword", "")); + // it is possible to use basic without any authentication (not recommended) + if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "") + { + await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), + settingRepository.GetSettingValue(settings, "SMTPPassword", "")); + } + } + else + { + // oauth authentication + var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(settingRepository.GetSettingValue(settings, "SMTPClientId", "")) + .WithAuthority(settingRepository.GetSettingValue(settings, "SMTPAuthority", "")) + .WithClientSecret(settingRepository.GetSettingValue(settings, "SMTPClientSecret", "")) + .Build(); + try + { + var result = await confidentialClientApplication.AcquireTokenForClient(settingRepository.GetSettingValue(settings, "SMTPScopes", "").Split(',')).ExecuteAsync(); + var oauth2 = new SaslMechanismOAuth2(settingRepository.GetSettingValue(settings, "SMTPSender", ""), result.AccessToken); + await client.AuthenticateAsync(oauth2); + } + catch (Exception ex) + { + log += "SMTP Not Configured Properly In Site Settings - OAuth Token Could Not Be Retrieved From Authority - " + ex.Message + "
"; + valid = false; + } } - // iterate through undelivered notifications - int sent = 0; - List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); - foreach (Notification notification in notifications) + if (valid) { - // get sender and receiver information from user object if not provided - if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) + // iterate through undelivered notifications + int sent = 0; + List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); + foreach (Notification notification in notifications) { - var user = userRepository.GetUser(notification.FromUserId.Value); - if (user != null) + // get sender and receiver information from user object if not provided + if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) { - notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; - notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; - } - } - if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) - { - var user = userRepository.GetUser(notification.ToUserId.Value); - if (user != null) - { - notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; - notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; - } - } - - // validate recipient - if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _)) - { - log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}
"; - notification.IsDeleted = true; - notificationRepository.UpdateNotification(notification); - } - else - { - MimeMessage mailMessage = new MimeMessage(); - - // sender - if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) - { - if (!string.IsNullOrEmpty(notification.FromDisplayName)) + var user = userRepository.GetUser(notification.FromUserId.Value); + if (user != null) { - mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail)); + notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; + notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; + } + } + if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) + { + var user = userRepository.GetUser(notification.ToUserId.Value); + if (user != null) + { + notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; + notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; + } + } + + // validate recipient + if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _)) + { + log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}
"; + notification.IsDeleted = true; + notificationRepository.UpdateNotification(notification); + } + else + { + MimeMessage mailMessage = new MimeMessage(); + + // sender + if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) + { + if (!string.IsNullOrEmpty(notification.FromDisplayName)) + { + mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail)); + } + else + { + mailMessage.From.Add(new MailboxAddress("", notification.FromEmail)); + } } else { - mailMessage.From.Add(new MailboxAddress("", notification.FromEmail)); + mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name, + settingRepository.GetSettingValue(settings, "SMTPSender", ""))); + } + + // recipient + if (!string.IsNullOrEmpty(notification.ToDisplayName)) + { + mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail)); + } + else + { + mailMessage.To.Add(new MailboxAddress("", notification.ToEmail)); + } + + // subject + mailMessage.Subject = notification.Subject; + + //body + var bodyText = notification.Body; + + if (!bodyText.Contains('<') || !bodyText.Contains('>')) + { + // plain text messages should convert line breaks to HTML tags to preserve formatting + bodyText = bodyText.Replace("\n", "
"); + } + + mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8) + { + Text = bodyText + }; + + // send mail + try + { + await client.SendAsync(mailMessage); + sent++; + notification.IsDelivered = true; + notification.DeliveredOn = DateTime.UtcNow; + notificationRepository.UpdateNotification(notification); + } + catch (Exception ex) + { + // error + log += $"NotificationId: {notification.NotificationId} - {ex.Message}
"; } } - else - { - mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name, - settingRepository.GetSettingValue(settings, "SMTPSender", ""))); - } - - // recipient - if (!string.IsNullOrEmpty(notification.ToDisplayName)) - { - mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail)); - } - else - { - mailMessage.To.Add(new MailboxAddress("", notification.ToEmail)); - } - - // subject - mailMessage.Subject = notification.Subject; - - //body - var bodyText = notification.Body; - - if (!bodyText.Contains('<') || !bodyText.Contains('>')) - { - // plain text messages should convert line breaks to HTML tags to preserve formatting - bodyText = bodyText.Replace("\n", "
"); - } - - mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8) - { - Text = bodyText - }; - - // send mail - try - { - client.Send(mailMessage); - sent++; - notification.IsDelivered = true; - notification.DeliveredOn = DateTime.UtcNow; - notificationRepository.UpdateNotification(notification); - } - catch (Exception ex) - { - // error - log += $"NotificationId: {notification.NotificationId} - {ex.Message}
"; - } } + await client.DisconnectAsync(true); + log += "Notifications Delivered: " + sent + "
"; } - client.Disconnect(true); - log += "Notifications Delivered: " + sent + "
"; - } - else - { - log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "
"; } } else