Programmatically confirming projection and join operation in FIM Synchronization Service during integration testing

  

The objective of this blog is to provide FIM Synchronization Service integration testers with tools to automate validation of projection and join operations.

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm

Integration testing Pattern

FIM Sync Service integration pattern follows a simple model

  1. Introduce changes to the connected systems
  2. Invoke run profiles
  3. Validate that projection, join, attribute flow rule, etc. rules were executed as expected

Ideally all 3 parts should be fully automated, but in this blog we will look at automating step 3. For automation of run profiles from C#, see my other post here.

What utilities do we need to make assertions about the correctness of rule execution?

For simplicity sake, let’s assume that we are working with two connected systems: HR (SQL table) and Active Directory, and that we wrote two helper classes: HRHelper and ActiveDirectoryHelper, which automate testing of those systems (i.e. allow us to add, delete, modify objects for testing). Let’s also assume that HR projects, and AD joins on employeeID. In order to ensure that our FIM rules are configured properly (in an automated fashion), we will need the following facilities:

  1. MetaverseHelper Class, which allows us to locate MVEntries based on anchor attributes
  2. ConnectorSpace.Helper Class, which allows us to check if a csEntry identified by a DN is linked to an MVEntry, previously located by the MetaverseHelper Class

In our example, if HR Record was successfully projected, then we should be able to locate in the Metaverse, one and only one, MVEntry which contains the employeeID in question (assuming, there exist and import attribute flow which sends emploeeID to the Metaverse). Also, if Active Directory MA produced a join, then we should be able to locate a link between the CSEntry and the MVEntry.

Code sample below illustrates this pattern.

// Introduce changes to the connected systems

 var dn = ActiveDirectoryHelper.CreateNewAccount(ADAccountInfo adAccountInfo);
var employeeID = HRHelper.CreateNewHRRecord(HRAccountInfo hrAccountInfo);
  
 // Run FIM Sync Cycle 
 RunProfileInvoker.RunFullCycle();
 // Validate Projection and Joins
 mms_metaverse mvEntryFoundByDN;
mms_metaverse mvEntryFoundByEmployeeID;
 // Assumes that there is an import attribute flow of employeeID to the Metaverse
 Assert.IsTrue(MetaverseHelper.TryToGetMVEntryByEmployeeID(employeeID, out mvEntryFoundByEmployeeID));
 // Assumes that there is an import attribute flow of distinghuishedName to the Metaverse
 Assert.IsTrue(MetaverseHelper.TryToGetMVEntryByActiveDirectoryDN(dn, out mvEntryFoundByDN));
 // Make sure that the two queries above returned the same MVEntry, thus confirming the join.

Assert.IsTrue(mventryFoundByGUID.object_id == mvEntryFoundByEmployeeID.object_id);
 // As an extra check, confirm that the csEntry corresponding the AD DN is in fact linked to the MVEntry 
 Assert.IsTrue(ConnectorSpaceHelper.IsCsEntryConnectedToMVEntry(
      mvEntryFoundByDN.object_id,
         dn,
     ActiveDirectoryMAName);
  

Important Note on accessing FIMSynchronization database

Sample code provided in this blog makes direct calls to the FIMSynchronizationService database. Such direct access is not supported in the production environment, hence the samples provided here should only be used in a test environment.

  

Metaverse Helper

Physically FIM Metaverse manifests itself as mms_metaverse table in the FIMSynchronizationService database. Each Metaverse attribute is represented as a column in this table (ex. employeeID). Using Entity Framework, it is fairly simple to define helper methods to query this table.

image

Sample below assumes that both employeeID and activeDirectoryDN (distinghuishedName) are flown into the Metaverse.

 namespace FIMTestCasesDriver.EnvironmentHelpers.FIM
{
    using System;
    using System.Linq;

    public static class MetaverseHelper
    {
        private static readonly FIMEntities FIMEntities = new FIMEntities();
        
        public static bool TryToGetMVEntryByEmployeeID(string employeeID, out mms_metaverse mvEntryOut)
        {
            try
            {
                mvEntryOut = FIMEntities.mms_metaverse.AsNoTracking().Single(
   mvEntry => mvEntry.employeeID == employeeID);
                return true;
            }
            catch (Exception)
            {
                mvEntryOut = null;
                return false;
            }
        }

        public static bool TryToGetMVEntryByActiveDirectoryDN(string dn, out mms_metaverse mvEntryOut)
        {
                      
            try
            {
                mvEntryOut = FIMEntities.mms_metaverse.AsNoTracking().Single(
                    mvEntry => mvEntry.activeDirectoryDN == dn);
                return true;
            }
            catch (Exception)
            {
                mvEntryOut = null;
                return false;
            }
        }
    }
}
  

See my other post for the explanation on why the AsNoTracking option was used.

Asserting Referential Relationship in Metaverse

Often FIM is required to maintain or calculate hierarchical relationships. In order to write integration tests, which validate our reference building logic we will need another helper method.

Internally FIM maintains referential relationships in the mms_mv_link table, which has a very simple structure. For example, the first row of the sample data depicted in the screenshot could be interpreted as – MVEntry identified as CFD95…. is a direct report of an MVEntry of CBD95… The object_id and reference_id could in turn be linked to the mms_metaverse table to get additional information.

image

Testing Pattern

In the arrange part of the test, we would set a reference attribute value in a connected system. The sample below creates a new AD account by using a helper class ActiveDirectoryStateMachine, and links this account to an existing account by populating value of manager attribute. This is followed by a series of Asserts, which ensure that we are dealing with a brand new account for the direct report, and that the MVEntry for the manager exists.

Execute FIM Run profiles - RunProfileInvoker.RunFullCycle();

The assertion part of the test uses the MetaverseHelper function DoesReferenceRelationshipExist, which takes as an input MVEntries of the origin of the reference (direct report) and the destination (manager), plus the name of the attribute on which the referential relationship is built. To get the MVEntries of the objects in question, we leverage the TryToGetMVEntryByActiveDirectoryDN helper method.

[TestClass]

public class FIMShouldReflectManagerToDirectReportsRelationshipFeature : SyncTestHarness

{

private mms_metaverse mvEntryManager;

        [TestInitialize]

public void TestSetup()

{

ActiveDirectoryStateMachine.TransitionFromNotInADToEnabled();

var managerDN = (string)ActiveDirectoryStateMachine.GetAttributeValue("manager");

Assert.IsFalse(string.IsNullOrEmpty(managerDN));

mms_metaverse mvEntry;

Assert.IsFalse(MetaverseHelper.TryToGetMVEntryByActiveDirectoryDN(

ActiveDirectoryStateMachine.DistinguishedName,

out mvEntry));

Assert.IsTrue(MetaverseHelper.TryToGetMVEntryByActiveDirectoryDN(

managerDN,

out mvEntryManager));

}

        [TestMethod]

public void FIMConsumesManagerInfoFromAD()

{

RunProfileInvoker.RunFullCycle();

            mms_metaverse mvEntryDirectReport;

Assert.IsTrue(MetaverseHelper.TryToGetMVEntryByActiveDirectoryDN(

ActiveDirectoryStateMachine.DistinghuishedName,

out mvEntryDirectReport));

            Assert.IsTrue(MetaverseHelper.DoesReferenceRelationshipExist(

mvEntryDirectReport,

mvEntryManager,

GlobalSettingsConfig.MVAttributeNames.ManagerAttributeName));

}

}

// The function below assumes that the mms_mv_link was imported into your project

// See Appendix for details on connecting to the FIMSynchronizationService database via EntityFramework

public static bool DoesReferenceRelationshipExist(

mms_metaverse source,

mms_metaverse destination,

string attributeName)

{

return (from reference in FIMEntities.mms_mv_link

where reference.attribute_name.Equals(attributeName) &&

reference.object_id == source.object_id &&

reference.reference_id == destination.object_id

select reference).Any();

}

ConnectorSpaceHelper

This class makes use of three tables:

  • mms_connectorspace – where CSEntries are stored. There are several important points related to this table:
    1. The actually attribute values of a CsEntry are packaged in a binary blob stored in the hologram column. To decode this blob we need to use WMI call against MIIS_CSObject, see TryToGetCsEntryGuid function for details.
    2. The objectGUID which uniquely identifies a CSEntry, is not stored in a column, but is packaged inside the hologram blob, hence the need for WMI call. We need this GUID to validate a join.
    3. Only RDN of a CsEntry is stored in a column, which makes no difference in case of HR MA where the anchor is based on employeeID, but certainly something to keep in mind in case of AD MA.
    4. Since an MVEntry could be joined to multiple CsEntries in different connector spaces, MAGuid is also required for unique CsEntry object identification.

image

  • mms_csmv_link – where the links between CSEntries and MVEntries are recorded (both mvEntry and CsEntry objects are uniquely identified by a GUID). Each row in this table represents a join.

mv_object_id comes from the mms_metaverse, and cs_object_id is stored in the mms_connectorspace (inside the hologram)

image

  • mms_management_agent – we the information on the MAs is stored. We will only need to access this table to get the mapping between the MA name and MA GUID.

image

 

 namespace FIMTestCasesDriver.EnvironmentHelpers.FIM
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Management;

    public static class ConnectorSpaceHelper
    {
        private static readonly ManagementScope FIMWmiNameSpace =
            new ManagementScope("root\\MicrosoftIdentityIntegrationServer");

        private static readonly FIMEntities FIMEntities = new FIMEntities();

        public static bool IsCsEntryConnectedToMvEntry(
            Guid mvEntryGuid,
            string csEntryDN,
            string maName)
        {
            Guid csEntryGuid;
            Guid maGUID;

            if (!TryToGetMAGuid(maName, out maGUID))
            {
                throw new Exception(
                    string.Format("Unable to find MAGuid for MAName:{0} in TryToGetCsEntryGuid", maName));
            }

            if (!TryToGetCsEntryGuid(csEntryDN, maGUID, out csEntryGuid))
            {
                return false;
            }

            return (from link in FIMEntities.mms_csmv_link
                    where link.mv_object_id == mvEntryGuid && link.cs_object_id == csEntryGuid
                    select link).Count() == 1;
        }

        private static bool TryToGetMAGuid(string maName, out Guid maGUID)
        {
            maGUID = (from ma in FIMEntities.mms_management_agent 
                      where ma.ma_name == maName 
                      select ma.ma_id).Single();
            return true;
        }

        private static bool TryToGetCsEntryGuid(
            string csEntryDN,
            Guid maGUID,
            out Guid csEntryGUID)
        {
            var csQueryString = string.Format(
                CultureInfo.CurrentCulture, "DN='{0}' and MaGuid='{{{1}}}'", csEntryDN, maGUID);
                   
            var csQuery = new SelectQuery("MIIS_CSObject", csQueryString);
            var csSearcher = new ManagementObjectSearcher(FIMWmiNameSpace, csQuery);
            var csObjects = csSearcher.Get();

            if (csObjects.Count > 1)
            {
                throw new Exception(string.Format(
  "Multiple connectors for the same DN detected{0}", csEntryDN));           
     }

            if (csObjects.Count == 0)
            {
                csEntryGUID = Guid.Empty;
                return false;
            }

            var guid = string.Empty;
            foreach (ManagementObject csObject in csObjects)
            {
                guid = csObject["Guid"].ToString();
                break;
            }

            csEntryGUID = new Guid(guid);
            return true;
        }
    }
}
  

Appendix

Connecting to FIMSynchronizationService database via Entity Framework

  In order to get samples to work you will need to add FIM Sync Service Entity model into your test project
 1. Using nuget add EntityFramework 4.1 into your test project

image

2. In your project add new item of type “ADO.NET Entity Data Model”

image

3. Create a new SQL connection to the server which hosts FIM Sync database

image

4. Select the tables referenced in the samples

image

5. Once the model is imported navigate to the edmx file, right-click and select to add “Code Generation Item”

image

6. Select “EF 4.X DbContext Generator”

image

References

https://openldap-ma.sourceforge.net/, the sample for decoding the hologram blob comes from this project.