Ich hatte letzte Woche eine "kleine" Anforderung von einem Kunden:
Er ist interessiert an der erweiterten kommerziellen Version unseres
assono Password-Safes. Aber seine
interne Revisionsabteilung hatte noch eine neue Anforderung: Der aktuelle
Benutzer sollte vor dem Öffnen eines Dokuments oder beim Kopieren eines
Passworts in die Zwischenablage direkt aus einer Ansicht vorher noch einmal
sein Notes-Passwort eingeben müssen.
Dafür gibt es ein @Command: ToolsUserLogoff.
Wenn es ausgeführt wird, logt es den Benutzer aus. Wenn man dieses Kommando
mit etwas Code kombiniert, mit dem man auf den Server zugreift, erscheint
der Passworteingabe-Dialog, wie es zum Bespiel hier beschrieben wurde:
Forcing
user re-entry of passwords for electronic signatures in script.
Dieser Ansatz hat aber für mich drei
Nachteile:
1. Er funktioniert nicht offline, also
z. B. bei einer lokalen Replik, weil der Serverzugriff notwendig ist, um
den Passworteingabe-Dialog zu öffnen.
2. Ich möchte es nutzen, um das Öffnen
von existierenden Dokumenten abzusichern. Wenn der Benutzer den Dialog
abbricht, wird das Dokument trotzdem geöffnet.
3. Der Formel-Code muss im QueryOpen-Ereignis
der Maske eingetragen werden. Dort brauche ich aber zwangsläufig LotusScript.
Also musste ich weiter suchen nach einer
Lösung in LotusScript. Und ich wurde fündig bei Eknori, der in 2004 diesen
Blog-Eintrag verfasst hat: @Command(ToolsUserLogoff)
in Lotus Script.
Diese Lösung muss für die aktuellen
Versionen von Notes angepasst werden, weil man sich jetzt nicht mehr mit
F5, sondern mit Strg-F5 auslogt.
Ich war immer noch nicht überzeugt,
dass dies die bestmögliche Lösung für mein Problem sein sollte. Es ist
wegen der Verwendung von Windows-DLLs nicht auf andere Plattformen übertragbar,
der Benutzer bleibt ausgeloggt, wenn er den Passworteingabe-Dialog abbricht,
es würde wohl auch nicht lokal funktionieren usw.
Ich suchte weiter und fand diese großartige
Idee: Mittels Notes C-API auf den privaten Teil der Benutzer-ID-Datei zugreifen,
genauer mit der REGGetIDInfoString-Function
mit REGIDGetPrivateKey
als infoType.
- Dies würde den Passworteingabe-Dialog erzwingen, aber gleichzeitig den Benutzer nicht abmelden.
- Es würde auch lokal ohne jeden Server-Kontakt funktionieren.
- Und ich könnte sogar feststellen, wenn der Benutzer den Dialog abgebrochen hätte und darauf falls nötig reagieren.
Vielen Dank an Davy Vanherbergen für
seinen OpenNTF Code Bin-Beitrag (von 2003!): Call
notes password prompt from lotusscript
Ich habe seine Idee genommen und auf
"meine Art" neu implementiert. Dabei habe ich unsere C-API-Hilfsfunktionen
genutzt, und ich lasse den Benutzer eine andere ID-Datei auswählen, wenn
die in der notes.ini eingestellte nicht die seine ist.
Function
ValidateCurrentUser As
Boolean
'/**
'
* validates current user by letting him enter his password
'
*
'
* @return True, if user has successfully entered his password
'
*
'
* @author Thomas Bahn/assono <tbahn@assono.de>
'
* @version 2014-09-30
'
*/
Const
MAXOUTBUFRLEN% = 4096
Dim
idFileName As
String
Dim
returnCode As
Integer
Dim
userNameBuffer As
String*MAXUSERNAME
Dim
actualLen As
Long
Dim
currentUserName As
String
Dim
outBufrLen As
String*MAXOUTBUFRLEN
If
Not
IsDebugMode() Then
On
Error
GoTo
errorHandler
ValidateCurrentUser
= False
idFileName
= session.GetEnvironmentString("KeyFileName",
True)
returnCode
= REGGetIDInfoString(idFileName, REGIDGetName, _
userNameBuffer, MAXUSERNAME, actualLen)
Call
ShowCAPIErrorIfAnyAndEnd(returnCode, "REGGetIDInfo",
_
NULLHANDLE)
currentUserName
= Left(userNameBuffer,
actualLen - 1)
Do
While
session.UserName <> currentUserName
'
ID file configured in notes.ini is not the ID file of the
'
current user
idFileName = uiws.OpenFileDialog(False,
_
"Wählen
Sie Ihre ID-Datei:", "*.ID|",
_
GetNotesDataDirectory(),
idFileName)(0)
returnCode = REGGetIDInfoString(idFileName,
REGIDGetName, _
userNameBuffer,
MAXUSERNAME, actualLen)
Call
ShowCAPIErrorIfAnyAndEnd(returnCode, "REGGetIDInfo",
_
NULLHANDLE)
currentUserName = Left(userNameBuffer,
actualLen - 1)
Loop
returnCode
= REGGetIDInfoString(idFileName, REGIDGetPrivateKey, _
outBufrLen,
MAXOUTBUFRLEN, actualLen)
If
returnCode = -32355
Then
Exit
Function
' user cancelled
dialog
Else
Call
ShowCAPIErrorIfAnyAndEnd(returnCode, "REGGetIDInfo",
_
NULLHANDLE)
End
If
'
when we get here, the user must have entered his password
'
successfully
ValidateCurrentUser
= True
Exit
Function
errorHandler:
If
HandleError() = RESUME_NEXT_LINE Then
Resume
Next
Exit
Function
End
Function
GetNotesDataDirectory()
ist eine Hilfsfunktion, die das Notes-Datenverzeichnis zurück gibt. Ersetze
diese Funktion durch deine eigene oder einfach eine String-Konstante.
Und passe den Fehlerbehandlungscode
(IsDebugMode()
and HandleError())
entsprechend deinen Standards an.
Ich benötige noch einige Deklarationen
(Declarations):
Private
Const
LIBRARY = "Eintrag
utils"
' WORD LNPUBLIC OSLoadString(HMODULE
hModule, STATUS StringCode, char far *retBuffer, WORD BufferLength);
Declare
Function
OSLoadString Lib
"nnotes"
Alias
"OSLoadString"
(ByVal
hModule As
Long,
ByVal
stringCode As
Integer,
ByVal
retBuffer As
LMBCS
String,
ByVal
bufferLength As
Integer)
As
Integer
' STATUS LNPUBLIC
REGGetIDInfo(char far *IDFileName, WORD InfoType, void far *OutBufr, WORD
OutBufrLen, WORD far *ActualLen);
Declare
Function
REGGetIDInfoString Lib
"nnotes"
Alias
"REGGetIDInfo"
(ByVal
idFileName As
String,
ByVal
infoType As
Integer,
ByVal
outBufr As
String,
ByVal
outBufrLen As
Integer,
actualLen As
Long)
As
Integer
Declare
Function
REGGetIDInfoBoolean Lib
"nnotes"
Alias
"REGGetIDInfo"
(ByVal
idFileName As
String,
ByVal
infoType As
Integer,
ByVal
outBufr As
Long,
ByVal
outBufrLen As
Integer,
actualLen As
Long)
As
Integer
Const
REGIDGetName = 7
' Data structure
returned Is char xx[MAXUSERNAME]
Const
REGIDGetPrivateKey = 9
' Data structure
returned Is char xx[xx]
' STATUS LNPUBLIC
NSFDbClose(DBHANDLE hDB);
Declare
Function
NSFDbClose Lib
"nnotes.dll"
(ByVal
hDB As
Long)
As
Integer
Const
NOERROR = 0
Const
NULLHANDLE = 0&
Const
MAXUSERNAME = 256
And two support functions for the C
API error handling:
Sub
ShowCAPIErrorIfAnyAndEnd(errorCode As
Integer,
functionName As
String,
hDB As
Long)
'/**
'
* shows user the C API error and aborts execution.
'
*
'
* @param errorCode return code of the function's execution
'
* @param functionName name of the C API function called
'
* @param hDB handle to the open database
'
*
'
* @author Thomas Bahn/assono <tbahn@assono.de>
'
* @version 2014-07-17
'
*/
If
errorCode = NOERROR Then
Exit
Sub
' exit if no error
occured
If
hDB <> 0
Then
'
if there is a valid handle, try to close database
Call
NSFDbClose(hDB)
End
If
Error
Err,
"Fehler in
Bibliothek '" & LIBRARY
& "'"
& Chr$(10)
& _
"Ein
Fehler ist aufgetreten in der C-API-Funktion'"
& _
functionName
& "':
" & Chr$(10)
&_
"Fehler-Code:
" & Trim$(Str$(errorCode))
& Chr$(10)
& _
"Fehler-Text:
" & Chr$(10)
& GetCAPIErrorMsg(errorCode)
End
Sub
Function
GetCAPIErrorMsg(errorCode As
Integer)
As
String
'/**
'
* gets error message for the C API error.
'
*
'
* @param errorCode return code of the function's execution
'
* @return error message for the C API error
'
*
'
* @author Thomas Bahn/assono <tbahn@assono.de>
'
* @version 2014-07-17
'
*/
Dim
length As
Integer
Dim
buffer As
String
'
initialize a buffer of adequate length to accept the error string
buffer
= String$(256,
0)
'
get the API error message from the internal Notes/Domino string
'
tables
length
= OSLoadString(NULLHANDLE, errorCode, buffer, Len(buffer))
If
length > 0
Then
'
remove any trailing characters from the string and
'
return it to the caller
GetCAPIErrorMsg = Left$(buffer,
InStr(1,buffer,Chr$(0))-1)
Else
'
couldn?t locate the error message in the string tables
GetCAPIErrorMsg = "Unbekannter
Fehler"
End
If
End
Function
Schließlich platziere Code ähnlich dem
folgenden in das QueryOpen-Ereignis
deiner Maske:
If
continue Then
continue
= ValidateCurrentUser()
If
continue Then
'
do some stuff if necessary
End
If
End
If
Da der Code ausschließlich Notes C-API-Aufrufe
benutzt, kann er leicht auf weitere Plattformen erweitert werden. Momentan
ist er auf Windows beschränkt.