Programmatically Getting Effective Directory/File Permissions

While working on integrating Active Directory into our product I had a need to determine a domain user’s effective permissions on a directory.  Surprisingly, I could not find that many current C# examples so I figured that I would post the code that I came up with…

The first step was to get a list of Security Identifiers.  I tried a couple different approaches to get the SIDs.  In my first attempt, I figured that I would need to impersonate the user.  I found a class to handle the impersonation.  This class was easy to use… all I need to do was pass the user name, domain, and password into the constructor.  This approached worked fine in my test application, but when I tried to incorporate it in my production application, I realized that I didn’t have the user’s domain password available.  I considered a little refactoring to make the password available but decided that doing so just was not a good idea.

My next idea was to query Active Directory.  Using AD worked well for getting the user SID, but didn’t work out so well for the groups – it was missing the local machine groups.   I decided to use WindowsIdentity to retrieve the groups.

GetSecurityIdentifierArray:

private static string[] GetSecurityIdentifierArray(string userName) {
    // connect to the domain
    PrincipalContext pc = new PrincipalContext(ContextType.Domain);
    
    // search for the domain user
    UserPrincipal user = new UserPrincipal(pc) { SamAccountName = userName };
    PrincipalSearcher searcher = new PrincipalSearcher { QueryFilter = user };
    user = searcher.FindOne() as UserPrincipal;
 
    if (user == null) {
        throw new ApplicationException(string.Format("Invalid User Name:  {0}", userName));
    }
 
    // use WindowsIdentity to get the user's groups
    WindowsIdentity windowsIdentity = new WindowsIdentity(user.UserPrincipalName);
    string[] sids = new string[windowsIdentity.Groups.Count + 1];
 
    sids[0] = windowsIdentity.User.Value;
 
    for (int index = 1, total = windowsIdentity.Groups.Count; index < total; index++) {
        sids[index] = windowsIdentity.Groups[index].Value;
    }
 
    return sids;
}

The next step was to get a list of Access Rules for the path filtered by the SIDs.  This was pretty easy… just needed to call GetAccessRules and then filter the list using the SIDs.  However, I must say that I did get tripped up by one oddity.  In my first attempt I used the DirectoryInfo class for directory paths and FileInfo class for file paths.  What I had noticed is that using DirectoryInfo would not return the correct rights.  Additionally, I noticed that it was returning an integer value that was outside the range of the FileSystemRights enum.  I suspect that this has something to do with using the same integer for more than one enum item.

image

I found that the FileInfo class could be used for the directory path.

GetAccessRulesArray:

private static FileSystemAccessRule[] GetAccessRulesArray(string userName, string path) {
    // get all access rules for the path - this works for a directory path as well as a file path
    AuthorizationRuleCollection authorizationRules = (new FileInfo(path)).GetAccessControl().GetAccessRules(true, true, typeof(SecurityIdentifier));
 
    // get the user's sids
    string[] sids = GetSecurityIdentifierArray(userName);
 
    // get the access rules filtered by the user's sids
    return (from rule in authorizationRules.Cast<FileSystemAccessRule>()
            where sids.Contains(rule.IdentityReference.Value)
            select rule).ToArray();
}

The final step in creating the Effective Rights was to merge all the Access Rules.  To do this, I first needed to combine all the Deny Access Rules and the Allow Access Rules separately.  Then remove all the denied rights from the allowed rights.

GetEffectiveRights:

private static FileSystemRights GetEffectiveRights(string userName, string path) {
    FileSystemAccessRule[] accessRules = GetAccessRulesArray(userName, path);
    FileSystemRights denyRights = 0;
    FileSystemRights allowRights = 0;
 
    for (int index = 0, total = accessRules.Length; index < total; index++) {
        FileSystemAccessRule rule = accessRules[index];
 
        if (rule.AccessControlType == AccessControlType.Deny) {
            denyRights |= rule.FileSystemRights;
        }
        else {
            allowRights |= rule.FileSystemRights;
        }
    }
 
    return (allowRights | denyRights) ^ denyRights;
}

Complete code:

public static class FileSystemEffectiveRights {
    public static FileSystemRights GetRights(string userName, string path) {
        if (string.IsNullOrEmpty(userName)) {
            throw new ArgumentException("userName");
        }
 
        if (!Directory.Exists(path) && !File.Exists(path)) {
            throw new ArgumentException(string.Format("path:  {0}", path));
        }
 
        return GetEffectiveRights(userName, path);
    }
 
    private static FileSystemRights GetEffectiveRights(string userName, string path) {
        FileSystemAccessRule[] accessRules = GetAccessRulesArray(userName, path);
        FileSystemRights denyRights = 0;
        FileSystemRights allowRights = 0;
 
        for (int index = 0, total = accessRules.Length; index < total; index++) {
            FileSystemAccessRule rule = accessRules[index];
 
            if (rule.AccessControlType == AccessControlType.Deny) {
                denyRights |= rule.FileSystemRights;
            }
            else {
                allowRights |= rule.FileSystemRights;
            }
        }
 
        return (allowRights | denyRights) ^ denyRights;
    }
 
    private static FileSystemAccessRule[] GetAccessRulesArray(string userName, string path) {
        // get all access rules for the path - this works for a directory path as well as a file path
        AuthorizationRuleCollection authorizationRules = (new FileInfo(path)).GetAccessControl().GetAccessRules(true, true, typeof(SecurityIdentifier));
 
        // get the user's sids
        string[] sids = GetSecurityIdentifierArray(userName);
 
        // get the access rules filtered by the user's sids
        return (from rule in authorizationRules.Cast<FileSystemAccessRule>()
                where sids.Contains(rule.IdentityReference.Value)
                select rule).ToArray();
    }
 
    private static string[] GetSecurityIdentifierArray(string userName) {
        // connect to the domain
        PrincipalContext pc = new PrincipalContext(ContextType.Domain);
        
        // search for the domain user
        UserPrincipal user = new UserPrincipal(pc) { SamAccountName = userName };
        PrincipalSearcher searcher = new PrincipalSearcher { QueryFilter = user };
        user = searcher.FindOne() as UserPrincipal;
 
        if (user == null) {
            throw new ApplicationException(string.Format("Invalid User Name:  {0}", userName));
        }
 
        // use WindowsIdentity to get the user's groups
        WindowsIdentity windowsIdentity = new WindowsIdentity(user.UserPrincipalName);
        string[] sids = new string[windowsIdentity.Groups.Count + 1];
 
        sids[0] = windowsIdentity.User.Value;
 
        for (int index = 1, total = windowsIdentity.Groups.Count; index < total; index++) {
            sids[index] = windowsIdentity.Groups[index].Value;
        }
 
        return sids;
    }
}

Example of use:

static class Program {
    [STAThread]
    static void Main() {
        string userName = "me";
        string path = @"c:\";
 
        FileSystemRights rights = FileSystemEffectiveRights.GetRights(userName, path);
        bool canWriteData = rights.HasRights(FileSystemRights.WriteData);
    }
}
 
public static class FileSystemRightsEx {
    public static bool HasRights(this FileSystemRights rights, FileSystemRights testRights) {
        return (rights & testRights) == testRights;
    }
}

Post to Twitter

Post a Comment

Your email is never shared. Required fields are marked *

*
*
Blog WebMastered by All in One Webmaster.