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.HostedServices;
|
||||||
using Umbraco.Cms.Infrastructure.Install;
|
using Umbraco.Cms.Infrastructure.Install;
|
||||||
using Umbraco.Cms.Infrastructure.Mail;
|
using Umbraco.Cms.Infrastructure.Mail;
|
||||||
|
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||||
using Umbraco.Cms.Infrastructure.Manifest;
|
using Umbraco.Cms.Infrastructure.Manifest;
|
||||||
using Umbraco.Cms.Infrastructure.Migrations;
|
using Umbraco.Cms.Infrastructure.Migrations;
|
||||||
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
||||||
@@ -172,14 +173,18 @@ public static partial class UmbracoBuilderExtensions
|
|||||||
|
|
||||||
builder.Services.AddSingleton<IContentLastChanceFinder, ContentFinderByConfigured404>();
|
builder.Services.AddSingleton<IContentLastChanceFinder, ContentFinderByConfigured404>();
|
||||||
|
|
||||||
|
builder.Services.AddTransient<IEmailSenderClient, BasicSmtpEmailSenderClient>();
|
||||||
|
|
||||||
// replace
|
// replace
|
||||||
builder.Services.AddSingleton<IEmailSender, EmailSender>(
|
builder.Services.AddSingleton<IEmailSender, EmailSender>(
|
||||||
services => new EmailSender(
|
services => new EmailSender(
|
||||||
services.GetRequiredService<ILogger<EmailSender>>(),
|
services.GetRequiredService<ILogger<EmailSender>>(),
|
||||||
services.GetRequiredService<IOptionsMonitor<GlobalSettings>>(),
|
services.GetRequiredService<IOptionsMonitor<GlobalSettings>>(),
|
||||||
services.GetRequiredService<IEventAggregator>(),
|
services.GetRequiredService<IEventAggregator>(),
|
||||||
|
services.GetRequiredService<IEmailSenderClient>(),
|
||||||
services.GetService<INotificationHandler<SendEmailNotification>>(),
|
services.GetService<INotificationHandler<SendEmailNotification>>(),
|
||||||
services.GetService<INotificationAsyncHandler<SendEmailNotification>>()));
|
services.GetService<INotificationAsyncHandler<SendEmailNotification>>()));
|
||||||
|
|
||||||
builder.Services.AddTransient<IUserInviteSender, EmailUserInviteSender>();
|
builder.Services.AddTransient<IUserInviteSender, EmailUserInviteSender>();
|
||||||
builder.Services.AddTransient<IUserForgotPasswordSender, EmailUserForgotPasswordSender>();
|
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.
|
// Copyright (c) Umbraco.
|
||||||
// See LICENSE for more details.
|
// See LICENSE for more details.
|
||||||
|
|
||||||
using System.Net.Mail;
|
|
||||||
using MailKit.Net.Smtp;
|
using MailKit.Net.Smtp;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MimeKit.IO;
|
using MimeKit.IO;
|
||||||
using Umbraco.Cms.Core.Configuration.Models;
|
using Umbraco.Cms.Core.Configuration.Models;
|
||||||
|
using Umbraco.Cms.Core.DependencyInjection;
|
||||||
using Umbraco.Cms.Core.Events;
|
using Umbraco.Cms.Core.Events;
|
||||||
using Umbraco.Cms.Core.Mail;
|
using Umbraco.Cms.Core.Mail;
|
||||||
using Umbraco.Cms.Core.Models.Email;
|
using Umbraco.Cms.Core.Models.Email;
|
||||||
using Umbraco.Cms.Core.Notifications;
|
using Umbraco.Cms.Core.Notifications;
|
||||||
using Umbraco.Cms.Infrastructure.Extensions;
|
using Umbraco.Cms.Infrastructure.Extensions;
|
||||||
using SecureSocketOptions = MailKit.Security.SecureSocketOptions;
|
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
|
||||||
|
|
||||||
namespace Umbraco.Cms.Infrastructure.Mail;
|
namespace Umbraco.Cms.Infrastructure.Mail;
|
||||||
|
|
||||||
@@ -28,15 +28,18 @@ public class EmailSender : IEmailSender
|
|||||||
private readonly ILogger<EmailSender> _logger;
|
private readonly ILogger<EmailSender> _logger;
|
||||||
private readonly bool _notificationHandlerRegistered;
|
private readonly bool _notificationHandlerRegistered;
|
||||||
private GlobalSettings _globalSettings;
|
private GlobalSettings _globalSettings;
|
||||||
|
private readonly IEmailSenderClient _emailSenderClient;
|
||||||
|
|
||||||
|
[Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")]
|
||||||
public EmailSender(
|
public EmailSender(
|
||||||
ILogger<EmailSender> logger,
|
ILogger<EmailSender> logger,
|
||||||
IOptionsMonitor<GlobalSettings> globalSettings,
|
IOptionsMonitor<GlobalSettings> globalSettings,
|
||||||
IEventAggregator eventAggregator)
|
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(
|
public EmailSender(
|
||||||
ILogger<EmailSender> logger,
|
ILogger<EmailSender> logger,
|
||||||
IOptionsMonitor<GlobalSettings> globalSettings,
|
IOptionsMonitor<GlobalSettings> globalSettings,
|
||||||
@@ -48,6 +51,24 @@ public class EmailSender : IEmailSender
|
|||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_globalSettings = globalSettings.CurrentValue;
|
_globalSettings = globalSettings.CurrentValue;
|
||||||
_notificationHandlerRegistered = handler1 is not null || handler2 is not null;
|
_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);
|
globalSettings.OnChange(x => _globalSettings = x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,29 +173,7 @@ public class EmailSender : IEmailSender
|
|||||||
while (true);
|
while (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var client = new SmtpClient();
|
await _emailSenderClient.SendAsync(message);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.Serialization;
|
||||||
using Umbraco.Cms.Core.Strings;
|
using Umbraco.Cms.Core.Strings;
|
||||||
using Umbraco.Cms.Infrastructure.Mail;
|
using Umbraco.Cms.Infrastructure.Mail;
|
||||||
|
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
|
||||||
using Umbraco.Cms.Infrastructure.Persistence;
|
using Umbraco.Cms.Infrastructure.Persistence;
|
||||||
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
|
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
|
||||||
using Umbraco.Cms.Persistence.SqlServer.Services;
|
using Umbraco.Cms.Persistence.SqlServer.Services;
|
||||||
@@ -80,7 +81,7 @@ public static class TestHelper
|
|||||||
|
|
||||||
public static UriUtility UriUtility => s_testHelperInternal.UriUtility;
|
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();
|
public static ITypeFinder GetTypeFinder() => s_testHelperInternal.GetTypeFinder();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user