Saturday, September 28, 2013

Configuring OpenLDAP as external user store of WSO2 IS 4.5.0

By default WSO2 IS uses embedded ldap, which is shipped with the product, as the primary user store. But it's possible to configure other user stores (such as OpenLDAP, Active Directory and JDBC user stores) as primary user store. In this blog post I'm going to explain how to configure OpenLDAP as the primary user store of WSO2 IS 4.5.0 in following modes.
  • Read/Write mode
  • Read-only mode
- Since we no longer need embedded ldap, let's disable starting it at server start up. This can be changed in IS_HOME/repository/conf/embedded-ldap.xml

<EmbeddedLDAP>
<Property name="enable">false</Property>
.......................
</EmbeddedLDAP>

- Now let's comment out the default user store manager in user-mgt.xml which resides in IS_HOME/repository/conf.
<!--UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager">
<Property name="TenantManager">org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager</Property>
<Property name="defaultRealmName">WSO2.ORG</Property>
<Property name="kdcEnabled">false</Property>
<Property name="Disabled">false</Property>
<Property name="ConnectionURL">ldap://localhost:${Ports.EmbeddedLDAP.LDAPServerPort}</Property>
<Property name="ConnectionName">uid=admin,ou=system</Property>
<Property name="ConnectionPassword">admin</Property>
<Property name="passwordHashMethod">SHA</Property>
<Property name="UserNameListFilter">(objectClass=person)</Property>
<Property name="UserEntryObjectClass">identityPerson</Property>
<Property name="UserSearchBase">ou=Users,dc=wso2,dc=org</Property>
<Property name="UserNameSearchFilter">(&amp;(objectClass=person)(uid=?))</Property>
<Property name="UserNameAttribute">uid</Property>
<Property name="PasswordJavaScriptRegEx">^[\S]{5,30}$</Property>
<Property name="ServicePasswordJavaRegEx">^[\\S]{5,30}$</Property>
<Property name="ServiceNameJavaRegEx">^[\\S]{2,30}/[\\S]{2,30}$</Property>
<Property name="UsernameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="UsernameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
<Property name="RolenameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="RolenameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
<Property name="ReadGroups">true</Property>
<Property name="WriteGroups">true</Property>
<Property name="EmptyRolesAllowed">true</Property>
<Property name="GroupSearchBase">ou=Groups,dc=wso2,dc=org</Property>
<Property name="GroupNameListFilter">(objectClass=groupOfNames)</Property>
<Property name="GroupEntryObjectClass">groupOfNames</Property>
<Property name="GroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=?))</Property>
<Property name="GroupNameAttribute">cn</Property>
<Property name="SharedGroupNameAttribute">cn</Property>
<Property name="SharedGroupSearchBase">ou=SharedGroups,dc=wso2,dc=org</Property>
<Property name="SharedGroupEntryObjectClass">groupOfNames</Property>
<Property name="SharedGroupNameListFilter">(objectClass=groupOfNames)</Property>
<Property name="SharedGroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=?))</Property>
<Property name="SharedTenantNameListFilter">(objectClass=organizationalUnit)</Property>
<Property name="SharedTenantNameAttribute">ou</Property>
<Property name="SharedTenantObjectClass">organizationalUnit</Property>
<Property name="MembershipAttribute">member</Property>
<Property name="UserRolesCacheEnabled">true</Property>
<Property name="UserDNPattern">uid={0},ou=Users,dc=wso2,dc=org</Property>
<Property name="RoleDNPattern">cn={0},ou=Groups,dc=wso2,dc=org</Property>
<Property name="SCIMEnabled">true</Property>
<Property name="MaxRoleNameListLength">100</Property>
<Property name="MaxUserNameListLength">100</Property>
</UserStoreManager-->
view raw user-mgt1.xml hosted with ❤ by GitHub


OpenLDAP in Read/Write mode

- First uncomment relevant user store manager configuration. i.e.
<UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager">
view raw test1.xml hosted with ❤ by GitHub


Note : There are three such user store manager classes in user-mgt.xml. You have to uncomment the configuration which is available after following comment.

<!-- If product is using an external LDAP as the user store in read/write mode, use following user manager
In case if user core cache domain is needed to identify uniquely set property <Property name="UserCoreCacheIdentifier">domain</Property>
-->
view raw comment1 hosted with ❤ by GitHub


- Below I've attached a sample configuration.

<UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager">
<Property name="TenantManager">org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager</Property>
<Property name="ConnectionURL">ldap://192.168.18.11:389</Property>
<Property name="Disabled">false</Property>
<Property name="ConnectionName">cn=admin,dc=wso2qa,dc=com</Property>
<Property name="ConnectionPassword">qaldap</Property>
<Property name="passwordHashMethod">PLAIN_TEXT</Property>
<Property name="UserNameListFilter">(objectClass=person)</Property>
<Property name="UserEntryObjectClass">inetOrgPerson</Property>
<Property name="UserSearchBase">ou=users,dc=wso2qa,dc=com</Property>
<Property name="UserNameSearchFilter">(&amp;(objectClass=person)(uid=?))</Property>
<Property name="UserNameAttribute">uid</Property>
<Property name="UsernameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
<Property name="UsernameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="RolenameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="RolenameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
<Property name="PasswordJavaScriptRegEx">^[\S]{5,30}$</Property>
<Property name="ReadGroups">true</Property>
<Property name="WriteGroups">true</Property>
<Property name="EmptyRolesAllowed">false</Property>
<Property name="GroupSearchBase">ou=groups,dc=wso2qa,dc=com</Property>
<Property name="GroupNameListFilter">(objectClass=groupOfNames)</Property>
<Property name="GroupEntryObjectClass">groupOfNames</Property>
<Property name="GroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=?))</Property>
<Property name="GroupNameAttribute">cn</Property>
<Property name="SharedGroupNameAttribute">cn</Property>
<Property name="SharedGroupSearchBase">ou=SharedGroups,dc=wso2qa,dc=com</Property>
<Property name="SharedGroupEntryObjectClass">groupOfNames</Property>
<Property name="SharedGroupNameListFilter">(objectClass=groupOfNames)</Property>
<Property name="SharedGroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=?))</Property>
<Property name="SharedTenantNameListFilter">(objectClass=organizationalUnit)</Property>
<Property name="SharedTenantNameAttribute">ou</Property>
<Property name="SharedTenantObjectClass">organizationalUnit</Property>
<Property name="MembershipAttribute">member</Property>
<Property name="UserRolesCacheEnabled">true</Property>
<Property name="ReplaceEscapeCharactersAtUserLogin">true</Property>
<Property name="MaxRoleNameListLength">100</Property>
<Property name="MaxUserNameListLength">100</Property>
<Property name="SCIMEnabled">false</Property>
</UserStoreManager>
view raw user-mgt2.xml hosted with ❤ by GitHub


Now let's walk through the above configuration to identity the properties that needs to be changed.

1) 
<Property name="ConnectionName">cn=admin,dc=wso2qa,dc=com</Property>
view raw connectionName hosted with ❤ by GitHub

Here you have to provide the connection url of openldap. Pay attention to accuracy of ip and port when specifying it.

2) 
<Property name="ConnectionName">cn=admin,dc=wso2qa,dc=com</Property>
view raw connectionurl hosted with ❤ by GitHub

This is the bind dn or the user. i.e. Distinguished name used to authenticate to the Directory server when performing an operation. This user has permission to read all users and perform search operations in Directory server

3) 
<Property name="passwordHashMethod">PLAIN_TEXT</Property>
view raw password hosted with ❤ by GitHub

This is the password of above bind dn or user

4) 
<Property name="UserSearchBase">ou=users,dc=wso2qa,dc=com</Property>
view raw users hosted with ❤ by GitHub

This is the directory in which users created through IS are stored. Also LDAP search for users starts from this location

5) 
<Property name="UserNameAttribute">uid</Property>
view raw attribute hosted with ❤ by GitHub

This is the attribute to which the user name is mapped. "cn" and "uid" are commonly used for this purpose

6) 
<Property name="ReadGroups">true</Property>
view raw readgroup hosted with ❤ by GitHub

This property specifies whether groups in LDAP should be read from IS or not. If we do not want to read LDAP groups then non of the other group related properties need to be changed

For more information, please go through IS documentation

7) 
<Property name="WriteGroups">true</Property>
view raw writegroup hosted with ❤ by GitHub

If this is set to false, you cannot add groups to LDAP through Identity Server.

8) 
<Property name="EmptyRolesAllowed">false</Property>
view raw emptyrole hosted with ❤ by GitHub

OpenLDAP does not allow to create roles without at least one user assigned to it. Therefore, if you are going to use openldap this property should be false.

9) 
<Property name="GroupSearchBase">ou=groups,dc=wso2qa,dc=com</Property>
view raw groups hosted with ❤ by GitHub

This is the directory in which groups created through Identity Server are stored. In addition to that LDAP search for groups begin from this place.

Note : Changing the rest of the properties are optional and you can configure them according to your requirement.

<AddAdmin>true</AddAdmin>
<AdminRole>adminopenldap</AdminRole>
<AdminUser>
<UserName>adminldap</UserName>
<Password>admin123</Password>
</AdminUser>
view raw user-mgt3.xml hosted with ❤ by GitHub


- This configuration can be found on top part of user-mgt.xml

- If AddAdmin is set to true, at the first startup of Identity server it checks whether this admin user/admin role is in LDAP. If admin user/admin role are not there, IS adds them to LDAP.
You can set this to false if you need an existing admin user/role for IS.

- Admin user name, admin password and admin role can be changed according to your requirement.

OpenLDAP in Read-Only mode

- For this also we should first comment out the default user store manager as I've explained before.

- Since we need to configure LDAP in read only mode, uncomment following user store manager class
<UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadOnlyLDAPUserStoreManager">
view raw test2.xml hosted with ❤ by GitHub


- Below I've attached a sample configuration.

<UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadOnlyLDAPUserStoreManager">
<Property name="TenantManager">org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager</Property>
<Property name="ReadOnly">true</Property>
<Property name="Disabled">false</Property>
<Property name="MaxUserNameListLength">100</Property>
<Property name="ConnectionURL">ldap://192.168.18.11:389</Property>
<Property name="ConnectionName">cn=admin,dc=wso2qa,dc=com</Property>
<Property name="ConnectionPassword">qaldap</Property>
<Property name="passwordHashMethod">PLAIN_TEXT</Property>
<Property name="UserSearchBase">ou=users,dc=wso2qa,dc=com</Property>
<Property name="UserNameListFilter">(objectClass=person)</Property>
<Property name="UserNameSearchFilter">(&amp;(objectClass=person)(uid=?))</Property>
<Property name="UserNameAttribute">uid</Property>
<Property name="ReadGroups">true</Property>
<Property name="GroupSearchBase">ou=groups,dc=wso2qa,dc=com</Property>
<Property name="GroupNameListFilter">(objectClass=groupOfNames)</Property>
<Property name="GroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=?))</Property>
<Property name="GroupNameAttribute">cn</Property>
<Property name="SharedGroupNameAttribute">cn</Property>
<Property name="SharedGroupSearchBase">ou=SharedGroups,dc=wso2qa,dc=com</Property>
<Property name="SharedGroupNameListFilter">(objectClass=groupOfNames)</Property>
<Property name="SharedTenantNameListFilter">(objectClass=organizationalUnit)</Property>
<Property name="SharedTenantNameAttribute">ou</Property>
<Property name="SharedTenantObjectClass">organizationalUnit</Property>
<Property name="MembershipAttribute">member</Property>
<Property name="UserRolesCacheEnabled">true</Property>
<Property name="ReplaceEscapeCharactersAtUserLogin">true</Property>
<Property name="MaxRoleNameListLength">100</Property>
<Property name="MaxUserNameListLength">100</Property>
<Property name="SCIMEnabled">false</Property>
</UserStoreManager>
view raw user-mgt4.xml hosted with ❤ by GitHub


- Properties are same as what I've explained earlier. Only difference is there's no need of having properties like UsernameJavaRegEx, UsernameJavaScriptRegEx, RolenameJavaScriptRegEx, RolenameJavaRegEx, PasswordJavaScriptRegEx, WriteGroups and EmptyRolesAllowed because we are not going to add users or role through Identity Server.

<AddAdmin>false</AddAdmin>
<AdminRole>admin</AdminRole>
<AdminUser>
<UserName>admin</UserName>
<Password>admin</Password>
</AdminUser>
view raw user-mgt5.xml hosted with ❤ by GitHub

Since this is a read-only user store, you should provide a user who's already there in user store as the admin user.
But depending on your requirement you can provide and existing or non-existing admin role. If admin role is already not there in user store, then it will be added as an internal role at Identity serer first startup.

Special Note : You can find sample user-mgt.xml  files for read/write mode in this location  and for read-only mode here.

Below I've attached some images of LDAP to understand the LDAP structure used in this sample.






1 comment: