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 f28a97303e..332813faf8 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -321,6 +321,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 1b400c3506..2f9766e5c2 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -185,23 +185,19 @@ namespace Umbraco.Core.Runtime // register ourselves (TODO: Should we put this in RegisterEssentials?) composition.Register(_ => this, Lifetime.Singleton); - // determine our runtime level - DetermineRuntimeLevel(databaseFactory, ProfilingLogger); - - // get composers, and compose - var composerTypes = ResolveComposerTypes(typeLoader); - - IEnumerable enableDisableAttributes; - using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) + try { - enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + // determine our runtime level + DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + } + finally + { + // always run composers + RunComposers(typeLoader, composition); } - var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); - composers.Compose(); - - // create the factory - factory = composition.CreateFactory(); + // create the factory + factory = composition.CreateFactory(); } catch (Exception e) { @@ -286,6 +282,21 @@ namespace Umbraco.Core.Runtime }; } + private void RunComposers(TypeLoader typeLoader, Composition composition) + { + // get composers, and compose + var composerTypes = ResolveComposerTypes(typeLoader); + + IEnumerable enableDisableAttributes; + using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) + { + enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + } + + var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); + composers.Compose(); + } + private bool AcquireMainDom(IMainDom mainDom, IApplicationShutdownRegistry applicationShutdownRegistry) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 177b35b27d..7948c3ea31 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 55% rename from src/Umbraco.Core/EmailSender.cs rename to src/Umbraco.Infrastructure/Users/EmailSender.cs index 5cfdd765bc..9a2f425021 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,17 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - client.Send(message); + + client.Connect(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) + { + client.Authenticate(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + + client.Send(ConstructEmailMessage(message)); + client.Disconnect(true); } } } @@ -65,14 +78,25 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) { - await client.SendMailAsync(message); + await client.AuthenticateAsync(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + + var mailMessage = ConstructEmailMessage(message); + if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) + { + await client.SendAsync(mailMessage); } else { - client.Send(message); + client.Send(mailMessage); } + + await client.DisconnectAsync(true); } } } @@ -83,7 +107,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 +127,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 652541da0c..a35e891c1c 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -93,7 +93,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 8c40085e50..31d1f25c1d 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -105,7 +105,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; @@ -179,7 +180,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -223,7 +225,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -302,7 +305,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -493,7 +497,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.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 228f133ab0..5a68fddb73 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -27,8 +27,6 @@ namespace Umbraco.Extensions return runtime.State.Level > RuntimeLevel.BootFailed; } - - /// /// Start Umbraco /// @@ -38,27 +36,20 @@ namespace Umbraco.Extensions { if (app == null) throw new ArgumentNullException(nameof(app)); - if (app.UmbracoCanBoot()) - { - var runtime = app.ApplicationServices.GetRequiredService(); + if (!app.UmbracoCanBoot()) return app; - // Register a listener for application shutdown in order to terminate the runtime - var hostLifetime = app.ApplicationServices.GetRequiredService(); - var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime); - hostLifetime.RegisterObject(runtimeShutdown); + var runtime = app.ApplicationServices.GetRequiredService(); + // Register a listener for application shutdown in order to terminate the runtime + var hostLifetime = app.ApplicationServices.GetRequiredService(); + var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime); + hostLifetime.RegisterObject(runtimeShutdown); - // Register our global threadabort enricher for logging - var threadAbortEnricher = app.ApplicationServices.GetRequiredService(); - LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context + // Register our global threadabort enricher for logging + var threadAbortEnricher = app.ApplicationServices.GetRequiredService(); + LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context - // Start the runtime! - runtime.Start(); - } - else - { - // TODO: Register simple middleware to show the error like we used to in UmbracoModule? Or maybe that's part of a UseUmbracoWebsite/backoffice type thing .. probably :) - - } + // Start the runtime! + runtime.Start(); return app; } @@ -73,13 +64,24 @@ namespace Umbraco.Extensions { if (app == null) throw new ArgumentNullException(nameof(app)); - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + { + app.UseMiddleware(); + } + else + { + app.UseMiddleware(); + app.UseMiddleware(); + } - app.UseMiddleware(); - app.UseMiddleware(); return app; } + /// + /// Adds request based serilog enrichers to the LogContext for each request + /// + /// + /// public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuilder app) { if (app == null) throw new ArgumentNullException(nameof(app)); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs index 79e253e053..3350af756e 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs @@ -13,6 +13,8 @@ namespace Umbraco.Extensions /// public static IApplicationBuilder UseUmbracoInstaller(this IApplicationBuilder app) { + if (!app.UmbracoCanBoot()) return app; + app.UseEndpoints(endpoints => { var installerRoutes = app.ApplicationServices.GetRequiredService(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs index 559730bdc3..a3e9b901dc 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs @@ -34,10 +34,6 @@ namespace Umbraco.Extensions /// public static IServiceCollection AddUmbracoWebComponents(this IServiceCollection services) { - services.AddTransient(); - services.AddTransient(); - - services.TryAddSingleton(); services.ConfigureOptions(); services.TryAddEnumerable(ServiceDescriptor.Transient()); diff --git a/src/Umbraco.Web.Common/Install/InstallController.cs b/src/Umbraco.Web.Common/Install/InstallController.cs index 6614f2d577..a4f659379f 100644 --- a/src/Umbraco.Web.Common/Install/InstallController.cs +++ b/src/Umbraco.Web.Common/Install/InstallController.cs @@ -92,6 +92,10 @@ namespace Umbraco.Web.Common.Install return View(); } + /// + /// Used to perform the redirect to the installer when the runtime level is or + /// + /// [HttpGet] public ActionResult Redirect() { diff --git a/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs b/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs new file mode 100644 index 0000000000..685312778d --- /dev/null +++ b/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Umbraco.Core; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Web.Common.Middleware +{ + /// + /// Executes when Umbraco booting fails in order to show the problem + /// + public class BootFailedMiddleware : IMiddleware + { + private readonly IRuntimeState _runtimeState; + + public BootFailedMiddleware(IRuntimeState runtimeState) + { + _runtimeState = runtimeState; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + if (_runtimeState.Level == RuntimeLevel.BootFailed) + { + // short circuit + BootFailedException.Rethrow(_runtimeState.BootFailedException); + } + else + { + await next(context); + } + } + } +} diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs index 803eb95d62..febf939d03 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs @@ -8,6 +8,9 @@ using Umbraco.Core.Logging.Serilog.Enrichers; namespace Umbraco.Web.Common.Middleware { + /// + /// Adds request based serilog enrichers to the LogContext for each request + /// public class UmbracoRequestLoggingMiddleware : IMiddleware { private readonly HttpSessionIdEnricher _sessionIdEnricher; diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index e1662e834f..6cf3929e06 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -6,6 +6,8 @@ using Umbraco.Web.Common.Lifetime; using Umbraco.Core; using Umbraco.Core.Logging; using System.Threading; +using Umbraco.Core.Cache; +using System.Collections.Generic; namespace Umbraco.Web.Common.Middleware { @@ -18,22 +20,26 @@ namespace Umbraco.Web.Common.Middleware private readonly ILogger _logger; private readonly IUmbracoRequestLifetimeManager _umbracoRequestLifetimeManager; private readonly IUmbracoContextFactory _umbracoContextFactory; + private readonly IRequestCache _requestCache; public UmbracoRequestMiddleware( ILogger logger, IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager, - IUmbracoContextFactory umbracoContextFactory) + IUmbracoContextFactory umbracoContextFactory, + IRequestCache requestCache) { _logger = logger; _umbracoRequestLifetimeManager = umbracoRequestLifetimeManager; _umbracoContextFactory = umbracoContextFactory; + _requestCache = requestCache; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - // do not process if client-side request + var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); - if (new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) + // do not process if client-side request + if (requestUri.IsClientSideRequest()) { await next(context); return; @@ -43,6 +49,12 @@ namespace Umbraco.Web.Common.Middleware try { + if (umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest) + { + LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); + _logger.Verbose("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, requestUri); + } + try { _umbracoRequestLifetimeManager.InitRequest(context); @@ -52,15 +64,80 @@ namespace Umbraco.Web.Common.Middleware // try catch so we don't kill everything in all requests _logger.Error(ex); } - - await next(context); - - _umbracoRequestLifetimeManager.EndRequest(context); + finally + { + try + { + await next(context); + } + finally + { + _umbracoRequestLifetimeManager.EndRequest(context); + } + } } finally { - umbracoContextReference.Dispose(); + if (umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest) + { + LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); + _logger.Verbose("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, requestUri, DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds); + } + + try + { + DisposeRequestCacheItems(_logger, _requestCache, requestUri); + } + finally + { + umbracoContextReference.Dispose(); + } } } + + /// + /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request + /// + /// + /// + /// + private static void DisposeRequestCacheItems(ILogger logger, IRequestCache requestCache, Uri requestUri) + { + // do not process if client-side request + if (requestUri.IsClientSideRequest()) + return; + + //get a list of keys to dispose + var keys = new HashSet(); + foreach (var i in requestCache) + { + if (i.Value is IDisposeOnRequestEnd || i.Key is IDisposeOnRequestEnd) + { + keys.Add(i.Key); + } + } + //dispose each item and key that was found as disposable. + foreach (var k in keys) + { + try + { + requestCache.Get(k).DisposeIfDisposable(); + } + catch (Exception ex) + { + logger.Error("Could not dispose item with key " + k, ex); + } + try + { + k.DisposeIfDisposable(); + } + catch (Exception ex) + { + logger.Error("Could not dispose item key " + k, ex); + } + } + } + + } } diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs new file mode 100644 index 0000000000..010e6533d9 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs @@ -0,0 +1,18 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Runtime +{ + /// + /// Executes if the boot fails to ensure critical services are registered + /// + [RuntimeLevel(MinLevel = RuntimeLevel.BootFailed)] + public class AspNetCoreBootFailedComposer : IComposer + { + public void Compose(Composition composition) + { + composition.RegisterUnique(); + } + } +} diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index c2a29c63a6..babf6cb80d 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -18,9 +18,13 @@ using Umbraco.Web.Common.Install; using Umbraco.Extensions; using System.Linq; using Umbraco.Web.Common.Controllers; +using System; +using Umbraco.Web.Common.Middleware; +using Umbraco.Web.Common.ModelBinding; namespace Umbraco.Web.Common.Runtime { + /// /// Adds/replaces AspNetCore specific services /// @@ -77,6 +81,11 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 009d970fd5..063bc89269 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; @@ -48,6 +49,7 @@ namespace Umbraco.Web.Editors private readonly IRuntimeState _runtimeState; private readonly ISecuritySettings _securitySettings; private readonly IRequestAccessor _requestAccessor; + private readonly IEmailSender _emailSender; public AuthenticationController( IUserPasswordConfiguration passwordConfiguration, @@ -62,7 +64,8 @@ namespace Umbraco.Web.Editors UmbracoMapper umbracoMapper, ISecuritySettings securitySettings, IPublishedUrlProvider publishedUrlProvider, - IRequestAccessor requestAccessor) + IRequestAccessor requestAccessor, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper, publishedUrlProvider) { _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); @@ -70,6 +73,7 @@ namespace Umbraco.Web.Editors _runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); _securitySettings = securitySettings ?? throw new ArgumentNullException(nameof(securitySettings)); _requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(securitySettings)); + _emailSender = emailSender; } protected BackOfficeUserManager UserManager => _userManager @@ -331,12 +335,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 5984a86b15..9a35990be7 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; @@ -53,6 +54,7 @@ namespace Umbraco.Web.Editors private readonly IImageUrlGenerator _imageUrlGenerator; private readonly ISecuritySettings _securitySettings; private readonly IRequestAccessor _requestAccessor; + private readonly IEmailSender _emailSender; public UsersController( IGlobalSettings globalSettings, @@ -70,7 +72,8 @@ namespace Umbraco.Web.Editors IImageUrlGenerator imageUrlGenerator, IPublishedUrlProvider publishedUrlProvider, ISecuritySettings securitySettings, - IRequestAccessor requestAccessor) + IRequestAccessor requestAccessor, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) { _mediaFileSystem = mediaFileSystem; @@ -80,6 +83,7 @@ namespace Umbraco.Web.Editors _imageUrlGenerator = imageUrlGenerator; _securitySettings = securitySettings; _requestAccessor = requestAccessor; + _emailSender = emailSender; } /// @@ -503,17 +507,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); } /// diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 1df99ad45f..a63e305ce2 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -210,11 +210,9 @@ namespace Umbraco.Web case RuntimeLevel.Install: case RuntimeLevel.Upgrade: - // redirect to install - ReportRuntime(level, "Umbraco must install or upgrade."); - var installPath = _uriUtility.ToAbsolute(Constants.SystemDirectories.Install); - var installUrl = $"{installPath}/?redir=true&url={HttpUtility.UrlEncode(uri.ToString())}"; - httpContext.Response.Redirect(installUrl, true); + + // NOTE: We have moved the logic that was here to netcore already + return false; // cannot serve content default: @@ -222,17 +220,6 @@ namespace Umbraco.Web } } - private static bool _reported; - private static RuntimeLevel _reportedLevel; - - private void ReportRuntime(RuntimeLevel level, string message) - { - if (_reported && _reportedLevel == level) return; - _reported = true; - _reportedLevel = level; - _logger.Warn(message); - } - // ensures Umbraco has at least one published node // if not, rewrites to splash and return false // if yes, return true @@ -309,47 +296,7 @@ namespace Umbraco.Web } - /// - /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request - /// - /// - private void DisposeRequestCacheItems(HttpContext http, IRequestCache requestCache) - { - // do not process if client-side request - if (http.Request.Url.IsClientSideRequest()) - return; - - //get a list of keys to dispose - var keys = new HashSet(); - foreach (var i in requestCache) - { - if (i.Value is IDisposeOnRequestEnd || i.Key is IDisposeOnRequestEnd) - { - keys.Add(i.Key); - } - } - //dispose each item and key that was found as disposable. - foreach (var k in keys) - { - try - { - requestCache.Get(k).DisposeIfDisposable(); - } - catch (Exception ex) - { - _logger.Error("Could not dispose item with key " + k, ex); - } - try - { - k.DisposeIfDisposable(); - } - catch (Exception ex) - { - _logger.Error("Could not dispose item key " + k, ex); - } - } - } - + #endregion #region IHttpModule @@ -361,55 +308,13 @@ namespace Umbraco.Web /// public void Init(HttpApplication app) { - if (_runtime.Level == RuntimeLevel.BootFailed) - { - // there's nothing we can do really - app.BeginRequest += (sender, args) => - { - // would love to avoid throwing, and instead display a customized Umbraco 500 - // page - however if we don't throw here, something else might go wrong, and - // it's this later exception that would be reported. could not figure out how - // to prevent it, either with httpContext.Response.End() or .ApplicationInstance - // .CompleteRequest() - - // also, if something goes wrong with our DI setup, the logging subsystem may - // not even kick in, so here we try to give as much detail as possible - - BootFailedException.Rethrow(Current.RuntimeState.BootFailedException); - }; - return; - } - app.BeginRequest += (sender, e) => { var httpContext = ((HttpApplication) sender).Context; - LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); - - _logger.Verbose("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, httpContext.Request.Url); BeginRequest(new HttpContextWrapper(httpContext)); }; - //disable asp.net headers (security) - // This is the correct place to modify headers according to MS: - // https://our.umbraco.com/forum/umbraco-7/using-umbraco-7/65241-Heap-error-from-header-manipulation?p=0#comment220889 - app.PostReleaseRequestState += (sender, args) => - { - var httpContext = ((HttpApplication) sender).Context; - try - { - httpContext.Response.Headers.Remove("Server"); - //this doesn't normally work since IIS sets it but we'll keep it here anyways. - httpContext.Response.Headers.Remove("X-Powered-By"); - httpContext.Response.Headers.Remove("X-AspNet-Version"); - httpContext.Response.Headers.Remove("X-AspNetMvc-Version"); - } - catch (PlatformNotSupportedException) - { - // can't remove headers this way on IIS6 or cassini. - } - }; - app.PostAuthenticateRequest += (sender, e) => { var httpContext = ((HttpApplication) sender).Context; @@ -427,16 +332,7 @@ namespace Umbraco.Web { var httpContext = ((HttpApplication) sender).Context; - if (Current.UmbracoContext != null && Current.UmbracoContext.IsFrontEndUmbracoRequest) - { - LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); - - _logger.Verbose("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, httpContext.Request.Url, DateTime.Now.Subtract(Current.UmbracoContext.ObjectCreated).TotalMilliseconds); - } - UmbracoModule.OnEndRequest(this, new UmbracoRequestEventArgs(Current.UmbracoContext)); - - DisposeRequestCacheItems(httpContext, _requestCache); }; }