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:
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:
Authentication process to connect to LDAP server.
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.
