I had a "small" request from a customer last week: He'd be interested
in the extended commercial version of our assono
Password-Safe. But his internal
audit team had the requirement that a user should have to re-enter his
Notes client password when opening a document or copying it directly to
the clipboard from a view.
There is a @Command for this: ToolsUserLogoff.
It just logs the user off, thus you have to combine it with some kind of
server access to get the password input dialog to appear, like described
here: Forcing
user re-entry of passwords for electronic signatures in script.
This approach has three drawbacks:
1. It doesn't work offline, i.e. on
a local replica, because it depends on the server contact for the password
input dialog to be opened.
2. I need it to protect the opening
of existing documents. When the user just cancels the password input dialog,
the document continues to be opened.
3. The formula code must be placed in
the QueryOpen event of the form. But I really need to put some LotusScript
there.
Thus I had to search further for a solution
working in LotusScript. And I found one by Eknori from 2004: @Command(ToolsUserLogoff)
in Lotus Script.
It had to be adopted for the current
versions of Notes, because the log out key had been changed from F5 to
Ctrl-F5.
I still wasn't convinced this would
be the best possible solution. Its strictly Windows only and the user keeps
logged out, when he cancels the dialog, I don't know, it would work locally
etc.
I looked further and found a great idea:
Use the Notes C api to access privat portions of the user's ID file, namely
the REGGetIDInfoString
function with REGIDGetPrivateKey
as infoType.
- This would bring up the password input dialog, but doesn't log off the user.
- It would work locally without any server contact.
- I even could determine, if the user cancelled the dialog box, and react, if necessary.
Thank you, Davy Vanherbergen, for
your OpenNTF Code Bin post (from 2003!): Call
notes password prompt from lotusscript
I took the idea and implemented it "my
way", using my C api helper functions and letting the user choose
another ID file, if the configured ID file is not his own.
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,
_
"Choose
your ID file:", "*.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()
is a helper function to get the name of the Notes data directory. Replace
it by your own function or a String constant.
And adopt the error handling code (IsDebugMode()
and HandleError())
to your standard.
It needs some (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,
"Error in
library '" & LIBRARY
& "'"
& Chr$(10)
& _
"An
error has occurred in the C api function '"
& _
functionName
& "':
" & Chr$(10)
&_
"Error
code: " & Trim$(Str$(errorCode))
& Chr$(10)
& _
"Error
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 = "Unknown
error"
End
If
End
Function
Finally place some code like this into
the QueryOpen
event handler of your form:
If
continue Then
continue
= ValidateCurrentUser()
If
continue Then
'
do some stuff if necessary
End
If
End
If
Since this code only uses Notes C api
calls, it can easily be extended to other platforms than Windows, but for
now it is restricted to this operating system.