Table of Contents

Authorization with nested groups in memberOf

Overview

The examples below demonstrate how to retrieve the full list of groups a user is part of, including nested groups. Group nesting refers to the process of making one Active Directory group a member of another, primarily to simplify the management of access to resources.

The issue here is that the memberOf attribute will not display nested groups when using an ldapsearch. Therefore, an additional search is required to identify the sub-groups to which the user belongs.

We will use the matching rule OID, LDAP_MATCHING_RULE_IN_CHAIN in Active Directory, explained here: https://learn.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax, to retrieve nested groups.

Add a new Authenticator

First, start by creating an authenticator. In this example, we will use a BankID authenticator. The ldapsearch will search for the user in Active Directory (AD) using their personal number, which is stored in AD as employeeNumber.

Modify the execution flow

Navigate to the new authenticator and select "execution flow", then proceed to the first "LDAPSearchValve".

The search filter in the LDAPSearchValve should look like this for BankID, Freja, and SITHS eID:

(employeeNumber={{request.userPersonalNumber}})

The search filter in the LDAPSearchValve should look like this for Username & Password, for example:

(samAccountName={{request.username}})

The attribute field should contain "sAMAccountName, distinguishedName". It may include additional attributes, but these are the ones required to perform the search for nested groups in later steps.

This example is from a BankID authenticator:

LDAPSearchValve Configuration

Note that if you retrieve the memberOf attribute in the LDAPSearchValve, it will only contain the direct groups the user is a member of, not nested groups. If memberOf is included in the attribute list, further steps may be needed to avoid duplicates.

Next, add a FlowFailValve under the LDAPSearchValve. Here, we want to fail the flow if there was no match for that search filter, in order to prevent additional ldapsearch queries and reduce the load on the AD servers. It should look like this:

FLowFailValve Configuration

And in the "Advanced" tab:

FlowFailValve Configuration, execute if expression

Add a PropertyRenameValve after the FlowFailValve. We need to save the distinguishedName for later use, so rename distinguishedName to distinguishedName_org.

Next, add another LDAPSearchValve to search for all groups, including nested ones. The search filter should look like this:

(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{item.distinguishedName_org}}))

The attribute field should only contain "distinguishedName"

LDAPSearchValve Configuration

This will result in several items in your flow. The first item is from the initial LDAPSearchValve, containing the sAMAccountName and distinguishedName (renamed as distinguishedName_org) of the user. Additionally, there will be one item for each group the user is a member of, including nested groups, with the distinguishedName of each group. This is why we renamed the user's distinguishedName in the PropertyRenameValve in a previous step—we don't want the user’s distinguishedName to be confused with the group distinguishedNames.

Now, add a PropertyAddValve and copy item.distinguishedName to memberOf_tmp for later use:

PropertyAddValve Configuration

Add a PropertyRemoveValve to remove distinguishedName.

Next, add a ItemMergeValve to merge all the items into one. This will combine multi-values of all items with the same attribute names, such as memberOf_tmp. The destination item can be anything; set it to, for example, newitem.

Then, add a PropertyRenameValve to rename distinguishedName_org back to distinguishedName.

Finally, add a PropertyKeepInMultiValueValve and remove all values in memberOf_tmp that don’t contain a CN. The regular expression should be: ^CN=.*$. Set the destination to memberOf.

PropertyKeepInMultiValueValve Configuration

Remove memberOf_tmp using a PropertyRemoveValve.

When you are done, the execution flow should look like this or similar:

execution flow Configuration