As of Grouper 2.1, LDAPPCNG has been replaced by the Provisioning Service Provider (PSP).

The purpose of LDAPPC-NG is to provision group memberships from Grouper to an external target. In many cases this target is an LDAP directory, and a connector is provided for LDAP provisioning. The Shibboleth Attribute Resolver and Service Provisioning Markup Language (SPML) are used by LDAPPC-NG, making it highly configurable. LDAPPG-NG operations are initiated using gsh, and can be set to repeat at a set interval. A typical operation would comprise the comparison of groups and memberships in Grouper and the external target, followed by operations to create/delete/modify stems (containers), groups and memberships.

LDAPPC-NG is controlled using configuration files, which can be modified to suit you LDAP directory. The distribution includes sample configuration files to provision to an LDAP directory which includes the eduPerson schema extensions. Targets other than LDAP can be used, but you will need to develop a connector.

LDAPPC-NG built on LDAPPC and was first released with Grouper 1.6. LDAPPC is not being continued further, with all effort concentrated on LDAPPC-NG.

Download

Download the binary distribution of LDAPPC-NG from http://www.internet2.edu/grouper/software.html. Unpack the download into a directory, which I'll refer to as LDAPPGHG_HOME.

Installation

Before installing LDAPPC-NG, I will assume that you have completed the following:

  1. Configured a repository database for Grouper
  2. Configured a source for Grouper subjects
  3. Built the Grouper API (which is turn required Ant and Java)
  4. Successfully tested the Grouper API

Installation consists of

  1. Coping the contents of LDAPPCNG_HOME/lib/custom to GROUPER_HOME/lib/custom
  2. Copying the contents of LDAPPCNG_HOME/conf to GROUPER_HOME/conf

Testing the installation

TBC. This section will be added later, aim is to use an embedded ApacheDS server and the quickstart database to show provisioning to LDAP. I'm currently looking at providing a pre-configured quick-start including LDAPPC-NG.

Configuration

The following files will need to be configured to suit your environment:

See the links below for more information on these files and their contents.

Usage

See this page for detailed usage information.

Sample config files for Vanilla OpenLDAP

The shipped config files are configured for a LDAP target with uses the eduPerson schema. Not all LDAP directories include have this schema. The following configurations work on a vanilla installation of OpenLDAP, with no schema extensions installed. It assumes user objects already exist for subjects in ou=users,dc=example,dc=com, and stems and groups are to be created in ou=groups,dc=example,dc=com. Stems are created as OUs and groups are created within these OUs.

ldappc.properties:

#
# ldappc.properties
#

# Macros of the form ${name} in your configuration
# will be replaced with the values of the matching keys of this file.

 edu.vt.middleware.ldap.ldapUrl=ldap://127.0.0.1:1389
 edu.vt.middleware.ldap.base=dc=example,dc=com
 edu.vt.middleware.ldap.authtype=simple
 edu.vt.middleware.ldap.serviceUser=cn=admin
 edu.vt.middleware.ldap.serviceCredential=password
 edu.vt.middleware.ldap.ssl=false
 edu.vt.middleware.ldap.tls=false

# For Active Directory
# edu.vt.middleware.ldap.pagedResultsSize=100

# DNstructure=flat|bushy
DNstructure=bushy

# Group objectClass for OpenLDAP, RedHat/Fedora, ApacheDS, etc.
groupObjectClass=groupOfNames

# Group objectClass for Active Directory
# groupObjectClass=group

# Base DN for members
peopleOU=ou=users,dc=example,dc=com

# Base DN for groups
groupsOU=ou=groups,dc=example,dc=com

# For LDAPPC (not LDAPPCNG)
# The QuotedDnResultHandler removes quotes from DNs of the form "CN=quoted/name",DC=edu.
# The FqdnSearchResultHandler makes sure that all ldap dns are fully qualified.
# edu.vt.middleware.ldap.searchResultHandlers=edu.internet2.middleware.ldappc.util.QuotedDnResultHandler,edu.vt.middleware.ldap.handler.FqdnSearchResultHandler

# handle Active Directory groups with a large (>1500) number of members
# see https://bugs.internet2.edu/jira/browse/GRP-335
# see http://code.google.com/p/vt-middleware/wiki/vtldapAD#Range_Attributes
# edu.vt.middleware.ldap.searchResultHandlers=edu.internet2.middleware.ldappc.util.QuotedDnResultHandler,edu.vt.middleware.ldap.handler.FqdnSearchResultHandler,edu.internet2.middleware.ldappc.util.RangeSearchResultHandler

ldappc-internal.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans classpath:/schema/spring-beans-2.5.xsd
      http://www.springframework.org/schema/util classpath:/schema/spring-util-2.5.xsd">

  <bean id="shibboleth.VelocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
    <property name="velocityProperties">
      <props>
        <prop key="resource.loader">classpath, string</prop>
        <prop key="classpath.resource.loader.class">
          org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
        </prop>
        <prop key="string.resource.loader.class">
          edu.internet2.middleware.shibboleth.common.util.StringResourceLoader
        </prop>
      </props>
    </property>
  </bean>

  <bean id="shibboleth.TemplateEngine" class="edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.TemplateEngine">
    <constructor-arg ref="shibboleth.VelocityEngine" />
  </bean>

</beans>

ldappc-ldap.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xmlns:util="http://www.springframework.org/schema/util"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans classpath:/schema/spring-beans-2.5.xsd
    http://www.springframework.org/schema/util classpath:/schema/spring-util-2.5.xsd">

  <bean id="ldapFactory"
    class="edu.vt.middleware.ldap.pool.DefaultLdapFactory"
    p:connectOnCreate="false" >
    <constructor-arg index="0" ref="ldapConfig"/>
  </bean>

  <bean id="ldapPool"
    class="edu.vt.middleware.ldap.pool.SoftLimitLdapPool"
    init-method="initialize"
    p:blockWaitTime="1000">
    <constructor-arg index="0">
      <bean class="edu.vt.middleware.ldap.pool.LdapPoolConfig" />
    </constructor-arg>
    <constructor-arg index="1" ref="ldapFactory"/>
  </bean>

  <bean id="ldapConfig"
    class="edu.vt.middleware.ldap.LdapConfig"
    p:ldapUrl="${edu.vt.middleware.ldap.ldapUrl}"
    p:tls="${edu.vt.middleware.ldap.tls}"
    p:ssl="${edu.vt.middleware.ldap.ssl}"
    p:authtype="${edu.vt.middleware.ldap.authtype}"
    p:serviceUser="${edu.vt.middleware.ldap.serviceUser}">
    <property name="serviceCredential" value="${edu.vt.middleware.ldap.serviceCredential}" />

    <!--
    <property name="sslSocketFactory">
      <bean class="edu.vt.middleware.ldap.LdapTLSSocketFactory"
        init-method="initialize"
        p:keyStoreName="/edid.keystore"
        p:keyStorePathType="CLASSPATH"
        p:keyStoreType="BKS"
        p:trustStoreName="/vt-truststore.bks"
        p:trustStorePathType="CLASSPATH"
        p:trustStoreType="BKS"/>
    </property>
    -->

    <!--
    <property name="searchScope">
      <util:constant static-field="javax.naming.directory.SearchControls.SUBTREE_SCOPE"/>
    </property>
     -->

    <property name="searchResultHandlers">
      <list>
        <bean id="quotedDnSrh" class="edu.internet2.middleware.ldappc.util.QuotedDnResultHandler" />
        <bean id="fqdnSrh" class="edu.vt.middleware.ldap.handler.FqdnSearchResultHandler" />
        <!-- handle Active Directory groups with a large (>1500) number of members
             see http://code.google.com/p/vt-middleware/wiki/vtldapAD#Range_Attributes
        <bean id="rangeSRH" class="edu.internet2.middleware.ldappc.util.RangeSearchResultHandler" />
        -->
      </list>
    </property>

  </bean>

</beans>

ldappcng.xml:

<?xml version="1.0" encoding="utf-8"?>

<ldappc xmlns="http://grouper.internet2.edu/ldappc"
        xmlns:ldappc="http://grouper.internet2.edu/ldappc"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://grouper.internet2.edu/ldappc classpath:/schema/ldappc.xsd">

  <targets id="LDAP">

    <target id="ldap" provider="ldap-provider" />

    <object id="stem">
      <identifier ref="stem-dn" baseId="${groupsOU}">
        <identifyingAttribute name="objectclass" value="organizationalUnit" />
      </identifier>
      <attribute name="objectClass" ref="stem-objectclass" />
      <attribute name="ou" ref="stem-ou" />
      <attribute name="description" ref="stem-description" />
    </object>

    <object id="group" authoritative="true">
      <identifier ref="group-dn" baseId="${groupsOU}">
        <identifyingAttribute name="objectClass" value="${groupObjectClass}" />
      </identifier>
      <attribute name="objectClass" ref="group-objectclass-eduMember" />
      <attribute name="cn" />
      <attribute name="description" />
      <!--<attribute name="hasMember" ref="hasMember" />-->
      <!--<attribute name="isMemberOf" ref="groupIsMemberOf" />-->
      <references name="member" emptyValue="">
        <reference ref="members-jdbc" toObject="member" />
        <reference ref="members-g:gsa" toObject="group" />
      </references>
    </object>

    <object id="member">
      <identifier ref="member-dn" baseId="${peopleOU}">
        <identifyingAttribute name="objectclass" value="person" />
      </identifier>
      <attribute name="objectClass" ref="member-objectclass" retainAll="true" />
      <!--<attribute name="isMemberOf" ref="memberIsMemberOf" />-->
    </object>

  </targets>

</ldappc>

ldappc-resolver.xml:

<?xml version="1.0" encoding="UTF-8"?>
<AttributeResolver
  xmlns="urn:mace:shibboleth:2.0:resolver"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:resolver="urn:mace:shibboleth:2.0:resolver"
  xmlns:ad="urn:mace:shibboleth:2.0:resolver:ad"
  xmlns:dc="urn:mace:shibboleth:2.0:resolver:dc"
  xmlns:grouper="http://grouper.internet2.edu/shibboleth/2.0"
  xmlns:ldappc="http://grouper.internet2.edu/ldappc"
  xsi:schemaLocation="
   urn:mace:shibboleth:2.0:resolver classpath:/schema/shibboleth-2.0-attribute-resolver.xsd
   urn:mace:shibboleth:2.0:resolver:dc classpath:/schema/shibboleth-2.0-attribute-resolver-dc.xsd
   urn:mace:shibboleth:2.0:resolver:ad classpath:/schema/shibboleth-2.0-attribute-resolver-ad.xsd
   http://grouper.internet2.edu/shibboleth/2.0 classpath:/schema/shibboleth-2.0-grouper.xsd
   http://grouper.internet2.edu/ldappc classpath:/schema/ldappc.xsd">

  <resolver:DataConnector id="GroupDataConnector" xsi:type="grouper:GroupDataConnector">
    <grouper:Attribute id="members" />
    <grouper:Attribute id="groups" />
  </resolver:DataConnector>

  <resolver:DataConnector id="StemDataConnector" xsi:type="grouper:StemDataConnector">
  </resolver:DataConnector>

  <resolver:DataConnector id="MemberDataConnector" xsi:type="grouper:MemberDataConnector">
    <grouper:Attribute id="groups" />
  </resolver:DataConnector>

  <resolver:DataConnector id="StaticDataConnector" xsi:type="dc:Static">
    <dc:Attribute id="group-objectclass">
      <dc:Value>top</dc:Value>
      <dc:Value>${groupObjectClass}</dc:Value>
    </dc:Attribute>
    <dc:Attribute id="group-objectclass-eduMember">
      <dc:Value>top</dc:Value>
      <dc:Value>${groupObjectClass}</dc:Value>
      <!--<dc:Value>eduMember</dc:Value>-->
    </dc:Attribute>
    <dc:Attribute id="stem-objectclass">
      <dc:Value>top</dc:Value>
      <dc:Value>organizationalUnit</dc:Value>
    </dc:Attribute>
    <dc:Attribute id="member-objectclass">
      <dc:Value>inetOrgPerson</dc:Value>
    </dc:Attribute>
  </resolver:DataConnector>

  <resolver:AttributeDefinition id="stem-dn" xsi:type="ldappc:LdapDnPSOIdentifier"
    structure="${DNstructure}" sourceAttributeID="name" rdnAttributeName="ou" base="${groupsOU}">
    <resolver:Dependency ref="StemDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="stem-objectclass" xsi:type="ad:Simple">
    <resolver:Dependency ref="StaticDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="stem-ou" xsi:type="ad:Simple" sourceAttributeID="extension">
    <resolver:Dependency ref="StemDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="stem-description" xsi:type="ad:Simple" sourceAttributeID="description">
    <resolver:Dependency ref="StemDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="group-dn" xsi:type="ldappc:LdapDnPSOIdentifier"
    structure="${DNstructure}" sourceAttributeID="extension" rdnAttributeName="cn" base="${groupsOU}">
    <resolver:Dependency ref="GroupDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="group-objectclass" xsi:type="ad:Simple">
    <resolver:Dependency ref="StaticDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="group-objectclass-eduMember" xsi:type="ad:Simple">
    <resolver:Dependency ref="StaticDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="description" xsi:type="ad:Simple">
    <resolver:Dependency ref="GroupDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="cn" xsi:type="ad:Simple" sourceAttributeID="extension">
    <resolver:Dependency ref="GroupDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition xsi:type="Script" xmlns="urn:mace:shibboleth:2.0:resolver:ad" id="sAMAccountName" sourceAttributeID="name">
    <resolver:Dependency ref="GroupDataConnector" />
    <Script><![CDATA[
      // Import Shibboleth attribute provider
      importPackage(Packages.edu.internet2.middleware.shibboleth.common.attribute.provider);

      value = name.getValues().get(0);

      value = value.replaceAll("\\/", "_");
      value = value.replaceAll("\\/", "_");
      value = value.replaceAll("\\[", "_");
      value = value.replaceAll("\\]", "_");
      value = value.replaceAll("\\:", "_");
      value = value.replaceAll("\\;", "_");
      value = value.replaceAll("\\|", "_");
      value = value.replaceAll("\\=", "_");
      value = value.replaceAll("\\,", "_");
      value = value.replaceAll("\\+", "_");
      value = value.replaceAll("\\*", "_");
      value = value.replaceAll("\\?", "_");

      sAMAccountName = new BasicAttribute("sAMAccountName");
      sAMAccountName.getValues().add(value);
      ]]></Script>
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="hasMember" xsi:type="grouper:Member" sourceAttributeID="members">
    <resolver:Dependency ref="GroupDataConnector" />
    <grouper:Attribute id="name" source="drre" />
    <grouper:Attribute id="name" source="g:gsa" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="groupIsMemberOf" xsi:type="grouper:Group" sourceAttributeID="groups">
    <resolver:Dependency ref="GroupDataConnector" />
    <grouper:Attribute id="name" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="members-jdbc" xsi:type="grouper:Member" sourceAttributeID="members">
    <resolver:Dependency ref="GroupDataConnector" />
    <grouper:Attribute id="id" source="drre" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="members-g:gsa" xsi:type="grouper:Member" sourceAttributeID="members">
    <resolver:Dependency ref="GroupDataConnector" />
    <grouper:Attribute id="name" source="g:gsa" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="member-dn" xsi:type="ad:Simple" sourceAttributeID="psoID">
    <resolver:Dependency ref="SpmlDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:DataConnector id="SpmlDataConnector" provider="ldap-provider" xsi:type="ldappc:SPMLDataConnector"
    scope="subTree" base="${peopleOU}" returnData="identifier">
    <resolver:Dependency ref="MemberDataConnector" />
    <ldappc:FilterTemplate>(cn=${id.get(0)})</ldappc:FilterTemplate>
  </resolver:DataConnector>

  <resolver:AttributeDefinition id="member-objectclass" xsi:type="ad:Simple">
    <resolver:Dependency ref="StaticDataConnector" />
  </resolver:AttributeDefinition>

  <resolver:AttributeDefinition id="memberIsMemberOf" xsi:type="grouper:Group" sourceAttributeID="groups">
    <resolver:Dependency ref="MemberDataConnector" />
    <grouper:Attribute id="extension" />
  </resolver:AttributeDefinition>

</AttributeResolver>

ldappc-services.xml:

<?xml version="1.0" encoding="UTF-8"?>

<Services xmlns="urn:mace:shibboleth:2.0:services"
          xmlns:attribute-afp="urn:mace:shibboleth:2.0:afp"
          xmlns:attribute-authority="urn:mace:shibboleth:2.0:attribute:authority"
          xmlns:attribute-resolver="urn:mace:shibboleth:2.0:resolver"
          xmlns:resource="urn:mace:shibboleth:2.0:resource"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:grouper="http://grouper.internet2.edu/shibboleth/2.0"
          xmlns:ldappc="http://grouper.internet2.edu/ldappc"
          xsi:schemaLocation="urn:mace:shibboleth:2.0:services classpath:/schema/shibboleth-2.0-services.xsd
                              urn:mace:shibboleth:2.0:afp classpath:/schema/shibboleth-2.0-afp.xsd
                              urn:mace:shibboleth:2.0:attribute:authority classpath:/schema/shibboleth-2.0-attribute-authority.xsd
                              urn:mace:shibboleth:2.0:resolver classpath:/schema/shibboleth-2.0-attribute-resolver.xsd
                              urn:mace:shibboleth:2.0:resource classpath:/schema/shibboleth-2.0-resource.xsd
                              http://grouper.internet2.edu/shibboleth/2.0 classpath:/schema/shibboleth-2.0-grouper.xsd
                              http://grouper.internet2.edu/ldappc classpath:/schema/ldappc.xsd">

  <!-- the attribute resolver id must be grouper.AttributeResolver -->
  <Service id="grouper.AttributeResolver" xsi:type="attribute-resolver:ShibbolethAttributeResolver">
    <ConfigurationResource file="/ldappc-resolver.xml" xsi:type="resource:ClasspathResource">
      <ResourceFilter xsi:type="grouper:ClasspathPropertyReplacement" xmlns="urn:mace:shibboleth:2.0:resource" propertyFile="/ldappc.properties" />
    </ConfigurationResource>
  </Service>

  <!-- the attribute authority id must be grouper.AttributeAuthority -->
  <Service id="grouper.AttributeAuthority" xsi:type="grouper:SimpleAttributeAuthority" depends-on="grouper.AttributeResolver" resolver="grouper.AttributeResolver" />

  <!-- the provisioning service provider is required for ldappcng, not ldappc -->
  <Service id="ldappc" xsi:type="ldappc:ProvisioningServiceProvider" depends-on="grouper.AttributeAuthority" authority="grouper.AttributeAuthority">
    <ConfigurationResource file="/ldappcng.xml" xsi:type="resource:ClasspathResource">
      <ResourceFilter xsi:type="grouper:ClasspathPropertyReplacement" xmlns="urn:mace:shibboleth:2.0:resource" propertyFile="/ldappc.properties" />
    </ConfigurationResource>
  </Service>

  <!-- the ldap provider is required for ldappcng, not ldappc -->
  <Service id="ldap-provider" xsi:type="ldappc:LdapPoolProvider" ldapPoolId="ldapPool">
    <ConfigurationResource file="/ldappc-ldap.xml" xsi:type="resource:ClasspathResource">
      <ResourceFilter xsi:type="grouper:ClasspathPropertyReplacement" xmlns="urn:mace:shibboleth:2.0:resource" propertyFile="/ldappc.properties" />
    </ConfigurationResource>
  </Service>

</Services>

Sample config file for Vanilla Microsoft Active Directory

TBC, config file will be added to work with a standard installation of Microsoft Active Directory on 2008 Server.

Performance

Further Information

https://spaces.at.internet2.edu/display/Grouper/LDAPPCNGhttps://spaces.at.internet2.edu/display/Grouper/LDAPPCNG+Documentation