Identity connector for Alfresco
In this article we are going to explain how to connect the Alfresco system to our Identity Manager CzechIdM. First of all, we are going to design and implement a special Identity connector. However, we are not going to pay any attention to details of the implementation itself as we did with our Universal SSH connector. Let’s look at the problem from another point of view: We’ll take a look at what we usually do when a customer requests a new system be connected to the CzechIdM, and then we’ll explain what we actually did in this particular case.
What is Alfresco?
The Alfresco Community, version 3.4.0, is the particular system to be connected. We do not know much about this system, so let’s find out some more about it !
The expression „Alfresco“ is used for a set of systems developed by an American company of the same name. The set contains, for instance, the Alfresco Document Manager, the Alfresco Web Content Management or a company team-work support system and system for sharing data via social networks called Alfresco Share. All the systems can be downloaded from Alfresco websites. There are a few versions of Alfresco:
It is possible to download and install a 30-day trial version called Alfresco Enterprise. This version contains the Document Manager, the Web Content Manager and Alfresco Share. It has been tested and certified for all open-source and proprietary systems. You could expect full commercial support for it.
On the other hand, the Alfresco Community is a pure open-source system with no restrictions. Anyone can take part in development and so there is no oficial support. This version of Alfresco is determined for developers and administrators for non-critical environment usage. This is the reason why developers can modify the software for their own needs, but they cannot expect any support or certification from Alfresco or its partners.
Alfresco also runs in a cloud, so it is possible to test it without any installation using a one-day trial licence.
In the picture you can see a screenshot of an openly shared document in the Alfresco system. As you can also see, Alfresco supports office software, for example, OpenOffice. You could find out more about this complex system at Alfresco websites.
How to connect Alfresco to CzechIdM?
In the paragraph above, we learned what version of Alfresco the customer has and what it is used for. Now we must explain just why we should connect Alfresco to CzechIdM: we expect CzechIdM to manage Alfresco user accounts. Concerning design, the connector we would like to develop must implement methods for:
creating a new user account in the Alfresco system
modification of an existing account (modification of any particular user attributes)
removing an account from the system
blocking and unblocking an account
looking up an account by its unique identifier
listing all accounts in the system
This leads us to a question: How can we read t information about user accounts from Alfresco? And how can wewrite it into the system? The first simple idea is to use the database directly. There are quite a lot of databases supported by Alfresco (MySQL, PostgreSQL, Microsoft SQL, Oracle, …). If we work with the database directly, we will not have to implement a brand new connector and we can use one of those we already have (the Database Table connector or our Universal JDBC connector). However, the database data structure is not really clear. The database is designed to be able to not modify its current data structure during the further development of Alfresco. No table for user records exists, for example. User attributes are stored as so-called “extended” attributes. For example, if we decided to add a new use attribute, we would not have to modify the database. We can easily save the attribute as an extended attribute.
Web Services
Fortunately, there is another way we can go. The Alfresco system provides web services. We can create a client application to use these services. In fact, our connector will be just such an application. Alfresco supports SOAP web services and services based on RESTful. We studied both ways and this one seemed to be much easier than direct access to the database. Which web service did we use? Classic web services or RESTful? Actually, we used both of them in the end.
In the next part we are going to describe both technologies and explain how we used them to create our connector.
SOAP web services
SOAP (Simple Object Access Protocol) is a protocol specifying the format of data transferred between an implementation of the web service and its client (this could be another web service). Data has an XML structure, as you can see in the picture below. Messages are divided into SOAP parts. The first contains the message header and the body, the second one attachments.
There are a few important terms we should explain: UDDI and WSDL. UDDI (Universal Description, Discovery and Integration) is a register mechanism in which the web services are looked up. WSDL (Web Service Description Language) is a W3C standard for web service description. It is a XML document, in which all the methods provided by the given service are described (what functions they have, where they are stored, how to connect such a service). WSDL describes web services as a collection of final points, so-called ports. Such a port can be described as a remote interface of a service. WSDL defines ports in two ways:
abstract port (final point) description – a description of a service interface with no dependance on the technology used
specific port description – a binding of the abstract description to the real implementation and communication to a given protocol.
Let’s focus again on our task. We will not implement the web service itself, because for our purposes it is already implemented in Alfresco. The only thing we have to do is to implementits client for services: Authentication (a service for user authentication in Alfresco) and Administration (a user account management service). The Alfresco system provides SDK (Software Development Kit) for development of systems that cooperate with Alfresco. In this package there are also libraries for development of applications using Alfresco web services. The problem is, SDK needs Java 1.6 or higher, but we need the connector to be able to run in Java 1.5. this is the reason why we decided to write the necessary service classes ourselves.
The first step we have to do is to implement ports of Alfresco web services and other auxilliary classes described in WSDL. We will use Apache Axis for this purpose. It is an open-source web-service framework for Java implementation of the SOAP standard. Apache Axis can be integrated into Eclipse IDE and classed as according to what we need to be be generated from it. Another option is using the WSDL2JAVA tool. Alfresco demands that section “security” containing authentication details be present in the SOAP message header. When a user logs in, Alfresco sends an answer, which contains the username of the current user, his authentication ticket and session ID. This ticket must be in other SOAP messages in the “security” section. Moreover, every HTTP request must have the same session ID as the one we get during authentication. Our sollution is that the class connecting a user to Alfresco extends javax.security.auth.callback.CallbackHandler and implements a method called “handle”.
package eu.bcvsolutions.alfresco.utils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.rmi.RemoteException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.xml.rpc.ServiceException; import org.apache.axis.EngineConfiguration; import org.apache.axis.configuration.FileProvider; import org.apache.ws.security.WSPasswordCallback; import eu.bcvsolutions.alfresco.authentication.AuthenticationFault; import eu.bcvsolutions.alfresco.authentication.AuthenticationResult; public class AuthenticationUtils implements CallbackHandler { /** WS security information */ private static final String WS_SECURITY_INFO = "<deployment xmlns='http://xml.apache.org/axis/wsdd/' xmlns:java='http://xml.apache.org/axis/wsdd/providers/java'>" + " <transport name='http' pivot='java:org.apache.axis.transport.http.HTTPSender'/>" + " <globalConfiguration >" + " <requestFlow >" + " <handler type='java:org.apache.ws.axis.security.WSDoAllSender' >" + " <parameter name='action' value='UsernameToken Timestamp'/>" + " <parameter name='user' value='ticket'/>" + " <parameter name='passwordCallbackClass' value='eu.bcvsolutions.alfresco.utils.AuthenticationUtils'/>" + " <parameter name='passwordType' value='PasswordText'/>" + " </handler>" + " <handler name='cookieHandler' type='java:eu.bcvsolutions.alfresco.utils.CookieHandler' />" + " </requestFlow >" + " </globalConfiguration>" + "</deployment>"; /** * Start a session. * * @param username * @param password * @throws AuthenticationFault * @throws ServiceException */ public static void startSession(String username, String password) throws AuthenticationFault, ServiceException { // Implementation. } // ... /** * The implementation of the passwrod call back used by the WS Security. * * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[]) */ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof WSPasswordCallback) { WSPasswordCallback pc = (WSPasswordCallback)callbacks[i]; String ticket = AuthenticationUtils.getTicket(); if (ticket == null) { throw new RuntimeException("Ticket could not be found when calling callback handler."); } pc.setPassword(ticket); } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } } }
In the variable called WS_SECURITY_INFO we store the configuration for the “security” part of SOAP messages. As a username we transfer the given Alfresco ticket. The password CallbackClass parameter refers to the class, where the handler adding the ticket to SOAP messages is implemented. In our case it is the class AuthenticationUtils and the method called “handle”. In WS_SECURITY_INFO there is also a reference to a class which checksthat the same HTTP session ID is always transferred.
For most actions, SOAP web services are used in our connector. However, we still have a problem as to how to block and unblock an Alfresco user account. In this case it seems much easier to use RESTful.
RESTful
Up until now when we said “a web service”, we have meant SOAP WS., There is, however, anothertechnology. SOAP WS follow the RPC principle (Remote Procedure Call). On the other hand, REST (REpresentational State Transfer) services are resource oriented. The REST architecture accesses the data directly via HTTP protocol (or another protocol can be used). There are four basic methods implemented: Create, Retrieve, Update and Deleted (CRUD). Every one of them uses its own HTTP method: Create uses the POST method, Retrive the GET method, Update the PUT method and Delete the DELETE method.
To be able to use RESTful web services and Alfresco in our connector, we have to use a reference RESTful implementation. We have decided to use Jersey.
The first step during the implementation itself is repeating the Alfresco user authentication. We can use the following URL request and fill in {username} and {password?}. Then we can send the request by the GET method to get the ticket we need to attach to our next requests.
http://virt2:8088/alfresco/service/api/login?u={username}&pw={password?}
This way of authentication does not seem to be suitable, because we need the password directly in the URL request. Instead of this we cand?send a POST request with the username and the password in an attached JSON object (we used the json-org.jar library to work with JSON). This is our method for the Alfresco login:
/** * Metoda pro přihlášení uživatele k webovým službám. * @param username uživatelské jméno * @param password heslo * @return Řetězec reprezentující autentizační ticket přihlášeného uživatele. */ public static String login(String username, String password) { // Vstup pro autentizaci JSONObject obj = null; try { obj = new JSONObject(); obj.put(WSConnectionConstant.PARAM_NAME_USERNAME, username); obj.put(WSConnectionConstant.PARAM_NAME_PASSWORD, password); } catch (JSONException e) { throw new RuntimeException(e); } // Spuštení webového skriptu WebResource source = Client.create().resource(getWebscriptAddress()).path(WSConnectionConstant.WEB_SCRIPT_LOGIN); ClientResponse response = source.accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, obj.toString()); // Výstup = autentizační ticket JSONObject out = null; String ticket = null; try { out = new JSONObject(response.getEntity(String.class)); ticket = out.getJSONObject(WSConnectionConstant.PARAM_NAME_DATA).getString(WSConnectionConstant.PARAM_NAME_TICKET); } catch (Exception e) { throw new RuntimeException(e); } return ticket; }
If the logging passes correctly, the method returns the authentication ticket. Now we can block or unblock an Alfresco user account simply by sending a HTTP PUT request with our authentication ticket appended to URL:
http://virt2:8088/alfresco/service/api/people/moris?alf_ticket=1234567890
The logout can be done in the same way by sending a HTTP DELETE request with the ticket in URL:
http://virt2:8088/alfresco/service/api/login/ticket/1234567890
Conclusion
In this article we have shown how we design and implement a connector for a specific system. We did not focus much on the implementation itself, but rather paid more attention to the technical scheme. The usage of web services seemed to be the best method in this case. We talked about Alfresco 3.4.0, but only a few details would have to be changed if we had switched to another version.
If you have any questions about Alfresco, connectors or the Identity Manager, you are welcome to send us an e-mail to info@bcvsolutions.eu.