();
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/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs
new file mode 100644
index 0000000000..e134dcd413
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.Options;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.HealthChecks.Checks.LiveEnvironment
+{
+ ///
+ /// Health check for the configuration of debug-flag.
+ ///
+ [HealthCheck(
+ "61214FF3-FC57-4B31-B5CF-1D095C977D6D",
+ "Debug Compilation Mode",
+ Description = "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.",
+ Group = "Live Environment")]
+ public class CompilationDebugCheck : AbstractSettingsCheck
+ {
+ private readonly IOptionsMonitor _hostingSettings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CompilationDebugCheck(ILocalizedTextService textService, IOptionsMonitor hostingSettings)
+ : base(textService) =>
+ _hostingSettings = hostingSettings;
+
+ ///
+ public override string ItemPath => Constants.Configuration.ConfigHostingDebug;
+
+ ///
+ public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.LiveEnvironment.CompilationDebugCheck;
+
+ ///
+ public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual;
+
+ ///
+ public override IEnumerable Values => new List
+ {
+ new AcceptableConfiguration
+ {
+ IsRecommended = true,
+ Value = bool.FalseString.ToLower()
+ }
+ };
+
+ ///
+ public override string CurrentValue => _hostingSettings.CurrentValue.Debug.ToString();
+
+ ///
+ public override string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck/compilationDebugCheckSuccessMessage");
+
+ ///
+ public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck/compilationDebugCheckErrorMessage");
+ }
+}
diff --git a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs
new file mode 100644
index 0000000000..0bb7c56486
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs
@@ -0,0 +1,101 @@
+// 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.Install;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.HealthChecks.Checks.Permissions
+{
+ ///
+ /// Health check for the folder and file permissions.
+ ///
+ [HealthCheck(
+ "53DBA282-4A79-4B67-B958-B29EC40FCC23",
+ "Folder & File Permissions",
+ Description = "Checks that the web server folder and file permissions are set correctly for Umbraco to run.",
+ Group = "Permissions")]
+ public class FolderAndFilePermissionsCheck : HealthCheck
+ {
+ private readonly ILocalizedTextService _textService;
+ private readonly IFilePermissionHelper _filePermissionHelper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public FolderAndFilePermissionsCheck(
+ ILocalizedTextService textService,
+ IFilePermissionHelper filePermissionHelper)
+ {
+ _textService = textService;
+ _filePermissionHelper = filePermissionHelper;
+ }
+
+ ///
+ /// Get the status for this health check
+ ///
+ public override Task> GetStatus()
+ {
+ _filePermissionHelper.RunFilePermissionTestSuite(out Dictionary> errors);
+
+ return Task.FromResult(errors.Select(x => new HealthCheckStatus(GetMessage(x))
+ {
+ ResultType = x.Value.Any() ? StatusResultType.Error : StatusResultType.Success,
+ ReadMoreLink = GetReadMoreLink(x),
+ Description = GetErrorDescription(x)
+ }));
+ }
+
+ private string GetErrorDescription(KeyValuePair> status)
+ {
+ if (!status.Value.Any())
+ {
+ return null;
+ }
+
+ var sb = new StringBuilder("The following failed:");
+
+ sb.AppendLine("");
+ foreach (var error in status.Value)
+ {
+ sb.Append("- " + error + "
");
+ }
+
+ sb.AppendLine("
");
+ return sb.ToString();
+ }
+
+ private string GetMessage(KeyValuePair> status)
+ => _textService.Localize("permissions", status.Key);
+
+ private string GetReadMoreLink(KeyValuePair> status)
+ {
+ if (!status.Value.Any())
+ {
+ return null;
+ }
+
+ switch (status.Key)
+ {
+ case FilePermissionTest.FileWriting:
+ return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWriting;
+ case FilePermissionTest.FolderCreation:
+ return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FolderCreation;
+ case FilePermissionTest.FileWritingForPackages:
+ return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWritingForPackages;
+ case FilePermissionTest.MediaFolderCreation:
+ return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.MediaFolderCreation;
+ default: return null;
+ }
+ }
+
+ ///
+ /// Executes the action and returns it's status
+ ///
+ public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions");
+ }
+}
diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs
new file mode 100644
index 0000000000..d8869e12fa
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs
@@ -0,0 +1,143 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.HealthChecks.Checks.Security
+{
+ ///
+ /// Provides a base class for health checks of http header values.
+ ///
+ public abstract class BaseHttpHeaderCheck : HealthCheck
+ {
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly string _header;
+ private readonly string _value;
+ private readonly string _localizedTextPrefix;
+ private readonly bool _metaTagOptionAvailable;
+ private static HttpClient s_httpClient;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected BaseHttpHeaderCheck(
+ IHostingEnvironment hostingEnvironment,
+ ILocalizedTextService textService,
+ string header,
+ string value,
+ string localizedTextPrefix,
+ bool metaTagOptionAvailable)
+ {
+ LocalizedTextService = textService ?? throw new ArgumentNullException(nameof(textService));
+ _hostingEnvironment = hostingEnvironment;
+ _header = header;
+ _value = value;
+ _localizedTextPrefix = localizedTextPrefix;
+ _metaTagOptionAvailable = metaTagOptionAvailable;
+ }
+
+ private static HttpClient HttpClient => s_httpClient ??= new HttpClient();
+
+
+ ///
+ /// Gets the localized text service.
+ ///
+ protected ILocalizedTextService LocalizedTextService { get; }
+
+ ///
+ /// Gets a link to an external read more page.
+ ///
+ protected abstract string ReadMoreLink { get; }
+
+ ///
+ /// Get the status for this health check
+ ///
+ public override async Task> GetStatus() =>
+ await Task.WhenAll(CheckForHeader());
+
+ ///
+ /// Executes the action and returns it's status
+ ///
+ public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
+ => throw new InvalidOperationException("HTTP Header action requested is either not executable or does not exist");
+
+ ///
+ /// The actual health check method.
+ ///
+ protected async Task CheckForHeader()
+ {
+ string message;
+ var success = false;
+
+ // Access the site home page and check for the click-jack protection header or meta tag
+ Uri url = _hostingEnvironment.ApplicationMainUrl;
+
+ try
+ {
+ using HttpResponseMessage response = await HttpClient.GetAsync(url);
+
+ // Check first for header
+ success = HasMatchingHeader(response.Headers.Select(x => x.Key));
+
+ // If not found, and available, check for meta-tag
+ if (success == false && _metaTagOptionAvailable)
+ {
+ success = await DoMetaTagsContainKeyForHeader(response);
+ }
+
+ message = success
+ ? LocalizedTextService.Localize($"healthcheck/{_localizedTextPrefix}CheckHeaderFound")
+ : LocalizedTextService.Localize($"healthcheck/{_localizedTextPrefix}CheckHeaderNotFound");
+ }
+ catch (Exception ex)
+ {
+ message = LocalizedTextService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url.ToString(), ex.Message });
+ }
+
+ return
+ new HealthCheckStatus(message)
+ {
+ ResultType = success ? StatusResultType.Success : StatusResultType.Error,
+ ReadMoreLink = success ? null : ReadMoreLink
+ };
+ }
+
+ private bool HasMatchingHeader(IEnumerable headerKeys)
+ => headerKeys.Contains(_header, StringComparer.InvariantCultureIgnoreCase);
+
+ private async Task DoMetaTagsContainKeyForHeader(HttpResponseMessage response)
+ {
+ using (Stream stream = await response.Content.ReadAsStreamAsync())
+ {
+ if (stream == null)
+ {
+ return false;
+ }
+
+ using (var reader = new StreamReader(stream))
+ {
+ var html = reader.ReadToEnd();
+ Dictionary metaTags = ParseMetaTags(html);
+ return HasMatchingHeader(metaTags.Keys);
+ }
+ }
+ }
+
+ private static Dictionary ParseMetaTags(string html)
+ {
+ var regex = new Regex("()
+ .ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs
new file mode 100644
index 0000000000..6bb92e4176
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Services;
+
+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",
+ Description = "Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.",
+ Group = "Security")]
+ public class ClickJackingCheck : BaseHttpHeaderCheck
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ClickJackingCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService)
+ : base(hostingEnvironment, 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 51%
rename from src/Umbraco.Core/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs
rename to src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs
index 9cf1127bb0..000c14f93d 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.Hosting;
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",
@@ -15,67 +22,64 @@ namespace Umbraco.Core.HealthCheck.Checks.Security
public class ExcessiveHeadersCheck : HealthCheck
{
private readonly ILocalizedTextService _textService;
- private readonly IRequestAccessor _requestAccessor;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private static HttpClient s_httpClient;
- public ExcessiveHeadersCheck(ILocalizedTextService textService, IRequestAccessor requestAccessor)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ExcessiveHeadersCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment)
{
_textService = textService;
- _requestAccessor = requestAccessor;
+ _hostingEnvironment = hostingEnvironment;
}
+ 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 = _hostingEnvironment.ApplicationMainUrl;
// 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/HealthChecks/Checks/Security/HstsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs
new file mode 100644
index 0000000000..828d2d2470
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.HealthChecks.Checks.Security
+{
+ ///
+ /// Health check for the recommended production setup regarding the Strict-Transport-Security header.
+ ///
+ [HealthCheck(
+ "E2048C48-21C5-4BE1-A80B-8062162DF124",
+ "Cookie hijacking and protocol downgrade attacks Protection (Strict-Transport-Security Header (HSTS))",
+ Description = "Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS).",
+ Group = "Security")]
+ public class HstsCheck : BaseHttpHeaderCheck
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The check is mostly based on the instructions in the OWASP CheatSheet
+ /// (https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md)
+ /// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/)
+ /// If you want do to it perfectly, you have to submit it https://hstspreload.org/,
+ /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites.
+ ///
+ public HstsCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService)
+ : base(hostingEnvironment, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true)
+ {
+ }
+
+ ///
+ protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.HstsCheck;
+ }
+}
diff --git a/src/Umbraco.Core/HealthCheck/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs
similarity index 52%
rename from src/Umbraco.Core/HealthCheck/Checks/Security/HttpsCheck.cs
rename to src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs
index 6bba41b7d5..5916c48b82 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.Web;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Services;
-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",
@@ -21,82 +27,86 @@ 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;
+ private readonly IHostingEnvironment _hostingEnvironment;
- 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)
+ IHostingEnvironment hostingEnvironment)
{
_textService = textService;
_globalSettings = globalSettings;
- _requestAccessor = requestAccessor;
- _logger = logger;
+ _hostingEnvironment = hostingEnvironment;
}
+ 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 url = _hostingEnvironment.ApplicationMainUrl.ToString().Replace("http:", "https:");
+
+ 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 = _hostingEnvironment.ApplicationMainUrl;
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();
+ Uri uri = _hostingEnvironment.ApplicationMainUrl;
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/HealthChecks/Checks/Security/NoSniffCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs
new file mode 100644
index 0000000000..0722f4cf64
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Services;
+
+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",
+ Description = "Checks that your site contains a header used to protect against MIME sniffing vulnerabilities.",
+ Group = "Security")]
+ public class NoSniffCheck : BaseHttpHeaderCheck
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NoSniffCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService)
+ : base(hostingEnvironment, textService, "X-Content-Type-Options", "nosniff", "noSniff", false)
+ {
+ }
+
+ ///
+ protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.NoSniffCheck;
+ }
+}
diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs
new file mode 100644
index 0000000000..5a1973d05b
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.HealthChecks.Checks.Security
+{
+ ///
+ /// Health check for the recommended production setup regarding the X-XSS-Protection header.
+ ///
+ [HealthCheck(
+ "F4D2B02E-28C5-4999-8463-05759FA15C3A",
+ "Cross-site scripting Protection (X-XSS-Protection header)",
+ Description = "This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.",
+ Group = "Security")]
+ public class XssProtectionCheck : BaseHttpHeaderCheck
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The check is mostly based on the instructions in the OWASP CheatSheet
+ /// (https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet)
+ /// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/)
+ /// If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/,
+ /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites.
+ ///
+ public XssProtectionCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService)
+ : base(hostingEnvironment, textService, "X-XSS-Protection", "1; mode=block", "xssProtection", true)
+ {
+ }
+
+ ///
+ protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.XssProtectionCheck;
+ }
+}
diff --git a/src/Umbraco.Core/HealthCheck/Checks/Services/SmtpCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs
similarity index 71%
rename from src/Umbraco.Core/HealthCheck/Checks/Services/SmtpCheck.cs
rename to src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs
index 9e1a6f84af..f4e150fbed 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,27 +68,28 @@ 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() });
}
}
- var actions = new List();
return
new HealthCheckStatus(message)
{
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
- 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/HealthChecks/HealthCheckCollection.cs b/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs
new file mode 100644
index 0000000000..88fadee5ec
--- /dev/null
+++ b/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.HealthChecks
+{
+ public class HealthCheckCollection : BuilderCollectionBase
+ {
+ public HealthCheckCollection(IEnumerable items)
+ : base(items)
+ { }
+ }
+}
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 86%
rename from src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
rename to src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs
index ad92886ecd..97ef86d205 100644
--- a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
+++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs
@@ -1,22 +1,19 @@
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.Hosting;
using Umbraco.Core.Mail;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
-using Umbraco.Infrastructure.HealthCheck;
-namespace Umbraco.Web.HealthCheck.NotificationMethods
+namespace Umbraco.Core.HealthChecks.NotificationMethods
{
[HealthCheckNotificationMethod("email")]
public class EmailNotificationMethod : NotificationMethodBase
{
private readonly ILocalizedTextService _textService;
- private readonly IRequestAccessor _requestAccessor;
+ private readonly IHostingEnvironment _hostingEnvironment;
private readonly IEmailSender _emailSender;
private readonly IMarkdownToHtmlConverter _markdownToHtmlConverter;
@@ -24,7 +21,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
public EmailNotificationMethod(
ILocalizedTextService textService,
- IRequestAccessor requestAccessor,
+ IHostingEnvironment hostingEnvironment,
IEmailSender emailSender,
IOptions healthChecksSettings,
IOptions contentSettings,
@@ -41,7 +38,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
RecipientEmail = recipientEmail;
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
- _requestAccessor = requestAccessor;
+ _hostingEnvironment = hostingEnvironment;
_emailSender = emailSender;
_markdownToHtmlConverter = markdownToHtmlConverter;
_contentSettings = contentSettings.Value ?? throw new ArgumentNullException(nameof(contentSettings));
@@ -70,9 +67,9 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
// Include the umbraco Application URL host in the message subject so that
// you can identify the site that these results are for.
- var host = _requestAccessor.GetApplicationUrl();
+ var host = _hostingEnvironment.ApplicationMainUrl?.ToString();
- var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host.ToString() });
+ var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host });
var mailMessage = CreateMailMessage(subject, message);
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..e01435422d 100644
--- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs
+++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs
@@ -31,7 +31,10 @@ namespace Umbraco.Core.Hosting
///
bool IsHosted { get; }
- Version IISVersion { get; }
+ ///
+ /// Gets the main application url.
+ ///
+ Uri ApplicationMainUrl { get; }
///
/// Maps a virtual path to a physical path to the application's web root
@@ -63,5 +66,10 @@ namespace Umbraco.Core.Hosting
/// If virtualPath does not start with ~/ or /
///
string ToAbsolute(string virtualPath);
+
+ ///
+ /// Ensures that the application know its main Url.
+ ///
+ void EnsureApplicationMainUrl(Uri currentApplicationUrl);
}
}
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
public DatabaseServerMessengerNotificationHandler(
IServerMessenger serverMessenger,
- IRequestAccessor requestAccessor,
IUmbracoDatabaseFactory databaseFactory,
IDistributedCacheBinder distributedCacheBinder,
ILogger logger)
{
- _requestAccessor = requestAccessor;
_databaseFactory = databaseFactory;
_distributedCacheBinder = distributedCacheBinder;
_logger = logger;
@@ -38,19 +35,6 @@ namespace Umbraco.Infrastructure.Cache
///
public void Handle(UmbracoApplicationStarting notification)
- {
- // The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services.
- // The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL
- // being available (via IRequestAccessor), which can only be retrieved within an HTTP request (unless it's explicitly configured).
- // Hence we hook up a one-off task on an HTTP request to ensure this is retrieved, which caches the value and makes it available
- // for the hosted services to use when the HTTP request is not available.
- _requestAccessor.RouteAttempt += EnsureApplicationUrlOnce;
- _requestAccessor.EndRequest += EndRequest;
-
- Startup();
- }
-
- private void Startup()
{
if (_databaseFactory.CanConnect == false)
{
@@ -65,28 +49,9 @@ namespace Umbraco.Infrastructure.Cache
}
}
- // TODO: I don't really know or think that the Application Url plays a role anymore with the DB dist cache,
- // this might be really old stuff. I 'think' all this is doing is ensuring that the IRequestAccessor.GetApplicationUrl
- // is definitely called during the first request. If that is still required, that logic doesn't belong here. That logic
- // should be part of it's own service/middleware. There's also TODO notes within IRequestAccessor.GetApplicationUrl directly
- // mentioning that the property doesn't belong on that service either. This should be investigated and resolved in a separate task.
- private void EnsureApplicationUrlOnce(object sender, RoutableAttemptEventArgs e)
- {
- if (e.Outcome == EnsureRoutableOutcome.IsRoutable || e.Outcome == EnsureRoutableOutcome.NotDocumentRequest)
- {
- _requestAccessor.RouteAttempt -= EnsureApplicationUrlOnce;
- EnsureApplicationUrl();
- }
- }
-
- // By retrieving the application URL within the context of a request (as we are here in responding
- // to the IRequestAccessor's RouteAttempt event), we'll get it from the HTTP context and save it for
- // future requests that may not be within an HTTP request (e.g. from hosted services).
- private void EnsureApplicationUrl() => _requestAccessor.GetApplicationUrl();
-
///
/// Clear the batch on end request
///
- private void EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.SendMessages();
+ public void Handle(UmbracoRequestEnd notification) => _messenger?.SendMessages();
}
}
diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs
index 767f36aaf5..ad3efc88df 100644
--- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs
+++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs
@@ -2,11 +2,12 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Hosting;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
@@ -185,7 +186,7 @@ namespace Umbraco.Web.Compose
public sealed class Notifier
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
- private readonly IRequestAccessor _requestAccessor;
+ private readonly IHostingEnvironment _hostingEnvironment;
private readonly INotificationService _notificationService;
private readonly IUserService _userService;
private readonly ILocalizedTextService _textService;
@@ -193,18 +194,11 @@ namespace Umbraco.Web.Compose
private readonly ILogger _logger;
///
- /// Constructor
+ /// Initializes a new instance of the class.
///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
public Notifier(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
- IRequestAccessor requestAccessor,
+ IHostingEnvironment hostingEnvironment,
INotificationService notificationService,
IUserService userService,
ILocalizedTextService textService,
@@ -212,7 +206,7 @@ namespace Umbraco.Web.Compose
ILogger logger)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
- _requestAccessor = requestAccessor;
+ _hostingEnvironment = hostingEnvironment;
_notificationService = notificationService;
_userService = userService;
_textService = textService;
@@ -236,7 +230,7 @@ namespace Umbraco.Web.Compose
}
}
- SendNotification(user, entities, action, _requestAccessor.GetApplicationUrl());
+ SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl);
}
private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri)
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.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs
index b88c2346a7..e816972989 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs
@@ -38,6 +38,7 @@ namespace Umbraco.Infrastructure.DependencyInjection
builder.SetDatabaseServerMessengerCallbacks(GetCallbacks);
builder.SetServerMessenger();
builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
return builder;
}
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/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
index 0ec237c6d6..f0acd22230 100644
--- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs
@@ -8,9 +8,9 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
-using Umbraco.Web;
namespace Umbraco.Infrastructure.HostedServices
{
@@ -19,7 +19,7 @@ namespace Umbraco.Infrastructure.HostedServices
///
public class KeepAlive : RecurringHostedServiceBase
{
- private readonly IRequestAccessor _requestAccessor;
+ private readonly IHostingEnvironment _hostingEnvironment;
private readonly IMainDom _mainDom;
private readonly KeepAliveSettings _keepAliveSettings;
private readonly ILogger _logger;
@@ -38,7 +38,7 @@ namespace Umbraco.Infrastructure.HostedServices
/// Provider of server registrations to the distributed cache.
/// Factory for instances.
public KeepAlive(
- IRequestAccessor requestAccessor,
+ IHostingEnvironment hostingEnvironment,
IMainDom mainDom,
IOptions keepAliveSettings,
ILogger logger,
@@ -47,7 +47,7 @@ namespace Umbraco.Infrastructure.HostedServices
IHttpClientFactory httpClientFactory)
: base(TimeSpan.FromMinutes(5), DefaultDelay)
{
- _requestAccessor = requestAccessor;
+ _hostingEnvironment = hostingEnvironment;
_mainDom = mainDom;
_keepAliveSettings = keepAliveSettings.Value;
_logger = logger;
@@ -88,7 +88,7 @@ namespace Umbraco.Infrastructure.HostedServices
{
if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}"))
{
- var umbracoAppUrl = _requestAccessor.GetApplicationUrl().ToString();
+ var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl.ToString();
if (umbracoAppUrl.IsNullOrWhiteSpace())
{
_logger.LogWarning("No umbracoApplicationUrl for service (yet), skip.");
diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs
index 69f9280fc0..6771705c8e 100644
--- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs
+++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs
@@ -7,8 +7,8 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Hosting;
using Umbraco.Core.Services;
-using Umbraco.Web;
namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
{
@@ -19,7 +19,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
{
private readonly IRuntimeState _runtimeState;
private readonly IServerRegistrationService _serverRegistrationService;
- private readonly IRequestAccessor _requestAccessor;
+ private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILogger _logger;
private readonly GlobalSettings _globalSettings;
@@ -31,12 +31,17 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
/// Accessor for the current request.
/// The typed logger.
/// The configuration for global settings.
- public TouchServerTask(IRuntimeState runtimeState, IServerRegistrationService serverRegistrationService, IRequestAccessor requestAccessor, ILogger logger, IOptions globalSettings)
+ public TouchServerTask(
+ IRuntimeState runtimeState,
+ IServerRegistrationService serverRegistrationService,
+ IHostingEnvironment hostingEnvironment,
+ ILogger logger,
+ IOptions globalSettings)
: base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15))
{
_runtimeState = runtimeState;
_serverRegistrationService = serverRegistrationService ?? throw new ArgumentNullException(nameof(serverRegistrationService));
- _requestAccessor = requestAccessor;
+ _hostingEnvironment = hostingEnvironment;
_logger = logger;
_globalSettings = globalSettings.Value;
}
@@ -48,7 +53,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
return Task.CompletedTask;
}
- var serverAddress = _requestAccessor.GetApplicationUrl()?.ToString();
+ var serverAddress = _hostingEnvironment.ApplicationMainUrl?.ToString();
if (serverAddress.IsNullOrWhiteSpace())
{
_logger.LogWarning("No umbracoApplicationUrl for service (yet), skip.");
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.Infrastructure/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridConfiguration.cs
index 74ea517fa2..d00f1b5e18 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/GridConfiguration.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/GridConfiguration.cs
@@ -22,7 +22,7 @@ namespace Umbraco.Web.PropertyEditors
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
public bool IgnoreUserStartNodes { get; set; }
- [ConfigurationField("mediaParentId", "Image Upload Folder", "MediaFolderPicker",
+ [ConfigurationField("mediaParentId", "Image Upload Folder", "mediafolderpicker",
Description = "Choose the upload location of pasted images")]
public GuidUdi MediaParentId { get; set; }
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs
index cf5235387d..eafc006c26 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs
@@ -117,7 +117,7 @@ namespace Umbraco.ModelsBuilder.Embedded
public void Handle(UmbracoRequestEnd notification)
{
- if (IsEnabled && _mainDom.IsMainDom && !notification.HttpContext.Request.IsClientSideRequest())
+ if (IsEnabled && _mainDom.IsMainDom)
{
GenerateModelsIfRequested();
}
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
index 4c698b221d..8bdca33561 100644
--- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
+++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
@@ -141,6 +141,7 @@ namespace Umbraco.Tests.Integration.Implementations
public override IHostingEnvironment GetHostingEnvironment()
=> _hostingEnvironment ??= new TestHostingEnvironment(
GetIOptionsMonitorOfHostingSettings(),
+ GetIOptionsMonitorOfWebRoutingSettings(),
_hostEnvironment);
private IOptionsMonitor GetIOptionsMonitorOfHostingSettings()
@@ -149,6 +150,12 @@ namespace Umbraco.Tests.Integration.Implementations
return Mock.Of>(x => x.CurrentValue == hostingSettings);
}
+ private IOptionsMonitor GetIOptionsMonitorOfWebRoutingSettings()
+ {
+ var webRoutingSettings = new WebRoutingSettings();
+ return Mock.Of>(x => x.CurrentValue == webRoutingSettings);
+ }
+
public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _hostingLifetime;
public override IIpResolver GetIpResolver() => _ipResolver;
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs
index 2a91b6db83..8690a5f6f8 100644
--- a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs
+++ b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs
@@ -11,8 +11,8 @@ namespace Umbraco.Tests.Integration.Implementations
{
public class TestHostingEnvironment : AspNetCoreHostingEnvironment, IHostingEnvironment
{
- public TestHostingEnvironment(IOptionsMonitor hostingSettings, IWebHostEnvironment webHostEnvironment)
- : base(hostingSettings, webHostEnvironment)
+ public TestHostingEnvironment(IOptionsMonitor hostingSettings,IOptionsMonitor webRoutingSettings, IWebHostEnvironment webHostEnvironment)
+ : base(hostingSettings,webRoutingSettings, webHostEnvironment)
{
}
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs
index b0aa42730e..15862ae24f 100644
--- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs
+++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -21,79 +21,79 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers
Assert.Multiple(() =>
{
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetNiceUrl(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetNiceUrl(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetNiceUrl(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetEmpty("test", 0)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetChildren(intId, string.Empty, 0, 0, "SortOrder", Direction.Ascending, true, string.Empty, string.Empty)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetNiceUrl(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetNiceUrl(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetNiceUrl(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetEmpty("test", 0)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetChildren(intId, string.Empty, 0, 0, "SortOrder", Direction.Ascending, true, string.Empty, string.Empty)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetPath(intId, UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetPath(guidId, UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetPath(udiId, UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetUrl(intId, UmbracoEntityTypes.Document, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetUrl(udiId, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetUrlAndAnchors(intId, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetUrlAndAnchors(udiId, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId, UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId, UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId, UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetByIds(new Guid[0], UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetByIds(new Udi[0], UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetByIds(new int[0], UmbracoEntityTypes.Document)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetPagedChildren(intId, UmbracoEntityTypes.Document, 0, 1, "SortOrder", Direction.Ascending, string.Empty, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetPagedChildren(guidId.ToString(), UmbracoEntityTypes.Document, 0, 1, "SortOrder", Direction.Ascending, string.Empty, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetPagedChildren(udiId.ToString(), UmbracoEntityTypes.Document, 0, 1, "SortOrder", Direction.Ascending, string.Empty, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetPath(intId, UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetPath(guidId, UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetPath(udiId, UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetUrl(intId, UmbracoEntityTypes.Document, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetUrl(udiId, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetUrlAndAnchors(intId, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetUrlAndAnchors(udiId, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId, UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId, UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId, UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetByIds(new Guid[0], UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetByIds(new Udi[0], UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetByIds(new int[0], UmbracoEntityTypes.Document)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetPagedChildren(intId, UmbracoEntityTypes.Document, 0, 1, "SortOrder", Direction.Ascending, string.Empty, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetPagedChildren(guidId.ToString(), UmbracoEntityTypes.Document, 0, 1, "SortOrder", Direction.Ascending, string.Empty, null)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetPagedChildren(udiId.ToString(), UmbracoEntityTypes.Document, 0, 1, "SortOrder", Direction.Ascending, string.Empty, null)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetIcon(string.Empty)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetIcon(string.Empty)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetChildren(intId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetChildren(guidId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetChildren(udiId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetChildren(intId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetChildren(guidId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetChildren(udiId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetAllowedChildren(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetAllowedChildren(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetAllowedChildren(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetAllowedChildren(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetAllowedChildren(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetAllowedChildren(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(intId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(guidId)));
- EnsureNotAmbiguousActionName(PrepareUrl(x => x.GetById(udiId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId)));
+ EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId)));
- EnsureNotAmbiguousActionName(PrepareUrl