Extending OpenPTK, the User Provisioning Toolkit Blog

Version 2


    In every serious project that we develop, there should be some kind of user provisioningin place. This provisioning is in addition to usual authentication and authorization processes. Provisioning is all about managing a user identity lifecycle across the enterprise. Operations like creating a user, deleting a user, editing user information, resetting a user's password, etc. are some of the operations that form user provisioning by provisioning.

    While enough core functionalities and third-party frameworks are available in order to facilitate authentication and authorization, there are a very few identity provisioning toolkits around. OpenPTK, with its well-thought-out architecture, is a good example of a provisioning toolkit, and OpenSSO is a good example of a product that facilitates user authentication and authorization.

    In this article, we will go through following steps in order to introduce OpenPTK:

    • Introduction to OpenPTK
    • OpenPTK architecture
    • OpenPTK use cases
    • Extending OpenPTK

    Introduction to OpenPTK

    OpenPTK lets developers have a unified API for user provisioning, whether the users' information are stored in a directory service or in an XML file. By using OpenPTK, developers will not have to dig into every user's information repository, but rather can concentrate on utilizing a well-defined set of APIs for interaction with a user-provisioning framework that already knows several kinds of user information repositories. At a later time, if any of the required user stores is not supported by OpenPTK, the developer can implement the required set of interfaces in order to provide the framework with a channel of communication with this new user repository. By using OpenPTK developers can perform CRUDoperations on the user repository in addition to utilizing its password management functionalities like changing password, reseting password, and password recovery. The current version of OpenPTK supports LDAP(SPML), JDBC, and Sun Identity Manager as user information repositories.

    OpenPTK provides some front-end facilities for framework users in order to ease their access to framework functionalities, which lead to interaction with user repositories. Currently provided front ends include a JSR-168 portlet, a JAX-RPS web service endpoint, and a JSP tag library, in addition to APIs provided for direct interaction with the framework.

    OpenPTK Architecture

    As I mentioned, OpenPTK is well-architected and it has easy-to-extend and easy-to-customize foundation classes and interfaces; a goal of the OpenPTK project is to unify user provisioning from the developer's point of view and handle a variety of interaction mechanisms with user information repositories underneath. The following three sequential layers form OpenPTK:

    • OpenPTK consumer tier
    • OpenPTK service tier
    • OpenPTK core framework

    The purpose of the components that reside in the consumer tier is to ease accessing the framework functionalities for different kinds of applications and clients. Components that reside in this tier do not implement a specific interface or adhere to some rules defined by the framework; instead, they are designed and implemented to ease development of different kinds of applications that need to interact with the OpenPTK core framework without involving the provided Java APIs.

    OpenPTK tag libraries, the OpenPTK web service endpoint, and the OpenPTK JSR 168 portlet are the current components implemented to ease development of applications that require user provisioning. In addition to Java applications with direct access to the OpenPTK API, web applications, portal systems, and web-service-based applications that need to have user provisioning available to end users or the administrator of the system can benefit from current consumer tier components. Each of the above components fully supports the described user provisioning functionalities and internally uses OpenPTK Java APIs to perform these tasks. The OpenPTK Java APIs, which are the interaction point of the framework with consumers, reside in the org.openptk.provision.*packages.

    The OpenPTK service tier contains components that communicate with different types of user repositories like JDBC or LDAP. This tier is where developers usually focus in order to add support for a new type of user information repository. You can add support for other kinds of user information repositories, like an XML file containing user information, simply by developing a newService, and OpenPTK will take care of communicating with this new service when required. OpenPTK uses a configuration file that describes all available services, along with other details that provide service with required configuration parameters. Generally, each Service has two parts; the first part is responsible for performing the CRUD operations. This part must extend the abstract classorg.openptk.provision.spi.Service and implementorg.openptk.provision.spi.ServiceIF. Providing these classes requires you to write the methods that insert a user into your repository, delete a user, edit a user, etc.

    Sometimes consumers need to find a user or a set of users by querying the user repository, so there should be a standard way for the framework to query the back ends, and as all back ends do not have the same querying mechanism, a converter is required to convert framework-standard queries to back-end-specific ones. OpenPTK provides an interface and an abstract class that, when implemented, lets the framework query all user repositories in one unified way. The query converter should extend theorg.openptk.provision.spi.QueryConverter abstract class and org.openptk.provision.spi.QueryConverterIFis the interface that the query converter must implement in order to let the framework load it when a user or service needs to perform a query.

    The OpenPTK core framework's responsibility is bridging the consumer and service tiers. In order to perform this task, the core framework needs to be configured with the appropriateContext that it should use. The first step to use OpenPTK for user provisioning is to load this configuration file. The configuration file contains descriptions ofService, Context, Subjects,Loggers, etc. Context is the element that we use to interconnect specific Services,Subjects, Loggers, and so on, and our access to the user repository will go though theContext that we select. Figure 1 briefly illustrates the OpenPTK architecture.

    Figure 1. A brief representation of OpenPTK architecture

    OpenPTK Use Cases

    Provisioning, as a enabler and accelerator for an IT system, is useful for:

    • VoIP service providers
    • ERP system providers
    • Internet service providers
    • Custom application development
    • Legacy application integration

    Using a provisioning subsystem can reduce the effort that developers need to put into user management. Usually we have several identity repositories across the enterprise, and a well implemented user provisioning allows the developer to have one central point of identity management, which reduces the risk of programming mistakes or human user mistakes. By having one central point for user identity management, you can apply all rules for user management in one single point instead of applying them in several parts of the enterprise-wide system.

    Extending OpenPTK

    As you have seen, OpenPTK can be extended to support new user repositories by adding new services in its service tier. Before we look into extending OpenPTK, we should see how we can use it. Thefollowing sample codeshows how we can create a new user. The configuration file name isopenptk.xml and the context that we use to interact with the user repository is named sample-xml-store-context. The sample code snippet will store a user's information into a repository without knowing what the repository is or what kind of structure it has. The repository is determined by the context that we use, so changing the context that we use can change the repository that we interact with.

    try { Configuration conf = new Configuration("openptk.xml"); SubjectIF subject = conf.getContextSubject("sample-xml-store-context"); Input input = new Input(); Output output = null; input.addAttribute("userid", "Jack@ctu.com"); input.addAttribute("firstname", "Jack"); input.addAttribute("lastname", "Bauer"); input.addAttribute("password", "mypassword"); output = subject.doCreate(input); } catch (ProvisionException ex) { System.out.println("Operation failed" + ex.getMessage()); } 

    Input and Output are two classes that consumers usually use to send required data to a service or get a result back from the service. However, the framework core will add some more information in order to allow the service to perform the requested operation efficiently. As you can see, before we perform any action we should load a configuration file that contains all configuration-related information mentioned before. The default name for the configuration file is openptk.xml; the framework loads when we call the no-argument constructor ofConfiguration. In the second line we try to use aContext named sample-xml-store-context. The description for sample-xml-store-context is as follows:

    <Context id="sample-xml-store-context"> <Subject id="Person"/> <Service id="xml-store"> <Properties> <Property name="filepath" value="/opt/openptk-sample/storage.xml"/> </Properties> </Service> <Query type="EQ" name="userid" value="10459845"/> </Context> 

    This Context is defined inside aContexts element that can contain severalContext tags. As you can see, Contextuses a Context, which is defined under thexml-store ID. The defined properties are what theContext will pick when the framework initializes the service. Each Context can have as many initialization properties as needed. For example, a JDBC service can havejdbcurl, username, password,driver-class, etc. Finally, we can determine a default querying type that can be used when we are using the query's no-argument constructor. Two other attributes define which attribute of the subject should be used for querying and the value of this attribute in candidate entities. The above snippet shows how we should assign a Context toContext, while the definition of Contextitself looks like:

    <Service id="xml-store" classname="org.openptk.provision.spi.XmlStore" description="A sample Service for managing XML identity storage" sort="userid"> <Properties> <Property name="filepath" value="/opt/openptk-sample/storage.xml"/> </Properties> <Operations> <Operation type="create"/> <Operation type="read"/> <Operation type="update"/> <Operation type="delete"/> <Operation type="search"/> </Operations> <Attributes> <Attribute id="userid" servicename="userid"/> <Attribute id="firstname" servicename="givenName"/> <Attribute id="lastname" servicename="lastname" required="true"/> <Attribute id="password" servicename="password" required="true"/> </Attributes> </Service> 

    The definition can include required properties with default values, operations that the service implements and supports, and finally a mapping between Subject attributes and equivalent Context attributes with necessary constraints. Context attributes' names are names that each attribute value will store under that name in the identity information repository. By using this mapping mechanism we can separate the attribute names that we use in the consumer tier from the real attribute names that back end uses to store that attribute's value.

    The defined Context uses a Subject, which has a unique ID named Person. What we define in the Subject tag reflects what attributes each subject should have, how these attributes should be treated (mandatory, optional, possible constraint, type, etc.), how these attributes should be passed to CRUD operations, how they should be transformed, and so on. The following sample code shows how aSubject can be defined. For simplicity, thisSubject only contains one attribute.

    <Subject id="Person" key="userid" password="password" classname="org.openptk.provision.api.Person"> <Attributes> <Attribute id="fullname"> <Transformations> <Transform type="toService" useexisting="true" classname="org.openptk.provision.transform.ConcatStrings"> <Operations> <Operation type="create"/> <Operation type="update"/> </Operations> <Arguments> <Argument name="arg1" arg="attribute" value="firstname"/> <Argument name="arg2" arg="literal" value=" "/> <Argument name="arg3" arg="attribute" value="lastname"/> </Arguments> </Transform> <Transform type="toFramework" useexisting="true" classname="org.openptk.provision.transform.ConcatStrings"> <Operations> <Operation type="read"/> <Operation type="search"/> </Operations> <Arguments> <Argument name="arg1" arg="attribute" value="firstname"/> <Argument name="arg2" arg="literal" value=" "/> <Argument name="arg3" arg="attribute" value="lastname"/> </Arguments> </Transform> </Transformations> </Attribute> </Attributes> </Subject> 

    In the first place, we have a Subject with a unique identifier attribute named userid, itspassword attribute, and classname. Thekey attribute is an attribute that should be defined in a similar way to how fullname is defined. Theclassname attribute points to the name of a fully qualified class that extendsorg.openptk.provision.api.Subject and implementsorg.openptk.provision.api.SubjectIF. Having this option to use a custom subject class lets us have more control over CRUD operation on subjects when they are performed. Thefullname attribute is composed offirstname, a space character, andlastname. Transformation definition can help us to get something out of the current attribute by performing a custom transformation on attributes before sending the attribute toContext, or before we deliver an attribute to framework when we take it from the service. There are several default transformations already included in OpenPTK, such asorg.openptk.provision.transform.ConcatString. An OpenPTK transformation class should extendorg.openptk.provision.transform.Transformation and implementorg.openptk.provision.transform.TransformationIF. Each transformation can get as many arguments as required, as arguments are accessible trough a map of argument name to value, inside theorg.openptk.provision.transform.TransformationIF.transform(...)method.

    Now It is time to take a look at theorg.openptk.provision.spi.Service abstract class andorg.openptk.provision.spi.ServiceIF interface, which are direct parents of each Context class.

    You might have asked yourself during course of the article, "Why are we extending an abstract class and implementing an interface for all mentioned parts of OpenPTK?" The reason is that there are several methods in the interface, and all of those methods usually have the same implementation across different extensions. So the OpenPTK developers decided to put those methods into an abstract class and let developers decide whether or not they want to change those functionalities that are usually the same. Important methods that should be implemented in each service are as follows:

    • void doCreate(RequestIF req, ResponseIF res)
    • void doRead(RequestIF req, ResponseIF res)
    • void doUpdate(RequestIF req, ResponseIF res)
    • void doDelete(RequestIF req, ResponseIF res)
    • void doSearch(RequestIF req, ResponseIF res)
    • void doPasswordChange(RequestIF req, ResponseIF res)
    • void doPasswordReset(RequestIF req, ResponseIF res)
    • void startup()
    • void shutdown()

    The method names explain what each method's expected functionality is, except for startup, inside of which we should initialize resources that we will use during the life of the service, such as database connection, etc. Similarly,shutdown will perform cleanup before the class becomes eligible for garbage collection.

    In the following sample code, we assume that we have a sample user repository similar to the following XML document.

    <persons> <person> <userid>Jack@ctu.com</userid> <name>Jack</name> <lastname>Bauer</lastname> <password>sample_pass</password> </person> </persons> 

    The following sample implementation of doRead anddoCreate shows how you can use theRequestIF and ResponseIF parameters.

    @Override public void doRead(RequestIF request, ResponseIF response) throws ServiceException { try { String keyFw = this.getContext().getDefinition().getKey(); String keySrvc = this.getSrvcName(keyFw); String xpathString; String keyValue = request.getSubject().getUniqueId(); List<Component> attributes = new LinkedList<Component>(); String[] attributeNames = {"userid", "givenname", "lastname", "password"}; //attribute IDs used by repository String UNIQIE_ID = "userid"; Component compnt; if (keyValue != null && keyValue.length() > 0) { if (keySrvc != null && keySrvc.length() > 0) { xpathString = "//person[@" + keySrvc + "=" + "'" + keyValue + "']"; response.setUniqueId(keyValue); } else { response.setStatus("Unique Id attribute name is not set"); return; } } else { response.setStatus("UniqueId value is not set"); return; } this.getProperty("filepath"); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); /* We have access to all properties that we defined in openptk.xml These attributes let us have access to configuration parameters that Service need to operate correctly. */ XPath xpath = XPathFactory.newInstance().newXPath(); Document persons = db.parse(this.getProperty("filepath")); Node person = (Node) xpath.evaluate(xpathString, persons, XPathConstants.NODE); /* Now we have the person with all of its attributes. we can send the attributes back by using the response object. however we can check the request object to see which attributes are requested and then only send back the attributes that are requested. */ for (int i = 0; i < attributeNames.length; i++) { String attributeName = attributeNames[i]; String attrXPath = "/" + attributeName+"/text()"; compnt = new Component(); String nodeValue = (String) xpath.evaluate(attrXPath, person, XPathConstants.STRING); if (UNIQIE_ID.equals(this.getSrvcName(attributeName))){//we are dealing with uniqueID compnt.setUniqueId(attributeName); } else { /*other attributes, we need to send back attributes with thier attributes id as * defined in openptk.xml configuration file, it is what getFwName do. */ BasicAttr attr = new BasicAttr(this.getFwName(attributeName), nodeValue); compnt.setAttribute(this.getFwName(attributeName), attr); } attributes.add(compnt);//adding component to the list of attributes } response.setResults(attributes); response.setStatus("Search Complete"); return; } catch (Exception ex) { //Handle the exceptions... } } 

    And the doCreate(), which is the method that should create a user in the repository, can be similar to:

    @Override public void doCreate(RequestIF request, ResponseIF response) throws ServiceException { Properties attribValues= new Properties(); Map<String, AttrIF> attributes=request.getSubject().getAttributes(); Iterator<AttrIF> attNames=attributes.values().iterator(); while(attNames.hasNext()){ AttrIF attrib = attNames.next(); if(attrib!=null){ attribValues.put(attrib.getServiceName(), attrib.getValue()); } } /*As you saw we get service name of each attribute in order to make sure that attribute *will be saved with the service dependent name. Here we have a list of all attributes and their values, just form the required structure and insert it into the storage */ response.setState(ResponseIF.STATE_SUCCESS); response.setStatus("Create operation complete"); /* We can send back some attributes to the framework when we finish the creating the subject, for example we may return back a sequence number indicating our user auto generated ID. */ Component copnt = new Component(); BasicAttr attr = new BasicAttr("sequenceID","database_returned_ID"); copnt.setAttribute("sequenceID",attr); List<Component> resultList = new ArrayList<Component>(1); resultList.add(copnt); response.setResults(resultList); return; } 

    Finally, we need to implement some querying mechanism to allow the framework to perform a typical search on top of our identity repository. As I mentioned, there is only one method that usually needs to be implemented; this method should be implemented in a way that satisfies the query type that we defined in the configuration file. There are more than ten types of querying, falling under two simple and complex categories. In the first case, a simple querying operand can be any of the Boolean operators; for example:like, begin with, end with,equal, not equal, and so on. A complex query is a conjunction of two simple or complex queries by theand or or operands. A sample implementation of the equal query type is as follows:

    @Override public Object convert() throws QueryException { StringBuffer buf = new StringBuffer(); String name = null; int type = 0; type = query.getType(); if (type == Query.TYPE_AND || type == Query.TYPE_OR) { // COMPLEX QUERY, We wave them for sake of simplicity } else{ // Simple query name = query.getServiceName();//Do we have a default query? if (name == null || name.length() < 1) { name = query.getName(); } switch (type) { case Query.TYPE_EQUALS: { buf.append("//person[@" + name + "='" + query.getValue() + "']"); break; } /* Generally the way to implement other query types is similar to Query.TYPE_EQUALS with some changes regarding the logic of selecting nodes. */ case Query.TYPE_BEGINSWITH: case Query.TYPE_CONTAINS: case Query.TYPE_ENDSWITH: case Query.TYPE_GREATER: case Query.TYPE_GREATER_EQ: case Query.TYPE_LESS: case Query.TYPE_LESS_EQ: case Query.TYPE_NOTEQUALS: case Query.TYPE_SOUNDSLIKE: } } return buf.toString(); } 


    As you can see, implementing a new service for OpenPTK is as easy as writing a very common piece of code in daily projects. It shows that the OpenPTK base has been well architected and developed to allow adding any kind of further extension to the framework.

    As I mentioned, the OpenPTK consumer tier has some components, like a provisioning portlet and JAX-RPC web service, that are built on top of OpenPTK's consumer Java API. You may need to have other means of communication with OpenPTK core from your application; for example, you can develop a REST endpoint on top of the OpenPTK Java APIs in order to allow your REST-friendly applications to perform user provisioning operations. The first example shows how you can access the OpenPTK code from your REST endpoint in order to perform any kind of user provisioning.