Shibboleth IdP - Scriptable LDAP Filter

Posted by Misagh Moayyed on January 10, 2021 · 2 mins read ·

I have been working on a use case that involves configuring the Shibboleth Identity Provider to handle a SAML2 authentication flow to an external SAML2-capable Okta identity provider. The external identity provider was releasing a SAML2 NameID in the eppn format (i.e. user@example.org) and the Shibboleth installation was set to resolve attributes using the filter defined in the LDAP DataConnector:

<DataConnector id="myLDAP" 
     xsi:type="LDAPDirectory"
     ...
     exportAttributes="mail displayName">
     <FilterTemplate>
          <![CDATA[
               (uid=$resolutionContext.principal)
          ]]>
     </FilterTemplate>
</DataConnector>

This meant that regardless of the system handling the authentication attempt and whether it was the identity provider itself or the external Okta instance, querying LDAP for attributes was always based on the resolved principal matching the uid attribute. For the Okta instance, this could cause the Shibboleth identity provider to load the incorrect account.

The question is, could the LDAP FilterTemplate choose a different attribute based on the structure of the resolved principal?

Scripting Filter Templates

The answer, though not quite clear from the documentation to the novice eye, is yes. The search filter is put together using an instance of ExecutableSearchBuilder<ExecutableSearchFilter> which can treat the provided template as an Apache Velocity template. This means that the Apache Velocity syntax and its scripting capabilities can be used to modify the search filter dynamically. So one possible filter template could as follows:

<FilterTemplate>
<![CDATA[
     #set ($principal = $resolutionContext.principal)
     #if( $principal.contains("@example.org") )
          (oktaNetId=$principal.substring(0, $principal.indexOf("@")) )
     #else
          (uid=$principal)
     #end
]]>
</FilterTemplate>

That’s it.

So…

I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please know that all other use cases, scenarios, features, and theories certainly are possible as well. Feel free to engage and contribute as best as you can.

Happy Coding,

Misagh Moayyed