views:

373

answers:

2

I am wanting to list all the files in a folder that a user has read access to. The user visits the website and can use Forms Authentication for some aspects of the site (e.g. adding links etc), but I want to list files in a given folder using their Windows Credentials (since I have anonymous access turned off), hiding those they can't read.

However, when using Directory.GetFiles, it also includes files that can't be read (although the meta data (file size, creation date etc) can be read).

This is what I have:

string[] files;
string[] folders;
string rootDir = @"\\server\path\to\dir\";
WindowsIdentity id = (WindowsIdentity)User.Identity ;
using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(id.Token))
{
        files = Directory.GetFiles(rootDir);
        folders = Directory.GetDirectories(rootDir);

        foreach (string file in files)
        {
            FileInfo fi = new FileInfo(file);
            FileSecurity fs = fi.GetAccessControl(AccessControlSections.Access);
            //foreach (FileSystemAccessRule rule in fs.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)))
            //{
            //  Response.Write((FileSystemRights.Read & rule.FileSystemRights) + " <br />");
            //}

            Response.Write(file + " " + fi.Length + "<br />");
        }
        context.Undo();
}

When I visit the page, I get UnauthorizedAccessException as soon as as I use GetAccessControl, even though it should be using the current user credentials. Taking the using off fails as the asp.net account does not have access to the folder. When FileSecurity is commented out, it lists all the files.

Stack Trace:

[UnauthorizedAccessException: Attempted to perform an unauthorized operation.]
   System.Security.AccessControl.Win32.GetSecurityInfo(ResourceType resourceType, String name, SafeHandle handle, AccessControlSections accessControlSections, RawSecurityDescriptor& resultSd) +697
   System.Security.AccessControl.NativeObjectSecurity.CreateInternal(ResourceType resourceType, Boolean isContainer, String name, SafeHandle handle, AccessControlSections includeSections, Boolean createByName, ExceptionFromErrorCode exceptionFromErrorCode, Object exceptionContext) +63
   System.Security.AccessControl.FileSystemSecurity..ctor(Boolean isContainer, String name, AccessControlSections includeSections, Boolean isDirectory) +86
   System.Security.AccessControl.FileSecurity..ctor(String fileName, AccessControlSections includeSections) +42
   System.IO.FileInfo.GetAccessControl(AccessControlSections includeSections) +29
   UNCDirectory.Page_Load(Object sender, EventArgs e) +213
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   System.Web.UI.Control.LoadRecursive() +50
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627

Any ideas of how I can do this, without resorting to trying to open each file and catching the exception that occurs?

+1  A: 

You probably need to set up Windows/Integrated Authentication in asp.net/IIS. http://msdn.microsoft.com/en-us/library/aa292114(VS.71).aspx

Moron
The site is using Integrated Authentication and the fact it can retrieve the list of files shows that is the case (otherwise there will be an exception - different to the one I am getting). The error occurs when trying to get the access rules for a file.
Sam
A: 

Managed to fix it. When you deny Read on a file, it also denies read permissions, hence the UnauthorizedAccessException. So using try... catch ... looks like it is the only option.

It is possible to prevent the exception, if Read is denied and ReadPermissions is allowed (done under Advance settings). This handles both:

string[] files;
string[] folders;
WindowsIdentity id = (WindowsIdentity)User.Identity;
using (System.Security.Principal.WindowsImpersonationContext context = id.Impersonate())
{
    files = Directory.GetFiles(RootDir);
    folders = Directory.GetDirectories(RootDir);

    foreach (string file in files)
    {
        FileInfo fi = new FileInfo(file);
        FileSecurity fs = null;
        try
        {
            fs = fi.GetAccessControl(AccessControlSections.Access);
        }
        catch (UnauthorizedAccessException)
        {
            goto Next;
        }
        foreach (FileSystemAccessRule rule in fs.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)))
        {
            if (id.User.CompareTo(rule.IdentityReference as SecurityIdentifier) == 0)
            {
                if(rule.AccessControlType.ToString() == "Deny" &&
                    rule.FileSystemRights.ToString().Contains("ReadData"))
                {
                    goto Next;
                }
            }
        }

        Response.Write(file + " " + fi.Length + "<br />");
        // next in sequence. label for goto
        Next: ;
    }
    context.Undo();
}

Now files can be listed in any directory (using impersonation) without giving the ASP.NET account (usually Network Service) access as well.

Sam
I hate to nitpick... but why are you using goto and a label? just break or continue as necessary.
Justin Drury
Tried break but it only breaks the FileAccessRule foreach.. wanted to to break out of it and continue to the next file. Although I suppose a bool could have been used... Are label's not good to use and if so, why not?
Sam