Prueba de C # si el usuario tiene acceso de escritura a una carpeta

187

Necesito probar si un usuario puede escribir en una carpeta antes de intentar hacerlo.

Implementé el siguiente método (en C # 2.0) que intenta recuperar los permisos de seguridad para la carpeta usando el método Directory.GetAccessControl () .

private bool hasWriteAccessToFolder(string folderPath)
{
    try
    {
        // Attempt to get a list of security permissions from the folder. 
        // This will raise an exception if the path is read only or do not have access to view the permissions. 
        System.Security.AccessControl.DirectorySecurity ds = Directory.GetAccessControl(folderPath);
        return true;
    }
    catch (UnauthorizedAccessException)
    {
        return false;
    }
}

Cuando busqué en Google cómo probar el acceso de escritura, no apareció nada como esto y parecía muy complicado probar los permisos en Windows. Me preocupa que estoy simplificando demasiado las cosas y que este método no es robusto, aunque parece funcionar.

¿Mi método para probar si el usuario actual tiene acceso de escritura funcionará correctamente?

Chris B
fuente
13
¿No es realmente tener acceso para ver los permisos lo mismo que no tener permiso para escribir en ellos?
deed02392

Respuestas:

61

Esa es una forma perfectamente válida de verificar el acceso a la carpeta en C #. El único lugar donde podría caerse es si necesita llamar a esto en un ciclo cerrado donde la sobrecarga de una excepción puede ser un problema.

Ha habido otras preguntas similares formuladas anteriormente.

Ceniza
fuente
1
Curiosamente tuve una de esas otras preguntas abiertas en otra pestaña pero no había visto la respuesta sobre DirectorySecurity, enséñame a leer todas las respuestas, no solo la aceptada ;-)
Chris B
¿No se caerá también cuando utilices rutas largas en Windows?
Alexandru
11
Eso no le dirá si tiene permiso de escritura, solo le dirá si puede buscar permisos en esa carpeta o no. También es posible que pueda escribir pero no pueda buscar permisos.
RandomEngy
65

Aprecio que esto sea un poco tarde para esta publicación, pero es posible que este código sea útil.

string path = @"c:\temp";
string NtAccountName = @"MyDomain\MyUserOrGroup";

DirectoryInfo di = new DirectoryInfo(path);
DirectorySecurity acl = di.GetAccessControl(AccessControlSections.All);
AuthorizationRuleCollection rules = acl.GetAccessRules(true, true, typeof(NTAccount));

//Go through the rules returned from the DirectorySecurity
foreach (AuthorizationRule rule in rules)
{
    //If we find one that matches the identity we are looking for
    if (rule.IdentityReference.Value.Equals(NtAccountName,StringComparison.CurrentCultureIgnoreCase))
    {
        var filesystemAccessRule = (FileSystemAccessRule)rule;

        //Cast to a FileSystemAccessRule to check for access rights
        if ((filesystemAccessRule.FileSystemRights & FileSystemRights.WriteData)>0 && filesystemAccessRule.AccessControlType != AccessControlType.Deny)
        {
            Console.WriteLine(string.Format("{0} has write access to {1}", NtAccountName, path));
        }
        else
        {
            Console.WriteLine(string.Format("{0} does not have write access to {1}", NtAccountName, path));
        }
    }
}

Console.ReadLine();

Deje caer eso en una aplicación de consola y vea si hace lo que necesita.

Duncan Howe
fuente
¡Justo en el blanco! Me ayuda mucho!
smwikipedia
Recibo una excepción en la llamada GetAccessControlpero mi software es realmente capaz de escribir en el directorio que estoy viendo ...
Jon Cage
@ JonCage: ¿qué excepción está recibiendo? Lo primero que me viene a la mente es, irónicamente, un problema de seguridad. ¿La cuenta que está ejecutando su aplicación tiene permiso para obtener la información de ACL?
Duncan Howe
1
Debe agregar una comprobación para el tipo FileSystemAccessRule. Si es una regla de Denegación, la informará incorrectamente como de escritura.
tdemay
2
Estoy tratando de usar esto. Encontró otro problema. Si los derechos solo se asignan a grupos y no a usuarios específicos, esto informará incorrectamente que no tienen acceso de escritura. Por ejemplo, acceso de escritura otorgado a "Usuarios autenticados"
tdemay
63
public bool IsDirectoryWritable(string dirPath, bool throwIfFails = false)
{
    try
    {
        using (FileStream fs = File.Create(
            Path.Combine(
                dirPath, 
                Path.GetRandomFileName()
            ), 
            1,
            FileOptions.DeleteOnClose)
        )
        { }
        return true;
    }
    catch
    {
        if (throwIfFails)
            throw;
        else
            return false;
    }
}
priit
fuente
77
Esta respuesta detectará todas las excepciones que podrían ocurrir al intentar escribir un archivo, no solo las infracciones de permisos.
Matt Ellen
77
@GY, string tempFileName = Path.GetRandomFileName();evidentemente
Alexey Khoroshikh
3
@Matt, esto responde exactamente a la pregunta formulada "es que el directorio se puede escribir", independientemente de la razón del error, sin embargo. Prefieres responder a " por qué no puedo escribir en el directorio". :)
Alexey Khoroshikh
1
Me sale un falso positivo con este código. File.Create () se ejecuta correctamente (y deja un archivo temporal si cambia la última opción) a pesar de que el usuario ejecutor no tiene permiso para escribir en esa carpeta. Realmente muy extraño: pasé una hora tratando de descubrir por qué, pero estoy perplejo.
NickG
44
De todas las alternativas que he probado a continuación (y enlaces referenciados), esta es la única que funciona de manera confiable.
TarmoPikaro
24

Intenté la mayoría de estos, pero dan falsos positivos, todo por la misma razón. No es suficiente probar el directorio para obtener un permiso disponible, debe verificar que el usuario conectado sea miembro de un grupo que tiene ese permiso. Para hacer esto, obtiene la identidad de los usuarios y comprueba si es miembro de un grupo que contiene FileSystemAccessRule IdentityReference. He probado esto, funciona perfectamente ...

    /// <summary>
    /// Test a directory for create file access permissions
    /// </summary>
    /// <param name="DirectoryPath">Full path to directory </param>
    /// <param name="AccessRight">File System right tested</param>
    /// <returns>State [bool]</returns>
    public static bool DirectoryHasPermission(string DirectoryPath, FileSystemRights AccessRight)
    {
        if (string.IsNullOrEmpty(DirectoryPath)) return false;

        try
        {
            AuthorizationRuleCollection rules = Directory.GetAccessControl(DirectoryPath).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
            WindowsIdentity identity = WindowsIdentity.GetCurrent();

            foreach (FileSystemAccessRule rule in rules)
            {
                if (identity.Groups.Contains(rule.IdentityReference))
                {
                    if ((AccessRight & rule.FileSystemRights) == AccessRight)
                    {
                        if (rule.AccessControlType == AccessControlType.Allow)
                            return true;
                    }
                }
            }
        }
        catch { }
        return false;
    }
JGU
fuente
Gracias John, también tengo falso positivo hasta que he usado su código para verificar nuevamente el grupo de usuarios de la regla IdentifyReference.
Paul L
1
Tuve que agregar una verificación adicional para la identidad. Propietario == regla.IdentityReference ya que tenía un usuario que daba acceso pero no en ningún grupo, como una cuenta local dedicada para servicios
grinder22
2
AccessControlType deny tiene precedencia sobre allow, por lo que para ser completamente completas, las reglas que niegan el derecho de acceso también deben verificarse, y al verificar los tipos de (AccessRight & rule.FileSystemRights) > 0denegación , debe ser porque cualquier tipo de acceso secundario denegado es parte de AccessRightque no tiene acceso completo acceso aAccessRight
TJ Rockefeller
Como grinder22 mencionado anteriormente, necesitaba cambiar; if (identity.Groups.Contains (rule.IdentityReference)) a if (identity.Groups.Contains (rule.IdentityReference) || identity.Owner.Equals (rule.IdentityReference)) ya que tenía un usuario que tenía acceso pero no estaba ' t en cualquiera de los grupos.
Ehambright
13

En mi humilde opinión, la única forma 100% confiable de probar si puede escribir en un directorio es escribir en él y, finalmente, detectar excepciones.

Darin Dimitrov
fuente
13

Por ejemplo, para todos los usuarios (Builtin \ Users), este método funciona bien: disfrute.

public static bool HasFolderWritePermission(string destDir)
{
   if(string.IsNullOrEmpty(destDir) || !Directory.Exists(destDir)) return false;
   try
   {
      DirectorySecurity security = Directory.GetAccessControl(destDir);
      SecurityIdentifier users = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
      foreach(AuthorizationRule rule in security.GetAccessRules(true, true, typeof(SecurityIdentifier)))
      {
          if(rule.IdentityReference == users)
          {
             FileSystemAccessRule rights = ((FileSystemAccessRule)rule);
             if(rights.AccessControlType == AccessControlType.Allow)
             {
                    if(rights.FileSystemRights == (rights.FileSystemRights | FileSystemRights.Modify)) return true;
             }
          }
       }
       return false;
    }
    catch
    {
        return false;
    }
}
UGEEN
fuente
8

Prueba esto:

try
{
    DirectoryInfo di = new DirectoryInfo(path);
    DirectorySecurity acl = di.GetAccessControl();
    AuthorizationRuleCollection rules = acl.GetAccessRules(true, true, typeof(NTAccount));

    WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
    WindowsPrincipal principal = new WindowsPrincipal(currentUser);
    foreach (AuthorizationRule rule in rules)
    {
        FileSystemAccessRule fsAccessRule = rule as FileSystemAccessRule;
        if (fsAccessRule == null)
            continue;

        if ((fsAccessRule.FileSystemRights & FileSystemRights.WriteData) > 0)
        {
            NTAccount ntAccount = rule.IdentityReference as NTAccount;
            if (ntAccount == null)
            {
                continue;
            }

            if (principal.IsInRole(ntAccount.Value))
            {
                Console.WriteLine("Current user is in role of {0}, has write access", ntAccount.Value);
                continue;
            }
            Console.WriteLine("Current user is not in role of {0}, does not have write access", ntAccount.Value);                        
        }
    }
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("does not have write access");
}
CsabaS
fuente
Si no me equivoco, esto está cerca pero no del todo; pasa por alto el hecho de que fsAccessRule.AccessControlTypepodría estarlo AccessControlType.Deny.
Jonathan Gilbert
Esto funcionaba para mí en mi máquina de desarrollo Win7 pero falla en Win10 (tanto para un probador como para mi propia máquina de prueba). La modificación de ssds (ver más abajo) parece solucionarlo.
winwaed
6

Su código obtiene el DirectorySecurityde un directorio dado y maneja una excepción (debido a que no tiene acceso a la información de seguridad) correctamente. Sin embargo, en su muestra, en realidad no interroga al objeto devuelto para ver qué acceso está permitido, y creo que debe agregarlo.

Vinay Sajip
fuente
+1: acabo de encontrarme con este problema en el que no se produjo una excepción al llamar a GetAccessControl, pero recibo una excepción no autorizada al intentar escribir en ese mismo directorio.
Mayo
6

Aquí hay una versión modificada de la respuesta de CsabaS , que explica las reglas explícitas de acceso denegado . La función pasa por todos los FileSystemAccessRules para un directorio, y verifica si el usuario actual está en un rol que tiene acceso a un directorio. Si no se encuentran tales roles o el usuario está en un rol con acceso denegado, la función devuelve falso. Para verificar los derechos de lectura, pase FileSystemRights.Read a la función; para derechos de escritura, pase FileSystemRights.Write. Si desea verificar los derechos de un usuario arbitrario y no los actuales, sustituya la identidad de Windows del usuario actual por la identidad de Windows deseada. También recomendaría no confiar en funciones como esta para determinar si el usuario puede usar el directorio de manera segura. Esta respuesta explica perfectamente por qué.

    public static bool UserHasDirectoryAccessRights(string path, FileSystemRights accessRights)
    {
        var isInRoleWithAccess = false;

        try
        {
            var di = new DirectoryInfo(path);
            var acl = di.GetAccessControl();
            var rules = acl.GetAccessRules(true, true, typeof(NTAccount));

            var currentUser = WindowsIdentity.GetCurrent();
            var principal = new WindowsPrincipal(currentUser);
            foreach (AuthorizationRule rule in rules)
            {
                var fsAccessRule = rule as FileSystemAccessRule;
                if (fsAccessRule == null)
                    continue;

                if ((fsAccessRule.FileSystemRights & accessRights) > 0)
                {
                    var ntAccount = rule.IdentityReference as NTAccount;
                    if (ntAccount == null)
                        continue;

                    if (principal.IsInRole(ntAccount.Value))
                    {
                        if (fsAccessRule.AccessControlType == AccessControlType.Deny)
                            return false;
                        isInRoleWithAccess = true;
                    }
                }
            }
        }
        catch (UnauthorizedAccessException)
        {
            return false;
        }
        return isInRoleWithAccess;
    }
sdds
fuente
El código de Csaba me fallaba en Windows 10 (pero está bien en mi máquina de desarrollo Win7). Lo anterior parece solucionar el problema.
winwaed
4

Las soluciones anteriores son buenas, pero para mí, este código me parece simple y viable. Simplemente cree un archivo temporal. Si se crea el archivo, su usuario medio tiene acceso de escritura.

        public static bool HasWritePermission(string tempfilepath)
        {
            try
            {
                System.IO.File.Create(tempfilepath + "temp.txt").Close();
                System.IO.File.Delete(tempfilepath + "temp.txt");
            }
            catch (System.UnauthorizedAccessException ex)
            {

                return false;
            }

            return true;
        }
Ali Asad
fuente
3
¡Agradable! Pero una cosa es la que el usuario puede tener Createpermiso, pero no Delete, en cuyo caso esto sería falso de vuelta a pesar de que el usuario hace tener permiso de escritura.
Chris B
La respuesta más conveniente para la codificación :) También uso esta única, sin embargo, cuando hay grandes solicitudes concurrentes, tanta lectura / escritura puede ralentizar el rendimiento, por lo que en esos casos puede usar la metodología de control de acceso como se indica en otras respuestas.
vibs2006
1
Use en su Path.Combinelugar como Path.Combine(tempfilepath, "temp.txt").
ΩmegaMan
3

Puede intentar seguir el siguiente bloque de código para verificar si el directorio tiene acceso de escritura. Comprueba la FileSystemAccessRule.

string directoryPath = "C:\\XYZ"; //folderBrowserDialog.SelectedPath;
bool isWriteAccess = false;
try
{
    AuthorizationRuleCollection collection =
        Directory.GetAccessControl(directoryPath)
            .GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
    foreach (FileSystemAccessRule rule in collection)
    {
        if (rule.AccessControlType == AccessControlType.Allow)
        {
            isWriteAccess = true;
            break;
        }
    }
}
catch (UnauthorizedAccessException ex)
{
    isWriteAccess = false;
}
catch (Exception ex)
{
    isWriteAccess = false;
}
if (!isWriteAccess)
{
    //handle notifications 
}
RockWorld
fuente
2

Tiene una posible condición de carrera en su código: ¿qué sucede si el usuario tiene permisos para escribir en la carpeta cuando verifica, pero antes de que el usuario realmente escriba en la carpeta, se retira este permiso? La escritura arrojará una excepción que deberá atrapar y manejar. Entonces la verificación inicial no tiene sentido. También podría escribir y manejar cualquier excepción. Este es el patrón estándar para su situación.


fuente
1

Simplemente intentar acceder al archivo en cuestión no es necesariamente suficiente. La prueba se ejecutará con los permisos del usuario que ejecuta el programa, que no son necesariamente los permisos de usuario con los que desea probar.

Mort
fuente
0

Estoy de acuerdo con Ash, eso debería estar bien. Alternativamente, podría usar CAS declarativa y evitar que el programa se ejecute en primer lugar si no tienen acceso.

Creo que algunas de las características de CAS pueden no estar presentes en C # 4.0 por lo que he escuchado, no estoy seguro de si eso podría ser un problema o no.

Ian
fuente
0

No pude obtener GetAccessControl () para lanzar una excepción en Windows 7 como se recomienda en la respuesta aceptada.

Terminé usando una variación de la respuesta de sdds :

        try
        {
            bool writeable = false;
            WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            DirectorySecurity security = Directory.GetAccessControl(pstrPath);
            AuthorizationRuleCollection authRules = security.GetAccessRules(true, true, typeof(SecurityIdentifier));

            foreach (FileSystemAccessRule accessRule in authRules)
            {

                if (principal.IsInRole(accessRule.IdentityReference as SecurityIdentifier))
                {
                    if ((FileSystemRights.WriteData & accessRule.FileSystemRights) == FileSystemRights.WriteData)
                    {
                        if (accessRule.AccessControlType == AccessControlType.Allow)
                        {
                            writeable = true;
                        }
                        else if (accessRule.AccessControlType == AccessControlType.Deny)
                        {
                            //Deny usually overrides any Allow
                            return false;
                        }

                    } 
                }
            }
            return writeable;
        }
        catch (UnauthorizedAccessException)
        {
            return false;
        }

Espero que esto ayude.

Patricio
fuente
0

Me enfrenté al mismo problema: cómo verificar si puedo leer / escribir en un directorio en particular. Terminé con la solución fácil para ... realmente probarlo. Aquí está mi solución simple pero efectiva.

 class Program
{

    /// <summary>
    /// Tests if can read files and if any are present
    /// </summary>
    /// <param name="dirPath"></param>
    /// <returns></returns>
    private genericResponse check_canRead(string dirPath)
    {
        try
        {
            IEnumerable<string> files = Directory.EnumerateFiles(dirPath);
            if (files.Count().Equals(0))
                return new genericResponse() { status = true, idMsg = genericResponseType.NothingToRead };

            return new genericResponse() { status = true, idMsg = genericResponseType.OK };
        }
        catch (DirectoryNotFoundException ex)
        {

            return new genericResponse() { status = false, idMsg = genericResponseType.ItemNotFound };

        }
        catch (UnauthorizedAccessException ex)
        {

            return new genericResponse() { status = false, idMsg = genericResponseType.CannotRead };

        }

    }

    /// <summary>
    /// Tests if can wirte both files or Directory
    /// </summary>
    /// <param name="dirPath"></param>
    /// <returns></returns>
    private genericResponse check_canWrite(string dirPath)
    {

        try
        {
            string testDir = "__TESTDIR__";
            Directory.CreateDirectory(string.Join("/", dirPath, testDir));

            Directory.Delete(string.Join("/", dirPath, testDir));


            string testFile = "__TESTFILE__.txt";
            try
            {
                TextWriter tw = new StreamWriter(string.Join("/", dirPath, testFile), false);
                tw.WriteLine(testFile);
                tw.Close();
                File.Delete(string.Join("/", dirPath, testFile));

                return new genericResponse() { status = true, idMsg = genericResponseType.OK };
            }
            catch (UnauthorizedAccessException ex)
            {

                return new genericResponse() { status = false, idMsg = genericResponseType.CannotWriteFile };

            }


        }
        catch (UnauthorizedAccessException ex)
        {

            return new genericResponse() { status = false, idMsg = genericResponseType.CannotWriteDir };

        }
    }


}

public class genericResponse
{

    public bool status { get; set; }
    public genericResponseType idMsg { get; set; }
    public string msg { get; set; }

}

public enum genericResponseType
{

    NothingToRead = 1,
    OK = 0,
    CannotRead = -1,
    CannotWriteDir = -2,
    CannotWriteFile = -3,
    ItemNotFound = -4

}

Espero eso ayude !

l.raimondi
fuente
0

La mayoría de las respuestas aquí no verifican el acceso de escritura. Simplemente verifique si el usuario / grupo puede 'Leer permiso' (Lea la lista ACE del archivo / directorio).

También iterar a través de ACE y verificar si coincide con el Identificador de seguridad no funciona porque el usuario puede ser miembro de un grupo del que podría obtener / perder privilegios. Peor que eso son los grupos anidados.

Sé que este es un hilo viejo, pero hay una mejor manera para cualquiera que busque ahora.

Siempre que el usuario tenga el privilegio de permiso de lectura, se puede usar la API Authz para verificar el acceso efectivo.

https://docs.microsoft.com/en-us/windows/win32/secauthz/using-authz-api

https://docs.microsoft.com/en-us/windows/win32/secauthz/checking-access-with-authz-api

Kamaal
fuente