In every serious project that we develop, there should be some kind of user provisioning [8] 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 [9], with its well-thought-out architecture, is a good example of a provisioning toolkit, and OpenSSO [10] 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:
OpenPTK lets developers have a unified API for user
provisioning, whether the users' information are stored in a
directory
service [11] 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 CRUD [12]
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
LDAP [13] (SPML [14]), 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 [15] portlet, a JAX-RPS web service endpoint, and a JSP tag library, in addition to APIs provided for direct interaction with the framework.
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:
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
Provisioning, as a enabler and accelerator for an IT system, is useful for:
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.
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();
}
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 [16] shows how you can access the OpenPTK code from your REST endpoint in order to perform any kind of user provisioning.
Links:
[1] http://www.java.net/author/masoud-kalali
[2] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit
[3] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#introduction-to-openptk
[4] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#openptk-architecture
[5] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#openptk-use-cases
[6] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#extending-openptk
[7] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#conclusion
[8] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#resources
[9] https://openptk.dev.java.net
[10] http://opensso.dev.java.net
[11] http://en.wikipedia.org/wiki/Directory_service
[12] http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
[13] http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol
[14] http://en.wikipedia.org/wiki/SPML
[15] http://jcp.org/en/jsr/detail?id=168
[16] http://www.java.net/article/2008/03/24/extending-openptk-user-provisioning-toolkit#first-sample