Проверка разрешений на запись в каталог и файл в.NET
В моем приложении.NET 2.0 мне нужно проверить, достаточно ли разрешений для создания и записи файлов в каталог. Для этого у меня есть следующая функция, которая пытается создать файл и записать в него один байт, а затем удалить себя, чтобы проверить, существуют ли разрешения.
Я решил, что лучший способ проверить это на самом деле попытаться сделать это, перехватывая любые возникающие исключения. Я не особенно доволен общим уловом Exception, так есть ли лучший или, возможно, более приемлемый способ сделать это?
private const string TEMP_FILE = "\\tempFile.tmp";
/// <summary>
/// Checks the ability to create and write to a file in the supplied directory.
/// </summary>
/// <param name="directory">String representing the directory path to check.</param>
/// <returns>True if successful; otherwise false.</returns>
private static bool CheckDirectoryAccess(string directory)
{
bool success = false;
string fullPath = directory + TEMP_FILE;
if (Directory.Exists(directory))
{
try
{
using (FileStream fs = new FileStream(fullPath, FileMode.CreateNew,
FileAccess.Write))
{
fs.WriteByte(0xff);
}
if (File.Exists(fullPath))
{
File.Delete(fullPath);
success = true;
}
}
catch (Exception)
{
success = false;
}
}
8 ответов
Ответы richardwiden и Джейсона как бы в правильном направлении. Однако то, что вы должны сделать, - это вычислить эффективные разрешения для идентификатора пользователя, выполняющего ваш код. Например, ни один из приведенных выше примеров не учитывает членство в группе.
Я почти уверен, что у Кейта Брауна был некоторый код для этого в его вики-версии (в настоящее время не в сети) Руководства разработчика по.NET по безопасности Windows. Об этом также достаточно подробно говорится в его книге " Программирование Windows Security".
Вычисление эффективных разрешений не для слабонервных, и ваш код пытается создать файл и перехватить исключение безопасности, вероятно, путь наименьшего сопротивления.
Directory.GetAcessControl(path)
делает то, что вы просите.
public static bool HasWritePermissionOnDir(string path)
{
var writeAllow = false;
var writeDeny = false;
var accessControlList = Directory.GetAccessControl(path);
if (accessControlList == null)
return false;
var accessRules = accessControlList.GetAccessRules(true, true,
typeof(System.Security.Principal.SecurityIdentifier));
if (accessRules ==null)
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;
}
(FileSystemRights.Write & rights) == FileSystemRights.Write
использует что-то под названием "Флаги", кстати, если вы не знаете, что это такое, вы должны действительно прочитать:)
Deny
имеет приоритет над Allow
, Локальные правила имеют приоритет над унаследованными правилами. Я видел много решений (включая некоторые ответы, показанные здесь), но ни одно из них не учитывает, наследуются ли правила или нет. Поэтому я предлагаю следующий подход, который учитывает наследование правил (аккуратно заключенное в класс):
public class CurrentUserSecurity
{
WindowsIdentity _currentUser;
WindowsPrincipal _currentPrincipal;
public CurrentUserSecurity()
{
_currentUser = WindowsIdentity.GetCurrent();
_currentPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
}
public bool HasAccess(DirectoryInfo directory, FileSystemRights right)
{
// Get the collection of authorization rules that apply to the directory.
AuthorizationRuleCollection acl = directory.GetAccessControl()
.GetAccessRules(true, true, typeof(SecurityIdentifier));
return HasFileOrDirectoryAccess(right, acl);
}
public bool HasAccess(FileInfo file, FileSystemRights right)
{
// Get the collection of authorization rules that apply to the file.
AuthorizationRuleCollection acl = file.GetAccessControl()
.GetAccessRules(true, true, typeof(SecurityIdentifier));
return HasFileOrDirectoryAccess(right, acl);
}
private bool HasFileOrDirectoryAccess(FileSystemRights right,
AuthorizationRuleCollection acl)
{
bool allow = false;
bool inheritedAllow = false;
bool inheritedDeny = false;
for (int i = 0; i < acl.Count; i++) {
var currentRule = (FileSystemAccessRule)acl[i];
// If the current rule applies to the current user.
if (_currentUser.User.Equals(currentRule.IdentityReference) ||
_currentPrincipal.IsInRole(
(SecurityIdentifier)currentRule.IdentityReference)) {
if (currentRule.AccessControlType.Equals(AccessControlType.Deny)) {
if ((currentRule.FileSystemRights & right) == right) {
if (currentRule.IsInherited) {
inheritedDeny = true;
} else { // Non inherited "deny" takes overall precedence.
return false;
}
}
} else if (currentRule.AccessControlType
.Equals(AccessControlType.Allow)) {
if ((currentRule.FileSystemRights & right) == right) {
if (currentRule.IsInherited) {
inheritedAllow = true;
} else {
allow = true;
}
}
}
}
}
if (allow) { // Non inherited "allow" takes precedence over inherited rules.
return true;
}
return inheritedAllow && !inheritedDeny;
}
}
Однако я понял, что это не всегда работает на удаленных компьютерах, так как у вас не всегда будет право запрашивать права доступа к файлам. Решение в этом случае состоит в том, чтобы попытаться; возможно, даже просто пытаясь создать временный файл, если вам нужно знать право доступа перед работой с "настоящими" файлами.
Принятый ответ Кева на этот вопрос на самом деле не дает никакого кода, он просто указывает на другие ресурсы, к которым у меня нет доступа. Итак, вот моя лучшая попытка на функции. Он фактически проверяет, что разрешение, которое он просматривает, является разрешением "Запись" и что текущий пользователь принадлежит к соответствующей группе.
Возможно, он не является полным в отношении сетевых путей или чего-либо еще, но для моей цели этого достаточно, проверяя локальные файлы конфигурации в разделе "Program Files" на возможность записи:
using System.Security.Principal;
using System.Security.AccessControl;
private static bool HasWritePermission(string FilePath)
{
try
{
FileSystemSecurity security;
if (File.Exists(FilePath))
{
security = File.GetAccessControl(FilePath);
}
else
{
security = Directory.GetAccessControl(Path.GetDirectoryName(FilePath));
}
var rules = security.GetAccessRules(true, true, typeof(NTAccount));
var currentuser = new WindowsPrincipal(WindowsIdentity.GetCurrent());
bool result = false;
foreach (FileSystemAccessRule rule in rules)
{
if (0 == (rule.FileSystemRights &
(FileSystemRights.WriteData | FileSystemRights.Write)))
{
continue;
}
if (rule.IdentityReference.Value.StartsWith("S-1-"))
{
var sid = new SecurityIdentifier(rule.IdentityReference.Value);
if (!currentuser.IsInRole(sid))
{
continue;
}
}
else
{
if (!currentuser.IsInRole(rule.IdentityReference.Value))
{
continue;
}
}
if (rule.AccessControlType == AccessControlType.Deny)
return false;
if (rule.AccessControlType == AccessControlType.Allow)
result = true;
}
return result;
}
catch
{
return false;
}
}
IMO, вам нужно работать с такими каталогами как обычно, но вместо проверки разрешений перед использованием предоставьте правильный способ обработки UnauthorizedAccessException и отреагируйте соответствующим образом. Этот метод проще и намного менее подвержен ошибкам.
Попробуйте поработать с этим фрагментом C#, который я только что создал:
using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string directory = @"C:\downloads";
DirectoryInfo di = new DirectoryInfo(directory);
DirectorySecurity ds = di.GetAccessControl();
foreach (AccessRule rule in ds.GetAccessRules(true, true, typeof(NTAccount)))
{
Console.WriteLine("Identity = {0}; Access = {1}",
rule.IdentityReference.Value, rule.AccessControlType);
}
}
}
}
И вот ссылка, которую вы также можете посмотреть. Мой код может дать вам представление о том, как вы можете проверить наличие разрешений перед попыткой записи в каталог.
Поскольку статический метод GetAccessControl, похоже, отсутствует в текущей версии.Net core/Standard, мне пришлось изменить ответ @Bryce Wagner (я пошел дальше и использовал более современный синтаксис):
public static class PermissionHelper
{
public static bool? CurrentUserHasWritePermission(string filePath)
=> new WindowsPrincipal(WindowsIdentity.GetCurrent())
.SelectWritePermissions(filePath)
.FirstOrDefault();
private static IEnumerable<bool?> SelectWritePermissions(this WindowsPrincipal user, string filePath)
=> from rule in filePath
.GetFileSystemSecurity()
.GetAccessRules(true, true, typeof(NTAccount))
.Cast<FileSystemAccessRule>()
let right = user.HasRightSafe(rule)
where right.HasValue
// Deny takes precedence over allow
orderby right.Value == false descending
select right;
private static bool? HasRightSafe(this WindowsPrincipal user, FileSystemAccessRule rule)
{
try
{
return user.HasRight(rule);
}
catch
{
return null;
}
}
private static bool? HasRight(this WindowsPrincipal user,FileSystemAccessRule rule )
=> rule switch
{
{ FileSystemRights: FileSystemRights fileSystemRights } when (fileSystemRights &
(FileSystemRights.WriteData | FileSystemRights.Write)) == 0 => null,
{ IdentityReference: { Value: string value } } when value.StartsWith("S-1-") &&
!user.IsInRole(new SecurityIdentifier(rule.IdentityReference.Value)) => null,
{ IdentityReference: { Value: string value } } when value.StartsWith("S-1-") == false &&
!user.IsInRole(rule.IdentityReference.Value) => null,
{ AccessControlType: AccessControlType.Deny } => false,
{ AccessControlType: AccessControlType.Allow } => true,
_ => null
};
private static FileSystemSecurity GetFileSystemSecurity(this string filePath)
=> new FileInfo(filePath) switch
{
{ Exists: true } fileInfo => fileInfo.GetAccessControl(),
{ Exists: false } fileInfo => (FileSystemSecurity)fileInfo.Directory.GetAccessControl(),
_ => throw new Exception($"Check the file path, {filePath}: something's wrong with it.")
};
}
По этой ссылке: http://www.authorcode.com/how-to-check-file-permission-to-write-in-c/
проще использовать существующий класс SecurityManager
string FileLocation = @"C:\test.txt";
FileIOPermission writePermission = new FileIOPermission(FileIOPermissionAccess.Write, FileLocation);
if (SecurityManager.IsGranted(writePermission))
{
// you have permission
}
else
{
// permission is required!
}
но, похоже, он устарел, вместо него предлагается использовать PermissionSet.
[Obsolete("IsGranted is obsolete and will be removed in a future release of the .NET Framework. Please use the PermissionSet property of either AppDomain or Assembly instead.")]
private static void GrantAccess(string file)
{
bool exists = System.IO.Directory.Exists(file);
if (!exists)
{
DirectoryInfo di = System.IO.Directory.CreateDirectory(file);
Console.WriteLine("The Folder is created Sucessfully");
}
else
{
Console.WriteLine("The Folder already exists");
}
DirectoryInfo dInfo = new DirectoryInfo(file);
DirectorySecurity dSecurity = dInfo.GetAccessControl();
dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));
dInfo.SetAccessControl(dSecurity);
}