Removing Enforce Consistent ACLHave you ever had someone send you a database that had the ACL setting "Enforce a consistent Access Control List across all replicas" enabled? (See figure 1). This setting is not truly a security setting, although it does affect security somewhat. The true purpose of the setting is for replication - two replicas will fail to replicate if their ACL's are not identical and they cannot be resolved (meaning, the person/server doing the replication does not have manager access to the other replica).
When a user creates a new local replica and that setting is enabled, information about their group membership is stored in the database. So this means that the user will end up with the same access they had on the server - they don't have to be explicitly listed in the ACL. However, this is also where we can run into trouble - if that same local replica is sent as an attachment to someone else, that group membership doesn't apply to that recipient. So that recipient must be listed in the ACL explicitly or else the Default access will take over.
That's the situation I ran into a few weeks ago. Someone sent me a database and all the roles were disabled for Default, so I didn't have access to many views. It's hard to trouble-shoot an application when you can't see everything. I could have had the user make a brand new local copy from their local replica and not copy the ACL. But this was a novice Notes user, and so it became clear to me that it would be easier for me to just remove the setting on my version. Then I could get into the database, update the ACL, and enable all the roles.
This setting can be removed because the setting is not a security setting. In fact, here's a quote from the Notes Administrator Help:
Additionally, enforcing a consistent access control list does not provide additional security for local replicas. To keep data in local replicas secure, encrypt the database.
So, how can it be removed? Through an agent that accesses the Notes API. Note that there is a read/write property in the NotesACL class in LotusScript, but that will only work if you have the authority to update the ACL in the user interface. (LotusScript obeys the security of the Notes client). Since that setting could not be disabled through the user interface (Default had only Designer access in my example) then I had to use the API.
Create an agent that will be used to remove the setting. In the (Declarations) section of the agent is where you need to define the API calls and a constant:
Declare Function NSFDbOpen Lib "nnotes.dll" (Byval pathName As String, _
hDB As Long) As Integer
Declare Function NSFDbReadACL Lib "nnotes.dll" (Byval hDB As Long, _
hACL As Long) As Integer
Declare Function ACLGetFlags Lib "nnotes.dll" (Byval hACL As Long, _
dwFlags As Long) As Integer
Declare Function ACLSetFlags Lib "nnotes.dll" (Byval hACL As Long, _
dwFlags As Long) As Integer
Declare Function NSFDbStoreACL Lib "nnotes.dll" (Byval hDB As Long, _
Byval hACL As Long, objectID As Long, method As Long) As Integer
Declare Function NSFDbClose Lib "nnotes.dll" (Byval hDB As Long) As Integer
Const ACL_UNIFORM_ACCESS = 1
The actual updating happens in the Initialize section of the agent. Since this was for a local database, some assumptions could be made (namely, that no server information would be needed). This shortens the agent a bit. It also means that we don't need to call the API function OSPathNetConstruct to build the path to the database.
First, the variables that are needed are defined:
Dim ws As New NotesUIWorkspace
Dim session As New NotesSession
Dim msg As String
Dim choice As String
Dim db As NotesDatabase
Dim filePath As String
Dim result As Integer
Dim hDB As Long
Dim hACL As Long
Dim flags As Long
Next, we prompt the user for the local database. The prompt box defaults to the user's data directory, although the database could be found in another directory.
msg = "Enter the path to the local database:"
choice = session.GetEnvironmentString("Directory", True) & "\"
choice = ws.Prompt(3, "Enter Path", msg, choice, "")
If choice = "" Then Exit Sub
If Instr(choice, session.GetEnvironmentString("Directory", True)) <> 0 Then
choice = Strright(choice, session.GetEnvironmentString("Directory", True))
While Left(choice, 1) = "\"
choice = Mid(choice, 2)
Next, the database is opened in the code to make sure it exists. Then, the variable filePath is set to the path of the database (this will be needed by the API calls) and the handle to the database in script is terminated (this prevents any potential conflicts).
Set db = session.GetDatabase("", choice, False)
If Not db.IsOpen Then Call db.Open("", "")
If db Is Nothing Then Exit Sub
filePath = db.FilePath
Set db = Nothing
The setting is then removed from the database using the API. This code first checks the setting. If it is already disabled, then nothing is done (a message is printed to the status bar). So it is disabled only if it needs to be disabled.
result = NSFDbOpen(filePath, hDB)
result = NSFDbReadACL(hDB, hACL)
result = ACLGetFlags(hACL, flags)
If flags And ACL_UNIFORM_ACCESS Then
flags = flags Xor ACL_UNIFORM_ACCESS
result = ACLSetFlags(hACL, flags)
result = NSFDbStoreACL(hDB, hACL, 0, 0)
Print "The flag is not set. Nothing changed."
result = NSFDbClose(hDB)
The API calls go through 6 steps in the case where the ACL setting needs to be disabled:
- Open the database through the API. This establishes an API handle (hDB) to the database.
- Read the ACL in the database. This establishes an API handle (hACL) to the ACL design element.
- Read the current ACL settings for "enforce consistent ACL" (this also returns whether the administration server can update readers/authors fields and/or regular names fields).
- If the setting needs to be updated, then update the setting. Don't change any other ACL settings.
- Store the updated settings back to the ACL design element.
- Close the database whether the ACL was updated or not.
Note the use of AND and XOR in the LotusScript code. These are used to find out if the setting is currently enabled (AND) and to disable the setting (XOR). These are binary functions, so you have to think of the values in binary terms. The constant ACL_UNIFORM_ACCESS is 1, which is 0000 0000 0000 0001 in binary. Let's say that the value for flags is 16,385. This is 0100 0000 0000 0001 in binary. A logical AND compares each bit and returns a 1 if both bits are 1 and returns a 0 if either (or both) are zero. So 16385 AND 1 is 1 because the very last bit is 1 in both numbers. So this indicates that the "enforce consistent ACL" needs to be changed.
XOR modifies values. It looks at each bit and if one and only one of the bits is 1, the result is 1. If both are 0 or both are 1, then the result is 0. Since the ACL_UNIFORM_ACCESS constant has a 0 in every position except the last, then none of those others will be changed (if it's a 0 in flags, it will still be a 0, and if it's a 1 in flags, it will still be a 1). For the last position, the bit will be switched off. (We already know that it's a 1 because of the AND comparison earlier). So that last bit will be switched to a 0. All other bits will remain the same, so this code is only affecting the ACL_UNIFORM_ACCESS bit in the ACL flags.