Identity konektor pro Alfresco – SOAP nebo RESTful?

V tomto příspěvku si ukážeme, jakým způsobem lze připojit systém Alfresco k našemu Identity Manageru CzechIdM. Dopředu prozradím, že si k tomuto účelu navrhneme a naimplementujeme speciální Identity connector. Nebudeme si zde podrobně rozebírat implementaci konetoru jako například v případě Universal SSH konektoru, ale podíváme se na tento problém z poněkud jiného pohledu. Ukážeme si, jak postupujeme při zákazníkově požadavku na připojení nějakého nového systému k CzechIdM a jak jsme postupovali v tomto případě.

Co to je Alfresco?

V tomto případě je připojovaným systémem Alfresco Community, verze 3.4.0. Zatím toho moc o tomto systému nevíme, tak nezbývá nic jiného, než si zjistit více informací.

Alfresco je souhrnné označení pro řadu systémů vyvíjených stejně pojmenovanou americkou společností. Konkrétně se jedná například o systém pro správu dokumentů (Alfresco Document Management), systém pro správu webového obsahu (Alfresco Web Content Management) či systém pro podporu firemní spolupráce a sdílení obsahu prostřednictvím sociálních sítí (Alfresco Share). Systémy lze stáhnou ze stránek Alfresca v několika verzích.

Je možné si stáhnou a nainstalovat 30-ti denní TRIAL verzi Alfresco Enterprise, což je verze, která obsahuje systémy (moduly) pro správu dokumentů (DM), pro řízení webového obsahu (WCM) a pro správu firemní spolupráce (Share). Ta je plně otestována a certifikována pro open-source i proprietární background systémy. Samozřejmostí je plná komerční podpora.

Oproti tomu Alfresco Community je čistě open-source, bez jakéhokoliv omezení. Do vývoje se může připojit kdokoli, a proto zde není žádná oficiální podpora. Alfresco v této verzi je určeno pro vývojáře a administrátory  pro nasazení v tzv. nekritickém prostředí. Vývojáři si tedy mohou software upravit dle svých potřeb, ale za cenu ztráty podpory a certifikace od Alfresca a jeho partnerů.

Alfresco běží také pod cloudem, a tak je možné jej vyzkoušet bez jakékoliv instalace prostřednictvím jednodenní TRIAL licence.

Screenshot Alfresca s otevřenou prezentací.

Screenshot Alfresca

Na obrázku výše je sceenshot otevřeného sdíleného dokumentu v Alfrescu. Jak lze vidět, tak Alfresco má integrovanou podporu kancelářského softwaru, konkrétně se jedná o OpenOffice. Bližsí informace o tomto komplexním systému lze najít na stránkách výrobce.

Jak Alfresco připojit k CzechIdM?

Nyní již víme v jaké verzi se Alfresco u zákazníka nachází a k čemu obecně slouží. Dále si musíme stanovit, za jakým účelem budeme systém k CzechIdM připojovat. V tomto případě bude CzechIdM spravovat jeho uživatelské účty. Z hlediska návrhu tedy musí vyvíjený konektor implementovat metody pro:

  • vytvoření uživatelského účtu na systému Alfresco,
  • modifikaci účtu (modifikace atributů daného uživatele),
  • odstranění účtu ze systému,
  • zablokování/odblokování příslušného účtu,
  • vyhledání účtu dle jeho jedinečného identifikátoru a
  • vypsání všech účtů na systému Alfresco.
Tím se dostáváme k tomu, jak informace o uživatelích z Alfresca získávat a jak je tam naopak zapisovat. Prvním nápadem může být to, že si data budeme vytahovat přímo z databáze. Podporovaných databází pro Alfresco je poměrně mnoho (od MySQL a PostgreSQL, po Microsoft SQL a Oracle). Pokud bychom pracovali přímo s databází, tak nemusíme konektor speciálně pro Alfresco implementovat a můžeme použít některý ze stávajících konektorů pro databáze (např. Database Table connector nebo náš Universal JDBC connector). Jenže struktura dat v databázi není zrovna moc přehledná. Databáze je navržena s tím cílem, aby se nemusela příliš měnit její stávající struktura během vývoje Alfresca. Například neexistuje žádná pevná tabulka, kam by se ukládaly uživatelské záznamy. Uživatelské atributy se ukládají ve formě tzv. „extended“ atributů. Pokud bychom se například rozhodli, že chceme u uživatelů uvádět další atribut, tak nemusíme vůbec databázi měnit. Jednoduše si daný údaj uživatele uložíme jako extended atribut.

Webové služby

Naštěstí je zde ještě jiná cesta, kterou se můžeme vydat. Alfresco totiž poskytuje webové služby, pro které je možné napsat klientskou aplikaci, která je bude využívat. A touto aplikací bude v našem případě daný konektor. Alfresco podporuje jak SOAP webové služby, tak i služby postavené na RESTful. Po bližším prostudování poskytovaných metod jsme se rozhodli, že jejich použití bude rozhodně jednodušší než pracovat přímo s databází. A jakou formu webových služeb jsme nakonec použili? Klasické webové služby nebo RESTful? Nakonec obě dvě.

V následující části si obě technologie webových služeb zevrubně popíšeme a ukážeme si, kde a jak jsme je použili v daném konektoru.

SOAP webové služby

SOAP (Simple Object Access Protocol) je protokol specifikující formát přenášených dat mezi implementací webové služby a jejím klientem (což může být klidně nějaká jiná webová služba). Data mají strukturu XML dokumentu, viz. následující obrázek. Zprávy se rozdělují na jednu SOAP část, kde je případná hlavička zprávy a její tělo, a případné přílohy.

Struktura SOAP zprávy

Struktura SOAP zprávy, zdroj dokumentace Java EE.

Dalšími důležitými pojmy jsou UDDI a WSDL. UDDI (Universal Description, Discovery and Integration) je mechanismus registrů, ve kterých se vyhledávají webové služby. WSDL (Web Service Description Language) je standard W3C pro popis webových služeb. Jedná se o XML dokument, kde jsou popsány metody poskytované danou webovou službou (jaké to jsou funkce, kde jsou uloženy, jak je s danou službou možné navázat kontakt). WSDL popisuje webové služby jako kolekci koncových bodů, tzv. portů. Port si lze představit jako vzdálené rozhraní dané služby. WSDL pak charakterizuje porty dvěma způsoby, a to

  • abstraktní  popis portů (koncových bodů) – popis rozhraní služby bez ohledu na konkrétní použitou technologii, a
  • konkrétní popis portů – svázání abstraktního popisu k reálné implementaci a komunikaci na určitý protokol.

 

Popis WSDL

Popis WSDL, zdroj Wikipedie.

Nyní se zase vraťme k naší úloze. My nebudeme implementovat samotnou webovou službu, protože ta je již pro naše účely standardně implementována v Alfrescu. Musíme tedy napsat pouze jejího klienta, a to pro služby Authentication (WS pro autentizaci uživatele k Alfrescu) a Administration (WS pro správu uživatelských účtů). Alfresco poskytuje SDK (Software Development Kit) pro vývoj aplikací spolupracujících s Alfrescem. V tomto balíku jsou také knihovny pro vývoj aplikací využívající webové služby Alfresca. Problém je však v tom, že SDK vyžaduje Javu 1.6 a vyšší, ale my požadujeme, aby konektor mohl pracovat také pod Javou 1.5. Z tohoto důvodu jsme se rozhodli, že potřebné servisní třídy si napíšeme sami.

Prvním krokem, co musíme udělat, je vygenerovat implementaci jednotlivých portů webových služeb Alfresca a dalších pomocných tříd popsaných ve WSDL. K tomuto účelu použijeme Apach Axis, což je open-source web-service framework pro Javu implementující SOAP standard. Apache Axis je možné integrovat například do Eclipse IDE a potřebné třídy vygenerovat po zadání adresy WSDL přímo z něho, případně je možné použít nástroj WSDL2JAVA. Alfresco dále vyžaduje, aby v hlavičce SOAP zpráv byla sekce „security“, kde jsou uvedeny autentizační detaily. Po přihlášení uživatele totiž Alfresco odešle odpověď, ve které je uživatelské jméno přihlášeného uživatele, jeho autentizační ticket a session ID. Právě tento ticket musí být uložen v dalších SOAP zprávách v sekci „security“. Navíc každý další HTTP požadavek musí mít stejné session ID jako to, které jsme obdrželi při autentizaci. Toho všeho docílíme tak, že třída zajišťující přihlášení uživatele k Alfrescu bude dědit od třídy javax.security.auth.callback.CallbackHandler a bude implementovat navíc metodu 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");
          }
       }
    }   

}

V proměnné WS_SECURITY_INFO jsou uloženy konfigurační údaje pro „security“ část SOAP zpráv. Lze vidět, že pod uživatelským jménem se v této sekci bude předávat právě daný Alfresco ticket. Parametr passwordCallbackClass odkazuje na třídu, kde je implementován „handler“, který bude doplňovat do SOAP zpráv daný ticket. V našem případě to je právě výše vypsaná třída AuthenticationUtils a metoda handle. Ve WS_SECURITY_INFO je také uveden odkaz na třídu, která bude zajišťovat to, že se vždy budou předávat stejné HTTP sessionID.

SOAP webové služby jsou v konektoru použity pro většinu akcí. Problém je však s akcemi pro zablokování/odblokování uživatelského účtu na Alfrescu. A právě zde je možné použít RESTful, protože zde to je mnohem jednodušší.

RESTful

Doposud jsme pod termínem webové služby měli na mysli právě SOAP WS, ale je zde ještě poněkud jinak koncipovaná technologie. SOAP WS odpovídají principu RPC (Remote Procedure Call), tedy volání procedur. REST (REpresentational State Transfer) služby jsou oproti tomu orientované na „zdroje“. REST architektura tedy přistupuje přímo k datům, a to prostřednictvím HTTP protokolu (může však být použit i jiný protokol). Implementovány jsou čtyři základní metody Create, Retrieve, Update a Delete (CRUD). Každá z nich používá vlastní HTTP metodu, Create metodu POST, Retrieve metodu GET, Update metodu PUT a Delete metodu DELETE.

Zpátky k našemu konektoru. Abychom mohli použít RESTful webové služby Alfresca (zde označené jako webové skripty), tak musíme použít nějakou referenční RESTful implementaci. My jsme se rozhodli použít Jersey.

Prvním krokem při samotné implementaci je opět autentizace uživatele vůči Alfrescu. K tomu můžeme použít následující URL požadavek, kde místo {username} a {password?} doplníme odpovídající údaje a požadavek odešleme metodou GET (abychom získaly již dříve zmiňovaný ticket, který budeme přikládat k dalším požadavkům).

http://virt2:8088/alfresco/service/api/login?u={username}&pw={password?}

Tento způsob autentizace není ale příliš vhodný, protože posíláme heslo přímo v URL požadavku. Místo toho však můžeme poslat POST požadavek, kde uživatelské jméno a heslo není uvedeno v URL, ale v přiloženém JSON objektu (pozn. pro psáci s JSON objekty jsme použili knihovnu json-org.jar). Metoda pro přihlášení k Alfrescu poté bude vypadat nějak takto.

        /**
	 * 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;
	}

Pokud přihlášení proběhne v pořádku, tak nám metoda navrátí autentizační ticket. Nyní můžeme uživatelský účet na Alfrescu jednoduše zablokovat/odblokovat odesláním příslušného HTTP PUT požadavku, ke kterému připojíme do URL daný ticket, např.

http://virt2:8088/alfresco/service/api/people/moris?alf_ticket=1234567890

Odhlášení uživatele proběhne zase tak, že se odešle příslušný HTTP DELETE požadavek, kde se do URL přidá ticket, např.

http://virt2:8088/alfresco/service/api/login/ticket/1234567890

Závěrem

V tomto příspěvku jsme si ukázali, jak postupujeme při návrhu a implementaci konektoru pro určitý koncový systém. Již jsme se nezabývali tolik problematikou implementace konektoru samotného, ale spíše technickým návrhem. V tomto případě jsme použili webové služby, jejichž použití se v tomto případě více než nabízelo. I když byl konektor navržen pro Alfresco ve verzi 3.4.0, tak případné změny pro jinou verzi jsou již drobností.

Pokud máte jakékoliv dotazy ohledně Alfresca, konektorů či Identity Managementu (případně i jakékoliv jiné dotazy), tak se nám neváhejte ozvat na mail info@bcvsolutions.eu.