Customizing user management functions of WSO2 Identity Server

Identity Server supports most of the user management related functions. It provides SOAP based web service API (More details from here ) and REST API (According to SCIM specification) for applications to use these user management functions. Also,  these user management functions are called by the other component of the WSO2IS.  As an example,  These user APIs are call to retrieve attribute for creating SAML2,  ID Tokens, JWT,  authenticating users,  provision users with JIT  and so on

However, their may be some use cases which you need to extend some existing functions of the Identity Server. You may need to do some customization in to the default behavior of the Identity Server. Lets see how we can do it.  There are two ways to extend default behaviors.

1.  Extending existing user manager implementation

2. Adding new listener implementation

Lets go through more details on above.

Extending an existing user manager implementation

Identity server is shipped with four user store manager implementations.

Those are

  • ReadOnlyLDAPUserStoreManager – To connect with LDAP based user store in read only mode. It means Identity Server can not write to LDAP
  • ReadWriteLDAPUserStoreManager – To connect with LDAP based user store in read write mode (This is enabled by default)
  • ActiveDirectoryUserStoreManager – To connect with Active Directory in read write mode
  • JDBCUserStoreManager – This is can be used with default JDBC user store schema that is shipped.

All these implementations are extended from AbstractUserStoreManager class. Therefore you can override any abstract method and write your own implementation. Yes… Like that , You can override/customize/extend the authentication and adding/retrieving/editing/delete users/groups functions of the Identity Server.

Let take following simple scenario.

When user is authenticated with LDAP, there is a requirement to check custom attribute in the LDAP. It means that you want to customize the default authentication and add some additional check.

Following is the extended user store implementation of default “ReadOnlyLDAPUserStoreManager”. I have only override the doAuthenticate() method and have implement the custom logic. In custom logic i verify the user with some additional custom LDAP attribute called “isAppUser”

package org.soasecurity.sample.user.store;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.ldap.ReadOnlyLDAPUserStoreManager;
import java.util.Map;
public class MyLDAPUserStoreManager extends ReadOnlyLDAPUserStoreManager{
@Override
public boolean doAuthenticate(String userName, Object credential) throws UserStoreException {
// verify user authenticate by calling default authentication method in the super class
boolean authenticate = super.doAuthenticate(userName, credential);
// If user is only authenticated, Do custom verification
if(authenticate){
// retrieve ldap attribute of the given user
// ldap attribute name is "isAppUser"
Map<String,String> map = getUserPropertyValues(userName, new String[]{"isAppUser"}, null);
// retrieve attribute from Map
String value = map.get("isAppUser");
// "isAppUser" only contain boolean value
authenticate = Boolean.parseBoolean(value);
}
return authenticate;
}
}

Let see what are steps to configure the custom implementation with the Identity Server

Step 1. Create jar file out of the class.

Step 2. Copy jar file in to IS_HOME/repository/components/lib directory.

Step 3. Configure IS_HOME/repository/conf/user-mgt.xml file with new custom class name.

<UserStoreManager class="org.soasecurity.sample.user.store.MyLDAPUserStoreManager">

Step 4. Restart the server.

Adding new listener implementation

Listener is an another extension to extend the user core functions. Any number of listeners can be plugged with the user core and they would be called one by one. By using listener, we are not overriding the user store implementation, which is good as we are not customizing the existing implementations.

Let see how it works.

Every time when user core method is called, all the listeners that are registered with that method,  are called. Listeners can be registered before or after the actual method is called. Let take some example; In user core, there is a method called “addUser()“. When user is created in Identity Server, “addUser()” method is called. You can register a listener before the actual execution of “addUser()” method and also, you can register a listener after the actual execution of “addUser()” method.

“addUser()” method can be seen as follows.

addUser() {
preAddUser(); // you can implement this using listener
actualAddUser();
postAddUser(); // you can implement this using listener
}

Both “preAddUser()” and “postAddUser()” method can be customized as you want. It means, you can do some custom things before user is added or after user is added.  All the method in user core has been implemented as above.  It means that you can customize them before and after…

Let take following simple scenario.

When user is authenticated with LDAP, there is a requirement to add authenticated time as an attribute of the user.

Therefore we need to write some custom code after successful user authentication is happened.  Following is the custom listener implementation for this. “doPostAuthenticate()” method would be called after actual user authentication is done.

package org.soasecurity.user.mgt.custom.extension;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;
import org.wso2.carbon.user.core.common.AbstractUserOperationEventListener;
/**
*
*/
public class MyUserMgtCustomExtension extends AbstractUserOperationEventListener {
private static Log log = LogFactory.getLog(MyUserMgtCustomExtension.class);
@Override
public int getExecutionOrderId() {
return 9883;
}
@Override
public boolean doPreAuthenticate(String userName, Object credential,
UserStoreManager userStoreManager) throws UserStoreException {
// just log
log.info("doPreAuthenticate method is called before authenticating with user store");
return true;
}
@Override
public boolean doPostAuthenticate(String userName, boolean authenticated, UserStoreManager userStoreManager) throws UserStoreException {
// just log
log.info("doPreAuthenticate method is called after authenticating with user store");
// custom logic
// check whether user is authenticated
if(authenticated){
// persist user attribute in to user store
// "http://wso2.org/claims/lastlogontime" is the claim uri which represent the LDAP attribute
// more detail about claim management from here http://soasecurity.org/2012/05/02/claim-management-with-wso2-identity-server/
userStoreManager.setUserClaimValue(userName, "http://wso2.org/claims/lastlogontime",
Long.toString(System.currentTimeMillis()), null);
}
return true;
}
}

Likewise, you can add custom extensions to any method of the user core.

Here, I would like to note following things.

1.getExecutionOrderId()” can return any random value. This would be important when there are more than one listeners are in the identity Server and you need to consider about execution order of them

2. All the methods are return a boolean value. This value mentioned, whether you want to execute next listener or not.

Let see what are steps to configure the custom implementation.

Step 1. Listeners are registered as OSGI components. Therefore you need to register this class in OSGI framework. You can go through this which is the complete project.

Step 2. Copy OSGI bundle file in to IS_HOME/repository/components/dropins directory.

Step 3. Restart the server.

Thanks for reading.