Teste C # se o usuário tiver acesso de gravação a uma pasta


187

Preciso testar se um usuário pode gravar em uma pasta antes de realmente tentar fazer isso.

Eu implementei o método a seguir (no C # 2.0) que tenta recuperar as permissões de segurança da pasta usando o 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;
    }
}

Quando eu estava pesquisando no Google como testar o acesso de gravação, nada disso apareceu e parecia muito complicado testar as permissões no Windows. Preocupo-me por simplificar demais as coisas e por esse método não ser robusto, embora pareça funcionar.

Meu método para testar se o usuário atual tem acesso de gravação funcionará corretamente?


13
Não ter acesso para visualizar as permissões é realmente o mesmo que não ter permissão para gravar nela?
deed02392

Respostas:


61

Essa é uma maneira perfeitamente válida de verificar o acesso à pasta em C #. O único lugar em que ela pode cair é se você precisar chamar isso em um loop apertado, onde a sobrecarga de uma exceção pode ser um problema.

Houve outras perguntas semelhantes feitas anteriormente.


1
Curiosamente eu tinha uma dessas outras questões em aberto em outra guia, mas não tinha visto a resposta sobre DirectorySecurity, ensina-me a ler todas as respostas não apenas o aceita ;-)
Chris B

Também não cairá quando você usa caminhos longos no Windows?
Alexandru

11
Isso não informa se você tem permissão de gravação, mas apenas se você pode procurar permissões nessa pasta ou não. Além disso, você pode escrever, mas não pode procurar permissões.
RandomEngy

65

Compreendo que este seja um pouco tarde para este post, mas você pode achar útil esse código.

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

Coloque isso em um aplicativo de console e veja se ele faz o que você precisa.


Direto no alvo! Me ajuda muito!
smwikipedia

Recebo uma exceção na chamada para, GetAccessControlmas meu software é realmente capaz de gravar no diretório que estou procurando ..?
9788 Jon Cage

@ JonCage - que exceção você está recebendo? A primeira coisa que vem à mente é, ironicamente, um problema de segurança. A conta em que seu aplicativo está sendo executado tem permissão para obter as informações da ACL?
Duncan Howe #

1
Você precisa adicionar uma verificação para o tipo FileSystemAccessRule. Se for uma regra de Negação, você a reportará incorretamente como gravável.
tdemay

2
Estou tentando usar isso. Encontrei outro problema. Se os direitos forem atribuídos apenas a grupos e não a usuários específicos, isso informará incorretamente que eles não têm acesso de gravação. Por exemplo, acesso de gravação concedido a "Usuários autenticados"
terça

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;
    }
}

7
Esta resposta captura todas as exceções que podem ocorrer ao tentar gravar um arquivo, não apenas violações de permissão.
Matt Ellen

7
@GY,, string tempFileName = Path.GetRandomFileName();evidentemente #
Alexey Khoroshikh

3
@ Matt, isso responde exatamente à pergunta "o diretório é gravável", independentemente do motivo da falha, no entanto. Você prefere responder a " por que não consigo escrever no diretório". :)
Alexey Khoroshikh

1
Eu recebo um falso positivo com este código. O File.Create () executa OK (e deixa um arquivo temporário se você alterar a última opção), mesmo que o usuário em execução não tenha permissão para gravar nessa pasta. Realmente muito estranho - passei uma hora tentando descobrir o porquê, mas estou perplexo.
NickG

4
De todas as alternativas que tentei abaixo (e links referenciados) - este é o único que funciona de maneira confiável.
TarmoPikaro

24

Eu tentei a maioria delas, mas elas fornecem falsos positivos, pelo mesmo motivo. Não é suficiente testar o diretório para obter uma permissão disponível; você deve verificar se o usuário conectado é membro de um grupo que possui esse tipo de permissão. permissão. Para fazer isso, obtenha a identidade dos usuários e verifique se é membro de um grupo que contém o FileSystemAccessRule IdentityReference. Eu testei isso, funciona perfeitamente ..

    /// <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;
    }

Obrigado John, eu também tenho falso positivo até que eu usei seu código para verificar o grupo de usuários novamente a regra IdentifyReference!
Paul L

1
i teve que adicionar uma verificação adicional para identity.Owner == rule.IdentityReference como eu tinha um usuário que tenha acesso, mas não em qualquer grupo, como uma conta local dedicado para serviços
grinder22

2
A negação do AccessControlType tem precedência sobre a permissão, portanto, também é necessário verificar regras completamente minuciosas que negam o direito de acesso e, ao verificar os tipos de negação, deve-se ao (AccessRight & rule.FileSystemRights) > 0fato de que qualquer tipo de sub-acesso negado faz parte de um AccessRightmeio que você não possui completo acesso aAccessRight
TJ Rockefeller

Como moedor22 mencionado acima, eu precisava mudar; if (identity.Groups.Contains (rule.IdentityReference)) para if (identity.Groups.Contains (rule.IdentityReference) || identity.Owner.Equals (rule.IdentityReference)) como eu tinha um usuário que tinha acesso, mas não era ' t em qualquer um dos grupos.
ehambright

13

IMHO, a única maneira 100% confiável de testar se você pode gravar em um diretório é realmente gravá-lo e, eventualmente, capturar exceções.


13

Por exemplo, para todos os usuários (Builtin \ Users), esse método funciona bem.

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;
    }
}

8

Tente o seguinte:

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");
}

Se não me engano, isso está próximo, mas não chega - ele ignora o fato de que fsAccessRule.AccessControlTypepoderia estar AccessControlType.Deny.
Jonathan Gilbert

Isso estava funcionando para mim na minha máquina de desenvolvimento Win7, mas falha no Win10 (tanto para um testador quanto para minha própria máquina de teste). A modificação do ssds (veja abaixo) parece corrigi-la.
WinWeded

6

Seu código obtém o DirectorySecuritypara um determinado diretório e manipula uma exceção (devido ao fato de você não ter acesso às informações de segurança) corretamente. No entanto, na sua amostra, você realmente não interroga o objeto retornado para ver qual acesso é permitido - e acho que você precisa adicionar isso.


+1 - Acabei de encontrar esse problema em que uma exceção não foi lançada ao chamar GetAccessControl, mas recebo uma exceção não autorizada ao tentar gravar no mesmo diretório.
Mayo '

6

Aqui está uma versão modificada da resposta do CsabaS , que explica regras explícitas de acesso negado. A função passa por todos os FileSystemAccessRules para um diretório e verifica se o usuário atual está em uma função que tem acesso a um diretório. Se nenhuma dessas funções for encontrada ou o usuário estiver em uma função com acesso negado, a função retornará false. Para verificar os direitos de leitura, passe FileSystemRights.Read para a função; para direitos de gravação, passe FileSystemRights.Write. Se você deseja verificar os direitos de um usuário arbitrário e não o atual, substitua o currentUser WindowsIdentity pelo WindowsIdentity desejado. Eu também desaconselho confiar em funções como esta para determinar se o usuário pode usar o diretório com segurança. Esta resposta explica perfeitamente o porquê.

    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;
    }

O código da Csaba estava falhando para mim no Windows 10 (mas bom na minha máquina de desenvolvimento Win7). O texto acima parece resolver o problema.
WinWeded

4

As soluções acima são boas, mas para mim, acho esse código simples e viável. Basta criar um arquivo temporário. Se o arquivo for criado, seu usuário médio terá acesso de gravação.

        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;
        }

3
Agradável! Uma coisa, porém, é o que o usuário pode ter Createpermissão, mas não Deleteno caso em que isso iria retornar false mesmo que o usuário não tem permissão de escrita.
Chris B

Resposta mais conveniente para codificação :) Eu também uso essa apenas, no entanto, quando existem solicitações simultâneas grandes, muitas leituras / gravações podem diminuir o desempenho, portanto, nesses casos, você pode usar a metodologia de controle de acesso conforme indicado em outras respostas.
precisa saber é o seguinte

1
Use em Path.Combinevez disso como Path.Combine(tempfilepath, "temp.txt").
precisa saber é o seguinte

3

Você pode tentar seguir o bloco de código para verificar se o diretório está tendo acesso de gravação. Ele verifica o 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 
}

2

Você tem uma condição de corrida em potencial no seu código - o que acontece se o usuário tiver permissões para gravar na pasta quando você marcar, mas antes que o usuário realmente grave na pasta, essa permissão será retirada? A gravação lançará uma exceção que você precisará capturar e manipular. Portanto, a verificação inicial é inútil. Você também pode escrever e manipular qualquer exceção. Este é o padrão padrão para sua situação.



1

Simplesmente tentar acessar o arquivo em questão não é necessariamente suficiente. O teste será executado com as permissões do usuário que está executando o programa - que não são necessariamente as permissões de usuário com as quais você deseja testar.


0

Eu concordo com Ash, isso deve ficar bem. Como alternativa, você pode usar o CAS declarativo e impedir a execução do programa em primeiro lugar, se eles não tiverem acesso.

Acredito que alguns dos recursos do CAS podem não estar presentes no C # 4.0 pelo que ouvi, não tenho certeza se isso pode ser um problema ou não.


0

Não consegui obter GetAccessControl () para lançar uma exceção no Windows 7, conforme recomendado na resposta aceita.

Acabei usando uma variação da resposta do 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 isto ajude.


0

Eu enfrentei o mesmo problema: como verificar se consigo ler / gravar em um diretório específico. Acabei com a solução fácil para ... realmente testá-lo. Aqui está minha solução simples, porém eficaz.

 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 que ajude !


0

A maioria das respostas aqui não verifica o acesso de gravação. Apenas verifique se o usuário / grupo pode 'Ler Permissão' (Leia a lista ACE do arquivo / diretório).

Também itera através do ACE e verifica se ele corresponde ao Identificador de Segurança não funciona porque o usuário pode ser membro de um grupo do qual ele pode obter / perder privilégios. Pior que isso é grupos aninhados.

Eu sei que este é um tópico antigo, mas existe uma maneira melhor para quem procura agora.

Desde que o usuário tenha o privilégio de permissão de leitura, é possível usar a API Authz para verificar o acesso efetivo.

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.