<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
       default-init-method="initialize"
       default-destroy-method="destroy"
       default-lazy-init="true">

    <!--
    Default behavior is to rely on properties to populate the various beans.
    You can override these, particularly shibboleth.authn.LDAP.authenticator,
    to customize the settings or avoid use of properties.

    Be cautious of any direct dependency on ldaptive classes to simplify upgrades.
    -->

    <alias name="%{idp.authn.LDAP.authenticator:anonSearchAuthenticator}" alias="shibboleth.authn.LDAP.authenticator" />
    <bean id="shibboleth.authn.LDAP.returnAttributes" parent="shibboleth.CommaDelimStringArray">
        <constructor-arg type="java.lang.String" value="%{idp.authn.LDAP.returnAttributes:1.1}" />
    </bean>

    <alias name="ValidateUsernamePasswordAgainstLDAP" alias="ValidateUsernamePassword" />

    <!-- Connection Configuration -->
    <bean id="connectionConfig" class="org.ldaptive.ConnectionConfig" abstract="true" p:ldapUrl="%{idp.authn.LDAP.ldapURL}"
        p:useStartTLS="%{idp.authn.LDAP.useStartTLS:true}"
        p:useSSL="%{idp.authn.LDAP.useSSL:false}"
        p:connectTimeoutDuration="%{idp.authn.LDAP.connectTimeout:PT3S}"
        p:responseTimeoutDuration="%{idp.authn.LDAP.responseTimeout:PT3S}"
        p:sslConfig-ref="sslConfig" />

    <alias name="%{idp.authn.LDAP.sslConfig:certificateTrust}" alias="sslConfig" />

    <bean id="certificateTrust" class="org.ldaptive.ssl.SslConfig">
        <property name="credentialConfig">
            <bean parent="shibboleth.X509ResourceCredentialConfig" p:trustCertificates="%{idp.authn.LDAP.trustCertificates:undefined}" />
        </property>
    </bean>

    <!-- Authentication handler -->
    <bean id="authHandler" class="org.ldaptive.auth.PooledBindAuthenticationHandler" p:connectionFactory-ref="bindPooledConnectionFactory" />
    <bean id="bindPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory" p:connectionPool-ref="bindConnectionPool" />
    <bean id="bindConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
        p:connectionFactory-ref="bindConnectionFactory" p:name="bind-pool" />
    <bean id="bindConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="bindConnectionConfig" />
    <bean id="bindConnectionConfig" parent="connectionConfig" />

    <!-- Pool Configuration -->
    <bean id="connectionPool" class="org.ldaptive.pool.BlockingConnectionPool" abstract="true"
        p:blockWaitTimeDuration="%{idp.pool.LDAP.blockWaitTime:PT3S}"
        p:poolConfig-ref="poolConfig"
        p:pruneStrategy-ref="pruneStrategy"
        p:validator-ref="searchValidator"
        p:failFastInitialize="%{idp.pool.LDAP.failFastInitialize:false}" />
    <bean id="poolConfig" class="org.ldaptive.pool.PoolConfig"
        p:minPoolSize="%{idp.pool.LDAP.minSize:3}"
        p:maxPoolSize="%{idp.pool.LDAP.maxSize:10}"
        p:validateOnCheckOut="%{idp.pool.LDAP.validateOnCheckout:false}"
        p:validatePeriodically="%{idp.pool.LDAP.validatePeriodically:true}"
        p:validatePeriodDuration="%{idp.pool.LDAP.validatePeriod:PT5M}" />
    <bean id="pruneStrategy" class="org.ldaptive.pool.IdlePruneStrategy"
        p:prunePeriodDuration="%{idp.pool.LDAP.prunePeriod:PT5M}"
        p:idleTimeDuration="%{idp.pool.LDAP.idleTime:PT10M}" />
    <bean id="searchValidator" class="org.ldaptive.pool.SearchValidator" />

    <!-- Bind Search Configuration -->
    <bean name="bindSearchAuthenticator" class="org.ldaptive.auth.Authenticator" p:resolveEntryOnFailure="%{idp.authn.LDAP.resolveEntryOnFailure:false}">
        <constructor-arg index="0" ref="bindSearchDnResolver" />
        <constructor-arg index="1" ref="authHandler" />
    </bean>
    <bean id="bindSearchDnResolver" class="net.shibboleth.idp.authn.PooledTemplateSearchDnResolver"
        p:baseDn="#{'%{idp.authn.LDAP.baseDN:undefined}'.trim()}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
        p:connectionFactory-ref="bindSearchPooledConnectionFactory" >
        <constructor-arg index="0" ref="shibboleth.VelocityEngine" />
        <constructor-arg index="1" value="#{'%{idp.authn.LDAP.userFilter:undefined}'.trim()}" />
    </bean>
    <bean id="bindSearchPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory"
        p:connectionPool-ref="bindSearchConnectionPool" />
    <bean id="bindSearchConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
        p:connectionFactory-ref="bindSearchConnectionFactory" p:name="search-pool" />
    <bean id="bindSearchConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="bindSearchConnectionConfig" />
    <bean id="bindSearchConnectionConfig" parent="connectionConfig" p:connectionInitializer-ref="bindConnectionInitializer" />
    <bean id="bindConnectionInitializer" class="org.ldaptive.BindConnectionInitializer"
        p:bindDn="#{'%{idp.authn.LDAP.bindDN:undefined}'.trim()}">
        <property name="bindCredential">
            <bean class="org.ldaptive.Credential">
                <constructor-arg value="%{idp.authn.LDAP.bindDNCredential:undefined}" />
            </bean>
        </property>
    </bean>

    <!-- Define a custom "aggregateAuthenticator" bean to allow searching for user accounts across multiple ldap bases.
    See "Single Directory with multiple branches > Aggregate DN Resolver" at
    https://wiki.shibboleth.net/confluence/display/IDP4/LDAPAuthnConfiguration#55804291ae05b98371814cefae58d3bb594a5067
    -->
    <bean name="aggregateAuthenticator" class="org.ldaptive.auth.Authenticator">
        <constructor-arg index="0" ref="aggregateDnResolver" />
        <constructor-arg index="1" ref="aggregateAuthHandler" />
    </bean>
    <bean id="aggregateDnResolver" class="org.ldaptive.auth.AggregateDnResolver">
        <constructor-arg index="0" ref="dnResolvers" />
    </bean>
    <bean id="aggregateAuthHandler" class="org.ldaptive.auth.AggregateDnResolver$AuthenticationHandler"
        p:authenticationHandlers-ref="authHandlers" />
    <util:map id="dnResolvers">
        <!-- Note, the key values can be anything -->
        <entry key="base1" value-ref="dnResolver1" />
        <entry key="base2" value-ref="dnResolver2" />
        <entry key="base3" value-ref="dnResolver3" />
    </util:map>
    <!-- Define three DN resolvers that use bind search against the same directory -->
    <bean id="dnResolver1" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN1}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter}"
        p:connectionFactory-ref="bindSearchPooledConnectionFactory" />
    <bean id="dnResolver2" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN2}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter}"
        p:connectionFactory-ref="bindSearchPooledConnectionFactory" />
    <bean id="dnResolver3" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN3}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter}"
        p:connectionFactory-ref="bindSearchPooledConnectionFactory" />
    <!-- Use the same authentication handler for all DN resolvers -->
    <util:map id="authHandlers">
        <!-- Note, the key values can be anything -->
        <entry key="base1" value-ref="authHandler" />
        <entry key="base2" value-ref="authHandler" />
        <entry key="base3" value-ref="authHandler" />
    </util:map>
</beans>
