ACLs programmieren - Teil 5

15.06.2006 von Martin Kuppinger
Mit einem genaueren Blick auf die Methoden und Eigenschaften, mit denen ACLs ausgelesen werden können, schließen wir die Artikelserie ab. Das geschieht im Rahmen einer Anwendung, mit der sich ACLs in Datenbanken dokumentieren lassen.

Bei der hier vorgestellten Anwendung werden alle Informationen der Datenbanken im Datenbankverzeichnis ausgelesen und in eine Datenbank geschrieben. Die Grundstruktur der Anwendung ist relativ einfach. Es gibt eine Maske, in der einzelne Einträge in der ACL dokumentiert werden. Dort finden sich Felder für alle Informationen, die aus der ACL und den einzelnen CL-Einträgen ausgelesen werden. Dazu gehören beispielsweise die Attribute und die Rolleninformationen.

Eine zweite Maske enthält die Schaltfläche zur Generierung des Berichts. Im Skript wird eine äußere Schleife für die Datenbanken durchlaufen. Für jede Datenbank wird auf die ACL zugegriffen. In einer weiteren Schleife werden alle ACL-Einträge verarbeitet. Dabei wird pro Eintrag ein Dokument erstellt und in der Datenbank gespeichert.

Auf diese Dokumente kann wiederum mit Hilfe on Ansichten zugegriffen werden. Dabei lassen ich unterschiedliche Anforderungen durch entsprechende ortierungen und Auswahlkriterien ür die Ansichten erfüllen.

Die Datenbank

Die Struktur der Maske ergibt sich aus den Eigenschaften, die zu ACLs und ACL-Einträgen abgefragt werden können. Die Eigenschaften ergeben sich aus der Dokumentation. So lassen sich beispielsweise die definierten Rollen und die Funktionen, die ein Administrations-Server ausführen darf, ermitteln.

Einige dieser Eigenschaften werden auch in der Anwendung genutzt. Allerdings werden nicht alle abgefragt. Die Anwendung lässt sich aber einfach entsprechend erweitern, da alle möglichen Varianten zumindest mit einem Beispiel darin zu finden sind. Bild 1 zeigt die Maske, über die die Datenbank definiert wird.

Bild 1: Die Struktur der Maske für die Informationen, die abgespeichert werden.

Das einzige Feld, das nicht über eine Eigenschaft gefüllt wird, ist RunDate. Dort wird das Startdatum des Skripts abgelegt, um dokumentieren zu können, von welchem Zeitpunkt die definierten ACLs stammen.

Die zweite Maske ist noch sehr viel einfacher gestaltet. Dort gibt es nur eine Schaltfläche, über die das Auslesen der Informationen gestartet wird. Für beide Masken wäre eine Optimierung vorstellbar. So könnte man beispielsweise beim Auslesen zunächst den Servernamen erfragen, für den die Auswertung erfolgen soll. Ebenso könnte man die Arten von Dateien festlegen, also beispielsweise Datenbank oder Template. Um die grundlegenden Aspekte des Auslesens von ACL-Informationen zu dokumentieren, reicht as aber aus.

Das Skript

Listing 1 zeigt das Skript, das für diese Analyse verwendet wird. Es ist relativ lang, was aber vor allem durch erforderliche Fallabfragen und eine Reihe von If-Else-Anweisungen bedingt ist, mit denen die Eigenschaften verarbeitet und in eine einfacher lesbare Form gebracht werden.

Sub Click(Source As Button)
Dim dbdir As New NotesDbDirectory ("domino7test1")
Dim db As NotesDatabase
Dim session As New NotesSession
Dim currdb As NotesDatabase
Dim doc As NotesDocument
Set currdb=session.CurrentDatabase
Dim acl As NotesACL
Dim entry As NotesACLEntry
Dim datetime As String
datetime = Date$ + Time$
Dim inetAccess As String
Dim consACLs As String
Dim roles As String
Set db = dbdir.GetFirstDatabase(DATABASE)
While Not(db Is Nothing)
Call db.Open("","")
Set acl = db.ACL
Select Case acl.InternetLevel
Case ACLLEVEL_NOACCESS:
inetAccess = "Kein Zugriff"
Case ACLLEVEL_DEPOSITOR:
inetAccess = "Ersteller"
Case ACLLEVEL_READER:
inetAccess = "Leser"
Case ACLLEVEL_AUTHOR:
inetAccess = "Autor"
Case ACLLEVEL_EDITOR:
inetAccess = "Editor"
Case ACLLEVEL_DESIGNER:
inetAccess = "Designer"
Case ACLLEVEL_MANAGER:
inetAccess = "Manager"
End Select
If acl.UniformAccess Then
consACLs = "Ja"
Else
consACLs = "Nein"
End If
Set entry = acl.GetFirstEntry
While Not (entry Is Nothing)
Set doc = New NotesDocument(currdb)
doc.Form = "ACLinfo"
doc.Server="domino7test1"
doc.Datenbank=db.FileName
doc.Datum=datetime
doc.Name = entry.Name
doc.InternetZugriff = inetAccess
doc.ConsistentACLs = consACLs
Select Case entry.Level
Case ACLLEVEL_NOACCESS:
doc.Zugriffslevel = "Kein Zugriff"
Case ACLLEVEL_DEPOSITOR:
doc.Zugriffslevel = "Ersteller"
Case ACLLEVEL_READER:
doc.Zugriffslevel = "Leser"
Case ACLLEVEL_AUTHOR:
doc.Zugriffslevel = "Autor"
Case ACLLEVEL_EDITOR:
doc.Zugriffslevel = "Editor"
Case ACLLEVEL_DESIGNER:
doc.ZUgriffslevel = "Designer"
Case ACLLEVEL_MANAGER:
doc.Zugriffslevel = "Manager"
End Select
If entry.IsPerson Then
doc.Benutzertyp = "Person"
Else
If entry.IsGroup Then
doc.Benutzertyp = "Gruppe"
Else
doc.Benutzertyp = "Server"
End If
End If
If entry.CanCreateDocuments Then
doc.DocCreate = "Ja"
Else
doc.DocCreate = "Nein"
End If
If entry.CanDeleteDocuments Then
doc.DocDelete = "Ja"
Else
doc.DocDelete = "Nein"
End If
If entry.CanCreatePersonalAgent Then
doc.PersonalAgent = "Ja"
Else
doc.PersonalAgent = "Nein"
End If
If entry.CanCreatePersonalFolder Then
doc.PersonalFolder = "Ja"
Else
doc.PersonalFolder = "Nein"
End If
If entry.CanCreateSharedFolder Then
doc.SharedFolders = "Ja"
Else
doc.SharedFolders = "Nein"
End If
If entry.CanCreateLSOrJavaAgent Then
doc.LSAgents = "Ja"
Else
doc.LSAgents = "Nein"
End If
If entry.CanReplicateOrCopyDocuments Then
doc.DocRepl = "Ja"
Else
doc.DocRepl = "Nein"
End If
If entry.IsPublicReader Then
doc.PubRead = "Ja"
Else
doc.PubRead = "Nein"
End If
If entry.IsPublicWriter Then
doc.PubWrite = "Ja"
Else
doc.PubWrite = "Nein"
End If
roles = ""
Forall r In entry.Roles
roles = roles + r
End Forall
doc.Roles = roles
Call doc.Save(True,True)
Set entry = acl.GetNextEntry(entry)
Wend
Set db = dbdir.GetNextDatabase
Wend
End Sub

Die Grundstruktur des Skripts ist sehr einfach. Für jede Datenbank werden innerhalb einer Schleife über alle Datenbanken im Datenbankverzeichnis allgemeine ACL-Informationen ausgelesen, bevor in einer weiteren Schleife die ACLEinträge durchlaufen werden. Für jeden ACLEintrag wird ein Dokument erstellt, in dem alle Informationen gespeichert werden.

Dadurch sammeln sich bei jedem Durchlauf einige hundert Dokumente an, weil es pro Datenbank mehrere ACL-Einträge gibt. Bei 40 Datenbanken mit durchschnittlich fünf Einträgen werden beispielsweise 200 Dokumente erzeugt.

Der Code

Der Code ist zwar lang, aber recht einfach. Zu Beginn müssen zunächst mehrere Objekte dimensioniert und teilweise auch initialisiert werden. Wichtig sind vor allem die ersten Zeilen:

Dim dbdir As New NotesDbDirectory ("domino7test1")
Dim db As NotesDatabase
Dim session As New NotesSession
Dim currdb As NotesDatabase
Dim doc As NotesDocument
Set currdb=session.CurrentDatabase

Es gibt zwei Datenbank-Objekte. Eines wird für die aktuelle Datenbank benötigt. Das ist das Objekt urrdb. Das andere wird für die verschiedenen Datenbanken genutzt, die der Reihe nach in der Schleife geöffnet werden. Außerdem muss es ein Dokument geben, in das die Ergebnisse der Analyse geschrieben werden.

Folgende Variablen werden für die ACL und die ACL-Einträge benötigt:

Dim acl As NotesACL
Dim entry As NotesACLEntry

Um das Datum der Analyse festzuhalten, muss der aktuelle Zeitpunkt ermittelt werden. Dazu wird, wie auch in dem in Teil 4 vorgestellten Skript, folgender Code verwendet:

Dim datetime As String
datetime = Date$ + Time$

Nach einigen weiteren Dim-Anweisungen erfolgt der Zugriff auf die erste Datenbank im Datenbankverzeichnis. Diese wird nach dem Beginn der Schleife gleich geöffnet.

Set db = dbdir.GetFirstDatabase(DATABASE)
While Not(db Is Nothing)
Call db.Open("","")

Innerhalb der Schleife wird auf die ACL zugegriffen. Die erste ermittelte Eigenschaft ist das maximale Zugriffsniveau für Zugriffe über den Browser, also die Eigenschaft InternetLevel. Die Rückgabewerte werden dabei in der Select-Case- Anweisung in verständlicheren Text umgesetzt:

Set acl = db.ACL
Select Case acl.InternetLevel
Case ACLLEVEL_NOACCESS:
inetAccess = "Kein Zugriff"
Case ACLLEVEL_DEPOSITOR:
inetAccess = "Ersteller"
Case ACLLEVEL_READER:
inetAccess = "Leser"
Case ACLLEVEL_AUTHOR:
inetAccess = "Autor"
Case ACLLEVEL_EDITOR:
inetAccess = "Editor"
Case ACLLEVEL_DESIGNER:
inetAccess = "Designer"
Case ACLLEVEL_MANAGER:
inetAccess = "Manager"
End Select

Überprüfen der ACLs

Da es immer diese sieben Level gibt, könnte man hier auch mit einer Funktion arbeiten. Das Ergebnis wird in eine Variable geschrieben, da das Dokument erst später bei der Verarbeitung der einzelnen ACLEinträge zur Verfügung steht.

Weiter geht es mit der Auswertung einer weiteren Eigenschaft. Über UniformAccess wird noch geprüft, ob einheitliche ACLs über alle Server für diese Datenbank definiert sind. Als Ergebnis werden die Texte Ja oder Nein in eine Variable gespeichert:

If acl.UniformAccess Then
consACLs = "Ja"
Else
consACLs = "Nein"
End If

Nun beginnt die Verarbeitung der einzelnen ACLEinträge. Dazu wird zunächst auf den ersten Eintrag zugegriffen, bevor der Schleifendurchlauf beginnt.

Set entry = acl.GetFirstEntry
While Not (entry Is Nothing)

Innerhalb der Schleife wird anschließend ein neues Dokument in der aktuellen Datenbank – also der zur ACL-Analyse – erstellt. Dieses Dokument basiert auf der oben kurz vorgestellten Maske.

Set doc = New NotesDocument(currdb)
doc.Form = "ACLinfo"

In dieses Dokument werden gleich die Informationen geschrieben, die bereits vorhanden sind. Dazu zählen der Name des Servers, der Datenbank, des Internet-Zugriffslevels und – als erste Eigenschaft des Eintrags – auch der Name, für den dieser ACL-Eintrag definiert ist:

doc.Server="domino7test1"
doc.Datenbank=db.FileName
doc.Datum=datetime
doc.Name = entry.Name
doc.InternetZugriff = inetAccess
doc.ConsistentACLs = consACLs

Vereinfachung des Zugriffslevel

Es folgt eine weitere Select-Case-Anweisung, über die der Zugriffslevel für diesen Eintrag in eine einfacher verständliche Form umgesetzt wird.

Select Case entry.Level
Case ACLLEVEL_NOACCESS:
doc.Zugriffslevel = "Kein Zugriff"
Case ACLLEVEL_DEPOSITOR:
doc.Zugriffslevel = "Ersteller"
Case ACLLEVEL_READER:
doc.Zugriffslevel = "Leser"
Case ACLLEVEL_AUTHOR:
doc.Zugriffslevel = "Autor"
Case ACLLEVEL_EDITOR:
doc.Zugriffslevel = "Editor"
Case ACLLEVEL_DESIGNER:
doc.ZUgriffslevel = "Designer"
Case ACLLEVEL_MANAGER:
doc.Zugriffslevel = "Manager"
End Select'

Anschließend erfolgt die Verarbeitung weiterer Eigenschaften. Zunächst wird ermittelt, ob der Eintrag für eine Person, eine Gruppe oder einen Server definiert wurde. Dazu werden die folgenden Bedingungen genutzt:

If entry.IsPerson Then
doc.Benutzertyp = "Person"
Else
If entry.IsGroup Then
doc.Benutzertyp = "Gruppe"
Else
doc.Benutzertyp = "Server"
End If
End If

Dieser Ansatz funktioniert nur, wenn diese Einträge konsequent gesetzt werden. Ansonsten muss man auch explizit prüfen, ob es sich um einen Server handelt.

Verarbeitung der Attribute

Der nachfolgende Teil des Listings verarbeitet die verschiedenen Attribute, wobei immer mit einer If-Else-Anweisung gearbeitet wird, um diese in Ja respektive Nein umzusetzen. Ein Beispiel:

If entry.CanCreateDocuments Then
doc.DocCreate = "Ja"
Else
doc.DocCreate = "Nein"
End If

Bild 2: Die Maske mit der Schaltfläche für den Start der Analyse.

Weiter geht es mit der Verarbeitung der Rollen. Die Rollen können über die Eigenschaft Roles der Klasse NotesACLEntry ermittelt werden. Diese Eigenschaft ist nicht mit der in der Klasse NotesACL zu verwechseln. Während bei der Klasse NotesACL ermittelt werden kann, welche Rollen überhaupt in einer Datenbank definiert sind, geht es bei NotesACLEntry um die Klassen, die einem Eintrag in der ACL zugeordnet sind.

In beiden Fällen ist der Rückgabewert ein Array, das Schritt für Schritt in einer Forall-Schleife durchlaufen wird. Im Beispiel wird einfach eine Zeichenkette erzeugt, in der nacheinander die Rollen stehen. Das ist der einfachste Ansatz, da die Zahl der Rollen variabel ist.

Wichtig ist, dass die Variable roles vor der Abfrage der Eigenschaft auf eine leere Zeichenkette gesetzt wird, weil sonst immer längere Texte generiert würden.

roles = ""
Forall r In entry.Roles
roles = roles + r
End Forall
doc.Roles = roles

Speichern des Dokuments

Nachdem alle Informationen zu dem ACL-Eintrag zusammengetragen wurden, muss das Dokument nur noch gespeichert werden.

Call doc.Save(True,True)

Nach Aufruf des nächsten Eintrags in der ACL kann der nächste Durchlauf der inneren Schleife für die ACL-Einträge beginnen.

Set entry = acl.GetNextEntry(entry)
Wend

Abschließend wird auch die äußere Schleife beendet. Hier wird zunächst die nächste Datenbank aus dem Datenbankverzeichnis aufgerufen, bevor ein weiterer Schleifendurchlauf beginnt.

Set db = dbdir.GetNextDatabase
Wend

In den beiden Durchläufen werden nun alle ACLEinträge verarbeitet. Weitere Eigenschaften können sehr einfach im Skript hinzugefügt werden.

Aufwändiger wird nur eine effiziente Verarbeitung der Rollen. Die Herausforderung ist eben die variable Anzahl der Rollen. Ein Lösungsansatz ist die Verwendung zusätzlicher Dokumente für die Rolleninformationen. Der Aufwand wird dabei aber generell deutlich höher. Man könnte die Informationen aber auch in einem mehrwertigen Feld speichern.

Die Ansichten

Die Ergebnisse der Verarbeitung können nun in Ansichten ausgegeben werden. Bild 3 zeigt eine einfache Ansicht.

Bild 3: Eine einfache Ansicht mit Informationen in der Datenbank mit den ACL-Einträgen.

Da in der Datenbank sehr viele Datensätze erzeugt werden, bietet es sich an, die Ansichten zu kategorisieren, beispielsweise nach Datenbanken, Servern, Ausführungsdatum oder Zugriffsrechten.

Die einzelnen Einträge in der Datenbank lassen sich in der zu Beginn vorgestellten Maske im Detail anzeigen (Bild 4). In der Abbildung ist noch einmal sichtbar, wie die Rollen ausgewertet und bestimmte Informationen wie das Zugriffslevel umgesetzt werden.

Bild 4: Die Maske mit den Details zu einem ACL-Eintrag

Das Skript zeigt, dass die Verarbeitung von ACLs in Domino-Datenbanken nicht allzu komplex ist. Es können damit beispielsweise eigene Dokumentationsanwendungen geschrieben werden, die Informationen beispielsweise für weitergehende Compliance-Analysen sammeln. Ebenso lassen sich die Funktionen aber auch nutzen, um innerhalb einer Datenbank zu prüfen, ob bestimmte vorgegebene Berechtigungen auch tatsächlich gesetzt sind.