A common requirement is that user accounts should go through a disabled stage of some length before being deleted. This makes excellent sense, particularly in AD with its fastidiousness concerning SIDs.
In this post I outline a way to achieve this in AD using a datestamped attribute, export flow rules and provisioning code.
The General Approach
For this example I use the info attribute in AD to keep a record of the latest change, for example “Disabled on 12/10/2008”.
Say we are retaining disabled accounts for 90 days – once the provisioning code sees that the Disabled date is more than 90 days in the past, and as long as the account is still actually disabled, the account will be deprovisioned.
Disabling the Account
Now firstly I am assuming you have some kind of status attribute in the metaverse which has just been changed to “inactive”. You feed this into an export flow rule which updates the userAccountControl attribute.
Case "export_userAccountControl" Const ADS_UF_ACCOUNTDISABLE As Integer = &H2 Const ADS_UF_NORMAL_ACCOUNT As Integer = &H200 Dim currentValue As Long If csentry("userAccountControl").IsPresent Then currentValue = csentry("userAccountControl").IntegerValue Else currentValue = ADS_UF_NORMAL_ACCOUNT End If Select Case mventry("status").Value Case "active" csentry("userAccountControl").IntegerValue = (currentValue Or ADS_UF_NORMAL_ACCOUNT) _ And (Not ADS_UF_ACCOUNTDISABLE) Case "inactive" csentry("userAccountControl").IntegerValue = currentValue _ Or ADS_UF_ACCOUNTDISABLE End Select
Moving the Account
A lot of people like to keep the disabled accounts in a seperate OU. This is a straight-forward case of determining the OU in your provisioning code based on the status, and then renaming the DN if necessary.
The following snippet is not a complete provision sub, but shows the code you need to work in to get the accounts moved.
Const ACTIVE_OU = "OU=Users, dc=mydomain, dc=com" Const INAVTIVE_OU = "OU=Disabled, OU=Users, dc=mydomain, dc=com" Const MA_NAME = "AD" Dim MA As ConnectedMA = mventry.ConnectedMAs(MA_NAME) Dim ShouldExist as Boolean 'Not shown here - some sort of logic to work out this value 'Generate the expected DN Dim RDN As String = "CN=" & mventry("displayName").Value Dim Container As String Select Case mventry("status").Value.ToLower Case "active" Container = ACTIVE_OU Case "inactive" Container = INACTIVE_OU Case Else Throw New UnexpectedDataException("User Status " & mventry("status").Value & _ " is not supported for user provisioning in " & MA_NAME) Exit Sub End Select Dim DN As ReferenceValue = MA.EscapeDNComponent(RDN).Concat(Container)
If ShouldExist and MA.Connectors.Count = 1 Then 'Check if rename needed Dim CSEntry As CSEntry = MA.Connectors.ByIndex(0) If CSEntry.DN.ToString.ToLower <> DN.ToString.ToLower Then CSEntry.DN = DN End If End If
Recording the Disabled Date
Now, this is where it gets a little tricky.
I want to update the info attribute with the date the account was disabled. You could base this on status however the change in status alone does not prove the account was actually disabled. I prefer to base this change on the hard evidence of the userAccountControl attribute – the downside being that I have to flow userAccountControl back in before I can update info – that is, the info will be updated on the sync after the account was actually disabled.
So, create a metaverse attribute for userAccountControl and then create a direct Import flow rule to flow it into the metaverse.
Next, create an Advanced export flow rule for info, as follows.
Case "export_info" 'AD userAccountControl must have been flowed into metaverse 'Info is updated on the sync following userAccountControl being changed. If mventry("userAccountControl").IsPresent AndAlso _ (mventry("userAccountControl").IntegerValue And ADS_UF_ACCOUNTDISABLE) = ADS_UF_ACCOUNTDISABLE Then 'Account is disabled If Not csentry("info").Value.Contains("Disabled") Then csentry("info").Value = "Disabled on " + Today.ToString("d") End If Else 'Account is enabled If csentry("info").Value.Contains("Disabled") Then csentry("info").Value = "Enabled on " + Today.ToString("d") End If End If
And finally, the Deletion
So, we now should have accounts successfully disabled and tagged with the date it happened.
The final step is to add the Deprovisioning instructions into the provisioning code – and for this you are going to need that info attribute in the metaverse as well. So create another metaverse attribute, and the corresponding Direct import flow rule.
Now you can update your provisioning code to decide when the time is right to delete the account. Note in the provisioning snippet above I made use of a boolean called ShouldExist. When I have determined that ShouldExist is FALSE I can call my csentry.Deprovision method. The code will be something like this:
Select Case mventry("status").Value.ToLower Case "active" ShouldExist = True Case "inactive" ShouldExist = False If mventry("userAccountControl").IsPresent AndAlso _ mventry("info").IsPresent Then If (mventry("userAccountControl").IntegerValue And ADS_UF_ACCOUNTDISABLE) = ADS_UF_ACCOUNTDISABLE Then 'Account disabled ShouldExist = True If mventry("description").Value.Contains("Disabled") Then Dim strDate As String = mventry("description").Value.Replace("Disabled on ", "") Dim disabledDate As DateTime = Convert.ToDateTime(strDate) If Now.Subtract(disabledDate).Days > KEEP_DISABLED_DAYS Then ShouldExist = False End If End If Else 'Account enabled ShouldExist = True End If End If Case Else Throw New UnexpectedDataException("Unexpected User Status " & mventry("status").Value) Exit Sub End Select . . . If Not ShouldExist And MA.Connectors.Count = 1 Then Dim CSEntry As CSEntry = MA.Connectors.ByIndex(0) CSEntry.Deprovision() End If
Downsides to this approach
The main weakness I would confess to is that the system can be effected by changes made directly in AD. If someone changes the date in the info field the account may be deleted sooner or later – though I have made good use of this as a simple way to extend the life of a particular account. The main problem would occur if someone deleted all text from the info field, leaving MIIS with no information. If you need a system that is entirely separated from this AD dependance then I would recommend an extra SQL MA to keep track of the dates and statuses.
The other potential concern is the fact that the info attribute is updated on the second sync. This is fine if you’re happy to just have a date in the field – not so fine if you want an exact time. You can minimise the problem by running two Export/DIDS operations in a row, but even then the time won’t be exact. Unfortunately I’ve never found a great solution to whole description-updating issue – for more on that see this post.