0

I occasionally migrate some website from one web server to another.

After copying all files from the old server to the new server, it takes me quite some time to get (re)acquainted with which folders or files need to be writable by IIS. (Sounds familiar, by the way ? :) )

I have written a WinForms application that allows me to select a starting directory. The application should (recursively) compare if the security permissions of each file/directory are equal to that of its parent directory.

I want to use this application on the old server to scan for directories with different permissions.

Example: C:\MySites\Uploads does not have the same permissions set as its parent directory. (This folder was writable for the IIS user 'IUSR', while its parent folder was only readable.)

The application is almost complete in the sense that I manage to traverse all directories and files. I just need to compare their permissions!

Can you please help? Here is an excerpt of where I need your help.

string results = "";

string parentFolderPath = "c:\\someParentDir";
string childItemPath = "c:\\someParentDir\\SomeChildDir.ext";

DirectorySecurity parentFolderAccessControl = Directory.GetAccessControl(parentFolderPath);
DirectorySecurity childItemAccessControl = Directory.GetAccessControl(childItemPath);

if (!parentFolderAccessControl.Equals(childItemAccessControl)) // <-- D'oh here
{
    results += childItemPath + " does not have the same permissions set as its parent directory.\n";
}

The if is always true, because the DirectorySecurities are never equal. (I understand why that is: reference to different memory allocations ... blah blah.) But what would be the best way to compare the DirectorySecurities?

nl-x
  • 11,762
  • 7
  • 33
  • 61
  • I think this answer will probably help you. http://stackoverflow.com/a/1281638/1181408 Essentially you'll want to call GetAccessRules on your DirectorySecurity objects and then iterate over the resulting collection. – cgotberg Jun 10 '13 at 17:48
  • @cgotberg That sounds rather expensive. Get both collections. Check if users in collection1 exist in collection2 and other way around. And then foreach user check if access is equal. That was exactly my starting point, but I'm hoping someone here has a better solution. – nl-x Jun 10 '13 at 18:09
  • I suppose it's expensive but in this concept of occasionally needing to migrate code it probably doesn't matter. You can use this generic .net compare objects code to do a deep compare and that's probably as expensive as it will ever get. http://comparenetobjects.codeplex.com/ – cgotberg Jun 10 '13 at 19:07
  • @cgotberg I ended up just doing it. Maybe you have some feedback on the code I pasted in my own answer below – nl-x Jun 11 '13 at 14:55

2 Answers2

2

This actually became much more complex while I was working it out, because Windows rights can:

  • split up into Allow and Deny
  • Fragmented over multiple entries (multiple entries per user per Allow/Deny)

Eventually, this is what I made out of it:

private bool compareAccessControls(
    DirectorySecurity parentAccessControl,
    DirectorySecurity childAccessControl,
    out Dictionary<IdentityReference, FileSystemRights> accessAllowRulesGainedByChild,
    out Dictionary<IdentityReference, FileSystemRights> accessDenyRulesGainedByChild,
    out Dictionary<IdentityReference, FileSystemRights> accessAllowRulesGainedByParent,
    out Dictionary<IdentityReference, FileSystemRights> accessDenyRulesGainedByParent
)
{
    // combine parent access rules

    Dictionary<IdentityReference, FileSystemRights> combinedParentAccessAllowRules = new Dictionary<IdentityReference, FileSystemRights>();
    Dictionary<IdentityReference, FileSystemRights> combinedParentAccessDenyRules = new Dictionary<IdentityReference, FileSystemRights>();
    foreach (FileSystemAccessRule parentAccessRule in parentAccessControl.GetAccessRules(true, true, typeof(NTAccount)))
    {
        if (parentAccessRule.AccessControlType == AccessControlType.Allow)
            if (combinedParentAccessAllowRules.ContainsKey(parentAccessRule.IdentityReference))
                combinedParentAccessAllowRules[parentAccessRule.IdentityReference] = combinedParentAccessAllowRules[parentAccessRule.IdentityReference] | parentAccessRule.FileSystemRights;
            else
                combinedParentAccessAllowRules.Add(parentAccessRule.IdentityReference, parentAccessRule.FileSystemRights);
        else
            if (combinedParentAccessDenyRules.ContainsKey(parentAccessRule.IdentityReference))
                combinedParentAccessDenyRules[parentAccessRule.IdentityReference] = combinedParentAccessDenyRules[parentAccessRule.IdentityReference] | parentAccessRule.FileSystemRights;
            else
                combinedParentAccessDenyRules.Add(parentAccessRule.IdentityReference, parentAccessRule.FileSystemRights);
    }

    // combine child access rules

    Dictionary<IdentityReference, FileSystemRights> combinedChildAccessAllowRules = new Dictionary<IdentityReference, FileSystemRights>();
    Dictionary<IdentityReference, FileSystemRights> combinedChildAccessDenyRules = new Dictionary<IdentityReference, FileSystemRights>();
    foreach (FileSystemAccessRule childAccessRule in childAccessControl.GetAccessRules(true, true, typeof(NTAccount)))
    {
        if (childAccessRule.AccessControlType == AccessControlType.Allow)
            if (combinedChildAccessAllowRules.ContainsKey(childAccessRule.IdentityReference))
                combinedChildAccessAllowRules[childAccessRule.IdentityReference] = combinedChildAccessAllowRules[childAccessRule.IdentityReference] | childAccessRule.FileSystemRights;
            else
                combinedChildAccessAllowRules.Add(childAccessRule.IdentityReference, childAccessRule.FileSystemRights);
        else
            if (combinedChildAccessDenyRules.ContainsKey(childAccessRule.IdentityReference))
                combinedChildAccessDenyRules[childAccessRule.IdentityReference] = combinedChildAccessDenyRules[childAccessRule.IdentityReference] | childAccessRule.FileSystemRights;
            else
                combinedChildAccessDenyRules.Add(childAccessRule.IdentityReference, childAccessRule.FileSystemRights);
    }

    // compare combined rules

    accessAllowRulesGainedByChild = new Dictionary<IdentityReference, FileSystemRights>();
    foreach (KeyValuePair<IdentityReference, FileSystemRights> combinedChildAccessAllowRule in combinedChildAccessAllowRules)
    {
        if (combinedParentAccessAllowRules.ContainsKey(combinedChildAccessAllowRule.Key))
        {
            FileSystemRights accessAllowRuleGainedByChild = combinedChildAccessAllowRule.Value & ~combinedParentAccessAllowRules[combinedChildAccessAllowRule.Key];
            if (accessAllowRuleGainedByChild != default(FileSystemRights))
                accessAllowRulesGainedByChild.Add(combinedChildAccessAllowRule.Key, accessAllowRuleGainedByChild);
        }
        else
        {
            accessAllowRulesGainedByChild.Add(combinedChildAccessAllowRule.Key, combinedChildAccessAllowRule.Value);
        }
    }

    accessDenyRulesGainedByChild = new Dictionary<IdentityReference, FileSystemRights>();
    foreach (KeyValuePair<IdentityReference, FileSystemRights> combinedChildAccessDenyRule in combinedChildAccessDenyRules)
    {
        if (combinedParentAccessDenyRules.ContainsKey(combinedChildAccessDenyRule.Key))
        {
            FileSystemRights accessDenyRuleGainedByChild = combinedChildAccessDenyRule.Value & ~combinedParentAccessDenyRules[combinedChildAccessDenyRule.Key];
            if (accessDenyRuleGainedByChild != default(FileSystemRights))
                accessDenyRulesGainedByChild.Add(combinedChildAccessDenyRule.Key, accessDenyRuleGainedByChild);
        }
        else
        {
            accessDenyRulesGainedByChild.Add(combinedChildAccessDenyRule.Key, combinedChildAccessDenyRule.Value);
        }
    }

    accessAllowRulesGainedByParent = new Dictionary<IdentityReference, FileSystemRights>();
    foreach (KeyValuePair<IdentityReference, FileSystemRights> combinedParentAccessAllowRule in combinedParentAccessAllowRules)
    {
        if (combinedChildAccessAllowRules.ContainsKey(combinedParentAccessAllowRule.Key))
        {
            FileSystemRights accessAllowRuleGainedByParent = combinedParentAccessAllowRule.Value & ~combinedChildAccessAllowRules[combinedParentAccessAllowRule.Key];
            if (accessAllowRuleGainedByParent != default(FileSystemRights))
                accessAllowRulesGainedByParent.Add(combinedParentAccessAllowRule.Key, accessAllowRuleGainedByParent);
        }
        else
        {
            accessAllowRulesGainedByParent.Add(combinedParentAccessAllowRule.Key, combinedParentAccessAllowRule.Value);
        }
    }

    accessDenyRulesGainedByParent = new Dictionary<IdentityReference, FileSystemRights>();
    foreach (KeyValuePair<IdentityReference, FileSystemRights> combinedParentAccessDenyRule in combinedParentAccessDenyRules)
    {
        if (combinedChildAccessDenyRules.ContainsKey(combinedParentAccessDenyRule.Key))
        {
            FileSystemRights accessDenyRuleGainedByParent = combinedParentAccessDenyRule.Value & ~combinedChildAccessDenyRules[combinedParentAccessDenyRule.Key];
            if (accessDenyRuleGainedByParent != default(FileSystemRights))
                accessDenyRulesGainedByParent.Add(combinedParentAccessDenyRule.Key, accessDenyRuleGainedByParent);
        }
        else
        {
            accessDenyRulesGainedByParent.Add(combinedParentAccessDenyRule.Key, combinedParentAccessDenyRule.Value);
        }
    }

    if (accessAllowRulesGainedByChild.Count > 0 || accessDenyRulesGainedByChild.Count > 0 || accessAllowRulesGainedByParent.Count > 0 || accessDenyRulesGainedByParent.Count > 0)
        return false;
    else
        return true;
}
nl-x
  • 11,762
  • 7
  • 33
  • 61
  • I thought this seemed a little absurd at first, but I ended up using it. Thanks! – Randy H. Jun 18 '15 at 16:17
  • BTW, I made it an extension method on FileSystemSecurity so that it can be used for both files and directories. I also added parameters so that includeExplicit and includeInherited parameters for the GetAccessRules calls can be changed. `public static bool IsEqual(this FileSystemSecurity sourceAccessControl, FileSystemSecurity destAccessControl, bool includeExplicit, bool includeInherited)` – Randy H. Jun 18 '15 at 16:20
1

You cannot use Equals() since this method is inherited from Object. You need to find a identifying attribute on that DirectorySecurity class. I think String GetSecurityDescriptorSddlForm()

should do your job. You can invoke Equals() on that.

Edit: Well sorry, this method needs a parameter for invocation. Try finding another attribute on the DirectorySecurity which is better for comparison.

Edit2: I'm not familar with .NET Security Framework and Right-Management, but something like this should be your approach. You can do != resp: == on FileSystemAccessRule.FileSystemRights because that attribute is an enum (internally an int).

ArrayList notIdenticalList = new ArrayList(); 

        DirectorySecurity parentFolderAccessControl = Directory.GetAccessControl(null);
        DirectorySecurity childItemAccessControl = Directory.GetAccessControl(null);
        foreach (FileSystemAccessRule parentRule in parentFolderAccessControl.GetAccessRules(true, true, typeof(NTAccount)))
        {
            foreach (FileSystemAccessRule childRule in childItemAccessControl.GetAccessRules(true, true, typeof(NTAccount)))
            {
                if (parentRule.FileSystemRights != childRule.FileSystemRights)
                {
                    // add to not identical-list
                    notIdenticalList.Add(fileToAdd...);
                    break;
                }
            }
        }
Stefano L
  • 1,486
  • 2
  • 15
  • 36
  • Is that function sensitive to in which order the user accounts are listed that have their specific access? Or does it always first sort on user accounts? Example: let's say both the System account and the IUSR account have write access to a directory AND its parent directory. But for the first directory the System account is listed first and for the parent directory the IUSR is listed first... ? – nl-x Jun 10 '13 at 17:55
  • do you mean Equals() or what function? – Stefano L Jun 10 '13 at 18:36
  • No. I meant the function that you suggested. Whether or not the string could be different (different user account order) while the effective permissions are the same. – nl-x Jun 10 '13 at 19:24
  • Sorry that I took the night off :) ... Appreciate your help!. Funny, I came to using `SecurityIdentifier` in stead of `NTAccount` before I saw your edit. Do you know the big difference? Should both work on any Windows-XP-and-newer machine ? – nl-x Jun 11 '13 at 07:31
  • By the way, your current code now checks of both files, if ALL users have the same permissions to it. Eg. User "System" has ALL privileges to folder "A", while User "Somebody" has only read access to folder "B". ==> notIdenticalList ... But I can manage to fix that. – nl-x Jun 11 '13 at 08:02
  • I ended up just doing what I had in mind in the first place. Maybe you have some feedback on the code I pasted in my own answer. – nl-x Jun 11 '13 at 14:56