Monday, June 19, 2017

[WSO2 identity server] Extending self registration to add users to custom roles

Self registration feature allows admin to add users to the organization without going through a manual registration process.
This can be easily configured in IS 5.3.0 by following the official documentation [1].

When the registration process completed, the new users are added to internal/selfsignup role. You can go to users and roles in management console and add user to preferred roles later (Figure 1).

Home --> Users and Roles --> List --> Roles , select Assign Users to add users to a specific role.

Screen Shot 2017-06-10 at 2.00.52 AM.png

 Figure 1: Assign users to a role



However, there can be situations where you want to add users directly to a specific role. This functionality can be achieved through a custom event handler.


There are two major steps:

  1. Writing a custom event handler
  2. Registering handler in identity-event.properties

  1. Writing the custom handler



Custom handler should extend the AbstractEventHandler class. The handler should be built as a OSGI bundle.



There are few methods to be overridden.

  1. getName() - this gives a unique name to the handler
  2. handleEvent() - this is where all the event handling logic is implemented
  3. getPriority() - this property sets the priority of the handler. Since this should get executed at the very end pos the add user process, we should provide a larger priority value such as 250.

Following is a sample handler class (In this sample we add users to Internal/Subscriber role).
package org.wso2.carbon.identity.customhandler.handler;

import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.base.IdentityRuntimeException;
import org.wso2.carbon.identity.core.handler.InitConfig;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.event.IdentityEventConstants;
import org.wso2.carbon.identity.event.IdentityEventException;
import org.wso2.carbon.identity.event.event.Event;
import org.wso2.carbon.identity.event.handler.AbstractEventHandler;

import org.wso2.carbon.identity.customhandler.internal.CustomUserSelfRegistrationHandlerDataHolder;
import org.wso2.carbon.identity.recovery.IdentityRecoveryConstants;
import org.wso2.carbon.identity.recovery.IdentityRecoveryServerException;
import org.wso2.carbon.identity.recovery.util.Utils;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
import org.wso2.carbon.identity.core.bean.context.MessageContext;
import org.wso2.carbon.user.core.service.RealmService;

import java.util.List;

import static java.util.Arrays.asList;

public class CustomUserSelfRegistrationHandler extends AbstractEventHandler {

    //Role Constants
    public static final String SUBSCRIBER_ROLE = "Internal/Subscriber";
    public static final String SELF_SIGNUP_ROLE = "Internal/selfsignup";


    @Override public String getName() {
        return "customUserSelfRegistration";
    }

    public void handleEvent(Event event) throws IdentityEventException {

        String tenantDomain = (String) event.getEventProperties()
                .get(IdentityEventConstants.EventProperty.TENANT_DOMAIN);
        String userName = (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.USER_NAME);

        //The handler should be called ss a post add user event.
        if (IdentityEventConstants.Event.POST_ADD_USER.equals(event.getEventName())) {
            try {
                addNewRole(tenantDomain, userName);
            } catch (IdentityRecoveryServerException e) {
                throw new IdentityEventException("Error while adding custom roles to the user", e);
            }
        }
    }

    private void addNewRole(String tenantDomain, String userName)
            throws org.wso2.carbon.identity.recovery.IdentityRecoveryServerException {
        try {
            //Realm service is used for user management tasks
            RealmService realmService = CustomUserSelfRegistrationHandlerDataHolder.getInstance().getRealmService();
            UserStoreManager userStoreManager;
            try {
                userStoreManager = realmService.getTenantUserRealm(IdentityTenantUtil.getTenantId(tenantDomain))
                        .getUserStoreManager();
            } catch (UserStoreException e) {
                throw Utils
                        .handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UNEXPECTED, userName,
                                e);
            }
            //Start a tenant flow
            PrivilegedCarbonContext.startTenantFlow();
            PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
            carbonContext.setTenantId(IdentityTenantUtil.getTenantId(tenantDomain));
            carbonContext.setTenantDomain(tenantDomain);
            try {
                //Since this handler is called as a post add user event, the user should exists in the userstore
                if (userStoreManager.isExistingUser(userName)) {
                    List<String> roleList = asList(userStoreManager.getRoleListOfUser(userName));
                    //User should have selfSignup role. Checking whether the user is in the new role
                    if (roleList.contains(SELF_SIGNUP_ROLE) && !roleList.contains(SUBSCRIBER_ROLE)) {
                        String[] userRoles = new String[] { SUBSCRIBER_ROLE };
                        userStoreManager.updateRoleListOfUser(userName, null, userRoles);
                    }
                }
            } catch (UserStoreException e) {
                throw Utils
                        .handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UNEXPECTED, userName,
                                e);
            }
        } finally {
            Utils.clearArbitraryProperties();
            PrivilegedCarbonContext.endTenantFlow();
        }
    }

    @Override
    public void init(InitConfig configuration) throws IdentityRuntimeException {
        super.init(configuration);
    }

    @Override
    public int getPriority(MessageContext messageContext) {
        return 250;
    }

}

The complete sample code can be found at [git repo].

Please note that this is a basic sample and you can even improve it to read roles from a config file, create roles if not exist, etc.

2. Registering handler in identity-event.properties

1. Open the identity-event.properties file located at <IS_HOME>/repository/conf/identity/ directory.
2. Add your handler module as a new module as below:


module.name.10=suspension.notification
suspension.notification.subscription.1=POST_AUTHENTICATION
module.name.11=customUserSelfRegistration
customUserSelfRegistration.subscription.1=POST_ADD_USER
suspension.notification.enable=false

The module name is the name you provide in the getName() method of the handler.
3. Save the file and restart the server.

Finally, go to the end user dashboad and add a user to test the functionality. (https://localhost:9443/dashboard

[1] https://docs.wso2.com/display/IS530/Self+Sign+Up+and+Account+Confirmation