259 lines
9.3 KiB
C#
259 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.IO;
|
|
using System.Security.AccessControl;
|
|
using Umbraco.Core;
|
|
using Umbraco.Core.Install;
|
|
using Umbraco.Core.IO;
|
|
using Umbraco.Web.Composing;
|
|
|
|
namespace Umbraco.Web.Install
|
|
{
|
|
internal class FilePermissionHelper: IFilePermissionHelper
|
|
{
|
|
// ensure that these directories exist and Umbraco can write to them
|
|
private readonly string[] _permissionDirs = { Current.Configs.Global().UmbracoCssPath, Constants.SystemDirectories.Config, Constants.SystemDirectories.Data, Current.Configs.Global().UmbracoMediaPath, Constants.SystemDirectories.Preview };
|
|
private readonly string[] _packagesPermissionsDirs = { Constants.SystemDirectories.Bin, Current.Configs.Global().UmbracoPath, Constants.SystemDirectories.Packages };
|
|
|
|
// ensure Umbraco can write to these files (the directories must exist)
|
|
private readonly string[] _permissionFiles = { };
|
|
|
|
public bool RunFilePermissionTestSuite(out Dictionary<string, IEnumerable<string>> report)
|
|
{
|
|
report = new Dictionary<string, IEnumerable<string>>();
|
|
|
|
using (ChangesMonitor.Suspended()) // hack: ensure this does not trigger a restart
|
|
{
|
|
if (EnsureDirectories(_permissionDirs, out var errors) == false)
|
|
report["Folder creation failed"] = errors.ToList();
|
|
|
|
if (EnsureDirectories(_packagesPermissionsDirs, out errors) == false)
|
|
report["File writing for packages failed"] = errors.ToList();
|
|
|
|
if (EnsureFiles(_permissionFiles, out errors) == false)
|
|
report["File writing failed"] = errors.ToList();
|
|
|
|
if (TestPublishedSnapshotService(out errors) == false)
|
|
report["Published snapshot environment check failed"] = errors.ToList();
|
|
|
|
if (EnsureCanCreateSubDirectory(Current.Configs.Global().UmbracoMediaPath, out errors) == false)
|
|
report["Media folder creation failed"] = errors.ToList();
|
|
}
|
|
|
|
return report.Count == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will test the directories for write access
|
|
/// </summary>
|
|
/// <param name="dirs"></param>
|
|
/// <param name="errors"></param>
|
|
/// <param name="writeCausesRestart">
|
|
/// 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.
|
|
/// </param>
|
|
/// <returns></returns>
|
|
public bool EnsureDirectories(string[] dirs, out IEnumerable<string> errors, bool writeCausesRestart = false)
|
|
{
|
|
List<string> temp = null;
|
|
var success = true;
|
|
foreach (var dir in dirs)
|
|
{
|
|
// 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 (temp == null) temp = new List<string>();
|
|
temp.Add(dir);
|
|
success = false;
|
|
}
|
|
|
|
errors = success ? Enumerable.Empty<string>() : temp;
|
|
return success;
|
|
}
|
|
|
|
public bool EnsureFiles(string[] files, out IEnumerable<string> errors)
|
|
{
|
|
List<string> temp = null;
|
|
var success = true;
|
|
foreach (var file in files)
|
|
{
|
|
var canWrite = TryWriteFile(file);
|
|
if (canWrite) continue;
|
|
|
|
if (temp == null) temp = new List<string>();
|
|
temp.Add(file);
|
|
success = false;
|
|
}
|
|
|
|
errors = success ? Enumerable.Empty<string>() : temp;
|
|
return success;
|
|
}
|
|
|
|
public bool EnsureCanCreateSubDirectory(string dir, out IEnumerable<string> errors)
|
|
{
|
|
return EnsureCanCreateSubDirectories(new[] { dir }, out errors);
|
|
}
|
|
|
|
public bool EnsureCanCreateSubDirectories(IEnumerable<string> dirs, out IEnumerable<string> errors)
|
|
{
|
|
List<string> temp = null;
|
|
var success = true;
|
|
foreach (var dir in dirs)
|
|
{
|
|
var canCreate = TryCreateSubDirectory(dir);
|
|
if (canCreate) continue;
|
|
|
|
if (temp == null) temp = new List<string>();
|
|
temp.Add(dir);
|
|
success = false;
|
|
}
|
|
|
|
errors = success ? Enumerable.Empty<string>() : temp;
|
|
return success;
|
|
}
|
|
|
|
public bool TestPublishedSnapshotService(out IEnumerable<string> errors)
|
|
{
|
|
var publishedSnapshotService = Current.PublishedSnapshotService;
|
|
return publishedSnapshotService.EnsureEnvironment(out errors);
|
|
}
|
|
|
|
// tries to create a sub-directory
|
|
// if successful, the sub-directory is deleted
|
|
// creates the directory if needed - does not delete it
|
|
private bool TryCreateSubDirectory(string dir)
|
|
{
|
|
try
|
|
{
|
|
var path = Current.IOHelper.MapPath(dir + "/" + CreateRandomName());
|
|
Directory.CreateDirectory(path);
|
|
Directory.Delete(path);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// tries to create a file
|
|
// if successful, the file is deleted
|
|
// creates the directory if needed - does not delete it
|
|
public bool TryCreateDirectory(string dir)
|
|
{
|
|
try
|
|
{
|
|
var dirPath = Current.IOHelper.MapPath(dir);
|
|
|
|
if (Directory.Exists(dirPath) == false)
|
|
Directory.CreateDirectory(dirPath);
|
|
|
|
var filePath = dirPath + "/" + CreateRandomName() + ".tmp";
|
|
File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it.");
|
|
File.Delete(filePath);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// tries to create a file
|
|
// if successful, the file is deleted
|
|
//
|
|
// or
|
|
//
|
|
// use the ACL APIs to avoid creating files
|
|
//
|
|
// if the directory does not exist, do nothing & success
|
|
public bool TryAccessDirectory(string dir, bool canWrite)
|
|
{
|
|
try
|
|
{
|
|
var dirPath = Current.IOHelper.MapPath(dir);
|
|
|
|
if (Directory.Exists(dirPath) == false)
|
|
return true;
|
|
|
|
if (canWrite)
|
|
{
|
|
var filePath = dirPath + "/" + CreateRandomName() + ".tmp";
|
|
File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it.");
|
|
File.Delete(filePath);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return HasWritePermission(dirPath);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private bool HasWritePermission(string path)
|
|
{
|
|
var writeAllow = false;
|
|
var writeDeny = false;
|
|
var accessControlList = Directory.GetAccessControl(path);
|
|
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)
|
|
{
|
|
// This is not 100% accurate because it could turn out that the current user doesn't
|
|
// have access to read the current permissions but does have write access.
|
|
// I think this is an edge case however
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// tries to write into a file
|
|
// fails if the directory does not exist
|
|
private bool TryWriteFile(string file)
|
|
{
|
|
try
|
|
{
|
|
var path = Current.IOHelper.MapPath(file);
|
|
File.AppendText(path).Close();
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private string CreateRandomName()
|
|
{
|
|
return "umbraco-test." + Guid.NewGuid().ToString("N").Substring(0, 8);
|
|
}
|
|
}
|
|
}
|