检查.NET 中的目录和文件写权限

在我的。NET 2.0应用程序,我需要检查是否存在足够的权限来创建和写入文件到一个目录。为此,我使用以下函数尝试创建一个文件并向其写入一个字节,然后删除它本身以测试权限是否存在。

我认为最好的检查方法是实际尝试并执行,捕捉任何发生的异常。不过,我对一般的 Exception catch 并不是特别满意,那么有没有更好的或者更容易被接受的方法来实现这一点呢?

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;
}
}
137378 次浏览

Directory.GetAccessControl(path) does what you are asking for.

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 is using something called "Flags" btw which if you don't know what it is you should really read up on :)

Try working with this C# snippet I just crafted:

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);
}
}
}
}

And here's a reference you could also look at. My code might give you an idea as to how you could check for permissions before attempting to write to a directory.

IMO, you need to work with such directories as usual, but instead of checking permissions before use, provide the correct way to handle UnauthorizedAccessException and react accordingly. This method is easier and much less error prone.

The answers by Richard and Jason are sort of in the right direction. However what you should be doing is computing the effective permissions for the user identity running your code. None of the examples above correctly account for group membership for example.

I'm pretty sure Keith Brown had some code to do this in his wiki version (offline at this time) of The .NET Developers Guide to Windows Security. This is also discussed in reasonable detail in his Programming Windows Security book.

Computing effective permissions is not for the faint hearted and your code to attempt creating a file and catching the security exception thrown is probably the path of least resistance.

The accepted answer by Kev to this question doesn't actually give any code, it just points to other resources that I don't have access to. So here's my best attempt at the function. It actually checks that the permission it's looking at is a "Write" permission and that the current user belongs to the appropriate group.

It might not be complete with regard to network paths or whatever, but it's good enough for my purpose, checking local configuration files under "Program Files" for writability:

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;
}
}

Deny takes precedence over Allow. Local rules take precedence over inherited rules. I have seen many solutions (including some answers shown here), but none of them takes into account whether rules are inherited or not. Therefore I suggest the following approach that considers rule inheritance (neatly wrapped into a class):

public class CurrentUserSecurity
{
WindowsIdentity _currentUser;
WindowsPrincipal _currentPrincipal;


public CurrentUserSecurity()
{
_currentUser = WindowsIdentity.GetCurrent();
_currentPrincipal = new WindowsPrincipal(_currentUser);
}


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;
}
}

However, I made the experience that this does not always work on remote computers as you will not always have the right to query the file access rights there. The solution in that case is to try; possibly even by just trying to create a temporary file, if you need to know the access right before working with the "real" files.

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);


}

according to this link: http://www.authorcode.com/how-to-check-file-permission-to-write-in-c/

it's easier to use existing class SecurityManager

string FileLocation = @"C:\test.txt";
FileIOPermission writePermission = new FileIOPermission(FileIOPermissionAccess.Write, FileLocation);
if (SecurityManager.IsGranted(writePermission))
{
// you have permission
}
else
{
// permission is required!
}

but it seems it's been obsoleted, it is suggested to use PermissionSet instead.

[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.")]

Since the static method 'GetAccessControl' seems to be missing from the present version of .Net core/Standard I had to modify @Bryce Wagner's answer (I went ahead and used more modern syntax):

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.")
};
}