Creating Directory Service Providers

You can create directory service providers for User Management to store user information. Creating Directory Service Providers explains how to use the User Management SPI to create a directory service provider that you can integrate with LiveCycle. Typically User Management retrieves user data from an LDAP repository. a LiveCycle administrator configures User Management to retrieve records from LDAP using Administration Console. (see LiveCycle Administration Help.)

If your organization uses a non-LDAP repository to store user records, you can create a directory service provider that retrieves user data from your user repository. For example, assume that your organization uses a relational database such as MySQL to store user data. You can create a directory service provider to retrieve user data from the MySQL database.

The sample MySQL database, named spi, consists of three tables:

  • users: Stores user records. This table contains the following fields: userid, firstName, lastName, email, and user_password. The user_password field is encrypted.

  • groups: Stores group records. This table contains the following fields: name, desc, email, email, and aliases.

  • membership: Stores membership information for each group. This table contains the following fields: userid and groupName.

Users table

The following illustration shows the users table.

Groups table

The following illustration shows the groups table.

Membership table

The following illustration shows the membership table.

LiveCycle users

After the directory service provider retrieves user data from the MySQL database, the users shown in the users table become LiveCycle users.

Directory service providers retrieve records from a user repository (for example, the MySQL relational database) at the request of User Management. LiveCycle caches user data to improve performance. You can programmatically synchronize users by using the User Management API. (See Programmatically Synchronizing Users.)

To perform LiveCycle operations using these users, develop a custom authentication handler that is able to authenticate the user. For example, assume that you attempt to log in to Administration Console using tblue. The custom authication handler authenticates tblue by checking the spi.users database. (See Creating Authentication Providers.)

Note: In addition to authenticating a user, a user has to have the Administration Console User role to log in to LiveCycle Administration Console.

A directory service provider retrieves data from both a user repository and group repostory. That is, the directory service provider retrieves data from the users table and the groups and membership tables.

Summary of steps

To develop a directory service provider, perform the following steps:

  1. Set up your development environment.

  2. Define your application logic.

  3. Define the component XML file.

  4. Package the component.

  5. Deploy and test your component.

Sample files

To see sample files that represent a directory service provider, see the <install directory>\sdk\samples\LiveCycleESAPI\FoundationSPI\UserManagementDirectoryManager folder.

Setting up your development environment

To create a directory service provider, create an Eclipse Java project. The version of Eclipse that is supported is 3.2.1 or later. The User Management SPI requires the um-spi.jar file to exist in your project’s class path. If you do not reference this JAR file, you cannot use the User Management SPI in your Java project. This JAR file is installed with the LiveCycle SDK in the <install directory>/Adobe/Adobe LiveCycle ES3/sdk\spi folder.

The following JAR files must be added to your project’s classpath:

  • adobe-livecycle-client.jar

  • adobe-usermanager-client.jar

  • um-spi.jar

Defining your application logic

To define a directory service provider, create Java classes that implement the following interfaces:

  • com.adobe.idp.um.spi.directoryservices.DirectoryUserProvider

  • com.adobe.idp.um.spi.directoryservices.DirectoryGroupProvider

These interfaces implement the com.adobe.idp.um.spi.directoryservices.DirectoryPrincipalProvider interface.

Both the DirectoryUserProvider and DirectoryGroupProvider interfaces are used for retrieving user and group records. User Management requires an implementation of the DirectoryUserProvider interface to synchronize its database with the user repository. User Management also requires an implementation of the DirectoryGroupProvider interface when an action is performed on a group, such as adding a group to a policy.

Implement the following methods that User Management invokes when it performs user and group synchronization:

  • DirectoryUserProvider.getPrincipals()

  • DirectoryUserProvider.testConfiguration()

  • DirectoryGroupProvider.getGroupMembers()

Retrieving user records

To create an implementation that allows for the retrieval of user and group records (principals), implement the DirectoryUserProvider.getPrincipals method. User Management invokes this method repeatedly until all of the specified records are retrieved. The getPrincipals method requires the following parameters:

  • config: A com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig object that contains information about the records to retrieve. Information within this object include the domain and other information specific to the implementation. This object provides access to either a com.adobe.idp.um.spi.directoryservices.UserConfigBO or com.adobe.idp.um.spi.directoryservices.GroupConfigBO object.

  • state: An Object that contains the state returned from the previous call to the method, or null for the initial call. You can create an object of any type as long as it is serializable and available to the server. Each time it is updated, you pass it back to LiveCycle using the com.adobe.idp.um.spi.directoryservices.DSPrincipalCollection.setState method.

The getPrincipals method returns a com.adobe.idp.um.spi.directoryservices.DSPrincipalCollection object that contains either the retrieved user records and state, or null if records were not returned.

Within your implementation of the getPrincipals method, you can create application logic that retrieves user records from the use repository. For example, you can create application logic that retrieves records from the spi.users database. The directory service provider uses classes located in the java.sql.* package to retrieve records from the spi.users database.

To successfully retrieve records from a user repository, create a DSPrincipalRecord instance for each user that you retrieve. A DSPrincipalRecord object represents a user (or group) that becomes a LiveCycle user (or group). Using methods that belong to the DSPrincipalRecord object, you can set user information such as the user’s common name, email, identifier value, and so on. For example, to set the user’s email value, invoke the DSPrincipalRecord instance’s setEmail method. You can retrieve values to pass from the result set from the database query. Add each DSPrincipalRecord instance to the DSPrincipalCollection object by invoking its addDSPrincipalRecord method.

The following application logic shows a database query. The results are passed as parameters when invoking methods that belong to the DSPrincipalRecord instance.

//Prepare the return collection 
DSPrincipalCollection collection = new DSPrincipalCollection(); 
 
//Create connection to mySQL server 
Class.forName("com.mysql.jdbc.Driver"); 
Connection con = DriverManager.getConnection("jdbc:mysql://hsmithk-380:3306/spi", "root", "password"); 
Statement statement = con.createStatement(); 
 
//Branch to the correct type of principal for the sync 
if(principalType.equals(UMConstants.PrincipalTypes.PRINCIPALTYPE_USER)){ 
 
//Select all users from the Users table 
ResultSet res = statement.executeQuery("SELECT * FROM users"); 
while(res.next()){ 
 
//Create a record and set general properties 
DSPrincipalRecord record = new DSPrincipalRecord(); 
record.setDomainName(domainName); 
record.setPrincipalType(principalType); 
record.setDisabled(false); 
record.setVisibility(2); 
 
//Retrieve data from the SQL query 
String userid = res.getString("userid"); 
String firstName = res.getString("firstName"); 
String lastName = res.getString("lastName"); 
String email = res.getString("email"); 
 
//Mandatory field for the record 
record.setCanonicalName(firstName + " " + lastName); 
 
//Set other info pulled from the database 
record.setCommonName(firstName + " " + lastName); 
record.setUserid(userid); 
record.setGivenName(firstName); 
record.setFamilyName(lastName); 
record.setEmail(email); 
                     
//Add the record to the return collection 
collection.addDSPrincipalRecord(record); 
} 
}

Retrieving group members

Your implementation of the DirectoryGroupProvider interface must include a getGroupMembers method that is used to retrieve associations between users and groups. The getGroupMembers method requires the following parameters:

  • config: A com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig object that contains information about the records to retrieve. This object also contains the GroupConfigBO object as configured by User Management.

  • group: A com.adobe.idp.um.spi.directoryservices.DSPrincipalIdRecord that identifies the group whose members are retrieved. This object contains the domain name and canonical name of the group.

The getGroupMembers method returns a com.adobe.idp.um.spi.directoryservices.DSGroupContainmentRecord object that contains the retrieved user records of the group.

To successfully retrieve group records, create a DSPrincipalRecord instance for each group record that you retrieve. Create a DSPrincipalRecord object that represents the group. Using methods that belong to the DSPrincipalRecord object, you can set group information such as the group name, description, and email. For example, to set the group’s email value, invoke the DSPrincipalRecord instance’s setEmail method. You can retrieve values to pass from the result set from the database query. Add each DSPrincipalRecord instance to the DSPrincipalCollection object by invoking its addDSPrincipalRecord method.

The sample directory service provider consists of two classes:

  • SampleDirectoryManager: Handles the requests from LiveCycle to retrieve user and group data. This class implements the implements DirectoryUserProvider and DirectoryGroupProvider classes. This class must contain the following methods: getPrincipals, getGroupMembers, and testConfiguration.

  • DirectoryController: Retrieves user and group data from the MySQL database. This class retrieves data from the MySQL database by using classes located in the java.sql.* package.

Defining the SampleDirectoryManager implementation

The following application logic represents the SampleDirectoryManager class.

package com.adobe.livecycle.samples.directory.provider; 
 
import com.adobe.idp.common.errors.exception.IDPException; 
import com.adobe.idp.um.api.UMConstants; 
import com.adobe.idp.um.spi.directoryservices.DSGroupContainmentRecord; 
import com.adobe.idp.um.spi.directoryservices.DSPrincipalCollection; 
import com.adobe.idp.um.spi.directoryservices.DSPrincipalIdRecord; 
import com.adobe.idp.um.spi.directoryservices.DirectoryGroupProvider; 
import com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig; 
import com.adobe.idp.um.spi.directoryservices.DirectoryUserProvider; 
import com.adobe.livecycle.samples.directory.provider.DirectoryController; 
 
/** 
    * This class provides a sample implementation of User and Group provider SPIs.      
    */ 
 
public class SampleDirectoryManager implements DirectoryUserProvider, 
        DirectoryGroupProvider { 
 
    /* This method is used to obtain all the Users and Groups from any directory. The config object passed to this  
     * method would determine whether to return Users or groups. If config.getUserConfig() is not null then users would 
     * be returned and if config.getGroupConfig() is not null then groups would be returned. 
     */ 
     
    public DSPrincipalCollection getPrincipals(DirectoryProviderConfig config, 
            Object state) throws IDPException { 
         
        if (config.getUserConfig() == null && config.getGroupConfig() == null)  { 
            return null; 
        } 
         
        String principalType = UMConstants.PrincipalTypes.PRINCIPALTYPE_USER; 
 
        if (config.getGroupConfig()  != null) { 
            principalType = UMConstants.PrincipalTypes.PRINCIPALTYPE_GROUP; 
        } 
         
        /* Because the getPrincipals method is continuously called by the sync application 
         * we must tell it to stop after the first pass. 
         * In DirectoryController we set the state event to "Done" so that the next time  
         * this method is called, it will return null, meaning the end of the sync. 
         */ 
        if (state != null) { 
            return null; 
        }  
         
        DirectoryController directoryController = new DirectoryController(principalType, config.getDomain()); 
        return directoryController.getPrincipalsBatch(); 
    } 
 
    /* 
     * Once all the users & groups have been retrieved by calling getPrincipals, LiveCycle will call this method  
     * to obtain user-group associations. Here it just need to pass all those users which belong to the group  
     * being sent here. 
     */ 
    public DSGroupContainmentRecord getGroupMembers( 
            DirectoryProviderConfig config, DSPrincipalIdRecord group) 
            throws IDPException { 
 
        System.out.println("Sending Members of Group : " + group.getCanonicalName()); 
        DSGroupContainmentRecord dsGroupContainmentRecord = DirectoryController.getGroupMembers(group); 
        return dsGroupContainmentRecord; 
    } 
 
    /* 
     * This is a utility method that could be used to validate the settings of 
     * the custom directory whether it's running as expected or not. 
     */ 
 
    public boolean testConfiguration(DirectoryProviderConfig config) { 
        return DirectoryController.testConfiguration(config); 
 
    } 
 
} 

Defining the DirectoryController implementation

The following application logic represents the DirectoryController class.

package com.adobe.livecycle.samples.directory.provider; 
 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.ResultSet; 
import java.sql.Statement; 
import java.util.Arrays; 
 
import com.adobe.idp.common.errors.exception.IDPException; 
import com.adobe.idp.um.api.UMConstants; 
import com.adobe.idp.um.spi.directoryservices.DSGroupContainmentRecord; 
import com.adobe.idp.um.spi.directoryservices.DSPrincipalCollection; 
import com.adobe.idp.um.spi.directoryservices.DSPrincipalIdRecord; 
import com.adobe.idp.um.spi.directoryservices.DSPrincipalRecord; 
import com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig; 
 
/** 
    * This class is an intermediary module which will talk to user repository  
    * to retrieve users and groups in a domain. Hence, it will act as a Controller 
    * interacting with custom directories. 
    */ 
 
public class DirectoryController { 
 
    private String principalType; 
    private String domainName; 
 
    public DirectoryController( String principalType, String domainName) { 
        super(); 
        this.principalType = principalType; 
        this.domainName = domainName; 
    } 
 
    /** 
     * This method returns a batch of principals (users or groups) from the directory.  
     */ 
    public DSPrincipalCollection getPrincipalsBatch() { 
         
        //Prepare the return collection 
        DSPrincipalCollection collection = new DSPrincipalCollection(); 
         
        try{ 
            //Create connection to mySQL server 
            Class.forName("com.mysql.jdbc.Driver"); 
            Connection con = DriverManager.getConnection("jdbc:mysql://hsmithk-380:3306/spi", "root", "password"); 
            Statement statement = con.createStatement(); 
 
            //Branch to the correct type of principal for the sync 
            if(principalType.equals(UMConstants.PrincipalTypes.PRINCIPALTYPE_USER)){ 
                 
                //Select all users from the Users table 
                ResultSet res = statement.executeQuery("SELECT * FROM users"); 
                while(res.next()){ 
 
                    //Create a record and set general properties 
                    DSPrincipalRecord record = new DSPrincipalRecord(); 
                    record.setDomainName(domainName); 
                    record.setPrincipalType(principalType); 
                    record.setDisabled(false); 
                    record.setVisibility(2); 
                     
                    //Retrieve data from the SQL query 
                    String userid = res.getString("userid"); 
                    String firstName = res.getString("firstName"); 
                    String lastName = res.getString("lastName"); 
                    String email = res.getString("email"); 
 
                    //Mandatory field for the record 
                    record.setCanonicalName(firstName + " " + lastName); 
 
                    //Set other info pulled from the database 
                    record.setCommonName(firstName + " " + lastName); 
                    record.setUserid(userid); 
                    record.setGivenName(firstName); 
                    record.setFamilyName(lastName); 
                    record.setEmail(email); 
                     
                    //Add the record to the return collection 
                    collection.addDSPrincipalRecord(record); 
                } 
            }else{ 
                 
                //Select all groups from the Groups table 
                ResultSet res = statement.executeQuery("SELECT * FROM groups "); 
                while(res.next()){ 
 
                    //Create a record and set general properties 
                    DSPrincipalRecord record = new DSPrincipalRecord(); 
                    record.setDomainName(domainName); 
                    record.setPrincipalType(principalType); 
                    record.setDisabled(false); 
                    record.setVisibility(2); 
 
                    //Retrieve data from the SQL query 
                    String name = res.getString("name"); 
                    String desc = res.getString("desc"); 
                    String email = res.getString("email"); 
                    String aliases = res.getString("aliases"); 
 
                    //Mandatory field for the record 
                    record.setCanonicalName(name); 
 
                    //Set other info pulled from the database 
                    record.setCommonName(name); 
                    record.setDescription(desc); 
                    record.setEmail(email); 
                     
                    //The setEmailAliases requires a list as parameter, so we split the string 
                    //containing the comma-seperated email addresses. 
                    record.setEmailAliases(Arrays.asList(aliases.split(","))); 
 
                    //Add the record to the return collection 
                    collection.addDSPrincipalRecord(record); 
                } 
            } 
        }catch(Exception e){ 
            e.printStackTrace(); 
        } 
         
        //Set state to done so that we won't iterate through the list again. 
        collection.setState("DONE"); 
        return collection; 
    } 
 
    /** 
     * This method will return all the users which belong to a particular group. The group members are  
     * formed by randomly selecting users. 
     */ 
    public static DSGroupContainmentRecord getGroupMembers(DSPrincipalIdRecord group) 
    throws IDPException { 
         
        //Create the record and set the group properties 
        DSGroupContainmentRecord record = new DSGroupContainmentRecord(); 
        record.setDomainName(group.getDomainName()); 
        record.setCanonicalName(group.getCanonicalName()); 
         
        try{ 
             
            //Create a connection to the database 
            Class.forName("com.mysql.jdbc.Driver"); 
            Connection con = DriverManager.getConnection("jdbc:mysql://hsmithk-380:3306/spi", "root", "password"); 
            Statement statement = con.createStatement(); 
 
            //Query the membership, to get the users who are in the group 
            ResultSet res = statement.executeQuery("SELECT * FROM users where userid IN (select userid from membership where groupName='"+group.getCanonicalName()+"')"); 
            while(res.next()){ 
                 
                //Retrieve data form resultSet 
                String firstName = res.getString("firstName"); 
                String lastName = res.getString("lastName"); 
                 
                //Create a new DSPrincipalIdRecord member and match the info from the database 
                DSPrincipalIdRecord member = new DSPrincipalIdRecord(); 
                member.setDomainName(group.getDomainName()); 
                member.setCanonicalName(firstName + " "+ lastName); 
                 
                //Add the user to the list of members 
                record.addPrincipalMember(member); 
            } 
        }catch(Exception e){ 
            e.printStackTrace(); 
        } 
         
        //return the list of members 
        return record; 
 
    } 
 
    /** 
     * This method can be used to validate the settings of the custom directory 
     * whether it's running as expected or not 
     *  
     */ 
    public static boolean testConfiguration(DirectoryProviderConfig config) { 
        System.out.println("Testing configuration unimplemented"); 
        return true; 
    } 
 
}

Defining the component XML file for the directory service provider

Create a component XML file in order to deploy the directory service provider component. A component XML file exists for each component and provides metadata about the component. For more information about the component XML file, see the Component XML Reference.

The following component.xml file is used for the directory service provider. Notice that the service name is SampleDirectoryManagerService and the operations this service exposes are named getPrincipals, testConfiguration, and getGroupMembers.

Defining the component XML file for the directory service provider

<component xmlns="http://adobe.com/idp/dsc/component/document"> 
      <component-id>com.adobe.livecycle.samples.directory.SampleDirectoryManagerService</component-id> 
      <version>1.0</version>     
      <search-order>PARENT_FIRST</search-order> 
      <class-path>um-spi.jar</class-path> 
      <dynamic-import-packages> 
         <package version="1.0" isOptional="true">*</package> 
      </dynamic-import-packages> 
      <services> 
         <service name="SampleDirectoryManagerService"> 
           <implementation-class>com.adobe.livecycle.samples.directory.provider.SampleDirectoryManager</implementation-class> 
             <specifications> 
                 <specification spec-id="com.adobe.idp.um.spi.directoryservices.directoryGroupProvider"/> 
                 <specification spec-id="com.adobe.idp.um.spi.directoryservices.directoryUserProvider"/> 
             </specifications> 
            <operations> 
               <operation name="getPrincipals" method="getPrincipals" > 
                  <input-parameter name="config" type="com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig" /> 
                  <input-parameter name="state" type="java.lang.Object" /> 
                  <output-parameter name="result" type="com.adobe.idp.um.spi.directoryservices.DSPrincipalCollection"/> 
               </operation> 
               <operation name="testConfiguration" method="testConfiguration" > 
                  <input-parameter name="config" type="com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig" /> 
                  <output-parameter name="result" type="boolean"/> 
               </operation> 
               <operation name="getGroupMembers" method="getGroupMembers" > 
                  <input-parameter name="config" type="com.adobe.idp.um.spi.directoryservices.DirectoryProviderConfig" /> 
                  <input-parameter name="group" type="com.adobe.idp.um.spi.directoryservices.DSPrincipalIdRecord" /> 
                  <output-parameter name="result" type="com.adobe.idp.um.spi.directoryservices.DSGroupContainmentRecord"/> 
               </operation> 
              </operations> 
          </service> 
      </services> 
</component> 
 

Packaging the directory service provider

Before deploying the directory service provider to LiveCycle, package your Eclipse project into a JAR file. Also, the component XML file and external JAR files must be located at the root of the JAR file.

The following illustration shows the Eclipse project’s content that is packaged into the external directory service provider’s JAR file.

Package the directory service provider into a JAR file named directoryServiceProviderSample-dsc.jar. In the previous diagram, notice that .JAVA files are listed. Once packaged into a JAR file, the corresponding .CLASS files must also be specified. Without the .CLASS files, the directory service provider does not work.

Note: After you package the directory service provider into a JAR file, you can deploy the component to LiveCycle. (See Deploying your component.)
Note: You can also programmatically deploy a component. (See Programmatically Deploying Components.)

Deploying and testing the directory service provider

After you package the directory service provider, deploy and test it.

To deploy and test the directory service provider:

  1. Log in to the Administration Console.

  2. Click Settings > User Management > Domain Management > New Enterprise Domain.

  3. Click Add Directory and provide the profile name.

  4. Select Custom SPI Provider, and click Next. A list of all of the custom SPI providers is displayed and your custom directory provider component is included.

  5. Select your component and click OK. When you start synchronization for this domain, the users and groups are retrieved using your component.

// Ethnio survey code removed