Einzelnen Beitrag anzeigen

berens

Registriert seit: 3. Sep 2004
431 Beiträge
 
Delphi 2010 Professional
 
#1

Windows-Berechtigungen (Benutzer/Gruppen) im eigenen Programm verwenden

  Alt 23. Apr 2015, 09:49
Hallo zusammen!

Ich bilde mir ein schon die Suchfunktion genutzt zu haben, aber konnte leider keine Einsteiger-Freundlichen Infos erhalten, womit ich aktuell sinnvoll etwas anfangen könnte.

Das will ich in etwa machen: Siehe Anhang.

Ziel ist es, -mit dem Sicherheitssystem von Windows- die Benutzer/Gruppen von Windows (bei Einzelsystemen) bzw. ActiveDirectory in Domänen für bestimmte Funktionen in meinem Programm zu berechtigen bzw. zu sperren. So dürfen dann beispielsweise alle Personen einer Gruppe das Programm starten und Daten betrachten, bestimmte Benutzer allerdings nur Daten verändern oder löschen.

Es gelten die üblichen Bedingungen (und Einschränkungen): Benutzer dürfen das Programm auch dann starten, wenn Sie nicht namentlich in [meiner] Liste aufgeführt sind, sondern auch wenn Sie Mitglieder in einer oder mehrerer Gruppen sind, die diese Berechtigung haben.
Das Sicherheitssystem von Windows (Zumindesten in Bezug auf die Sicherheitsrechte von Dateien und Registry-Keys) sollte den Meisten hier bekannt sein.

Da mir das auf API-Ebene alles zu abstrakt ist verwendet ich die "Security Library" der Jedi-Komponenten: http://blog.delphi-jedi.net/security-library/

Bisherige, unter anderem verwendete Websites:
http://www.michael-puff.de/Programmi...kel/DACL.shtml (Danke Luckie!)
http://www.delphipraxis.net/86609-st...rogrammen.html
http://borland.newsgroups.archived.a...060121859.html

Leider ist es so, dass man mit der bei Jedi mitgelieferten Dokumentation nirgendwo "einsteigen" kann. Dort wird stets nur in Abkürzungen gesprochen und direkt auf der Startseite gesagt nach dem Motto "Ihr müsst euch mit dem Windows-Sicherheitssystem auskennen, um die Komponente nutzen zu können." Hmja... Ohne Einstiegspunkt kann man sich das schlecht beibringen...

In dem o.g. Tutorial zum Sicherheitsdialog wird zwar schön beschrieben, wie man die Zugriffsrechte von Dateien ändern kann, aber leider nicht, die das mit einem eigenen Objekt geht.

Klären wir vielleicht zunächst mal die Terminologie, korrigiert mich, wenn ich falsch liege:

DACL (discretionary access control list)
Ist letztendlich die Liste mit allen Benutzern und allen Rechten (und "Verweigerungen") für das Objekt, die Datei oder den Registrykey.

ACE (Access control entry)
Ist EIN Eintrag in der DACL, also z.B. "Gruppe A" darf die Datenbank bearbeiten oder "Gruppe Leiharbeiter" wird Bearbeiten verweigert. Eine DACL darf beliebig viele ACEs enthalten.
Wenn Benutzer "Alice" nun in "Gruppe A" ist, darf Sie also damit auch automatisch die Datenbank bearbeiten, es sei denn, Sie ist zusätzlich|stattdessen Mitglied in "Gruppe Leiharbeiter".


Korrektur: ACE ist ein RECHT, das immer nur für den aktuell ausgewählen Benutzer/Gruppe bearbeitet werden kann (z.B. "Lesen" oder "Schreiben")?

ACLEditor
Das Programm/Fenster aus dem Screenshot (siehe Anhang). Kann nativ über die Windows-API "EditSecurity(GetActiveWindow, Self)" aufgerufen werden.


Vorgehensweise:
Anhand eines Beispiels der Jedi-Komponenten habe ich mich zunächst für die "StringDescriptor"-Demo entschieden: rein optisch kommt diese meinem Vorhaben am Nächsten.

Der ACL Editor wird vorbereitet
Delphi-Quellcode:
  ACLEditor := TJwSecurityDescriptorDialog.Create(GetActiveWindow);
// ACLEditor.Flags := [sdfAdvanced, sdfEditDacl, sdfEditOwner, sdfEditEffective,
// sdfNoAdditionalPermission, sdfEditSacl];
  ACLEditor.Flags := [sdfEditSacl];
  ACLEditor.ObjectName := 'Beschreibung des Sicherheitsdialog DEMO';
  ACLEditor.ServerName := '';
Erzeugen der Instanz des Editors, setzen der Flags (welche Buttons sollen sichtbar sein, etc.) und der Titelleiste des Editors.

Für die Demo: Erzeugen von ein paar ACEs, damit schon mal ein paar Leute Berechtigungen haben, wenn wir mit der GUI vom Editor rumspielen:
Delphi-Quellcode:
  SecurityDescriptor := TJwSecurityDescriptor.Create;
  SecurityDescriptor.DACL.Add(TJwDiscretionaryAccessControlEntryAllow.Create(nil,[],GENERIC_ALL,JwGuestsSID,True));
  SecurityDescriptor.DACL.Add(TJwDiscretionaryAccessControlEntryAllow.Create(nil,[],GENERIC_READ,JwWorldSID,false));
  SecurityDescriptor.DACL.Add(TJwDiscretionaryAccessControlEntryAllow.Create(nil,[],GENERIC_WRITE,JwLocalSystemSID,false));

  ACLEditor.SecurityDescriptor := SecurityDescriptor; // das Editor-GUI soll nun also ^-- diese Liste bearbeiten
Die Gruppe "Gäste" [meines PCs --> "JwGuestsSID"] darf alles machen, "Jeder" (JwWorldSID) darf nur lesen, und "SYSTEM" (JwLocalSystemSID) darf (nur) schreiben. Soweit so gut. Die Thematik der SIDs verstehe ich und soll hier nicht unnötiger Weise (?) näher beleuchtet werden. Parameter 3+4 habe ich also verstanden, der Rest ist noch eher unklar.

Nach dem Ausführen des Editors (if ACLEditor.ShowModal then) kann ich das Objekt SecurityDescriptor durchgehen und alle Einträge auslesen.

Wenn ich das richtig Verstanden habe, kann ich mir mit Memo1.Text := ACLEditor.SecurityDescriptor.GetSecurityDescriptorString(JwAllSiFlags) ;
diese komplette DACL als String zurückgeben lassen, in dem alle zugelassenen/verweigerten Benutzer(-SIDs) stehen, sowie die Berechtigungen, so dass ich diese z.B. in meiner Datenbank speichern, und beim nächsten Programmstart wieder auslesen kann (?).

Nur kommt der Punkt, an dem ich hänge:
Wie weise ich dem Dialog "zeilenweise" die Rechte zu, die der Benutzer bearbeiten können soll?
Als Optionen sollen nur "Zulassen" und "Verweigern" verwendet werden. (Oder nur "Zulassen", wenn das wesentlich einfacher ist.)

In der o.g. Demo "StringDescriptor" wird folgendes für diese zeilenweisen Rechte verwendet:
Delphi-Quellcode:
    Mapping := TJwSecurityGenericMappingClass(ComboBoxMapping.Items.Objects[ComboBoxMapping.ItemIndex]);
    ACLEditor.Mapping := Mapping; // zuweisung der o.g. Rechte an den Editor/GUI
ComboBoxMapping.Items.Objects[ComboBoxMapping.ItemIndex] bezieht sich hier in der Demo natürlich auf ein Objekt in einer ComboBox, die beim Programmstart befüllt wurde:
Delphi-Quellcode:
  ComboBoxMapping.Items.AddObject('Generic',Pointer(TJwSecurityGenericMapping));
  ComboBoxMapping.Items.AddObject('Process',Pointer(TJwSecurityProcessMapping));
  ComboBoxMapping.Items.AddObject('Threads',Pointer(TJwSecurityThreadMapping));
  ComboBoxMapping.Items.AddObject('Service',Pointer(TJwSecurityServiceMapping));
  ComboBoxMapping.Items.AddObject('FileFolder',Pointer(TJwSecurityFileFolderMapping));
Hier wird also EIN Pointer auf eine KLASSE (nicht ein instanziertes Objekt!) übergeben, welche -abgeleitet von TJwSecurityGenericMapping- die Klassen-Funktionen "GetMapping", "MapAccessMaskToString" und "GetAccessNames" bereitstellt. Je nach verlinkert Klasse sind in dem Editor also andere ACE-Zeilen zu sehen.
Der DACL-Editor kann sich also über diese Callback-Funktionen ein Kontanten/Konstantes(?) Array holen, in dem die einzelnen Zeilen (ACEs?) stehen. Beispiel für Datei/Ordner-Sicherheit im Windows-Explorer:
Delphi-Quellcode:
 FileFolderMapping: array[1..19] of TJwRightsMapping =
    (

    (Right: FILE_ALL_ACCESS; Name: 'Full control';
    Flags: SI_ACCESS_GENERAL or SI_ACCESS_SPECIFIC),
    (Right: FILE_GENERIC_READ or FILE_GENERIC_WRITE or FILE_GENERIC_EXECUTE or Delete;
    Name: 'Modify';
    Flags: SI_ACCESS_GENERAL or SI_ACCESS_SPECIFIC),
    (Right: FILE_GENERIC_READ or FILE_GENERIC_EXECUTE;
    Name: 'Read and execute';
etc.
Angenommen ich leite diese Klasse ab und erstelle mein statisches eigenes Array, sollte das ja dann klappen?

Die Frage ist nun halt auch noch, wie werden diese Werte abgespeichert, bzw. um was genau muss ich mich kümmern. Der DACL-Editor scheint ja selbst eine "Bit-Maske" zu erwarten, die er dann für jeden Benutzer/Gruppe füllen kann mit den Berechtigungen. Wie geht man das am schlausten an?

Wofür braucht der Editor
ACLEditor.OnSetSecurity := OnSetSecurity; In der Demo wird kommentarlos in OnSetSecurity die var-Variable bSuccess := true gesetzt...

Letztendlich: Wie prüfe ich, ob Benutzer "Alice" (bzw. deren SID) das Recht "Datenbank bearbeiten" hat, wenn in der DACL nur "Gruppe A" und "Gruppe Leiharbeiter" aufgeführt sind? (Annahme: Alice ist Mitglied in Gruppe A)

Vielen Dank im Voraus, vielleicht hilft mein Beitrag auch mal jemand anderem...


Edit1: Scheinbar geht es auch ohne das Konstanten-Array. Seine eigenen Berechtigungen kann man erstellen, indem man von TJwSecurityGenericMapping eine Klasse ableitet und bestimmte Funktionen überschreibt:
Delphi-Quellcode:
type
  TMySampleMapping = class(TJwSecurityGenericMapping)
  public
    class function GetMapping: TGenericMapping; override;
    class function GetBitMappingString(Idx: Cardinal): TJwString; override;
  end;

class function TMySampleMapping.GetBitMappingString(Idx: Cardinal): TJwString;
begin
  case idx of
    0: Result := 'Programm starten'; // Zeile 1 im ACL-Editor
    1: Result := 'Daten schreiben'; // Zeile 2
  else
    Result := 'DEMO ' + IntToStr(Idx); // Alle weiteren Zeilen, max. 32 insgesamt.
  end
end;

class function TMySampleMapping.GetMapping: TGenericMapping;
begin
  Result.GenericRead := READ_CONTROL or STANDARD_RIGHTS_READ;
  Result.GenericWrite := WRITE_DAC or WRITE_OWNER or Delete or ACCESS_SYSTEM_SECURITY or STANDARD_RIGHTS_WRITE;
  Result.GenericExecute := STANDARD_RIGHTS_EXECUTE or SYNCHRONIZE;
  Result.GenericAll := Result.GenericRead or Result.GenericWrite or
  Result.GenericExecute;
end;

//Im Hauptprogramm:
Mapping := TJwSecurityGenericMappingClass(Pointer(TMySampleMapping));
ACLEditor.Mapping := Mapping;
Die "GetMapping" Prozedur soll in der abgeleiteten Klasse unbedingt überschrieben werden. Warum?

Edit2:
So kann man die im Dialog eingestellten Optionen laden/speichern:
Delphi-Quellcode:
  // Speichern:
  if ACLEditor.ShowModal then // Dialog ausführen, und wenn mit "Ok" beendet:
  begin
    // Einstellungen/Auswahl in einem String speichern (hier: Memo1.Text)
    // diesen String kann man nun z.B. in der Datenbank speichern!
    Memo1.Text := ACLEditor.SecurityDescriptor.GetSecurityDescriptorString(JwAllSiFlags);
  end;

  // Laden:
  // hier muss der vorher gespeicherte Wert schon fertig drinnen geladen sein, sonst gibts Exception!
  SecurityDescriptor := TJwSecurityDescriptor.Create(Memo1.Text);
  ACLEditor.SecurityDescriptor := SecurityDescriptor;
Miniaturansicht angehängter Grafiken
dlg.jpg  

Geändert von berens (23. Apr 2015 um 10:31 Uhr)
  Mit Zitat antworten Zitat