Largely based on work by Vladimir Mencl, Tuakiri Federation, New Zealand (ResolverScriptAttributeDefinitionExamples).

Active Directory and other LDAP directories support the ability to make one group a member of another and nest them. The directories usually, however, only return a memberOf attribute with the groups that the user is directly a member of and don't "resolve" those memberships unless asked specifically using a LDAP filter flag.

This is achieved by asking the directory for all the groups a given user (by distinguished name) is a member of but this needs a second LDAP query for which we need a second DataConnector which takes, as its input, the DN of the resolved user object.

  1. Ensure your existing LDAP Data Connector provides the dn or distinguishedName attribute.
  2. Create a new LDAP Data Connector with an InputDataConnector element referencing the existing one.
  3. Set the search filter to (member:1.2.840.113556.1.4.1941:=${attributename.get(0)})
  4. The results of the search will be a set of Group objects so tell the data connector it can have any number of responses (maxResultsSize="0")
  5. The attributes returned will be collated into a set and made available to the resolver.

Data Connector

Existing data connector (example)

<DataConnector id="ldap" xsi:type="LDAPDirectory"
    ldapURL="%{idp.attribute.resolver.LDAP.ldapURL}"
    baseDN="%{idp.attribute.resolver.LDAP.baseDN}"
    principal="%{idp.attribute.resolver.LDAP.bindDN}"
    principalCredential="%{idp.attribute.resolver.LDAP.bindDNCredential}"
    trustFile="%{idp.attribute.resolver.LDAP.trustCertificates}"
    useStartTLS="%{idp.attribute.resolver.LDAP.useStartTLS}" >

    <FilterTemplate>
        <![CDATA[
            %{idp.attribute.resolver.LDAP.searchFilter}
        ]]>
    </FilterTemplate>

    <ReturnAttributes>%{idp.attribute.resolver.LDAP.returnAttributes}</ReturnAttributes>

</DataConnector>

New data connector

<!-- Resolve nested groups in AD using the DN of the resolved user -->
<DataConnector id="ldap-groups" xsi:type="LDAPDirectory"
    ldapURL="%{idp.attribute.resolver.LDAP.ldapURL}"
    baseDN="%{idp.attribute.resolver.LDAP.baseDN}"
    principal="%{idp.attribute.resolver.LDAP.bindDN}"
    principalCredential="%{idp.attribute.resolver.LDAP.bindDNCredential}"
    useStartTLS="%{idp.attribute.resolver.LDAP.useStartTLS}"
    maxResultSize="0">

    <InputDataConnector ref="ldap" attributeNames="distinguishedName" />

    <FilterTemplate>
        <![CDATA[
            (member:1.2.840.113556.1.4.1941:=$distinguishedName.get(0))
        ]]>
    </FilterTemplate>

    <ReturnAttributes>
        distinguishedName
        sAMAccountName
    </ReturnAttributes>
</DataConnector>



Attribute definition

You now have two (probably multi-valued) attributes available to use. For example you could map them into an affiliation or turn them into entitlements.

<AttributeDefinition xsi:type="Mapped" id="eduPersonAffiliation">
    <InputDataConnector ref="ldap-groups" attributeNames="distinguishedName" />
    <DefaultValue passThru="false"/>
    <ValueMap>
        <ReturnValue>student</ReturnValue>
        <SourceValue>CN=All Students,OU=Groups,DC=example,DC=ac,DC=uk</SourceValue>
    </ValueMap>
    <ValueMap>
        <ReturnValue>staff</ReturnValue>
        <SourceValue>CN=All Staff,OU=Groups,DC=example,DC=ac,DC=uk</SourceValue>
    </ValueMap>
    <ValueMap>
        <ReturnValue>member</ReturnValue>
        <SourceValue>CN=All Students,OU=Groups,DC=example,DC=ac,DC=uk</SourceValue>
        <SourceValue>CN=All Staff,OU=Groups,DC=example,DC=ac,DC=uk</SourceValue>
    </ValueMap>

    <AttributeEncoder xsi:type="SAML2String" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" encodeType="false"/>
 </AttributeDefinition>