Sunday, August 23. 2015
SPMLv2 - Part I: DSML profile
Today's entry is about another standard: Service Provisioning Markup Language (SPML) Version 2. It is the only standard I know that models how to communicate with a provisioning system. SPML is almost unknown and rarely implemented by applications although it was defined long time ago. The standard is an XML-based framework, developed by OASIS, for exchanging any resource information (users, groups, containers,...) between cooperating organizations. If this framework finally got some traction it would be an excellent new for anyone related with provisioning or identity solutions. This series is my modest contribution to help spreading this unknown standard.
The framework defines several operations separated in capability groups. The core capability includes the functions listTargets, add, lookup, modify and delete. Over this basic CRUD functionality some other capabilities can be exposed by the server: async (asynchronous execution), batch (multiple operations sent in the same request), password (password change, reset and so on), search (searching over the objects), suspend (enables / disables users), updates (obtaining changed records) and custom capability (custom extensions). As you see SPML is thought to be open and extensible, for this reason, it defines two profiles that controls the schema of the interchanged data.
The DSML schema as defined in the SPMLv2 DSMLv2 Profile. In this profile all the resources are managed as a bunch of attributes similar to an LDAP server.
- The XML Schema as defined in the SPMLv2 XSD Profile. In this profile all the interchanged format is XML data formatted using an XSD schema file which is exposed in the listTargets operation.
A SPML server can define several targets, each target should use one of the previous two profiles. This first entry is going to show the DSML profile. One of the problems of SPML is the absence of good implementations in any language (you know, if the standard is not used, there are no implementations). The old Sun openspml is the only library that handles SPML in Java. The project only covers the DSML profile and it is not maintained at all since Oracle acquisition. In spite of these disadvantages I am going to use the library to implement a sample server and client in order to show how SPML works. Only the core capabilities and synchronous execution will be examined.
My simple PoC uses SPML to provision a user inside a fake database application (just a simple table with a one to many relation to a role table). The tables that are provisioned were defined in MySQL like this:
CREATE TABLE USER (
USERNAME varchar(50) NOT NULL,
PASSWORD varchar(50) NOT NULL,
COMMON_NAME varchar(256) NOT NULL,
DESCRIPTION varchar(1024) DEFAULT NULL,
PRIMARY KEY (USERNAME)
);
CREATE TABLE USER_ROLES (
USERNAME varchar(50) NOT NULL,
ROLENAME varchar(50) NOT NULL,
PRIMARY KEY (USERNAME,ROLENAME),
FOREIGN KEY (USERNAME) REFERENCES USER(USERNAME)
);
Obviously a User POJO class and a UserManager were developed. A class that models the user tables in Java and the manager with CRUD operations.
The openspml library gives a Servlet (RPCRouterServlet) and a SOAP dispatcher (SPMLViaSoapDispatcher) that can be used to implement an empty SOAP/SPML server (SPMLv2 does not force any transport protocol, i.e., any protocol can be used to exchange SPML data, but SOAP is usually used cos it is easy to include SPML/XML messages inside a SOAP conversation). The Servlet receives a SPMLExecutor, which is an interface that just receives an SPML Request and returns a Response. The openspml library uses the command pattern which fits quite well in the SPML protocol (each operation uses a specific Request and Response object). My server implementation just manages a SimpleSPMLExecutor which receives the SPML requests, transforms to a intermediate User class, interacts with the ddbb with the UserManager and returns a SPML response.
Once the PoC is explained the different core methods will be shown.
ListTargets
The listTargets in SPML is intended to be the first request any client should send. The command is used to identify how many targets the server has and their schema definition. Using openspml library a listTargets is easily created:
Spml2Client client = new Spml2Client("http://localhost:8080/SpmlServlet/rpcrouter2");
ListTargetsRequest ltr = new ListTargetsRequest();
ltr.setRequestID("ltr1");
ltr.setProfile(new URI(XSDProfileRegistrar.PROFILE_URI_STRING));
ltr.setExecutionMode(ExecutionMode.SYNCHRONOUS);
ListTargetsResponse res = client.send(ltr);
My PoC supports both profiles (XSD and DSML) and the standard says that the server should just answer with the targets of the requested profile that was specified. So the implementation checks if the request asks for a specific profile and responds accordingly. The conversation is shown below:
<listTargetsRequest xmlns='urn:oasis:names:tc:SPML:2:0' requestID='ltr1' executionMode='synchronous' profile='urn:oasis:names:tc:SPML:2:0:DSML'/>
<listTargetsResponse xmlns='urn:oasis:names:tc:SPML:2:0' status='success' requestID='ltr1'>
<target targetID='ddbb-spml-dsml' profile='urn:oasis:names:tc:SPML:2:0:DSML'>
<schema>
<spmldsml:schema xmlns:spmldsml='urn:oasis:names:tc:SPML:2:0:DSML'>
<spmldsml:objectClassDefinition name='user'>
<spmldsml:memberAttributes>
<spmldsml:attributeDefinitionReference required='true' name='uid'/>
<spmldsml:attributeDefinitionReference required='true' name='password'/>
<spmldsml:attributeDefinitionReference required='true' name='cn'/>
<spmldsml:attributeDefinitionReference name='description'/>
<spmldsml:attributeDefinitionReference name='role'/>
</spmldsml:memberAttributes>
</spmldsml:objectClassDefinition>
<spmldsml:attributeDefinition description='User name' name='uid'/>
<spmldsml:attributeDefinition description='Password' name='password'/>
<spmldsml:attributeDefinition description='Common name' name='cn'/>
<spmldsml:attributeDefinition description='description' name='description'/>
<spmldsml:attributeDefinition description='roles (multiple)' name='role' multivalued='true'/>
</spmldsml:schema>
<supportedSchemaEntity entityName='user'/>
</schema>
<capabilities/>
</target>
</listTargetsResponse>
It is clear that the listTargets answers with the DSML target of the server. In the DSML profile this operation answers with the schema of the object classes and attributes managed in each target. In my PoC there is only one objectclass (user) which has five attributes (uid, password, cn, description and role). So the listTargets operations gives to the client enough information about the objects and the attributes the target manages.
Add
The add request performs the creation of an object, in this PoC a user in the database. The client part is just the construction of the add request, adding all the attributes of the user (as specified in the schema returned in the listTargets response) and sending it. The openspml library gives a generic Extensible class that can handle the DSML attributes.
AddRequest ar = new AddRequest();
ar.setRequestID("ar1");
ar.setExecutionMode(ExecutionMode.SYNCHRONOUS);
ar.setTargetId("ddbb-spml-dsml");
Extensible data = new Extensible();
data.addOpenContentElement(new DSMLAttr("uid", "ricky"));
data.addOpenContentElement(new DSMLAttr("objectclass", "user"));
data.addOpenContentElement(new DSMLAttr("password", "ricky"));
data.addOpenContentElement(new DSMLAttr("cn", "Ricardo Martin"));
data.addOpenContentElement(new DSMLAttr("description", "me"));
data.addOpenContentElement(new DSMLAttr("role",
new DSMLValue[]{new DSMLValue("Admin"), new DSMLValue("User")}));
ar.setData(data);
Response res = client.send(ar);
The server part recovers the data in DSML attributes and convert again into a User, then the user is saved into the tables using the manager. The conversation is as follows:
<addRequest xmlns='urn:oasis:names:tc:SPML:2:0' requestID='ar1' executionMode='synchronous' targetId='ddbb-spml-dsml' returnData='everything'>
<data>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='uid'>
<dsml:value>ricky</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='objectclass'>
<dsml:value>user</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='password'>
<dsml:value>ricky</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='cn'>
<dsml:value>Ricardo Martin</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='description'>
<dsml:value>me</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='role'>
<dsml:value>Admin</dsml:value>
<dsml:value>User</dsml:value>
</dsml:attr>
</data>
</addRequest>
<addResponse xmlns='urn:oasis:names:tc:SPML:2:0' status='success' requestID='ar1'>
<pso>
<psoID ID='ricky' targetID='ddbb-spml-dsml'/>
<data>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='uid'>
<dsml:value>ricky</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='password'>
<dsml:value>ricky</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='cn'>
<dsml:value>Ricardo Martin</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='description'>
<dsml:value>me</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='role'>
<dsml:value>User</dsml:value>
<dsml:value>Admin</dsml:value>
</dsml:attr>
</data>
</pso>
</addResponse>
The request just specifies in which target the user is going to be created and then all the data is specified as DSML attributes and values. The returnData attribute defines what data the response will include. Everything means thet the server should return all the data of the object after the creation. Inside SPML every object (pso) has a ID (psoID) which is used in all the following operations.
Lookup
The lookup operation is the method of reading a user inside a target. The data is returned inside the pso as it was done in the add request. The client lookup using openspml is easy to be done.
LookupRequest lr = new LookupRequest();
lr.setRequestID("lr1");
lr.setExecutionMode(ExecutionMode.SYNCHRONOUS);
PSOIdentifier psoId = new PSOIdentifier("ricky", null, "ddbb-spml-dsml");
lr.setPsoID(psoId);
Response res = client.send(lr);
The request specifies which object is requested (ID and the target are specified, the second argument is used to define a container, some objects in SPML can act as containers, folders where other objects can be created like in a File System or in an LDAP server). By default the return data is everything, so the pso is returned exactly as in the add operation.
<lookupRequest xmlns='urn:oasis:names:tc:SPML:2:0' requestID='lr1' executionMode='synchronous' returnData='everything'>
<psoID ID='ricky' targetID='ddbb-spml-dsml'/>
</lookupRequest>
<lookupResponse xmlns='urn:oasis:names:tc:SPML:2:0' status='success' requestID='lr1'>
<pso>
<psoID ID='ricky' targetID='ddbb-spml-dsml'/>
<data>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='uid'>
<dsml:value>ricky</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='cn'>
<dsml:value>Ricardo Martin</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='description'>
<dsml:value>me</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='role'>
<dsml:value>User</dsml:value>
<dsml:value>Admin</dsml:value>
</dsml:attr>
</data>
</pso>
</lookupResponse>
Modify
The modify request let us change or update any resource in the target server. In DSML profile some attribute values can be added, deleted or replaced (very similar to an LDAP server). With openspml the implementation is very similar to the add request but sending all the modifications you want.
ModifyRequest mr = new ModifyRequest();
mr.setRequestID("mr1");
mr.setExecutionMode(ExecutionMode.SYNCHRONOUS);
PSOIdentifier psoId = new PSOIdentifier("ricky", null, "ddbb-spml-dsml");
mr.setPsoID(psoId);
Modification wrapper = new Modification();
// delete description attribute
DSMLModification dsmlmod = new DSMLModification("description", "me", ModificationMode.DELETE);
wrapper.addOpenContentElement(dsmlmod);
// replace the password attribute
dsmlmod = new DSMLModification("password", "ricky123", ModificationMode.REPLACE);
wrapper.addOpenContentElement(dsmlmod);
// remove Admin role
dsmlmod = new DSMLModification("role", "Admin", ModificationMode.DELETE);
wrapper.addOpenContentElement(dsmlmod);
// Add Test role
dsmlmod = new DSMLModification("role", "Test", ModificationMode.ADD);
wrapper.addOpenContentElement(dsmlmod);
mr.addModification(wrapper);
Response res = client.send(mr);
The conversation is like this:
<modifyRequest xmlns='urn:oasis:names:tc:SPML:2:0' requestID='mr1' executionMode='synchronous' returnData='everything'>
<psoID ID='ricky' targetID='ddbb-spml-dsml'/>
<modification>
<dsml:modification xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='description' operation='delete'>
<dsml:value>me</dsml:value>
</dsml:modification>
<dsml:modification xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='password' operation='replace'>
<dsml:value>ricky123</dsml:value>
</dsml:modification>
<dsml:modification xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='role' operation='delete'>
<dsml:value>Admin</dsml:value>
</dsml:modification>
<dsml:modification xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='role' operation='add'>
<dsml:value>Test</dsml:value>
</dsml:modification>
</modification>
</modifyRequest>
<modifyResponse xmlns='urn:oasis:names:tc:SPML:2:0' status='success' requestID='mr1'>
<pso>
<psoID ID='ricky' targetID='ddbb-spml-dsml'/>
<data>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='uid'>
<dsml:value>ricky</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='password'>
<dsml:value>ricky123</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='cn'>
<dsml:value>Ricardo Martin</dsml:value>
</dsml:attr>
<dsml:attr xmlns:dsml='urn:oasis:names:tc:DSML:2:0:core' name='role'>
<dsml:value>User</dsml:value>
<dsml:value>Test</dsml:value>
</dsml:attr>
</data>
</pso>
</modifyResponse>
The modify request contains several DSML modifications (I have sent four different modified attributes in the previous example) to a specified psoID user. The response (just like add or lookup operations) contains all the data of the user as everything was set as return data.
Delete
The last delete operation needs the psoID of the object to be deleted.
DeleteRequest dr = new DeleteRequest();
dr.setRequestID("dr1");
dr.setExecutionMode(ExecutionMode.SYNCHRONOUS);
PSOIdentifier psoId = new PSOIdentifier("ricky", null, "ddbb-spml-dsml");
dr.setPsoID(psoId);
Response res = client.send(dr);
And the conversation is the following:
<deleteRequest xmlns='urn:oasis:names:tc:SPML:2:0' requestID='dr1' executionMode='synchronous' recursive='false'>
<psoID ID='ricky' targetID='ddbb-spml-dsml'/>
</deleteRequest>
<deleteResponse xmlns='urn:oasis:names:tc:SPML:2:0' status='success' requestID='dr1'/>
This entry is a little introduction about a rare provisioning standard called SPMLv2. This standard defines an interesting framework to provision any object or piece of data or information inside an application. Like any standard it gives the ability to separate the client and the server (any SPML compliant server should work with a compatible client and vice-versa). It would be incredible to deploy your automatic provisioning system independently of the chosen software (it is a quite ambitious idea but a provisioning standard like this one is always needed as a first step). Until now SPML is uncommon and rarely implemented by provisioning or identity solutions. Consequently there are no production level SPML implementations in any of the typical development languages. For example in Java only openspml library exists, which is an old try developed by Sun Microsystems that only covers the DSML profile and is currently unmaintained. This entry uses this outdated library to exemplify how the DSML profile works and how to perform basic CRUD operations with SPML/DSML. I am trying to extend the openspml library to cover the XSD profile in order to also show both profiles. You can expect a second part in the series if I can get it working. I will link the sources then cos now it is a bit messy and not complete.
Be standard my friend!
Comments