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