【问题标题】:Checking for directory and file write permissions in .NET检查 .NET 中的目录和文件写入权限
【发布时间】:2010-11-19 21:04:30
【问题描述】:

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

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

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 并摆脱文件删除。显然您将不再需要此代码,但我编写此代码是为了让未来的读者受益。
  • string fullPath = directory + TEMP_FILE; 请使用 Path.Combine 方法而不是连接字符串来获取 fullPath。 Path.Combine(directory, TEMP_FILE)
  • 如果有人打卡然后第二天打卡怎么办。如果他们打卡然后两天后打卡怎么办?我确信人们不应该做这些事情,但应该定义行为。

标签: c# .net winforms directory file-permissions


【解决方案1】:

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 &amp; rights) == FileSystemRights.Write 正在使用一种叫做“Flags”的东西,顺便说一句,如果你不知道它是什么,你应该仔细阅读 :)

【讨论】:

  • 如果您实际上无法获取目录上的 ACL,那当然会抛出异常。
  • 它检查什么?该目录具有写入权限,但对于哪个用户? :)
  • 如果您只想查看当前用户是否具有写入权限,则可以使用。
  • @aloneguid:“GetAccessRules”方法返回一个 AuthorizationRuleCollection。 AthorizationRule 类有一个 IdentityReference 属性,其运行时类型实际上是从 IdenityReference 类型(NTAccount 或 Security)派生的两种类型之一,您可以看到在对 GetAccessRules 的调用中指定了该类型。通过 IdentityReference 实例(或其派生类型),您可以发现规则适用于哪个用户。它将采用 SID 或 NTAccount 名称的形式。
  • 尝试使用非管理员应用程序在 Windows 7 上的系统磁盘上运行它,它将返回 true,但是当您尝试写入 c:\ 时,您会收到一个异常,说明您没有'无权访问!
【解决方案2】:

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

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

【讨论】:

  • 我认为这个答案是完成它的最佳方法,其他答案也使用相同的方式来获得结果,但由于只有这个答案计算继承规则和本地规则,它是我最准确的一个猜测。谢谢&恭喜。
  • 旧线程。但我实际上如何让参数FileSystemRights right 传递给方法HasAccess
  • FileSystemRights 是一个枚举,具有ReadDataWriteData 等常量。您必须传入您感兴趣的枚举常量。它们是标志值,因此您可以像这样组合它们:FileSystemRights.ReadData | FileSystemRights.ListDirectory
【解决方案3】:

RichardJason 的答案有点正确。但是,您应该做的是computing the effective permissions 用于运行您的代码的用户身份。例如,上述示例均未正确说明组成员身份。

我很确定Keith Brown 在他的wiki version(此时离线)和The .NET Developers Guide to Windows Security 中有一些代码可以做到这一点。这也在他的Programming Windows Security 书中进行了合理详细的讨论。

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

【讨论】:

  • 这也是唯一可靠的方法,否则有人可能会在检查和实际尝试保存之间更改权限(不太可能,但可能)。
  • 谢谢。所以我应该对我的代码做的唯一改变是捕获一个安全异常而不是一般的“异常”?
  • @Andy - 是的,这是阻力最小的路径,除非您想编写代码来计算有效权限。
  • 为什么一切都必须如此复杂!
  • @Triynko - 我建议你阅读我引用的文章:groups.google.com/group/… - 计算有效权限并不像听起来那么简单。做我的客人,并提供一个答案来证明我错了。
【解决方案4】:

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 字符串表示。
  • 问题是“new SecurityIdentifier(SDDLFormat)”不适用于普通组名(你会得到一个参数异常)。所以我添加了一个检查它是否为 SDDL 格式。
  • 这个解决方案对我有用,但网络文件夹有一个问题。该文件夹具有允许写入BUILTIN\Administrators 的访问规则。由于我是本地站的管理员,所以 sn-p 错误地返回了true
【解决方案5】:

IMO,您需要像往常一样使用此类目录,但不要在使用前检查权限,而是提供正确的方法来处理 UnauthorizedAccessException 并做出相应的反应。这种方法更简单,更不容易出错。

【讨论】:

  • 您可能想说“这种方法更简单,而且更少容易出错。”
【解决方案6】:

尝试使用我刚刚制作的这个 C# sn-p:

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

还有here's 您也可以查看参考。我的代码可能会让您了解如何在尝试写入目录之前检查权限。

【讨论】:

【解决方案7】:

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

【讨论】:

    【解决方案8】:

    Since the static method 'GetAccessControl' seems to be missing from the present version of .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.")
        };
    }
    

    【讨论】:

      【解决方案9】:
      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 WorldSid 是“所有人”构建组。
      猜你喜欢
      • 1970-01-01
      • 2016-11-14
      • 1970-01-01
      • 1970-01-01
      • 2019-11-04
      • 2010-11-19
      • 2011-04-17
      • 2019-12-18
      相关资源
      最近更新 更多