当文件被拒绝访问与目录被拒绝访问时,File.Exists 的行为不同

Posted

技术标签:

【中文标题】当文件被拒绝访问与目录被拒绝访问时,File.Exists 的行为不同【英文标题】:File.Exists acts differently when access is denied to the file vs denied to the dir 【发布时间】:2016-04-25 16:57:54 【问题描述】:

基于File.Exists 的MSDN documentation,File.Exists 方法应在出现任何错误时返回false,包括调用者无权读取文件。

我希望它在文件设置为 FullControl 拒绝用户和 FullControl 拒绝用户到文件所在的目录时返回 false

我看到的是当用户可以访问目录而不是文件时,File.Exists 返回true;但是,如果用户无权访问该目录,File.Exists 将返回 false

我写了一个小程序来演示我在说什么:

using System;
using System.DirectoryServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;

namespace ConsoleApplication1

    internal class Program
    
        private const string DirName = "TestDir";
        private const string FileName = "File.txt";
        private const string Password = "Password1";
        private const string UserName = "PermissionTestUser";
        private static WindowsImpersonationContext Identity = null;
        private static IntPtr LogonToken = IntPtr.Zero;

        public enum LogonProvider
        
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35 = 1,
            LOGON32_PROVIDER_WINNT40 = 2,
            LOGON32_PROVIDER_WINNT50 = 3
        ;

        public enum LogonType
        
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
            LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
        ;

        public static void Main(string[] args)
        
            string filePath = Path.Combine(DirName, FileName);
            try
            
                CreateUser();
                CreateDir();
                CreateFile(filePath);

                // grant user full control to the dir
                SetAccess(DirName, AccessControlType.Allow);
                // deny user full control to the file
                SetAccess(filePath, AccessControlType.Deny);

                // impersonate user
                Impersonate();
                Console.WriteLine("File.Exists (with dir permissions): 0", File.Exists(filePath));
                UndoImpersonate();

                // deny access to dir
                SetAccess(DirName, AccessControlType.Deny);

                // impersonate user
                Impersonate();
                Console.WriteLine("File.Exists (without dir permissions): 0", File.Exists(filePath));
                UndoImpersonate();
            
            finally
            
                UndoImpersonate();
                DeleteDir();
                DeleteUser();
            
        

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        private static void CreateDir()
        
            Directory.CreateDirectory(DirName);
        

        private static void CreateFile(string path)
        
            File.Create(path).Dispose();
        

        private static void CreateUser()
        
            DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
            DirectoryEntry newUser = ad.Children.Add(UserName, "user");
            newUser.Invoke("SetPassword", new object[]  Password );
            newUser.Invoke("Put", new object[]  "Description", "Test user" );
            newUser.CommitChanges();
        

        private static void DeleteDir()
        
            Directory.Delete(DirName, true);
        

        private static void DeleteUser()
        
            DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
            DirectoryEntries users = ad.Children;
            DirectoryEntry user = users.Find(UserName, "user");

            if (user != null)
            
                users.Remove(user);
            
        

        private static void Impersonate()
        
            if (LogonUser(UserName, ".", Password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref LogonToken))
            
                Identity = WindowsIdentity.Impersonate(LogonToken);
                return;
            
        

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(string lpszUserName,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        private static void SetAccess(string path, AccessControlType type)
        
            FileSecurity fs = File.GetAccessControl(path);
            FileSystemAccessRule far = new FileSystemAccessRule(UserName, FileSystemRights.FullControl, type);
            fs.AddAccessRule(far);
            File.SetAccessControl(path, fs);
        

        private static void UndoImpersonate()
        
            if (Identity != null)
            
                Identity.Undo();
                Identity = null;
            

            if (LogonToken != IntPtr.Zero)
            
                CloseHandle(LogonToken);
                LogonToken = IntPtr.Zero;
            
        
    

运行这个程序的结果是:

File.Exists (with dir permissions): True
File.Exists (without dir permissions): False

谁能解释他们为什么不同?在这两种情况下,用户都没有文件的读取权限。

【问题讨论】:

【参考方案1】:

这是File.Exist 的默认行为。根据MSDN:

File.Exist

返回值类型:System.Boolean

true 如果调用者有 必需的权限和路径包含现有的名称 文件;否则为假。如果 path 是,此方法也返回 false null、无效路径或零长度字符串。如果调用者不 有足够的权限读取指定的文件,也不例外 被抛出并且该方法返回 false 而不管是否存在 路径。

另外

Exists方法不应该用于路径验证,这个方法 仅检查 path 中指定的文件是否存在。传递无效 Exists 的路径返回 false

换句话说,这里的所需权限,是知道文件存在所需的权限(正如方法名称所暗示的,File.Exist)。这意味着只要用户有权访问该目录,它就可以知道该文件是否存在

在给定目录权限的情况下,用户是否具有文件访问权限并不影响用户对文件存在的认识。但是没有目录权限,用户无法知道文件的存在,因此File.Exist返回false


编辑(根据 cmets 的反馈):

可能相当令人困惑的部分是最后一句话:

如果调用者没有 有足够的权限读取指定的文件,也不例外 被抛出并且该方法返回 false 而不管是否存在 路径。

读取指定文件的足够权限取决于目录的读取权限,而不是指定文件的读取权限。 (Rob先生的补充评论)。 “足够”一词可能会暗示其行为将取决于需要对 目录的读取访问权限,对指定文件的读取权限。

但我承认,解释和用词可能听起来相当反直觉,因为人们可能会直观地将“读取指定文件的足够权限”解释为对指定文件的读取权限而不是到父目录。

【讨论】:

如果不是MSDN解释中的最后一句,您的响应将非常有意义,“如果调用者没有足够的权限来读取指定的文件,则不会抛出异常并且方法无论路径是否存在,都返回 false。"由于我明确删除了读取访问权限,我希望它返回 false... @Middas 你是对的“读取指定文件的足够权限”这可能意味着可以访问目录或可以访问文件。但如果这意味着可以访问目录,我也会认为这样写是相当含糊的。注意“足够”这个词。 感谢您的解释,“足够”这个词非常违反直觉。我希望他们能更新文章以使其更加清晰。 @Middas 要查看文件取决于父目录的读取权限,而不是文件的读取权限。 @Middas 没问题。很高兴它可以提供任何帮助。顺便说一句,好问题和实验。这也让我更好地理解。 :)【参考方案2】:

如果文件确实存在,但File.Exists(filePath)返回false,则说明应用程序对目录和文件都没有有读权限,可以证明。

如果你真的想测试一个文件是否存在,你可以调用File.GetAccessControl(filePath)。如果调用无异常返回,则说明文件确实存在。

【讨论】:

以上是关于当文件被拒绝访问与目录被拒绝访问时,File.Exists 的行为不同的主要内容,如果未能解决你的问题,请参考以下文章

dotnet 6.0 C# - 访问路径被拒绝

为啥从目录执行 ssis 包时访问文件路径被拒绝消息

访问路径被拒绝

“对路径的访问被拒绝.” 是怎么回事

c#winForm动态创建文件夹访问被拒绝

未经授权的访问异常 - 在C#中将文件复制到其他目录时访问被拒绝的路径