From 8624a246ba86ac2ff91eb5f320de4f003bdb5a7b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 3 Feb 2021 07:42:56 +0100 Subject: [PATCH] Updated healthchecks to show a link to documentation instead of trying to fix something that can often not be fixed automatically. --- .../HealthChecksNotificationMethodSettings.cs | 2 +- src/Umbraco.Core/Constants-HealthChecks.cs | 58 +++++ .../UmbracoBuilder.Collections.cs | 8 +- src/Umbraco.Core/DictionaryExtensions.cs | 20 ++ .../AcceptableConfiguration.cs | 2 +- .../Checks/Configuration}/MacroErrorsCheck.cs | 70 +++--- .../Checks/Data/DatabaseIntegrityCheck.cs | 72 ++++--- .../Checks/Security/ClickJackingCheck.cs | 16 +- .../Checks/Security/ExcessiveHeadersCheck.cs | 54 ++--- .../Checks/Security/HttpsCheck.cs | 143 ++++++------- .../Checks/Security/NoSniffCheck.cs | 16 +- .../Checks/Services/SmtpCheck.cs | 41 ++-- .../ConfigurationServiceResult.cs | 2 +- .../HealthCheck.cs | 5 +- .../HealthCheckAction.cs | 2 +- .../HealthCheckAttribute.cs | 2 +- .../HealthCheckGroup.cs | 2 +- .../HealthCheckNotificationMethodAttribute.cs | 2 +- ...HealthCheckNotificationMethodCollection.cs | 4 +- ...heckNotificationMethodCollectionBuilder.cs | 4 +- .../HealthCheckNotificationVerbosity.cs | 2 +- .../HealthCheckResults.cs | 29 ++- .../HealthCheckStatus.cs | 8 +- .../HeathCheckCollectionBuilder.cs | 2 +- .../EmailNotificationMethod.cs | 7 +- .../IHealthCheckNotificationMethod.cs | 6 +- .../IMarkdownToHtmlConverter.cs | 5 +- .../NotificationMethodBase.cs | 4 +- .../StatusResultType.cs | 2 +- .../ValueComparisonType.cs | 2 +- .../Hosting/IHostingEnvironment.cs | 2 - .../Install/FilePermissionTest.cs | 10 + .../Install/IFilePermissionHelper.cs | 24 +-- .../InstallSteps/FilePermissionsStep.cs | 48 +++-- .../LocalizedTextServiceExtensions.cs | 8 +- .../UmbracoBuilder.CoreServices.cs | 6 +- .../UmbracoBuilder.Installer.cs | 1 + .../MarkdownToHtmlConverter.cs | 7 +- .../HostedServices/HealthCheckNotifier.cs | 10 +- .../Install/FilePermissionHelper.cs | 132 +++++++----- .../Install/InstallStepCollection.cs | 1 + .../HealthChecks/HealthCheckResultsTests.cs | 28 +-- .../HealthCheckNotifierTests.cs | 8 +- .../Controllers/BackOfficeServerVariables.cs | 2 +- .../HealthCheckController.cs | 49 +++-- .../AspNetCoreHostingEnvironment.cs | 2 - src/Umbraco.Web.UI.Client/package-lock.json | 201 +++++++++++++----- .../installer/steps/permissionsreport.html | 11 +- .../settings/healthcheck.controller.js | 2 +- .../Umbraco.Web.UI.NetCore.csproj | 1 - .../umbraco/config/lang/da.xml | 7 + .../umbraco/config/lang/en.xml | 7 + .../umbraco/config/lang/en_us.xml | 21 +- .../AspNet/AspNetHostingEnvironment.cs | 3 - src/Umbraco.Web/Composing/Current.cs | 21 +- 55 files changed, 745 insertions(+), 459 deletions(-) create mode 100644 src/Umbraco.Core/Constants-HealthChecks.cs rename src/Umbraco.Core/{HealthCheck => HealthChecks}/AcceptableConfiguration.cs (79%) rename src/Umbraco.Core/{Configuration/HealthChecks => HealthChecks/Checks/Configuration}/MacroErrorsCheck.cs (54%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/Checks/Data/DatabaseIntegrityCheck.cs (62%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/Checks/Security/ClickJackingCheck.cs (51%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/Checks/Security/ExcessiveHeadersCheck.cs (56%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/Checks/Security/HttpsCheck.cs (56%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/Checks/Security/NoSniffCheck.cs (50%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/Checks/Services/SmtpCheck.cs (70%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/ConfigurationServiceResult.cs (78%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheck.cs (92%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckAction.cs (98%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckAttribute.cs (94%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckGroup.cs (90%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckNotificationMethodAttribute.cs (92%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckNotificationMethodCollection.cs (79%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckNotificationMethodCollectionBuilder.cs (79%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckNotificationVerbosity.cs (71%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckResults.cs (88%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HealthCheckStatus.cs (85%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/HeathCheckCollectionBuilder.cs (94%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/NotificationMethods/EmailNotificationMethod.cs (94%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/NotificationMethods/IHealthCheckNotificationMethod.cs (56%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/NotificationMethods/IMarkdownToHtmlConverter.cs (54%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/NotificationMethods/NotificationMethodBase.cs (92%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/StatusResultType.cs (74%) rename src/Umbraco.Core/{HealthCheck => HealthChecks}/ValueComparisonType.cs (71%) create mode 100644 src/Umbraco.Core/Install/FilePermissionTest.cs rename src/Umbraco.Infrastructure/{HealthCheck => HealthChecks}/MarkdownToHtmlConverter.cs (88%) rename src/Umbraco.Web.BackOffice/{HealthCheck => HealthChecks}/HealthCheckController.cs (67%) diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs index b4bffab4d6..03293180db 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs @@ -2,7 +2,7 @@ // See LICENSE for more details. using System.Collections.Generic; -using Umbraco.Core.HealthCheck; +using Umbraco.Core.HealthChecks; namespace Umbraco.Core.Configuration.Models { diff --git a/src/Umbraco.Core/Constants-HealthChecks.cs b/src/Umbraco.Core/Constants-HealthChecks.cs new file mode 100644 index 0000000000..e81380134d --- /dev/null +++ b/src/Umbraco.Core/Constants-HealthChecks.cs @@ -0,0 +1,58 @@ +namespace Umbraco.Core +{ + /// + /// Defines constants. + /// + public static partial class Constants + { + /// + /// Defines constants for ModelsBuilder. + /// + public static class HealthChecks + { + + public static class DocumentationLinks + { + public const string SmtpCheck = "https://umbra.co/healthchecks/smtp"; + + public static class LiveEnvironment + { + + public const string CompilationDebugCheck = "https://umbra.co/healthchecks/compilation-debug"; + } + public static class Configuration + { + public const string MacroErrorsCheck = "https://umbra.co/healthchecks/macro-errors"; + public const string TrySkipIisCustomErrorsCheck = "https://umbra.co/healthchecks/skip-iis-custom-errors"; + public const string NotificationEmailCheck = "https://umbra.co/healthchecks/notification-email"; + } + public static class FolderAndFilePermissionsCheck + { + + public const string FileWriting = "https://umbra.co/healthchecks/file-writing"; + public const string FolderCreation = "https://umbra.co/healthchecks/folder-creation"; + public const string FileWritingForPackages = "https://umbra.co/healthchecks/file-writing-for-packages"; + public const string MediaFolderCreation = "https://umbra.co/healthchecks/media-folder-creation"; + } + + public static class Security + { + + public const string ClickJackingCheck = "https://umbra.co/healthchecks/click-jacking"; + public const string HstsCheck = "https://umbra.co/healthchecks/hsts"; + public const string NoSniffCheck = "https://umbra.co/healthchecks/no-sniff"; + public const string XssProtectionCheck = "https://umbra.co/healthchecks/xss-protection"; + public const string ExcessiveHeadersCheck = "https://umbra.co/healthchecks/excessive-headers"; + + public static class HttpsCheck + { + + public const string CheckIfCurrentSchemeIsHttps = "https://umbra.co/healthchecks/https-request"; + public const string CheckHttpsConfigurationSetting = "https://umbra.co/healthchecks/https-config"; + public const string CheckForValidCertificate = "https://umbra.co/healthchecks/valid-certificate"; + } + } + } + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index f6dc6fd6ff..e964852129 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -1,8 +1,8 @@ -using System.Security.Cryptography; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Dashboards; -using Umbraco.Core.HealthCheck; +using Umbraco.Core.HealthChecks; +using Umbraco.Core.HealthChecks.NotificationMethods; using Umbraco.Core.Manifest; using Umbraco.Core.PackageActions; using Umbraco.Core.PropertyEditors; @@ -13,8 +13,6 @@ using Umbraco.Web.Actions; using Umbraco.Web.ContentApps; using Umbraco.Web.Dashboards; using Umbraco.Web.Editors; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.HealthCheck.NotificationMethods; using Umbraco.Web.Media.EmbedProviders; using Umbraco.Web.Routing; using Umbraco.Web.Sections; @@ -54,7 +52,7 @@ namespace Umbraco.Core.DependencyInjection .Append() .Append(); builder.EditorValidators().Add(() => builder.TypeLoader.GetTypes()); - builder.HealthChecks().Add(() => builder.TypeLoader.GetTypes()); + builder.HealthChecks().Add(() => builder.TypeLoader.GetTypes()); builder.HealthCheckNotificationMethods().Add(() => builder.TypeLoader.GetTypes()); builder.TourFilters(); builder.UrlProviders() diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index 29cbc221ca..b44e9b68c0 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -6,6 +6,7 @@ using System.Collections.Specialized; using System.Linq; using System.Net; using System.Text; +using System.Threading.Tasks; namespace Umbraco.Core { @@ -280,5 +281,24 @@ namespace Umbraco.Core ? dictionary[key] : defaultValue; } + + public static async Task> ToDictionaryAsync( + this IEnumerable enumerable, + Func syncKeySelector, + Func> asyncValueSelector) + { + Dictionary dictionary = new Dictionary(); + + foreach (var item in enumerable) + { + var key = syncKeySelector(item); + + var value = await asyncValueSelector(item); + + dictionary.Add(key,value); + } + + return dictionary; + } } } diff --git a/src/Umbraco.Core/HealthCheck/AcceptableConfiguration.cs b/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs similarity index 79% rename from src/Umbraco.Core/HealthCheck/AcceptableConfiguration.cs rename to src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs index f879172a5d..f0f20fd4a2 100644 --- a/src/Umbraco.Core/HealthCheck/AcceptableConfiguration.cs +++ b/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { public class AcceptableConfiguration { diff --git a/src/Umbraco.Core/Configuration/HealthChecks/MacroErrorsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs similarity index 54% rename from src/Umbraco.Core/Configuration/HealthChecks/MacroErrorsCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs index b31f23667d..9ce0ae1404 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/MacroErrorsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs @@ -1,37 +1,48 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Services; -namespace Umbraco.Core.HealthCheck.Checks.Configuration +namespace Umbraco.Core.HealthChecks.Checks.Configuration { - [HealthCheck("D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", "Macro errors", - Description = - "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", + /// + /// Health check for the recommended production configuration for Macro Errors. + /// + [HealthCheck( + "D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", + "Macro errors", + Description = "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", Group = "Configuration")] public class MacroErrorsCheck : AbstractSettingsCheck { private readonly ILocalizedTextService _textService; - private readonly ILoggerFactory _loggerFactory; private readonly IOptionsMonitor _contentSettings; - public MacroErrorsCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory, + /// + /// Initializes a new instance of the class. + /// + public MacroErrorsCheck( + ILocalizedTextService textService, IOptionsMonitor contentSettings) - : base(textService, loggerFactory) + : base(textService) { _textService = textService; - _loggerFactory = loggerFactory; _contentSettings = contentSettings; } + /// + public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Configuration.MacroErrorsCheck; + + /// public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; + /// public override string ItemPath => Constants.Configuration.ConfigContentMacroErrors; - /// /// Gets the values to compare against. /// @@ -57,42 +68,23 @@ namespace Umbraco.Core.HealthCheck.Checks.Configuration } } + /// public override string CurrentValue => _contentSettings.CurrentValue.MacroErrors.ToString(); /// /// Gets the message for when the check has succeeded. /// - public override string CheckSuccessMessage - { - get - { - return _textService.Localize("healthcheck/macroErrorModeCheckSuccessMessage", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); - } - } + public override string CheckSuccessMessage => + _textService.Localize( + "healthcheck/macroErrorModeCheckSuccessMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); /// /// Gets the message for when the check has failed. /// - public override string CheckErrorMessage - { - get - { - return _textService.Localize("healthcheck/macroErrorModeCheckErrorMessage", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); - } - } - - /// - /// Gets the rectify success message. - /// - public override string RectifySuccessMessage - { - get - { - return TextService.Localize("healthcheck/macroErrorModeCheckRectifySuccessMessage", - new[] { Values.First(v => v.IsRecommended).Value }); - } - } + public override string CheckErrorMessage => + _textService.Localize( + "healthcheck/macroErrorModeCheckErrorMessage", + new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); } } diff --git a/src/Umbraco.Core/HealthCheck/Checks/Data/DatabaseIntegrityCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs similarity index 62% rename from src/Umbraco.Core/HealthCheck/Checks/Data/DatabaseIntegrityCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs index 0fb34950bd..a826d4dd45 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Data/DatabaseIntegrityCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs @@ -1,12 +1,19 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; using Umbraco.Core.Models; using Umbraco.Core.Services; -namespace Umbraco.Core.HealthCheck.Checks.Data +namespace Umbraco.Core.HealthChecks.Checks.Data { + /// + /// Health check for the integrity of the data in the database. + /// [HealthCheck( "73DD0C1C-E0CA-4C31-9564-1DCA509788AF", "Database data integrity check", @@ -16,12 +23,17 @@ namespace Umbraco.Core.HealthCheck.Checks.Data { private readonly IContentService _contentService; private readonly IMediaService _mediaService; - private const string _fixMediaPaths = "fixMediaPaths"; - private const string _fixContentPaths = "fixContentPaths"; - private const string _fixMediaPathsTitle = "Fix media paths"; - private const string _fixContentPathsTitle = "Fix content paths"; + private const string SSsFixMediaPaths = "fixMediaPaths"; + private const string SFixContentPaths = "fixContentPaths"; + private const string SFixMediaPathsTitle = "Fix media paths"; + private const string SFixContentPathsTitle = "Fix content paths"; - public DatabaseIntegrityCheck(IContentService contentService, IMediaService mediaService) + /// + /// Initializes a new instance of the class. + /// + public DatabaseIntegrityCheck( + IContentService contentService, + IMediaService mediaService) { _contentService = contentService; _mediaService = mediaService; @@ -30,32 +42,32 @@ namespace Umbraco.Core.HealthCheck.Checks.Data /// /// Get the status for this health check /// - /// - public override IEnumerable GetStatus() - { - //return the statuses - return new[] + public override Task> GetStatus() => + Task.FromResult((IEnumerable)new[] { CheckDocuments(false), CheckMedia(false) - }; - } + }); - private HealthCheckStatus CheckMedia(bool fix) - { - return CheckPaths(_fixMediaPaths, _fixMediaPathsTitle, Core.Constants.UdiEntityType.Media, fix, - () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); - } + private HealthCheckStatus CheckMedia(bool fix) => + CheckPaths( + SSsFixMediaPaths, + SFixMediaPathsTitle, + Constants.UdiEntityType.Media, + fix, + () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); - private HealthCheckStatus CheckDocuments(bool fix) - { - return CheckPaths(_fixContentPaths, _fixContentPathsTitle, Core.Constants.UdiEntityType.Document, fix, - () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); - } + private HealthCheckStatus CheckDocuments(bool fix) => + CheckPaths( + SFixContentPaths, + SFixContentPathsTitle, + Constants.UdiEntityType.Document, + fix, + () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, Func doCheck) { - var report = doCheck(); + ContentDataIntegrityReport report = doCheck(); var actions = new List(); if (!report.Ok) @@ -82,7 +94,9 @@ namespace Umbraco.Core.HealthCheck.Checks.Data sb.AppendLine($"

All {entityType} paths are valid

"); if (!detailed) + { return sb.ToString(); + } } else { @@ -92,7 +106,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Data if (detailed && report.DetectedIssues.Count > 0) { sb.AppendLine("
    "); - foreach (var issueGroup in report.DetectedIssues.GroupBy(x => x.Value.IssueType)) + foreach (IGrouping> issueGroup in report.DetectedIssues.GroupBy(x => x.Value.IssueType)) { var countByGroup = issueGroup.Count(); var fixedByGroup = issueGroup.Count(x => x.Value.Fixed); @@ -100,19 +114,21 @@ namespace Umbraco.Core.HealthCheck.Checks.Data sb.AppendLine($"{countByGroup} issues of type {issueGroup.Key} ... {fixedByGroup} fixed"); sb.AppendLine(""); } + sb.AppendLine("
"); } return sb.ToString(); } + /// public override HealthCheckStatus ExecuteAction(HealthCheckAction action) { switch (action.Alias) { - case _fixContentPaths: + case SFixContentPaths: return CheckDocuments(true); - case _fixMediaPaths: + case SSsFixMediaPaths: return CheckMedia(true); default: throw new InvalidOperationException("Action not supported"); diff --git a/src/Umbraco.Core/HealthCheck/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs similarity index 51% rename from src/Umbraco.Core/HealthCheck/Checks/Security/ClickJackingCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs index a7b4c0ba85..9b654b5f8b 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs @@ -1,8 +1,14 @@ -using Umbraco.Core.Services; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Services; using Umbraco.Web; -namespace Umbraco.Core.HealthCheck.Checks.Security +namespace Umbraco.Core.HealthChecks.Checks.Security { + /// + /// Health check for the recommended production setup regarding the X-Frame-Options header. + /// [HealthCheck( "ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0", "Click-Jacking Protection", @@ -10,9 +16,15 @@ namespace Umbraco.Core.HealthCheck.Checks.Security Group = "Security")] public class ClickJackingCheck : BaseHttpHeaderCheck { + /// + /// Initializes a new instance of the class. + /// public ClickJackingCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService) : base(requestAccessor, textService, "X-Frame-Options", "sameorigin", "clickJacking", true) { } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.ClickJackingCheck; } } diff --git a/src/Umbraco.Core/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs similarity index 56% rename from src/Umbraco.Core/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index 9cf1127bb0..010683c6fe 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -1,12 +1,19 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; -using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using Umbraco.Core.Services; using Umbraco.Web; -namespace Umbraco.Core.HealthCheck.Checks.Security +namespace Umbraco.Core.HealthChecks.Checks.Security { + /// + /// Health check for the recommended production setup regarding unnecessary headers. + /// [HealthCheck( "92ABBAA2-0586-4089-8AE2-9A843439D577", "Excessive Headers", @@ -16,66 +23,63 @@ namespace Umbraco.Core.HealthCheck.Checks.Security { private readonly ILocalizedTextService _textService; private readonly IRequestAccessor _requestAccessor; + private static HttpClient s_httpClient; + /// + /// Initializes a new instance of the class. + /// public ExcessiveHeadersCheck(ILocalizedTextService textService, IRequestAccessor requestAccessor) { _textService = textService; _requestAccessor = requestAccessor; } + private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); + /// /// Get the status for this health check /// - /// - public override IEnumerable GetStatus() - { - //return the statuses - return new[] { CheckForHeaders() }; - } + public override async Task> GetStatus() => + await Task.WhenAll(CheckForHeaders()); /// /// Executes the action and returns it's status /// - /// - /// public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - throw new InvalidOperationException("ExcessiveHeadersCheck has no executable actions"); - } + => throw new InvalidOperationException("ExcessiveHeadersCheck has no executable actions"); - private HealthCheckStatus CheckForHeaders() + private async Task CheckForHeaders() { - var message = string.Empty; + string message; var success = false; - var url = _requestAccessor.GetApplicationUrl(); + Uri url = _requestAccessor.GetApplicationUrl(); // Access the site home page and check for the headers - var request = WebRequest.Create(url); - request.Method = "HEAD"; + var request = new HttpRequestMessage(HttpMethod.Head, url); try { - var response = request.GetResponse(); - var allHeaders = response.Headers.AllKeys; - var headersToCheckFor = new [] {"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version"}; + using HttpResponseMessage response = await HttpClient.SendAsync(request); + + IEnumerable allHeaders = response.Headers.Select(x => x.Key); + var headersToCheckFor = new[] { "Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version" }; var headersFound = allHeaders .Intersect(headersToCheckFor) .ToArray(); success = headersFound.Any() == false; message = success ? _textService.Localize("healthcheck/excessiveHeadersNotFound") - : _textService.Localize("healthcheck/excessiveHeadersFound", new [] { string.Join(", ", headersFound) }); + : _textService.Localize("healthcheck/excessiveHeadersFound", new[] { string.Join(", ", headersFound) }); } catch (Exception ex) { message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url.ToString(), ex.Message }); } - var actions = new List(); return new HealthCheckStatus(message) { ResultType = success ? StatusResultType.Success : StatusResultType.Warning, - Actions = actions + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.ExcessiveHeadersCheck, }; } } diff --git a/src/Umbraco.Core/HealthCheck/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs similarity index 56% rename from src/Umbraco.Core/HealthCheck/Checks/Security/HttpsCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index 6bba41b7d5..187fb2d300 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -1,17 +1,23 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; +using System.Net.Security; using System.Security.Cryptography.X509Certificates; -using Microsoft.Extensions.Logging; -using Umbraco.Core.Services; -using Umbraco.Core.Configuration.Models; +using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Umbraco.Core.Configuration.HealthChecks; -using Umbraco.Core.IO; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Services; using Umbraco.Web; -namespace Umbraco.Core.HealthCheck.Checks.Security +namespace Umbraco.Core.HealthChecks.Checks.Security { + /// + /// Health checks for the recommended production setup regarding https. + /// [HealthCheck( "EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7", "HTTPS Configuration", @@ -22,81 +28,85 @@ namespace Umbraco.Core.HealthCheck.Checks.Security private readonly ILocalizedTextService _textService; private readonly IOptionsMonitor _globalSettings; private readonly IRequestAccessor _requestAccessor; - private readonly ILogger _logger; - private const string FixHttpsSettingAction = "fixHttpsSetting"; - string itemPath => Constants.Configuration.ConfigGlobalUseHttps; - public HttpsCheck(ILocalizedTextService textService, + private static HttpClient s_httpClient; + private static HttpClientHandler s_httpClientHandler; + private static int s_certificateDaysToExpiry; + + /// + /// Initializes a new instance of the class. + /// + public HttpsCheck( + ILocalizedTextService textService, IOptionsMonitor globalSettings, - IIOHelper ioHelper, - IRequestAccessor requestAccessor, - ILogger logger) + IRequestAccessor requestAccessor) { _textService = textService; _globalSettings = globalSettings; _requestAccessor = requestAccessor; - _logger = logger; } + private static HttpClient HttpClient => s_httpClient ??= new HttpClient(HttpClientHandler); + + private static HttpClientHandler HttpClientHandler => s_httpClientHandler ??= new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation + }; + /// /// Get the status for this health check /// - /// - public override IEnumerable GetStatus() - { - //return the statuses - return new[] { CheckIfCurrentSchemeIsHttps(), CheckHttpsConfigurationSetting(), CheckForValidCertificate() }; - } + public override async Task> GetStatus() => + await Task.WhenAll( + CheckIfCurrentSchemeIsHttps(), + CheckHttpsConfigurationSetting(), + CheckForValidCertificate()); /// /// Executes the action and returns it's status /// - /// - /// public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + => throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); + + private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors sslErrors) { - switch (action.Alias) + if (!(certificate is null) && s_certificateDaysToExpiry == default) { - case FixHttpsSettingAction: - return FixHttpsSetting(); - default: - throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); + s_certificateDaysToExpiry = (int)Math.Floor((certificate.NotAfter - DateTime.Now).TotalDays); } + + return sslErrors == SslPolicyErrors.None; } - private HealthCheckStatus CheckForValidCertificate() + private async Task CheckForValidCertificate() { - var message = string.Empty; + string message; StatusResultType result; // Attempt to access the site over HTTPS to see if it HTTPS is supported // and a valid certificate has been configured var url = _requestAccessor.GetApplicationUrl().ToString().Replace("http:", "https:"); - var request = (HttpWebRequest)WebRequest.Create(url); - request.Method = "HEAD"; + + var request = new HttpRequestMessage(HttpMethod.Head, url); try { - var response = (HttpWebResponse)request.GetResponse(); + using HttpResponseMessage response = await HttpClient.SendAsync(request); if (response.StatusCode == HttpStatusCode.OK) { // Got a valid response, check now for if certificate expiring within 14 days // Hat-tip: https://stackoverflow.com/a/15343898/489433 const int numberOfDaysForExpiryWarning = 14; - var cert = request.ServicePoint.Certificate; - var cert2 = new X509Certificate2(cert); - var expirationDate = cert2.NotAfter; - var daysToExpiry = (int)Math.Floor((cert2.NotAfter - DateTime.Now).TotalDays); - if (daysToExpiry <= 0) + if (s_certificateDaysToExpiry <= 0) { result = StatusResultType.Error; message = _textService.Localize("healthcheck/httpsCheckExpiredCertificate"); } - else if (daysToExpiry < numberOfDaysForExpiryWarning) + else if (s_certificateDaysToExpiry < numberOfDaysForExpiryWarning) { result = StatusResultType.Warning; - message = _textService.Localize("healthcheck/httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() }); + message = _textService.Localize("healthcheck/httpsCheckExpiringCertificate", new[] { s_certificateDaysToExpiry.ToString() }); } else { @@ -107,7 +117,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Security else { result = StatusResultType.Error; - message = _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url, response.StatusDescription }); + message = _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url, response.ReasonPhrase }); } } catch (Exception ex) @@ -127,34 +137,31 @@ namespace Umbraco.Core.HealthCheck.Checks.Security result = StatusResultType.Error; } - var actions = new List(); - return new HealthCheckStatus(message) { ResultType = result, - Actions = actions + ReadMoreLink = result == StatusResultType.Success + ? null + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps }; } - private HealthCheckStatus CheckIfCurrentSchemeIsHttps() + private Task CheckIfCurrentSchemeIsHttps() { - var uri = _requestAccessor.GetApplicationUrl(); + Uri uri = _requestAccessor.GetApplicationUrl(); var success = uri.Scheme == "https"; - var actions = new List(); - - return new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) + return Task.FromResult(new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) { ResultType = success ? StatusResultType.Success : StatusResultType.Error, - Actions = actions - }; + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps + }); } - private HealthCheckStatus CheckHttpsConfigurationSetting() + private Task CheckHttpsConfigurationSetting() { bool httpsSettingEnabled = _globalSettings.CurrentValue.UseHttps; Uri uri = _requestAccessor.GetApplicationUrl(); - var actions = new List(); string resultMessage; StatusResultType resultType; @@ -165,35 +172,19 @@ namespace Umbraco.Core.HealthCheck.Checks.Security } else { - if (httpsSettingEnabled == false) - { - actions.Add(new HealthCheckAction(FixHttpsSettingAction, Id) - { - Name = _textService.Localize("healthcheck/httpsCheckEnableHttpsButton"), - Description = _textService.Localize("healthcheck/httpsCheckEnableHttpsDescription") - }); - } - - resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationCheckResult", + resultMessage = _textService.Localize( + "healthcheck/httpsCheckConfigurationCheckResult", new[] { httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not" }); resultType = httpsSettingEnabled ? StatusResultType.Success : StatusResultType.Error; } - return new HealthCheckStatus(resultMessage) + return Task.FromResult(new HealthCheckStatus(resultMessage) { ResultType = resultType, - Actions = actions - }; - } - - private HealthCheckStatus FixHttpsSetting() - { - //TODO: return message instead of actual fix - - return new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsSuccess")) - { - ResultType = StatusResultType.Success - }; + ReadMoreLink = resultType == StatusResultType.Success + ? null + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckHttpsConfigurationSetting + }); } } } diff --git a/src/Umbraco.Core/HealthCheck/Checks/Security/NoSniffCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs similarity index 50% rename from src/Umbraco.Core/HealthCheck/Checks/Security/NoSniffCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs index c392842049..b74be4ca6b 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Security/NoSniffCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs @@ -1,8 +1,14 @@ -using Umbraco.Core.Services; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Services; using Umbraco.Web; -namespace Umbraco.Core.HealthCheck.Checks.Security +namespace Umbraco.Core.HealthChecks.Checks.Security { + /// + /// Health check for the recommended production setup regarding the X-Content-Type-Options header. + /// [HealthCheck( "1CF27DB3-EFC0-41D7-A1BB-EA912064E071", "Content/MIME Sniffing Protection", @@ -10,9 +16,15 @@ namespace Umbraco.Core.HealthCheck.Checks.Security Group = "Security")] public class NoSniffCheck : BaseHttpHeaderCheck { + /// + /// Initializes a new instance of the class. + /// public NoSniffCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService) : base(requestAccessor, textService, "X-Content-Type-Options", "nosniff", "noSniff", false) { } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.NoSniffCheck; } } diff --git a/src/Umbraco.Core/HealthCheck/Checks/Services/SmtpCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs similarity index 70% rename from src/Umbraco.Core/HealthCheck/Checks/Services/SmtpCheck.cs rename to src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs index 9e1a6f84af..e53d4078a0 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Services/SmtpCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs @@ -1,13 +1,20 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; +using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Services; -namespace Umbraco.Core.HealthCheck.Checks.Services +namespace Umbraco.Core.HealthChecks.Checks.Services { + /// + /// Health check for the recommended setup regarding SMTP. + /// [HealthCheck( "1B5D221B-CE99-4193-97CB-5F3261EC73DF", "SMTP Settings", @@ -18,6 +25,9 @@ namespace Umbraco.Core.HealthCheck.Checks.Services private readonly ILocalizedTextService _textService; private readonly IOptionsMonitor _globalSettings; + /// + /// Initializes a new instance of the class. + /// public SmtpCheck(ILocalizedTextService textService, IOptionsMonitor globalSettings) { _textService = textService; @@ -27,28 +37,20 @@ namespace Umbraco.Core.HealthCheck.Checks.Services /// /// Get the status for this health check /// - /// - public override IEnumerable GetStatus() - { - //return the statuses - return new[] { CheckSmtpSettings() }; - } + public override Task> GetStatus() => + Task.FromResult(CheckSmtpSettings().Yield()); /// /// Executes the action and returns it's status /// - /// - /// public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - throw new InvalidOperationException("SmtpCheck has no executable actions"); - } + => throw new InvalidOperationException("SmtpCheck has no executable actions"); private HealthCheckStatus CheckSmtpSettings() { var success = false; - var smtpSettings = _globalSettings.CurrentValue.Smtp; + SmtpSettings smtpSettings = _globalSettings.CurrentValue.Smtp; string message; if (smtpSettings == null) @@ -66,7 +68,9 @@ namespace Umbraco.Core.HealthCheck.Checks.Services success = CanMakeSmtpConnection(smtpSettings.Host, smtpSettings.Port); message = success ? _textService.Localize("healthcheck/smtpMailSettingsConnectionSuccess") - : _textService.Localize("healthcheck/smtpMailSettingsConnectionFail", new [] { smtpSettings.Host, smtpSettings.Port.ToString() }); + : _textService.Localize( + "healthcheck/smtpMailSettingsConnectionFail", + new[] { smtpSettings.Host, smtpSettings.Port.ToString() }); } } @@ -75,18 +79,19 @@ namespace Umbraco.Core.HealthCheck.Checks.Services new HealthCheckStatus(message) { ResultType = success ? StatusResultType.Success : StatusResultType.Error, - Actions = actions + Actions = actions, + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.SmtpCheck }; } - private bool CanMakeSmtpConnection(string host, int port) + private static bool CanMakeSmtpConnection(string host, int port) { try { using (var client = new TcpClient()) { client.Connect(host, port); - using (var stream = client.GetStream()) + using (NetworkStream stream = client.GetStream()) { using (var writer = new StreamWriter(stream)) using (var reader = new StreamReader(stream)) diff --git a/src/Umbraco.Core/HealthCheck/ConfigurationServiceResult.cs b/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs similarity index 78% rename from src/Umbraco.Core/HealthCheck/ConfigurationServiceResult.cs rename to src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs index b4940f927a..114f1d9ed2 100644 --- a/src/Umbraco.Core/HealthCheck/ConfigurationServiceResult.cs +++ b/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { public class ConfigurationServiceResult { diff --git a/src/Umbraco.Core/HealthCheck/HealthCheck.cs b/src/Umbraco.Core/HealthChecks/HealthCheck.cs similarity index 92% rename from src/Umbraco.Core/HealthCheck/HealthCheck.cs rename to src/Umbraco.Core/HealthChecks/HealthCheck.cs index 9f4e364be6..36c60e5164 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheck.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheck.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using System.Threading.Tasks; using Umbraco.Core.Composing; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { /// /// Provides a base class for health checks, filling in the healthcheck metadata on construction @@ -42,7 +43,7 @@ namespace Umbraco.Core.HealthCheck /// Get the status for this health check /// /// - public abstract IEnumerable GetStatus(); + public abstract Task> GetStatus(); /// /// Executes the action and returns it's status diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckAction.cs b/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs similarity index 98% rename from src/Umbraco.Core/HealthCheck/HealthCheckAction.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckAction.cs index e1771a4262..a89f6b73ad 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckAction.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { [DataContract(Name = "healthCheckAction", Namespace = "")] public class HealthCheckAction diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckAttribute.cs b/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs similarity index 94% rename from src/Umbraco.Core/HealthCheck/HealthCheckAttribute.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs index bd8c10f899..9a265a2e03 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckAttribute.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { /// /// Metadata attribute for Health checks diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckGroup.cs b/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs similarity index 90% rename from src/Umbraco.Core/HealthCheck/HealthCheckGroup.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs index 2cd1040896..71b0013d8e 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckGroup.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { [DataContract(Name = "healthCheckGroup", Namespace = "")] public class HealthCheckGroup diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodAttribute.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs similarity index 92% rename from src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodAttribute.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs index f78df14942..7e5223772f 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodAttribute.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { /// /// Metadata attribute for health check notification methods diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodCollection.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs similarity index 79% rename from src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodCollection.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs index 6c5f89e4bc..bcf197aa39 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodCollection.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using Umbraco.Core.Composing; -using Umbraco.Web.HealthCheck.NotificationMethods; +using Umbraco.Core.HealthChecks.NotificationMethods; -namespace Umbraco.Web.HealthCheck +namespace Umbraco.Core.HealthChecks { public class HealthCheckNotificationMethodCollection : BuilderCollectionBase { diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodCollectionBuilder.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs similarity index 79% rename from src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodCollectionBuilder.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs index d498716b71..e5d91432f5 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationMethodCollectionBuilder.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs @@ -1,7 +1,7 @@ using Umbraco.Core.Composing; -using Umbraco.Web.HealthCheck.NotificationMethods; +using Umbraco.Core.HealthChecks.NotificationMethods; -namespace Umbraco.Web.HealthCheck +namespace Umbraco.Core.HealthChecks { public class HealthCheckNotificationMethodCollectionBuilder : LazyCollectionBuilderBase { diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationVerbosity.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs similarity index 71% rename from src/Umbraco.Core/HealthCheck/HealthCheckNotificationVerbosity.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs index 74cd4eb93b..e79b1e627c 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckNotificationVerbosity.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { public enum HealthCheckNotificationVerbosity { diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckResults.cs b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs similarity index 88% rename from src/Umbraco.Core/HealthCheck/HealthCheckResults.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckResults.cs index 44955bfaae..904649deb1 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckResults.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs @@ -2,27 +2,32 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Core.HealthCheck; -namespace Umbraco.Infrastructure.HealthCheck +namespace Umbraco.Core.HealthChecks { public class HealthCheckResults { private readonly Dictionary> _results; public readonly bool AllChecksSuccessful; - private ILogger Logger => StaticApplicationLogging.Logger; // TODO: inject + private static ILogger Logger => StaticApplicationLogging.Logger; // TODO: inject - public HealthCheckResults(IEnumerable checks) + private HealthCheckResults(Dictionary> results, bool allChecksSuccessful) { - _results = checks.ToDictionary( + _results = results; + AllChecksSuccessful = allChecksSuccessful; + } + + public static async Task Create(IEnumerable checks) + { + var results = await checks.ToDictionaryAsync( t => t.Name, - t => { + async t => { try { - return t.GetStatus(); + return await t.GetStatus(); } catch (Exception ex) { @@ -39,16 +44,18 @@ namespace Umbraco.Infrastructure.HealthCheck }); // find out if all checks pass or not - AllChecksSuccessful = true; - foreach (var result in _results) + var allChecksSuccessful = true; + foreach (var result in results) { var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success || x.ResultType == StatusResultType.Info || x.ResultType == StatusResultType.Warning); if (checkIsSuccess == false) { - AllChecksSuccessful = false; + allChecksSuccessful = false; break; } } + + return new HealthCheckResults(results, allChecksSuccessful); } public void LogResults() diff --git a/src/Umbraco.Core/HealthCheck/HealthCheckStatus.cs b/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs similarity index 85% rename from src/Umbraco.Core/HealthCheck/HealthCheckStatus.cs rename to src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs index 2eb873603f..84e3933133 100644 --- a/src/Umbraco.Core/HealthCheck/HealthCheckStatus.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Runtime.Serialization; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { /// /// The status returned for a health check when it performs it check @@ -49,6 +49,10 @@ namespace Umbraco.Core.HealthCheck [DataMember(Name = "actions")] public IEnumerable Actions { get; set; } - // TODO: What else? + /// + /// This is optional but would allow a developer to specify a link that is shown as a "read more" button. + /// + [DataMember(Name = "readMoreLink")] + public string ReadMoreLink { get; set; } } } diff --git a/src/Umbraco.Core/HealthCheck/HeathCheckCollectionBuilder.cs b/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs similarity index 94% rename from src/Umbraco.Core/HealthCheck/HeathCheckCollectionBuilder.cs rename to src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs index be3a788cff..57d89b00d9 100644 --- a/src/Umbraco.Core/HealthCheck/HeathCheckCollectionBuilder.cs +++ b/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Composing; -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { public class HealthCheckCollectionBuilder : LazyCollectionBuilderBase { diff --git a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs similarity index 94% rename from src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs rename to src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs index ad92886ecd..7492b8b9ad 100644 --- a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs @@ -1,16 +1,13 @@ using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.HealthCheck; using Umbraco.Core.Mail; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Infrastructure.HealthCheck; +using Umbraco.Web; -namespace Umbraco.Web.HealthCheck.NotificationMethods +namespace Umbraco.Core.HealthChecks.NotificationMethods { [HealthCheckNotificationMethod("email")] public class EmailNotificationMethod : NotificationMethodBase diff --git a/src/Umbraco.Core/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs similarity index 56% rename from src/Umbraco.Core/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs rename to src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs index 77614c41e3..1bed571e14 100644 --- a/src/Umbraco.Core/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs @@ -1,9 +1,7 @@ -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; using Umbraco.Core.Composing; -using Umbraco.Infrastructure.HealthCheck; -namespace Umbraco.Web.HealthCheck.NotificationMethods +namespace Umbraco.Core.HealthChecks.NotificationMethods { public interface IHealthCheckNotificationMethod : IDiscoverable { diff --git a/src/Umbraco.Core/HealthCheck/NotificationMethods/IMarkdownToHtmlConverter.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs similarity index 54% rename from src/Umbraco.Core/HealthCheck/NotificationMethods/IMarkdownToHtmlConverter.cs rename to src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs index 20d8f0f07e..0ab33eb6d2 100644 --- a/src/Umbraco.Core/HealthCheck/NotificationMethods/IMarkdownToHtmlConverter.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs @@ -1,7 +1,4 @@ -using Umbraco.Core.HealthCheck; -using Umbraco.Infrastructure.HealthCheck; - -namespace Umbraco.Web.HealthCheck.NotificationMethods +namespace Umbraco.Core.HealthChecks.NotificationMethods { public interface IMarkdownToHtmlConverter { diff --git a/src/Umbraco.Core/HealthCheck/NotificationMethods/NotificationMethodBase.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs similarity index 92% rename from src/Umbraco.Core/HealthCheck/NotificationMethods/NotificationMethodBase.cs rename to src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs index eeb8452492..f491e26ae9 100644 --- a/src/Umbraco.Core/HealthCheck/NotificationMethods/NotificationMethodBase.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs @@ -3,10 +3,8 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.HealthCheck; -using Umbraco.Infrastructure.HealthCheck; -namespace Umbraco.Web.HealthCheck.NotificationMethods +namespace Umbraco.Core.HealthChecks.NotificationMethods { public abstract class NotificationMethodBase : IHealthCheckNotificationMethod { diff --git a/src/Umbraco.Core/HealthCheck/StatusResultType.cs b/src/Umbraco.Core/HealthChecks/StatusResultType.cs similarity index 74% rename from src/Umbraco.Core/HealthCheck/StatusResultType.cs rename to src/Umbraco.Core/HealthChecks/StatusResultType.cs index 3f2c392933..ce91080267 100644 --- a/src/Umbraco.Core/HealthCheck/StatusResultType.cs +++ b/src/Umbraco.Core/HealthChecks/StatusResultType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { public enum StatusResultType { diff --git a/src/Umbraco.Core/HealthCheck/ValueComparisonType.cs b/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs similarity index 71% rename from src/Umbraco.Core/HealthCheck/ValueComparisonType.cs rename to src/Umbraco.Core/HealthChecks/ValueComparisonType.cs index c5dd6517a8..905c92e7ce 100644 --- a/src/Umbraco.Core/HealthCheck/ValueComparisonType.cs +++ b/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.HealthCheck +namespace Umbraco.Core.HealthChecks { public enum ValueComparisonType { diff --git a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs index 60d582c6c9..ff2b3adfa5 100644 --- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs +++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs @@ -31,8 +31,6 @@ namespace Umbraco.Core.Hosting /// bool IsHosted { get; } - Version IISVersion { get; } - /// /// Maps a virtual path to a physical path to the application's web root /// diff --git a/src/Umbraco.Core/Install/FilePermissionTest.cs b/src/Umbraco.Core/Install/FilePermissionTest.cs new file mode 100644 index 0000000000..fe714ca8fa --- /dev/null +++ b/src/Umbraco.Core/Install/FilePermissionTest.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Install +{ + public enum FilePermissionTest + { + FolderCreation, + FileWritingForPackages, + FileWriting, + MediaFolderCreation + } +} diff --git a/src/Umbraco.Core/Install/IFilePermissionHelper.cs b/src/Umbraco.Core/Install/IFilePermissionHelper.cs index ab521d214e..6bddd02db4 100644 --- a/src/Umbraco.Core/Install/IFilePermissionHelper.cs +++ b/src/Umbraco.Core/Install/IFilePermissionHelper.cs @@ -1,26 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; namespace Umbraco.Core.Install { + /// + /// Helper to test File and folder permissions + /// public interface IFilePermissionHelper { - bool RunFilePermissionTestSuite(out Dictionary> report); - /// - /// This will test the directories for write access + /// Run all tests for permissions of the required files and folders. /// - /// The directories to check - /// The resulting errors, if any - /// - /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause - /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as - /// reliable but we cannot write a file since it will cause an app domain restart. - /// - /// Returns true if test succeeds - // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus - bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false); + /// True if all permissions are correct. False otherwise. + bool RunFilePermissionTestSuite(out Dictionary> report); - // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus - bool EnsureFiles(string[] files, out IEnumerable errors); } } diff --git a/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs b/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs index 152c5a831f..d2c2c84339 100644 --- a/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs @@ -1,38 +1,56 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; -using System.IO; +using System.Linq; using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.Install; +using Umbraco.Core.Services; +using Umbraco.Web.Install; using Umbraco.Web.Install.Models; -namespace Umbraco.Web.Install.InstallSteps +namespace Umbraco.Core.Install.InstallSteps { - [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "Permissions", 0, "", + /// + /// Represents a step in the installation that ensure all the required permissions on files and folders are correct. + /// + [InstallSetupStep( + InstallationType.NewInstall | InstallationType.Upgrade, + "Permissions", + 0, + "", PerformsAppRestart = true)] public class FilePermissionsStep : InstallSetupStep { private readonly IFilePermissionHelper _filePermissionHelper; - public FilePermissionsStep(IFilePermissionHelper filePermissionHelper) + private readonly ILocalizedTextService _localizedTextService; + + /// + /// Initializes a new instance of the class. + /// + public FilePermissionsStep( + IFilePermissionHelper filePermissionHelper, + ILocalizedTextService localizedTextService) { _filePermissionHelper = filePermissionHelper; + _localizedTextService = localizedTextService; } + + /// public override Task ExecuteAsync(object model) { // validate file permissions - Dictionary> report; - var permissionsOk = _filePermissionHelper.RunFilePermissionTestSuite(out report); + var permissionsOk = _filePermissionHelper.RunFilePermissionTestSuite(out Dictionary> report); + var translatedErrors = report.ToDictionary(x => _localizedTextService.Localize("permissions", x.Key), x => x.Value); if (permissionsOk == false) - throw new InstallException("Permission check failed", "permissionsreport", new { errors = report }); + { + throw new InstallException("Permission check failed", "permissionsreport", new { errors = translatedErrors }); + } return Task.FromResult(null); } - public override bool RequiresExecution(object model) - { - return true; - } + /// + public override bool RequiresExecution(object model) => true; } } diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 92854c5a2b..e02cdec7c9 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -2,7 +2,6 @@ using System.Globalization; using System.Linq; using System.Threading; -using Umbraco.Core.Composing; using Umbraco.Core.Dictionary; namespace Umbraco.Core.Services @@ -12,6 +11,13 @@ namespace Umbraco.Core.Services /// public static class LocalizedTextServiceExtensions { + + public static string Localize(this ILocalizedTextService manager, string area, T key) + where T: System.Enum + { + var fullKey = string.Join("/", area, key); + return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture); + } public static string Localize(this ILocalizedTextService manager, string area, string key) { var fullKey = string.Join("/", area, key); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 53f2d29709..f8fc338ee1 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.HealthChecks.NotificationMethods; using Umbraco.Core.Hosting; using Umbraco.Core.Install; using Umbraco.Core.Logging.Serilog.Enrichers; @@ -27,14 +28,13 @@ using Umbraco.Core.Strings; using Umbraco.Core.Templates; using Umbraco.Examine; using Umbraco.Infrastructure.Examine; +using Umbraco.Infrastructure.HealthChecks; using Umbraco.Infrastructure.HostedServices; +using Umbraco.Infrastructure.Install; using Umbraco.Infrastructure.Logging.Serilog.Enrichers; using Umbraco.Infrastructure.Media; using Umbraco.Infrastructure.Runtime; using Umbraco.Web; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.HealthCheck.NotificationMethods; -using Umbraco.Web.Install; using Umbraco.Web.Media; using Umbraco.Web.Migrations.PostMigrations; using Umbraco.Web.Models.PublishedContent; diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs index 1e40831f75..21bb4d7ceb 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Install.InstallSteps; using Umbraco.Web.Install; using Umbraco.Web.Install.InstallSteps; using Umbraco.Web.Install.Models; diff --git a/src/Umbraco.Infrastructure/HealthCheck/MarkdownToHtmlConverter.cs b/src/Umbraco.Infrastructure/HealthChecks/MarkdownToHtmlConverter.cs similarity index 88% rename from src/Umbraco.Infrastructure/HealthCheck/MarkdownToHtmlConverter.cs rename to src/Umbraco.Infrastructure/HealthChecks/MarkdownToHtmlConverter.cs index 39c5fe0bf2..739035b177 100644 --- a/src/Umbraco.Infrastructure/HealthCheck/MarkdownToHtmlConverter.cs +++ b/src/Umbraco.Infrastructure/HealthChecks/MarkdownToHtmlConverter.cs @@ -1,9 +1,8 @@ using HeyRed.MarkdownSharp; -using Umbraco.Core.HealthCheck; -using Umbraco.Infrastructure.HealthCheck; -using Umbraco.Web.HealthCheck.NotificationMethods; +using Umbraco.Core.HealthChecks; +using Umbraco.Core.HealthChecks.NotificationMethods; -namespace Umbraco.Web.HealthCheck +namespace Umbraco.Infrastructure.HealthChecks { public class MarkdownToHtmlConverter : IMarkdownToHtmlConverter { diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index c1412d4169..dcbec3d8d1 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -11,13 +11,11 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Extensions; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.HealthCheck; +using Umbraco.Core.HealthChecks; +using Umbraco.Core.HealthChecks.NotificationMethods; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; -using Umbraco.Infrastructure.HealthCheck; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.HealthCheck.NotificationMethods; namespace Umbraco.Infrastructure.HostedServices { @@ -118,10 +116,10 @@ namespace Umbraco.Infrastructure.HostedServices .Distinct() .ToArray(); - IEnumerable checks = _healthChecks + IEnumerable checks = _healthChecks .Where(x => disabledCheckIds.Contains(x.Id) == false); - var results = new HealthCheckResults(checks); + var results = await HealthCheckResults.Create(checks); results.LogResults(); // Send using registered notification methods that are enabled. diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs index 00f7c80fe4..ec73035dc2 100644 --- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs @@ -1,18 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; -using System.Linq; using System.IO; +using System.Linq; using System.Security.AccessControl; +using Microsoft.Extensions.Options; using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Install; using Umbraco.Core.IO; -using Umbraco.Web.PublishedCache; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; -using Umbraco.Core.Hosting; -namespace Umbraco.Web.Install +namespace Umbraco.Infrastructure.Install { + /// public class FilePermissionHelper : IFilePermissionHelper { // ensure that these directories exist and Umbraco can write to them @@ -23,40 +26,54 @@ namespace Umbraco.Web.Install private readonly string[] _permissionFiles = Array.Empty(); private readonly GlobalSettings _globalSettings; private readonly IIOHelper _ioHelper; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IPublishedSnapshotService _publishedSnapshotService; + private string _basePath; - public FilePermissionHelper(IOptions globalSettings, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IPublishedSnapshotService publishedSnapshotService) + /// + /// Initializes a new instance of the class. + /// + public FilePermissionHelper(IOptions globalSettings, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment) { _globalSettings = globalSettings.Value; _ioHelper = ioHelper; - _hostingEnvironment = hostingEnvironment; - _publishedSnapshotService = publishedSnapshotService; - _permissionDirs = new[] { _globalSettings.UmbracoCssPath, Constants.SystemDirectories.Config, Constants.SystemDirectories.Data, _globalSettings.UmbracoMediaPath, Constants.SystemDirectories.Preview }; - _packagesPermissionsDirs = new[] { Constants.SystemDirectories.Bin, _globalSettings.UmbracoPath, Constants.SystemDirectories.Packages }; + _basePath = hostingEnvironment.MapPathContentRoot("/"); + _permissionDirs = new[] + { + hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), + hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config), + hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data), + hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath), + hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Preview) + }; + _packagesPermissionsDirs = new[] + { + hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Bin), + hostingEnvironment.MapPathContentRoot(_globalSettings.UmbracoPath), + hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoPath), + hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages) + }; } - public bool RunFilePermissionTestSuite(out Dictionary> report) + /// + public bool RunFilePermissionTestSuite(out Dictionary> report) { - report = new Dictionary>(); + report = new Dictionary>(); - if (EnsureDirectories(_permissionDirs, out var errors) == false) - report["Folder creation failed"] = errors.ToList(); + EnsureDirectories(_permissionDirs, out IEnumerable errors); + report[FilePermissionTest.FolderCreation] = errors.ToList(); - if (EnsureDirectories(_packagesPermissionsDirs, out errors) == false) - report["File writing for packages failed"] = errors.ToList(); + EnsureDirectories(_packagesPermissionsDirs, out errors); + report[FilePermissionTest.FileWritingForPackages] = errors.ToList(); - if (EnsureFiles(_permissionFiles, out errors) == false) - report["File writing failed"] = errors.ToList(); + EnsureFiles(_permissionFiles, out errors); + report[FilePermissionTest.FileWriting] = errors.ToList(); - if (EnsureCanCreateSubDirectory(_globalSettings.UmbracoMediaPath, out errors) == false) - report["Media folder creation failed"] = errors.ToList(); + EnsureCanCreateSubDirectory(_globalSettings.UmbracoMediaPath, out errors); + report[FilePermissionTest.MediaFolderCreation] = errors.ToList(); - return report.Count == 0; + return report.Sum(x => x.Value.Count()) == 0; } - /// - public bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false) + private bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false) { List temp = null; var success = true; @@ -65,10 +82,17 @@ namespace Umbraco.Web.Install // we don't want to create/ship unnecessary directories, so // here we just ensure we can access the directory, not create it var tryAccess = TryAccessDirectory(dir, !writeCausesRestart); - if (tryAccess) continue; + if (tryAccess) + { + continue; + } - if (temp == null) temp = new List(); - temp.Add(dir); + if (temp == null) + { + temp = new List(); + } + + temp.Add(dir.TrimStart(_basePath)); success = false; } @@ -76,7 +100,7 @@ namespace Umbraco.Web.Install return success; } - public bool EnsureFiles(string[] files, out IEnumerable errors) + private bool EnsureFiles(string[] files, out IEnumerable errors) { List temp = null; var success = true; @@ -93,7 +117,7 @@ namespace Umbraco.Web.Install temp = new List(); } - temp.Add(file); + temp.Add(file.TrimStart(_basePath)); success = false; } @@ -101,21 +125,26 @@ namespace Umbraco.Web.Install return success; } - public bool EnsureCanCreateSubDirectory(string dir, out IEnumerable errors) - { - return EnsureCanCreateSubDirectories(new[] { dir }, out errors); - } + private bool EnsureCanCreateSubDirectory(string dir, out IEnumerable errors) + => EnsureCanCreateSubDirectories(new[] { dir }, out errors); - public bool EnsureCanCreateSubDirectories(IEnumerable dirs, out IEnumerable errors) + private bool EnsureCanCreateSubDirectories(IEnumerable dirs, out IEnumerable errors) { List temp = null; var success = true; foreach (var dir in dirs) { var canCreate = TryCreateSubDirectory(dir); - if (canCreate) continue; + if (canCreate) + { + continue; + } + + if (temp == null) + { + temp = new List(); + } - if (temp == null) temp = new List(); temp.Add(dir); success = false; } @@ -131,7 +160,7 @@ namespace Umbraco.Web.Install { try { - var path = _hostingEnvironment.MapPathContentRoot(dir + "/" + _ioHelper.CreateRandomFileName()); + var path = Path.Combine(dir, _ioHelper.CreateRandomFileName()); Directory.CreateDirectory(path); Directory.Delete(path); return true; @@ -150,14 +179,14 @@ namespace Umbraco.Web.Install // use the ACL APIs to avoid creating files // // if the directory does not exist, do nothing & success - public bool TryAccessDirectory(string dir, bool canWrite) + private bool TryAccessDirectory(string dirPath, bool canWrite) { try { - var dirPath = _hostingEnvironment.MapPathContentRoot(dir); - if (Directory.Exists(dirPath) == false) + { return true; + } if (canWrite) { @@ -166,10 +195,8 @@ namespace Umbraco.Web.Install File.Delete(filePath); return true; } - else - { - return HasWritePermission(dirPath); - } + + return HasWritePermission(dirPath); } catch { @@ -182,14 +209,11 @@ namespace Umbraco.Web.Install var writeAllow = false; var writeDeny = false; var accessControlList = new DirectorySecurity(path, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); - if (accessControlList == null) - return false; + AuthorizationRuleCollection accessRules; try { accessRules = accessControlList.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)); - if (accessRules == null) - return false; } catch (Exception) { @@ -202,12 +226,18 @@ namespace Umbraco.Web.Install foreach (FileSystemAccessRule rule in accessRules) { if ((FileSystemRights.Write & rule.FileSystemRights) != FileSystemRights.Write) + { continue; + } if (rule.AccessControlType == AccessControlType.Allow) + { writeAllow = true; + } else if (rule.AccessControlType == AccessControlType.Deny) + { writeDeny = true; + } } return writeAllow && writeDeny == false; @@ -219,7 +249,7 @@ namespace Umbraco.Web.Install { try { - var path = _hostingEnvironment.MapPathContentRoot(file); + var path = file; File.AppendText(path).Close(); return true; } diff --git a/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs b/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs index 4b352190b0..c8be2fc5a9 100644 --- a/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs +++ b/src/Umbraco.Infrastructure/Install/InstallStepCollection.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Install.InstallSteps; using Umbraco.Web.Install.InstallSteps; using Umbraco.Web.Install.Models; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs index 10349a4f9e..1c20c384df 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using NUnit.Framework; -using Umbraco.Core.HealthCheck; -using Umbraco.Infrastructure.HealthCheck; +using Umbraco.Core.HealthChecks; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks { @@ -26,7 +26,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new NotImplementedException(); - public override IEnumerable GetStatus() => + public override async Task> GetStatus() => new List { new HealthCheckStatus(_message) @@ -62,18 +62,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks { } - public override IEnumerable GetStatus() => throw new Exception("Check threw exception"); + public override async Task> GetStatus() => throw new Exception("Check threw exception"); } [Test] - public void HealthCheckResults_WithSuccessfulChecks_ReturnsCorrectResultDescription() + public async Task HealthCheckResults_WithSuccessfulChecks_ReturnsCorrectResultDescription() { var checks = new List { new StubHealthCheck1(StatusResultType.Success, "First check was successful"), new StubHealthCheck2(StatusResultType.Success, "Second check was successful"), }; - var results = new HealthCheckResults(checks); + var results = await HealthCheckResults.Create(checks); Assert.IsTrue(results.AllChecksSuccessful); @@ -83,14 +83,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks } [Test] - public void HealthCheckResults_WithFailingChecks_ReturnsCorrectResultDescription() + public async Task HealthCheckResults_WithFailingChecks_ReturnsCorrectResultDescription() { var checks = new List { new StubHealthCheck1(StatusResultType.Success, "First check was successful"), new StubHealthCheck2(StatusResultType.Error, "Second check was not successful"), }; - var results = new HealthCheckResults(checks); + var results = await HealthCheckResults.Create(checks); Assert.IsFalse(results.AllChecksSuccessful); @@ -100,7 +100,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks } [Test] - public void HealthCheckResults_WithErroringCheck_ReturnsCorrectResultDescription() + public async Task HealthCheckResults_WithErroringCheck_ReturnsCorrectResultDescription() { var checks = new List { @@ -108,7 +108,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks new StubHealthCheck3(StatusResultType.Error, "Third check was not successful"), new StubHealthCheck2(StatusResultType.Error, "Second check was not successful"), }; - var results = new HealthCheckResults(checks); + var results = await HealthCheckResults.Create(checks); Assert.IsFalse(results.AllChecksSuccessful); @@ -119,28 +119,28 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks } [Test] - public void HealthCheckResults_WithSummaryVerbosity_ReturnsCorrectResultDescription() + public async Task HealthCheckResults_WithSummaryVerbosity_ReturnsCorrectResultDescription() { var checks = new List { new StubHealthCheck1(StatusResultType.Success, "First check was successful"), new StubHealthCheck2(StatusResultType.Success, "Second check was successful"), }; - var results = new HealthCheckResults(checks); + var results = await HealthCheckResults.Create(checks); var resultAsMarkdown = results.ResultsAsMarkDown(HealthCheckNotificationVerbosity.Summary); Assert.IsTrue(resultAsMarkdown.IndexOf("Result: 'Success'" + Environment.NewLine) > -1); } [Test] - public void HealthCheckResults_WithDetailedVerbosity_ReturnsCorrectResultDescription() + public async Task HealthCheckResults_WithDetailedVerbosity_ReturnsCorrectResultDescription() { var checks = new List { new StubHealthCheck1(StatusResultType.Success, "First check was successful"), new StubHealthCheck2(StatusResultType.Success, "Second check was successful"), }; - var results = new HealthCheckResults(checks); + var results = await HealthCheckResults.Create(checks); var resultAsMarkdown = results.ResultsAsMarkDown(HealthCheckNotificationVerbosity.Detailed); Assert.IsFalse(resultAsMarkdown.IndexOf("Result: 'Success'" + Environment.NewLine) > -1); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs index d5bd10fe3c..325aa4f74b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs @@ -12,14 +12,12 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.HealthCheck; +using Umbraco.Core.HealthChecks; +using Umbraco.Core.HealthChecks.NotificationMethods; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; -using Umbraco.Infrastructure.HealthCheck; using Umbraco.Infrastructure.HostedServices; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.HealthCheck.NotificationMethods; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { @@ -186,7 +184,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => new HealthCheckStatus("Check message"); - public override IEnumerable GetStatus() => Enumerable.Empty(); + public override async Task> GetStatus() => Enumerable.Empty(); } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 066f6e3463..92c6d887ff 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Media; using Umbraco.Core.WebAssets; using Umbraco.Extensions; -using Umbraco.Web.BackOffice.HealthCheck; +using Umbraco.Web.BackOffice.HealthChecks; using Umbraco.Web.BackOffice.Profiling; using Umbraco.Web.BackOffice.PropertyEditors; using Umbraco.Web.BackOffice.Routing; diff --git a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs similarity index 67% rename from src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs rename to src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs index 002cfe4d7b..8bd86eb106 100644 --- a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs @@ -1,19 +1,22 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.HealthCheck; +using Umbraco.Core.HealthChecks; using Umbraco.Web.BackOffice.Controllers; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; -using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; -namespace Umbraco.Web.BackOffice.HealthCheck +namespace Umbraco.Web.BackOffice.HealthChecks { /// /// The API controller used to display the health check info and execute any actions @@ -26,12 +29,15 @@ namespace Umbraco.Web.BackOffice.HealthCheck private readonly IList _disabledCheckIds; private readonly ILogger _logger; + /// + /// Initializes a new instance of the class. + /// public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings) { _checks = checks ?? throw new ArgumentNullException(nameof(checks)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - var healthCheckConfig = healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings)); + HealthChecksSettings healthCheckConfig = healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings)); _disabledCheckIds = healthCheckConfig.DisabledChecks .Select(x => x.Id) .ToList(); @@ -43,12 +49,12 @@ namespace Umbraco.Web.BackOffice.HealthCheck /// Returns a collection of anonymous objects representing each group. public object GetAllHealthChecks() { - var groups = _checks + IOrderedEnumerable> groups = _checks .Where(x => _disabledCheckIds.Contains(x.Id) == false) .GroupBy(x => x.Group) .OrderBy(x => x.Key); var healthCheckGroups = new List(); - foreach (var healthCheckGroup in groups) + foreach (IGrouping healthCheckGroup in groups) { var hcGroup = new HealthCheckGroup { @@ -63,15 +69,18 @@ namespace Umbraco.Web.BackOffice.HealthCheck return healthCheckGroups; } + /// + /// Gets the status of the HealthCheck with the specified id. + /// [HttpGet] - public object GetStatus(Guid id) + public async Task GetStatus(Guid id) { - var check = GetCheckById(id); + HealthCheck check = GetCheckById(id); try { - //Core.Logging.LogHelper.Debug("Running health check: " + check.Name); - return check.GetStatus(); + _logger.LogDebug("Running health check: " + check.Name); + return await check.GetStatus(); } catch (Exception ex) { @@ -80,20 +89,26 @@ namespace Umbraco.Web.BackOffice.HealthCheck } } + /// + /// Executes a given action from a HealthCheck. + /// [HttpPost] public HealthCheckStatus ExecuteAction(HealthCheckAction action) { - var check = GetCheckById(action.HealthCheckId); + HealthCheck check = GetCheckById(action.HealthCheckId); return check.ExecuteAction(action); } - private Core.HealthCheck.HealthCheck GetCheckById(Guid id) + private HealthCheck GetCheckById(Guid id) { - var check = _checks + HealthCheck check = _checks .Where(x => _disabledCheckIds.Contains(x.Id) == false) .FirstOrDefault(x => x.Id == id); - if (check == null) throw new InvalidOperationException($"No health check found with id {id}"); + if (check == null) + { + throw new InvalidOperationException($"No health check found with id {id}"); + } return check; } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index 6b06db315c..64e5e92e38 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -23,8 +23,6 @@ namespace Umbraco.Web.Common.AspNetCore SiteName = webHostEnvironment.ApplicationName; ApplicationId = AppDomain.CurrentDomain.Id.ToString(); ApplicationPhysicalPath = webHostEnvironment.ContentRootPath; - - IISVersion = new Version(0, 0); // TODO not necessary IIS } /// diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 452b5c2071..dea27a0d69 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1842,7 +1842,8 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "dev": true, + "optional": true }, "base64id": { "version": "1.0.0", @@ -2051,7 +2052,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "dev": true, + "optional": true }, "got": { "version": "8.3.2", @@ -2129,6 +2131,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", "integrity": "sha1-2N0ZeVldLcATnh/ka4tkbLPN8Dg=", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -2170,6 +2173,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -2179,13 +2183,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2201,6 +2207,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2341,6 +2348,7 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "optional": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -2366,7 +2374,8 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-equal": { "version": "1.0.0", @@ -2563,6 +2572,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", "integrity": "sha1-bDygcfwZRyCIPC3F2psHS/x+npU=", "dev": true, + "optional": true, "requires": { "get-proxy": "^2.0.0", "isurl": "^1.0.0-alpha5", @@ -2994,7 +3004,8 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "optional": true }, "component-bind": { "version": "1.0.0", @@ -3086,6 +3097,7 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=", "dev": true, + "optional": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -3141,6 +3153,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", "dev": true, + "optional": true, "requires": { "safe-buffer": "5.1.2" } @@ -3582,6 +3595,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -3598,6 +3612,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, + "optional": true, "requires": { "pify": "^3.0.0" }, @@ -3606,7 +3621,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } } @@ -3617,6 +3633,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, + "optional": true, "requires": { "mimic-response": "^1.0.0" } @@ -3626,6 +3643,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", "integrity": "sha1-cYy9P8sWIJcW5womuE57pFkuWvE=", "dev": true, + "optional": true, "requires": { "file-type": "^5.2.0", "is-stream": "^1.1.0", @@ -3636,7 +3654,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3645,6 +3664,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", "integrity": "sha1-MIKluIDqQEOBY0nzeLVsUWvho5s=", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", @@ -3657,7 +3677,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", "integrity": "sha1-5QzXXTVv/tTjBtxPW89Sp5kDqRk=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3666,6 +3687,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", "integrity": "sha1-wJvDXE0R894J8tLaU+neI+fOHu4=", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", @@ -3676,7 +3698,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3685,6 +3708,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", "dev": true, + "optional": true, "requires": { "file-type": "^3.8.0", "get-stream": "^2.2.0", @@ -3696,13 +3720,15 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" @@ -3712,7 +3738,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4000,7 +4027,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4017,7 +4045,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "dev": true, + "optional": true }, "duplexify": { "version": "3.7.1", @@ -4662,6 +4691,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, + "optional": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -4803,6 +4833,7 @@ "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha1-C5jmTtgvWs8PKTG6v2khLvUt3Tc=", "dev": true, + "optional": true, "requires": { "mime-db": "^1.28.0" } @@ -4812,6 +4843,7 @@ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha1-cHgZgdGD7hXROZPIgiBFxQbI8KY=", "dev": true, + "optional": true, "requires": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -5049,6 +5081,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -5087,13 +5120,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", "integrity": "sha1-iPr0lfsbR6v9YSMAACoWIoxnfuk=", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^2.0.0", "strip-outer": "^1.0.0", @@ -5442,7 +5477,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true + "dev": true, + "optional": true }, "fs-mkdirp-stream": { "version": "1.0.0", @@ -5489,7 +5525,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5510,12 +5547,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5530,17 +5569,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5657,7 +5699,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5669,6 +5712,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5683,6 +5727,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5690,12 +5735,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5714,6 +5761,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5794,7 +5842,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5806,6 +5855,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5891,7 +5941,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5927,6 +5978,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5946,6 +5998,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5989,12 +6042,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -6021,6 +6076,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", "integrity": "sha1-NJ8rTZHUTE1NTpy6KtkBQ/rF75M=", "dev": true, + "optional": true, "requires": { "npm-conf": "^1.1.0" } @@ -6029,13 +6085,15 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, + "optional": true, "requires": { "pump": "^3.0.0" }, @@ -6045,6 +6103,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -6157,7 +6216,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "optional": true }, "pump": { "version": "3.0.0", @@ -7262,7 +7322,8 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", "integrity": "sha1-FAn5i8ACR9pF2mfO4KNvKC/yZFU=", - "dev": true + "dev": true, + "optional": true }, "has-symbols": { "version": "1.0.0", @@ -7275,6 +7336,7 @@ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha1-oEWrOD17SyASoAFIqwql8pAETU0=", "dev": true, + "optional": true, "requires": { "has-symbol-support-x": "^1.4.1" } @@ -7480,7 +7542,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "dev": true, + "optional": true }, "ignore": { "version": "4.0.6", @@ -7620,7 +7683,8 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "optional": true }, "svgo": { "version": "1.3.2", @@ -7692,6 +7756,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, + "optional": true, "requires": { "repeating": "^2.0.0" } @@ -8018,7 +8083,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true + "dev": true, + "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -8068,7 +8134,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true + "dev": true, + "optional": true }, "is-negated-glob": { "version": "1.0.0", @@ -8106,13 +8173,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true + "dev": true, + "optional": true }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "dev": true, + "optional": true }, "is-plain-object": { "version": "2.0.4", @@ -8182,13 +8251,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", "integrity": "sha1-13hIi9CkZmo76KFIK58rqv7eqLQ=", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "dev": true, + "optional": true }, "is-svg": { "version": "3.0.0", @@ -8283,6 +8354,7 @@ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", "integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=", "dev": true, + "optional": true, "requires": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -9178,7 +9250,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -9248,7 +9321,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", @@ -9416,7 +9490,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha1-SSNTiHju9CBjy4o+OweYeBSHqxs=", - "dev": true + "dev": true, + "optional": true }, "minimatch": { "version": "3.0.4", @@ -12763,6 +12838,7 @@ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", "integrity": "sha1-JWzEe9DiGMJZxOlVC/QTvCGSr/k=", "dev": true, + "optional": true, "requires": { "config-chain": "^1.1.11", "pify": "^3.0.0" @@ -12772,7 +12848,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -12781,6 +12858,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, + "optional": true, "requires": { "path-key": "^2.0.0" } @@ -13149,7 +13227,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-is-promise": { "version": "1.1.0", @@ -13186,6 +13265,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -13376,7 +13456,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "dev": true, + "optional": true }, "performance-now": { "version": "2.1.0", @@ -13883,7 +13964,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true + "dev": true, + "optional": true }, "prr": { "version": "1.0.1", @@ -14241,6 +14323,7 @@ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, + "optional": true, "requires": { "is-finite": "^1.0.0" } @@ -14595,6 +14678,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", "dev": true, + "optional": true, "requires": { "commander": "^2.8.1" } @@ -14989,6 +15073,7 @@ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, + "optional": true, "requires": { "is-plain-obj": "^1.0.0" } @@ -14998,6 +15083,7 @@ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", "dev": true, + "optional": true, "requires": { "sort-keys": "^1.0.0" } @@ -15327,6 +15413,7 @@ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", "integrity": "sha1-SYdzYmT8NEzyD2w0rKnRPR1O1sU=", "dev": true, + "optional": true, "requires": { "is-natural-number": "^4.0.1" } @@ -15335,7 +15422,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "dev": true, + "optional": true }, "strip-final-newline": { "version": "2.0.0", @@ -15365,6 +15453,7 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15490,6 +15579,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15504,13 +15594,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15526,6 +15618,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15536,13 +15629,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true + "dev": true, + "optional": true }, "tempfile": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", "dev": true, + "optional": true, "requires": { "temp-dir": "^1.0.0", "uuid": "^3.0.1" @@ -15637,7 +15732,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15694,7 +15790,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -15796,6 +15893,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15931,6 +16029,7 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "optional": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -16139,7 +16238,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true + "dev": true, + "optional": true }, "use": { "version": "3.1.1", @@ -16633,6 +16733,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html index 0c78e211dc..366f8b9d19 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html @@ -1,25 +1,26 @@ 

Your permission settings are not ready for Umbraco

-

+

In order to run Umbraco, you'll need to update your permission settings. - Detailed information about the correct file and folder permissions for Umbraco can be found - here. + Detailed information about the correct file and folder permissions for Umbraco can be found + here.

The following report list the permissions that are currently failing. Once the permissions are fixed press the 'Go back' button to restart the installation.

- +
  • {{category}}

      +
    • {{item}}
- +

diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js index f3446663e1..9810ff9aff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js @@ -73,7 +73,7 @@ healthCheckResource.getStatus(check.id) .then(function (response) { check.loading = false; - check.status = response; + check.status = response; }); } diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index eaf2534341..bfb32d1e0b 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -22,7 +22,6 @@ - diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml index d7dccbc08c..f0f292bae5 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml @@ -751,6 +751,7 @@ Ja Mappe Søgeresultater + Læs mere Sortér Afslut sortering Eksempel @@ -1877,4 +1878,10 @@ Mange hilsner fra Umbraco robotten Se udgivet version Forbliv i forhåndsvisning + + Mappeoprettelse + Filskrivning for pakker + Filskrivning + Medie mappeoprettelse + diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml index c7b974e208..0792c1b70f 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml @@ -740,6 +740,7 @@ One moment please... Previous Properties + Read more Rebuild Email to receive form data Recycle Bin @@ -2551,4 +2552,10 @@ To manage your website, simply open the Umbraco back office and start adding con View published version Stay in preview mode + + Folder creation + File writing for packages + File writing + Media folder creation + diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml index 7a0afb0a69..dc5664213d 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml @@ -749,6 +749,7 @@ One moment please... Previous Properties + Read more Rebuild Email to receive form data Recycle Bin @@ -2130,13 +2131,13 @@ To manage your website, simply open the Umbraco back office and start adding con Your website's SSL certificate is expiring in %0% days. Error pinging the URL %0% - '%1%' You are currently %0% viewing the site using the HTTPS scheme. - The appSetting 'Umbraco.Core.UseHttps' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. - The appSetting 'Umbraco.Core.UseHttps' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. - Could not update the 'Umbraco.Core.UseHttps' setting in your web.config file. Error: %0% + The configuration value 'Umbraco:CMS:Global:UseHttps' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The configuration value 'Umbraco:CMS:Global:UseHttps' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'Umbraco:CMS:Global:UseHttps' setting in your web.config file. Error: %0% Enable HTTPS Sets umbracoSSL setting to true in the appSettings of the web.config file. - The appSetting 'Umbraco.Core.UseHttps' is now set to 'true' in your web.config file, your cookies will be marked as secure. + The configuration value 'Umbraco:CMS:Global:UseHttps' is now set to 'true' in your web.config file, your cookies will be marked as secure. Fix Cannot fix a check with a value comparison type of 'ShouldNotEqual'. Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. @@ -2186,10 +2187,10 @@ To manage your website, simply open the Umbraco back office and start adding con --> %0%.]]> No headers revealing information about the website technology were found. - In the Web.config file, system.net/mailsettings could not be found. - In the Web.config file system.net/mailsettings section, the host is not configured. + The 'Umbraco:CMS:Global:Smtp' configuration could not be found. + The 'Umbraco:CMS:Global:Smtp:Host' configuration could not be found. SMTP settings are configured correctly and the service is operating as expected. - The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the configuration 'Umbraco:CMS:Global:Smtp' are correct. %0%.]]> %0%.]]>

Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

%2%]]>
@@ -2572,4 +2573,10 @@ To manage your website, simply open the Umbraco back office and start adding con View published version Stay in preview mode + + Folder creation + File writing for packages + File writing + Media folder creation + diff --git a/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs b/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs index ec9d66859e..6e0bca8af5 100644 --- a/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs +++ b/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs @@ -29,7 +29,6 @@ namespace Umbraco.Web.Hosting ApplicationVirtualPath = _hostingSettings.ApplicationVirtualPath?.EnsureStartsWith('/') ?? HostingEnvironment.ApplicationVirtualPath?.EnsureStartsWith("/") ?? "/"; - IISVersion = HttpRuntime.IISVersion; } public string SiteName { get; } @@ -42,8 +41,6 @@ namespace Umbraco.Web.Hosting /// public bool IsHosted => (HttpContext.Current != null || HostingEnvironment.IsHosted); - public Version IISVersion { get; } - public string MapPathWebRoot(string path) { if (HostingEnvironment.IsHosted) diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 8f698db995..b99847422e 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -3,33 +3,24 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Dictionary; +using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using Umbraco.Core.HealthChecks; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.HealthCheck; -using Umbraco.Core.Hosting; using Umbraco.Core.Mapping; -using Umbraco.Core.Templates; -using Umbraco.Net; -using Umbraco.Core.PackageActions; -using Umbraco.Core.Packaging; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Runtime; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Scoping; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Core.Sync; +using Umbraco.Core.Templates; using Umbraco.Core.WebAssets; +using Umbraco.Net; using Umbraco.Web.Actions; using Umbraco.Web.Cache; using Umbraco.Web.Editors; -using Umbraco.Web.HealthCheck; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing;