ACLs programmieren - Teil 4

01.04.2006 von Martin Kuppinger
Mit dem im vorliegenden Beitrag vorgestellten Code wird detailliert gezeigt, wie Änderungen an ACLs durchgeführt werden. Dabei werden die abgefragten Daten in Änderungen von ACLs umgesetzt, indem auf dieser Basis neue ACL-Einträge erstellt werden. Die Vorgehensweise wird Schritt für Schritt erläutert.

Um die eingegebenen Informationen aus der Datenbank zu verarbeiten und umzusetzen, werden Methoden und Eigenschaften der Klasse Notes- ACLEntry genutzt.

Die Änderung von ACLs

Im vorangegangenen Teil der Serie wurde die Struktur der Anwendung vorgestellt. In dieser können Datenbanken, Benutzer und Zugriffsniveaus ausgewählt werden. Außerdem können spezielle Berechtigungen angegeben werden. Von den drei Schaltflächen in der Anwendung wird allerdings bisher erst eine genutzt, mit der man die vorgenommenen Informationen in der Datenbank speichern kann. Die beiden anderen Schaltflächen zur unmittelbaren und zur späteren Ausführung der Änderungen sind dagegen noch nicht mit Code belegt worden. Der Code für diese beiden Schaltflächen wird sich relativ stark ähneln, weil der Unterschied nur darin liegt, dass einmal die aktuellen Informationen und einmal die in der Datenbank gespeicherten Informationen – Letztere in einer Schleife – verarbeitet werden.

Modifikationen an der Maske

Um den Code möglichst einfach zu gestalten, gibt es noch einige Modifikationen an der Maske. Zum einen wurden die Optionsfelder so verändert, dass mit einem Feld und einer daraus generierten Liste gearbeitet wird. Das ist einfacher, als mehrere Optionsfelder zu verwenden, die jeweils nur zu Listen mit einem Eintrag führen.

Außerdem wird für die versteckten Felder im unteren Bereich nun mit Textfeldern gearbeitet, die in der Handhabung einfacher sind und für den Zweck der Anwendung ausreichen.

Der Code für eine Änderung

Sub Click(Source As Button)
Dim sess As New NotesSession
Dim ws As NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set ws = New NotesUIWorkspace
Set uidoc = ws.CurrentDocument
Call uidoc.Save
Dim doclist As NotesView
Dim doc As NotesDocument
Dim dbcurr As NotesDatabase
Set dbcurr = sess.CurrentDatabase
Set doclist = dbcurr.getview("Standardansicht")
Set doc = doclist.GetLastDocument
Dim db As New NotesDatabase("domino7test1",doc.datei(0))
Dim acl As NotesACL
Set acl = db.ACL
Dim entry As NotesACLEntry
Dim zugriffslevel As Integer
Select Case doc.Zugriffsniveau(0)
Case "Manager":
zugriffslevel = ACLLEVEL_MANAGER
Case "Entwickler":
zugriffslevel = ACLLEVEL_DESIGNER
Case "Editor":
zugriffslevel = ACLLEVEL_EDITOR
Case "Autor":
zugriffslevel = ACLLEVEL_AUTHOR
Case "Leser":
zugriffslevel = ACLLEVEL_READER
Case "Ersteller":
zugriffslevel = ACLLEVEL_DEPOSITOR
Case "Kein Zugriff":
zugriffslevel = ACLLEVEL_NOACCESS
End Select
Set entry = New NotesACLEntry (acl, doc.benutzer(0), zugriffslevel)
Forall optionswert In doc.agentcreate
If optionswert = "Private Agenten erstellen" Then
entry.CanCreatePersonalAgent = True
End If
If optionswert = "Private Ordner und Ansichten erstellen" Then
entry.CanCreatePersonalFolder = True
End If
If optionswert = "Gemeinsame Ordner und Ansichten erstellen" Then
entry.CanCreateSharedFolder = True
End If
If optionswert = "LotusScript und Java Agenten erstellen" Then
entry.CanCreateLSOrJavaAgent = True
End If
If optionswert = "Erstellen öffentlicher Dokumente" Then
entry.CanCreateDocuments = True
End If
If optionswert = "Löschen öffentlicher Dokumente" Then
entry.CanDeleteDocuments = True
End If
If optionswert = "Replizieren oder Kopieren öffentlicher Dokumente" Then
entry.CanReplicateOrCopyDocuments = True
End If
End Forall
Call acl.Save
doc.verarbeitet = "True"
Dim dateTime As String
dateTime = Date$ + Time$
doc.verarbeitungsdatum = dateTime
Call doc.save (True,False)
Call uidoc.close
End Sub

Der Code für die Änderungen ist relativ lang, weil eine Reihe von Feldern etwas speziellere Verarbeitungsschritte benötigen. Er findet sich in Listing 1. Zunächst wird mit

Dim sess As New NotesSession

eine neue Session erstellt. Anschließend muss der Zugriff auf die aktuellen Informationen in der Maske vorbereitet werden. Dazu werden die Anweisungen

Dim ws As NotesUIWorkspace
Dim uidoc As NotesUIDocument

verwendet. Das ist erforderlich, um auf diese Informationen zugreifen zu können. Der Arbeitsbereich, also die aktuelle Arbeitsumgebung, wird anschließend mit

Set ws = New NotesUIWorkspace

geöffnet. Dort befindet sich ein Dokument in der aktuellen Maske. Dieses Dokument kann nun mit

Set uidoc = ws.CurrentDocument
Call uidoc.Save

gesichert werden. Es wäre grundsätzlich auch denkbar, dieses Dokument zunächst zu verarbeiten und dann zu sichern. Der größte Vorteil dieses Weges ist, dass man auf diese Weise den größten Teil des Codes unverändert auch für die andere noch ausstehende Schaltfläche verwenden kann. Das wird direkt im Anschluss deutlich, wenn auf die aktuelle Datenbank zugegriffen wird. Dazu erfolgt zunächst wieder eine Dimensionierung von Variablen:

Dim doclist As NotesView
Dim doc As NotesDocument
Dim dbcurr As NotesDatabase

Im Anschluss wird auf die aktuelle Datenbank zugegriffen, die ja in jedem Fall geöffnet ist, und die Anweisung

Set dbcurr = sess.CurrentDatabase

ausgeführt. Innerhalb der Datenbank kann nun auf das letzte Dokument in der Standardansicht zugegriffen werden:

Set doclist = dbcurr.getview("Standardansicht")
Set doc = doclist.GetLastDocument

Der Zugriff ist in dieser Form möglich, weil die Standardansicht nach der Reihenfolge der Erstellung der Dokumente sortiert ist. Damit muss man nicht lange in der Datenbank nach dem gewünschten Dokument suchen. Dieses Konzept funktioniert nicht bei allen Anwendungen, ist für diese Lösung aber ausreichend.

Im nächsten Schritt muss auf die Datenbank zugegriffen werden, für die die ACL angepasst werden soll. Dazu wird die Anweisung

Dim db As New NotesDatabase("domino7test1",
doc.datei(0))

verwendet. Der Server ist immer gleich, weil sich ja auch der Datenbank-Katalog, der für das Listenfeld in der Maske verwendet wird, von diesem Server gelesen wird. Die Datei wird aus dem Dokument übernommen, wobei der Zugriff auf das erste Element in einem Array erfolgen muss, da die Werte beim Zugriff als Array behandelt werden. Der erste Wert in einem Array ist immer 0. Damit wird die Datenbank nun geöffnet.

Bild 1: Die modifizierte Maske.

Der nächste Schritt ist die Vorbereitung des Zugriffs auf die ACL und die Bearbeitung von Einträgen in dieser ACL:

Dim acl As NotesACL
Set acl = db.ACL
Dim entry As NotesACLEntry

Hier muss zunächst die ACL der angegebenen Datenbank geöffnet werden. Im folgenden Schritt wird ein Eintrag in der ACL vorbereitet. Der nun folgende Block des Codes setzt den Zugriffslevel in die korrekten Werte um, wobei mit den definierten Konstanten im System gearbeitet wird. Die Verarbeitung erfolgt über eine Select- Case-Anweisung:

Dim zugriffslevel As Integer
Select Case doc.Zugriffsniveau(0)
Case "Manager":
zugriffslevel = ACLLEVEL_MANAGER
Case "Entwickler":
zugriffslevel = ACLLEVEL_DESIGNER
Case "Editor":
zugriffslevel = ACLLEVEL_EDITOR
Case "Autor":
zugriffslevel = ACLLEVEL_AUTHOR
Case "Leser":
zugriffslevel = ACLLEVEL_READER
Case "Ersteller":
zugriffslevel = ACLLEVEL_DEPOSITOR
Case "Kein Zugriff":
zugriffslevel = ACLLEVEL_NOACCESS
End Select

Der Wert wird in der Variablen zugriffslevel abgelegt, die im folgenden Schritt wieder verwendet wird. Hier wird nun der ACL-Eintrag gesetzt:

Set entry = New NotesACLEntry (acl, doc.benutzer(0),
zugriffslevel)

Damit ist aber erst ein Teil der Informationen aus der Maske verarbeitet. Im folgenden Schritt müssen nun noch die Optionen verarbeitet werden. Diese finden sich als Listenwerte in dem zusammengefassten Optionsfeld agentcreate, das man natürlich bei der Änderung der Maske auch noch hätte umbenennen können. Hier wird mit eine Forall-Schleife gearbeitet, die für jeden Eintrag in der Liste durchlaufen wird. Eine andere Möglichkeit gibt es nicht, da man die Länge des Arrays nicht kennt und daher nicht vorhersagen kann, wie viele Werte in diesem enthalten sind. Einfache For-Next-Schleifen sind damit nicht nutzbar. Die Struktur der Schleife ist wie folgt:

Forall optionswert In doc.agentcreate
If optionswert = "Private Agenten erstellen" Then
entry.CanCreatePersonalAgent = True
End If
If optionswert = "Private Ordner und Ansichten _
erstellen" Then
entry.CanCreatePersonalFolder = True
End If
If optionswert = "Gemeinsame Ordner und _
Ansichten erstellen" Then
entry.CanCreateSharedFolder = True
End If
If optionswert = "LotusScript und Java Agenten _
erstellen" Then
entry.CanCreateLSOrJavaAgent = True
End If
If optionswert = "Erstellen öffentlicher _
Dokumente" Then
entry.CanCreateDocuments = True
End If
If optionswert = "Löschen öffentlicher _
Dokumente" Then
entry.CanDeleteDocuments = True
End If
If optionswert = "Replizieren oder Kopieren _
öffentlicher Dokumente" Then
entry.CanReplicateOrCopyDocuments = True
End If
End Forall

Auch hier hätte man mit einer Select-Case-Anweisung arbeiten können, was den Code etwas schlanker gemacht hätte. Man muss aber in jedem Schleifendurchlauf alle Optionen abprüfen, da man nicht vorhersagen kann, welche Werte an welcher Position in der Liste vorkommen. Wenn beispielsweise die erste Option nicht ausgewählt wurde, verschieben sich alle folgenden Werte um eine Position nach vorne. Mit

Call acl.Save

Bild 2: Die modifizierte Maske in Notes.

wird nun die Verarbeitung der ACL abgeschlossen. Die geänderten Eigenschaften werden damit geschrieben. Nun sind nur noch einige Aufräumarbeiten erforderlich. Dazu wird zunächst mit

doc.verarbeitet = "True"

der Wert für das Feld verarbeitet gesetzt, wobei wie oben ausgeführt mit einer Zeichenkette gearbeitet wird. Anschließend muss noch das Datum modifiziert werden:

Dim dateTime As String
dateTime = Date$ + Time$
doc.verarbeitungsdatum = dateTime

Nach den Veränderungen wird das Dokument gespeichert. Dazu setzen Sie die Anweisung

Call doc.save (True,False)

ein. Schließlich sollte die Eingabemaske noch geschlossen werden. Das geschieht mit

Call uidoc.close

Die Änderungen sind damit abgeschlossen. Bei der ACL der Datenbank lässt sich nun überprüfen, ob der Eintrag auch tatsächlich gesetzt wurde (Bild 3).

Bild 3: Die geänderten Einträge in einer ACL.

Beim Code fehlt derzeit allerdings noch die Fehlerüberprüfung. Darauf kommen wir weiter unten noch einmal zurück.

Der Code für mehrere Änderungen

Auf der Basis des vorgestellten Codes lässt sich nun auch einfach das Skript für die dritte Schaltfläche erstellen, das alle Änderungen aus dem aktuellen Dokument und aus der Datenbank verarbeitet. Der Code findet sich in Listing 2.

Sub Click(Source As Button)
Dim sess As New NotesSession
Dim ws As NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set ws = New NotesUIWorkspace
Set uidoc = ws.CurrentDocument
Call uidoc.Save
Dim doclist As NotesView
Dim dbcurr As NotesDatabase
Dim doc As NotesDocument
Set dbcurr = sess.CurrentDatabase
Set doclist = dbcurr.getview("Standardansicht")
Set doc =doclist.GetFirstDocument
While Not (doc Is Nothing)
If doc.verarbeitet <> "true" Then
Dim dateiname As Variant
dateiname = doc.GetItemValue("datei")
Dim db As New NotesDatabase("domino7test1",doc.datei(0))
Dim acl As NotesACL
Set acl = db.ACL
Dim entry As NotesACLEntry
Dim zugriffslevel As Integer
Select Case doc.Zugriffsniveau(0)
Case "Manager":
zugriffslevel = ACLLEVEL_MANAGER
Case "Entwickler":
zugriffslevel = ACLLEVEL_DESIGNER
Case "Editor":
zugriffslevel = ACLLEVEL_EDITOR
Case "Autor":
zugriffslevel = ACLLEVEL_AUTHOR
Case "Leser":
zugriffslevel = ACLLEVEL_READER
Case "Ersteller":
zugriffslevel = ACLLEVEL_DEPOSITOR
Case "Kein Zugriff":
zugriffslevel = ACLLEVEL_NOACCESS
End Select
Set entry = New NotesACLEntry (acl, doc.benutzer(0), zugriffslevel)
Dim Optionsfelder As Variant
Forall optionswert In doc.agentcreate
If optionswert = "Private Agenten erstellen" Then
entry.CanCreatePersonalAgent = True
End If
If optionswert = "Private Ordner und Ansichten erstellen" Then
entry.CanCreatePersonalFolder = True
End If
If optionswert = "Gemeinsame Ordner und Ansichten erstellen" Then
entry.CanCreateSharedFolder = True
End If
If optionswert = "LotusScript und Java Agenten erstellen" Then
entry.CanCreateLSOrJavaAgent = True
End If
If optionswert = "Erstellen öffentlicher Dokumente" Then
entry.CanCreateDocuments = True
End If
If optionswert = "Löschen öffentlicher Dokumente" Then
entry.CanDeleteDocuments = True
End If
If optionswert = "Replizieren oder Kopieren öffentlicher Dokumente" Then
entry.CanReplicateOrCopyDocuments = True
End If
End Forall
Call acl.Save
doc.verarbeitet = "True"
Dim dateTime As String
dateTime = Date$ + Time$
doc.verarbeitungsdatum = dateTime
Call doc.save (True,False)
End If
Set doc = doclist.GetNextDocument (doc)
Wend
Call uidoc.close
End Sub

Der Unterschied zwischen den beiden Anwendungen ist relativ gering. Im Wesentlichen beschränken sich die Änderungen auf drei Zeilen nach der Dimensionierung und Initialisierung der Variablen sowie kurz vor dem Ende des Programms. Zu Beginn sind es diese drei Zeilen:

Set doc =doclist.GetFirstDocument
While Not (doc Is Nothing)
If doc.verarbeitet <> "true" Then

Damit wird zunächst auf das erste Dokument in der Ansicht zugegriffen. Anschließend wird eine Schleife gestartet, die abgebrochen wird, wenn doc keinen Wert hat. Das bedeutet in diesem Zusammenhang einfach, dass die Schleife durchlaufen wird, bis alle Dokumente in der View verarbeitet sind.

Für jedes der Dokumente wird zunächst geprüft, ob der Wert des Feldes verarbeitet auf true steht oder nicht. Felder, die noch nicht verarbeitetwurden, müssen anschließend verarbeitet werden. Die Verarbeitung unterscheidet sich nicht von der bei nur einem Dokument.

Am Ende muss die Bedingung geschlossen werden, bevor auf das nächste Dokument zugegriffen wird:

End If
Set doc = doclist.GetNextDocument (doc)
Wend

Beim Zugriff auf das folgende Dokument ist das aktuelle Dokument als Referenz anzugeben. Abschließend wird die Schleife beendet und es erfolgt die Prüfung, ob es noch ein solches Dokument gibt.

Wichtig ist auch bei diesem Listing, dass das aktuelle Dokument zunächst gesichert werden muss, damit es mit verarbeitet wird. Da aber anschließend auf die einzelnen Dokumente zugegriffen wird, wird auch deutlich, warum der etwas komplexere Ansatz mit dem Zugriff auf die Datenbank beim Listing 1 gewählt wurde – beim Listing 2 könnte man nicht mehr nur die Informationen aus der aktuellen Maske verarbeiten.

Optimierungen

Auf den ersten Blick wirkt der Code etwas ineffizient, was aber nur daran liegt, dass innerhalb der If-End-if-Anweisung so viel Code steht. Falls die Prüfung bei dieser Bedingung nicht erfolgreich ist, erfolgt aber keine weitere Verarbeitung, so dass der Aufwand doch sehr überschaubar bleibt.

Was für den produktiven Einsatz in jedem Fall fehlt, ist die Fehlerbehandlung. Das größte Problem: Wenn es bereits einen ACL-Eintrag für einen Benutzer, eine Gruppe oder einen Server gibt, wird eine Fehlermeldung ausgeworfen. Daher müsste vor der Definition des entsprechenden Eintrags eine Prüfung erfolgen, um anschließend entweder den bestehenden Eintrag zu modifizieren oder eben einen neuen Eintrag zu setzen.

Für eine optimale Laufzeit würde es sich anbieten, die Ansicht nach den Datenbanken, in denen Änderungen vorgenommen werden sollen, zu sortieren. Derzeit kann es vorkommen, dass eine Datenbank mehrfach geöffnet werden muss. Bei einer entsprechend sortierten Ansicht könnte man dagegen zunächst prüfen, ob sich die Datenbank geändert hat. Soweit sie sich nicht ändert, muss sie auch nicht wieder geöffnet werden. Damit würde man die Last deutlich reduzieren.

Außerdem wäre die Verwendung einer Select- Case-Anweisung anstelle der vielen If-End-if-Anweisungen auch empfehlenswert.

Wie geht es weiter?

Der abschließende Teil der Serie befasst sich näher mit den Methoden und Eigenschaften, mit denen ACLs ausgelesen werden können. Dort wird für diesen Zweck eine Anwendung vorgestellt, mit der sich ACLs in Datenbanken dokumentieren lassen. In diesem Rahmen werden verschiedene Methoden und Eigenschaften für den Umgang mit ACLs weiter vertieft.