Showing posts with label Identity Management. Show all posts
Showing posts with label Identity Management. Show all posts

Wednesday, February 15, 2012

Activiti Authentication And Identity Management Tutorial

This time we focus on Business Process Management (BPM) and more precisely on Activiti. Activiti is an open source process engine for Java, that we decided to look into. One of the questions that arose during development was how to assimilate our users and groups into the Activiti platform. Luckily, this is supported by the Activiti engine, but not so well documented (to our knowledge). We have decided to take on the challenge, and create a quick tutorial on how to manage users and groups identity in Activiti (and more precisely Activiti 5.8).

Activiti supports two main entry points that you need to implement in order to integrate your identity solution. These are the user manager and the group manager classes. We will first create a factory for group/user management, and then implement the class that we create in the factory. Lets take a dive into the code, implementing the MyGroupManagerFactory class first:

   1: package my.great.company.com.businessprocessengine.identityservice;

   2:  

   3: import org.activiti.engine.impl.interceptor.Session;

   4: import org.activiti.engine.impl.interceptor.SessionFactory;

   5: import org.activiti.engine.impl.persistence.entity.GroupManager;

   6:  

   7: public class MyGroupManagerFactory implements SessionFactory {

   8:  

   9:     private MyConnectionParams    connectionParams;

  10:  

  11:     public MyGroupManagerFactory(MyConnectionParams params) {

  12:         this.connectionParams = params;

  13:     }

  14:  

  15:     @Override

  16:     public Class<?> getSessionType() {

  17:         return GroupManager.class;

  18:     }

  19:  

  20:     @Override

  21:     public Session openSession() {

  22:         return new MyGroupManager(connectionParams);

  23:     }

  24: }
We will also want to provide a factory for user management classes, so here is the MyUserManagerFactory :
   1: package my.great.company.com.businessprocessengine.identityservice;

   2:  

   3: import org.activiti.engine.impl.interceptor.Session;

   4: import org.activiti.engine.impl.interceptor.SessionFactory;

   5: import org.activiti.engine.impl.persistence.entity.UserManager;

   6:  

   7: public class MyUserManagerFactory implements SessionFactory {

   8:  

   9:     private MyConnectionParams connectionParams;

  10:     

  11:     public MyUserManagerFactory(MyConnectionParams params) {

  12:         this.connectionParams = params;

  13:     }

  14:     

  15:     @Override

  16:     public Class<?> getSessionType() {

  17:       return UserManager.class;

  18:     }

  19:  

  20:     @Override

  21:     public Session openSession() {

  22:       return new MyUserManager(connectionParams);

  23:     }

  24:  

  25: }
As you can see above we assume that we want to get connection prams in the instantiation of the classes (assume MyConnectionParams is a simple POJO, and implement it for your needs). Moreover, you will need the Activiti engine jar file in your classpath.

We now implement MyGroupManager and MyUserManager classes. We did not not detail the entire code, but created an example to show what must be implemented and what you can skip. In MyGroupManager  we only implemented the findGroupByQueryCriteria, and findGroupCountByQueryCriteria. Methods that are marked with TODO notation are mandatory in order to work with Activiti core engine. If you want to work with Activity Explorer - implement all the methods.
   1: package my.great.company.com.businessprocessengine.identityservice;
   2:  

   3: import java.util.ArrayList;

   4: import java.util.List;

   5:  

   6: import org.activiti.engine.ActivitiException;

   7: import org.activiti.engine.identity.Group;

   8: import org.activiti.engine.impl.GroupQueryImpl;

   9: import org.activiti.engine.impl.Page;

  10: import org.activiti.engine.impl.persistence.entity.GroupEntity;

  11: import org.activiti.engine.impl.persistence.entity.GroupManager;

  12: import org.apache.commons.lang.StringUtils;

  13:  

  14: public class MyGroupManager extends GroupManager {

  15:  

  16:     

  17:     public MyGroupManager(MyConnectionParams connectionParams) {

  18:         

  19:     }

  20:  

  21:     @Override

  22:     public Group createNewGroup(String groupId) {

  23:         throw new ActivitiException("My group manager doesn't support creating a new group");

  24:     }

  25:  

  26:     @Override

  27:     public void insertGroup(Group group) {

  28:         throw new ActivitiException("My group manager doesn't support inserting a new group");

  29:     }

  30:  

  31:     @Override

  32:     public void updateGroup(Group updatedGroup) {

  33:         throw new ActivitiException("My group manager doesn't support updating a new group");

  34:     }

  35:  

  36:     @Override

  37:     public void deleteGroup(String groupId) {

  38:         throw new ActivitiException("My group manager doesn't support deleting a new group");

  39:     }
  40:  

  41:     @Override

  42:     public long findGroupCountByQueryCriteria(Object query) {

  43:         return findGroupByQueryCriteria(query, null).size();

  44:     }

  45:  

  46:     @Override

  47:     public List<Group> findGroupByQueryCriteria(Object query, Page page) {

  48:         List<Group> groupList = new ArrayList<Group>();

  49:         GroupQueryImpl groupQuery = (GroupQueryImpl) query;

  50:         if (StringUtils.isNotEmpty(groupQuery.getId())) {

  51:             GroupEntity singleGroup = findGroupById(groupQuery.getId());

  52:             groupList.add(singleGroup);

  53:             return groupList;

  54:         } else if (StringUtils.isNotEmpty(groupQuery.getName())) {

  55:             GroupEntity singleGroup = findGroupById(groupQuery.getId());

  56:             groupList.add(singleGroup);

  57:             return groupList;

  58:         } else if (StringUtils.isNotEmpty(groupQuery.getUserId())) {

  59:             return findGroupsByUser(groupQuery.getUserId());

  60:         } else {

  61:             //TODO: get all groups from your identity domain and convert them to List<Group>

  62:             return null;

  63:         } //TODO: you can add other search criteria that will allow extended support using the Activiti engine API

  64:     }

  65:  

  66:     @Override

  67:     public GroupEntity findGroupById(String activitiGroupID) {

  68:         //TODO

  69:         throw new ActivitiException("My group manager doesn't support finding a group");

  70:     }

  71:  

  72:     @Override

  73:     public List<Group> findGroupsByUser(String userLogin) {

  74:         //TODO

  75:         throw new ActivitiException("My group manager doesn't support finding a group");

  76:     }

  77: }
In MyUserManager, we have implemented findUserCountByQueryCriteria and findUserByQueryCriteria. Again - the methods that are marked with TODO notation are mandatory in order to work with Activiti core engine.

   1: package my.great.company.com.businessprocessengine.identityservice;

   2:  

   3: import java.util.ArrayList;

   4: import java.util.List;

   5:  

   6: import org.activiti.engine.ActivitiException;

   7: import org.activiti.engine.identity.User;

   8: import org.activiti.engine.impl.Page;

   9: import org.activiti.engine.impl.UserQueryImpl;

  10: import org.activiti.engine.impl.persistence.entity.UserEntity;

  11: import org.activiti.engine.impl.persistence.entity.UserManager;

  12: import org.apache.commons.lang.StringUtils;

  13:  

  14: public class MyUserManager extends UserManager {

  15:     

  16:     public MyUserManager(MyConnectionParams connectionParams) {

  17:     }

  18:  

  19:     @Override

  20:     public User createNewUser(String userId) {

  21:         throw new ActivitiException("My user manager doesn't support creating a new user");

  22:     }

  23:  

  24:     @Override

  25:     public void insertUser(User user) {

  26:         throw new ActivitiException("My user manager doesn't support inserting a new user");

  27:     }

  28:  

  29:     @Override

  30:     public void updateUser(User updatedUser) {

  31:         throw new ActivitiException("My user manager doesn't support updating a user");

  32:     }

  33:  

  34:     @Override

  35:     public void deleteUser(String userId) {

  36:         throw new ActivitiException("My user manager doesn't support deleting a user");

  37:     }

  38:  

  39:     @Override

  40:     public UserEntity findUserById(String userLogin) {

  41:         //TODO: get my user according to userLogin and convert it to UserEntity

  42:         throw new ActivitiException("My user manager doesn't support finding a user");

  43:     }

  44:  

  45:     @Override

  46:     public List<User> findUserByQueryCriteria(Object query, Page page) {

  47:  

  48:         List<User> userList = new ArrayList<User>();

  49:         UserQueryImpl userQuery = (UserQueryImpl) query;

  50:         if (StringUtils.isNotEmpty(userQuery.getId())) {

  51:             userList.add(findUserById(userQuery.getId()));

  52:             return userList;

  53:         } else if (StringUtils.isNotEmpty(userQuery.getLastName())) {

  54:             userList.add(findUserById(userQuery.getLastName()));

  55:             return userList;

  56:         } else {

  57:             //TODO: get all users from your identity domain and convert them to List<User>

  58:             return null;

  59:         } //TODO: you can add other search criteria that will allow extended support using the Activiti engine API

  60:     }

  61:  

  62:     @Override

  63:     public long findUserCountByQueryCriteria(Object query) {

  64:         return findUserByQueryCriteria(query, null).size();

  65:     }

  66:  

  67:     @Override

  68:     public Boolean checkPassword(String userId, String password) {

  69:         //TODO: check the password in your domain and return the appropriate boolean

  70:         return false;

  71:     }

  72: }
The only thing left to do is to inject (using Spring) our classes to the Activity engine configuration. We will need to inject MyConnectionParams as well as the factories themselves. Find your configuration file and add:
   1: <?xml version="1.0" encoding="UTF-8"?>

   2: <beans xmlns="http://www.springframework.org/schema/beans"

   3:     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   4:     xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

   5:  

   6:     <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">

   7:  

   8:         ...

   9:         <property name="customSessionFactories">

  10:             <list>

  11:                 <bean

  12:                     class="my.great.company.com.businessprocessengine.identityservice.MyUserManagerFactory">

  13:                     <constructor-arg ref="MyConnectionParams" />

  14:                 </bean>

  15:                 <bean

  16:                     class="my.great.company.com.businessprocessengine.identityservice.MyGroupManagerFactory">

  17:                     <constructor-arg ref="MyConnectionParams" />

  18:                 </bean>

  19:             </list>

  20:         </property>

  21:         ...

  22:     </bean>

  23:    

  24:     <bean id="MyConnectionParams"

  25:         class="my.great.company.com.businessprocessengine.identityservice.MyConnectionParams">

  26:     </bean>

  27:     ...

  28:  

  29: </beans>


That’s it. You can now use your own users and groups to create processes, deploy them, login to the Activiti Explorer or Rest API, etc.

So, here are a few extra pointers about Activiti Identity:
  • First of all there are 3 predefined group types:
    • activity-role
    • assignment
    • user
  • Now, no one tells you this, but if you are using Activiti Explorer, and you want your user to have admin privileges (i.e. be able to see the 'manage' tab), then the user's group type must be 'security-role' and the user's group name must be hard coded 'admin'. This means you will need to create some special code hacking for special users to use the Activiti Explorer.
  • Lets say you created a process that has a user task with a candidate group option (denote said group as X). NOTE: If you start a process instance, than only if the type of the group X is 'assignment' then users that belong to X will be able to claim the task. Otherwise, the user will not be able to claim the task at all.