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


78

在我的.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;
        }
    }

感谢您提供的代码,尽管如此,但如果用户能够写但不能删除,则调用者可能会得到错误的印象,即缺少写许可权。我将其更改为使用FileMode.Create并摆脱文件删除。显然,您将不再需要此代码,但我编写此代码是为了将来的读者。
n00b

3
string fullPath = directory + TEMP_FILE;请使用Path.Combine方法,而不是连接字符串以获取fullPath。Path.Combine(directory, TEMP_FILE)
nomail

如果有人先打孔然后第二天再打孔怎么办。如果他们打孔然后两天后打孔怎么办?我确定人们不应该做那些事情,但是应该定义行为。
Scott Hannen

Answers:


23

理查德杰森的答案都是正确的方向。但是,您应该做的是计算运行代码的用户身份的有效权限。例如,以上示例均未正确说明组成员身份。

我很确定基思·布朗在他的NET版Windows安全开发人员指南的Wiki版本中(此时处于脱机状态)有一些代码可以执行此操作。“编程Windows安全性”中也对此进行了合理的详细讨论。一书中。

计算有效权限并不适合胆怯的人,代码尝试创建文件并捕获引发的安全异常可能是阻力最小的途径。


2
这也是唯一可靠的方法,否则有人可以在检查和实际尝试保存之间更改权限(不太可能,但是可能)。
克里斯·奇尔弗斯

1
谢谢你 因此,我应该对代码进行的唯一更改是捕获安全异常,而不是常规的“异常”?
安迪

@Andy-是的,这是阻力最小的路径,除非您想编写代码以计算有效权限。
凯夫

2
为什么一切都必须如此复杂!
维达尔

3
@Triynko-我建议您阅读我引用的文章:groups.google.com/group/…-计算有效权限并不像听起来那么简单。成为我的客人并提供答案以证明我错了。
凯夫

48

Directory.GetAccessControl(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 正在使用一种称为“标志”的东西,如果您不知道它是什么,则应该仔细阅读:)


6
当然,如果您实际上无法在目录上获取ACL,则将抛出异常。
09年

3
它要检查什么?该目录具有“写”权限,但是对于哪个用户?:)
Ivan G.

2
如果您只想查看当前用户是否具有写访问权限,则可以使用它。
Donny V.

@aloneguid:“ GetAccessRules”方法返回一个AuthorizationRuleCollection。AthorizationRule类具有IdentityReference属性,其运行时类型实际上是从IdenityReference类型(NTAccount或Security)派生的两个类型之一,您可以看到该类型是在对GetAccessRules的调用中指定的。通过IdentityReference实例(或其派生类型),您可以发现规则适用于哪个用户。它将采用SID或NTAccount名称的形式。
Triynko 2011年

7
尝试在具有非管理员应用程序的Windows 7的系统磁盘上运行此命令,它将返回true,但是当您尝试写入c:\时,会出现一个异常,指出您无权访问!
彼得

34

Deny优先于Allow。本地规则优先于继承的规则。我已经看到了许多解决方案(包括此处显示的一些答案),但是它们都没有考虑规则是否被继承。因此,我建议采用以下方法来考虑规则继承(巧妙地包装到类中):

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

但是,我的经验是,这并不总是在远程计算机上起作用,因为您将不总是有权在那里查询文件访问权限。在这种情况下,解决方案是尝试;如果您需要在使用“实际”文件之前了解访问权限,甚至可以尝试仅创建一个临时文件。


2
我认为此答案是完成此问题的最佳方法,其他答案也使用相同的方法来获取结果,但由于只有此答案才可以计算继承的规则和本地规则,所以我猜这是最准确的答案。谢谢恭喜。
Tolga Evcimen 2014年

18

Kev对这个问题的公认答案实际上并没有给出任何代码,它只是指向我无权访问的其他资源。因此,这是我对该功能的最佳尝试。它实际上检查所查看的权限是否为“写入”权限,并且当前用户属于适当的组。

关于网络路径或任何其他内容,它可能并不完整,但是对于我来说已经足够了,检查“程序文件”下的本地配置文件是否具有可写性:

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

此帐户不适用于群组,但仅适用于本人情况下实际添加的帐户名称
随机

那么这与“(S-1-5-21-397955417-626881126-188441444-512)”类型格式有关吗?将字符串转换为类似的SecurityIdentifier是否可以解决您的问题?您的评论尚不清楚它是否现在对您有效。
布莱斯·瓦格纳

当您将“ rule.IdentityReference.Value”作为currentuser.IsInRole()的参数时,您将使用IsInRole(string)方法,该方法尝试按常规“ domain \ user”值进行匹配。因此,您要推送SID字符串而不是用户名字符串。但是,如果在该行前面使用我的行,则将获得与给定SID用户匹配的SecurityIdentifier对象。该“字符串”参数重载对于开发人员来说是一个小陷阱,它再次接受人类可重命名格式而不是SID字符串表示形式的帐户或组名。
随机

问题在于“新的SecurityIdentifier(SDDLFormat)”不适用于普通的组名(您会收到argment异常)。因此,我添加了检查是否为SDDL格式。
布莱斯·瓦格纳

2
该解决方案对我有用,但是网络文件夹有一个问题。该文件夹具有允许写入的访问规则BUILTIN\Administrators。当我是本地站点的管理员时,该摘要错误地返回了true
伊利亚·巴拉霍夫斯基

5

IMO,您需要照常使用此类目录,但与其使用前检查权限,还不如提供处理UnauthorizedAccessException并做出相应反应的正确方法。这种方法更容易,更不容易出错。


1
你大概的意思是说“这种方法会更加简单,不太容易出错。”
cjbarth

3

尝试使用我刚刚制作的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);
            }
        }
    }
}

这里的一个参考,你也可以关注一下。我的代码可能会让您了解在尝试写入目录之前如何检查权限。


typeof返回对象的类型,在这种情况下为NTAccountdocs.microsoft.com/zh-cn/dotnet/csharp/language-reference / ...调用时GetAccessRules()需要调用该帐户的类型。msdn.microsoft.com/en-us/library/…–
杰森·埃文斯

为什么要使用NTAccount?总是使用NTAccount吗?
Kiquenet '18

在这种情况下,可以。NTAccount代表Windows PC上的用户帐户,这就是我们在上面的代码中需要它的原因。
杰森·埃文斯

1

由于当前版本的.Net core / Standard似乎缺少静态方法'GetAccessControl',因此我不得不修改@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.")
    };
}

0

根据此链接: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.")]

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

        }

什么是WellKnownSidType.WorldSid
Kiquenet '18

只是对您的问题的一个随机答案,@ Kiquenet WorldSid是“所有人”内置组。
AussieALF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.