Technical Blog

1 Post authored by: Kevin Suen

Users of the commerce manager client are created and maintained locally on the commerce manager server.  As shown below, the Commerce Manager client provides an interface to add, update and remove users:

ldap.jpg

 

For some organizations, this is sufficient for managing their user pool. Even when there are several users, a bulk import can suffice. However, this becomes burdensome when organizations have dozens or hundreds of users needing access to the Commerce Manager client. At this scale, an LDAP server is commonly used to manage an organization's user directory.


To integrate an LDAP server into the Commerce Manager server, there are some minor changes required to the login process and management functions that audit events. These changes achieve the following goals:

 

  1. Authentication process to connect to LDAP server.

  2. Audit trails to use LDAP users.

 

Elastic Path's Commerce Manager Server's security is based upon ACEGI, now known as Spring Security.  The following code snippets are updates to the acegi.xml file. They allow the Commerce Manager server to authenticate against a LDAP server:

 

   <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">

        <property name="providers">

    <list>

         <ref local="ldapAuthenticationProvider" />

    </list>

</property>

    </bean>

<bean id="cmUserAuthenticationDao" class="com.elasticpath.persistence.impl.CmUserAuthenticationDaoImpl">

        <property name="persistenceEngine"><ref bean="persistenceEngine"/></property>

    </bean>

<bean id="ldapAuthenticationProvider" class="com.elasticpath.cmweb.security.impl.CmLdapAuthenticationProvider">

<constructor-arg><ref local="authenticator"/></constructor-arg>

<constructor-arg><ref local="populator"/></constructor-arg>

        <property name="userRoleService"><ref bean="userRoleService"/></property>

</bean>

<bean id="authenticator" class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">

<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>

<property name="userDnPatterns">

<list>

<value>cn={0},ou=People</value>

</list>

</property>

<property name="userSearch"><ref local="userSearch"/></property>

</bean>

<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">

<constructor-arg value="ldap://10.10.1.1:389/dc=elasticpath,dc=com"/>

<property name="managerDn">

<value>cn=Manager,dc=elasticpath,dc=com</value>

</property>

<property name="managerPassword">

<value>password</value>

</property>

</bean>

 

<bean id="populator" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">

        <constructor-arg>

<ref local="initialDirContextFactory"/>

        </constructor-arg>

        <constructor-arg>

<value>ou=Groups</value>

        </constructor-arg>

<property name="groupRoleAttribute">

<value>ou</value>

</property>

        <property name="searchSubtree">

<value>true</value>

        </property>

        <property name="rolePrefix">

<value>ROLE_</value>

        </property>

        <property name="convertToUpperCase">

<value>true</value>

        </property>

    </bean>

   

<bean id="userSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">

<constructor-arg>

<value>ou=People</value>

</constructor-arg>

<constructor-arg>

<value>(cn={0})</value>

</constructor-arg>

<constructor-arg>

<ref local="initialDirContextFactory" />

</constructor-arg>

<property name="searchSubtree">

<value>true</value>

</property>

</bean>

 

Upon authentication, an authentication token is returned to the Commerce Manager server from the LDAP server.  This token contains the user name, user password and list of user roles.  These values are used to create the Commerce Manager user in a method added to the CmUserServiceImpl class:

 

/**

* Creates a CmUser with the LDAP authentication token for use on the cm client.

*

* @param authToken the Authentication token created from the LDAP login information

* @return cmUser created from the Authentication token containing username, password and authorities

*/

public CmUser createLdapCmUser(final Authentication authToken) {

CmUser cmUser = getBean(ContextIdNames.CMUSER);

 

cmUser = initializeLdapCmUser(cmUser, authToken);

cmUser = saveOrUpdateLdapCmUser(cmUser, authToken);

return cmUser;

}

 

This public method calls two other methods which I named  initalizeLdapCmUser() and saveOrUpdateLdapCmUser().

 

initalizeLdapCmUser() instantiates a new user with the data returned in the authentication token.  The next method, saveOfUpdateLdapCmUser(), saves the new CmUser into the database.  This step allows the current implementation of auditing to associate a CmUser to a particular event.  Some refactoring is necessary to decouple the two and clean up the overall process, but that is for another day.


Configuring the users for roles is done by simply matching the role values created in the LDAP server to those created in the Commerce Manager.  For example, the following is an example of my LDIF file:

 

dn: dc=elasticpath,dc=com

objectClass: organization

objectClass: top

objectClass: dcObject

dc: elasticpath

o: elasticpath

 

dn: cn=Manager,dc=elasticpath,dc=com

objectClass: organizationalPerson

objectClass: person

objectClass: top

cn: Manager

sn: Manager

userPassword:: password

 

dn: ou=Groups,dc=elasticpath,dc=com

objectClass: organizationalUnit

objectClass: top

ou: Groups

 

dn: ou=SUPERUSER,ou=Groups,dc=elasticpath,dc=com

objectClass: groupOfNames

objectClass: top

cn: superuser

member: cn=user1,ou=People,dc=elasticpath,dc=com

ou: SUPERUSER

 

dn: ou=CMUSER,ou=Groups,dc=elasticpath,dc=com

objectClass: groupOfNames

objectClass: top

cn: cmuser

member: cn=user2,ou=People,dc=elasticpath,dc=com

ou: CMUSER

 

dn: ou=People,dc=elasticpath,dc=com

objectClass: organizationalUnit

objectClass: top

ou: People

 

dn: cn=ksuen,ou=People,dc=elasticpath,dc=com

objectClass: person

objectClass: top

cn: user1

sn: Kevin

userPassword:: password

 

dn: cn=kcleatharo,ou=People,dc=elasticpath,dc=com

objectClass: person

objectClass: top

cn: user2

sn: Maryam

userPassword:: password

 

I have defined a superuser and a cmuser group on the LDAP server.  I have also defined 'user1' and 'user2' belonging to superuser and cmuser respectively.  The value 'SUPERUSER' is matched and given the privilege of the corresponding role in the Commerce Manager.


If you have any questions, feel free to post a comment.

1 Comments Permalink