May 14, 2012 4:43:32 AM
Reference Attributes, Group Membership and Shifting Authoritative Sources

A recent requirement for a project was to have users and groups provisioned from Domain B to Domain A. Simple enough, but a catch was that, as applications were migrated to Domain A, their groups would be “owned” by Domain A, which would now be the authoritative source for all group attributes (in this case, the authoritative source is determined by the OU the group is in in Domain B. The name of this OU is stored in the rules extension configuration file).

A custom rules extension is used to determine which management agent is authoritative, and to be sure the user objects being added to the member attribute are from the appropriate domain.

Setting an advanced attribute flow from member to member gives this message:

“Defining a rules extension import attribute flow to a metaverse reference attribute is not allowed.”

But, there is a way around this.

Here’s the set up:

- Domain A: This is the domain of the parent organization.

- Domain B: This is the domain of the organization being merged into the parent organization.

- The security groups all begin life in Domain B. As the merger progresses, their authoritative source will change to Domain A on a group by group basis.

This is indicated when the group is manually moved to the OU called “Domain A Managed” in Domain B.

Here’s the solution. Code for each step will follow this summary:

  1. Create a new indexed string multi-valued attribute in the metaverse called memberString.
  2. Create a new attribute called SourceCSEntryDN to hold the DN value in Domain B for the security group. (This is used to look up the OU the group belongs to on Domain B when the Domain A MA is synching the group.)
  3. Write a function in the rules extension code to determine which OU the group is in.
  4. Create an advanced import attribute flow on both management agents to flow members to the new memberString attribute if that MA is authoritative. This value will have the DN of the users who are members of the group.
  5. Write an export attribute flow rule on both management agents to flow members from the memberString to the member attribute if that MA is authoritative. Part of this function is to find the connector for the appropriate domain on the mventry for the user CN that is in the memberString attribute.

For steps 4 and 5, the check in step 3 is performed to make sure the right attribute values are being treated as authoritative.

For shared code, a general Utils dll called “UtilsForFIM” was created. Code was also added the import and export attribute flow rules in the respective rules extensions.

For step 2, the attribute flow on the Domain B management agent is:

<dn>, cn ->SourceCSEntryDN

This code puts the entire DN of the user object in the SourceCSEntryDN attribute.

Here’s the code for step 2:

Case "cd.group:<dn>,cn->mv.group:SourceCSEntryDN"

Dim sDNPath As String

Dim dn As ReferenceValue = csentry.DN

sDNPath = (dn.Subcomponents(0, dn.Depth).ToString())

mventry("SourceCSEntryDN").Value = sDNPath

For step 3, the code is a bit different for the Domain A and Domain B management agents. The Domain A management agent relies on the SourceCSEntryDN whereas the Domain B management agent is able to access the dn from the csentry directly.

Both have an attribute flow as follows on the management agent:

<dn>,member -> memberString

This code checks if the dn of the group contains the OU that indicates the group should be managed by Domain A. There are different functions for each domain. Domain A needs to rely on the value that was put into the SourceCSEntryDN attribute, whereas Domain B can read the DN directly from the csentry object.

Here’s the code for step 3 for Domain A:

Public Shared Function IsGroupManagedByDomAByDN(ByVal sDNPath As String) As Boolean

Dim bIsGroupManagedByDomA As Boolean

Dim sDomAManagedGroupOU As String

bIsGroupManagedByDomA = False

sDomAManagedGroupOU = UtilsForFIM.CommonUtils.ReadAppSetting("FIMConfig/General", "DomAManagedGroupOU").ToString()

If (InStr(sDNPath.ToLower(), sDomAManagedGroupOU.ToLower())) < 1 Then

'Group is still managed by the acquisition domain.

bIsGroupManagedByDomA = False

Else

'The group is now managed by DomA.

bIsGroupManagedByDomA = True

End If

Return bIsGroupManagedByDomA

End Function

Here’s the code for step 3 for Domain B:

Public Shared Function IsGroupManagedByDomA(ByVal csentry As CSEntry) As Boolean

Dim bIsGroupManagedByDomA As Boolean

Dim dn As ReferenceValue = csentry.DN

Dim sDomAManagedGroupOU As String

bIsGroupManagedByDomA = False

sDomAManagedGroupOU = UtilsForFIM.CommonUtils.ReadAppSetting("FIMConfig/General", "DomAManagedGroupOU").ToString()

Dim sDNPath As String

sDNPath = (dn.Subcomponents(0, dn.Depth).ToString())

If (InStr(sDNPath.ToLower(), sDomAManagedGroupOU.ToLower())) < 1 Then

'Group is still managed by the acquisition domain.

bIsGroupManagedByDomA = False

Else

'The group is now managed by DomA.

bIsGroupManagedByDomA = True

End If

Return bIsGroupManagedByDomA

End Function

For both domains, the code is reading the members in the group and writing the dn value of the users into a multivalued attribute called memberString. Here’s the attribute flow for Step 4 and the code:

<dn>,member -> memberString

The code for this transformation for the Domain A management agent is:

Case "cd.group:<dn>,member->mv.group:memberString"

If (mventry("SourceCSEntryDN").IsPresent) Then

Dim bIsGroupManagedByDomA As Boolean = UtilsForFIM.CommonUtils.IsGroupManagedByDomAByDN(mventry("SourceCSEntryDN").Value)

If (bIsGroupManagedByDomA = True) Then

mventry("memberString").Values.Clear()

Dim i As Integer

For i = 0 To (csentry("member").Values.Count - 1)

Dim sMember As String = csentry("member").Values.Item(i).ToString()

mventry("memberString").Values.Add(sMember)

Next

End If

End If

The code for this transformation when the management agent is Domain B is:

Case "cd.group:member->mv.group:memberString"

Dim bIsGroupManagedByDomA As Boolean

bIsGroupManagedByDomA = UtilsForFIM.CommonUtils.IsGroupManagedByDomA(csentry)

If (bIsGroupManagedByDomA = False) Then

mventry("memberString").Values.Clear()

Dim i As Integer

For i = 0 To (csentry("member").Values.Count - 1)

Dim sMember As String = csentry("member").Values.Item(i).ToString()

mventry("memberString").Values.Add(sMember)

Next

End If

For step 5, both management agents have the following attribute flow rule:

member <- memberString

Here is the export attribute flow rule for Domain A:

Case "cd.group:member<-mv.group:memberString"

Dim bIsGroupManagedByDomA As Boolean

If (mventry("SourceCSEntryDN").IsPresent) Then

bIsGroupManagedByDomA = UtilsForFIM.CommonUtils.IsGroupManagedByDomAByDN(mventry("SourceCSEntryDN").Value)

If (bIsGroupManagedByDomA = False) Then

csentry("member").Values.Clear()

UtilsForFIM.CommonUtils.memberStringToMembers(mventry, csentry, csentry.MA.Name)

End If

Else

'Source csentry dn is missing

End If

And for Domain B:

Case "cd.group:member<-mv.group:memberString"

Dim bIsGroupManagedByDomA As Boolean

bIsGroupManagedByDomA = UtilsForFIM.CommonUtils.IsGroupManagedByDomA(csentry)

If (bIsGroupManagedByDomA = True) Then

csentry("member").Values.Clear()

UtilsForFIM.CommonUtils.memberStringToMembers(mventry, csentry, csentry.MA.Name)

End If

Here’s the memberStringToMembers function that translates the membership from the string value to the appropriate csentry for the domain the group is being exported to. Note that this is taking the connector on the mventry of the member for the MA that is processing the current run so that we have the user object in the appropriate domain.

Public Shared Sub memberStringToMembers(ByVal mventry As MVEntry, ByVal csentry As CSEntry, ByVal sMAName As String)

Dim i As Integer

For i = 0 To (mventry("memberString").Values.Count - 1)

Dim sMember As String = mventry("memberString").Values.Item(i).ToString()

'Need to find the mventry for this connector and then determine the DomA csentry.

Dim mvEntryMember() As MVEntry

Dim arrMVEntryCN As Array

Dim sMemberCN As String

arrMVEntryCN = sMember.Split(",")

sMemberCN = arrMVEntryCN(0).Replace("CN=", "").Replace("cn=", "")

mvEntryMember = Utils.FindMVEntries("cn", sMemberCN, 1)

If (mvEntryMember.Length > 0) Then

Dim ManagementAgent As ConnectedMA

Dim Connectors As Integer

ManagementAgent = mvEntryMember(0).ConnectedMAs(sMAName)

Connectors = ManagementAgent.Connectors.Count

If (Connectors > 0) Then

Dim csentryFound As CSEntry

csentryFound = ManagementAgent.Connectors.ByIndex(0)

csentry("member").Values.Add(mvEntryMember(0).ConnectedMAs.Item(sMAName).Connectors.ByIndex(0).DN.ToString().ToLower())

End If

End If

Next

End Sub