From 1f51d667e9837982a92985c46983646d2697ed14 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 18 May 2020 06:43:33 +0200 Subject: [PATCH 01/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated UmbracoWebApiRequireHttpsFilter --- .../UmbracoAuthorizedApiController.cs | 28 +++++++ .../UmbracoWebApiRequireHttpsAttribute.cs | 73 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../UmbracoWebApiRequireHttpsAttribute.cs | 56 -------------- 4 files changed, 101 insertions(+), 57 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs create mode 100644 src/Umbraco.Web.BackOffice/Filters/UmbracoWebApiRequireHttpsAttribute.cs delete mode 100644 src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs new file mode 100644 index 0000000000..071680b325 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs @@ -0,0 +1,28 @@ +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Filters; + +namespace Umbraco.Web.BackOffice.Controllers +{ + /// + /// Provides a base class for authorized auto-routed Umbraco API controllers. + /// + /// + /// This controller will also append a custom header to the response if the user + /// is logged in using forms authentication which indicates the seconds remaining + /// before their timeout expires. + /// + [IsBackOffice] + [UmbracoUserTimeoutFilter] + [UmbracoAuthorize] + [DisableBrowserCache] + [UmbracoWebApiRequireHttps] + [CheckIfUserTicketDataIsStale] + [UnhandedExceptionLoggerConfiguration] + [EnableDetailedErrors] + public abstract class UmbracoAuthorizedApiController : UmbracoApiController + { + + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoWebApiRequireHttpsAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoWebApiRequireHttpsAttribute.cs new file mode 100644 index 0000000000..7c7652c532 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoWebApiRequireHttpsAttribute.cs @@ -0,0 +1,73 @@ +using System; +using System.Net; +using System.Net.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// If Umbraco.Core.UseHttps property in web.config is set to true, this filter will redirect any http access to https. + /// + /// + /// This will only redirect Head/Get requests, otherwise will respond with text + /// + /// References: + /// http://issues.umbraco.org/issue/U4-8542 + /// https://blogs.msdn.microsoft.com/carlosfigueira/2012/03/09/implementing-requirehttps-with-asp-net-web-api/ + /// + public class UmbracoWebApiRequireHttpsAttribute : TypeFilterAttribute + { + public UmbracoWebApiRequireHttpsAttribute() : base(typeof(UmbracoWebApiRequireHttpsFilter)) + { + Arguments = Array.Empty(); + } + } + + public class UmbracoWebApiRequireHttpsFilter: IAuthorizationFilter + { + private readonly IGlobalSettings _globalSettings; + + public UmbracoWebApiRequireHttpsFilter(IGlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + var request = context.HttpContext.Request; + if (_globalSettings.UseHttps && request.Scheme != Uri.UriSchemeHttps) + { + var uri = new UriBuilder() + { + Scheme = Uri.UriSchemeHttps, + Host = request.Host.Value, + Path = request.Path, + Query = request.QueryString.ToUriComponent(), + Port = 443 + }; + var body = string.Format("

The resource can be found at {0}.

", + uri.Uri.AbsoluteUri); + if (request.Method.Equals(HttpMethod.Get.ToString()) || request.Method.Equals(HttpMethod.Head.ToString())) + { + context.HttpContext.Response.Headers.Add("Location", uri.Uri.ToString()); + context.Result = new ObjectResult(body) + { + StatusCode = (int)HttpStatusCode.Found, + }; + + } + else + { + context.Result = new ObjectResult(body) + { + StatusCode = (int)HttpStatusCode.NotFound + }; + } + + + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index fd20d4ef6e..79b62c57fb 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -407,7 +407,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs deleted file mode 100644 index fbe7d5b1b4..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Umbraco.Core; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// If Umbraco.Core.UseHttps property in web.config is set to true, this filter will redirect any http access to https. - /// - /// - /// This will only redirect Head/Get requests, otherwise will respond with text - /// - /// References: - /// http://issues.umbraco.org/issue/U4-8542 - /// https://blogs.msdn.microsoft.com/carlosfigueira/2012/03/09/implementing-requirehttps-with-asp-net-web-api/ - /// - public class UmbracoWebApiRequireHttpsAttribute : AuthorizationFilterAttribute - { - public override void OnAuthorization(HttpActionContext actionContext) - { - var request = actionContext.Request; - if (Current.Configs.Global().UseHttps && request.RequestUri.Scheme != Uri.UriSchemeHttps) - { - HttpResponseMessage response; - var uri = new UriBuilder(request.RequestUri) - { - Scheme = Uri.UriSchemeHttps, - Port = 443 - }; - var body = string.Format("

The resource can be found at {0}.

", - uri.Uri.AbsoluteUri); - if (request.Method.Equals(HttpMethod.Get) || request.Method.Equals(HttpMethod.Head)) - { - response = request.CreateResponse(HttpStatusCode.Found); - response.Headers.Location = uri.Uri; - if (request.Method.Equals(HttpMethod.Get)) - { - response.Content = new StringContent(body, Encoding.UTF8, "text/html"); - } - } - else - { - response = request.CreateResponse(HttpStatusCode.NotFound); - response.Content = new StringContent(body, Encoding.UTF8, "text/html"); - } - - actionContext.Response = response; - } - } - } -} From 52035c043d3f81a30ade547719e3328a4c0c7019 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 18 May 2020 12:40:01 +0200 Subject: [PATCH 02/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated UmbracoWebApiRequireHttpsFilter --- .../Controllers/UmbracoAuthorizedApiController.cs | 8 ++++---- src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs index 071680b325..c3e1a71b86 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs @@ -14,13 +14,13 @@ namespace Umbraco.Web.BackOffice.Controllers /// before their timeout expires. /// [IsBackOffice] - [UmbracoUserTimeoutFilter] + //[UmbracoUserTimeoutFilter] //TODO reintroduce [UmbracoAuthorize] [DisableBrowserCache] [UmbracoWebApiRequireHttps] - [CheckIfUserTicketDataIsStale] - [UnhandedExceptionLoggerConfiguration] - [EnableDetailedErrors] + //[CheckIfUserTicketDataIsStale] //TODO reintroduce + //[UnhandedExceptionLoggerConfiguration] //TODO reintroduce + //[EnableDetailedErrors] //TODO reintroduce public abstract class UmbracoAuthorizedApiController : UmbracoApiController { diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 2691063f19..42e0fe2940 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.WebApi [UmbracoUserTimeoutFilter] [UmbracoAuthorize] [DisableBrowserCache] - [UmbracoWebApiRequireHttps] + // [UmbracoWebApiRequireHttps] [CheckIfUserTicketDataIsStale] [UnhandedExceptionLoggerConfiguration] [EnableDetailedErrors] From 4ca6868553f845a10008076b55e0cf8581dad5b5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 18 May 2020 15:19:52 +0200 Subject: [PATCH 03/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated TourController --- .../Controllers/BackOfficeController.cs | 6 +- .../Controllers/TourController.cs | 216 ++++++++ .../UmbracoAuthorizedJsonController.cs | 21 + .../Filters/UmbracoAuthorizeFilter.cs | 56 +-- ...alidateAngularAntiForgeryTokenAttribute.cs | 115 +++++ .../Security/WebSecurity.cs | 5 +- .../BackOfficeTours/getting-started.json | 475 ++++++++++++++++++ src/Umbraco.Web/Editors/TourController.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 9 files changed, 863 insertions(+), 34 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Controllers/TourController.cs create mode 100644 src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs create mode 100644 src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs create mode 100644 src/Umbraco.Web.UI.NetCore/Config/BackOfficeTours/getting-started.json diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index c5507f00bf..0e244abda2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -27,17 +27,15 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IRuntimeMinifier _runtimeMinifier; private readonly IGlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; - private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILocalizedTextService _textService; private readonly IGridConfig _gridConfig; - public BackOfficeController(IRuntimeMinifier runtimeMinifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IGridConfig gridConfig) + public BackOfficeController(IRuntimeMinifier runtimeMinifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IGridConfig gridConfig) { _runtimeMinifier = runtimeMinifier; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; - _umbracoApplicationLifetime = umbracoApplicationLifetime; _umbracoContextAccessor = umbracoContextAccessor; _textService = textService; _gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig)); @@ -103,7 +101,7 @@ namespace Umbraco.Web.BackOffice.Controllers return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None }; } - //[UmbracoAuthorize(Order = 0)] TODO: Re-implement UmbracoAuthorizeAttribute + [UmbracoAuthorize(Order = 0)] [HttpGet] public JsonNetResult GetGridConfig() { diff --git a/src/Umbraco.Web.BackOffice/Controllers/TourController.cs b/src/Umbraco.Web.BackOffice/Controllers/TourController.cs new file mode 100644 index 0000000000..528a67fdff --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/TourController.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Models; +using Umbraco.Web.Tour; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + public class TourController : UmbracoAuthorizedJsonController + { + private readonly TourFilterCollection _filters; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ITourSettings _tourSettings; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IContentTypeService _contentTypeService; + + public TourController( + TourFilterCollection filters, + IHostingEnvironment hostingEnvironment, + ITourSettings tourSettings, + IUmbracoContextAccessor umbracoContextAccessor, + IContentTypeService contentTypeService) + { + _filters = filters; + _hostingEnvironment = hostingEnvironment; + + _tourSettings = tourSettings; + _umbracoContextAccessor = umbracoContextAccessor; + _contentTypeService = contentTypeService; + } + + public IEnumerable GetTours() + { + var result = new List(); + + if (_tourSettings.EnableTours == false) + return result; + + var user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser; + if (user == null) + return result; + + //get all filters that will be applied to all tour aliases + var aliasOnlyFilters = _filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList(); + + //don't pass in any filters for core tours that have a plugin name assigned + var nonPluginFilters = _filters.Where(x => x.PluginName == null).ToList(); + + //add core tour files + var coreToursPath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.Config), "BackOfficeTours"); + if (Directory.Exists(coreToursPath)) + { + foreach (var tourFile in Directory.EnumerateFiles(coreToursPath, "*.json")) + { + TryParseTourFile(tourFile, result, nonPluginFilters, aliasOnlyFilters); + } + } + + //collect all tour files in packages + var appPlugins = _hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.AppPlugins); + if (Directory.Exists(appPlugins)) + { + foreach (var plugin in Directory.EnumerateDirectories(appPlugins)) + { + var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); + var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)) + .ToList(); + + //If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely + var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null); + if (isPluginFiltered) continue; + + //combine matched package filters with filters not specific to a package + var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList(); + + foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice")) + { + foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours")) + { + foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) + { + TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); + } + } + } + } + } + + //Get all allowed sections for the current user + var allowedSections = user.AllowedSections.ToList(); + + var toursToBeRemoved = new List(); + + //Checking to see if the user has access to the required tour sections, else we remove the tour + foreach (var backOfficeTourFile in result) + { + if (backOfficeTourFile.Tours != null) + { + foreach (var tour in backOfficeTourFile.Tours) + { + if (tour.RequiredSections != null) + { + foreach (var toursRequiredSection in tour.RequiredSections) + { + if (allowedSections.Contains(toursRequiredSection) == false) + { + toursToBeRemoved.Add(backOfficeTourFile); + break; + } + } + } + } + } + } + + return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase); + } + + /// + /// Gets a tours for a specific doctype + /// + /// The documenttype alias + /// A + public IEnumerable GetToursForDoctype(string doctypeAlias) + { + var tourFiles = this.GetTours(); + + var doctypeAliasWithCompositions = new List + { + doctypeAlias + }; + + var contentType = _contentTypeService.Get(doctypeAlias); + + if (contentType != null) + { + doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases()); + } + + return tourFiles.SelectMany(x => x.Tours) + .Where(x => + { + if (string.IsNullOrEmpty(x.ContentType)) + { + return false; + } + var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim()); + return contentTypes.Intersect(doctypeAliasWithCompositions).Any(); + }); + } + + private void TryParseTourFile(string tourFile, + ICollection result, + List filters, + List aliasOnlyFilters, + string pluginName = null) + { + var fileName = Path.GetFileNameWithoutExtension(tourFile); + if (fileName == null) return; + + //get the filters specific to this file + var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList(); + + //If there is any filter applied to match the file only (no tour alias) then ignore the file entirely + var isFileFiltered = fileFilters.Any(x => x.TourAlias == null); + if (isFileFiltered) return; + + //now combine all aliases to filter below + var aliasFilters = aliasOnlyFilters.Concat(filters.Where(x => x.TourAlias != null)) + .Select(x => x.TourAlias) + .ToList(); + + try + { + var contents = System.IO.File.ReadAllText(tourFile); + var tours = JsonConvert.DeserializeObject(contents); + + var backOfficeTours = tours.Where(x => + aliasFilters.Count == 0 || aliasFilters.All(filter => filter.IsMatch(x.Alias)) == false); + + var user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser; + + var localizedTours = backOfficeTours.Where(x => + string.IsNullOrWhiteSpace(x.Culture) || x.Culture.Equals(user.Language, + StringComparison.InvariantCultureIgnoreCase)).ToList(); + + var tour = new BackOfficeTourFile + { + FileName = Path.GetFileNameWithoutExtension(tourFile), + PluginName = pluginName, + Tours = localizedTours + }; + + //don't add if all of the tours are filtered + if (tour.Tours.Any()) + result.Add(tour); + } + catch (IOException e) + { + throw new IOException("Error while trying to read file: " + tourFile, e); + } + catch (JsonReaderException e) + { + throw new JsonReaderException("Error while trying to parse content as tour data: " + tourFile, e); + } + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs new file mode 100644 index 0000000000..70d3155f23 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedJsonController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Filters; + +namespace Umbraco.Web.Editors +{ + /// + /// An abstract API controller that only supports JSON and all requests must contain the correct csrf header + /// + /// + /// Inheriting from this controller means that ALL of your methods are JSON methods that are called by Angular, + /// methods that are not called by Angular or don't contain a valid csrf header will NOT work. + /// + [TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))] + [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions + public abstract class UmbracoAuthorizedJsonController : UmbracoAuthorizedApiController + { + + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs index 5aec94e70a..712ece4773 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs @@ -52,36 +52,36 @@ namespace Umbraco.Web.BackOffice.Filters public UmbracoAuthorizeFilter( IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator) : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, false, null) - { - } - - /// - /// Constructor with redirect umbraco login behavior - /// - /// - /// - /// - /// If true will redirect to the umbraco login page if not authorized - public UmbracoAuthorizeFilter( - IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator, - bool redirectToUmbracoLogin) - : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, redirectToUmbracoLogin, null) - { - } - - /// - /// Constructor with redirect url behavior - /// - /// - /// - /// /// - /// If specified will redirect to this URL if not authorized - public UmbracoAuthorizeFilter( - IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator, - string redirectUrl) - : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, false, redirectUrl) { } + // + // /// + // /// Constructor with redirect umbraco login behavior + // /// + // /// + // /// + // /// + // /// If true will redirect to the umbraco login page if not authorized + // public UmbracoAuthorizeFilter( + // IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator, + // bool redirectToUmbracoLogin) + // : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, redirectToUmbracoLogin, null) + // { + // } + // + // /// + // /// Constructor with redirect url behavior + // /// + // /// + // /// + // /// /// + // /// If specified will redirect to this URL if not authorized + // public UmbracoAuthorizeFilter( + // IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator, + // string redirectUrl) + // : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, false, redirectUrl) + // { + // } public void OnAuthorization(AuthorizationFilterContext context) { diff --git a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs new file mode 100644 index 0000000000..d2e22dc623 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -0,0 +1,115 @@ +using System; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Antiforgery; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// A filter to check for the csrf token based on Angular's standard approach + /// + /// + /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ + /// + /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled + /// + public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute + { + private readonly ILogger _logger; + private readonly IAntiforgery _antiforgery; + private readonly ICookieManager _cookieManager; + + public ValidateAngularAntiForgeryTokenAttribute(ILogger logger, IAntiforgery antiforgery, ICookieManager cookieManager) + { + _logger = logger; + _antiforgery = antiforgery; + _cookieManager = cookieManager; + } + + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (context.Controller is ControllerBase controller && controller.User.Identity is ClaimsIdentity userIdentity) + { + //if there is not CookiePath claim, then exit + if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) + { + await base.OnActionExecutionAsync(context, next); + return; + } + } + var cookieToken = _cookieManager.GetCookieValue(Constants.Web.CsrfValidationCookieName); + var httpContext = context.HttpContext; + + var validateResult = await ValidateHeaders(httpContext, cookieToken); + if (validateResult.Item1 == false) + { + //TODO we should update this behavior, as HTTP2 do not have ReasonPhrase. Could as well be returned in body + // https://github.com/aspnet/HttpAbstractions/issues/395 + var httpResponseFeature = httpContext.Features.Get(); + if (!(httpResponseFeature is null)) + { + httpResponseFeature.ReasonPhrase = validateResult.Item2; + } + + context.Result = new StatusCodeResult((int)HttpStatusCode.ExpectationFailed); + return; + } + + await next(); + } + + private async Task<(bool,string)> ValidateHeaders( + HttpContext httpContext, + string cookieToken) + { + var requestHeaders = httpContext.Request.Headers; + if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false) + { + return (false, "Missing token"); + } + + var headerToken = requestHeaders + .Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) + .Select(z => z.Value) + .SelectMany(z => z) + .FirstOrDefault(); + + // both header and cookie must be there + if (cookieToken == null || headerToken == null) + { + return (false, "Missing token null"); + } + + if (await ValidateTokens(httpContext) == false) + { + return (false, "Invalid token"); + } + + return (true, "Success"); + } + + private async Task ValidateTokens(HttpContext httpContext) + { + // ensure that the cookie matches the header and then ensure it matches the correct value! + try + { + await _antiforgery.ValidateRequestAsync(httpContext); + return true; + } + catch (AntiforgeryValidationException ex) + { + _logger.Error(ex, "Could not validate XSRF token"); + return false; + } + return true; + } + } +} diff --git a/src/Umbraco.Web.Common/Security/WebSecurity.cs b/src/Umbraco.Web.Common/Security/WebSecurity.cs index e87720dc86..a52bc68cec 100644 --- a/src/Umbraco.Web.Common/Security/WebSecurity.cs +++ b/src/Umbraco.Web.Common/Security/WebSecurity.cs @@ -12,7 +12,10 @@ namespace Umbraco.Web.Common.Security public class WebSecurity : IWebSecurity { - public IUser CurrentUser => new User(Current.Configs.Global()); + public IUser CurrentUser => new User(Current.Configs.Global()) + { + + }; public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false) { diff --git a/src/Umbraco.Web.UI.NetCore/Config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI.NetCore/Config/BackOfficeTours/getting-started.json new file mode 100644 index 0000000000..7b3f2a2184 --- /dev/null +++ b/src/Umbraco.Web.UI.NetCore/Config/BackOfficeTours/getting-started.json @@ -0,0 +1,475 @@ +[ + { + "name": "Email Marketing", + "alias": "umbEmailMarketing", + "group": "Email Marketing", + "groupOrder": 10, + "hidden": true, + "requiredSections": [ + "content" + ], + "steps": [ + { + "title": "Do you want to stay updated on everything Umbraco?", + "content": "

Thank you for using Umbraco! Would you like to stay up-to-date with Umbraco product updates, security advisories, community news and special offers? Sign up for our newsletter and never miss out on the latest Umbraco news.

By signing up, you agree that we can use your info according to our privacy policy.

", + "view": "emails", + "type": "promotion" + } + ] + }, + { + "name": "Introduction", + "alias": "umbIntroIntroduction", + "group": "Getting Started", + "groupOrder": 100, + "allowDisable": true, + "requiredSections": [ + "content" + ], + "steps": [ + { + "title": "Welcome to Umbraco - The Friendly CMS", + "content": "

Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible.

In this quick tour we will introduce you to the main areas of Umbraco and show you how to best get started.

If you don't want to take the tour now you can always start it by opening the Help drawer in the top right corner.

", + "type": "intro" + }, + { + "element": "[data-element='sections']", + "elementPreventClick": true, + "title": "Main Menu", + "content": "This is the main menu in Umbraco backoffice. Here you can navigate between the different sections, search for items, see your user profile and open the help drawer.", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='section-content']", + "elementPreventClick": true, + "title": "Sections", + "content": "Each area in Umbraco is called a Section. Right now you are in the Content section, when you want to go to another section simply click on the appropriate name in the main menu and you'll be there in no time.", + "backdropOpacity": 0.6 + }, + { + "element": "#tree", + "elementPreventClick": true, + "title": "The Tree", + "content": "

This is the Tree and it is the main navigation inside a section.

In the Content section the tree is called the Content tree and here you can navigate the content of your website.

" + }, + { + "element": "[data-element='dashboard']", + "elementPreventClick": true, + "title": "Dashboards", + "content": "

A dashboard is the main view you are presented with when entering a section within the backoffice, and can be used to show valuable information to the users of the system.

Notice that some sections have multiple dashboards.

" + }, + { + "element": "[data-element='global-search']", + "title": "Search", + "content": "The search allows you to quickly find whatever you're looking for across sections within Umbraco." + }, + { + "element": "[data-element='global-user']", + "title": "User profile", + "content": "Now click on your user avatar to open the user profile dialog.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element~='overlay-user']", + "elementPreventClick": true, + "title": "User profile", + "content": "

Here you can see details about your user, change your password and log out of Umbraco.

In the User section you will be able to do more advanced user management.

" + }, + { + "element": "[data-element~='overlay-user'] [data-element='button-overlayClose']", + "title": "User profile", + "content": "Let's close the user profile again.", + "event": "click" + }, + { + "element": "[data-element='global-help']", + "title": "Help", + "content": "If you ever find yourself in trouble click here to open the Help drawer.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='drawer']", + "elementPreventClick": true, + "title": "Help", + "content": "

In the help drawer you will find articles and videos related to the section you are using.

This is also where you will find the next tour on how to get started with Umbraco.

", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='drawer'] [data-element='help-tours']", + "title": "Tours", + "content": "To continue your journey on getting started with Umbraco, you can find more tours right here." + } + ] + }, + { + "name": "Create document type", + "alias": "umbIntroCreateDocType", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "settings" + ], + "steps": [ + { + "title": "Create your first Document Type", + "content": "

Step 1 of any site is to create a Document Type.
A Document Type is a template for content. For each type of content you want to create you'll create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.

When you have at least one Document Type in place you can start creating content and this content can then be used in a template.

In this tour you will learn how to set up a basic Document Type with a property to enter a short text.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-settings']", + "title": "Navigate to the Settings sections", + "content": "In the Settings section you can create and manage Document types.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-item-documentTypes']", + "title": "Create Document Type", + "content": "

Hover over the Document Type tree and click the three small dots to open the context menu.

", + "event": "click", + "eventElement": "#tree [data-element='tree-item-documentTypes'] [data-element='tree-item-options']" + }, + { + "element": "#dialog [data-element='action-documentType']", + "title": "Create Document Type", + "content": "

Click Document Type to create a new document type with a template. The template will be automatically created and set as the default template for this Document Type.

You will use the template in a later tour to render content.

", + "event": "click" + }, + { + "element": "[data-element='editor-name-field']", + "title": "Enter a name", + "content": "

Your Document Type needs a name. Enter Home Page in the field and click Next.", + "view": "doctypename" + }, + { + "element": "[data-element='editor-description']", + "title": "Enter a description", + "content": "

A description helps to pick the right document type when creating content.

Write a description for our Home page. It could be:

The home page of the website

" + }, + { + "element": "[data-element='group-add']", + "title": "Add group", + "content": "Group are used to organize properties on content in the Content section. Click Add Group to add a group.", + "event": "click" + }, + { + "element": "[data-element='group-name-field']", + "title": "Name the group", + "content": "

Enter Home in the group name.

You can name a group anything you want and if you have a lot of properties it can be useful to add multiple groups.

", + "view": "tabName" + }, + { + "element": "[data-element='property-add']", + "title": "Add a property", + "content": "

Properties are the different input fields on a content page.

On our Home Page we want to add a welcome text.

Click Add property to open the property dialog.

", + "event": "click" + }, + { + "element": "[data-element='editor-property-settings'] [data-element='property-name']", + "title": "Name the property", + "content": "Enter Welcome Text as the name for the property.", + "view": "propertyname" + }, + { + "element": "[data-element~='editor-property-settings'] [data-element='property-description']", + "title": "Enter a description", + "content": "

A description will help your editor fill in the right content.

Enter a description for the property editor. It could be:

Write a nice introduction text so the visitors feel welcome

" + }, + { + "element": "[data-element~='editor-property-settings'] [data-element='editor-add']", + "title": "Add editor", + "content": "When you add an editor you choose what the input method for this property will be. Click Add editor to open the editor picker dialog.", + "event": "click" + }, + { + "element": "[data-element~='editor-data-type-picker']", + "elementPreventClick": true, + "title": "Editor picker", + "content": "

In the editor picker dialog we can pick one of the many built-in editors.

You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors).

" + }, + { + "element": "[data-element~='editor-data-type-picker'] [data-element='editor-Textarea']", + "title": "Select editor", + "content": "Select the Textarea editor. This will add a textarea to the Welcome Text property.", + "event": "click" + }, + { + "element": "[data-element~='editor-data-type-settings']", + "elementPreventClick": true, + "title": "Editor settings", + "content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed." + }, + { + "element": "[data-element~='editor-data-type-settings'] [data-element='button-submit']", + "title": "Save editor", + "content": "Click Submit to save the changes.", + "event": "click" + }, + { + "element": "[data-element~='editor-property-settings'] [data-element='button-submit']", + "title": "Add property to document type", + "content": "Click Submit to add the property to the document type.", + "event": "click" + }, + { + "element": "[data-element~='sub-view-permissions']", + "title": "Check the document type permissions", + "content": "Click Permissions to view the permissions page.", + "event": "click" + }, + { + "element": "[data-element~='permissions-allow-as-root']", + "title": "Allow this document type to work at the root of your site", + "content": "Toggle the switch Allow as root to allow new content pages based on this document type to be created at the root of your site", + "event": "click" + }, + { + "element": "[data-element='button-save']", + "title": "Save the document type", + "content": "All we need now is to save the document type. Click Save to create and save your new document type.", + "event": "click" + } + ] + }, + { + "name": "Create Content", + "alias": "umbIntroCreateContent", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content" + ], + "steps": [ + { + "title": "Creating your first content node", + "content": "

In this tour you will learn how to create the home page for your website. It will use the Home Page Document type you created in the previous tour.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-content']", + "title": "Navigate to the Content section", + "content": "

In the Content section you can create and manage the content of the website.

The Content section contains the content of your website. Content is displayed as nodes in the content tree.

", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='tree-root']", + "title": "Open context menu", + "content": "

Open the context menu by hovering over the root of the content section.

Now click the three small dots to the right.

", + "event": "click", + "eventElement": "#tree [data-element='tree-root'] [data-element='tree-item-options']" + }, + { + "element": "[data-element='action-create-homePage']", + "title": "Create Home page", + "content": "

The context menu shows you all the actions that are available on a node

Click on Home Page to create a new page of type Home Page.

", + "event": "click" + }, + { + "element": "[data-element='editor-content'] [data-element='editor-name-field']", + "title": "Give your new page a name", + "content": "

Our new page needs a name. Enter Home in the field and click Next.

", + "view": "nodename" + }, + { + "element": "[data-element='editor-content'] [data-element='property-welcomeText']", + "title": "Add a welcome text", + "content": "

Add content to the Welcome Text field.

If you don't have any ideas here is a start:

I am learning Umbraco. High Five I Rock #H5IR
.

" + }, + { + "element": "[data-element='editor-content'] [data-element='button-saveAndPublish']", + "title": "Publish", + "content": "

Now click the Publish button to publish your changes.

", + "event": "click" + } + ] + }, + { + "name": "Render in template", + "alias": "umbIntroRenderInTemplate", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "settings" + ], + "steps": [ + { + "title": "Render your content in a template", + "content": "

Templating in Umbraco builds on the concept of Razor Views from ASP.NET MVC. This tour is a sneak peak on how to write templates in Umbraco.

In this tour you will learn how to render content from the Home Page document type so you can see the content added to our Home content page.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-settings']", + "title": "Navigate to the Settings section", + "content": "

In the Settings section you will find all the templates.

It is of course also possible to edit all your code files in your favorite code editor.

", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-item-templates']", + "title": "Expand the Templates node", + "content": "

To see all our templates click the small triangle to the left of the templates node.

", + "event": "click", + "eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']", + "view": "templatetree" + }, + { + "element": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']", + "title": "Open Home template", + "content": "

Click the Home Page template to open and edit it.

", + "eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page'] a.umb-tree-item__label", + "event": "click" + }, + { + "element": "[data-element='editor-templates'] [data-element='code-editor']", + "title": "Edit template", + "content": "

The template can be edited here or in your favorite code editor.

To render the field from the document type add the following to the template:

<h1>@Model.Name</h1>
<p>@Model.WelcomeText</p>

" + }, + { + "element": "[data-element='editor-templates'] [data-element='button-save']", + "title": "Save the template", + "content": "Click the Save button and your template will be saved.", + "event": "click" + } + ] + }, + { + "name": "View Home page", + "alias": "umbIntroViewHomePage", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content" + ], + "steps": [ + { + "title": "View your Umbraco site", + "content": "

Our three main components for a page are done: Document type, Template, and Content. It is now time to see the result.

In this tour you will learn how to see your published website.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-content']", + "title": "Navigate to the content sections", + "content": "In the Content section you will find the content of our website.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-item-Home']", + "title": "Open the Home page", + "content": "

Click the Home page to open it.

", + "event": "click", + "eventElement": "#tree [data-element='tree-item-Home'] a.umb-tree-item__label" + }, + { + "element": "[data-element='editor-content'] [data-element='sub-view-umbInfo']", + "title": "Info", + "content": "

Under the Info-app you will find the default information about a content item.

", + "event": "click" + }, + { + "element": "[data-element='editor-content'] [data-element='node-info-urls']", + "title": "Open page", + "content": "

Click the Link to document to view your page.

Tip: Click the preview button in the bottom right corner to preview changes without publishing them.

", + "event": "click", + "eventElement": "[data-element='editor-content'] [data-element='node-info-urls'] a[target='_blank']" + } + ] + }, + { + "name": "The Media library", + "alias": "umbIntroMediaSection", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "media" + ], + "steps": [ + { + "title": "How to use the media library", + "content": "

A website would be boring without media content. In Umbraco you can manage all your images, documents, videos etc. in the Media section. Here you can upload and organise your media items and see details about each item.

In this tour you will learn how to upload and organise your Media library in Umbraco. It will also show you how to view details about a specific media item.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-media']", + "title": "Navigate to the Media section", + "content": "The media section is where you manage all your media items.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-root']", + "title": "Create a new folder", + "content": "

First create a folder for your images. Hover over the media root node and click the three small dots on the right side of the item.

", + "event": "click", + "eventElement": "#tree [data-element='tree-root'] [data-element='tree-item-options']" + }, + { + "element": "#dialog [data-element='action-Folder']", + "title": "Create a new folder", + "content": "

Select the Folder option to select the type folder.

", + "event": "click" + }, + { + "element": "[data-element='editor-media'] [data-element='editor-name-field']", + "title": "Enter a name", + "content": "

Enter My Images in the field.

", + "view": "foldername" + }, + { + "element": "[data-element='editor-media'] [data-element='button-save']", + "title": "Save the folder", + "content": "

Click the Save button to create the new folder.

", + "event": "click" + }, + { + "element": "[data-element='editor-media'] [data-element='dropzone']", + "title": "Upload images", + "content": "

In the upload area you can upload your media items.

Click the Click here to choose files button and select a couple of images on your computer and upload them.

", + "view": "uploadimages" + }, + { + "element": "[data-element='editor-media'] [data-element='media-grid-item-0']", + "title": "View media item details", + "content": "Hover over the media item and Click the white bar to view details about the media item.", + "event": "click", + "eventElement": "[data-element='editor-media'] [data-element='media-grid-item-0'] [data-element='media-grid-item-edit']" + }, + { + "element": "[data-element='editor-media'] [data-element='property-umbracoFile']", + "elementPreventClick": true, + "title": "The uploaded image", + "content": "

Here you can see the image you have uploaded.

" + }, + { + "element": "[data-element='editor-media'] [data-element='property-umbracoBytes']", + "title": "Image size", + "content": "

You will also find other details about the image, like the size.

Media items work in much the same way as content. So you can add extra properties to an image by creating or editing the Media types in the Settings section.

" + }, + { + "element": "[data-element='editor-media'] [data-element='sub-view-umbInfo']", + "title": "Info", + "content": "Like the content section you can also find default information about the media item. You will find these under the info app.", + "event": "click" + }, + { + "element": "[data-element='editor-media'] [data-element='node-info-urls']", + "title": "Link to media", + "content": "The path to the media item..." + }, + { + "element": "[data-element='editor-media'] [data-element='node-info-update-date']", + "title": "Last edited", + "content": "...and information about when the media item has been created and edited." + }, + { + "element": "[data-element='editor-container']", + "elementPreventClick": true, + "title": "Using media items", + "content": "You can reference a media item directly in a template by using the path or try adding a Media Picker to a document type property so you can select media items from the content section." + } + ] + } +] diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 93ec4252df..8a09673233 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -21,6 +21,7 @@ using Umbraco.Web.Tour; namespace Umbraco.Web.Editors { + // Migrated to .NET Core [PluginController("UmbracoApi")] public class TourController : UmbracoAuthorizedJsonController { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 083e26a755..4858fafa4c 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -145,6 +145,7 @@ + @@ -264,7 +265,6 @@ - From 35680bba46fb1e7911c04e9c29a8f780a4d1ba1e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 May 2020 09:52:58 +0200 Subject: [PATCH 04/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated DashboardController and BackOfficeAssetsController --- .../Controllers/AuthenticationController.cs | 27 +++++- .../BackOfficeAssetsController.cs | 5 +- .../Controllers/BackOfficeController.cs | 10 ++- .../Controllers}/DashboardController.cs | 87 +++++++++---------- .../Filters/EditorModelEventManager.cs | 74 ++++++++++++++++ .../OutgoingEditorModelEventAttribute.cs | 46 ++++++++++ .../Security/WebSecurity.cs | 33 ++++++- .../UmbracoContext/UmbracoContextFactory.cs | 2 +- .../Editors/BackOfficeController.cs | 61 ------------- .../Editors/BackOfficeServerVariables.cs | 18 ++-- src/Umbraco.Web/Umbraco.Web.csproj | 2 - 11 files changed, 241 insertions(+), 124 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/BackOfficeAssetsController.cs (95%) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/DashboardController.cs (68%) create mode 100644 src/Umbraco.Web.BackOffice/Filters/EditorModelEventManager.cs create mode 100644 src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index cb0cb4cc50..8ed4f8368b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Filters; +using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers @@ -9,8 +12,30 @@ namespace Umbraco.Web.BackOffice.Controllers //[ValidationFilter] // TODO: I don't actually think this is required with our custom Application Model conventions applied [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions [IsBackOffice] // TODO: This could be applied with our Application Model conventions - public class AuthenticationController : ControllerBase + public class AuthenticationController : UmbracoApiController { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; // TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController and it should not be an auto-routed api controller + + public AuthenticationController(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + /// + /// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest) + /// + /// + [HttpGet] + public bool IsAuthenticated() + { + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var attempt = umbracoContext.Security.AuthorizeRequest(); + if (attempt == ValidateRequestAttempt.Success) + { + return true; + } + return false; + } } } diff --git a/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeAssetsController.cs similarity index 95% rename from src/Umbraco.Web/Editors/BackOfficeAssetsController.cs rename to src/Umbraco.Web.BackOffice/Controllers/BackOfficeAssetsController.cs index d7bfc5c1fa..c549679251 100644 --- a/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeAssetsController.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Web.Http; -using Umbraco.Core.Composing; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Web.Mvc; +using Umbraco.Web.Common.Attributes; namespace Umbraco.Web.Editors { diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 0e244abda2..d8c0216341 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -31,7 +31,13 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly ILocalizedTextService _textService; private readonly IGridConfig _gridConfig; - public BackOfficeController(IRuntimeMinifier runtimeMinifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IGridConfig gridConfig) + public BackOfficeController( + IRuntimeMinifier runtimeMinifier, + IGlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment, + IUmbracoContextAccessor umbracoContextAccessor, + ILocalizedTextService textService, + IGridConfig gridConfig) { _runtimeMinifier = runtimeMinifier; _globalSettings = globalSettings; @@ -107,5 +113,7 @@ namespace Umbraco.Web.BackOffice.Controllers { return new JsonNetResult { Data = _gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; } + + } } diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs similarity index 68% rename from src/Umbraco.Web/Editors/DashboardController.cs rename to src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs index 84aad2d0c0..6a8e37a8ec 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs @@ -2,38 +2,39 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; using Newtonsoft.Json.Linq; using System.Threading.Tasks; using System.Net.Http; using System; using System.Linq; -using System.Net; using System.Text; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Cache; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Dashboards; -using Umbraco.Core.Strings; using Umbraco.Web.Services; -using Umbraco.Core.Mapping; -using Umbraco.Web.Routing; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Filters; +using Umbraco.Web.WebApi.Filters; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { //we need to fire up the controller like this to enable loading of remote css directly from this controller [PluginController("UmbracoApi")] [ValidationFilter] - [AngularJsonOnlyConfiguration] + [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions [IsBackOffice] - [WebApi.UmbracoAuthorize] - + [UmbracoAuthorize] public class DashboardController : UmbracoApiController { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly AppCaches _appCaches; + private readonly ILogger _logger; private readonly IDashboardService _dashboardService; private readonly IUmbracoVersion _umbracoVersion; private readonly IShortStringHelper _shortStringHelper; @@ -47,15 +48,16 @@ namespace Umbraco.Web.Editors ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, - IProfilingLogger logger, + ILogger logger, IRuntimeState runtimeState, IDashboardService dashboardService, IUmbracoVersion umbracoVersion, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, - IPublishedUrlProvider publishedUrlProvider) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper,publishedUrlProvider) + IShortStringHelper shortStringHelper) + { + _umbracoContextAccessor = umbracoContextAccessor; + _appCaches = appCaches; + _logger = logger; _dashboardService = dashboardService; _umbracoVersion = umbracoVersion; _shortStringHelper = shortStringHelper; @@ -65,10 +67,10 @@ namespace Umbraco.Web.Editors private static readonly HttpClient HttpClient = new HttpClient(); //we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side - [ValidateAngularAntiForgeryToken] + [TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))] public async Task GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/") { - var user = Security.CurrentUser; + var user = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; var allowedSections = string.Join(",", user.AllowedSections); var language = user.Language; var version = _umbracoVersion.SemanticVersion.ToSemanticString(); @@ -76,7 +78,7 @@ namespace Umbraco.Web.Editors var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version); var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; - var content = AppCaches.RuntimeCache.GetCacheItem(key); + var content = _appCaches.RuntimeCache.GetCacheItem(key); var result = new JObject(); if (content != null) { @@ -92,26 +94,26 @@ namespace Umbraco.Web.Editors content = JObject.Parse(json); result = content; - AppCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } catch (HttpRequestException ex) { - Logger.Error(ex.InnerException ?? ex, "Error getting dashboard content from {Url}", url); + _logger.Error(ex.InnerException ?? ex, "Error getting dashboard content from {Url}", url); //it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings - AppCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); } } return result; } - public async Task GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/") + public async Task GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/") { var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); var key = "umbraco-dynamic-dashboard-css-" + section; - var content = AppCaches.RuntimeCache.GetCacheItem(key); + var content = _appCaches.RuntimeCache.GetCacheItem(key); var result = string.Empty; if (content != null) @@ -130,24 +132,23 @@ namespace Umbraco.Web.Editors result = content; //save server content for 30 mins - AppCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } catch (HttpRequestException ex) { - Logger.Error(ex.InnerException ?? ex, "Error getting dashboard CSS from {Url}", url); + _logger.Error(ex.InnerException ?? ex, "Error getting dashboard CSS from {Url}", url); //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings - AppCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); } } - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(result, Encoding.UTF8, "text/css") - }; + + return Content(result,"text/css", Encoding.UTF8); + } - public async Task GetRemoteXml(string site, string url) + public async Task GetRemoteXml(string site, string url) { // This is used in place of the old feedproxy.config // Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv @@ -168,14 +169,14 @@ namespace Umbraco.Web.Editors break; default: - return new HttpResponseMessage(HttpStatusCode.NotFound); + return NotFound(); } //Make remote call to fetch videos or remote dashboard feed data var key = $"umbraco-XML-feed-{site}-{url.ToCleanString(_shortStringHelper, CleanStringType.UrlSegment)}"; - var content = AppCaches.RuntimeCache.GetCacheItem(key); + var content = _appCaches.RuntimeCache.GetCacheItem(key); var result = string.Empty; if (content != null) @@ -194,30 +195,28 @@ namespace Umbraco.Web.Editors result = content; //save server content for 30 mins - AppCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } catch (HttpRequestException ex) { - Logger.Error(ex.InnerException ?? ex, "Error getting remote dashboard data from {UrlPrefix}{Url}", urlPrefix, url); + _logger.Error(ex.InnerException ?? ex, "Error getting remote dashboard data from {UrlPrefix}{Url}", urlPrefix, url); //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings - AppCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); } } - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(result, Encoding.UTF8, "text/xml") - }; + return Content(result,"text/xml", Encoding.UTF8); } // return IDashboardSlim - we don't need sections nor access rules - [ValidateAngularAntiForgeryToken] - [OutgoingEditorModelEvent] + [TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))] + [TypeFilter(typeof(OutgoingEditorModelEventAttribute))] public IEnumerable> GetDashboard(string section) { - return _dashboardService.GetDashboards(section, Security.CurrentUser).Select(x => new Tab + var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser; + return _dashboardService.GetDashboards(section, currentUser).Select(x => new Tab { Id = x.Id, Alias = x.Alias, diff --git a/src/Umbraco.Web.BackOffice/Filters/EditorModelEventManager.cs b/src/Umbraco.Web.BackOffice/Filters/EditorModelEventManager.cs new file mode 100644 index 0000000000..7255c91f49 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/EditorModelEventManager.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core.Dashboards; +using Umbraco.Core.Events; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + /// + /// Used to emit events for editor models in the back office + /// + public sealed class EditorModelEventManager + { + public static event TypedEventHandler> SendingContentModel; + public static event TypedEventHandler> SendingMediaModel; + public static event TypedEventHandler> SendingMemberModel; + public static event TypedEventHandler> SendingUserModel; + + public static event TypedEventHandler>>> SendingDashboardSlimModel; + + private static void OnSendingDashboardModel(ActionExecutedContext sender, EditorModelEventArgs>> e) + { + var handler = SendingDashboardSlimModel; + handler?.Invoke(sender, e); + } + + private static void OnSendingUserModel(ActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingUserModel; + handler?.Invoke(sender, e); + } + + private static void OnSendingContentModel(ActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingContentModel; + handler?.Invoke(sender, e); + } + + private static void OnSendingMediaModel(ActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMediaModel; + handler?.Invoke(sender, e); + } + + private static void OnSendingMemberModel(ActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMemberModel; + handler?.Invoke(sender, e); + } + + /// + /// Based on the type, emit's a specific event + /// + /// + /// + internal static void EmitEvent(ActionExecutedContext sender, EditorModelEventArgs e) + { + if (e.Model is ContentItemDisplay) + OnSendingContentModel(sender, new EditorModelEventArgs(e)); + + if (e.Model is MediaItemDisplay) + OnSendingMediaModel(sender, new EditorModelEventArgs(e)); + + if (e.Model is MemberDisplay) + OnSendingMemberModel(sender, new EditorModelEventArgs(e)); + + if (e.Model is UserDisplay) + OnSendingUserModel(sender, new EditorModelEventArgs(e)); + + if (e.Model is IEnumerable>) + OnSendingDashboardModel(sender, new EditorModelEventArgs>>(e)); + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs new file mode 100644 index 0000000000..5c9e646ba0 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core; +using Umbraco.Web.Editors; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Used to emit outgoing editor model events + /// + internal sealed class OutgoingEditorModelEventAttribute : ActionFilterAttribute + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public OutgoingEditorModelEventAttribute(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + public override void OnActionExecuted(ActionExecutedContext context) + { + if (context.Result == null) return; + + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var user = umbracoContext.Security.CurrentUser; + if (user == null) return; + + if (context.Result is ObjectResult objectContent) + { + var model = objectContent.Value; + + if (model != null) + { + var args = new EditorModelEventArgs( + model, + umbracoContext); + EditorModelEventManager.EmitEvent(context, args); + objectContent.Value = args.Model; + } + } + + base.OnActionExecuted(context); + } + + } +} diff --git a/src/Umbraco.Web.Common/Security/WebSecurity.cs b/src/Umbraco.Web.Common/Security/WebSecurity.cs index a52bc68cec..59b4826f46 100644 --- a/src/Umbraco.Web.Common/Security/WebSecurity.cs +++ b/src/Umbraco.Web.Common/Security/WebSecurity.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; using System.Text; +using Microsoft.AspNetCore.Http; using Umbraco.Composing; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; using Umbraco.Web.Security; namespace Umbraco.Web.Common.Security @@ -12,10 +16,33 @@ namespace Umbraco.Web.Common.Security public class WebSecurity : IWebSecurity { - public IUser CurrentUser => new User(Current.Configs.Global()) - { + private readonly IUserService _userService; - }; + public WebSecurity(IUserService userService) + { + _userService = userService; + } + + private IUser _currentUser; + + /// + /// Gets the current user. + /// + /// The current user. + public IUser CurrentUser + { + get + { + //only load it once per instance! (but make sure groups are loaded) + if (_currentUser == null) + { + var id = GetUserId(); + _currentUser = id ? _userService.GetUserById(id.Result) : null; + } + + return _currentUser; + } + } public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false) { diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs index ac4b61909c..b2148ea5b3 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web _variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture); } - IWebSecurity webSecurity = new WebSecurity(); + IWebSecurity webSecurity = new WebSecurity(_userService); return new UmbracoContext( _publishedSnapshotService, diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 605690444e..5b3a01052d 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -182,67 +182,6 @@ namespace Umbraco.Web.Editors () => Redirect("/")); } - /// - /// Get the json localized text for a given culture or the culture for the current user - /// - /// - /// - /// Migrated already to .Net Core - [HttpGet] - public JsonNetResult LocalizedText(string culture = null) - { - var cultureInfo = string.IsNullOrWhiteSpace(culture) - //if the user is logged in, get their culture, otherwise default to 'en' - ? Security.IsAuthenticated() - //current culture is set at the very beginning of each request - ? Thread.CurrentThread.CurrentCulture - : CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage) - : CultureInfo.GetCultureInfo(culture); - - var allValues = Services.TextService.GetAllStoredValues(cultureInfo); - var pathedValues = allValues.Select(kv => - { - var slashIndex = kv.Key.IndexOf('/'); - var areaAlias = kv.Key.Substring(0, slashIndex); - var valueAlias = kv.Key.Substring(slashIndex+1); - return new - { - areaAlias, - valueAlias, - value = kv.Value - }; - }); - - Dictionary> nestedDictionary = pathedValues - .GroupBy(pv => pv.areaAlias) - .ToDictionary(pv => pv.Key, pv => - pv.ToDictionary(pve => pve.valueAlias, pve => pve.value)); - - return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None }; - } - - /// - /// Returns the JavaScript main file including all references found in manifests - /// - /// - [MinifyJavaScriptResult(Order = 0)] - [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] - public async Task Application() - { - var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(GlobalSettings, _hostingEnvironment); - - return JavaScript(result); - } - - /// Migrated already to .Net Core - [UmbracoAuthorize(Order = 0)] - [HttpGet] - public JsonNetResult GetGridConfig() - { - return new JsonNetResult { Data = _gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; - } - - /// /// Returns the JavaScript object representing the static server variables javascript object diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index cf63fc2131..c94513684b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -214,10 +214,11 @@ namespace Umbraco.Web.Editors "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0)) }, - { - "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetDashboard(null)) - }, + //TODO Reintroduce + // { + // "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetDashboard(null)) + // }, { "logApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null)) @@ -310,10 +311,11 @@ namespace Umbraco.Web.Editors "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetContextHelpForPage("","","")) }, - { - "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetSupportedLocales()) - }, + //TODO Reintroduce + // { + // "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetSupportedLocales()) + // }, { "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllLanguages()) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4858fafa4c..520b8845b6 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -236,7 +236,6 @@ - @@ -350,7 +349,6 @@ - From bff9477c313ae939998773a12d3ff58ccaa3ba49 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 May 2020 14:02:12 +0200 Subject: [PATCH 05/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated ExamineManagementController --- .../ExamineManagementController.cs | 85 +++++++++++-------- .../Exceptions/HttpResponseException.cs | 18 ++++ .../Extensions/ActionResultExtensions.cs | 20 +++++ .../Extensions/LinkGeneratorExtensions.cs | 2 +- .../Editors/BackOfficeServerVariables.cs | 9 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 6 files changed, 92 insertions(+), 43 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/ExamineManagementController.cs (73%) create mode 100644 src/Umbraco.Web.Common/Extensions/ActionResultExtensions.cs diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs similarity index 73% rename from src/Umbraco.Web/Editors/ExamineManagementController.cs rename to src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index bc8590b437..f61b463346 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -3,15 +3,19 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Web.Http; using Examine; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Examine; +using Umbraco.Extensions; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; using Umbraco.Web.Search; using SearchResult = Umbraco.Web.Models.ContentEditing.SearchResult; @@ -67,10 +71,10 @@ namespace Umbraco.Web.Editors return SearchResults.Empty(); var msg = ValidateSearcher(searcherName, out var searcher); - if (!msg.IsSuccessStatusCode) + if (!msg.IsSuccessStatusCode()) throw new HttpResponseException(msg); - // NativeQuery will work for a single word/phrase too (but depends on the implementation) the lucene one will work. + // NativeQuery will work for a single word/phrase too (but depends on the implementation) the lucene one will work. var results = searcher.CreateQuery().NativeQuery(query).Execute(maxResults: pageSize * (pageIndex + 1)); var pagedResults = results.Skip(pageIndex * pageSize); @@ -99,18 +103,19 @@ namespace Umbraco.Web.Editors /// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we /// have a listener for the index op complete so we'll just check if that key is no longer there in the runtime cache /// - public ExamineIndexModel PostCheckRebuildIndex(string indexName) + public ActionResult PostCheckRebuildIndex(string indexName) { var validate = ValidateIndex(indexName, out var index); - if (!validate.IsSuccessStatusCode) + + if (!validate.IsSuccessStatusCode()) throw new HttpResponseException(validate); validate = ValidatePopulator(index); - if (!validate.IsSuccessStatusCode) + if (!validate.IsSuccessStatusCode()) throw new HttpResponseException(validate); var cacheKey = "temp_indexing_op_" + indexName; - var found = AppCaches.RuntimeCache.Get(cacheKey); + var found = _runtimeCache.Get(cacheKey); //if its still there then it's not done return found != null @@ -124,15 +129,15 @@ namespace Umbraco.Web.Editors /// /// /// - public HttpResponseMessage PostRebuildIndex(string indexName) + public IActionResult PostRebuildIndex(string indexName) { var validate = ValidateIndex(indexName, out var index); - if (!validate.IsSuccessStatusCode) - return validate; + if (!validate.IsSuccessStatusCode()) + throw new HttpResponseException(validate); validate = ValidatePopulator(index); - if (!validate.IsSuccessStatusCode) - return validate; + if (!validate.IsSuccessStatusCode()) + throw new HttpResponseException(validate); _logger.Info("Rebuilding index '{IndexName}'", indexName); @@ -146,7 +151,7 @@ namespace Umbraco.Web.Editors { var cacheKey = "temp_indexing_op_" + index.Name; //put temp val in cache which is used as a rudimentary way to know when the indexing is done - AppCaches.RuntimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); + _runtimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); _indexRebuilder.RebuildIndex(indexName); @@ -154,18 +159,16 @@ namespace Umbraco.Web.Editors //foreach (var populator in _populators.Where(x => x.IsRegistered(indexName))) // populator.Populate(index); - return Request.CreateResponse(HttpStatusCode.OK); + return new OkResult(); } catch (Exception ex) { //ensure it's not listening index.IndexOperationComplete -= Indexer_IndexOperationComplete; - Logger.Error(ex, "An error occurred rebuilding index"); - var response = Request.CreateResponse(HttpStatusCode.Conflict); - response.Content = - new - StringContent($"The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {ex}"); - response.ReasonPhrase = "Could Not Rebuild"; + _logger.Error(ex, "An error occurred rebuilding index"); + var response = new ConflictObjectResult("The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {ex}"); + + SetReasonPhrase(response, "Could Not Rebuild"); return response; } } @@ -197,53 +200,61 @@ namespace Umbraco.Web.Editors return indexerModel; } - private HttpResponseMessage ValidateSearcher(string searcherName, out ISearcher searcher) + private ActionResult ValidateSearcher(string searcherName, out ISearcher searcher) { //try to get the searcher from the indexes if (_examineManager.TryGetIndex(searcherName, out var index)) { searcher = index.GetSearcher(); - return Request.CreateResponse(HttpStatusCode.OK); + return new OkResult(); } //if we didn't find anything try to find it by an explicitly declared searcher if (_examineManager.TryGetSearcher(searcherName, out searcher)) - return Request.CreateResponse(HttpStatusCode.OK); + return new OkResult(); - var response1 = Request.CreateResponse(HttpStatusCode.BadRequest); - response1.Content = new StringContent($"No searcher found with name = {searcherName}"); - response1.ReasonPhrase = "Searcher Not Found"; + var response1 = new BadRequestObjectResult($"No searcher found with name = {searcherName}"); + SetReasonPhrase(response1, "Searcher Not Found"); return response1; } - private HttpResponseMessage ValidatePopulator(IIndex index) + private ActionResult ValidatePopulator(IIndex index) { if (_indexRebuilder.CanRebuild(index)) - return Request.CreateResponse(HttpStatusCode.OK); + return new OkResult(); - var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.Content = new StringContent($"The index {index.Name} cannot be rebuilt because it does not have an associated {typeof(IIndexPopulator)}"); - response.ReasonPhrase = "Index cannot be rebuilt"; + var response = new BadRequestObjectResult($"The index {index.Name} cannot be rebuilt because it does not have an associated {typeof(IIndexPopulator)}"); + SetReasonPhrase(response, "Index cannot be rebuilt"); return response; } - private HttpResponseMessage ValidateIndex(string indexName, out IIndex index) + private ActionResult ValidateIndex(string indexName, out IIndex index) { index = null; if (_examineManager.TryGetIndex(indexName, out index)) { //return Ok! - return Request.CreateResponse(HttpStatusCode.OK); + return new OkResult(); } - var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.Content = new StringContent($"No index found with name = {indexName}"); - response.ReasonPhrase = "Index Not Found"; + var response = new BadRequestObjectResult($"No index found with name = {indexName}"); + SetReasonPhrase(response, "Index Not Found"); return response; } + private void SetReasonPhrase(IActionResult response, string reasonPhrase) + { + //TODO we should update this behavior, as HTTP2 do not have ReasonPhrase. Could as well be returned in body + // https://github.com/aspnet/HttpAbstractions/issues/395 + var httpResponseFeature = HttpContext.Features.Get(); + if (!(httpResponseFeature is null)) + { + httpResponseFeature.ReasonPhrase = reasonPhrase; + } + } + private void Indexer_IndexOperationComplete(object sender, EventArgs e) { var indexer = (IIndex)sender; diff --git a/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs b/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs index d88209fea4..750417fab6 100644 --- a/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs +++ b/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Net; using System.Runtime.Serialization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Umbraco.Web.Common.Exceptions { @@ -14,6 +16,22 @@ namespace Umbraco.Web.Common.Exceptions Value = value; } + public HttpResponseException(ActionResult actionResult) + { + + Status = actionResult switch + { + IStatusCodeActionResult x => (HttpStatusCode)x.StatusCode.GetValueOrDefault((int)HttpStatusCode.InternalServerError), + _ => HttpStatusCode.InternalServerError + }; + + Value = actionResult switch + { + ObjectResult x => x.Value, + _ => null + }; + } + public HttpStatusCode Status { get; set; } public object Value { get; set; } diff --git a/src/Umbraco.Web.Common/Extensions/ActionResultExtensions.cs b/src/Umbraco.Web.Common/Extensions/ActionResultExtensions.cs new file mode 100644 index 0000000000..21bfd6f9ba --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/ActionResultExtensions.cs @@ -0,0 +1,20 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Umbraco.Extensions +{ + public static class ActionResultExtensions + { + public static bool IsSuccessStatusCode(this ActionResult actionResult) + { + var statusCode = actionResult switch + { + IStatusCodeActionResult x => x.StatusCode, + _ => (int?)null + }; + + return statusCode.HasValue && statusCode.Value >= (int)HttpStatusCode.OK && statusCode.Value < (int) HttpStatusCode.Ambiguous; + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs index aa7700eca0..65b81c7e64 100644 --- a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs @@ -28,7 +28,7 @@ namespace Umbraco.Extensions return hostingEnvironment.ApplicationVirtualPath; // this would indicate that the installer is installed without the back office } - return linkGenerator.GetPathByAction("Default", ControllerExtensions.GetControllerName(backOfficeControllerType), new { area = Constants.Web.Mvc.BackOfficeApiArea }); + return linkGenerator.GetPathByAction("Default", ControllerExtensions.GetControllerName(backOfficeControllerType), values: new { area = Constants.Web.Mvc.BackOfficeApiArea }); } /// diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index c94513684b..d639d3a0a8 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -279,10 +279,11 @@ namespace Umbraco.Web.Editors "tagsDataBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetTags("", "", null)) }, - { - "examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetIndexerDetails()) - }, + //TODO reintroduce + // { + // "examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetIndexerDetails()) + // }, { "healthCheckBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllHealthChecks()) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 520b8845b6..614df8b29a 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -256,7 +256,6 @@ - From ea380985dbb3274c50fa6f428c3503bac3d74a49 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:19:43 +0200 Subject: [PATCH 06/18] Moving files using MailKit nuget package (Smtp successor) as SmtpClient has been marked as obsolete by Microsoft --- .../Events/SendEmailEventArgs.cs | 0 .../Users}/EmailSender.cs | 50 ++++++++++++++++--- .../Users}/IEmailSender.cs | 0 3 files changed, 43 insertions(+), 7 deletions(-) rename src/{Umbraco.Core => Umbraco.Infrastructure}/Events/SendEmailEventArgs.cs (100%) rename src/{Umbraco.Core => Umbraco.Infrastructure/Users}/EmailSender.cs (58%) rename src/{Umbraco.Core => Umbraco.Infrastructure/Users}/IEmailSender.cs (100%) diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs similarity index 100% rename from src/Umbraco.Core/Events/SendEmailEventArgs.cs rename to src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs similarity index 58% rename from src/Umbraco.Core/EmailSender.cs rename to src/Umbraco.Infrastructure/Users/EmailSender.cs index 5cfdd765bc..62c45b8f78 100644 --- a/src/Umbraco.Core/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -1,9 +1,12 @@ using System; using System.Net.Mail; using System.Threading.Tasks; -using Umbraco.Core.Composing; +using MailKit.Security; +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,21 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + var appSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; + var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), appSettingsDeliveryMethod, true); + + await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (deliveryMethod == SmtpDeliveryMethod.Network) { - await client.SendMailAsync(message); + await client.SendAsync(ConstructEmailMessage(message)); } else { - client.Send(message); + client.Send(ConstructEmailMessage(message)); } + + await client.DisconnectAsync(true); } } } @@ -83,7 +95,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 +115,29 @@ namespace Umbraco.Core var handler = SendEmail; if (handler != null) handler(null, e); } + + private MimeMessage ConstructEmailMessage(MailMessage mailMessage) + { + var messageToSend = new MimeMessage + { + Subject = mailMessage.Subject + }; + + var fromEmail = mailMessage.From?.Address; + if(string.IsNullOrEmpty(fromEmail)) + fromEmail = _globalSettings.SmtpSettings.From; + + messageToSend.From.Add(new MailboxAddress(fromEmail)); + + foreach (var mailAddress in mailMessage.To) + messageToSend.To.Add(new MailboxAddress(mailAddress.Address)); + + if (mailMessage.IsBodyHtml) + messageToSend.Body = new TextPart(TextFormat.Html) { Text = mailMessage.Body }; + else + messageToSend.Body = new TextPart(TextFormat.Plain) { Text = mailMessage.Body }; + + return messageToSend; + } } } diff --git a/src/Umbraco.Core/IEmailSender.cs b/src/Umbraco.Infrastructure/Users/IEmailSender.cs similarity index 100% rename from src/Umbraco.Core/IEmailSender.cs rename to src/Umbraco.Infrastructure/Users/IEmailSender.cs From e85ccb0dabd0b488292f566afcbb1640980f5cb6 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:20:25 +0200 Subject: [PATCH 07/18] Removing TODOs with the actual implementation --- .../Editors/AuthenticationController.cs | 21 +++++++++++++------ src/Umbraco.Web/Editors/UsersController.cs | 20 +++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index fc34a35566..50e9921154 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; @@ -328,12 +329,20 @@ 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 emailSender = new EmailSender(GlobalSettings, true); + var mailMessage = new MailMessage() + { + Subject = subject, + Body = message, + IsBodyHtml = true + }; + mailMessage.To.Add(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..029612dc90 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; @@ -500,17 +501,16 @@ 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 emailSender = new EmailSender(GlobalSettings, true); + var mailMessage = new MailMessage() + { + Subject = emailSubject, + Body = emailBody, + IsBodyHtml = true + }; + mailMessage.To.Add(to.Email); + await emailSender.SendAsync(mailMessage); } /// From ad31db9f4afa888a88e5617d25f94d43d856336b Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:21:45 +0200 Subject: [PATCH 08/18] Adding DeliveryMethod option which we make use of in EmailSender.cs --- src/Umbraco.Configuration/Legacy/SmtpSettings.cs | 1 + src/Umbraco.Configuration/Models/GlobalSettings.cs | 5 +++-- src/Umbraco.Core/Configuration/ISmtpSettings.cs | 1 + .../Builders/SmtpSettingsBuilder.cs | 10 ++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs index 7e3ff80690..beb6eb9691 100644 --- a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs +++ b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs @@ -8,5 +8,6 @@ namespace Umbraco.Configuration public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public string DeliveryMethod { get; set; } } } diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index 4b30813bd5..646bca0f13 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -72,10 +72,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 +90,7 @@ namespace Umbraco.Configuration.Models public string Host => _configurationSection.GetValue("Host"); public int Port => _configurationSection.GetValue("Port"); public string PickupDirectoryLocation => _configurationSection.GetValue("PickupDirectoryLocation"); + public string DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); } } } diff --git a/src/Umbraco.Core/Configuration/ISmtpSettings.cs b/src/Umbraco.Core/Configuration/ISmtpSettings.cs index c2fb4b2dbe..217e83455b 100644 --- a/src/Umbraco.Core/Configuration/ISmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/ISmtpSettings.cs @@ -6,5 +6,6 @@ namespace Umbraco.Core.Configuration string Host { get; } int Port{ get; } string PickupDirectoryLocation { get; } + string DeliveryMethod { get; } } } diff --git a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs index 344d7bcf87..140af5f723 100644 --- a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs @@ -16,6 +16,7 @@ namespace Umbraco.Tests.Common.Builders private string _host; private int? _port; private string _pickupDirectoryLocation; + private string _deliveryMethod; public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -45,12 +46,19 @@ namespace Umbraco.Tests.Common.Builders return this; } + public SmtpSettingsBuilder WithDeliveryMethod(string 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 ?? null; return new TestSmtpSettings() { @@ -58,6 +66,7 @@ namespace Umbraco.Tests.Common.Builders Host = host, Port = port, PickupDirectoryLocation = pickupDirectoryLocation, + DeliveryMethod = deliveryMethod }; } @@ -67,6 +76,7 @@ namespace Umbraco.Tests.Common.Builders public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public string DeliveryMethod { get; set; } } } } From 16d805e6f5b7750b5acb52fdd43d8380aae020c6 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:24:55 +0200 Subject: [PATCH 09/18] Adding Smtp section in appsettings, registering IEmailSender as an email service and ofc the MailKit nuget in Infrastructure proj --- .../Runtime/CoreInitialComposer.cs | 1 + .../Umbraco.Infrastructure.csproj | 1 + src/Umbraco.Web.UI.NetCore/appsettings.json | 10 ++++++++++ 3 files changed, 12 insertions(+) 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.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" + } } } } From 65fa1efc9c7f3dd4fddc3775810a224e35651659 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 20 May 2020 00:54:28 +0200 Subject: [PATCH 10/18] Changing a var name for better reflection on the origin of its value --- src/Umbraco.Infrastructure/Users/EmailSender.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Users/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs index 62c45b8f78..87f27e5097 100644 --- a/src/Umbraco.Infrastructure/Users/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -70,8 +70,8 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - var appSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; - var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), appSettingsDeliveryMethod, true); + var smtpSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; + var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), smtpSettingsDeliveryMethod, true); await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); From ca399c7e894e9cfe7afc219c18ffa8e0c8422a56 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 08:33:00 +0200 Subject: [PATCH 11/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - removed old tourcontroller --- src/Umbraco.Web/Editors/TourController.cs | 227 ---------------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 2 files changed, 228 deletions(-) delete mode 100644 src/Umbraco.Web/Editors/TourController.cs diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs deleted file mode 100644 index 8a09673233..0000000000 --- a/src/Umbraco.Web/Editors/TourController.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Mapping; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Persistence; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Web.Models; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; -using Umbraco.Web.Tour; - -namespace Umbraco.Web.Editors -{ - // Migrated to .NET Core - [PluginController("UmbracoApi")] - public class TourController : UmbracoAuthorizedJsonController - { - private readonly TourFilterCollection _filters; - private readonly IIOHelper _ioHelper; - private readonly ITourSettings _tourSettings; - - public TourController( - IGlobalSettings globalSettings, - IUmbracoContextAccessor umbracoContextAccessor, - ISqlContext sqlContext, - ServiceContext services, - AppCaches appCaches, - IProfilingLogger logger, - IRuntimeState runtimeState, - IShortStringHelper shortStringHelper, - UmbracoMapper umbracoMapper, - TourFilterCollection filters, - IIOHelper ioHelper, - IPublishedUrlProvider publishedUrlProvider, - ITourSettings tourSettings) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) - { - _filters = filters; - _ioHelper = ioHelper; - _tourSettings = tourSettings; - } - - public IEnumerable GetTours() - { - var result = new List(); - - if (_tourSettings.EnableTours == false) - return result; - - var user = UmbracoContext.Security.CurrentUser; - if (user == null) - return result; - - //get all filters that will be applied to all tour aliases - var aliasOnlyFilters = _filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList(); - - //don't pass in any filters for core tours that have a plugin name assigned - var nonPluginFilters = _filters.Where(x => x.PluginName == null).ToList(); - - //add core tour files - var coreToursPath = Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Config), "BackOfficeTours"); - if (Directory.Exists(coreToursPath)) - { - foreach (var tourFile in Directory.EnumerateFiles(coreToursPath, "*.json")) - { - TryParseTourFile(tourFile, result, nonPluginFilters, aliasOnlyFilters); - } - } - - //collect all tour files in packages - var appPlugins = _ioHelper.MapPath(Core.Constants.SystemDirectories.AppPlugins); - if (Directory.Exists(appPlugins)) - { - foreach (var plugin in Directory.EnumerateDirectories(appPlugins)) - { - var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); - var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)) - .ToList(); - - //If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely - var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null); - if (isPluginFiltered) continue; - - //combine matched package filters with filters not specific to a package - var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList(); - - foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice")) - { - foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours")) - { - foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) - { - TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); - } - } - } - } - } - - //Get all allowed sections for the current user - var allowedSections = user.AllowedSections.ToList(); - - var toursToBeRemoved = new List(); - - //Checking to see if the user has access to the required tour sections, else we remove the tour - foreach (var backOfficeTourFile in result) - { - if (backOfficeTourFile.Tours != null) - { - foreach (var tour in backOfficeTourFile.Tours) - { - if (tour.RequiredSections != null) - { - foreach (var toursRequiredSection in tour.RequiredSections) - { - if (allowedSections.Contains(toursRequiredSection) == false) - { - toursToBeRemoved.Add(backOfficeTourFile); - break; - } - } - } - } - } - } - - return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase); - } - - /// - /// Gets a tours for a specific doctype - /// - /// The documenttype alias - /// A - public IEnumerable GetToursForDoctype(string doctypeAlias) - { - var tourFiles = this.GetTours(); - - var doctypeAliasWithCompositions = new List - { - doctypeAlias - }; - - var contentType = this.Services.ContentTypeService.Get(doctypeAlias); - - if (contentType != null) - { - doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases()); - } - - return tourFiles.SelectMany(x => x.Tours) - .Where(x => - { - if (string.IsNullOrEmpty(x.ContentType)) - { - return false; - } - var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim()); - return contentTypes.Intersect(doctypeAliasWithCompositions).Any(); - }); - } - - private void TryParseTourFile(string tourFile, - ICollection result, - List filters, - List aliasOnlyFilters, - string pluginName = null) - { - var fileName = Path.GetFileNameWithoutExtension(tourFile); - if (fileName == null) return; - - //get the filters specific to this file - var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList(); - - //If there is any filter applied to match the file only (no tour alias) then ignore the file entirely - var isFileFiltered = fileFilters.Any(x => x.TourAlias == null); - if (isFileFiltered) return; - - //now combine all aliases to filter below - var aliasFilters = aliasOnlyFilters.Concat(filters.Where(x => x.TourAlias != null)) - .Select(x => x.TourAlias) - .ToList(); - - try - { - var contents = File.ReadAllText(tourFile); - var tours = JsonConvert.DeserializeObject(contents); - - var backOfficeTours = tours.Where(x => - aliasFilters.Count == 0 || aliasFilters.All(filter => filter.IsMatch(x.Alias)) == false); - - var localizedTours = backOfficeTours.Where(x => - string.IsNullOrWhiteSpace(x.Culture) || x.Culture.Equals(Security.CurrentUser.Language, - StringComparison.InvariantCultureIgnoreCase)).ToList(); - - var tour = new BackOfficeTourFile - { - FileName = Path.GetFileNameWithoutExtension(tourFile), - PluginName = pluginName, - Tours = localizedTours - }; - - //don't add if all of the tours are filtered - if (tour.Tours.Any()) - result.Add(tour); - } - catch (IOException e) - { - throw new IOException("Error while trying to read file: " + tourFile, e); - } - catch (JsonReaderException e) - { - throw new JsonReaderException("Error while trying to parse content as tour data: " + tourFile, e); - } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 614df8b29a..048b9f21bd 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -145,7 +145,6 @@ - From a8f5d21eeb9afcaf88a7c0ee23badd710d15965d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 08:39:27 +0200 Subject: [PATCH 12/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - removed old OutgoingEditorModelEventAttribute and EditorModelEventManager --- .../Editors/BackOfficeServerVariables.cs | 9 ++- src/Umbraco.Web/Editors/ContentController.cs | 14 ++-- .../Editors/EditorModelEventManager.cs | 74 ------------------- src/Umbraco.Web/Editors/MediaController.cs | 10 +-- src/Umbraco.Web/Editors/MemberController.cs | 6 +- src/Umbraco.Web/Editors/UsersController.cs | 4 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 - .../OutgoingEditorModelEventAttribute.cs | 40 ---------- 8 files changed, 22 insertions(+), 137 deletions(-) delete mode 100644 src/Umbraco.Web/Editors/EditorModelEventManager.cs delete mode 100644 src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index d639d3a0a8..3f63ef7516 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -146,10 +146,11 @@ namespace Umbraco.Web.Editors "redirectUrlManagementApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetEnableState()) }, - { - "tourApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetTours()) - }, + //TODO reintroduce + // { + // "tourApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetTours()) + // }, { "embedApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetEmbed("", 0, 0)) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index c1ddb6dba8..0bb9f93b83 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -303,7 +303,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(int id) { @@ -322,7 +322,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(Guid id) { @@ -342,7 +342,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(Udi id) { @@ -360,7 +360,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.Get(contentTypeAlias); @@ -383,7 +383,7 @@ namespace Umbraco.Web.Editors return mapped; } - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public ContentItemDisplay GetEmpty(int blueprintId, int parentId) { var blueprint = Services.ContentService.GetBlueprintById(blueprintId); @@ -608,7 +608,7 @@ namespace Umbraco.Web.Editors /// [FileUploadCleanupFilter] [ContentSaveValidation] - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal( @@ -1629,7 +1629,7 @@ namespace Umbraco.Web.Editors /// The content and variants to unpublish /// [EnsureUserPermissionForContent("model.Id", 'Z')] - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public ContentItemDisplay PostUnpublish(UnpublishContent model) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(model.Id)); diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs deleted file mode 100644 index df4c5e4b36..0000000000 --- a/src/Umbraco.Web/Editors/EditorModelEventManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using System.Web.Http.Filters; -using Umbraco.Core.Dashboards; -using Umbraco.Core.Events; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Editors -{ - /// - /// Used to emit events for editor models in the back office - /// - public sealed class EditorModelEventManager - { - public static event TypedEventHandler> SendingContentModel; - public static event TypedEventHandler> SendingMediaModel; - public static event TypedEventHandler> SendingMemberModel; - public static event TypedEventHandler> SendingUserModel; - - public static event TypedEventHandler>>> SendingDashboardSlimModel; - - private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs>> e) - { - var handler = SendingDashboardSlimModel; - handler?.Invoke(sender, e); - } - - private static void OnSendingUserModel(HttpActionExecutedContext sender, EditorModelEventArgs e) - { - var handler = SendingUserModel; - handler?.Invoke(sender, e); - } - - private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs e) - { - var handler = SendingContentModel; - handler?.Invoke(sender, e); - } - - private static void OnSendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs e) - { - var handler = SendingMediaModel; - handler?.Invoke(sender, e); - } - - private static void OnSendingMemberModel(HttpActionExecutedContext sender, EditorModelEventArgs e) - { - var handler = SendingMemberModel; - handler?.Invoke(sender, e); - } - - /// - /// Based on the type, emit's a specific event - /// - /// - /// - internal static void EmitEvent(HttpActionExecutedContext sender, EditorModelEventArgs e) - { - if (e.Model is ContentItemDisplay) - OnSendingContentModel(sender, new EditorModelEventArgs(e)); - - if (e.Model is MediaItemDisplay) - OnSendingMediaModel(sender, new EditorModelEventArgs(e)); - - if (e.Model is MemberDisplay) - OnSendingMemberModel(sender, new EditorModelEventArgs(e)); - - if (e.Model is UserDisplay) - OnSendingUserModel(sender, new EditorModelEventArgs(e)); - - if (e.Model is IEnumerable>) - OnSendingDashboardModel(sender, new EditorModelEventArgs>>(e)); - } - } -} diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 40f00d54e5..73db8bdef4 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -96,7 +96,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.MediaTypeService.Get(contentTypeAlias); @@ -144,7 +144,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [EnsureUserPermissionForMedia("id")] public MediaItemDisplay GetById(int id) { @@ -164,7 +164,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [EnsureUserPermissionForMedia("id")] public MediaItemDisplay GetById(Guid id) { @@ -184,7 +184,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [EnsureUserPermissionForMedia("id")] public MediaItemDisplay GetById(Udi id) { @@ -475,7 +475,7 @@ namespace Umbraco.Web.Editors /// [FileUploadCleanupFilter] [MediaItemSaveValidation] - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public MediaItemDisplay PostSave( [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 55a3e29c5c..30d34d4bd6 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -135,7 +135,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public MemberDisplay GetByKey(Guid key) { var foundMember = Services.MemberService.GetByKey(key); @@ -151,7 +151,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public MemberDisplay GetEmpty(string contentTypeAlias = null) { IMember emptyContent; @@ -178,7 +178,7 @@ namespace Umbraco.Web.Editors /// /// [FileUploadCleanupFilter] - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [MemberSaveValidation] public async Task PostSave( [ModelBinder(typeof(MemberBinder))] diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index e695139b8c..5984a86b15 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -199,7 +199,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core [AdminUsersAuthorize] public UserDisplay GetById(int id) { @@ -521,7 +521,7 @@ namespace Umbraco.Web.Editors /// /// /// - [OutgoingEditorModelEvent] + // [OutgoingEditorModelEvent] // TODO introduce when moved to .NET Core public async Task PostSaveUser(UserSave userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 048b9f21bd..4072bc9a2c 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -155,7 +155,6 @@ - @@ -399,7 +398,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs deleted file mode 100644 index e2a6f155d0..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Net.Http; -using System.Web.Http.Filters; -using Umbraco.Core; -using Umbraco.Web.Composing; -using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// Used to emit outgoing editor model events - /// - internal sealed class OutgoingEditorModelEventAttribute : ActionFilterAttribute - { - public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) - { - if (actionExecutedContext.Response == null) return; - - var user = Current.UmbracoContext.Security.CurrentUser; - if (user == null) return; - - if (actionExecutedContext.Response.Content is ObjectContent objectContent) - { - var model = objectContent.Value; - - if (model != null) - { - var args = new EditorModelEventArgs( - model, - Current.UmbracoContext); - EditorModelEventManager.EmitEvent(actionExecutedContext, args); - objectContent.Value = args.Model; - } - } - - base.OnActionExecuted(actionExecutedContext); - } - } -} From 1c220b492596c80f4c160db7029579af787f6584 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 08:44:02 +0200 Subject: [PATCH 13/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Removed uncommented code, that was removed due to the container needs only a single valid constructor --- .../Filters/UmbracoAuthorizeFilter.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs index 712ece4773..16d4b7ba33 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoAuthorizeFilter.cs @@ -54,34 +54,6 @@ namespace Umbraco.Web.BackOffice.Filters : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, false, null) { } - // - // /// - // /// Constructor with redirect umbraco login behavior - // /// - // /// - // /// - // /// - // /// If true will redirect to the umbraco login page if not authorized - // public UmbracoAuthorizeFilter( - // IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator, - // bool redirectToUmbracoLogin) - // : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, redirectToUmbracoLogin, null) - // { - // } - // - // /// - // /// Constructor with redirect url behavior - // /// - // /// - // /// - // /// /// - // /// If specified will redirect to this URL if not authorized - // public UmbracoAuthorizeFilter( - // IHostingEnvironment hostingEnvironment, IUmbracoContextAccessor umbracoContext, IRuntimeState runtimeState, LinkGenerator linkGenerator, - // string redirectUrl) - // : this(hostingEnvironment, umbracoContext, runtimeState, linkGenerator, false, redirectUrl) - // { - // } public void OnAuthorization(AuthorizationFilterContext context) { From 2b4dfbca9d44b7415bae5d6582bec35fde37a3b5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 11:42:23 +0200 Subject: [PATCH 14/18] #8142 - Small cleanup, and bugfix --- .../Legacy/SmtpSettings.cs | 7 +++- .../Models/GlobalSettings.cs | 7 +++- .../Configuration/ISmtpSettings.cs | 6 +++- .../Events/SendEmailEventArgs.cs | 0 .../Users => Umbraco.Core}/IEmailSender.cs | 0 .../Users/EmailSender.cs | 35 +++++++------------ .../Builders/SmtpSettingsBuilder.cs | 34 ++++++++++++++---- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 1 + .../AuthenticationControllerTests.cs | 3 +- .../Web/Controllers/UsersControllerTests.cs | 15 +++++--- .../Editors/AuthenticationController.cs | 12 ++++--- src/Umbraco.Web/Editors/UsersController.cs | 12 ++++--- 12 files changed, 85 insertions(+), 47 deletions(-) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Events/SendEmailEventArgs.cs (100%) rename src/{Umbraco.Infrastructure/Users => Umbraco.Core}/IEmailSender.cs (100%) diff --git a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs index beb6eb9691..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,6 +9,10 @@ namespace Umbraco.Configuration public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } - public string DeliveryMethod { 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 646bca0f13..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; @@ -90,7 +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 string DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); + 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 217e83455b..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,6 +8,8 @@ namespace Umbraco.Core.Configuration string Host { get; } int Port{ get; } string PickupDirectoryLocation { get; } - string DeliveryMethod { get; } + SmtpDeliveryMethod DeliveryMethod { get; } + string Username { get; } + string Password { get; } } } diff --git a/src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs similarity index 100% rename from src/Umbraco.Infrastructure/Events/SendEmailEventArgs.cs rename to src/Umbraco.Core/Events/SendEmailEventArgs.cs diff --git a/src/Umbraco.Infrastructure/Users/IEmailSender.cs b/src/Umbraco.Core/IEmailSender.cs similarity index 100% rename from src/Umbraco.Infrastructure/Users/IEmailSender.cs rename to src/Umbraco.Core/IEmailSender.cs diff --git a/src/Umbraco.Infrastructure/Users/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs index 87f27e5097..315a1748c3 100644 --- a/src/Umbraco.Infrastructure/Users/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -1,7 +1,7 @@ using System; +using System.Linq; using System.Net.Mail; using System.Threading.Tasks; -using MailKit.Security; using MimeKit; using MimeKit.Text; using Umbraco.Core.Configuration; @@ -70,20 +70,18 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - var smtpSettingsDeliveryMethod = _globalSettings.SmtpSettings.DeliveryMethod; - var deliveryMethod = (SmtpDeliveryMethod)Enum.Parse(typeof(SmtpDeliveryMethod), smtpSettingsDeliveryMethod, true); - await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); - if (deliveryMethod == SmtpDeliveryMethod.Network) + var mailMessage = ConstructEmailMessage(message); + if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) { - await client.SendAsync(ConstructEmailMessage(message)); + await client.SendAsync(mailMessage); } else { - client.Send(ConstructEmailMessage(message)); + client.Send(mailMessage); } - + await client.DisconnectAsync(true); } } @@ -118,24 +116,17 @@ namespace Umbraco.Core private MimeMessage ConstructEmailMessage(MailMessage mailMessage) { - var messageToSend = new MimeMessage - { - Subject = mailMessage.Subject - }; - var fromEmail = mailMessage.From?.Address; if(string.IsNullOrEmpty(fromEmail)) fromEmail = _globalSettings.SmtpSettings.From; - - messageToSend.From.Add(new MailboxAddress(fromEmail)); - foreach (var mailAddress in mailMessage.To) - messageToSend.To.Add(new MailboxAddress(mailAddress.Address)); - - if (mailMessage.IsBodyHtml) - messageToSend.Body = new TextPart(TextFormat.Html) { Text = mailMessage.Body }; - else - messageToSend.Body = new TextPart(TextFormat.Plain) { Text = mailMessage.Body }; + 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 140af5f723..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,7 +18,9 @@ namespace Umbraco.Tests.Common.Builders private string _host; private int? _port; private string _pickupDirectoryLocation; - private string _deliveryMethod; + private SmtpDeliveryMethod? _deliveryMethod; + private string _username; + private string _password; public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -34,19 +38,31 @@ 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(string deliveryMethod) + public SmtpSettingsBuilder WithDeliveryMethod(SmtpDeliveryMethod deliveryMethod) { _deliveryMethod = deliveryMethod; return this; @@ -58,7 +74,9 @@ namespace Umbraco.Tests.Common.Builders var host = _host ?? null; var port = _port ?? 25; var pickupDirectoryLocation = _pickupDirectoryLocation ?? null; - var deliveryMethod = _deliveryMethod ?? null; + var deliveryMethod = _deliveryMethod ?? SmtpDeliveryMethod.Network; + var username = _username ?? null; + var password = _password ?? null; return new TestSmtpSettings() { @@ -66,7 +84,9 @@ namespace Umbraco.Tests.Common.Builders Host = host, Port = port, PickupDirectoryLocation = pickupDirectoryLocation, - DeliveryMethod = deliveryMethod + DeliveryMethod = deliveryMethod, + Username = username, + Password = password, }; } @@ -76,7 +96,9 @@ namespace Umbraco.Tests.Common.Builders public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } - public string DeliveryMethod { 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/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 50e9921154..01e13ff051 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -48,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, @@ -61,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 @@ -333,16 +336,15 @@ namespace Umbraco.Web.Editors // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)); - var emailSender = new EmailSender(GlobalSettings, true); var mailMessage = new MailMessage() { Subject = subject, Body = message, - IsBodyHtml = true + IsBodyHtml = true, + To = { user.Email} }; - mailMessage.To.Add(user.Email); - await emailSender.SendAsync(mailMessage); + 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 029612dc90..3c71245a0a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -53,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, @@ -69,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; @@ -78,6 +80,7 @@ namespace Umbraco.Web.Editors _sqlContext = sqlContext; _imageUrlGenerator = imageUrlGenerator; _securitySettings = securitySettings; + _emailSender = emailSender; } /// @@ -501,16 +504,15 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); - var emailSender = new EmailSender(GlobalSettings, true); var mailMessage = new MailMessage() { Subject = emailSubject, Body = emailBody, - IsBodyHtml = true + IsBodyHtml = true, + To = { to.Email} }; - mailMessage.To.Add(to.Email); - await emailSender.SendAsync(mailMessage); + await _emailSender.SendAsync(mailMessage); } /// From 490ae37b28571996da37cdf0f932318d317a9c21 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 12:38:21 +0200 Subject: [PATCH 15/18] #8142 - Added smtp authentication if username or password provided --- src/Umbraco.Infrastructure/Users/EmailSender.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Umbraco.Infrastructure/Users/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs index 315a1748c3..9a2f425021 100644 --- a/src/Umbraco.Infrastructure/Users/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -48,7 +48,15 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { + 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); } @@ -72,6 +80,12 @@ namespace Umbraco.Core { await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) + { + await client.AuthenticateAsync(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + var mailMessage = ConstructEmailMessage(message); if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) { From a94af8db0342fcfae0ad414f1b543a0394095176 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 13:35:55 +0200 Subject: [PATCH 16/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated HelpController --- .../Controllers}/HelpController.cs | 13 +++++++++++-- .../Editors/BackOfficeServerVariables.cs | 9 +++++---- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 16 insertions(+), 7 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/HelpController.cs (81%) diff --git a/src/Umbraco.Web/Editors/HelpController.cs b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs similarity index 81% rename from src/Umbraco.Web/Editors/HelpController.cs rename to src/Umbraco.Web.BackOffice/Controllers/HelpController.cs index 39dbbc435c..fe055b5270 100644 --- a/src/Umbraco.Web/Editors/HelpController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs @@ -3,11 +3,20 @@ using System.Collections.Generic; using System.Net.Http; using System.Runtime.Serialization; using System.Threading.Tasks; +using Umbraco.Core.Logging; +using Umbraco.Web.Editors; -namespace Umbraco.Web.Editors +namespace Umbraco.Web.BackOffice.Controllers { public class HelpController : UmbracoAuthorizedJsonController { + private readonly ILogger _logger; + + public HelpController(ILogger logger) + { + _logger = logger; + } + private static HttpClient _httpClient; public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { @@ -28,7 +37,7 @@ namespace Umbraco.Web.Editors } catch (HttpRequestException rex) { - Logger.Info(GetType(), $"Check your network connection, exception: {rex.Message}"); + _logger.Info(GetType(), $"Check your network connection, exception: {rex.Message}"); } return new List(); diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 3f63ef7516..edf50ba36b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -309,10 +309,11 @@ namespace Umbraco.Web.Editors "publishedSnapshotCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetStatus()) }, - { - "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetContextHelpForPage("","","")) - }, + //TODO Reintroduce + // { + // "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetContextHelpForPage("","","")) + // }, //TODO Reintroduce // { // "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4072bc9a2c..99a165e6fd 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -255,7 +255,6 @@ - From 0d65fccd52bbb241b3e84571a17fe14f6eca4a78 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 13:51:21 +0200 Subject: [PATCH 17/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated ImagesController --- .../Controllers}/ImagesController.cs | 18 +++++++++--------- .../Editors/BackOfficeServerVariables.cs | 9 +++++---- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 14 insertions(+), 14 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/ImagesController.cs (91%) diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs similarity index 91% rename from src/Umbraco.Web/Editors/ImagesController.cs rename to src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index bbad627c3b..96eb120b25 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -2,10 +2,13 @@ using System.IO; using System.Net; using System.Net.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Media; using Umbraco.Core.Models; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -36,10 +39,10 @@ namespace Umbraco.Web.Editors /// /// If there is no original image is found then this will return not found. /// - public HttpResponseMessage GetBigThumbnail(string originalImagePath) + public IActionResult GetBigThumbnail(string originalImagePath) { return string.IsNullOrWhiteSpace(originalImagePath) - ? Request.CreateResponse(HttpStatusCode.OK) + ? Ok() : GetResized(originalImagePath, 500); } @@ -52,22 +55,20 @@ namespace Umbraco.Web.Editors /// /// If there is no media, image property or image file is found then this will return not found. /// - public HttpResponseMessage GetResized(string imagePath, int width) + public IActionResult GetResized(string imagePath, int width) { var ext = Path.GetExtension(imagePath); // we need to check if it is an image by extension if (_contentSettings.IsImageFile(ext) == false) - return Request.CreateResponse(HttpStatusCode.NotFound); + return NotFound(); //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file - var response = Request.CreateResponse(HttpStatusCode.Found); - DateTimeOffset? imageLastModified = null; try { imageLastModified = _mediaFileSystem.GetLastModified(imagePath); - + } catch (Exception) { @@ -80,8 +81,7 @@ namespace Umbraco.Web.Editors var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd }); - response.Headers.Location = new Uri(imageUrl, UriKind.RelativeOrAbsolute); - return response; + return new RedirectResult(imageUrl, false); } /// diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index edf50ba36b..33d4166112 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -171,10 +171,11 @@ namespace Umbraco.Web.Editors "mediaApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetRootMedia()) }, - { - "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetBigThumbnail("")) - }, + //TODO reintroduce + // { + // "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetBigThumbnail("")) + // }, { "sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetSections()) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 99a165e6fd..434d5eda88 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -346,7 +346,6 @@ - From 28c4f8723651c04e36477ce1cecad8cee44f58d0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 20 May 2020 17:39:07 +0200 Subject: [PATCH 18/18] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6586 - Migrated ImageUrlGeneratorController + Made usage some strings into the enums in the ImageUrlGenerationOptions --- .../Models/ImageUrlGenerationOptions.cs | 8 +++++--- .../Media/ImageSharpImageUrlGenerator.cs | 5 +++-- .../Models/UserExtensions.cs | 11 ++++++----- .../PropertyEditors/RichTextEditorPastedImages.cs | 3 ++- .../Models/ImageProcessorImageUrlGeneratorTest.cs | 15 ++++++++------- .../Controllers}/ImageUrlGeneratorController.cs | 11 ++++++++++- .../Controllers/ImagesController.cs | 5 +++-- .../Editors/BackOfficeServerVariables.cs | 9 +++++---- .../ImageCropperTemplateCoreExtensions.cs | 4 ++-- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 10 files changed, 44 insertions(+), 28 deletions(-) rename src/{Umbraco.Web/Editors => Umbraco.Web.BackOffice/Controllers}/ImageUrlGeneratorController.cs (75%) diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index f87657c33d..369ee9b25b 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Models +using Umbraco.Web.Models; + +namespace Umbraco.Core.Models { /// /// These are options that are passed to the IImageUrlGenerator implementation to determine @@ -17,8 +19,8 @@ public decimal? WidthRatio { get; set; } public decimal? HeightRatio { get; set; } public int? Quality { get; set; } - public string ImageCropMode { get; set; } - public string ImageCropAnchor { get; set; } + public ImageCropMode? ImageCropMode { get; set; } + public ImageCropAnchor? ImageCropAnchor { get; set; } public bool DefaultCrop { get; set; } public FocalPointPosition FocalPoint { get; set; } public CropCoordinates Crop { get; set; } diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 9058123eb3..48ff16f85b 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -3,6 +3,7 @@ using System.Text; using Umbraco.Core; using Umbraco.Core.Media; using Umbraco.Core.Models; +using Umbraco.Web.Models; namespace Umbraco.Infrastructure.Media { @@ -19,9 +20,9 @@ namespace Umbraco.Infrastructure.Media else if (options.DefaultCrop) imageProcessorUrl.Append("?anchor=center&mode=crop"); else { - imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? "crop").ToLower()); + imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLower()); - if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToLower()); + if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLower()); } var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); diff --git a/src/Umbraco.Infrastructure/Models/UserExtensions.cs b/src/Umbraco.Infrastructure/Models/UserExtensions.cs index 4fefc89d11..668d89a04d 100644 --- a/src/Umbraco.Infrastructure/Models/UserExtensions.cs +++ b/src/Umbraco.Infrastructure/Models/UserExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core.Security; using Umbraco.Core.Media; +using Umbraco.Web.Models; namespace Umbraco.Core.Models { @@ -78,11 +79,11 @@ namespace Umbraco.Core.Models var avatarUrl = mediaFileSystem.GetUrl(user.Avatar); return new[] { - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 30, Height = 30 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 60, Height = 60 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 90, Height = 90 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 150, Height = 150 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 300, Height = 300 }) + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 30, Height = 30 }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 60, Height = 60 }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 90, Height = 90 }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 150, Height = 150 }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 300, Height = 300 }) }; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs index 38b4611c1e..58a280e5db 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextEditorPastedImages.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Web.Templates; @@ -120,7 +121,7 @@ namespace Umbraco.Web.PropertyEditors if (width != int.MinValue && height != int.MinValue) { - location = imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(location) { ImageCropMode = "max", Width = width, Height = height }); + location = imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(location) { ImageCropMode = ImageCropMode.Max, Width = width, Height = height }); } img.SetAttributeValue("src", location); diff --git a/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs index 62c82caf66..a5b059cd23 100644 --- a/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs +++ b/src/Umbraco.Tests/Models/ImageProcessorImageUrlGeneratorTest.cs @@ -20,6 +20,7 @@ using Umbraco.Web; using Umbraco.Web.PropertyEditors; using System.Text; using Umbraco.Infrastructure.Media; +using Umbraco.Web.Models; namespace Umbraco.Tests.Models { @@ -126,11 +127,11 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_SpecifiedCropModeTest() { - var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Min", Width = 300, Height = 150 }); - var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "BoxPad", Width = 300, Height = 150 }); - var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Pad", Width = 300, Height = 150 }); - var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Max", Width = 300, Height = 150 }); - var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Stretch", Width = 300, Height = 150 }); + var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Min, Width = 300, Height = 150 }); + var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.BoxPad, Width = 300, Height = 150 }); + var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 300, Height = 150 }); + var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Max, Width = 300, Height = 150 }); + var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Stretch, Width = 300, Height = 150 }); Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); @@ -145,7 +146,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_UploadTypeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Crop", ImageCropAnchor = "Center", Width = 100, Height = 270 }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Crop, ImageCropAnchor = ImageCropAnchor.Center, Width = 100, Height = 270 }); Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); } @@ -225,7 +226,7 @@ namespace Umbraco.Tests.Models [Test] public void GetCropUrl_BackgroundColorParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = "Pad", Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); + var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); } } diff --git a/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs similarity index 75% rename from src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs rename to src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs index e5369fb7a1..bf9b14e86a 100644 --- a/src/Umbraco.Web/Editors/ImageUrlGeneratorController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Umbraco.Core.Media; +using Umbraco.Core.Models; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Models; using Umbraco.Web.Mvc; @@ -20,6 +22,7 @@ namespace Umbraco.Web.Editors /// building to generate correct URLs /// /// + [PluginController("UmbracoApi")] public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController { private readonly IImageUrlGenerator _imageUrlGenerator; @@ -31,7 +34,13 @@ namespace Umbraco.Web.Editors public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null, string animationProcessMode = null) { - return mediaPath.GetCropUrl(_imageUrlGenerator, null, width: width, height: height, imageCropMode: imageCropMode, animationProcessMode: animationProcessMode); + return _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(mediaPath) + { + Width = width, + Height = height, + ImageCropMode = imageCropMode, + AnimationProcessMode = animationProcessMode + }); } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 96eb120b25..90b52b845f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -79,7 +80,7 @@ namespace Umbraco.Web.Editors } var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; - var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd }); + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = ImageCropMode.Max, CacheBusterValue = rnd }); return new RedirectResult(imageUrl, false); } @@ -105,7 +106,7 @@ namespace Umbraco.Web.Editors int? focalPointLeft = null, int? focalPointTop = null, string animationProcessMode = "first", - string mode = "max", + ImageCropMode mode = ImageCropMode.Max, bool upscale = false, string cacheBusterValue = "") { diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 33d4166112..6441f6ad6a 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -340,10 +340,11 @@ namespace Umbraco.Web.Editors "tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.UploadImage()) }, - { - "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetCropUrl(null, null, null, null, null)) - }, + //TODO Reintroduce + // { + // "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + // controller => controller.GetCropUrl(null, null, null, null, null)) + // }, } }, { diff --git a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs index 68bbb26784..0b7e065c59 100644 --- a/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateCoreExtensions.cs @@ -346,8 +346,8 @@ namespace Umbraco.Web { options = new ImageUrlGenerationOptions (imageUrl) { - ImageCropMode = (imageCropMode ?? ImageCropMode.Pad).ToString().ToLowerInvariant(), - ImageCropAnchor = imageCropAnchor?.ToString().ToLowerInvariant() + ImageCropMode = (imageCropMode ?? ImageCropMode.Pad), + ImageCropAnchor = imageCropAnchor }; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 434d5eda88..2db3dcd909 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -158,7 +158,6 @@ -