From 1f8d7f18be71f6ff969feb48a3ba7baa1cb0d410 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 18 Sep 2020 11:30:26 +0200 Subject: [PATCH] Added configuration validation for content and request handler settings. --- .../Configuration/Models/ContentErrorPage.cs | 25 ++++++++--- .../Models/ContentImagingSettings.cs | 4 ++ .../Configuration/Models/ContentSettings.cs | 15 +++++-- .../Validation/ConfigurationValidationBase.cs | 27 ++++++++++++ .../Validation/ContentSettingsValidation.cs | 43 +++++++++++++++++++ .../RequestHandlerSettingsValidation.cs | 23 ++++++++++ .../Routing/NotFoundHandlerHelper.cs | 2 +- .../Configurations/GlobalSettingsTests.cs | 3 +- .../UmbracoCoreServiceCollectionExtensions.cs | 5 ++- .../Macros/MacroRenderer.cs | 2 +- src/Umbraco.Web/Macros/MacroRenderer.cs | 2 +- 11 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidationBase.cs create mode 100644 src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidation.cs create mode 100644 src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidation.cs diff --git a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs index 8971dda5cc..ec4869d3b4 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs @@ -4,12 +4,25 @@ namespace Umbraco.Core.Configuration.Models { public class ContentErrorPage { - //TODO introduce validation, to check only one of key/id/xPath is used. - public int ContentId { get; } - public Guid ContentKey { get; } - public string ContentXPath { get; } - public bool HasContentId { get; } - public bool HasContentKey { get; } + public int ContentId { get; set; } + + public Guid ContentKey { get; set; } + + public string ContentXPath { get; set; } + + public bool HasContentId => ContentId != 0; + + public bool HasContentKey => ContentKey != Guid.Empty; + + public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); + public string Culture { get; set; } + + public bool IsValid() + { + // Entry is valid if Culture and one and only one of ContentId, ContentKey or ContentXPath is provided. + return !string.IsNullOrWhiteSpace(Culture) && + ((HasContentId ? 1 : 0) + (HasContentKey ? 1 : 0) + (HasContentXPath ? 1 : 0) == 1); + } } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs index 018936896c..681dcbbe88 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs @@ -24,9 +24,13 @@ namespace Umbraco.Core.Configuration.Models private class ImagingAutoFillUploadField : IImagingAutoFillUploadField { public string Alias { get; set; } + public string WidthFieldAlias { get; set; } + public string HeightFieldAlias { get; set; } + public string LengthFieldAlias { get; set; } + public string ExtensionFieldAlias { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 5158a5c746..00b9ef2181 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.Models @@ -16,11 +15,21 @@ namespace Umbraco.Core.Configuration.Models public bool ResolveUrlsFromTextString { get; set; } = false; - public IEnumerable Error404Collection { get; set; } = Array.Empty(); + public ContentErrorPage[] Error404Collection { get; set; } = Array.Empty(); public string PreviewBadge { get; set; } = DefaultPreviewBadge; - public MacroErrorBehaviour MacroErrors { get; set; } = MacroErrorBehaviour.Inline; + public string MacroErrors { get; set; } = MacroErrorBehaviour.Inline.ToString(); + + public MacroErrorBehaviour MacroErrorsBehaviour + { + get + { + return Enum.TryParse(MacroErrors, true, out var value) + ? value + : MacroErrorBehaviour.Inline; + } + } public IEnumerable DisallowedUploadFiles { get; set; } = new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" }; diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidationBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidationBase.cs new file mode 100644 index 0000000000..d311b5157c --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidationBase.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Configuration.Models.Validation +{ + public abstract class ConfigurationValidationBase + { + public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, out string message) + { + if (!validValues.InvariantContains(value)) + { + message = $"Configuration entry {configPath} contains an invalid value '{value}', it should be one of the following: '{string.Join(", ", validValues)}'."; + return false; + } + + message = string.Empty; + return true; + } + + public bool ValidateStringIsOneOfEnumValues(string configPath, string value, Type enumType, out string message) + { + var validValues = Enum.GetValues(enumType).OfType().Select(x => x.ToString().ToFirstLowerInvariant()); + return ValidateStringIsOneOfValidValues(configPath, value, validValues, out message); + } + } +} diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidation.cs b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidation.cs new file mode 100644 index 0000000000..8693cfd218 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidation.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Options; +using Umbraco.Core.Macros; + +namespace Umbraco.Core.Configuration.Models.Validation +{ + public class ContentSettingsValidation : ConfigurationValidationBase, IValidateOptions + { + public ValidateOptionsResult Validate(string name, ContentSettings options) + { + string message; + if (!ValidateMacroErrors(options.MacroErrors, out message)) + { + return ValidateOptionsResult.Fail(message); + } + + if (!ValidateError404Collection(options.Error404Collection, out message)) + { + return ValidateOptionsResult.Fail(message); + } + + return ValidateOptionsResult.Success; + } + + private bool ValidateMacroErrors(string value, out string message) + { + return ValidateStringIsOneOfEnumValues("Content:MacroErrors", value, typeof(MacroErrorBehaviour), out message); + } + + private bool ValidateError404Collection(IEnumerable values, out string message) + { + if (values.Any(x => !x.IsValid())) + { + message = $"Configuration entry Content:Error404Collection contains one or more invalid values. Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry."; + return false; + } + + message = string.Empty; + return true; + } + } +} diff --git a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidation.cs b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidation.cs new file mode 100644 index 0000000000..022f0289d8 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidation.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; + +namespace Umbraco.Core.Configuration.Models.Validation +{ + public class RequestHandlerSettingsValidation : ConfigurationValidationBase, IValidateOptions + { + public ValidateOptionsResult Validate(string name, RequestHandlerSettings options) + { + if (!ValidateConvertUrlsToAscii(options.ConvertUrlsToAscii, out var message)) + { + return ValidateOptionsResult.Fail(message); + } + + return ValidateOptionsResult.Success; + } + + private bool ValidateConvertUrlsToAscii(string value, out string message) + { + var validValues = new[] { "try", "true", "false" }; + return ValidateStringIsOneOfValidValues("RequestHandler:ConvertUrlsToAscii", value, validValues, out message); + } + } +} diff --git a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs index 20fbe9a1ef..459d95466d 100644 --- a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs @@ -62,7 +62,7 @@ namespace Umbraco.Web.Routing } /// - /// Returns the content id based on the configured IContentErrorPage section + /// Returns the content id based on the configured ContentErrorPage section. /// /// /// diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs index a7b148c8e5..59d5aa516d 100644 --- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs @@ -1,5 +1,4 @@ -using Moq; -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Tests.Common.Builders; diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 44fb18a159..09e4c0c82a 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Configuration.Models.Validation; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; @@ -126,6 +127,9 @@ namespace Umbraco.Extensions { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + services.AddSingleton, ContentSettingsValidation>(); + services.AddSingleton, RequestHandlerSettingsValidation>(); + services.Configure(configuration.GetSection(Constants.Configuration.ConfigPrefix + "ActiveDirectory")); services.Configure(configuration.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true); services.Configure(configuration.GetSection(Constants.Configuration.ConfigPrefix + "Content")); @@ -149,7 +153,6 @@ namespace Umbraco.Extensions services.Configure(configuration.GetSection(Constants.Configuration.ConfigSecurityPrefix + "UserPassword")); services.Configure(configuration.GetSection(Constants.Configuration.ConfigPrefix + "WebRouting")); - return services; } diff --git a/src/Umbraco.Web.Common/Macros/MacroRenderer.cs b/src/Umbraco.Web.Common/Macros/MacroRenderer.cs index 58c1e59338..5114f4a20f 100644 --- a/src/Umbraco.Web.Common/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web.Common/Macros/MacroRenderer.cs @@ -290,7 +290,7 @@ namespace Umbraco.Web.Macros Alias = macro.Alias, MacroSource = macro.MacroSource, Exception = e, - Behaviour = _contentSettings.MacroErrors + Behaviour = _contentSettings.MacroErrorsBehaviour }; switch (macroErrorEventArgs.Behaviour) diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 8d13c03e8b..8c11478c78 100644 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -290,7 +290,7 @@ namespace Umbraco.Web.Macros Alias = macro.Alias, MacroSource = macro.MacroSource, Exception = e, - Behaviour = _contentSettings.MacroErrors + Behaviour = _contentSettings.MacroErrorsBehaviour }; switch (macroErrorEventArgs.Behaviour)