Files
Umbraco-CMS/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs

182 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
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(ILocalizedTextService textService)
{
_textService = textService;
}
/// <summary>
/// Get the status for this health check
/// </summary>
/// <returns></returns>
public override IEnumerable<HealthCheckStatus> GetStatus()
{
//return the statuses
return new[] { CheckFolderPermissions(), CheckFilePermissions() };
}
/// <summary>
/// Executes the action and returns it's status
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
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<string, PermissionCheckRequirement>
{
{ Constants.SystemDirectories.Data, PermissionCheckRequirement.Required },
{ Constants.SystemDirectories.Packages, PermissionCheckRequirement.Required},
{ Constants.SystemDirectories.Preview, PermissionCheckRequirement.Required },
{ Constants.SystemDirectories.AppPlugins, PermissionCheckRequirement.Required },
{ Constants.SystemDirectories.Config, PermissionCheckRequirement.Optional },
{ Current.Configs.Global().UmbracoCssPath, PermissionCheckRequirement.Optional },
{ Current.Configs.Global().UmbracoMediaPath, PermissionCheckRequirement.Optional },
{ Current.Configs.Global().UmbracoScriptsPath, PermissionCheckRequirement.Optional },
{ Current.Configs.Global().UmbracoPath, PermissionCheckRequirement.Optional },
{ Constants.SystemDirectories.MvcViews, PermissionCheckRequirement.Optional }
};
//These are special paths to check that will restart an app domain if a file is written to them,
//so these need to be tested differently
var pathsToCheckWithRestarts = new Dictionary<string, PermissionCheckRequirement>
{
{ Constants.SystemDirectories.AppCode, PermissionCheckRequirement.Optional },
{ Constants.SystemDirectories.Bin, PermissionCheckRequirement.Optional }
};
// Run checks for required and optional paths for modify permission
var requiredPathCheckResult = FilePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out var requiredFailedPaths);
var optionalPathCheckResult = FilePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out var optionalFailedPaths);
//now check the special folders
var requiredPathCheckResult2 = FilePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart:true);
var optionalPathCheckResult2 = FilePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Optional), out var optionalFailedPaths2, writeCausesRestart: true);
requiredPathCheckResult = requiredPathCheckResult && requiredPathCheckResult2;
optionalPathCheckResult = optionalPathCheckResult && optionalPathCheckResult2;
//combine the paths
requiredFailedPaths = requiredFailedPaths.Concat(requiredFailedPaths2).ToList();
optionalFailedPaths = requiredFailedPaths.Concat(optionalFailedPaths2).ToList();
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<string, PermissionCheckRequirement>
{
{ "~/Web.config", PermissionCheckRequirement.Optional },
};
// Run checks for required and optional paths for modify permission
IEnumerable<string> requiredFailedPaths;
IEnumerable<string> 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<string, PermissionCheckRequirement> pathsToCheck,
PermissionCheckRequirement requirement)
{
return pathsToCheck
.Where(x => x.Value == requirement)
.Select(x => Current.IOHelper.MapPath(x.Key))
.OrderBy(x => x)
.ToArray();
}
private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable<string> requiredFailedPaths,
bool optionalPathCheckResult, IEnumerable<string> optionalFailedPaths,
PermissionCheckFor checkingFor)
{
// Return error if any required paths 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<HealthCheckAction>();
return
new HealthCheckStatus(message)
{
ResultType = resultType,
Actions = actions
};
}
private string GetMessageForPathCheckFailure(string messageKey, IEnumerable<string> failedPaths)
{
var rootFolder = Current.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);
}
}
}