C # Sprawdź, czy użytkownik ma dostęp do zapisu do folderu

187

Muszę sprawdzić, czy użytkownik może napisać do folderu, zanim faktycznie spróbuje to zrobić.

Zaimplementowałem następującą metodę (w C # 2.0), która próbuje pobrać uprawnienia bezpieczeństwa dla folderu przy użyciu metody 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;
    }
}

Kiedy szukałem w Google, jak testować dostęp do zapisu, nic takiego się nie pojawiło, a testowanie uprawnień w systemie Windows wydawało się bardzo skomplikowane. Obawiam się, że nadmiernie upraszczam rzeczy i że ta metoda nie jest niezawodna, chociaż wydaje się, że działa.

Czy moja metoda przetestowania, czy bieżący użytkownik ma dostęp do zapisu, działa poprawnie?

Chris B.
źródło
13
Czy dostęp do przeglądania uprawnień nie jest tak naprawdę taki sam, jak zakazu pisania?
deed02392,

Odpowiedzi:

61

Jest to całkowicie poprawny sposób na sprawdzenie dostępu do folderu w języku C #. Jedynym miejscem, w którym może upaść, jest konieczność wywołania tego w ciasnej pętli, gdzie problemem może być narzut wyjątku .

Wcześniej zadawano inne podobne pytania .

Popiół
źródło
1
Co ciekawe, jedno z tych pytań było otwarte w innej zakładce, ale nie widziałem odpowiedzi na temat DirectorySecurity, naucz mnie czytać wszystkie odpowiedzi, nie tylko te zaakceptowane ;-)
Chris B
Czy nie spadnie również, gdy użyjesz długich ścieżek w systemie Windows?
Alexandru
11
To nie powie ci, czy masz uprawnienia do zapisu, powie ci tylko, czy możesz wyszukać uprawnienia do tego folderu, czy nie. Ponadto możesz pisać, ale nie możesz wyszukiwać uprawnień.
RandomEngy
65

Rozumiem, że ten post jest trochę za późno, ale ten fragment kodu może się przydać.

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

Upuść to w aplikacji na konsolę i sprawdź, czy robi to, czego potrzebujesz.

Duncan Howe
źródło
Prosto w cel! Bardzo mi pomaga!
smwikipedia,
Dostaję wyjątek na wezwanie do, GetAccessControlale moje oprogramowanie jest w stanie rzeczywiście pisać do katalogu, na który patrzę ...?
Jon Cage
@JonCage - jaki wyjątek otrzymujesz? Pierwszą rzeczą, która przychodzi mi na myśl, jest, jak na ironię, problem bezpieczeństwa. Czy konto, na którym działa Twoja aplikacja, ma uprawnienia do uzyskiwania informacji ACL?
Duncan Howe
1
Musisz dodać kontrolę typu FileSystemAccessRule. Jeśli jest to reguła odmowy, niepoprawnie zgłosisz ją jako zapisywalną.
tdemay
2
Próbuję tego użyć. Znaleziono inny problem. Jeśli prawa są przypisane tylko do grup, a nie do konkretnych użytkowników, niepoprawnie zgłosi to, że nie mają oni dostępu do zapisu. Na przykład, dostęp do zapisu przyznany „Uwierzytelnionym użytkownikom”
wtorek
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
źródło
7
Ta odpowiedź przechwytuje wszystkie wyjątki, które mogą wystąpić podczas próby zapisu pliku, a nie tylko naruszenia uprawnień.
Matt Ellen
7
@GY, string tempFileName = Path.GetRandomFileName();najwyraźniej
Alexey Khoroshikh
3
@Matt odpowiada to dokładnie na zadane pytanie „czy katalog jest zapisywalny” niezależnie od przyczyny niepowodzenia. Raczej odpowiadasz na pytanie „ dlaczego nie mogę pisać do katalogu”. :)
Alexey Khoroshikh
1
Otrzymuję fałszywy pozytyw z tym kodem. File.Create () działa OK (i pozostawia plik tymczasowy, jeśli zmienisz ostatnią opcję), nawet jeśli wykonujący użytkownik nie ma uprawnień do zapisu w tym folderze. Naprawdę bardzo dziwne - spędziłem godzinę próbując dowiedzieć się, dlaczego, ale jestem zakłopotany.
NickG
4
Ze wszystkich alternatyw, które wypróbowałem poniżej (i odnośników) - to jedyna, która działa niezawodnie.
TarmoPikaro
24

Próbowałem większości z nich, ale dają one fałszywe wyniki pozytywne, wszystkie z tego samego powodu. Nie wystarczy przetestować katalog pod kątem dostępnego uprawnienia, musisz sprawdzić, czy zalogowany użytkownik jest członkiem grupy, która ma to pozwolenie. Aby to zrobić, uzyskujesz tożsamość użytkowników i sprawdzasz, czy jest członkiem grupy zawierającej FileSystemAccessRule IdentityReference. Przetestowałem to, działa bezbłędnie ..

    /// <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
źródło
Dzięki John, mam też fałszywie pozytywne nastawienie, dopóki nie użyję twojego kodu, by ponownie sprawdzić grupę użytkowników regułę IdentifyReference!
Paul L
1
Musiałem dodać dodatkowe sprawdzenie tożsamości. własność == reguła. tożsamość Odniesienie, ponieważ miałem użytkownika, który dał dostęp, ale nie w żadnej grupie, jak dedykowane konto lokalne dla usług
grinder22
2
AccessControlType odmowa ma pierwszeństwo przed zezwoleniem, więc aby być całkowicie dokładnym, zasady odmawiające prawa dostępu powinny być również sprawdzane, a podczas sprawdzania typów odmowy powinno to być (AccessRight & rule.FileSystemRights) > 0spowodowane tym, że każdy odmowy dostęp podrzędny jest częścią AccessRightśrodków, które nie są pełne dostęp doAccessRight
TJ Rockefeller
Jak wspomniano powyżej w przypadku grinder22, musiałem się zmienić; if (ident.Groups.Contains (rules.IdentityReference)) do if (tożsamość.Groups.Contains (rules.IdentityReference) || identity.Owner.Equals (rules.IdentityReference)) tak, jak miałem użytkownika, który miał dostęp, ale nie był t w dowolnej grupie.
ehambright,
13

IMHO to jedyny w 100% niezawodny sposób na sprawdzenie, czy możesz pisać do katalogu, to faktycznie do niego pisać i ewentualnie wychwytywać wyjątki.

Darin Dimitrov
źródło
13

Na przykład dla wszystkich użytkowników (Builtin \ Users) ta metoda działa dobrze - ciesz się.

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
źródło
8

Spróbuj tego:

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
źródło
Jeśli się nie mylę, jest blisko, ale nie całkiem - pomija fakt, że fsAccessRule.AccessControlTypemoże być AccessControlType.Deny.
Jonathan Gilbert,
To działało dla mnie na mojej maszynie deweloperskiej Win7, ale zawiodło na Win10 (zarówno dla testera, jak i mojej własnej maszyny testowej). modyfikacja ssds (patrz poniżej) wydaje się to naprawić.
winwaed 28.10.17
6

Twój kod pobiera DirectorySecuritydla danego katalogu i poprawnie obsługuje wyjątek (z powodu braku dostępu do informacji o bezpieczeństwie). Jednak w twojej próbce tak naprawdę nie przesłuchujesz zwróconego obiektu, aby zobaczyć, jaki dostęp jest dozwolony - i myślę, że musisz to dodać.

Vinay Sajip
źródło
+1 - Właśnie natknąłem się na ten problem, w którym wyjątek nie został zgłoszony podczas wywoływania GetAccessControl, ale dostaję nieautoryzowany wyjątek podczas próby zapisu do tego samego katalogu.
Mayo,
6

Oto zmodyfikowana wersja odpowiedzi CsabaS , która zawiera jawne reguły odmowy dostępu. Funkcja przechodzi przez wszystkie FileSystemAccessRules dla katalogu i sprawdza, czy bieżący użytkownik ma rolę, która ma dostęp do katalogu. Jeśli nie znaleziono takich ról lub użytkownik jest w roli z odmową dostępu, funkcja zwraca wartość false. Aby sprawdzić prawa do odczytu, przekaż FileSystemRights.Read do funkcji; dla praw zapisu, przekaż FileSystemRights.Write. Jeśli chcesz sprawdzić uprawnienia dowolnego użytkownika, a nie bieżące, zastąp bieżącą identyfikatorem użytkownika WindowsIdentity żądaną identyfikacją WindowsIdentity. Odradzałbym także poleganie na takich funkcjach w celu ustalenia, czy użytkownik może bezpiecznie korzystać z katalogu. Ta odpowiedź doskonale wyjaśnia, dlaczego.

    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
źródło
Kod Csaba zawiódł mnie w systemie Windows 10 (ale dobrze na moim komputerze deweloperskim Win7). Powyższe wydaje się rozwiązać problem.
winwaed 28.10.17
4

Powyższe rozwiązania są dobre, ale dla mnie ten kod jest prosty i wykonalny. Wystarczy utworzyć plik tymczasowy. Jeśli plik zostanie utworzony, jego średni użytkownik ma dostęp do zapisu.

        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
źródło
3
Miły! Jedną rzeczą jest to, że użytkownik może mieć Createpozwolenie, ale nie Deletew tym przypadku byłoby to return false mimo że użytkownik nie ma uprawnień do zapisu.
Chris B
Najwygodniejsza odpowiedź na kodowanie :) Używam tego tylko jednego, jednak w przypadku dużych równoczesnych żądań tak duża ilość odczytu / zapisu może spowolnić działanie, więc w takich przypadkach można zastosować metodologię kontroli dostępu podaną w innych odpowiedziach.
vibs2006
1
Path.CombineZamiast tego użyj np Path.Combine(tempfilepath, "temp.txt").
ΩmegaMan
3

Możesz spróbować wykonać następujący blok kodu, aby sprawdzić, czy katalog ma dostęp do zapisu. Sprawdza 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
źródło
2

W kodzie masz potencjalny stan wyścigu - co się stanie, jeśli użytkownik będzie miał uprawnienia do zapisu do folderu podczas sprawdzania, ale zanim użytkownik faktycznie zapisze w folderze, to uprawnienie zostanie cofnięte? Zapis wyrzuci wyjątek, który będziesz musiał złapać i obsłużyć. Zatem wstępna kontrola jest bezcelowa. Równie dobrze możesz po prostu pisać i obsługiwać wszelkie wyjątki. To jest standardowy wzorzec dla twojej sytuacji.


źródło
1

Sama próba uzyskania dostępu do danego pliku niekoniecznie wystarcza. Test zostanie uruchomiony z uprawnieniami użytkownika uruchamiającego program - co niekoniecznie oznacza uprawnienia użytkownika, na których chcesz przetestować.

Mort
źródło
0

Zgadzam się z Ashem, to powinno być w porządku. Alternatywnie możesz użyć deklaratywnego CAS i faktycznie uniemożliwić uruchomienie programu, jeśli nie mają dostępu.

Uważam, że niektóre funkcje CAS mogą nie być obecne w C # 4.0 z tego, co słyszałem, nie jestem pewien, czy to może być problem, czy nie.

Ian
źródło
0

Nie mogłem zmusić GetAccessControl () do zgłaszania wyjątku w systemie Windows 7 zgodnie z zaleceniami przyjętej odpowiedzi.

Skończyło się na tym, że użyłem odmiany odpowiedzi SDD :

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

Mam nadzieję że to pomoże.

Patrick
źródło
0

Napotkałem ten sam problem: jak sprawdzić, czy umiem czytać / pisać w określonym katalogu. Skończyło się na łatwym rozwiązaniu, aby ... faktycznie to przetestować. Oto moje proste, ale skuteczne rozwiązanie.

 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

}

Mam nadzieję, że to pomoże !

l.raimondi
źródło
0

Większość odpowiedzi tutaj nie sprawdza dostępu do zapisu. Po prostu sprawdza, czy użytkownik / grupa może „Zezwolić na odczyt” (Przeczytaj listę ACE pliku / katalogu).

Również iteracja przez ACE i sprawdzanie, czy pasuje do identyfikatora bezpieczeństwa, nie działa, ponieważ użytkownik może być członkiem grupy, od której może uzyskać / utracić uprawnienia. Gorzej niż grupy zagnieżdżone.

Wiem, że to stary wątek, ale dla każdego, kto teraz patrzy, jest lepszy sposób.

Pod warunkiem, że użytkownik ma uprawnienia do odczytu, można użyć interfejsu API Authz, aby sprawdzić Efektywny dostęp.

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
źródło