From 9dbe2d211c11fc69702dee3eb244af511cd9dc53 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 26 Jan 2022 10:57:49 +0100 Subject: [PATCH 1/4] Add allowlist for HelpPage --- src/Umbraco.Core/ConfigsExtensions.cs | 3 +++ src/Umbraco.Core/Constants-AppSettings.cs | 5 ++++ src/Umbraco.Core/Help/HelpPageSettings.cs | 12 ++++++++++ src/Umbraco.Core/Help/IHelpPageSettings.cs | 10 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ src/Umbraco.Web.UI/web.Template.config | 1 + src/Umbraco.Web/Editors/HelpController.cs | 28 ++++++++++++++++++++++ 7 files changed, 61 insertions(+) create mode 100644 src/Umbraco.Core/Help/HelpPageSettings.cs create mode 100644 src/Umbraco.Core/Help/IHelpPageSettings.cs diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs index 25c69899c0..01247cc69e 100644 --- a/src/Umbraco.Core/ConfigsExtensions.cs +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dashboards; +using Umbraco.Core.Help; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; @@ -50,6 +51,8 @@ namespace Umbraco.Core factory.GetInstance().Debug)); configs.Add(() => new ContentDashboardSettings()); + + configs.Add(() => new HelpPageSettings()); } } } diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 4e5619813e..6a58675e91 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -125,6 +125,11 @@ namespace Umbraco.Core /// public const string ContentDashboardUrlAllowlist = "Umbraco.Core.ContentDashboardUrl-Allowlist"; + /// + /// A list of allowed addresses to fetch content for the help page. + /// + public const string HelpPageUrlAllowList = "Umbraco.Core.HelpPage-Allowlist"; + /// /// TODO: FILL ME IN /// diff --git a/src/Umbraco.Core/Help/HelpPageSettings.cs b/src/Umbraco.Core/Help/HelpPageSettings.cs new file mode 100644 index 0000000000..d2a4a3a0f5 --- /dev/null +++ b/src/Umbraco.Core/Help/HelpPageSettings.cs @@ -0,0 +1,12 @@ +using System.Configuration; + +namespace Umbraco.Core.Help +{ + public class HelpPageSettings : IHelpPageSettings + { + public string HelpPageUrlAllowList => + ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.HelpPageUrlAllowList) + ? ConfigurationManager.AppSettings[Constants.AppSettings.HelpPageUrlAllowList] + : null; + } +} diff --git a/src/Umbraco.Core/Help/IHelpPageSettings.cs b/src/Umbraco.Core/Help/IHelpPageSettings.cs new file mode 100644 index 0000000000..5643e47a30 --- /dev/null +++ b/src/Umbraco.Core/Help/IHelpPageSettings.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Help +{ + public interface IHelpPageSettings + { + /// + /// Gets the allowed addresses to retrieve data for the help page. + /// + string HelpPageUrlAllowList { get; } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e27c6eeceb..35948ede91 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -137,6 +137,8 @@ + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index e4e3e19bcb..32e624e400 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -39,6 +39,7 @@ + diff --git a/src/Umbraco.Web/Editors/HelpController.cs b/src/Umbraco.Web/Editors/HelpController.cs index 39dbbc435c..77e1675f87 100644 --- a/src/Umbraco.Web/Editors/HelpController.cs +++ b/src/Umbraco.Web/Editors/HelpController.cs @@ -1,16 +1,33 @@ using Newtonsoft.Json; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Runtime.Serialization; using System.Threading.Tasks; +using System.Web.Http; +using Umbraco.Core.Help; +using Umbraco.Core.Logging; namespace Umbraco.Web.Editors { public class HelpController : UmbracoAuthorizedJsonController { + private readonly IHelpPageSettings _helpPageSettings; + + public HelpController(IHelpPageSettings helpPageSettings) + { + _helpPageSettings = helpPageSettings; + } + private static HttpClient _httpClient; public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { + if (IsAllowedUrl(baseUrl) is false) + { + Logger.Error($"The following URL is not listed in the allowlist for HelpPage in web.config: {baseUrl}"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "HelpPage source not permitted")); + } + var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); try @@ -33,6 +50,17 @@ namespace Umbraco.Web.Editors return new List(); } + + private bool IsAllowedUrl(string url) + { + if (string.IsNullOrEmpty(_helpPageSettings.HelpPageUrlAllowList) || + _helpPageSettings.HelpPageUrlAllowList.Contains(url)) + { + return true; + } + + return false; + } } [DataContract(Name = "HelpPage")] From 1b98f8985ec45c20ed23ab7d4bd7c71d5cba0dfd Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 21 Dec 2021 07:23:08 +0100 Subject: [PATCH 2/4] Use current request for emails (#11775) * Use current request for emails * Fix tests --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 17 ++++++++++ .../AuthenticationControllerTests.cs | 4 ++- .../Web/Controllers/UsersControllerTests.cs | 13 ++++--- .../Editors/AuthenticationController.cs | 19 +++++++++-- src/Umbraco.Web/Editors/UsersController.cs | 34 +++++++++++++------ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 52af734f1c..d934e24575 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -102,5 +102,22 @@ namespace Umbraco.Core.Sync return url.TrimEnd(Constants.CharArrays.ForwardSlash); } + + /// + /// Will get the application URL from configuration, if none is specified will fall back to URL from request. + /// + /// + /// + /// + /// + public static Uri GetApplicationUriUncached( + HttpRequestBase request, + IUmbracoSettingsSection umbracoSettingsSection) + { + var settingUrl = umbracoSettingsSection.WebRouting.UmbracoApplicationUrl; + return string.IsNullOrEmpty(settingUrl) + ? new Uri(request.Url, IOHelper.ResolveUrl(SystemDirectories.Umbraco)) + : new Uri(settingUrl); + } } } diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index 3d264663b5..9bd9ee73ed 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -16,6 +16,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -82,7 +83,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 85dd303432..b9289c1392 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -84,7 +85,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -148,7 +150,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -183,7 +186,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } @@ -253,7 +257,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - helper); + helper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 3ecc6b64a4..54612377e0 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -27,6 +27,8 @@ using Umbraco.Web.Composing; using IUser = Umbraco.Core.Models.Membership.IUser; using Umbraco.Web.Editors.Filters; using Microsoft.Owin.Security; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Sync; namespace Umbraco.Web.Editors { @@ -40,12 +42,23 @@ namespace Umbraco.Web.Editors [DisableBrowserCache] public class AuthenticationController : UmbracoApiController { + private readonly IUmbracoSettingsSection _umbracoSettingsSection; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; - public AuthenticationController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + public AuthenticationController( + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ISqlContext sqlContext, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger logger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IUmbracoSettingsSection umbracoSettingsSection) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, Current.Mapper) { + _umbracoSettingsSection = umbracoSettingsSection; } protected BackOfficeUserManager UserManager => _userManager @@ -552,8 +565,8 @@ namespace Umbraco.Web.Editors r = code }); - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = Current.RuntimeState.ApplicationUrl; + // Construct full URL using configured application URL (which will fall back to current request) + var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index dda0dfc933..4bfd72854f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -17,6 +17,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -27,6 +28,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Core.Sync; using Umbraco.Web.Editors.Filters; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -46,9 +48,21 @@ namespace Umbraco.Web.Editors [IsCurrentUserModelFilter] public class UsersController : UmbracoAuthorizedJsonController { - public UsersController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + private readonly IUmbracoSettingsSection _umbracoSettingsSection; + + public UsersController( + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ISqlContext sqlContext, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger logger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IUmbracoSettingsSection umbracoSettingsSection) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { + _umbracoSettingsSection = umbracoSettingsSection; } /// @@ -390,7 +404,7 @@ namespace Umbraco.Web.Editors user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager(); - + if (!EmailSender.CanSendRequiredEmail && !userMgr.HasSendingUserInviteEventHandler) { throw new HttpResponseException( @@ -462,12 +476,12 @@ namespace Umbraco.Web.Editors Email = userSave.Email, Username = userSave.Username }; - } + } } else { //send the email - await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); + await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); } display.AddSuccessNotification(Services.TextService.Localize("speechBubbles", "resendInviteHeader"), Services.TextService.Localize("speechBubbles", "resendInviteSuccess", new[] { user.Name })); @@ -525,9 +539,9 @@ namespace Umbraco.Web.Editors invite = inviteToken }); - // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = RuntimeState.ApplicationUrl; - var inviteUri = new Uri(applicationUri, action); + // Construct full URL will use the value in settings if specified, otherwise will use the current request URL + var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var inviteUri = new Uri(requestUrl, action); var emailSubject = Services.TextService.Localize("user", "inviteEmailCopySubject", //Ensure the culture of the found user is used for the email! @@ -622,7 +636,7 @@ namespace Umbraco.Web.Editors if (Current.Configs.Settings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email) { userSave.Username = userSave.Email; - } + } if (hasErrors) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); @@ -647,13 +661,13 @@ namespace Umbraco.Web.Editors } /// - /// + /// /// /// /// public async Task> PostChangePassword(ChangingPasswordModel changingPasswordModel) { - changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); + changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); if (ModelState.IsValid == false) { From a779803763d45c079579f4216d4f516d6e8e9648 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jan 2022 13:18:21 +0100 Subject: [PATCH 3/4] Bump version to 8.17.2 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index a7cfdaa562..a8a04a0679 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.17.1")] -[assembly: AssemblyInformationalVersion("8.17.1")] +[assembly: AssemblyFileVersion("8.17.2")] +[assembly: AssemblyInformationalVersion("8.17.2")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index afe9ade5f7..6114a84b2a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,9 +348,9 @@ False True - 8171 + 8172 / - http://localhost:8171 + http://localhost:8172 False False @@ -433,4 +433,4 @@ - \ No newline at end of file + From c79380191bfa206d081fcd08ca9008be39a79d6c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 27 Jan 2022 15:57:22 +0100 Subject: [PATCH 4/4] Use Umbraco Path instead of constant --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 21 +++++++++++++++---- .../Editors/AuthenticationController.cs | 2 +- src/Umbraco.Web/Editors/UsersController.cs | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index d934e24575..f3cc5a6db6 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -112,12 +112,25 @@ namespace Umbraco.Core.Sync /// public static Uri GetApplicationUriUncached( HttpRequestBase request, - IUmbracoSettingsSection umbracoSettingsSection) + IUmbracoSettingsSection umbracoSettingsSection, + IGlobalSettings globalSettings) { var settingUrl = umbracoSettingsSection.WebRouting.UmbracoApplicationUrl; - return string.IsNullOrEmpty(settingUrl) - ? new Uri(request.Url, IOHelper.ResolveUrl(SystemDirectories.Umbraco)) - : new Uri(settingUrl); + + + if (string.IsNullOrEmpty(settingUrl)) + { + if (!Uri.TryCreate(request.Url, VirtualPathUtility.ToAbsolute(globalSettings.Path), out var result)) + { + throw new InvalidOperationException( + $"Could not create an url from {request.Url} and {globalSettings.Path}"); + } + return result; + } + else + { + return new Uri(settingUrl); + } } } } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 54612377e0..85889c5869 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -566,7 +566,7 @@ namespace Umbraco.Web.Editors }); // Construct full URL using configured application URL (which will fall back to current request) - var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var applicationUri = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection, GlobalSettings); var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 4bfd72854f..58bb5dcb0a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -540,7 +540,7 @@ namespace Umbraco.Web.Editors }); // Construct full URL will use the value in settings if specified, otherwise will use the current request URL - var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection); + var requestUrl = ApplicationUrlHelper.GetApplicationUriUncached(http.Request, _umbracoSettingsSection, GlobalSettings); var inviteUri = new Uri(requestUrl, action); var emailSubject = Services.TextService.Localize("user", "inviteEmailCopySubject",