C # Test si l'utilisateur a un accès en écriture à un dossier

187

J'ai besoin de tester si un utilisateur peut écrire dans un dossier avant d'essayer de le faire.

J'ai implémenté la méthode suivante (en C # 2.0) qui tente de récupérer les autorisations de sécurité pour le dossier à l'aide de la méthode 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;
    }
}

Lorsque je cherchais sur Google comment tester l'accès en écriture, rien de tel ne s'est produit et il m'a semblé très compliqué de tester les autorisations dans Windows. Je crains de trop simplifier les choses et que cette méthode ne soit pas robuste, même si elle semble fonctionner.

Ma méthode pour tester si l'utilisateur actuel a un accès en écriture fonctionnera-t-elle correctement?

Chris B
la source
13
Le fait de ne pas avoir accès pour afficher les autorisations équivaut-il vraiment à ne pas être autorisé à y écrire?
deed02392

Réponses:

61

C'est un moyen parfaitement valide de vérifier l'accès aux dossiers en C #. Le seul endroit où il peut tomber est si vous devez appeler cela dans une boucle serrée où la surcharge d'une exception peut être un problème.

D'autres questions similaires ont déjà été posées.

Cendre
la source
1
Curieusement, j'avais une de ces autres questions ouvertes dans un autre onglet mais je n'avais pas vu la réponse sur DirectorySecurity, apprenez-moi à lire toutes les réponses pas seulement celles acceptées ;-)
Chris B
Ne tombera-t-il pas également lorsque vous utilisez de longs chemins dans Windows?
Alexandru
11
Cela ne vous dira pas si vous avez l'autorisation d'écriture, cela ne vous dira que si vous pouvez rechercher des autorisations sur ce dossier ou non. Vous pourrez peut-être également écrire mais ne pas pouvoir rechercher les autorisations.
RandomEngy
65

J'apprécie que ce soit un peu tard dans la journée pour cet article, mais vous pourriez trouver ce morceau de code utile.

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();

Déposez-le dans une application console et voyez s'il fait ce dont vous avez besoin.

Duncan Howe
la source
Droit dans le but! M'aide beaucoup!
smwikipedia
J'obtiens une exception à l'appel GetAccessControlmais mon logiciel est actuellement capable d'écrire dans le répertoire que je regarde ..?
Jon Cage
@JonCage - quelle exception obtenez-vous? La première chose qui me vient à l'esprit est, ironiquement, un problème de sécurité. Le compte sous lequel votre application s'exécute a-t-il l'autorisation d'obtenir les informations ACL?
Duncan Howe
1
Vous devez ajouter une vérification du type FileSystemAccessRule. S'il s'agit d'une règle de refus, vous la signalerez à tort comme inscriptible.
tdemay
2
J'essaye d'utiliser ceci. J'ai trouvé un autre problème. Si des droits sont attribués uniquement à des groupes et non à des utilisateurs spécifiques, cela signifiera à tort qu'ils n'ont pas d'accès en écriture. Par exemple, accès en écriture accordé aux "Utilisateurs authentifiés"
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
la source
7
Cette réponse détectera toutes les exceptions qui pourraient se produire lors de la tentative d'écriture d'un fichier, pas seulement les violations d'autorisation.
Matt Ellen
7
@GY`` string tempFileName = Path.GetRandomFileName();évidemment
Alexey Khoroshikh
3
@Matt, cela répond exactement à la question posée "le répertoire est-il accessible en écriture" quelle que soit la raison de l'échec, cependant. Vous répondez plutôt à " pourquoi je ne peux pas écrire dans le répertoire". :)
Alexey Khoroshikh
1
J'obtiens un faux positif avec ce code. File.Create () s'exécute OK (et laisse un fichier temporaire si vous modifiez la dernière option) même si l'utilisateur en cours d'exécution n'a pas l'autorisation d'écrire dans ce dossier. Vraiment vraiment étrange - j'ai passé une heure à essayer de comprendre pourquoi mais je suis perplexe.
NickG
4
De toutes les alternatives que j'ai essayées ci-dessous (et des liens référencés) - c'est la seule qui fonctionne de manière fiable.
TarmoPikaro le
24

J'ai essayé la plupart d'entre eux, mais ils donnent des faux positifs, tous pour la même raison .. Il ne suffit pas de tester le répertoire pour une autorisation disponible, vous devez vérifier que l'utilisateur connecté est membre d'un groupe qui a cela autorisation. Pour ce faire, vous obtenez l'identité des utilisateurs et vérifiez s'il est membre d'un groupe qui contient le FileSystemAccessRule IdentityReference. J'ai testé cela, fonctionne parfaitement.

    /// <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
la source
Merci John, j'ai aussi un faux positif jusqu'à ce que j'utilise votre code pour vérifier à nouveau le groupe d'utilisateurs la règle IdentifyReference!
Paul L
1
J'ai dû ajouter une vérification supplémentaire pour l'identité.Owner == rule.IdentityReference car j'avais un utilisateur qui a donné l'accès mais pas dans aucun groupe, comme un compte local dédié pour les services
grinder22
2
AccessControlType deny a priorité sur allow, donc pour être complètement approfondi, les règles qui refusent le droit d'accès doivent également être vérifiées, et lors de la vérification des types de refus, cela devrait être (AccessRight & rule.FileSystemRights) > 0parce que tout sous-type d'accès refusé qui fait partie des AccessRightmoyens que vous n'avez pas plein accès àAccessRight
TJ Rockefeller
Comme grinder22 mentionné ci-dessus, j'avais besoin de changer; if (identity.Groups.Contains (rule.IdentityReference)) à if (identity.Groups.Contains (rule.IdentityReference) || identity.Owner.Equals (rule.IdentityReference)) car j'avais un utilisateur qui avait accès mais ne l'était pas ' t dans l'un des groupes.
ehambright
13

À mon humble avis, le seul moyen fiable à 100% de tester si vous pouvez écrire dans un répertoire est d'y écrire et éventuellement d'attraper des exceptions.

Darin Dimitrov
la source
13

Par exemple, pour tous les utilisateurs (Builtin \ Users), cette méthode fonctionne très bien - profitez-en.

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
la source
8

Essaye ça:

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
la source
Si je ne me trompe pas, c'est proche mais pas tout à fait là - cela ne tient pas compte du fait que cela fsAccessRule.AccessControlTypepourrait être AccessControlType.Deny.
Jonathan Gilbert
Cela fonctionnait pour moi sur ma machine de développement Win7 mais échoue sur Win10 (à la fois pour un testeur et ma propre machine de test). La modification de ssds (voir ci-dessous) semble le corriger.
winwaed
6

Votre code obtient le DirectorySecuritypour un répertoire donné et gère correctement une exception (car vous n'avez pas accès aux informations de sécurité). Cependant, dans votre exemple, vous n'interrogez pas réellement l'objet retourné pour voir quel accès est autorisé - et je pense que vous devez l'ajouter.

Vinay Sajip
la source
+1 - Je viens de rencontrer ce problème où une exception n'a pas été levée lors de l'appel de GetAccessControl mais j'obtiens une exception non autorisée en essayant d'écrire dans ce même répertoire.
Mayo
6

Voici une version modifiée de la réponse de CsabaS , qui tient compte des règles d'accès de refus explicites. La fonction parcourt toutes les FileSystemAccessRules d'un répertoire et vérifie si l'utilisateur actuel est dans un rôle qui a accès à un répertoire. Si aucun de ces rôles n'est trouvé ou si l'utilisateur est dans un rôle avec un accès refusé, la fonction renvoie false. Pour vérifier les droits de lecture, transmettez FileSystemRights.Read à la fonction; pour les droits d'écriture, transmettez FileSystemRights.Write. Si vous souhaitez vérifier les droits d'un utilisateur arbitraire et non ceux en cours, remplacez le WindowsIdentity de WindowsIdentity par currentUser WindowsIdentity. Je déconseillerais également de s'appuyer sur des fonctions comme celle-ci pour déterminer si l'utilisateur peut utiliser le répertoire en toute sécurité. Cette réponse explique parfaitement pourquoi.

    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
la source
Le code de Csaba échouait pour moi sur Windows 10 (mais bien sur ma machine de développement Win7). Ce qui précède semble résoudre le problème.
winwaed
4

Les solutions ci-dessus sont bonnes mais pour moi, je trouve ce code simple et réalisable. Créez simplement un fichier temporaire. Si le fichier est créé, son utilisateur moyen a l'accès en écriture.

        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
la source
3
Agréable! Une chose cependant est que cet utilisateur peut avoir une Createautorisation, mais pas Deletedans ce cas, cela retournerait false même si l'utilisateur dispose d' une autorisation d'écriture.
Chris B
Réponse la plus pratique pour le codage :) J'utilise également celle-ci uniquement, cependant, quand il y a de grandes demandes simultanées, tant de lecture / écriture peut ralentir les performances, donc dans ces cas, vous pouvez utiliser la méthodologie de contrôle d'accès comme indiqué dans d'autres réponses.
vibs2006
1
Utilisez Path.Combineplutôt comme Path.Combine(tempfilepath, "temp.txt").
ΩmegaMan
3

Vous pouvez essayer le bloc de code suivant pour vérifier si le répertoire dispose d'un accès en écriture. Il vérifie le 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
la source
2

Vous avez une condition de concurrence potentielle dans votre code - que se passe-t-il si l'utilisateur dispose des autorisations d'écrire dans le dossier lorsque vous vérifiez, mais avant que l'utilisateur n'écrive réellement dans le dossier, cette autorisation est retirée? L'écriture lèvera une exception que vous devrez attraper et gérer. Le contrôle initial est donc inutile. Vous pouvez aussi bien écrire et gérer les exceptions. C'est le modèle standard pour votre situation.


la source
1

Essayer simplement d'accéder au fichier en question ne suffit pas nécessairement. Le test s'exécutera avec les autorisations de l'utilisateur exécutant le programme - Ce qui n'est pas nécessairement les autorisations utilisateur que vous souhaitez tester.

Mort
la source
0

Je suis d'accord avec Ash, ça devrait aller. Sinon, vous pouvez utiliser CAS déclaratif et empêcher le programme de s'exécuter en premier lieu s'ils n'y ont pas accès.

Je crois que certaines des fonctionnalités CAS peuvent ne pas être présentes dans C # 4.0 d'après ce que j'ai entendu, je ne sais pas si cela pourrait être un problème ou non.

Ian
la source
0

Je n'ai pas pu obtenir GetAccessControl () pour lever une exception sur Windows 7 comme recommandé dans la réponse acceptée.

J'ai fini par utiliser une variante de la réponse 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;
        }

J'espère que cela t'aides.

Patrick
la source
0

J'ai rencontré le même problème: comment vérifier si je peux lire / écrire dans un répertoire particulier. Je me suis retrouvé avec la solution simple pour ... le tester. Voici ma solution simple mais efficace.

 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

}

J'espère que ça aide !

l.raimondi
la source
0

La plupart des réponses ici ne vérifient pas l'accès en écriture. Il vérifie simplement si l'utilisateur / groupe peut «lire l'autorisation» (lire la liste ACE du fichier / répertoire).

Également itérer via ACE et vérifier s'il correspond à l'identificateur de sécurité ne fonctionne pas car l'utilisateur peut être membre d'un groupe dont il pourrait obtenir / perdre des privilèges. Pire encore, ce sont les groupes imbriqués.

Je sais que c'est un vieux fil mais il y a une meilleure façon pour quiconque regarde maintenant.

À condition que l'utilisateur dispose du privilège d'autorisation de lecture, on peut utiliser l'API Authz pour vérifier l'accès effectif.

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
la source