LDAP customization points

There are customization points available when you integrate with LDAP to handle extra synchronization logic.

A new task command that is called com.ibm.commerce.member.syncbeans.commands.LDAPIntegrationCmd contains methods that can be overridden. Since LDAP integration requires a site-wide implementation, only 1 implementation of this command can be registered by using store 0.

The methods are called at varying points in the LDAP integration scenarios:

Support for different organizational hierarchies in LDAP and HCL Commerce database

It is recommended that the organization structure and distinguished names on the LDAP server match what is in HCL Commerce database. If the organization hierarchies match, but the distinguished names of Root Organization and Default Organization in LDAP are different from the default values in HCL Commerce, then the Integration Wizard can handle renaming the DN values in HCL Commerce during the initial setup of HCL Commerce with LDAP. If, however, the organization hierarchies are different, for example, the LDAP server has a relatively flat structure while HCL Commerce database has a more complex structure, the recommended approach is still to make the LDAP structure the same as the HCL Commerce organizational structure. However, if this is not possible, you must override the default implementation of the following methods of the LDAPIntegrationCmd task command to handle the mapping between the LDAP DNs and the HCL Commerce database DNs:

public String getLDAPDN(String astrCommerceDN) throws ECException; 

public String getCommerceDN(String astrLDAPDN, DataObject adoMember) throws ECException; 

If the mapping between organization DNs is one-to-many, for example, one organization in LDAP maps to multiple buyer organizations in HCL Commerce, these organizations must be manually preloaded into both systems, rather than relying on the OrganizationSyncBean to automatically create them.

In all cases, HCL Commerce requires the Root Organization (-2001) be the common ancestor of all other organizations, and the Default Organization (-2000) exist directly under the Root Organization. HCL Commerce expects that the Default Organization is the parent organization of B2C customers.

Example

/**
* Returns the LDAP DN that corresponds to a specified Commerce DN.
* By default, the following mapping is done: <ul>
* <li> "uid=xxx,o=buyer a organization,o=root organization" maps to "uid=xxx,o=internal,o=root organization"
*
* <li> "uid=yyy,o=buyer b organization,o=root organization" maps to "uid=yyy,o=internal,o=root organization"
*
* <li> DNs ending with "o=default organization,o=root organization" are changed to end with "o=testorg,o=root organization"
*
* <li> Otherwise, the input DN is returned.
* </ul>
*
* @param astrCommerceDN The Commerce DN.
* @return The LDAP DN.
* @exception ECException Thrown if an error occurs.
*/
public String getLDAPDN(String astrCommerceDN) throws ECException {

String strLDAPDN = astrCommerceDN;

if (astrCommerceDN.equals(BUYER_A_ORG_DN) ||
astrCommerceDN.equals(BUYER_B_ORG_DN) ) {

//Map "o=buyer x organization,o=root organization" to "o=internal,o=root organization"
strLDAPDN = INTERNAL_ORG_DN;
} else if ( astrCommerceDN.indexOf(BUYER_A_ORG_DN) > 0) {
int nIndex = astrCommerceDN.indexOf(BUYER_A_ORG_DN);
String strDNBeginning = astrCommerceDN.substring(0, nIndex);

//Map "uid=yyy,o=buyer a organization,o=root organization" to "uid=yyy,o=internal,o=root organization"

strLDAPDN = strDNBeginning + INTERNAL_ORG_DN;

} else if ( astrCommerceDN.indexOf(BUYER_B_ORG_DN) > 0) {
int nIndex = astrCommerceDN.indexOf(BUYER_B_ORG_DN);
String strDNBeginning = astrCommerceDN.substring(0, nIndex);

//Map "uid=yyy,o=buyer b organization,o=root organization" to "uid=yyy,o=internal,o=root organization"

strLDAPDN = strDNBeginning + INTERNAL_ORG_DN;

} else if ( astrCommerceDN.endsWith("o=default organization,o=root organization")) {
strLDAPDN = SyncBeanUtil.getRDN(astrCommerceDN)
+ ECMemberConstants.EC_LDAP_DN_SEPARATOR
+ "o=testorg,o=root organization";
}

return strLDAPDN;
}


/**
* Returns the Commerce DN that corresponds to a specified LDAP DN: <ul>
*
* <li> If the user belongs to the "buyer x organization" member
* group in LDAP, then the user belongs to "buyer x organization" in Commerce.
*
* <li> DNs ending with "o=default organization,o=root organization" are changed to end with "o=testorg,o=root organization"
*
* <li> Otherwise, the input DN is returned.
* </ul>
*
* @param astrLDAPDN The LDAP DN.
* @param adoMember The entity data object corresponding to the member.
* @return The Commerce DN.
* @exception ECException Thrown if an error occurs.
*/
public String getCommerceDN(String astrLDAPDN, DataObject adoMember)
throws ECException {

String strCommerceDN = astrLDAPDN;

if (astrLDAPDN.endsWith(INTERNAL_ORG_DN)) {

/**
* Get the group information for the user.
* If the user belongs to the "buyer x organization" member
* group in LDAP, then the user belongs to "buyer x
* organization" in Commerce, etc.
*/
List<DataObject> lstGroups = adoMember.getList( SchemaConstants.DO_GROUPS);
Iterator<DataObject> iterGroups = lstGroups.iterator();
while (iterGroups.hasNext()) {
DataObject doGroup = iterGroups.next();
DataObject doID = doGroup.getDataObject(SchemaConstants.DO_IDENTIFIER);
String strGroupName = doID.getString(SchemaConstants.PROP_UNIQUE_NAME);

if ( BUYER_A_MBRGRP_DN.equals(strGroupName) ) {

strCommerceDN = SyncBeanUtil.getRDN(astrLDAPDN)
+ ECMemberConstants.EC_LDAP_DN_SEPARATOR
+ BUYER_A_ORG_DN;

break;
} else if (BUYER_B_MBRGRP_DN.equals(strGroupName) ) {

strCommerceDN = SyncBeanUtil.getRDN(astrLDAPDN)
+ ECMemberConstants.EC_LDAP_DN_SEPARATOR
+ BUYER_B_ORG_DN;

break;
}
}
} else if (astrLDAPDN.endsWith("o=testorg,o=root organization")) {
strCommerceDN = SyncBeanUtil.getRDN(astrLDAPDN)
+ ECMemberConstants.EC_LDAP_DN_SEPARATOR
+ "o=default organization,o=root organization";
}
return strCommerceDN;
} 

Callout to do extra processing during Single sign on (SSO) and Logon

In LogonCmdImpl.java, when the authentication mode is set to LDAP, and the authentication is successful, the following method of LDAPIntegrationCmdImpl.java is called to allow further processing to be done:

public void postLogonProcessing(UserAccessBean aUserAccessBean) throws ECException;
UserSyncBean.findByMemberId(aUserAccessBean.getMemberId()) can be called to get the UserSyncBean. From UserSyncBean.getLDAPMember() returns a DataObject representing the object in LDAP, which can be used for any further processing.
After single sign on takes place, the following method is called to handle any additional processing:

public void postSingleSignOnProcessing(UserSyncBean aUserSyncBean) throws ECException;

Making use of member group information of users in LDAP

The following is a sample implementation that shows how the member group information of a user in LDAP can be used to assign roles to the user in HCL Commerce:
/**
 * This method is called after single sign-on has taken place.
 * It enables extra processing to take place. 
 *
 * Behavior: 
 * 
 *
 * Get the LDAP groups that the user belongs to from the UserSyncBean's LDAP data object.
 * 
 * If the user belongs to the "cn=csr,cn=groups,o=root organization" LDAP group, assign 
 * them the CSR role in Root Organization, if they don't have that role already.
 * 
 * If the user belongs to the "cn=gold customers,cn=groups,o=root organization" LDAP 
 * group, include them in the corresponding WC member group: "gold customers", if they 
 * don't already belong to it.
 * 
 * @param aUserSyncBean Contains information about the user that has authenticated. 
 * @throws ECException Thrown if an error occurs.
 */
public void postSingleSignOnProcessing(UserSyncBean aUserSyncBean) throws ECException{

  final String METHODNAME = "postSingleSignOnProcessing(UserSyncBean aUserSyncBean)";

  DataObject doLDAPMember = aUserSyncBean.getLDAPMember();

  if (doLDAPMember == null) {
    return;
  }

  //Get the groups that the user belongs to in LDAP
  List<DataObject> lstGroups = doLDAPMember.getList( SchemaConstants.DO_GROUPS);
  Iterator<DataObject> iterGroups = lstGroups.iterator();

  //Get the groups the user belongs to and then synch to WC DB's MBRROLE or MBRGRPMBR
  while (iterGroups.hasNext()) {
    DataObject doGroup = iterGroups.next();
    DataObject doID = doGroup.getDataObject(SchemaConstants.DO_IDENTIFIER);
    String strGroupName = doID.getString(SchemaConstants.PROP_UNIQUE_NAME);

    try{
      if ("cn=csr,cn=groups,o=root organization".equals(strGroupName)) {
        //Assign the user the CSR role in the Root Organization
        try {
          MemberRoleCache.findByMemberIdRoleIdOrgEntityId(
              Long.valueOf(aUserSyncBean.getMemberId()), 
              CSR_ROLE,
              ROOT_ORG);
        } catch (FinderException e) {
          // Role assignment does not exist, so create it
          new MemberRoleAccessBean(
              Long.valueOf(aUserSyncBean.getMemberId()), 
              CSR_ROLE,
              ROOT_ORG);
        } 
      } else if (GOLD_CUST_LDAP_GROUP.equals(strGroupName)) {

        MemberGroupAccessBean abMbrGrp = null;
        try {
          abMbrGrp = MemberGroupCache.findByOwnerName(
              ROOT_ORG, 
              GOLD_CUST_WC_GROUP);
        } catch (FinderException e) {
          ECTrace.trace(ECTraceIdentifiers.COMPONENT_USER, CLASSNAME, METHODNAME,
              "Member group does not exist: " + GOLD_CUST_LDAP_GROUP);

          continue; // skip to next iteration in the loop
        }


        //Assign the user to this member group in Commerce if it doesn't already exist
        try {
          MemberGroupMemberCache.findByPrimaryKey(
              abMbrGrp.getMbrGrpId(), 
              aUserSyncBean.getMemberId());
        } catch (FinderException e) {
          //Since the user doesn't belong to member group, add it now
          new MemberGroupMemberAccessBean(
              abMbrGrp.getMbrGrpIdInEJBType(), 
              Long.valueOf(aUserSyncBean.getMemberId()));
        }

      }
    } catch (NamingException e) {
      throw new ECSystemException(
          ECMessage._ERR_NAMING_EXCEPTION, 
          CLASSNAME, 
          METHODNAME, 
          ECMessageHelper.generateMsgParms(e), 
          e);
    } catch (RemoteException e) {
      throw new ECSystemException(
          ECMessage._ERR_REMOTE_EXCEPTION, 
          CLASSNAME, 
          METHODNAME, 
          ECMessageHelper.generateMsgParms(e), 
          e);
    } catch (CreateException e) {
      throw new ECSystemException(
          ECMessage._ERR_CREATE_EXCEPTION, 
          CLASSNAME, 
          METHODNAME, 
          ECMessageHelper.generateMsgParms(e), 
          e);
    } catch (FinderException e) {
      throw new ECSystemException(
          ECMessage._ERR_FINDER_EXCEPTION, 
          CLASSNAME, 
          METHODNAME, 
          ECMessageHelper.generateMsgParms(e), 
          e);
    }
  }
} 

Callout to do extra processing in SyncBean

The following method can be overridden to do extra processing whenever LDAP is being updated using a sync bean:

public void LDAPIntegrationCmd.postUpdateToLDAP (UserSyncBean userSyncBean) throws ECException;
The following method can be overridden to do extra processing whenever the HCL Commerce database is being updated by data from LDAP:

public void LDAPIntegrationCmd.postRefreshFromLDAP (UserSyncBean userSyncBean) throws ECException;

Synchronizing extra data

UserSyncBean and OrganizationSyncBean read and write data to the database as well as to LDAP. Each class reads and writes to a default set of HCL Commerce database tables. Each of these tables has a corresponding sync helper data object (DO) class that is used by the sync bean to read and write to the table:
UserSyncBean
DO class Database Table
UserDO USERS
UserRegistryDO USERREG
UserDemographicsDO USERDEMO
SelfAddressDO ADDRESS (SELF ADDRESS)
BusinessProfileDO BUSPROF
UserProfileDO USERPROF
MemberAttributesDO MBRATTRVAL
OrganizationSyncBean
DO Class Database Table
OrgEntityDO ORGENTITY
SelfAddressDO ADDRESS (SELF ADDRESS)
MemberAttributesDO MBRATTRVAL
The DO classes to include for each sync bean can be specified and changed from the default implementation. For example, the following is the default implementation of LDAPIntegrationCmd.getUserDOs():

public Vector getUserDOs() {
        
        Vector vUserDOs = new Vector(7);
        
        vUserDOs.add(new UserDO());
        vUserDOs.add(new UserRegistryDO());
        vUserDOs.add(new UserDemographicsDO());
        vUserDOs.add(new SelfAddressDO());
        vUserDOs.add(new BusinessProfileDO());
        vUserDOs.add(new UserProfileDO());
        vUserDOs.add(new MemberAttributesDO());
        
        return vUserDOs;
    }
The task command can be extended, and more DO classes can be added if you want to synchronize with new custom user tables.
The following is the default implementation of LDAPIntegrationCmd.getOrganizationDOs():

public Vector getOrganizationDOs() {
        
        Vector vUserDOs = new Vector(3);
        
        vUserDOs.add(new OrgEntityDO());
        vUserDOs.add(new SelfAddressDO());
        vUserDOs.add(new MemberAttributesDO());
        
        return vUserDOs;
    }
Similar to the preceding example, the task command can be extended, and more DO classes can be added if you want to synchronize with new custom organization tables.