using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.AccessControl; using System.Security.Principal; using Nop.Core.Configuration; using Nop.Core.Infrastructure; using Nop.Services.Plugins; namespace Nop.Web.Framework.Security; /// /// File permission helper /// public static class FilePermissionHelper { #region Utilities private static bool CheckUserFilePermissions(int userFilePermission, bool checkRead, bool checkWrite, bool checkModify, bool checkDelete) { //read permissions var readPermissions = new[] { 5, 6, 7 }; //write permissions var writePermissions = new[] { 2, 3, 6, 7 }; if (checkRead && readPermissions.Contains(userFilePermission)) return true; return (checkWrite || checkModify || checkDelete) && writePermissions.Contains(userFilePermission); } [SupportedOSPlatform("windows")] private static void CheckAccessRule(FileSystemAccessRule rule, ref bool deleteIsDeny, ref bool modifyIsDeny, ref bool readIsDeny, ref bool writeIsDeny, ref bool deleteIsAllow, ref bool modifyIsAllow, ref bool readIsAllow, ref bool writeIsAllow) { switch (rule.AccessControlType) { case AccessControlType.Deny: if (CheckAccessRuleLocal(rule, FileSystemRights.Delete)) deleteIsDeny = true; if (CheckAccessRuleLocal(rule, FileSystemRights.Modify)) modifyIsDeny = true; if (CheckAccessRuleLocal(rule, FileSystemRights.Read)) readIsDeny = true; if (CheckAccessRuleLocal(rule, FileSystemRights.Write)) writeIsDeny = true; return; case AccessControlType.Allow: if (CheckAccessRuleLocal(rule, FileSystemRights.Delete)) deleteIsAllow = true; if (CheckAccessRuleLocal(rule, FileSystemRights.Modify)) modifyIsAllow = true; if (CheckAccessRuleLocal(rule, FileSystemRights.Read)) readIsAllow = true; if (CheckAccessRuleLocal(rule, FileSystemRights.Write)) writeIsAllow = true; break; } } [SupportedOSPlatform("windows")] private static bool CheckAccessRuleLocal(FileSystemAccessRule fileSystemAccessRule, FileSystemRights fileSystemRights) { return (fileSystemRights & fileSystemAccessRule.FileSystemRights) == fileSystemRights; } /// /// Check permissions /// /// File provider /// Path /// Check read /// Check write /// Check modify /// Check delete /// Result [SupportedOSPlatform("windows")] private static bool CheckPermissionsInWindows(INopFileProvider fileProvider, string path, bool checkRead, bool checkWrite, bool checkModify, bool checkDelete) { var permissionsAreGranted = true; try { var current = WindowsIdentity.GetCurrent(); var readIsDeny = false; var writeIsDeny = false; var modifyIsDeny = false; var deleteIsDeny = false; var readIsAllow = false; var writeIsAllow = false; var modifyIsAllow = false; var deleteIsAllow = false; var rules = fileProvider.GetAccessControl(path).GetAccessRules(true, true, typeof(SecurityIdentifier)) .Cast() .ToList(); foreach (var rule in rules.Where(rule => current.User?.Equals(rule.IdentityReference) ?? false)) { CheckAccessRule(rule, ref deleteIsDeny, ref modifyIsDeny, ref readIsDeny, ref writeIsDeny, ref deleteIsAllow, ref modifyIsAllow, ref readIsAllow, ref writeIsAllow); } if (current.Groups != null) { foreach (var reference in current.Groups) { foreach (var rule in rules.Where(rule => reference.Equals(rule.IdentityReference))) { CheckAccessRule(rule, ref deleteIsDeny, ref modifyIsDeny, ref readIsDeny, ref writeIsDeny, ref deleteIsAllow, ref modifyIsAllow, ref readIsAllow, ref writeIsAllow); } } } deleteIsAllow = !deleteIsDeny && deleteIsAllow; modifyIsAllow = !modifyIsDeny && modifyIsAllow; readIsAllow = !readIsDeny && readIsAllow; writeIsAllow = !writeIsDeny && writeIsAllow; if (checkRead) permissionsAreGranted = readIsAllow; if (checkWrite) permissionsAreGranted = permissionsAreGranted && writeIsAllow; if (checkModify) permissionsAreGranted = permissionsAreGranted && modifyIsAllow; if (checkDelete) permissionsAreGranted = permissionsAreGranted && deleteIsAllow; } catch (IOException) { return false; } catch { return true; } return permissionsAreGranted; } /// /// Check permissions /// /// Path /// Check read /// Check write /// Check modify /// Check delete /// Result private static bool CheckPermissionsInUnix(string path, bool checkRead, bool checkWrite, bool checkModify, bool checkDelete) { //MacOSX file permission check differs slightly from linux var arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"-c \"stat -f '%A %u %g' '{path}'\"" : $"-c \"stat -c '%a %u %g' '{path}'\""; try { //create bash command like //sh -c "stat -c '%a %u %g' " var process = new Process { StartInfo = new ProcessStartInfo { RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, FileName = "sh", Arguments = arguments } }; process.Start(); process.WaitForExit(); //result look like: 555 1111 2222 //where 555 - file permissions, 1111 - file owner ID, 2222 - file group ID var result = process.StandardOutput.ReadToEnd().Trim('\n').Split(' '); var filePermissions = result[0].Select(p => (int)char.GetNumericValue(p)).ToList(); var isOwner = CurrentOSUser.UserId == result[1]; var isInGroup = CurrentOSUser.Groups.Contains(result[2]); var filePermission = isOwner ? filePermissions[0] : (isInGroup ? filePermissions[1] : filePermissions[2]); return CheckUserFilePermissions(filePermission, checkRead, checkWrite, checkModify, checkDelete); } catch { return false; } } #endregion #region Methods /// /// Check permissions /// /// File provider /// Path /// Check read /// Check write /// Check modify /// Check delete /// Result public static bool CheckPermissions(this INopFileProvider fileProvider, string path, bool checkRead, bool checkWrite, bool checkModify, bool checkDelete) { if (!(fileProvider.FileExists(path) || fileProvider.DirectoryExists(path))) return true; var result = false; switch (Environment.OSVersion.Platform) { case PlatformID.Win32NT: if (OperatingSystem.IsWindows()) result = CheckPermissionsInWindows(fileProvider, path, checkRead, checkWrite, checkModify, checkDelete); break; case PlatformID.Unix: result = CheckPermissionsInUnix(path, checkRead, checkWrite, checkModify, checkDelete); break; } return result; } /// /// Gets a list of directories (physical paths) which require write permission /// /// Result public static IEnumerable GetDirectoriesWrite(this INopFileProvider fileProvider) { var rootDir = fileProvider.MapPath("~/"); var dirsToCheck = new List { fileProvider.Combine(rootDir, "App_Data"), fileProvider.Combine(rootDir, "bin"), fileProvider.Combine(rootDir, "logs"), fileProvider.Combine(rootDir, "Plugins"), fileProvider.Combine(rootDir, @"Plugins\Uploaded"), fileProvider.Combine(rootDir, @"wwwroot\.well-known"), fileProvider.Combine(rootDir, @"wwwroot\bundles"), fileProvider.Combine(rootDir, @"wwwroot\db_backups"), fileProvider.Combine(rootDir, @"wwwroot\files"), fileProvider.Combine(rootDir, @"wwwroot\files\exportimport"), fileProvider.Combine(rootDir, @"wwwroot\icons"), fileProvider.Combine(rootDir, @"wwwroot\images"), fileProvider.Combine(rootDir, @"wwwroot\images\thumbs"), fileProvider.Combine(rootDir, @"wwwroot\images\uploaded"), fileProvider.Combine(rootDir, @"wwwroot\sitemaps") }; return dirsToCheck; } /// /// Gets a list of files (physical paths) which require write permission /// /// Result public static IEnumerable GetFilesWrite(this INopFileProvider fileProvider) { return new List { fileProvider.MapPath(NopPluginDefaults.PluginsInfoFilePath), fileProvider.MapPath(NopConfigurationDefaults.AppSettingsFilePath) }; } #endregion }