Skip to main content

Extending OpenPTK, the User Provisioning Toolkit

March 27, 2008

{cs.r.title}






In every serious project that we develop, there should be some
kind of user
"http://en.wikipedia.org/wiki/Provisioning">provisioning in
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 "http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD
operations 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 "http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol">
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 "http://jcp.org/en/jsr/detail?id=168">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 new
Service, 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 class
org.openptk.provision.spi.Service and implement
org.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 the org.openptk.provision.spi.QueryConverter
abstract class and
org.openptk.provision.spi.QueryConverterIF is 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 appropriate
Context that it should use. The first step to use OpenPTK
for user provisioning is to load this configuration file. The
configuration file contains descriptions of Service,
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 the Context 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. The
following sample code
shows how we can create a new user. The configuration file name is
openptk.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
of Configuration. In the second line we try to use a
Context 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 a Contexts element
that can contain several Context tags. As you can see,
Context uses a Context, which is defined under the
xml-store ID. The defined properties are what the
Context will pick when the framework initializes the
service. Each Context can have as many initialization
properties as needed. For example, a JDBC service can have
jdbcurl, 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 to Context, while the
definition of Context itself 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 a Subject can be defined. For
simplicity, this Subject 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, its password
attribute, and classname. The key attribute is an
attribute that should be defined in a similar way to how
fullname is defined. The classname attribute points
to the name of a fully qualified class that extends
org.openptk.provision.api.Subject and implements
org.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. The
fullname attribute is composed of firstname, a space
character, and lastname. Transformation definition can help
us to get something out of the current attribute by performing a
custom transformation on attributes before sending the attribute to
Context, 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 as
org.openptk.provision.transform.ConcatString. An
OpenPTK transformation class should extend
org.openptk.provision.transform.Transformation and
implement
org.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 the
org.openptk.provision.transform.TransformationIF.transform(...)
method.

Now It is time to take a look at the
org.openptk.provision.spi.Service abstract class and
org.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 and
doCreate shows how you can use the
RequestIF 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 the and 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();
   }

Conclusion

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.

Resources


width="1" height="1" border="0" alt=" " />
Masoud Kalali holds a software engineering degree and has been working on software development projects since 1998. He has experience with a variety of technologies (.Net, J2EE, CORBA, and COM+) on diverse platforms (Solaris, Linux, and Windows).
Related Topics >> Programming   |