diff --git a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs index 7e3ff80690..dce3d85840 100644 --- a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs +++ b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs @@ -1,3 +1,4 @@ +using System.Net.Mail; using Umbraco.Core.Configuration; namespace Umbraco.Configuration @@ -8,5 +9,10 @@ namespace Umbraco.Configuration public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public SmtpDeliveryMethod DeliveryMethod { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } } } diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index 4b30813bd5..e4995cfb0b 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Mail; using Microsoft.Extensions.Configuration; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -72,10 +73,10 @@ namespace Umbraco.Configuration.Models _configuration.GetValue(Prefix + "NoNodesViewPath", "~/config/splashes/NoNodes.cshtml"); public bool IsSmtpServerConfigured => - _configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")?.GetChildren().Any() ?? false; + _configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")?.GetChildren().Any() ?? false; public ISmtpSettings SmtpSettings => - new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")); + new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")); private class SmtpSettingsImpl : ISmtpSettings { @@ -90,6 +91,11 @@ namespace Umbraco.Configuration.Models public string Host => _configurationSection.GetValue("Host"); public int Port => _configurationSection.GetValue("Port"); public string PickupDirectoryLocation => _configurationSection.GetValue("PickupDirectoryLocation"); + public SmtpDeliveryMethod DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); + + public string Username => _configurationSection.GetValue("Username"); + + public string Password => _configurationSection.GetValue("Password"); } } } diff --git a/src/Umbraco.Core/Configuration/ISmtpSettings.cs b/src/Umbraco.Core/Configuration/ISmtpSettings.cs index c2fb4b2dbe..ea42ae5567 100644 --- a/src/Umbraco.Core/Configuration/ISmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/ISmtpSettings.cs @@ -1,3 +1,5 @@ +using System.Net.Mail; + namespace Umbraco.Core.Configuration { public interface ISmtpSettings @@ -6,5 +8,8 @@ namespace Umbraco.Core.Configuration string Host { get; } int Port{ get; } string PickupDirectoryLocation { get; } + SmtpDeliveryMethod DeliveryMethod { get; } + string Username { get; } + string Password { get; } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index 19d3716e1c..34d69c2cd5 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -320,6 +320,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index db3ae1bc25..8da86df6e8 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs similarity index 63% rename from src/Umbraco.Core/EmailSender.cs rename to src/Umbraco.Infrastructure/Users/EmailSender.cs index 5cfdd765bc..315a1748c3 100644 --- a/src/Umbraco.Core/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -1,9 +1,12 @@ using System; +using System.Linq; using System.Net.Mail; using System.Threading.Tasks; -using Umbraco.Core.Composing; +using MimeKit; +using MimeKit.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; namespace Umbraco.Core { @@ -21,7 +24,7 @@ namespace Umbraco.Core { } - internal EmailSender(IGlobalSettings globalSettings, bool enableEvents) + public EmailSender(IGlobalSettings globalSettings, bool enableEvents) { _globalSettings = globalSettings; _enableEvents = enableEvents; @@ -45,7 +48,9 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - client.Send(message); + client.Connect(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + client.Send(ConstructEmailMessage(message)); + client.Disconnect(true); } } } @@ -65,14 +70,19 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + var mailMessage = ConstructEmailMessage(message); + if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) { - await client.SendMailAsync(message); + await client.SendAsync(mailMessage); } else { - client.Send(message); + client.Send(mailMessage); } + + await client.DisconnectAsync(true); } } } @@ -83,7 +93,7 @@ namespace Umbraco.Core /// /// We assume this is possible if either an event handler is registered or an smtp server is configured /// - internal static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured; + public static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured; /// /// returns true if an event handler has been registered @@ -103,5 +113,22 @@ namespace Umbraco.Core var handler = SendEmail; if (handler != null) handler(null, e); } + + private MimeMessage ConstructEmailMessage(MailMessage mailMessage) + { + var fromEmail = mailMessage.From?.Address; + if(string.IsNullOrEmpty(fromEmail)) + fromEmail = _globalSettings.SmtpSettings.From; + + var messageToSend = new MimeMessage + { + Subject = mailMessage.Subject, + From = { new MailboxAddress(fromEmail)}, + Body = new TextPart(mailMessage.IsBodyHtml ? TextFormat.Html : TextFormat.Plain) { Text = mailMessage.Body } + }; + messageToSend.To.AddRange(mailMessage.To.Select(x=>new MailboxAddress(x.Address))); + + return messageToSend; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs index 344d7bcf87..bd85807203 100644 --- a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Configuration; +using System.Net.Mail; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.Common.Builders { @@ -16,6 +18,9 @@ namespace Umbraco.Tests.Common.Builders private string _host; private int? _port; private string _pickupDirectoryLocation; + private SmtpDeliveryMethod? _deliveryMethod; + private string _username; + private string _password; public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -33,24 +38,45 @@ namespace Umbraco.Tests.Common.Builders return this; } + public SmtpSettingsBuilder WithUsername(string username) + { + _username = username; + return this; + } + public SmtpSettingsBuilder WithPost(int port) { _port = port; return this; } + public SmtpSettingsBuilder WithPassword(string password) + { + _password = password; + return this; + } + public SmtpSettingsBuilder WithPickupDirectoryLocation(string pickupDirectoryLocation) { _pickupDirectoryLocation = pickupDirectoryLocation; return this; } + public SmtpSettingsBuilder WithDeliveryMethod(SmtpDeliveryMethod deliveryMethod) + { + _deliveryMethod = deliveryMethod; + return this; + } + public override ISmtpSettings Build() { var from = _from ?? null; var host = _host ?? null; var port = _port ?? 25; var pickupDirectoryLocation = _pickupDirectoryLocation ?? null; + var deliveryMethod = _deliveryMethod ?? SmtpDeliveryMethod.Network; + var username = _username ?? null; + var password = _password ?? null; return new TestSmtpSettings() { @@ -58,6 +84,9 @@ namespace Umbraco.Tests.Common.Builders Host = host, Port = port, PickupDirectoryLocation = pickupDirectoryLocation, + DeliveryMethod = deliveryMethod, + Username = username, + Password = password, }; } @@ -67,6 +96,9 @@ namespace Umbraco.Tests.Common.Builders public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public SmtpDeliveryMethod DeliveryMethod { get; set; } + public string Username { get; set; } + public string Password { get; set; } } } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 80f6ab9c9e..bbc869fc65 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -313,6 +313,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); + Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index a162b0cd48..f8f02560c4 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -91,7 +91,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 4c373b2bc8..ce355180f6 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -103,7 +103,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; @@ -176,7 +177,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -219,7 +221,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -297,7 +300,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -487,7 +491,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance()); + Factory.GetInstance(), + Factory.GetInstance()); var mockOwinContext = new Mock(); var mockUserManagerMarker = new Mock(); diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 46dc20034b..95fdaeec67 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -116,6 +116,16 @@ "Replacement": "" } ] + }, + "Global": { + "Smtp": { + "From": "noreply@example.com", + "Host": "127.0.0.1", + "Port": 25, + "UserName": "username", + "Password": "password", + "DeliveryMethod": "network" + } } } } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index fc34a35566..01e13ff051 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Collections.Generic; +using System.Net.Mail; using System.Security.Principal; using System.Threading.Tasks; using System.Web; @@ -47,6 +48,7 @@ namespace Umbraco.Web.Editors private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtimeState; private readonly ISecuritySettings _securitySettings; + private readonly IEmailSender _emailSender; public AuthenticationController( IUserPasswordConfiguration passwordConfiguration, @@ -60,13 +62,15 @@ namespace Umbraco.Web.Editors IRuntimeState runtimeState, UmbracoMapper umbracoMapper, ISecuritySettings securitySettings, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper, publishedUrlProvider) { _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); _securitySettings = securitySettings ?? throw new ArgumentNullException(nameof(securitySettings)); + _emailSender = emailSender; } protected BackOfficeUserManager UserManager => _userManager @@ -328,12 +332,19 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings), new[] { identityUser.UserName, callbackUrl }); - // TODO: Port email service to ASP.NET Core - /*await UserManager.SendEmailAsync(identityUser.Id, - Services.TextService.Localize("login/resetPasswordEmailCopySubject", - // Ensure the culture of the found user is used for the email! - UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)), - message);*/ + var subject = Services.TextService.Localize("login/resetPasswordEmailCopySubject", + // Ensure the culture of the found user is used for the email! + UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)); + + var mailMessage = new MailMessage() + { + Subject = subject, + Body = message, + IsBodyHtml = true, + To = { user.Email} + }; + + await _emailSender.SendAsync(mailMessage); UserManager.RaiseForgotPasswordRequestedEvent(user.Id); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 7aa71b6e2e..3c71245a0a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Mail; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading.Tasks; @@ -52,6 +53,7 @@ namespace Umbraco.Web.Editors private readonly ISqlContext _sqlContext; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly ISecuritySettings _securitySettings; + private readonly IEmailSender _emailSender; public UsersController( IGlobalSettings globalSettings, @@ -68,7 +70,8 @@ namespace Umbraco.Web.Editors IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, IPublishedUrlProvider publishedUrlProvider, - ISecuritySettings securitySettings) + ISecuritySettings securitySettings, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) { _mediaFileSystem = mediaFileSystem; @@ -77,6 +80,7 @@ namespace Umbraco.Web.Editors _sqlContext = sqlContext; _imageUrlGenerator = imageUrlGenerator; _securitySettings = securitySettings; + _emailSender = emailSender; } /// @@ -500,17 +504,15 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); - // TODO: Port email service to ASP.NET Core - /*await UserManager.EmailService.SendAsync( - //send the special UmbracoEmailMessage which configures it's own sender - //to allow for events to handle sending the message if no smtp is configured - new UmbracoEmailMessage(new EmailSender(GlobalSettings, true)) - { - Body = emailBody, - Destination = userDisplay.Email, - Subject = emailSubject - });*/ + var mailMessage = new MailMessage() + { + Subject = emailSubject, + Body = emailBody, + IsBodyHtml = true, + To = { to.Email} + }; + await _emailSender.SendAsync(mailMessage); } ///