Getting Started with LDAP and Jetspeed

Jetspeed supports several LDAP servers:

This getting started section only covers getting started with Apache DS

Apache DS 1.0.2

The first step to getting started with Apache DS is to download and install it. Once it is up and running, you will need to add the Jetspeed LDAP schema to the Apache DS server configuration. The general instructions for adding a custom schema are documented here for version ApacheDS 1.0.2. However, as of 2.2.0, the pre Jetspeed build only works with version 0.9.3. As of Jetspeed 2.2.0, we recommend using the guidelines described here for version 1.0.2 instead of the Jetspeed build, as we have deprecated all 0.9.3 support with version 2.2 of Jetspeed.

http://directory.apache.org/apacheds/1.0/custom-schema.html

Apache DS 1.0 does not support dynamic schema updates via the LDAP protocol. This feature will be added in the future however you can still change the schema used by Apache DS. It just requires a restart. To include addtional schemas in Apache DS, simply add the schema definitions to the Apache DS server.xml configuration file found under the /conf directory in the Apache DS distribution. Find the property configuration named "bootstrapSchemas". Since we are interested specifically in adding the Jetspeed schema, we want to add a bean definition appropriate to Jetspeed. This looks like:

<property name="bootstrapSchemas">
      <set>
        <bean class="org.apache.directory.server.core.schema.bootstrap.AutofsSchema"/>
        <bean class="org.apache.directory.server.core.schema.bootstrap.CorbaSchema"/>
	...
    <bean class="org.apache.jetspeed.security.ldap.JetspeedSchema"/>		
      </set>
</property>

For version Apache LDAP 0.9.3 (which I have never tried), use the same:

	
	<bean class="org.apache.jetspeed.security.ldap.JetspeedSchema"/>

We simply added the Jetspeed schema definition at the end of the list of bean definitions. The bean references a class named org.apache.jetspeed.security.ldap.JetspeedSchema. This class is included in a JAR file that Jetspeed provides for you, see below.

Next, we need to create a new domain for the jetspeed schema named sevenSeas. The following steps will create the sevenSeas domain in Apache DS.

To add a partition with the suffix "o=sevenSeas" and the id "sevenSeasPartitionConfiguration", editthe conf/server.xml file in Apache DS. Open it in your favorite editor and look for the following element with name contextPartitionConfigurations. Add a second ref element for the sevenSeas partition:

<property name="contextPartitionConfigurations">
  <set>
    <ref bean="examplePartitionConfiguration"/>
    <ref bean="sevenSeasPartitionConfiguration"/>
  </set>
</property>

Next, create the actual partition for Seven Seas by pasting this code in after the examplePartitionConfiguration (you can also remove the example partition and ref if you like):

<bean id="sevenSeasPartitionConfiguration" class="org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration">    

    <!-- the optimizer is enabled by default but may not always be what     -->
    <!-- you want if your queries are really simple                         -->
    <!--<property name="optimizerEnabled" value="true" />-->
	
	<property name="name" value="The seven seas" />
	<property name="cacheSize" value="100" />
	<property name="suffix" value="o=sevenSeas" />
	<property name="optimizerEnabled" value="true" />
	<property name="synchOnWrite" value="true" />


    <!--
      Synchronization on writes does not wait for synch operations
      to flush dirty pages.  Writes persist immediately to disk at 
      a cost to performance with increased data integrity.  Otherwise
      the periodic synch operation will flush dirty pages using the
      synchPeriodMillis parameter in the main configuration.
    -->
    
    <property name="indexedAttributes">
      <set>
        <bean class="org.apache.directory.server.core.partition.impl.btree.MutableIndexConfiguration">
          <property name="attributeId" value="dc" />
          <property name="cacheSize" value="100" />
        </bean>
        <bean class="org.apache.directory.server.core.partition.impl.btree.MutableIndexConfiguration">
          <property name="attributeId" value="ou" />
          <property name="cacheSize" value="100" />
        </bean>
        <bean class="org.apache.directory.server.core.partition.impl.btree.MutableIndexConfiguration">
          <property name="attributeId" value="krb5PrincipalName" />
          <property name="cacheSize" value="100" />
        </bean>
        <bean class="org.apache.directory.server.core.partition.impl.btree.MutableIndexConfiguration">
          <property name="attributeId" value="uid" />
          <property name="cacheSize" value="100" />
        </bean>
        <bean class="org.apache.directory.server.core.partition.impl.btree.MutableIndexConfiguration">
          <property name="attributeId" value="objectClass" />
          <property name="cacheSize" value="100" />
        </bean>
      </set>
    </property>
    <property name="contextEntry">
      <value>
        objectClass: top
        objectClass: domain
        objectClass: extensibleObject
		o: sevenSeas
      </value>
    </property>
  </bean>
</bean>

Note that the important areas that you may need to change if you need to customize your partition are the name of the partition:

<bean id="sevenSeasPartitionConfiguration"

The suffix:

<property name="suffix" value="o=sevenSeas" />

The last property remaining now is the context entry. The object classes top and extensibleObject are universal hence they remain. But the object class domain is replaced by the object class organization, because our partition should not represent a domain but an organization:

<property name="contextEntry">
+  <value>
+      objectClass: top
+      objectClass: organization
+      objectClass: extensibleObject
+      o: sevenSeas
+  </value>
+</property>

After saving the server.xml, you will need to download the jar file and drop it into the /lib directory in the Apache DS distribution. The JAR contains the Java-implementation of the Jetspeed schema for LDAP. For ApacheDS version 1.0.2, download the Jetspeed LDAP schema JAR file from here:

Apache DS 1.0.2 - Jetspeed Schema Files

For ApacheDS version 0.9.3, download the Jetspeed LDAP schema JAR file from here:

Apache DS 0.9.3 - Jetspeed Schema Files

After dropping in the jar file, restart the server. Apache DS should now be ready to support Jetspeed schemas. When the server starts up, make sure that there are no error messages printing out on the console related to this configuration

Jetspeed Configuration

So, how do you tie Jetspeed into ApacheDS, now that ApacheDS has the required schema? There are two steps.

First, you need to modify the Spring configuration file for LDAP security in Jetspeed.

Second, you need to set up a working administrator account in the LDAP directory, so that you'll be able to log into Jetspeed.

Before we begin, the LDAP code in Jetspeed was broken until recently, and therefore unusable without manual changes to the Java code (at least with Apache DS, according to our testing). Therefore, you should make sure that you're using Jetspeed 2.1.3 or higher.

For the first step, you will need to download three Spring configuration files. When Jetspeed is deployed to Tomcat, it should be placed under WEB-INF/assembly/override/ directory. Download from here:

http://people.apache.org/~taylor/LDAP/security-spi-ldap.xml

http://people.apache.org/~taylor/LDAP/security-spi-ldap-atn.xml

http://people.apache.org/~taylor/LDAP/security-spi-ldap-atz.xml

The security-spi-ldap.xml file will need to be modified. The other two do not need to be modified.

One last step is to remove two files from the WEB-INF/assembly directory:

mv security-spi-atn.xml alternate/
mv security-spi-atz.xml alternate/

Configuring security-spi-ldap.xml

The security-spi-ldap.xml configuration file for LDAP in Jetspeed is actually an XML file that configures the Jetspeed LDAP implementation. There are a total of 36 arguments (really!). While not all of these arguments may not actually be used by you, they must all be specified, otherwise Jetspeed will fail to initialize. Here is a base assembly that you will need to modify to point to your LDAP server:

			
<beans>
  <!-- ************** Ldap Configuration ************** -->
  <bean id="org.apache.jetspeed.security.spi.impl.ldap.LdapBindingConfig"
      class="org.apache.jetspeed.security.spi.impl.ldap.LdapBindingConfig">
      <!-- The LDAP initial context factory. -->
      <constructor-arg index="0"><value>com.sun.jndi.ldap.LdapCtxFactory</value></constructor-arg>
      <!-- The LDAP server name. -->
      <constructor-arg index="1"><value>localhost</value></constructor-arg>
      <!-- The LDAP server port. -->
      <constructor-arg index="2"><value>10389</value></constructor-arg>
      <!-- The LDAP server root context. -->
      <constructor-arg index="3"><value>o=sevenSeas</value></constructor-arg>
      <!-- The LDAP server root dn. -->
      <constructor-arg index="4"><value>uid=admin,ou=system</value></constructor-arg>
      <!-- The LDAP server root password. -->
      <constructor-arg index="5"><value>secret</value></constructor-arg>
      <!-- The roles filter. -->
      <constructor-arg index="6"><value>(objectclass=jetspeed-2-role)</value></constructor-arg>
      <!-- The groups filter. -->
      <constructor-arg index="7"><value>(objectclass=jetspeed-2-group)</value></constructor-arg>
      <!-- The user filter. -->
      <constructor-arg index="8"><value>(objectclass=jetspeed-2-user)</value></constructor-arg>
      <!-- The roleMembershipAttributes. -->
      <constructor-arg index="9"><value>j2-role</value></constructor-arg>
      <!-- The userRoleMembershipAttributes. -->
      <constructor-arg index="10"><value>j2-role</value></constructor-arg>
      <!-- The groupMembershipAttributes. -->
      <constructor-arg index="11"><value>uniqueMember</value></constructor-arg>
      <!-- The userGroupMembershipAttributes. -->
      <constructor-arg index="12"><value>j2-group</value></constructor-arg>
      <!-- The groupMembershipForRoleAttributes. -->
      <constructor-arg index="13"><value>uniqueMember</value></constructor-arg>
      <!-- The roleGroupMembershipForRoleAttributes. -->
      <constructor-arg index="14"><value></value></constructor-arg>     
      <!-- The defaultSearchBase. -->
      <constructor-arg index="15"><value>o=sevenSeas</value></constructor-arg>
      <!-- The roleFilterBase. -->
      <constructor-arg index="16"><value>ou=Roles,ou=rootOrg</value></constructor-arg>
      <!-- The groupFilterBase. -->
      <constructor-arg index="17"><value>ou=Groups,ou=rootOrg</value></constructor-arg>
      <!-- The userFilterBase. -->
      <constructor-arg index="18"><value>ou=People,ou=rootOrg</value></constructor-arg>
      <!-- The roleObjectClasses. -->
      <constructor-arg index="19"><value>top,groupOfUniqueNames,jetspeed-2-role</value></constructor-arg>
      <!-- The groupObjectClasses. -->
      <constructor-arg index="20"><value>top,groupOfUniqueNames,jetspeed-2-group</value></constructor-arg>
      <!-- The userObjectClasses. -->
      <constructor-arg index="21"><value>top,person,organizationalPerson,inetorgperson,jetspeed-2-user</value></constructor-arg>
      <!-- The roleIdAttribute. -->
      <constructor-arg index="22"><value>cn</value></constructor-arg>
      <!-- The groupIdAttribute. -->
      <constructor-arg index="23"><value>cn</value></constructor-arg>
	  	<!-- The userIdAttribute. -->
      <constructor-arg index="24"><value>cn</value></constructor-arg>
      <!-- The UidAttribute. -->
      <constructor-arg index="25"><value>uid</value></constructor-arg>
      <!-- The MemberShipSearchScope. -->
      <constructor-arg index="26"><value>1</value></constructor-arg>
      <!-- The roleUidAttribute. -->
      <constructor-arg index="27"><value>cn</value></constructor-arg>
      <!-- The groupUidAttribute. -->
      <constructor-arg index="28"><value>cn</value></constructor-arg>
	  <!-- The userUidAttribute. -->
      <constructor-arg index="29"><value>uid</value></constructor-arg>
	  <!-- The roleObjectRequiredAttributeClasses. -->
      <constructor-arg index="30"><value>cn,j2-classname,uid,uniquemember</value></constructor-arg>
	  <!-- The groupObjectRequiredAttributeClasses. -->
      <constructor-arg index="31"><value>cn,j2-classname,uid,uniqueMember</value></constructor-arg>
	  <!-- The userAttributes. -->
      <constructor-arg index="32"><value>sn={u},cn={u},uid={u}</value></constructor-arg>
	  <!-- The roleAttributes. -->
      <constructor-arg index="33"><value></value></constructor-arg>
	  <!-- The groupAttributes. -->
      <constructor-arg index="34"><value></value></constructor-arg>
	  <!-- The userPasswordAttribute. -->
      <constructor-arg index="35"><value>userPassword</value></constructor-arg>
	  <!-- The knownAttributes. -->
      <constructor-arg index="36"><value>cn,sn,o,uid,ou,objectClass,userPassword,member,uniqueMember,memberOf,j2-role,j2-group</value></constructor-arg>
  </bean>
</beans>

Lets cover the most often used modifications. Further in the documentation on this page, we go into more detail of each parameter. You will probably need to make changes in the following locations in order to make it work with your setup. I've listed them according to the constructor argument it uses in the XML file. Possible changes marked with a (!) will require a corresponding change to the LDIF file (explained later), so do not change them unless you understand what you're doing in both files.

1. The hostname of your LDAP server. In our case, it was "localhost". If your LDAP server is on the same computer that Jetspeed is running on, you'll probably want to set it to "localhost".
2. Our LDAP server runs on port 10389. The default for most LDAP servers is port 389.
3.(!) We set the organization name as "o=sevenSeas", as was done in the ApacheDS example. If you want to use a different organization name, you can change it to anything of the form "o=yourOrganizationName".
15.(!) If you changed your organization name in #3, you need to make the exact same change here.
16.(!) We stored all Jetspeed keys in a group called "ou=rootOrg". You can change the name of it to anything you want, as long as it's of the form "ou=yourOrganizationalUnit", and your changes are reflected in #17, #18, and the LDIF file. Within the "ou=rootOrg" directory, we stored all roles in a subdirectory called "ou=Roles". Chances are you have no need to change that name as weell.
17.(!) As mentioned in #16, if you change the name of "ou=rootOrg", you need to change this value accordingly.
18.(!) Same as #17.

The other arguments are unlikely to require changes unless the LDAP schema itself is changed. Now, we need to set up at least one Jetspeed account in the LDAP directory. And we cannot use the Jetspeed administrative portlets to do it, because we'd need to log in as an administrator to do so (and no accounts of ANY kind exist at this point). Fortunately, we created an LDIF file that can be imported into ApacheDS and matches the above Jetspeed configuration exactly.

LDIF Import

LDAP Data Interchange Format (LDIF) is a standard data interchange format for representing LDAP directory content as well as directory update (Add, Modify, Delete, Rename) requests. The following text is the contents of the LDIF file for getting you started with a Jetspeed LDAP base configuration. The entries in the LDIF sample include definitions for creating the basic Jetspeed admin user and required roles to get a mimimal portal up and running. For your convenience, you can download this LDIF file from here:

http://people.apache.org/~taylor/LDAP/jetspeed-apacheds.ldif

With Apache DS, we could not create the root domain with an LDIF import. Instead we had to create a partition as described above. Also take a look at the root.ldif file, as it contains the root definitions for the sevenSeas organization that you may need on different LDAP server.

We recommend using LDAP Studio to import the Jetspeed LDIF file into the Apache DS server via File->Import

			
dn: ou=rootOrg,o=sevenSeas
objectClass: organizationalUnit
objectClass: top
ou: rootOrg

dn: ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalUnit
objectClass: top
ou: People

dn: ou=Groups,ou=rootOrg,o=sevenSeas
objectClass: organizationalUnit
objectClass: top
ou: Groups

dn: ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: organizationalUnit
objectClass: top
ou: Roles

dn: cn=accounting,ou=Groups,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-group
objectClass: groupOfUniqueNames
objectClass: top
cn: accounting
j2-classname: accounting
uid: accounting
uniquemember: user,local,sublocal

dn: cn=engineering,ou=Groups,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-group
objectClass: groupOfUniqueNames
objectClass: top
cn: engineering
j2-classname: engineering
uid: engineering
uniquemember: user

dn: cn=marketing,ou=Groups,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-group
objectClass: groupOfUniqueNames
objectClass: top
cn: marketing
j2-classname: marketing
uid: marketing
uniquemember: user

dn: cn=admin,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: admin
j2-classname: admin
uid: admin
uniquemember: admin

dn: cn=manager,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: manager
j2-classname: manager
uid: manager
uniquemember: admin,jetspeed,manager

dn: cn=user,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: user
j2-classname: user
uid: user
uniquemember: user,admin,manager,local

dn: cn=guest,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: guest
j2-classname: guest
uid: guest
uniquemember: guest

dn: cn=subsite,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: subsite
j2-classname: subsite
uid: subsite
uniquemember: subsite

dn: cn=subsite2,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: subsite2
j2-classname: subsite2
uid: subsite2
uniquemember: subsite

dn: cn=dev,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: dev
j2-classname: dev
uid: dev
uniquemember: dev

dn: cn=devmgr,ou=Roles,ou=rootOrg,o=sevenSeas
objectClass: jetspeed-2-role
objectClass: groupOfUniqueNames
objectClass: top
cn: devmgr
j2-classname: devmgr
uid: devmgr
uniquemember: devmgr

dn: cn=admin,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: admin
givenname: Admin
j2-role: admin
j2-role: manager
j2-role: user
sn: admin
uid: admin
userpassword:: c2VjcmV0

dn: cn=manager,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: manager
givenname: Manager
j2-role: manager
j2-role: user
sn: manager
uid: manager
userpassword:: c2VjcmV0

dn: cn=user,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: user
givenname: User
j2-role: user
sn: user
uid: user
userpassword:: c2VjcmV0

dn: cn=local,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: local
givenname: Local
j2-role: user
sn: local
uid: local
userpassword:: c2VjcmV0

dn: cn=sublocal,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: sublocal
givenname: sublocal
j2-role: user
sn: sublocal
uid: sublocal
userpassword:: c2VjcmV0

dn: cn=tomcat,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: tomcat
givenname: tomcat
sn: tomcat
uid: tomcat
userpassword:: c2VjcmV0

dn: cn=jetspeed,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: jetspeed
givenname: jetspeed
j2-role: manager
sn: jetspeed
uid: jetspeed
userpassword:: c2VjcmV0

dn: cn=guest,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: guest
givenname: guest
sn: guest
uid: guest
userpassword:: c2VjcmV0

dn: cn=subsite,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: subsite
givenname: subsite
j2-role: subsite
j2-role: subsite2
j2-role: user
sn: subsite
uid: subsite
userpassword:: c2VjcmV0

dn: cn=subsite2,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: subsite2
givenname: subsite2
j2-role: subsite
j2-role: subsite2
j2-role: user
sn: subsite2
uid: subsite2
userpassword:: c2VjcmV0

dn: cn=devmgr,ou=People,ou=rootOrg,o=sevenSeas
objectClass: organizationalPerson
objectClass: person
objectClass: jetspeed-2-user
objectClass: inetOrgPerson
objectClass: top
cn: devmgr
givenname: devmgr
j2-role: devmgr
j2-role: dev
j2-role: user
sn: devmgr
uid: devmgr
userpassword:: c2VjcmV0

So what exactly does it produce, from a Jetspeed perspective?

* All the same roles, users, and groups that come with Jetspeed out of the box on a relational database, required for normal operation of Jetspeed.
* Three groups (accounting, engineering, marketing) are created. They are not strictly required for normal operation of Jetspeed, but they show how groups are declared.
* Eight roles (guest, admin, devmgr, jetspeed, local, manager, sublocal, subsite, subsite2, tomcat, user) are created, the same set of roles found in the demo distribution of Jetspeed
* The administrative user has the name admin. This user has both the "admin" and "manager" roles, so it has full access to all administrative portlets.
* All users are created with the password secret.

WARNING: If you modified any of the arguments from security-spi-ldap.xml that had a (!) next to their explanations, the above LDIF file will not work. It will import into your LDAP server just fine, but Jetspeed will be unable to use it. Here's a list of the changes you'll need to make to the LDIF file, according to which argument you modified (if you didn't change it in the XML file, you do not need to change it in the LDIF file):

3. If you changed your organization name (the default was "o=sevenSeas"), you need to change it every single time it appears in the LDIF file. A simple "find/replace" (which is supported by nearly every modern text editor) should do just fine, but if any references to "o=sevenSeas" are left over (i.e. if you miss one or two), then the LDAP server will reject the LDIF file as malformed.
15. Same as #3.
16. If you changed your organization unit (the default was "ou=rootOrg"), you need to change it every single time it appears in the LDIF file. You can use the same "find/replace" trick as with #3. As with #3, a mistake here will result in a malformed LDIF file.
17. Same as #16.
18. Same as #16.

LDAP Configuration Reference

This section is a reference with examples for the configuration of the LDAP security module in Jetspeed. Out of the box, Jetspeed searches for user, group & role information in a relational database. However, it can also search this information in an LDAP directory.

Jetspeed stores its LDAP configuration in a Spring XML file called security-spi-ldap.xml

This XML file describes an object (used internally by Jetspeed) that contains LDAP configuration parameters. These configuration parameters are passed onto the object through constructor arguments:

<!-- The LDAP initial context factory. -->
<constructor-arg index="0">
  <value>com.sun.jndi.ldap.LdapCtxFactory</value>
</constructor-arg>

Each constructor argument contains an index to specify the correct order. The file defines the following arguments:

Index Name Example
0 Initial context factory com.sun.jndi.ldap.LdapCtxFactory
1 LDAP server host localhost
2 LDAP server port 389
3 Root context o=sevenSeas
4 The LDAP server root dn uid=admin,o=sevenSeas
5 The LDAP server root password secret
6 The roles filter (objectclass=groupOfUniqueNames))
7 The groups filter (objectClass=groupOfNames)
8 The user filter (objectclass=inetorgperson)
9 roleMembershipAttributes uniqueMember
10 userRoleMembershipAttributes
11 groupMembershipAttributes member
12 userGroupMembershipAttributes
13 groupMembershipForRoleAttributes uniqueMember
14 roleGroupMembershipForRoleAttributes
15 defaultSearchBase
16 roleFilterBase ou=Roles,ou=rootOrg
17 groupFilterBase ou=Groups,ou=rootOrg
18 userFilterBase ou=People,ou=rootOrg
19 roleObjectClasses top,groupOfUniqueNames
20 groupObjectClasses top,groupOfNames
21 userObjectClasses top,person,organizationalPerson,inetorgperson
22 roleIdAttribute cn
23 groupIdAttribute cn
24 userIdAttribute uid
25 UidAttribute uid
26 MemberShipSearchScope 1
27 roleUidAttribute cn
28 groupUidAttribute cn
29 userUidAttribute uid
30 roleObjectRequiredAttributeClasses uniqueMember
31 groupObjectRequiredAttributeClasses member
32 userAttributes sn={u},cn={u}
33 roleAttributes sn={u}
34 groupAttributes sn={u}
35 userPasswordAttribute passWord
36 knownAttributes cn,sn,o,uid,ou,objectClass,userPassword,member,uniqueMember,memberOf

Configuring Jetspeed 2 to Use LDAP

Configuring jetspeed for LDAP usage is simply a matter of having the proper configuration files in place. These configuration files are to be placed in the WEB-INF/assembly folder of the expanded jetspeed WAR.

The following files need to be copied into that directory if you want to connect Jetspeed2 to an LDAP server.

  • security-spi-ldap.xml : Provides the configuration information for LDAP binding, explained in detail below.
  • security-spi-ldap-atn.xml : Provides the SPI configuration for authentication. It replaces the default implementations of the CredentialHandler and UserSecurityHandler with an LDAP specific implementation.
  • security-spi-ldap-atz.xml : Provides the SPI configuration for authorization. It replaces the default implementations of the RoleSecurityHandler, GroupSecurityHandler and SecurityMappingHandler with an LDAP specific implementation.

The default authentication and authorization SPI configurations (the files called security-spi-atn.xml and security-spi-atz.xml) need to be removed from that assembly directory.

In the Jetspeed source tree the examples ldap configuration files can be found in:

${jetspeed-source-home}/components/security/etc/

If your application is deployed in Tomcat, the target assembly directory is located at:

${tomcat-home}/webapps/jetspeed/WEB-INF/assembly/

Furthermore, the source tree of the Jetspeed security component provides several tests using different configurations as well as ldiff sample data for testing the ApacheDS, OpenLDAP, Domino and sunDS LDAP servers. These are located at:

${jetspeed-source-home}/components/security/src/test/JETSPEED-INF/directory/config/

We’ll discuss the security-spi-ldap.xml file in detail below.

LDAP Connection properties

One of the first Jetspeed needs to know is how it to connect to the directory store.

This is done by providing the following properties:


initialContextFactory

The initial context factory

<constructor-arg index="0">
  <value>com.sun.jndi.ldap.LdapCtxFactory</value>
</constructor-arg>


ldapServerName

The name of the LDAP server

<constructor-arg index="1">
  <value>localhost</value>
</constructor-arg>


ldapServerPort

The port of the LDAP server

<constructor-arg index="2">
  <value>389</value>
</constructor-arg>


rootContext

The root context of the LDAP server

<constructor-arg index="3">
  <value>o=sevenSeas</value>
</constructor-arg>


rootDn

The username

<constructor-arg index="4">
  <value>uid=admin,ou=system</value>
</constructor-arg>


rootPassword

The password

<constructor-arg index="5">
  <value>secret</value>
</constructor-arg>

Validate the connection using an LDAP browser:


LDAP Object Filters

A directory service can store any type of object anywhere. As Jetspeed needs to work with roles, groups and users that are defined within the directory, it needs some help in finding them.

The following 3 properties define how Jetspeed will lookup Roles, Groups and Users from the directory store.

  • RoleFilter
  • GroupFilter
  • UserFilter

Property values must be valid objectClassses that are defined in the LDAP schema.

Most LDAP vendors usually expose their schema through an LDIF file that defines every attribute and objectclass available in the directory store.

A configuration based on Lotus Domino might look like this

RoleFilter=(&(objectclass=groupOfUniqueNames)(!(objectClass=dominoGroup)))
GroupFilter=(objectclass=dominoGroup)
UserFilter=(objectclass=dominoPerson)

Domino uses the dominoGroup objectClass to define a group, dominoPerson to define a user, and groupOfUniqueNames to define a role. Since group also has the groupOfUniqueNames as an object class, we need to define a filter for the roles, so that it will only pick up roles. If we had defined the RoleFilter as being (objectclass=groupOfUniqueNames), then the filter would have also picked up the groups.


RoleFilter

This property tells Jetspeed that roles can be recognized by looking for an objectClass attribute with value groupOfUniqueNames.

<constructor-arg index="6">
  <value>=(objectclass=groupOfUniqueNames)</value>
</constructor-arg>


GroupFilter

This property tells Jetspeed that groups can be recognized by looking for an objectClass attribute with value groupOfNames.

<constructor-arg index="7">
  <value>=(objectclass=groupOfUniqueNames)</value>
</constructor-arg>


UserFilter

This property tells Jetspeed that users can be recognized by looking for an objectClass attribute with value organizationalPerson.

<constructor-arg index="8">
  <value>=(objectclass= organizationalPerson)</value>
</constructor-arg>

Alongside these filters, we can also define a filter base for each of those objects (roles, groups and users).

Group/Role membership

In LDAP there are basically 2 ways to define group & role membership (the fact that a user belongs to a group or a role):

  • The user object has an attribute that specifies the groups he is a member of. This is usually done through a memberOf attribute. Microsoft Active Directory and Sun Directory Server use the memberOf and nsrole attribute on the user object.
  • The group/role object contains the group membership information via a multi-valued attribute. No attributes are put on the user to specify membership. Each group/role object has a member list that contains the users belonging to the group

Jetspeed supports both models.

The primary tasks concerning membership of an LDAP are

  • Determining if a user is part of a particular group/role
  • Obtain a list of users belonging to a particular group/role

The 2 models we just covered have an impact on how these tasks are performed

  • Attributes on user object
    • Determining if a user is part of a particular group/role:
      • lookup the membership attribute (ex: memberOf) on the user object for a particular group/role
    • Obtain a list of users belonging to a particular group/role:
      • iterate over the all users, and check their memberOf attribute values for the group
  • Attributes on group/role object
    • To determine if a user is part of a particular group:
      • search the member list on the group for the user
    • To determine the users belonging to a particular group:
      • iterate over the member list on the group

We’ll now discuss in detail how group/role membership can be configured.

Role membership

As already discussed, Jetspeed supports 2 models when it comes to Role membership:

  1. Putting the attributes on the user
  2. Putting the attributes on the role

Jetspeed requires that 1 of 2 properties is set with a value to determine the model:

  • RoleMembershipAttributes
  • UserRoleMembershipAttributes

RoleMembershipAttributes

In order to store role membership on the role, we’ll set the RoleMembershipAttributes attribute by specifying the attribute on the role object that contains the membership information. We don’t provide a value for the UserRoleMembershipAttributes property.

<constructor-arg index="9">
  <value>member</value>
</constructor-arg>

This will make sure that the member attribute is set on the role object, as can be seen in the following screenshot. In the next example, the RoleMembershipAttribute will be blank, so the attributes will be on the user level.

In the screenshot below, we have a Role object defined by
cn=Role3,ou=Roles,ou=rootOrg,o=sevenSeas

The role contains a member attribute, listing all users belonging to that role.


A role with 2 members


The value of the member attribute is the fully qualified DN of the user (including the root context). As you can see, the user doesn't contain any attributes with regards to role membership.


A user


When this attribute is set, Jetspeed will determine the roles for a particular user by performing the following query:

(&(member=cn=user1,ou=people,ou=rootOrg,o=sevenSeas)(objectclass=groupOfNames))

This search filter will return any number of Roles in the directory. The next step for Jetspeed is to identifiy these roles internally. In order to uniquely identify a role, it will use the RoleIdAttribute.

In the example above, cn=Role1 would have been amongst the searchresult. Jetspeed will use the RoleIdAttribute to pickup the role name.


UserRoleMembershipAttributes

In order to store role membership on the user, we’ll set the UserRoleMembershipAttributes attribute by specifying the attribute on the user object that contains the membership information. We don’t provide a value for the RoleMembershipAttributes property.

<constructor-arg index="10">
  <value>memberOf</value>
</constructor-arg>

This will make sure that for each role the user belongs to, the memberOf attribute is set on the user object, as can be seen in the following screenshot:


User belonging to 4 different roles


The value of the memberOf attribute is the fully qualified DN of the role (including the root context). It is a multi valued attribute, so a user can have zero or more memberOf attribute values.

As you can see, the user belongs to a role defined by
cn=role1,ou=Roles,rootOrg,o=sevenSeas.

In order to resolve role membership, Jetspeed will search the directory for roles by using the following filter:

# define the filters needed to search for roles/groups/users
RoleFilter=(objectclass=groupOfUniqueNames)

As you can see in the screenshot, cn=role1,o=sevenSeas corresponds to an object representing a role.

Notice the empty uniqueMember attribute. Most LDAP schemas force you to have a uniqueMember attribute on a groupOfUniqueNames object. Since Jetspeed needs to be able to create roles (that are empty upon creation), an empty uniqueMember attribute needs to be set. This is configurable by Jetspeed through the RequiredAttributeClasses property.


A role without any members


Group membership

As already discussed, Jetspeed supports 2 models when it comes to Group membership:

  1. Putting the attributes on the user
  2. Putting the attributes on the group

Jetspeed requires that 1 of 2 properties is set with a value to determine the model:

  • GroupMembershipAttributes
  • UserGroupMembershipAttributes

GroupMembershipAttributes

In order to store group membership on the group, we'll set the GroupMembershipAttributes attribute by specifying the attribute on the group object that contains the membership information. We don’t provide a value for the UserGroupMembershipAttributes property.

<constructor-arg index="11">
  <value>uniqueMember</value>
</constructor-arg>

This will make sure that the uniqueMember attribute is set on the group object, as can be seen in the following screenshot. In the previous example, the GroupMembershipAttributes was blank, so instead the UserGroupMembershipAttributes was used on the user level:



The value of the uniquemember attribute is the fully qualified DN of the user (including the root context). As you can see, the user doesn’t contain any attributes with regards to group membership.



UserGroupMembershipAttributes

In order to store group membership on the user, we’ll set the UserGroupMembershipAttributes attribute by specifying the attribute on the user object that contains the membership information. We don’t provide a value for the GroupMembershipAttributes property.

<constructor-arg index="12">
  <value>memberOf</value>
</constructor-arg>

This will make sure that the memberOf attribute is set on the user object, as can be seen in the following screenshot.

Only one of those parameters can be filled in. If the GroupMemberShipAttributes is set, Jetspeed assumes that the attribute to determine group membership is on the group object.


User belonging to 2 different roles


The value of the memberOf attribute is the fully qualified DN of the role (including the root context). It is a multi valued attribute, so a user can have zero or more memberOf attribute values. In the screenshot above, we can see that user1 belongs to 2 roles.

As you can see, the role is defined in cn=role1,o=sevenSeas. (notice the empty uniqueMember attribute).


Role definition


Group membership (roles)

Besides storing users in a group, Jetspeed also supports storing roles into groups.

Again, just like with the basic group membership for users, Jetspeed supports 2 models when it comes to Group membership for roles:

  1. Putting the attributes on the role
  2. Putting the attributes on the group

Jetspeed requires that 1 of 2 properties is set with a value to determine the model:

  • GroupMembershipForRoleAttributes
  • RoleGroupMembershipForRoleAttributes

GroupMembershipForRoleAttributes

In order to store group membership on the group, we’ll set the GroupMembershipAttributes attribute by specifying the attribute on the group object that contains the membership information. We don’t provide a value for the UserGroupMembershipAttributes property.

<constructor-arg index="13">
  <value>uniqueMember</value>
</constructor-arg>

This will make sure that the uniqueMember attribute is set on the group object, as can be seen in the following screenshot. In the previous example, the GroupMembershipAttributes was blank, so instead the UserGroupMembershipAttributes was used on the user level.



The value of the uniquemember attribute is the fully qualified DN of the user (including the root context). As you can see, the user doesn’t contain any attributes with regards to group membership.



RoleGroupMembershipForRoleAttributes

In order to store group membership on the user, we’ll set the UserGroupMembershipAttributes attribute by specifying the attribute on the user object that contains the membership information. We don’t provide a value for the GroupMembershipAttributes property.

<constructor-arg index="14">
  <value>memberOf</value>
</constructor-arg>

This will make sure that the memberOf attribute is set on the user object, as can be seen in the following screenshot.



The value of the uniquemember attribute is the fully qualified DN of the user (including the root context). As you can see, the user doesn’t contain any attributes with regards to group membership.



Only one of those parameters can be filled in. If the GroupMemberShipAttributes is set, Jetspeed assumes that the attribute to determine group membership is on the group object.


User belonging to 2 different roles


The value of the memberOf attribute is the fully qualified DN of the role (including the root context). It is a multi valued attribute, so a user can have zero or more memberOf attribute values. In the screenshot above, we can see that user1 belongs to 2 roles.

As you can see, the role is defined in cn=role1,o=sevenSeas. (notice the empty uniqueMember attribute).


Role definition


DefaultSearchBase

Jetspeed allows you to define a default search base that will be used to search the directory

<constructor-arg index="15">
  <value></value>
</constructor-arg>

LDAP Object Filter base

Jetspeed allows you to define the search base that will be applied to queries for roles, groups and users.

Roles, groups and user are typically stored in well-defined containers within the LDAP structure.

  • Roles can be stored in ou=Roles,ou=rootOrg
  • Groups can be stored in ou=Groups,ou=rootOrg
  • Users can be stored in ou=People,ou=rootOrg

This allows you to have the following structure in your LDAP schema. Notice how there are many organizational units within the o=sevenSeas schema. Jetspeed will limit its search scope on the LDAP to the property values defined above. This means that only roles, groups and people within rootOrg will be used by Jetspeed.



So, together with the object filers (RoleFilter, GroupFilter, UserFilter), Jetspeed will be able to locate the roles, groups and users within the directory.

Using these properties, Jetspeed will also create roles, groups and users using the provided ObjectClasses.


RoleFilterBase

Using the property value below, Jetspeed will search for roles in the ou=Roles,ou=OrgUnit subtree.

<constructor-arg index="16">
  <value>ou=Roles,ou=rootOrg</value>
</constructor-arg>



GroupFilterBase

Using the property value above, Jetspeed will search for groups in the ou=Groups,ou=OrgUnit subtree.

<constructor-arg index="17">
  <value>ou=Groups,ou=rootOrg</value>
</constructor-arg>



UserFilterBase

Using the property value above, Jetspeed will search for users in the ou=People,ou=OrgUnit subtree.

<constructor-arg index="18">
  <value>ou=People,ou=rootOrg</value>
</constructor-arg>


LDAP Object classes

Jetspeed allows you to define the ObjectClasses that are needed to create roles, groups and users through the following properties

  • RoleObjectClasses
  • GroupObjectClasses
  • UserObjectClasses

Through the administrative interface, Jetspeed allows an administrator to create roles, groups and users. Each directory server has its own way of defining a role, group or user. Some of the LDAP vendors use proprietary ObjectClasses to define these objects (for example Domino LDAP server uses an dominoGroup objectClass to define a group).

Using these properties, Jetspeed will create roles, groups and users using the provided ObjectClasses.


RoleObjectClasses

<constructor-arg index="19">
  <value>top,groupOfNames</value>
</constructor-arg>

Using the settings above, roles will be created like this


Notice how all of the objectClasses defined by the RoleObjectClasses attribute have been created in the LDAP


GroupObjectClasses

<constructor-arg index="20">
  <value>top,groupOfUniqueNames</value>
</constructor-arg>

Using the settings above, groups will be created like this


Notice how all of the objectClasses defined by the GroupObjectClasses attribute have been created in the LDAP


UserObjectClasses

<constructor-arg index="21">
  <value>top,groupOfUniqueNames</value>
</constructor-arg>

Using the settings above users will be created like this


Notice how all of the objectClasses defined by the UserObjectClasses attribute have been created in the LDAP

Naming Attributes

  • RoleIdAttribute
  • GroupIdAttribute
  • UserIdAttribute

The attributes above allow you to define the naming attribute for roles / groups and users. When an object is created in the directory, a naming attribute needs to be specified. The naming attribute is the attribute that uniquely defines the object within its subdirectory.

In the screenshot below, you can see that the admin user in rootOrg/People is defined by cn=admin.

cn is the naming attribute for the user object, as no 2 admin users can exist in the rootOrg/People subdirectory



By changing the property, you can control the way Jetspeed creates user objects.


RoleIdAttribute

<constructor-arg index="22">
  <value>cn</value>
</constructor-arg>


GroupIdAttribute

<constructor-arg index="23">
  <value>cn</value>
</constructor-arg>


UserIdAttribute

<constructor-arg index="24">
  <value>uid</value>
</constructor-arg>

In the screenshot below, users have the uid attribute as their naming attribute


UserId Attribute

When Jetspeed attempts to find a user, it does so based on the userId provided by the user in the login screen. This userId needs to be defined on the object through a specific attribute. Most LDAP servers have a uid attribute that defines the username of the user in the LDAP.

When Jetspeed builds a userPrincipal internally, it will use the attribute corresponding to the value of the userUidAttribute.



userUidAttribute

<constructor-arg index="25">
  <value>cn</value>
</constructor-arg>

This property is used in conjunction with the UidAttribute

UserIdAttribute=cn
UidAttribute=uid

membershipSearchScope

Jetspeed allows you to customize the search scope when it comes to membership

<constructor-arg index="26">
  <value>cn</value>
</constructor-arg>

RequiredAttributeClasses

Some ObjectClasses force you to add specific attributes on the object before storing it in the directory. Jetspeed allows you to specify these attributes for roles and groups through the following properties

  • roleObjectRequiredAttributeClasses
  • roleObjectRequiredAttributeClasses

For example, most LDAP schemas force you to have a uniqueMember attribute on a groupOfUniqueNames object.

Since Jetspeed needs to be able to create empty roles through the administrative console, an empty uniqueMember attribute needs to be set upon role creation.

This is handled internally by Jetspeed and can be customized by setting the groupObjectRequiredAttributeClasses property.


roleObjectRequiredAttributeClasses

The following property specifies that if a role is created, an empty member attribute will be created on the role object in order to comply with the LDAP schema.

<constructor-arg index="30">
  <value>member</value>
</constructor-arg>


groupObjectRequiredAttributeClasses

The following property specifies that if a group is created, an empty uniqueMember attribute will be created on the group object in order to comply with the LDAP schema.

<constructor-arg index="31">
  <value>uniqueMember</value>
</constructor-arg>

LDAP Object attributes

Jetspeed has an administrative console that allows an administrator to create groups, roles and users in the directory. The Jetspeed LDAP configuration has 3 properties that can manipulate the creation of those objects

  • userAttributes
  • roleAttributes
  • groupAttributes

Each property accepts a comma separated list of attributes. Placeholders can be used in the attribute value.


userAttributes

For example, the following userAttributes value will make sure that when Jetspeed creates a user in the directory, the sn, cn and uid attribute will be created containing the username of the user.

<constructor-arg index="32">
  <value>sn={u},cn={u}</value>
</constructor-arg>


roleAttributes

For example, the following roleAttributes value will make sure that when Jetspeed creates a user in the directory, the cn attribute will be created containing the username of the user.

<constructor-arg index="33">
  <value>cn={u}</value>
</constructor-arg>


groupAttributes

For example, the following groupAttributes value will make sure that when Jetspeed creates a user in the directory, the cn attribute will be created containing the username of the user.

<constructor-arg index="34">
  <value>cn={u}</value>
</constructor-arg>

LDAP Password attributes

During runtime, Jetspeed needs to read the password that is associated with a user. Jetspeed needs to know the attribute on the user object that contains the password. The userPasswordAttribute property defines the LDAP attribute that contains the password of the user

<constructor-arg index="35">
  <value>cn={u}</value>
</constructor-arg>

Known Attributes

When Jetspeed performs LDAP queries, we need to specify the set of attributes that we want to return. This is done by specifying a comma separated value of LDAP attributes in the knowAttributes property

<constructor-arg index="36">
  <value>cn,sn,o,uid,ou,objectClass,userPassword,member,uniqueMember,memberOf</value>
</constructor-arg>