diff --git a/VirtoCommerce.NotificationsModule.sln b/VirtoCommerce.NotificationsModule.sln index 55f5eb5..814a782 100644 --- a/VirtoCommerce.NotificationsModule.sln +++ b/VirtoCommerce.NotificationsModule.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtoCommerce.Notifications EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtoCommerce.NotificationsModule.Data.PostgreSql", "src\VirtoCommerce.NotificationsModule.Data.PostgreSql\VirtoCommerce.NotificationsModule.Data.PostgreSql.csproj", "{21509C88-133B-4375-B14F-F7CCAE90125D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtoCommerce.NotificationsModule.MicrosoftGraph", "src\VirtoCommerce.NotificationsModule.MicrosoftGraph\VirtoCommerce.NotificationsModule.MicrosoftGraph.csproj", "{F19BBFE0-BEB3-4ED3-95F7-833EBA2679FB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -99,6 +101,10 @@ Global {21509C88-133B-4375-B14F-F7CCAE90125D}.Debug|Any CPU.Build.0 = Debug|Any CPU {21509C88-133B-4375-B14F-F7CCAE90125D}.Release|Any CPU.ActiveCfg = Release|Any CPU {21509C88-133B-4375-B14F-F7CCAE90125D}.Release|Any CPU.Build.0 = Release|Any CPU + {F19BBFE0-BEB3-4ED3-95F7-833EBA2679FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F19BBFE0-BEB3-4ED3-95F7-833EBA2679FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F19BBFE0-BEB3-4ED3-95F7-833EBA2679FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F19BBFE0-BEB3-4ED3-95F7-833EBA2679FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -118,6 +124,7 @@ Global {E1219855-4C36-4C0A-BAAD-DEF7848C324E} = {EC6E4F2D-850E-44B1-9E77-4D1A8F830A19} {5E1291F9-197A-4FD7-9999-B07AC284172F} = {EC6E4F2D-850E-44B1-9E77-4D1A8F830A19} {21509C88-133B-4375-B14F-F7CCAE90125D} = {EC6E4F2D-850E-44B1-9E77-4D1A8F830A19} + {F19BBFE0-BEB3-4ED3-95F7-833EBA2679FB} = {EC6E4F2D-850E-44B1-9E77-4D1A8F830A19} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EF86C9C2-2D08-4D54-BDB2-9529400059E6} diff --git a/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/MicrosoftGraphEmailNotificationMessageSender.cs b/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/MicrosoftGraphEmailNotificationMessageSender.cs new file mode 100644 index 0000000..e42f2e2 --- /dev/null +++ b/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/MicrosoftGraphEmailNotificationMessageSender.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Azure.Identity; +using Microsoft.Extensions.Options; +using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Users.Item.SendMail; +using VirtoCommerce.NotificationsModule.Core.Exceptions; +using VirtoCommerce.NotificationsModule.Core.Model; +using VirtoCommerce.NotificationsModule.Core.Services; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.NotificationsModule.MicrosoftGraph; + +public class MicrosoftGraphEmailNotificationMessageSender( + IOptions microsoftGraphOptions, + IOptions emailSendingOptions) + : INotificationMessageSender +{ + public const string Name = "MicrosoftGraph"; + + private readonly MicrosoftGraphSenderOptions _microsoftGraphOptions = microsoftGraphOptions.Value; + private readonly EmailSendingOptions _emailSendingOptions = emailSendingOptions.Value; + + public virtual bool CanSend(NotificationMessage message) => message is EmailNotificationMessage; + + public async Task SendNotificationAsync(NotificationMessage message) + { + var emailNotificationMessage = message as EmailNotificationMessage + ?? throw new ArgumentException($"The message is not {nameof(EmailNotificationMessage)} type"); + + try + { + var graphMessage = new Message + { + Subject = emailNotificationMessage.Subject, + Body = new ItemBody { ContentType = BodyType.Html, Content = emailNotificationMessage.Body }, + From = NewRecipient(emailNotificationMessage.From ?? _emailSendingOptions.DefaultSender), + ToRecipients = [NewRecipient(emailNotificationMessage.To)], + CcRecipients = emailNotificationMessage.CC?.Select(NewRecipient).ToList(), + BccRecipients = emailNotificationMessage.BCC?.Select(NewRecipient).ToList() + }; + + if (!emailNotificationMessage.Attachments.IsNullOrEmpty()) + { + graphMessage.Attachments ??= []; + foreach (var attachment in emailNotificationMessage.Attachments) + { + graphMessage.Attachments.Add(new FileAttachment + { + ContentType = attachment.MimeType, + Name = attachment.FileName, + Size = Convert.ToInt32(attachment.Size), + ContentBytes = await File.ReadAllBytesAsync(attachment.FileName), + }); + } + } + + var request = new SendMailPostRequestBody { SaveToSentItems = false, Message = graphMessage }; + + var options = new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud }; + var clientSecretCredential = new ClientSecretCredential( + _microsoftGraphOptions.TenantId, + _microsoftGraphOptions.ApplicationId, + _microsoftGraphOptions.SecretValue, + options + ); + var scopes = new[] { "https://graph.microsoft.com/.default" }; + + var microsoftGraphClient = new GraphServiceClient(clientSecretCredential, scopes); + await microsoftGraphClient.Users[emailNotificationMessage.From].SendMail.PostAsync(request); + } + catch (Exception ex) + { + throw new SentNotificationException(ex); + } + } + + private static Recipient NewRecipient(string emailAddress) => new() + { + EmailAddress = new EmailAddress { Address = emailAddress } + }; +} diff --git a/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/MicrosoftGraphSenderOptions.cs b/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/MicrosoftGraphSenderOptions.cs new file mode 100644 index 0000000..45d666f --- /dev/null +++ b/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/MicrosoftGraphSenderOptions.cs @@ -0,0 +1,10 @@ +namespace VirtoCommerce.NotificationsModule.MicrosoftGraph; + +public class MicrosoftGraphSenderOptions +{ + public string ApplicationId { get; set; } + + public string TenantId { get; set; } + + public string SecretValue { get; set; } +} diff --git a/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/VirtoCommerce.NotificationsModule.MicrosoftGraph.csproj b/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/VirtoCommerce.NotificationsModule.MicrosoftGraph.csproj new file mode 100644 index 0000000..f620a1c --- /dev/null +++ b/src/VirtoCommerce.NotificationsModule.MicrosoftGraph/VirtoCommerce.NotificationsModule.MicrosoftGraph.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + 1591 + True + true + true + true + snupkg + + + + + + + + + + + + + + diff --git a/src/VirtoCommerce.NotificationsModule.Web/Module.cs b/src/VirtoCommerce.NotificationsModule.Web/Module.cs index 2aaa7f9..6c5d624 100644 --- a/src/VirtoCommerce.NotificationsModule.Web/Module.cs +++ b/src/VirtoCommerce.NotificationsModule.Web/Module.cs @@ -23,6 +23,7 @@ using VirtoCommerce.NotificationsModule.Data.SqlServer; using VirtoCommerce.NotificationsModule.LiquidRenderer; using VirtoCommerce.NotificationsModule.LiquidRenderer.Filters; +using VirtoCommerce.NotificationsModule.MicrosoftGraph; using VirtoCommerce.NotificationsModule.SendGrid; using VirtoCommerce.NotificationsModule.Smtp; using VirtoCommerce.NotificationsModule.TemplateLoader.FileSystem; @@ -104,6 +105,12 @@ public void Initialize(IServiceCollection serviceCollection) serviceCollection.AddTransient(); break; } + case MicrosoftGraphEmailNotificationMessageSender.Name: + { + serviceCollection.AddOptions().Bind(Configuration.GetSection($"Notifications:{MicrosoftGraphEmailNotificationMessageSender.Name}")).ValidateDataAnnotations(); + serviceCollection.AddTransient(); + break; + } } serviceCollection.AddOptions().Bind(Configuration.GetSection("Notifications")).ValidateDataAnnotations(); diff --git a/src/VirtoCommerce.NotificationsModule.Web/VirtoCommerce.NotificationsModule.Web.csproj b/src/VirtoCommerce.NotificationsModule.Web/VirtoCommerce.NotificationsModule.Web.csproj index a8cc6e7..13da53e 100644 --- a/src/VirtoCommerce.NotificationsModule.Web/VirtoCommerce.NotificationsModule.Web.csproj +++ b/src/VirtoCommerce.NotificationsModule.Web/VirtoCommerce.NotificationsModule.Web.csproj @@ -27,6 +27,7 @@ +