Überprüfen auf Verzeichnis- und Dateischreibberechtigungen in .NET

78

In meiner .NET 2.0-Anwendung muss ich überprüfen, ob ausreichende Berechtigungen zum Erstellen und Schreiben von Dateien in ein Verzeichnis vorhanden sind. Zu diesem Zweck habe ich die folgende Funktion, die versucht, eine Datei zu erstellen und ein einzelnes Byte in sie zu schreiben. Anschließend löscht sie sich selbst, um zu testen, ob Berechtigungen vorhanden sind.

Ich dachte, der beste Weg, dies zu überprüfen, wäre, es tatsächlich zu versuchen und alle auftretenden Ausnahmen abzufangen. Ich bin jedoch nicht besonders glücklich über den allgemeinen Ausnahmefang. Gibt es also einen besseren oder vielleicht akzeptierteren Weg, dies zu tun?

private const string TEMP_FILE = "\\tempFile.tmp";

/// <summary>
/// Checks the ability to create and write to a file in the supplied directory.
/// </summary>
/// <param name="directory">String representing the directory path to check.</param>
/// <returns>True if successful; otherwise false.</returns>
private static bool CheckDirectoryAccess(string directory)
{
    bool success = false;
    string fullPath = directory + TEMP_FILE;

    if (Directory.Exists(directory))
    {
        try
        {
            using (FileStream fs = new FileStream(fullPath, FileMode.CreateNew, 
                                                            FileAccess.Write))
            {
                fs.WriteByte(0xff);
            }

            if (File.Exists(fullPath))
            {
                File.Delete(fullPath);
                success = true;
            }
        }
        catch (Exception)
        {
            success = false;
        }
    }
Andy
quelle
Vielen Dank für den Code, obwohl der Anrufer möglicherweise den falschen Eindruck hat, dass die Schreibberechtigung fehlt, wenn der Benutzer schreiben, aber nicht löschen kann. Ich würde dies ändern, um FileMode.Create zu verwenden und das Löschen von Dateien zu beseitigen. Natürlich brauchen Sie diesen Code nicht mehr, aber ich schreibe ihn zum Nutzen zukünftiger Leser.
n00b
3
string fullPath = directory + TEMP_FILE;Verwenden Sie die Path.Combine-Methode, anstatt Zeichenfolgen zu verketten, um fullPath zu erhalten. Path.Combine(directory, TEMP_FILE)
Nomail
Was ist, wenn jemand am nächsten Tag ein- und ausschlägt? Was ist, wenn sie zwei Tage später ein- und aussteigen? Ich bin sicher, dass die Leute diese Dinge nicht tun sollen, aber das Verhalten sollte definiert werden.
Scott Hannen

Antworten:

23

Die Antworten von Richard und Jason gehen in die richtige Richtung. Sie sollten jedoch die effektiven Berechtigungen für die Benutzeridentität berechnen, auf der Ihr Code ausgeführt wird. Keines der obigen Beispiele berücksichtigt beispielsweise die Gruppenmitgliedschaft korrekt.

Ich bin mir ziemlich sicher, dass Keith Brown in seiner Wiki-Version (zu diesem Zeitpunkt offline) des .NET-Entwicklerhandbuchs für Windows-Sicherheit Code dafür hatte . Dies wird auch in seinem Buch zur Programmierung der Windows-Sicherheit ausführlich erläutert .

Das Berechnen effektiver Berechtigungen ist nichts für schwache Nerven, und Ihr Code, der versucht, eine Datei zu erstellen und die ausgelöste Sicherheitsausnahme abzufangen, ist wahrscheinlich der Weg des geringsten Widerstands.

Kev
quelle
2
Dies ist auch die einzige zuverlässige Methode, da sonst jemand die Berechtigung zwischen Überprüfen und tatsächlichem Speichern ändern könnte (unwahrscheinlich, aber möglich).
Chris Chilvers
1
Danke dafür. Die einzige Änderung, die ich an meinem Code vornehmen sollte, besteht darin, eine Sicherheitsausnahme anstelle der allgemeinen "Ausnahme" abzufangen.
Andy
@Andy - Ja, es ist der Weg des geringsten Widerstands, es sei denn, Sie möchten den Code schreiben, um effektive Berechtigungen zu berechnen.
Kev
2
warum muss alles so verdammt kompliziert sein!
Vidar
3
@Triynko - Ich schlage vor, Sie lesen den Artikel, den ich zitiert habe: groups.google.com/group/… - Das Berechnen effektiver Berechtigungen ist nicht so einfach, wie es sich anhört. Sei mein Gast und gib eine Antwort, um mir das Gegenteil zu beweisen.
Kev
49

Directory.GetAccessControl(path) tut, wonach Sie fragen.

public static bool HasWritePermissionOnDir(string path)
{
    var writeAllow = false;
    var writeDeny = false;
    var accessControlList = Directory.GetAccessControl(path);
    if (accessControlList == null)
        return false;
    var accessRules = accessControlList.GetAccessRules(true, true, 
                                typeof(System.Security.Principal.SecurityIdentifier));
    if (accessRules ==null)
        return false;

    foreach (FileSystemAccessRule rule in accessRules)
    {
        if ((FileSystemRights.Write & rule.FileSystemRights) != FileSystemRights.Write) 
            continue;

        if (rule.AccessControlType == AccessControlType.Allow)
            writeAllow = true;
        else if (rule.AccessControlType == AccessControlType.Deny)
            writeDeny = true;
    }

    return writeAllow && !writeDeny;
}

(FileSystemRights.Write & rights) == FileSystemRights.Write verwendet übrigens etwas namens "Flags", das Sie wirklich nachlesen sollten, wenn Sie nicht wissen, was es ist :)

richardwiden
quelle
6
Das wird natürlich eine Ausnahme auslösen, wenn Sie die ACL im Verzeichnis nicht erhalten können.
Blowdart
3
Worauf prüft es? Dieses Verzeichnis hat Schreibberechtigungen, aber für welchen Benutzer? :)
Ivan G.
2
Es funktioniert, wenn Sie nur sehen möchten, ob der aktuelle Benutzer Schreibzugriff hat.
Donny V.
@aloneguid: Die Methode "GetAccessRules" gibt eine AuthorizationRuleCollection zurück. Die AthorizationRule-Klasse verfügt über eine IdentityReference-Eigenschaft, deren Laufzeittyp tatsächlich einer der beiden ist, die vom IdenityReference-Typ (entweder NTAccount oder Security) abgeleitet sind, der im Aufruf von GetAccessRules angegeben ist. Über die IdentityReference-Instanz (oder ihre abgeleiteten Typen) können Sie ermitteln, für welchen Benutzer die Regel gilt. Es wird in Form einer SID oder eines NTAccount-Namens vorliegen.
Triynko
7
Versuchen Sie, dies auf Ihrer Systemfestplatte unter Windows 7 mit einer Nicht-Administrator-Anwendung auszuführen. Es wird true zurückgegeben. Wenn Sie jedoch versuchen, in c: \ zu schreiben, wird eine Ausnahme angezeigt, die besagt, dass Sie keinen Zugriff haben!
Peter
34

Denyhat Vorrang vor Allow. Lokale Regeln haben Vorrang vor geerbten Regeln. Ich habe viele Lösungen gesehen (einschließlich einiger hier gezeigter Antworten), aber keine davon berücksichtigt, ob Regeln vererbt werden oder nicht. Daher schlage ich den folgenden Ansatz vor, der die Regelvererbung berücksichtigt (ordentlich in eine Klasse verpackt):

public class CurrentUserSecurity
{
    WindowsIdentity _currentUser;
    WindowsPrincipal _currentPrincipal;

    public CurrentUserSecurity()
    {
        _currentUser = WindowsIdentity.GetCurrent();
        _currentPrincipal = new WindowsPrincipal(_currentUser);
    }

    public bool HasAccess(DirectoryInfo directory, FileSystemRights right)
    {
        // Get the collection of authorization rules that apply to the directory.
        AuthorizationRuleCollection acl = directory.GetAccessControl()
            .GetAccessRules(true, true, typeof(SecurityIdentifier));
        return HasFileOrDirectoryAccess(right, acl);
    }

    public bool HasAccess(FileInfo file, FileSystemRights right)
    {
        // Get the collection of authorization rules that apply to the file.
        AuthorizationRuleCollection acl = file.GetAccessControl()
            .GetAccessRules(true, true, typeof(SecurityIdentifier));
        return HasFileOrDirectoryAccess(right, acl);
    }

    private bool HasFileOrDirectoryAccess(FileSystemRights right,
                                          AuthorizationRuleCollection acl)
    {
        bool allow = false;
        bool inheritedAllow = false;
        bool inheritedDeny = false;

        for (int i = 0; i < acl.Count; i++) {
            var currentRule = (FileSystemAccessRule)acl[i];
            // If the current rule applies to the current user.
            if (_currentUser.User.Equals(currentRule.IdentityReference) ||
                _currentPrincipal.IsInRole(
                                (SecurityIdentifier)currentRule.IdentityReference)) {

                if (currentRule.AccessControlType.Equals(AccessControlType.Deny)) {
                    if ((currentRule.FileSystemRights & right) == right) {
                        if (currentRule.IsInherited) {
                            inheritedDeny = true;
                        } else { // Non inherited "deny" takes overall precedence.
                            return false;
                        }
                    }
                } else if (currentRule.AccessControlType
                                                  .Equals(AccessControlType.Allow)) {
                    if ((currentRule.FileSystemRights & right) == right) {
                        if (currentRule.IsInherited) {
                            inheritedAllow = true;
                        } else {
                            allow = true;
                        }
                    }
                }
            }
        }

        if (allow) { // Non inherited "allow" takes precedence over inherited rules.
            return true;
        }
        return inheritedAllow && !inheritedDeny;
    }
}

Ich habe jedoch die Erfahrung gemacht, dass dies auf Remotecomputern nicht immer funktioniert, da Sie dort nicht immer das Recht haben, die Dateizugriffsrechte abzufragen. Die Lösung in diesem Fall ist zu versuchen; Möglicherweise sogar, indem Sie nur versuchen, eine temporäre Datei zu erstellen, wenn Sie den Zugriff kennen müssen, bevor Sie mit den "echten" Dateien arbeiten.

Olivier Jacot-Descombes
quelle
2
Ich denke, diese Antwort ist der beste Weg, um dies zu erreichen. Andere Antworten verwenden den gleichen Weg, um das Ergebnis zu erhalten. Da jedoch nur diese Antwort die geerbten Regeln und lokalen Regeln berechnet, ist sie meiner Meinung nach die genaueste. Danke und Glückwunsch.
Tolga Evcimen
18

Die von Kev akzeptierte Antwort auf diese Frage enthält keinen Code, sondern verweist nur auf andere Ressourcen, auf die ich keinen Zugriff habe. Hier ist mein bester Versuch, die Funktion zu nutzen. Es wird tatsächlich überprüft, ob es sich bei der angezeigten Berechtigung um eine Schreibberechtigung handelt und ob der aktuelle Benutzer zur entsprechenden Gruppe gehört.

Es ist möglicherweise nicht vollständig in Bezug auf Netzwerkpfade oder was auch immer, aber es ist gut genug für meinen Zweck, lokale Konfigurationsdateien unter "Programme" auf Beschreibbarkeit zu überprüfen:

using System.Security.Principal;
using System.Security.AccessControl;

private static bool HasWritePermission(string FilePath)
{
    try
    {
        FileSystemSecurity security;
        if (File.Exists(FilePath))
        {
            security = File.GetAccessControl(FilePath);
        }
        else
        {
            security = Directory.GetAccessControl(Path.GetDirectoryName(FilePath));
        }
        var rules = security.GetAccessRules(true, true, typeof(NTAccount));

        var currentuser = new WindowsPrincipal(WindowsIdentity.GetCurrent());
        bool result = false;
        foreach (FileSystemAccessRule rule in rules)
        {
            if (0 == (rule.FileSystemRights &
                (FileSystemRights.WriteData | FileSystemRights.Write)))
            {
                continue;
            }

            if (rule.IdentityReference.Value.StartsWith("S-1-"))
            {
                var sid = new SecurityIdentifier(rule.IdentityReference.Value);
                if (!currentuser.IsInRole(sid))
                {
                    continue;
                }
            }
            else
            {
                if (!currentuser.IsInRole(rule.IdentityReference.Value))
                {
                    continue;
                }
            }

            if (rule.AccessControlType == AccessControlType.Deny)
                return false;
            if (rule.AccessControlType == AccessControlType.Allow)
                result = true;
        }
        return result;
    }
    catch
    {
        return false;
    }
}
Bryce Wagner
quelle
Dieser funktioniert nicht für Gruppen, sondern nur für buchstäblich hinzugefügte Kontonamen in meinem Fall
Random
Hat dies also etwas mit dem Format "(S-1-5-21-397955417-626881126-188441444-512)" zu tun? Hat die Konvertierung der Zeichenfolge in einen solchen SecurityIdentifier Ihr Problem behoben? Aus Ihrem Kommentar geht nicht hervor, ob es jetzt für Sie funktioniert oder nicht.
Bryce Wagner
Wenn Sie "rule.IdentityReference.Value" als Parameter von currentuser.IsInRole () eingeben, verwenden Sie die IsInRole (string) -Methode, die versucht, mit dem regulären Wert "domain \ user" übereinzustimmen. Sie drücken also die SID-Zeichenfolge anstelle der Benutzernamenzeichenfolge. Wenn Sie jedoch meine Zeile davor verwenden, erhalten Sie ein SecurityIdentifier-Objekt, das dem Benutzer der angegebenen SID entspricht. Diese Überladung mit "Zeichenfolgen" -Argumenten ist eine kleine Falle für Entwickler. Sie akzeptiert erneut den Konto- oder Gruppennamen im vom Menschen redigierbaren Format und nicht in der Darstellung von SID-Zeichenfolgen.
Zufällig
Das Problem ist, dass "new SecurityIdentifier (SDDLFormat)" nicht mit normalen Gruppennamen funktioniert (Sie erhalten eine Argumentationsausnahme). Also habe ich eine Überprüfung hinzugefügt, ob es im SDDL-Format ist.
Bryce Wagner
2
Diese Lösung funktionierte für mich, hatte jedoch ein Problem mit dem Netzwerkordner. Der Ordner verfügt über eine Zugriffsregel, die das Schreiben ermöglicht BUILTIN\Administrators. Und da ich Administrator an meiner lokalen Station bin, ist das Snippet fälschlicherweise zurückgekehrt true.
Ilia Barahovski
5

IMO, Sie müssen wie gewohnt mit solchen Verzeichnissen arbeiten, aber anstatt die Berechtigungen vor der Verwendung zu überprüfen, sollten Sie die richtige Methode zum Behandeln von UnauthorizedAccessException bereitstellen und entsprechend reagieren. Diese Methode ist einfacher und weniger fehleranfällig.

Schiedsrichter
quelle
1
Sie wollten wahrscheinlich sagen: "Diese Methode ist einfacher und viel weniger fehleranfällig."
CJBarth
3

Versuchen Sie, mit diesem C # -Schnipsel zu arbeiten, den ich gerade erstellt habe:

using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string directory = @"C:\downloads";

            DirectoryInfo di = new DirectoryInfo(directory);

            DirectorySecurity ds = di.GetAccessControl();

            foreach (AccessRule rule in ds.GetAccessRules(true, true, typeof(NTAccount)))
            {
                Console.WriteLine("Identity = {0}; Access = {1}", 
                              rule.IdentityReference.Value, rule.AccessControlType);
            }
        }
    }
}

Und hier ist eine Referenz, die Sie sich auch ansehen können. Mein Code gibt Ihnen möglicherweise eine Vorstellung davon, wie Sie nach Berechtigungen suchen können, bevor Sie versuchen, in ein Verzeichnis zu schreiben.

Jason Evans
quelle
typeofGibt in diesem Fall den Typ eines Objekts zurück NTAccount. docs.microsoft.com/en-us/dotnet/csharp/language-reference/… Der Aufruf von GetAccessRules()benötigt den Typ des Kontos, wenn er aufgerufen wird. msdn.microsoft.com/en-us/library/…
Jason Evans
Warum verwenden NTAccount? immer NTAccount verwenden ?
Kiquenet
In diesem Fall ja. NTAccountstellt ein Benutzerkonto auf einem Windows-PC dar, weshalb wir es im obigen Code benötigen.
Jason Evans
1

Da die statische Methode 'GetAccessControl' in der aktuellen Version von .Net Core / Standard zu fehlen scheint, musste ich die Antwort von @Bryce Wagner ändern (ich habe eine modernere Syntax verwendet):

public static class PermissionHelper
{
  public static bool? CurrentUserHasWritePermission(string filePath)

     => new WindowsPrincipal(WindowsIdentity.GetCurrent())
        .SelectWritePermissions(filePath)
        .FirstOrDefault();


  private static IEnumerable<bool?> SelectWritePermissions(this WindowsPrincipal user, string filePath)
     => from rule in filePath
                    .GetFileSystemSecurity()
                    .GetAccessRules(true, true, typeof(NTAccount))
                    .Cast<FileSystemAccessRule>()
        let right = user.HasRightSafe(rule)
        where right.HasValue
        // Deny takes precedence over allow
        orderby right.Value == false descending
        select right;


  private static bool? HasRightSafe(this WindowsPrincipal user, FileSystemAccessRule rule)
  {
     try
     {
        return user.HasRight(rule);
     }
     catch
     {
        return null;
     }
  }

  private static bool? HasRight(this WindowsPrincipal user,FileSystemAccessRule rule )
     => rule switch
     {
        { FileSystemRights: FileSystemRights fileSystemRights } when (fileSystemRights &
                                                                      (FileSystemRights.WriteData | FileSystemRights.Write)) == 0 => null,
        { IdentityReference: { Value: string value } } when value.StartsWith("S-1-") &&
                                                            !user.IsInRole(new SecurityIdentifier(rule.IdentityReference.Value)) => null,
        { IdentityReference: { Value: string value } } when value.StartsWith("S-1-") == false &&
                                                            !user.IsInRole(rule.IdentityReference.Value) => null,
        { AccessControlType: AccessControlType.Deny } => false,
        { AccessControlType: AccessControlType.Allow } => true,
        _ => null
     };


  private static FileSystemSecurity GetFileSystemSecurity(this string filePath)
    => new FileInfo(filePath) switch
    {
       { Exists: true } fileInfo => fileInfo.GetAccessControl(),
       { Exists: false } fileInfo => (FileSystemSecurity)fileInfo.Directory.GetAccessControl(),
       _ => throw new Exception($"Check the file path, {filePath}: something's wrong with it.")
    };
}
dtwk2
quelle
0

Laut diesem Link: http://www.authorcode.com/how-to-check-file-permission-to-write-in-c/

Es ist einfacher, die vorhandene Klasse SecurityManager zu verwenden

string FileLocation = @"C:\test.txt";
FileIOPermission writePermission = new FileIOPermission(FileIOPermissionAccess.Write, FileLocation);
if (SecurityManager.IsGranted(writePermission))
{
  // you have permission
}
else
{
 // permission is required!
}

Es scheint jedoch veraltet zu sein. Es wird empfohlen, stattdessen PermissionSet zu verwenden.

[Obsolete("IsGranted is obsolete and will be removed in a future release of the .NET Framework.  Please use the PermissionSet property of either AppDomain or Assembly instead.")]
Mulder2008
quelle
-1
private static void GrantAccess(string file)
        {
            bool exists = System.IO.Directory.Exists(file);
            if (!exists)
            {
                DirectoryInfo di = System.IO.Directory.CreateDirectory(file);
                Console.WriteLine("The Folder is created Sucessfully");
            }
            else
            {
                Console.WriteLine("The Folder already exists");
            }
            DirectoryInfo dInfo = new DirectoryInfo(file);
            DirectorySecurity dSecurity = dInfo.GetAccessControl();
            dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));
            dInfo.SetAccessControl(dSecurity);

        }
Anumars
quelle
Was ist WellKnownSidType.WorldSid ?
Kiquenet
Nur eine zufällige Antwort auf Ihre Frage @Kiquenet WorldSid ist die Build-Gruppe "Jeder".
AussieALF