Support for SMTP OAuth authentication through easier IEmailSenderClient implementation (#17484)
* Implement IEmailSenderClient interface and implementation
In an effort to support oauth 2 and other schemes, we extract a emailsenderclient interface, allowing to replace default smtp client with one that fits the usecase, without having to implement all of Umbracos logic that builds the mimemessage
* fix test
* Documentation
* EmailMessageExtensions public, use EmailMessage in interface and impl.
* move mimemessage into implementation
* revert EmailMessageExtensions back to internal
* use StaticServiceProvider to avoid breaking change
* Fix test after changing constructor
* revert constructor change and add new constructor an obsoletes
* Moved a paranthesis so it will build in release-mode
(cherry picked from commit bff321e6f5)
This commit is contained in:
committed by
Bjarke Berg
parent
2d69eb66ef
commit
7cc8f844f7
@@ -46,6 +46,7 @@ using Umbraco.Cms.Infrastructure.HealthChecks;
|
||||
using Umbraco.Cms.Infrastructure.HostedServices;
|
||||
using Umbraco.Cms.Infrastructure.Install;
|
||||
using Umbraco.Cms.Infrastructure.Mail;
|
||||
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||
using Umbraco.Cms.Infrastructure.Manifest;
|
||||
using Umbraco.Cms.Infrastructure.Migrations;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
||||
@@ -172,14 +173,18 @@ public static partial class UmbracoBuilderExtensions
|
||||
|
||||
builder.Services.AddSingleton<IContentLastChanceFinder, ContentFinderByConfigured404>();
|
||||
|
||||
builder.Services.AddTransient<IEmailSenderClient, BasicSmtpEmailSenderClient>();
|
||||
|
||||
// replace
|
||||
builder.Services.AddSingleton<IEmailSender, EmailSender>(
|
||||
services => new EmailSender(
|
||||
services.GetRequiredService<ILogger<EmailSender>>(),
|
||||
services.GetRequiredService<IOptionsMonitor<GlobalSettings>>(),
|
||||
services.GetRequiredService<IEventAggregator>(),
|
||||
services.GetRequiredService<IEmailSenderClient>(),
|
||||
services.GetService<INotificationHandler<SendEmailNotification>>(),
|
||||
services.GetService<INotificationAsyncHandler<SendEmailNotification>>()));
|
||||
|
||||
builder.Services.AddTransient<IUserInviteSender, EmailUserInviteSender>();
|
||||
builder.Services.AddTransient<IUserForgotPasswordSender, EmailUserForgotPasswordSender>();
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Net.Mail;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models.Email;
|
||||
using Umbraco.Cms.Infrastructure.Extensions;
|
||||
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||
using SecureSocketOptions = MailKit.Security.SecureSocketOptions;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Mail
|
||||
{
|
||||
/// <summary>
|
||||
/// A basic SMTP email sender client using MailKits SMTP client.
|
||||
/// </summary>
|
||||
public class BasicSmtpEmailSenderClient : IEmailSenderClient
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
public BasicSmtpEmailSenderClient(IOptionsMonitor<GlobalSettings> globalSettings)
|
||||
{
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
}
|
||||
|
||||
public async Task SendAsync(EmailMessage message)
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
|
||||
await client.ConnectAsync(
|
||||
_globalSettings.Smtp!.Host,
|
||||
_globalSettings.Smtp.Port,
|
||||
(SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_globalSettings.Smtp.Username) &&
|
||||
!string.IsNullOrWhiteSpace(_globalSettings.Smtp.Password))
|
||||
{
|
||||
await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
|
||||
}
|
||||
|
||||
var mimeMessage = message.ToMimeMessage(_globalSettings.Smtp!.From);
|
||||
|
||||
if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network)
|
||||
{
|
||||
await client.SendAsync(mimeMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Send(mimeMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Net.Mail;
|
||||
using MailKit.Net.Smtp;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using MimeKit.IO;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Mail;
|
||||
using Umbraco.Cms.Core.Models.Email;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Infrastructure.Extensions;
|
||||
using SecureSocketOptions = MailKit.Security.SecureSocketOptions;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Mail;
|
||||
|
||||
@@ -28,15 +28,18 @@ public class EmailSender : IEmailSender
|
||||
private readonly ILogger<EmailSender> _logger;
|
||||
private readonly bool _notificationHandlerRegistered;
|
||||
private GlobalSettings _globalSettings;
|
||||
private readonly IEmailSenderClient _emailSenderClient;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public EmailSender(
|
||||
ILogger<EmailSender> logger,
|
||||
IOptionsMonitor<GlobalSettings> globalSettings,
|
||||
IEventAggregator eventAggregator)
|
||||
: this(logger, globalSettings, eventAggregator, null, null)
|
||||
: this(logger, globalSettings, eventAggregator,null, null)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||
public EmailSender(
|
||||
ILogger<EmailSender> logger,
|
||||
IOptionsMonitor<GlobalSettings> globalSettings,
|
||||
@@ -48,6 +51,24 @@ public class EmailSender : IEmailSender
|
||||
_eventAggregator = eventAggregator;
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
_notificationHandlerRegistered = handler1 is not null || handler2 is not null;
|
||||
_emailSenderClient = StaticServiceProvider.Instance.GetRequiredService<IEmailSenderClient>();
|
||||
globalSettings.OnChange(x => _globalSettings = x);
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public EmailSender(
|
||||
ILogger<EmailSender> logger,
|
||||
IOptionsMonitor<GlobalSettings> globalSettings,
|
||||
IEventAggregator eventAggregator,
|
||||
IEmailSenderClient emailSenderClient,
|
||||
INotificationHandler<SendEmailNotification>? handler1,
|
||||
INotificationAsyncHandler<SendEmailNotification>? handler2)
|
||||
{
|
||||
_logger = logger;
|
||||
_eventAggregator = eventAggregator;
|
||||
_globalSettings = globalSettings.CurrentValue;
|
||||
_notificationHandlerRegistered = handler1 is not null || handler2 is not null;
|
||||
_emailSenderClient = emailSenderClient;
|
||||
globalSettings.OnChange(x => _globalSettings = x);
|
||||
}
|
||||
|
||||
@@ -152,29 +173,7 @@ public class EmailSender : IEmailSender
|
||||
while (true);
|
||||
}
|
||||
|
||||
using var client = new SmtpClient();
|
||||
|
||||
await client.ConnectAsync(
|
||||
_globalSettings.Smtp!.Host,
|
||||
_globalSettings.Smtp.Port,
|
||||
(SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_globalSettings.Smtp.Username) &&
|
||||
!string.IsNullOrWhiteSpace(_globalSettings.Smtp.Password))
|
||||
{
|
||||
await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
|
||||
}
|
||||
|
||||
var mailMessage = message.ToMimeMessage(_globalSettings.Smtp.From);
|
||||
if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network)
|
||||
{
|
||||
await client.SendAsync(mailMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Send(mailMessage);
|
||||
}
|
||||
|
||||
await client.DisconnectAsync(true);
|
||||
await _emailSenderClient.SendAsync(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Umbraco.Cms.Core.Models.Email;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Mail.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Client for sending an email from a MimeMessage
|
||||
/// </summary>
|
||||
public interface IEmailSenderClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends the email message
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public Task SendAsync(EmailMessage message);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Mail;
|
||||
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
|
||||
using Umbraco.Cms.Persistence.SqlServer.Services;
|
||||
@@ -80,7 +81,7 @@ public static class TestHelper
|
||||
|
||||
public static UriUtility UriUtility => s_testHelperInternal.UriUtility;
|
||||
|
||||
public static IEmailSender EmailSender { get; } = new EmailSender(new NullLogger<EmailSender>(), new TestOptionsMonitor<GlobalSettings>(new GlobalSettings()), Mock.Of<IEventAggregator>());
|
||||
public static IEmailSender EmailSender { get; } = new EmailSender(new NullLogger<EmailSender>(), new TestOptionsMonitor<GlobalSettings>(new GlobalSettings()), Mock.Of<IEventAggregator>(), Mock.Of<IEmailSenderClient>(), null,null);
|
||||
|
||||
public static ITypeFinder GetTypeFinder() => s_testHelperInternal.GetTypeFinder();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user