using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Web.Install; namespace Umbraco.Web.HealthCheck.Checks.Permissions { internal enum PermissionCheckRequirement { Required, Optional } internal enum PermissionCheckFor { Folder, File } [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; public FolderAndFilePermissionsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) { _textService = healthCheckContext.ApplicationContext.Services.TextService; } /// /// Get the status for this health check /// /// public override IEnumerable GetStatus() { //return the statuses return new[] { CheckFolderPermissions(), CheckFilePermissions() }; } /// /// Executes the action and returns it's status /// /// /// public override HealthCheckStatus ExecuteAction(HealthCheckAction action) { throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); } private HealthCheckStatus CheckFolderPermissions() { // Create lists of paths to check along with a flag indicating if modify rights are required // in ALL circumstances or just some var pathsToCheck = new Dictionary { { SystemDirectories.AppCode, PermissionCheckRequirement.Optional }, { SystemDirectories.Data, PermissionCheckRequirement.Required }, { SystemDirectories.Packages, PermissionCheckRequirement.Required}, { SystemDirectories.Preview, PermissionCheckRequirement.Required }, { SystemDirectories.AppPlugins, PermissionCheckRequirement.Required }, { SystemDirectories.Bin, PermissionCheckRequirement.Optional }, { SystemDirectories.Config, PermissionCheckRequirement.Optional }, { SystemDirectories.Css, PermissionCheckRequirement.Optional }, { SystemDirectories.Masterpages, PermissionCheckRequirement.Optional }, { SystemDirectories.Media, PermissionCheckRequirement.Optional }, { SystemDirectories.Scripts, PermissionCheckRequirement.Optional }, { SystemDirectories.Umbraco, PermissionCheckRequirement.Optional }, { SystemDirectories.UmbracoClient, PermissionCheckRequirement.Optional }, { SystemDirectories.UserControls, PermissionCheckRequirement.Optional }, { SystemDirectories.MvcViews, PermissionCheckRequirement.Optional }, { SystemDirectories.Xslt, PermissionCheckRequirement.Optional }, }; // Run checks for required and optional paths for modify permission IEnumerable requiredFailedPaths; IEnumerable optionalFailedPaths; var requiredPathCheckResult = FilePermissionHelper.EnsureDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); var optionalPathCheckResult = FilePermissionHelper.EnsureDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.Folder); } private HealthCheckStatus CheckFilePermissions() { // Create lists of paths to check along with a flag indicating if modify rights are required // in ALL circumstances or just some var pathsToCheck = new Dictionary { { "~/Web.config", PermissionCheckRequirement.Optional }, }; // Run checks for required and optional paths for modify permission IEnumerable requiredFailedPaths; IEnumerable optionalFailedPaths; var requiredPathCheckResult = FilePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); var optionalPathCheckResult = FilePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.File); } private static string[] GetPathsToCheck(Dictionary pathsToCheck, PermissionCheckRequirement requirement) { return pathsToCheck .Where(x => x.Value == requirement) .Select(x => IOHelper.MapPath(x.Key)) .OrderBy(x => x) .ToArray(); } private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable requiredFailedPaths, bool optionalPathCheckResult, IEnumerable optionalFailedPaths, PermissionCheckFor checkingFor) { // Return error if any required parths fail the check, or warning if any optional ones do var resultType = StatusResultType.Success; var messageKey = string.Format("healthcheck/{0}PermissionsCheckMessage", checkingFor == PermissionCheckFor.Folder ? "folder" : "file"); var message = _textService.Localize(messageKey); if (requiredPathCheckResult == false) { resultType = StatusResultType.Error; messageKey = string.Format("healthcheck/required{0}PermissionFailed", checkingFor == PermissionCheckFor.Folder ? "Folder" : "File"); message = GetMessageForPathCheckFailure(messageKey, requiredFailedPaths); } else if (optionalPathCheckResult == false) { resultType = StatusResultType.Warning; messageKey = string.Format("healthcheck/optional{0}PermissionFailed", checkingFor == PermissionCheckFor.Folder ? "Folder" : "File"); message = GetMessageForPathCheckFailure(messageKey, optionalFailedPaths); } var actions = new List(); return new HealthCheckStatus(message) { ResultType = resultType, Actions = actions }; } private string GetMessageForPathCheckFailure(string messageKey, IEnumerable failedPaths) { var rootFolder = IOHelper.MapPath("/"); var failedFolders = failedPaths .Select(x => ParseFolderFromFullPath(rootFolder, x)); return _textService.Localize(messageKey, new[] { string.Join(", ", failedFolders) }); } private string ParseFolderFromFullPath(string rootFolder, string filePath) { return filePath.Replace(rootFolder, string.Empty); } } }