views:

150

answers:

2

I'm working on a piece of software that needs to copy a file to a given directory on the filesystem. It needs to work on both UAC-aware OSs (Vista, 7) as well as XP. To get around the issue of writing to a directory where UAC elevation is required, the app actually kicks off another process with a manifest that states that UAC is required. This generates the prompt and then does the copy when the user confirms.

From what I can see, a directory can have three different logical permission states - writeable without UAC elevation, writeable with UAC elevation and not writeable.

My question is this: For a given directory, how do I reliably determine whether the current user can copy (and potentially overwrite) a file to that directory, and if I can, how do I determine if UAC elevation is required?

On XP, this could just be as simple as checking whether the 'Allow Write' permission is granted, but on Vista / 7, there are directories where this permission isn't granted, but this action is still possible with UAC.

A: 

We have a method for WriteAccess on files, you can probably adapt it for Directories (Directory.GetAccessControl and so on)

    /// <summary> Checks for write access for the given file.
    /// </summary>
    /// <param name="fileName">The filename.</param>
    /// <returns>true, if write access is allowed, otherwise false</returns>
    public static bool WriteAccess(string fileName)
    {
        if ((File.GetAttributes(fileName) & FileAttributes.ReadOnly) != 0)
            return false;

        // Get the access rules of the specified files (user groups and user names that have access to the file)
        var rules = File.GetAccessControl(fileName).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));

        // Get the identity of the current user and the groups that the user is in.
        var groups = WindowsIdentity.GetCurrent().Groups;
        string sidCurrentUser = WindowsIdentity.GetCurrent().User.Value;

        // Check if writing to the file is explicitly denied for this user or a group the user is in.
        if (rules.OfType<FileSystemAccessRule>().Any(r => (groups.Contains(r.IdentityReference) || r.IdentityReference.Value == sidCurrentUser) && r.AccessControlType == AccessControlType.Deny && (r.FileSystemRights & FileSystemRights.WriteData) == FileSystemRights.WriteData))
            return false;

        // Check if writing is allowed
        return rules.OfType<FileSystemAccessRule>().Any(r => (groups.Contains(r.IdentityReference) || r.IdentityReference.Value == sidCurrentUser) && r.AccessControlType == AccessControlType.Allow && (r.FileSystemRights & FileSystemRights.WriteData) == FileSystemRights.WriteData);
    }

Hope this helps.

testalino
Thanks - I just tested this out, and whilst this tells me whether I can write under the current identity, it returns false if both write access is explicitly denied as well as if it's permitted with a UAC elevation. I need to differentiate between these last two situations. I'll take it as a starting point though.
growse
+1  A: 

You handle the writable without elevation case just by trying the operation. It's when that fails, and you have to distinguish between not-writable vs writable via UAC elevation that is potentially difficult.

I don't think that I would like programs trying to figure that out for me (since they'll inevitably get it wrong quite often).

I think it's safe to design it with these assumptions:

  • Administrators sometimes run as restricted accounts to trial software they don't trust -> if your app is going to make invasive changes to the computer that require UAC they want to cancel, not elevate.
  • Elevated administrators can write the file (they are admin after all) -> no need for an actual ACL check, detecting a restricted token is enough.
  • Users may elevate using a different account, or may ask a co-worker to complete the UAC-required action -> checking for the restricted token will miss these cases.
  • Other recoverable things cause access denied, including file in use -> sometimes the right thing to do is retry using the same restricted permissions.

So altogether, I would suggest trying the operation AsInvoker, in case of access denied bring up a prompt that explains that Windows denied the operation, possible causes are: file-in-use, elevation required, administrator credentials required, and give the user three buttons:

  • Cancel
  • Retry with current credentials
  • (shield icon) Elevate permissions and retry
Ben Voigt
I had thought about going down the "try everything to see what works" approach, but had wondered if there was a better way. It shouldn't be too difficult to attempt the write as the current user, and if that fails kick off the process that elevates to UAC. If that fails, alert that the copy isn't possible.
growse