From 6a79053a9f887963942e1b1f51029d0739d5ee44 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 09:50:49 +0200 Subject: [PATCH 01/10] Move email models into a folder --- src/Umbraco.Core/Events/SendEmailEventArgs.cs | 2 +- .../HealthChecks/NotificationMethods/EmailNotificationMethod.cs | 2 +- src/Umbraco.Core/Mail/IEmailSender.cs | 2 +- src/Umbraco.Core/Mail/NotImplementedEmailSender.cs | 2 +- src/Umbraco.Core/Models/{ => Email}/EmailMessage.cs | 2 +- src/Umbraco.Core/Models/{ => Email}/EmailMessageAttachment.cs | 2 +- src/Umbraco.Core/Notifications/SendEmailNotification.cs | 2 +- src/Umbraco.Infrastructure/EmailSender.cs | 2 +- src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs | 2 +- .../Services/Implement/NotificationService.cs | 1 + .../Extensions/EmailMessageExtensionsTests.cs | 1 + .../Controllers/AuthenticationController.cs | 1 + src/Umbraco.Web.BackOffice/Controllers/UsersController.cs | 1 + 13 files changed, 13 insertions(+), 9 deletions(-) rename src/Umbraco.Core/Models/{ => Email}/EmailMessage.cs (98%) rename src/Umbraco.Core/Models/{ => Email}/EmailMessageAttachment.cs (88%) diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs index 720f125d9c..c1e626c6c1 100644 --- a/src/Umbraco.Core/Events/SendEmailEventArgs.cs +++ b/src/Umbraco.Core/Events/SendEmailEventArgs.cs @@ -1,5 +1,5 @@ using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Events { diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs index 51e31b1811..6e679ddbb1 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; diff --git a/src/Umbraco.Core/Mail/IEmailSender.cs b/src/Umbraco.Core/Mail/IEmailSender.cs index 5b5adf71eb..45959a5a9a 100644 --- a/src/Umbraco.Core/Mail/IEmailSender.cs +++ b/src/Umbraco.Core/Mail/IEmailSender.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Mail { diff --git a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs index 3632e79c23..29265b4038 100644 --- a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs +++ b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Mail { diff --git a/src/Umbraco.Core/Models/EmailMessage.cs b/src/Umbraco.Core/Models/Email/EmailMessage.cs similarity index 98% rename from src/Umbraco.Core/Models/EmailMessage.cs rename to src/Umbraco.Core/Models/Email/EmailMessage.cs index 153e8f33f7..74aaf0184c 100644 --- a/src/Umbraco.Core/Models/EmailMessage.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessage.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models.Email { public class EmailMessage { diff --git a/src/Umbraco.Core/Models/EmailMessageAttachment.cs b/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs similarity index 88% rename from src/Umbraco.Core/Models/EmailMessageAttachment.cs rename to src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs index ee4f3ef8cb..bbb24b69f7 100644 --- a/src/Umbraco.Core/Models/EmailMessageAttachment.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs @@ -1,6 +1,6 @@ using System.IO; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models.Email { public class EmailMessageAttachment { diff --git a/src/Umbraco.Core/Notifications/SendEmailNotification.cs b/src/Umbraco.Core/Notifications/SendEmailNotification.cs index 194ee68edc..85197c34b8 100644 --- a/src/Umbraco.Core/Notifications/SendEmailNotification.cs +++ b/src/Umbraco.Core/Notifications/SendEmailNotification.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Notifications { diff --git a/src/Umbraco.Infrastructure/EmailSender.cs b/src/Umbraco.Infrastructure/EmailSender.cs index 02e83405d6..ee5fa01195 100644 --- a/src/Umbraco.Infrastructure/EmailSender.cs +++ b/src/Umbraco.Infrastructure/EmailSender.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.Extensions; using SmtpClient = MailKit.Net.Smtp.SmtpClient; diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index cc1efa271e..5159d7bed2 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -1,7 +1,7 @@ using System; using MimeKit; using MimeKit.Text; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Infrastructure.Extensions { diff --git a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs index aedad0e56b..c6f440942e 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index 7915b6b92c..5dc66e284f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using NUnit.Framework; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Infrastructure.Extensions; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 707c486f3e..c552c0d976 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index cdd00913e7..79f02e3b4f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -26,6 +26,7 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; From 8a02118300bdd556677dbbe8edd277778c6e2ad6 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 10:41:17 +0200 Subject: [PATCH 02/10] Create notification email models --- .../Models/Email/NotificationEmailAddress.cs | 18 +++++++ .../Models/Email/NotificationEmailModel.cs | 50 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs create mode 100644 src/Umbraco.Core/Models/Email/NotificationEmailModel.cs diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs new file mode 100644 index 0000000000..04da915fab --- /dev/null +++ b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Cms.Core.Models.Email +{ + /// + /// Represents an email address used for notifications. Contains both the address and its display name. + /// + public class NotificationEmailAddress + { + public string DisplayName { get; } + + public string Adress { get; } + + public NotificationEmailAddress(string address, string displayName) + { + Adress = address; + DisplayName = displayName; + } + } +} diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs new file mode 100644 index 0000000000..a7fe0bd846 --- /dev/null +++ b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Cms.Core.Models.Email +{ + /// + /// Represents an email when sent with notifications. + /// + public class NotificationEmailModel + { + public NotificationEmailAddress From { get; } + + public IEnumerable To { get; } + + public IEnumerable Cc { get; } + + public IEnumerable Bcc { get; } + + public IEnumerable ReplyTo { get; } + + public string Subject { get; } + + public string Body { get; } + + public IList Attachments { get; } + + public bool HasAttachments => Attachments != null && Attachments.Count > 0; + + public NotificationEmailModel( + NotificationEmailAddress from, + IEnumerable to, + IEnumerable cc, + IEnumerable bcc, + IEnumerable replyTo, + string subject, + string body, + IEnumerable attachments) + { + From = from; + To = to; + Cc = cc; + Bcc = bcc; + ReplyTo = replyTo; + Subject = subject; + Body = body; + Attachments = attachments?.ToList(); + } + + } +} From 91b59c6c675c0425ae17a95d8722a9ac1e99f1f0 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 10:42:33 +0200 Subject: [PATCH 03/10] Send NotificationEmailModel with notification --- .../Notifications/SendEmailNotification.cs | 4 +-- src/Umbraco.Infrastructure/EmailSender.cs | 2 +- .../Extensions/EmailMessageExtensions.cs | 27 ++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Notifications/SendEmailNotification.cs b/src/Umbraco.Core/Notifications/SendEmailNotification.cs index 85197c34b8..3c9caabb0e 100644 --- a/src/Umbraco.Core/Notifications/SendEmailNotification.cs +++ b/src/Umbraco.Core/Notifications/SendEmailNotification.cs @@ -4,8 +4,8 @@ namespace Umbraco.Cms.Core.Notifications { public class SendEmailNotification : INotification { - public SendEmailNotification(EmailMessage message) => Message = message; + public SendEmailNotification(NotificationEmailModel message) => Message = message; - public EmailMessage Message { get; set; } + public NotificationEmailModel Message { get; } } } diff --git a/src/Umbraco.Infrastructure/EmailSender.cs b/src/Umbraco.Infrastructure/EmailSender.cs index ee5fa01195..6b51a9e56d 100644 --- a/src/Umbraco.Infrastructure/EmailSender.cs +++ b/src/Umbraco.Infrastructure/EmailSender.cs @@ -53,7 +53,7 @@ namespace Umbraco.Cms.Infrastructure { if (enableNotification) { - await _eventAggregator.PublishAsync(new SendEmailNotification(message)); + await _eventAggregator.PublishAsync(new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp.From))); } return; } diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index 5159d7bed2..62e94917a8 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using MimeKit; using MimeKit.Text; using Umbraco.Cms.Core.Models.Email; @@ -15,7 +16,7 @@ namespace Umbraco.Cms.Infrastructure.Extensions fromEmail = configuredFromAddress; } - if (!InternetAddress.TryParse(mailMessage.From, out InternetAddress fromAddress)) + if (!InternetAddress.TryParse(fromEmail, out InternetAddress fromAddress)) { throw new ArgumentException($"Email could not be sent. Could not parse from address {fromEmail} as a valid email address."); } @@ -58,6 +59,30 @@ namespace Umbraco.Cms.Infrastructure.Extensions return messageToSend; } + public static NotificationEmailModel ToNotificationEmail(this EmailMessage emailMessage, + string configuredFromAddress) + { + var mimeMessage = emailMessage.ToMimeMessage(configuredFromAddress); + if (mimeMessage.From.Any() is false || mimeMessage.To.Any() is false) + { + throw new InvalidOperationException("There must be a valid from address and recipient address."); + } + // EmailMessage only supports a single from mail, so take the first. + NotificationEmailAddress from = ToNotificationAddress(mimeMessage.From.Mailboxes.First()); + + return new NotificationEmailModel(from, + mimeMessage.To.Mailboxes.Select(ToNotificationAddress), + mimeMessage.Cc.Mailboxes.Select(ToNotificationAddress), + mimeMessage.Bcc.Mailboxes.Select(ToNotificationAddress), + mimeMessage.ReplyTo.Mailboxes.Select(ToNotificationAddress), + mimeMessage.Subject, + emailMessage.Body, + emailMessage.Attachments); + } + + private static NotificationEmailAddress ToNotificationAddress(MailboxAddress mailboxAddress) => + new NotificationEmailAddress(mailboxAddress.Address, mailboxAddress.Name); + private static void AddAddresses(MimeMessage message, string[] addresses, Func addressListGetter, bool throwIfNoneValid = false) { var foundValid = false; From d2dae7f35b87803d728858556dd7b5893865c855 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 10:57:14 +0200 Subject: [PATCH 04/10] Add null check when sending SendEmailNotification --- src/Umbraco.Infrastructure/EmailSender.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/EmailSender.cs b/src/Umbraco.Infrastructure/EmailSender.cs index 6b51a9e56d..72daad7de1 100644 --- a/src/Umbraco.Infrastructure/EmailSender.cs +++ b/src/Umbraco.Infrastructure/EmailSender.cs @@ -53,7 +53,8 @@ namespace Umbraco.Cms.Infrastructure { if (enableNotification) { - await _eventAggregator.PublishAsync(new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp.From))); + await _eventAggregator.PublishAsync( + new SendEmailNotification(message.ToNotificationEmail(_globalSettings.Smtp?.From))); } return; } From cd284fd2633f198ae265454e64d300d76a5d2af9 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 11:06:59 +0200 Subject: [PATCH 05/10] Include IsBodyHtml in NotificationEmailModel --- src/Umbraco.Core/Models/Email/NotificationEmailModel.cs | 6 +++++- .../Extensions/EmailMessageExtensions.cs | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs index a7fe0bd846..a606e8e680 100644 --- a/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs +++ b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs @@ -22,6 +22,8 @@ namespace Umbraco.Cms.Core.Models.Email public string Body { get; } + public bool IsBodyHtml { get; } + public IList Attachments { get; } public bool HasAttachments => Attachments != null && Attachments.Count > 0; @@ -34,7 +36,8 @@ namespace Umbraco.Cms.Core.Models.Email IEnumerable replyTo, string subject, string body, - IEnumerable attachments) + IEnumerable attachments, + bool isBodyHtml) { From = from; To = to; @@ -43,6 +46,7 @@ namespace Umbraco.Cms.Core.Models.Email ReplyTo = replyTo; Subject = subject; Body = body; + IsBodyHtml = isBodyHtml; Attachments = attachments?.ToList(); } diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index 62e94917a8..075c476afe 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -77,7 +77,8 @@ namespace Umbraco.Cms.Infrastructure.Extensions mimeMessage.ReplyTo.Mailboxes.Select(ToNotificationAddress), mimeMessage.Subject, emailMessage.Body, - emailMessage.Attachments); + emailMessage.Attachments, + emailMessage.IsBodyHtml); } private static NotificationEmailAddress ToNotificationAddress(MailboxAddress mailboxAddress) => From 3d0a72c39b09a53f6a7213f7f9ced530b4ad1cf9 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 11:17:35 +0200 Subject: [PATCH 06/10] Allow fallback to configured sender --- src/Umbraco.Core/Models/Email/EmailMessage.cs | 1 - .../Extensions/EmailMessageExtensionsTests.cs | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/Email/EmailMessage.cs b/src/Umbraco.Core/Models/Email/EmailMessage.cs index 74aaf0184c..55e30f2150 100644 --- a/src/Umbraco.Core/Models/Email/EmailMessage.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessage.cs @@ -33,7 +33,6 @@ namespace Umbraco.Cms.Core.Models.Email public EmailMessage(string from, string[] to, string[] cc, string[] bcc, string[] replyTo, string subject, string body, bool isBodyHtml, IEnumerable attachments) { - ArgumentIsNotNullOrEmpty(from, nameof(from)); ArgumentIsNotNullOrEmpty(to, nameof(to)); ArgumentIsNotNullOrEmpty(subject, nameof(subject)); ArgumentIsNotNullOrEmpty(body, nameof(body)); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index 5dc66e284f..43a17c2fb4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -78,5 +78,25 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions Assert.AreEqual(body, result.TextBody.ToString()); Assert.AreEqual(1, result.Attachments.Count()); } + + [Test] + public void Can_Construct_MimeMessage_With_ConfiguredSender() + { + const string to = "to@email.com"; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMesasge = new EmailMessage(null, to, subject, body, isBodyHtml); + + var result = emailMesasge.ToMimeMessage(ConfiguredSender); + + Assert.AreEqual(1, result.From.Count()); + Assert.AreEqual(ConfiguredSender, result.From.First().ToString()); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual(to, result.To.First().ToString()); + Assert.AreEqual(subject, result.Subject); + Assert.IsNull(result.TextBody); + Assert.AreEqual(body, result.HtmlBody.ToString()); + } } } From ba129b045bc527f3313bd9c4970e7ab185c79db6 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 11:39:25 +0200 Subject: [PATCH 07/10] Add unit tests --- .../Extensions/EmailMessageExtensionsTests.cs | 112 +++++++++++++++++- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index 43a17c2fb4..d53ecbf3f0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -25,9 +25,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions const string subject = "Subject"; const string body = "

Message

"; const bool isBodyHtml = true; - var emailMesasge = new EmailMessage(from, to, subject, body, isBodyHtml); + var emailMessage = new EmailMessage(from, to, subject, body, isBodyHtml); - var result = emailMesasge.ToMimeMessage(ConfiguredSender); + var result = emailMessage.ToMimeMessage(ConfiguredSender); Assert.AreEqual(1, result.From.Count()); Assert.AreEqual(from, result.From.First().ToString()); @@ -55,9 +55,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions { new EmailMessageAttachment(attachmentStream, "test.txt"), }; - var emailMesasge = new EmailMessage(from, to, cc, bcc, replyTo, subject, body, isBodyHtml, attachments); + var emailMessage = new EmailMessage(from, to, cc, bcc, replyTo, subject, body, isBodyHtml, attachments); - var result = emailMesasge.ToMimeMessage(ConfiguredSender); + var result = emailMessage.ToMimeMessage(ConfiguredSender); Assert.AreEqual(1, result.From.Count()); Assert.AreEqual(from, result.From.First().ToString()); @@ -86,9 +86,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions const string subject = "Subject"; const string body = "

Message

"; const bool isBodyHtml = true; - var emailMesasge = new EmailMessage(null, to, subject, body, isBodyHtml); + var emailMessage = new EmailMessage(null, to, subject, body, isBodyHtml); - var result = emailMesasge.ToMimeMessage(ConfiguredSender); + var result = emailMessage.ToMimeMessage(ConfiguredSender); Assert.AreEqual(1, result.From.Count()); Assert.AreEqual(ConfiguredSender, result.From.First().ToString()); @@ -98,5 +98,105 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions Assert.IsNull(result.TextBody); Assert.AreEqual(body, result.HtmlBody.ToString()); } + + [Test] + public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage() + { + const string from = "from@email.com"; + const string to = "to@email.com"; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(from, to, subject, body, isBodyHtml); + + NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual(from, result.From.Adress); + Assert.AreEqual("", result.From.DisplayName); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual(to, result.To.First().Adress); + Assert.AreEqual("", result.To.First().DisplayName); + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.IsTrue(result.IsBodyHtml); + Assert.IsFalse(result.HasAttachments); + } + + [Test] + public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage_With_DisplayName() + { + const string from = "\"From Email\" "; + const string to = "\"To Email\" "; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(from, to, subject, body, isBodyHtml); + + NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual("from@from.com", result.From.Adress); + Assert.AreEqual("From Email", result.From.DisplayName); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual("to@to.com", result.To.First().Adress); + Assert.AreEqual("To Email", result.To.First().DisplayName); + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.IsTrue(result.IsBodyHtml); + Assert.IsFalse(result.HasAttachments); + } + + + [Test] + public void Can_Construct_NotificationEmailModel_From_Full_EmailMessage() + { + const string from = "\"From Email\" "; + string[] to = new[] { "to@email.com", "\"Second Email\" " }; + string[] cc = new[] { "\"First CC\" ", "cc2@email.com" }; + string[] bcc = new[] { "bcc@email.com", "bcc2@email.com", "\"Third BCC\" ", "invalid@email@address" }; + string[] replyTo = new[] { "replyto@email.com" }; + const string subject = "Subject"; + const string body = "Message"; + const bool isBodyHtml = false; + + using var attachmentStream = new MemoryStream(Encoding.UTF8.GetBytes("test")); + var attachments = new List + { + new EmailMessageAttachment(attachmentStream, "test.txt"), + }; + var emailMessage = new EmailMessage(from, to, cc, bcc, replyTo, subject, body, isBodyHtml, attachments); + + var result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual("from@from.com", result.From.Adress); + Assert.AreEqual("From Email", result.From.DisplayName); + + Assert.AreEqual(2, result.To.Count()); + Assert.AreEqual("to@email.com", result.To.First().Adress); + Assert.AreEqual("", result.To.First().DisplayName); + Assert.AreEqual("to2@email.com", result.To.Skip(1).First().Adress); + Assert.AreEqual("Second Email", result.To.Skip(1).First().DisplayName); + + Assert.AreEqual(2, result.Cc.Count()); + Assert.AreEqual("cc@email.com", result.Cc.First().Adress); + Assert.AreEqual("First CC", result.Cc.First().DisplayName); + Assert.AreEqual("cc2@email.com", result.Cc.Skip(1).First().Adress); + Assert.AreEqual("", result.Cc.Skip(1).First().DisplayName); + + Assert.AreEqual(3, result.Bcc.Count()); + Assert.AreEqual("bcc@email.com", result.Bcc.First().Adress); + Assert.AreEqual("", result.Bcc.First().DisplayName); + Assert.AreEqual("bcc2@email.com", result.Bcc.Skip(1).First().Adress); + Assert.AreEqual("", result.Bcc.Skip(1).First().DisplayName); + Assert.AreEqual("bcc3@email.com", result.Bcc.Skip(2).First().Adress); + Assert.AreEqual("Third BCC", result.Bcc.Skip(2).First().DisplayName); + + Assert.AreEqual(1, result.ReplyTo.Count()); + Assert.AreEqual("replyto@email.com", result.ReplyTo.First().Adress); + Assert.AreEqual("", result.ReplyTo.First().DisplayName); + + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.AreEqual(1, result.Attachments.Count()); + } } } From baf13adef898a66c99e917e71d53c61cb4b2d787 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 13:10:28 +0200 Subject: [PATCH 08/10] Don't convert to MimeMessage --- .../Extensions/EmailMessageExtensions.cs | 55 ++++++++++++++----- .../Extensions/EmailMessageExtensionsTests.cs | 8 +-- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index 075c476afe..b4a9d4942e 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using System.Collections.Generic; using MimeKit; using MimeKit.Text; using Umbraco.Cms.Core.Models.Email; @@ -62,27 +62,52 @@ namespace Umbraco.Cms.Infrastructure.Extensions public static NotificationEmailModel ToNotificationEmail(this EmailMessage emailMessage, string configuredFromAddress) { - var mimeMessage = emailMessage.ToMimeMessage(configuredFromAddress); - if (mimeMessage.From.Any() is false || mimeMessage.To.Any() is false) - { - throw new InvalidOperationException("There must be a valid from address and recipient address."); - } - // EmailMessage only supports a single from mail, so take the first. - NotificationEmailAddress from = ToNotificationAddress(mimeMessage.From.Mailboxes.First()); + NotificationEmailAddress from = ToNotificationAddress(emailMessage.From); return new NotificationEmailModel(from, - mimeMessage.To.Mailboxes.Select(ToNotificationAddress), - mimeMessage.Cc.Mailboxes.Select(ToNotificationAddress), - mimeMessage.Bcc.Mailboxes.Select(ToNotificationAddress), - mimeMessage.ReplyTo.Mailboxes.Select(ToNotificationAddress), - mimeMessage.Subject, + GetNotificationAddresses(emailMessage.To), + GetNotificationAddresses(emailMessage.Cc), + GetNotificationAddresses(emailMessage.Bcc), + GetNotificationAddresses(emailMessage.ReplyTo), + emailMessage.Subject, emailMessage.Body, emailMessage.Attachments, emailMessage.IsBodyHtml); } - private static NotificationEmailAddress ToNotificationAddress(MailboxAddress mailboxAddress) => - new NotificationEmailAddress(mailboxAddress.Address, mailboxAddress.Name); + private static NotificationEmailAddress ToNotificationAddress(string address) + { + if (InternetAddress.TryParse(address, out InternetAddress internetAddress)) + { + if (internetAddress is MailboxAddress mailboxAddress) + { + return new NotificationEmailAddress(mailboxAddress.Address, internetAddress.Name); + } + } + + return null; + } + + private static IEnumerable GetNotificationAddresses(IEnumerable addresses) + { + if (addresses is null) + { + return null; + } + + var notificationAddresses = new List(); + + foreach (var address in addresses) + { + NotificationEmailAddress notificationAddress = ToNotificationAddress(address); + if (notificationAddress is not null) + { + notificationAddresses.Add(notificationAddress); + } + } + + return notificationAddresses; + } private static void AddAddresses(MimeMessage message, string[] addresses, Func addressListGetter, bool throwIfNoneValid = false) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index d53ecbf3f0..5112615431 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -150,10 +150,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions public void Can_Construct_NotificationEmailModel_From_Full_EmailMessage() { const string from = "\"From Email\" "; - string[] to = new[] { "to@email.com", "\"Second Email\" " }; - string[] cc = new[] { "\"First CC\" ", "cc2@email.com" }; - string[] bcc = new[] { "bcc@email.com", "bcc2@email.com", "\"Third BCC\" ", "invalid@email@address" }; - string[] replyTo = new[] { "replyto@email.com" }; + string[] to = { "to@email.com", "\"Second Email\" ", "invalid@invalid@invalid" }; + string[] cc = { "\"First CC\" ", "cc2@email.com", "invalid@invalid@invalid" }; + string[] bcc = { "bcc@email.com", "bcc2@email.com", "\"Third BCC\" ", "invalid@email@address" }; + string[] replyTo = { "replyto@email.com", "invalid@invalid@invalid" }; const string subject = "Subject"; const string body = "Message"; const bool isBodyHtml = false; From a56de298a324eb216478e0bfd5ec4a5f2552e881 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 14:18:49 +0200 Subject: [PATCH 09/10] Allow fallback to configured email --- .../Extensions/EmailMessageExtensions.cs | 10 ++++----- .../Extensions/EmailMessageExtensionsTests.cs | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index b4a9d4942e..3e1483c744 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -10,11 +10,7 @@ namespace Umbraco.Cms.Infrastructure.Extensions { public static MimeMessage ToMimeMessage(this EmailMessage mailMessage, string configuredFromAddress) { - var fromEmail = mailMessage.From; - if (string.IsNullOrEmpty(fromEmail)) - { - fromEmail = configuredFromAddress; - } + var fromEmail = string.IsNullOrEmpty(mailMessage.From) ? configuredFromAddress : mailMessage.From; if (!InternetAddress.TryParse(fromEmail, out InternetAddress fromAddress)) { @@ -62,7 +58,9 @@ namespace Umbraco.Cms.Infrastructure.Extensions public static NotificationEmailModel ToNotificationEmail(this EmailMessage emailMessage, string configuredFromAddress) { - NotificationEmailAddress from = ToNotificationAddress(emailMessage.From); + var fromEmail = string.IsNullOrEmpty(emailMessage.From) ? configuredFromAddress : emailMessage.From; + + NotificationEmailAddress from = ToNotificationAddress(fromEmail); return new NotificationEmailModel(from, GetNotificationAddresses(emailMessage.To), diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index 5112615431..f348a6e9a1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -122,6 +122,28 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions Assert.IsFalse(result.HasAttachments); } + [Test] + public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage_With_Configured_Sender() + { + const string to = "to@email.com"; + const string subject = "Subject"; + const string body = "

Message

"; + const bool isBodyHtml = true; + var emailMessage = new EmailMessage(null, to, subject, body, isBodyHtml); + + NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); + + Assert.AreEqual(ConfiguredSender, result.From.Adress); + Assert.AreEqual("", result.From.DisplayName); + Assert.AreEqual(1, result.To.Count()); + Assert.AreEqual(to, result.To.First().Adress); + Assert.AreEqual("", result.To.First().DisplayName); + Assert.AreEqual(subject, result.Subject); + Assert.AreEqual(body, result.Body); + Assert.IsTrue(result.IsBodyHtml); + Assert.IsFalse(result.HasAttachments); + } + [Test] public void Can_Construct_NotificationEmailModel_From_Simple_MailMessage_With_DisplayName() { From 96f88ac990c6423756582dffaa05a5d0c4c154e5 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 4 Jun 2021 15:47:34 +0200 Subject: [PATCH 10/10] Apply suggestions from PR --- .../Models/Email/NotificationEmailAddress.cs | 4 +- .../Extensions/EmailMessageExtensions.cs | 42 +++++++++---------- .../Extensions/EmailMessageExtensionsTests.cs | 30 ++++++------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs index 04da915fab..755947c6a4 100644 --- a/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs +++ b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs @@ -7,11 +7,11 @@ namespace Umbraco.Cms.Core.Models.Email { public string DisplayName { get; } - public string Adress { get; } + public string Address { get; } public NotificationEmailAddress(string address, string displayName) { - Adress = address; + Address = address; DisplayName = displayName; } } diff --git a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs index 3e1483c744..f7bed9d74c 100644 --- a/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs +++ b/src/Umbraco.Infrastructure/Extensions/EmailMessageExtensions.cs @@ -55,6 +55,27 @@ namespace Umbraco.Cms.Infrastructure.Extensions return messageToSend; } + private static void AddAddresses(MimeMessage message, string[] addresses, Func addressListGetter, bool throwIfNoneValid = false) + { + var foundValid = false; + if (addresses != null) + { + foreach (var address in addresses) + { + if (InternetAddress.TryParse(address, out InternetAddress internetAddress)) + { + addressListGetter(message).Add(internetAddress); + foundValid = true; + } + } + } + + if (throwIfNoneValid && foundValid == false) + { + throw new InvalidOperationException($"Email could not be sent. Could not parse a valid recipient address."); + } + } + public static NotificationEmailModel ToNotificationEmail(this EmailMessage emailMessage, string configuredFromAddress) { @@ -106,26 +127,5 @@ namespace Umbraco.Cms.Infrastructure.Extensions return notificationAddresses; } - - private static void AddAddresses(MimeMessage message, string[] addresses, Func addressListGetter, bool throwIfNoneValid = false) - { - var foundValid = false; - if (addresses != null) - { - foreach (var address in addresses) - { - if (InternetAddress.TryParse(address, out InternetAddress internetAddress)) - { - addressListGetter(message).Add(internetAddress); - foundValid = true; - } - } - } - - if (throwIfNoneValid && foundValid == false) - { - throw new InvalidOperationException($"Email could not be sent. Could not parse a valid recipient address."); - } - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs index f348a6e9a1..709873e8a3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Extensions/EmailMessageExtensionsTests.cs @@ -111,10 +111,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); - Assert.AreEqual(from, result.From.Adress); + Assert.AreEqual(from, result.From.Address); Assert.AreEqual("", result.From.DisplayName); Assert.AreEqual(1, result.To.Count()); - Assert.AreEqual(to, result.To.First().Adress); + Assert.AreEqual(to, result.To.First().Address); Assert.AreEqual("", result.To.First().DisplayName); Assert.AreEqual(subject, result.Subject); Assert.AreEqual(body, result.Body); @@ -133,10 +133,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); - Assert.AreEqual(ConfiguredSender, result.From.Adress); + Assert.AreEqual(ConfiguredSender, result.From.Address); Assert.AreEqual("", result.From.DisplayName); Assert.AreEqual(1, result.To.Count()); - Assert.AreEqual(to, result.To.First().Adress); + Assert.AreEqual(to, result.To.First().Address); Assert.AreEqual("", result.To.First().DisplayName); Assert.AreEqual(subject, result.Subject); Assert.AreEqual(body, result.Body); @@ -156,10 +156,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions NotificationEmailModel result = emailMessage.ToNotificationEmail(ConfiguredSender); - Assert.AreEqual("from@from.com", result.From.Adress); + Assert.AreEqual("from@from.com", result.From.Address); Assert.AreEqual("From Email", result.From.DisplayName); Assert.AreEqual(1, result.To.Count()); - Assert.AreEqual("to@to.com", result.To.First().Adress); + Assert.AreEqual("to@to.com", result.To.First().Address); Assert.AreEqual("To Email", result.To.First().DisplayName); Assert.AreEqual(subject, result.Subject); Assert.AreEqual(body, result.Body); @@ -189,31 +189,31 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Extensions var result = emailMessage.ToNotificationEmail(ConfiguredSender); - Assert.AreEqual("from@from.com", result.From.Adress); + Assert.AreEqual("from@from.com", result.From.Address); Assert.AreEqual("From Email", result.From.DisplayName); Assert.AreEqual(2, result.To.Count()); - Assert.AreEqual("to@email.com", result.To.First().Adress); + Assert.AreEqual("to@email.com", result.To.First().Address); Assert.AreEqual("", result.To.First().DisplayName); - Assert.AreEqual("to2@email.com", result.To.Skip(1).First().Adress); + Assert.AreEqual("to2@email.com", result.To.Skip(1).First().Address); Assert.AreEqual("Second Email", result.To.Skip(1).First().DisplayName); Assert.AreEqual(2, result.Cc.Count()); - Assert.AreEqual("cc@email.com", result.Cc.First().Adress); + Assert.AreEqual("cc@email.com", result.Cc.First().Address); Assert.AreEqual("First CC", result.Cc.First().DisplayName); - Assert.AreEqual("cc2@email.com", result.Cc.Skip(1).First().Adress); + Assert.AreEqual("cc2@email.com", result.Cc.Skip(1).First().Address); Assert.AreEqual("", result.Cc.Skip(1).First().DisplayName); Assert.AreEqual(3, result.Bcc.Count()); - Assert.AreEqual("bcc@email.com", result.Bcc.First().Adress); + Assert.AreEqual("bcc@email.com", result.Bcc.First().Address); Assert.AreEqual("", result.Bcc.First().DisplayName); - Assert.AreEqual("bcc2@email.com", result.Bcc.Skip(1).First().Adress); + Assert.AreEqual("bcc2@email.com", result.Bcc.Skip(1).First().Address); Assert.AreEqual("", result.Bcc.Skip(1).First().DisplayName); - Assert.AreEqual("bcc3@email.com", result.Bcc.Skip(2).First().Adress); + Assert.AreEqual("bcc3@email.com", result.Bcc.Skip(2).First().Address); Assert.AreEqual("Third BCC", result.Bcc.Skip(2).First().DisplayName); Assert.AreEqual(1, result.ReplyTo.Count()); - Assert.AreEqual("replyto@email.com", result.ReplyTo.First().Adress); + Assert.AreEqual("replyto@email.com", result.ReplyTo.First().Address); Assert.AreEqual("", result.ReplyTo.First().DisplayName); Assert.AreEqual(subject, result.Subject);