Search code examples
c#windowsfilepermissionsuser-accounts

API calls claim a user has folder permissions when they don't


Having an issue with a Windows service that needs to monitor/have access to a set of folders, and move files around between those folders.

There's have a bit of boilerplate code that's been used in the past, which will check a given folder for the specific granular permissions for the given user. The odd thing is that I discovered through testing that if I manually deny all permissions on that folder for the account the service is running under, and then run the code, it reports that all is well and the user does in fact have those permissions, even though it's obvious (and demonstrable) that he doesn't.

At first I thought this might be because the service was running under the local System account, but the same issue crops up if it is run with NetworkService as well as with a local user account. This is on Windows 7/2008 R2.

Boilerplate method:

public static void ValidateFolderPermissions(WindowsIdentity userId, string folder, FileSystemRights[] requiredAccessRights)
    {
        SecurityIdentifier secId;
        StringBuilder sb = new StringBuilder();
        bool permissionsAreSufficient = false;
        bool notAuthorized = false;
        String errorMsg = String.Empty;

        IdentityReferenceCollection irc = userId.Groups;

        foreach (IdentityReference ir in irc)
        {
            secId = ir.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;

            try
            {
                DirectoryInfo dInfo = new DirectoryInfo(folder);
                DirectorySecurity dSecurity = dInfo.GetAccessControl();
                AuthorizationRuleCollection rules = dSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier));
                foreach (FileSystemAccessRule ar in rules)
                {
                    if (secId.CompareTo(ar.IdentityReference as SecurityIdentifier) == 0)
                    {
                        sb.AppendLine(ar.FileSystemRights.ToString());

                        foreach (FileSystemRights right in requiredAccessRights)
                        {
                            if (right == ar.FileSystemRights)
                            {
                                permissionsAreSufficient = true;
                                break;
                            }
                        }
                    }
                }
            }
            catch (UnauthorizedAccessException)
            {
                notAuthorized = true;
                errorMsg = "user not authorized";
            }
            catch (SecurityException)
            {
                // If we failed authorization do not update error 
                if (!notAuthorized)
                    errorMsg = "security error";
            }
            catch (Exception)
            {
                // If we failed authorization do not update error 
                if (!notAuthorized)
                    errorMsg = "invalid folder or folder not accessible";
            }
        }

        if (!permissionsAreSufficient)
        {
            if (!String.IsNullOrEmpty(errorMsg))
                throw new Exception(String.Format("User {0} does not have required access to folder {1}. The error is {2}.", userId.Name, folder, errorMsg));
            else
                throw new Exception(String.Format("User {0} does not have required access rights to folder {1}.", userId.Name, folder));
        }
    }

And the calling snippet:

FileSystemRights[] requireAccessRights = 
        {
            FileSystemRights.Delete,
            FileSystemRights.Read,
            FileSystemRights.FullControl
        };

        try
        {
            FolderPermissionValidator.ValidateFolderPermissions(WindowsIdentity.GetCurrent(), inputFolder, requireAccessRights);
            Log.Debug("In ServiceConfigurationValidator: {0}, {1}", WindowsIdentity.GetCurrent().Name, inputFolder);
        }
        catch (Exception ex)
        {
            Log.Debug("Throwing exception {0}", ex.Message);
        }

Solution

  • This code enumerates the entries in the ACL as FileSystemAccessRule objects, but doesn't bother to check whether AccessControlType is allow or deny.

    I also note that the logic returns true if any ACE exactly matches any of the elements of the requiredAccessRights array; I suspect the intended behaviour is that it return true if all of the specified rights are present. This could cause false positives if only some of the requested rights are present, but because it only looks for exact matches it could also cause a false negative, e.g., if the ACE actually gives more rights than are being requested. (Not such a problem in the example given, though, because you're asking for Full Control.)

    Another flaw is that it only checks for access entries matching groups the user belongs to; access entries for the user account itself will be ignored. (I'm not sure what the behaviour of WindowsIdentity.Groups is for security primitives such as SYSTEM and NetworkService that are not actual user accounts, although it sounds like that part was working as desired.)

    Note that because it is very hard to cope properly with all the possible situations (consider, e.g., an access control entry for Everyone, or for SERVICE) it would be wise to allow the administrator to override the check if it is mistakenly reporting that the account doesn't have the necessary access.