Checking for directory and file write permissions in .NET
Asked Answered
G

9

91

In my .NET 2.0 application, I need to check if sufficient permissions exist to create and write to files to a directory. To this end, I have the following function that attempts to create a file and write a single byte to it, deleting itself afterwards to test that permissions do exist.

I figured the best way to check was to actually try and do it, catching any exceptions that occur. I'm not particularly happy about the general Exception catch though, so is there a better or perhaps a more accepted way of doing this?

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;
        }
    }
Guileless answered 15/8, 2009 at 10:40 Comment(3)
Thanks for the code, though one thing, the caller may get the false impression that write permission is missing if the user is able to write but not able to delete. I would change this to use FileMode.Create and get rid of the file deletion. Obviously you won't need this code anymore, but I write this for the benefit of future readers.Symmetrize
string fullPath = directory + TEMP_FILE; Please use Path.Combine method instead of concatenating strings to get fullPath. Path.Combine(directory, TEMP_FILE)Kun
What if someone punches in and then punches out the next day. What if they punch in and then punch out two days later? I'm sure people aren't supposed to do those things, but the behavior should be defined.Bouzoun
M
28

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.

Masked answered 15/8, 2009 at 12:23 Comment(6)
It is also the only reliable method as otherwise someone could change the permission between checking and actually trying save (unlikely, but possible).Columnar
Thanks for this. So the only change I should do to my code is to catch a security exception instead of the general 'Exception'?Guileless
@Guileless - yes, it's the path of least resistance unless you want to write the code to compute effective permissions.Masked
why does everything have to be so darned complicated!Holbrook
Things only appear complicated until you understand them. It's not that hard. You just get the Access Control List with Directory.GetAccessControl, then you get the AccessRules and look for Allow or Deny permissions on the rules your interested in (such as Read, Write, etc.). If you find NO rules, you have no access. If you find at least one Allow, then you have access, UNLESS you find even a single Deny. It's very basic security principles. See the other answer by Richard: https://mcmap.net/q/120328/-checking-for-directory-and-file-write-permissions-in-netWrongdoer
@Wrongdoer - I suggest you read the article I quoted: groups.google.com/group/… - computing effective permissions is not as simple as it sounds. Be my guest and supply an answer to prove me wrong.Masked
E
54

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

Experiment answered 15/8, 2009 at 10:53 Comment(13)
That will, of course, throw an exception if you can't actually get the ACL on the directory.Straightway
What does it check for? That directory has Write permissions, but for which user? :)Minoru
It works if you just want to see if current user has write access.Selfmortification
@aloneguid: The "GetAccessRules" methods returns an AuthorizationRuleCollection. The AthorizationRule class has an IdentityReference property, whose runtime type will actually be one of the two that derive from the IdenityReference type (either NTAccount or Security), which you can see is specified in the call to GetAccessRules. It is through the IdentityReference instance (or its derived types), that you can discover to which user the rule applies. It will be in the form of an SID or an NTAccount name.Wrongdoer
Also note that since the AccessControlType is for the rule as a whole, and the rule can specify multiple file system rights, there will be a separate rule for Allow permissions and Deny permissions for any single user (i.e. if both allow and deny permissions are set for a single user, then at least two rules will appear for that user. You can get the current user by calling WindowsIdentity.GetCurrent(), and add an additional check in the code above to see whether the rule applies to the current user.Wrongdoer
You may want to check the Groups member of the WindowsIdentity, because it will list the groups the user belongs to, and those groups may appear in the ACL as NTAccounts.Wrongdoer
Try running this on your system disk on a windows 7 with a non admin application, it will return true, but when you try to write to the c:\ you will get a exception stating that you don't have access!Straightforward
This does not take into account whether a rule is inherited or not. Local rules take precedence over inherited rules.Syncytium
Directory.GetAccessControl(path) throws an {System.UnauthorizedAccessException)} - this should be caught in an exception handler i think ..Catherin
I agree. This returns some false trues for the current user.Palatial
This no longer works with .Net Core 3.1. GetAccessControl(path) no longer exists as a member of the Directory class. It is also not a member of the DirectoryInfo class either. I've just been beating my head for hours just trying to finda way to check if I can search a directory or not. Yay, Microsoft! Directory.Exists always returns true now permission or not.Mcmorris
This ignores the possibility of ACEs having an access mask with generic bits set, which I have seen in the real world.Jacobus
Careful: If you have multiple users/groups and you deny permissions on any user/group different than yourself, this function will return false. You need to somehow validate the current user as well.Satisfied
S
36

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.

Syncytium answered 25/2, 2014 at 16:29 Comment(3)
I think this answer is the best way to accomplish it, other answers use the same way to get the result too but since only this answer calculates the inherited rules and local rules it is the most accurate one I guess. Thanks&Congrats.Sitzmark
Old thread. but how do I actually get the parameter FileSystemRights right to pass into the methods HasAccessHogtie
FileSystemRights is an enum having constants like ReadData, WriteData and so on. You have to pass in the enum constants you are interested in. They are flag values, so you can combine them like this: FileSystemRights.ReadData | FileSystemRights.ListDirectory.Syncytium
M
28

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.

Masked answered 15/8, 2009 at 12:23 Comment(6)
It is also the only reliable method as otherwise someone could change the permission between checking and actually trying save (unlikely, but possible).Columnar
Thanks for this. So the only change I should do to my code is to catch a security exception instead of the general 'Exception'?Guileless
@Guileless - yes, it's the path of least resistance unless you want to write the code to compute effective permissions.Masked
why does everything have to be so darned complicated!Holbrook
Things only appear complicated until you understand them. It's not that hard. You just get the Access Control List with Directory.GetAccessControl, then you get the AccessRules and look for Allow or Deny permissions on the rules your interested in (such as Read, Write, etc.). If you find NO rules, you have no access. If you find at least one Allow, then you have access, UNLESS you find even a single Deny. It's very basic security principles. See the other answer by Richard: https://mcmap.net/q/120328/-checking-for-directory-and-file-write-permissions-in-netWrongdoer
@Wrongdoer - I suggest you read the article I quoted: groups.google.com/group/… - computing effective permissions is not as simple as it sounds. Be my guest and supply an answer to prove me wrong.Masked
G
19

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;
    }
}
Gab answered 31/3, 2011 at 17:13 Comment(6)
This one does not work for groups but for literally added account names only in my caseBarbi
So is this something to do with "(S-1-5-21-397955417-626881126-188441444-512)" type format? Did converting the string to a SecurityIdentifier like that fix your issue? It's not clear from your comment whether it works now for you or not.Gab
When you put "rule.IdentityReference.Value" as parameter of currentuser.IsInRole() you use IsInRole(string) method which tries to match by regular "domain\user" value. So you are pushing SID string instead of user name string. However if you use my line in front of that you will get SecurityIdentifier object which match the user of given SID. That "string" argument overload is small trap for devs, once again it accepts account or group name in human redeable format not SID string representation.Barbi
The problem is that "new SecurityIdentifier(SDDLFormat)" doesn't work with normal group names (you get an argment exception). So I added a check for whether it's in SDDL format.Gab
@Bryce Wagner one of the "IF" statements should be removed because variable "sid" isn't in scope if (!currentuser.IsInRole(sid)) { continue; }Maniac
This solution worked for me, but had one issue with network folder. The folder has access rule allowing write to BUILTIN\Administrators. And as I'm an administrator at my local station, the snippet mistakenly returned true.Elegancy
Q
7

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.

Quixotism answered 15/8, 2009 at 11:20 Comment(1)
You probably meant to say 'This method is easier and much less error prone.'Judas
V
4

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.

Videlicet answered 15/8, 2009 at 11:9 Comment(3)
typeof returns the type of an object, in this case NTAccount. learn.microsoft.com/en-us/dotnet/csharp/language-reference/… The call to GetAccessRules() needs the type of the account when called. msdn.microsoft.com/en-us/library/…Videlicet
Why use NTAccount? always use NTAccount ?Joktan
In this case, yes. NTAccount represents a user account on a Windows PC, which is why we need it in the above code.Videlicet
N
1

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.")]
Nachison answered 11/4, 2018 at 1:57 Comment(0)
T
1

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.")
    };
}
Televisor answered 22/4, 2020 at 15:30 Comment(0)
U
-2
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);

        }
Uncomfortable answered 10/12, 2016 at 15:36 Comment(2)
What is WellKnownSidType.WorldSid ?Joktan
Just a a random answer to your question @Joktan WorldSid is the "Everyone" buildin group.Lan

© 2022 - 2024 — McMap. All rights reserved.