Carsten Rhein, Drupal-Entwickler

Tutorial Services

Services Modul zur Kommunikation mit externen Anwendungen (Drupal 6.x)

Die Welt der modernen Komunikationsmittel vergrößert sich Tag für Tag. Sei es Facebook, Twitter, Google+. Jeder dieser Dienste bringt seine eigenen Schnittstellen (APIs) mit, um mit der Außenwelt zu kommunizieren. Einbindung in externe Websites, Apps bis hin zu komplexen Desktop-Anwendungen werden immer mehr zur Tagesordnung. Solch eine Schnittstelle kann auch von Drupal bereitgestellt werden.

Genauer gesagt ist sie sogar schon im Drupal-Core vorhanden. Dem aufmerksamen Nutzer ist eventuell eine ominöse Datei mit dem Namen xmlrpc.php im Root von Drupal aufgefallen, wusste aber nicht, was damit anzufangen ist. Diese Datei ist die Schnittstelle!

XML-RPC bedient sich dabei zweier alten Bekannten: HTTP zum Transport der Daten und XML zur Darstellung dieser. Siehe auch Wikipedia.

Nun kann also mit Drupal von extern interagiert werden. Aber wie macht man das denn nun? Wie in der Drupal-Welt üblich: 'There is a module for it!' und dieses Modul heißt Services. Services stellt sogenannte Services bereit ‐ wer hätte es gedacht ‐, welche diverse Funktionen bedienen. Beispiele sind: Login und Logout, Node laden und speichern, Views laden und einiges mehr.

Ziel und Rahmen dieses Tutorials

Im Folgenden werde ich nur auf die Nutzung der älteren Services-Version 2.4 mit Drupal 6.x eingehen. Ein weiterer Beitrag für die aktuellen Versionen Services 3.x mit Drupal 7.x folgt.

In Drupal sind bereits alle benötigten Funktionen enthalten, um ein einfaches Script als Beispiel zu generieren. Dieses kann zum Beispiel für die Kommunikation zwischen zwei separaten Drupal-Installationen genutzt werden.

Das Ergebnis dieses Tutorials ist ein kleines PHP-Script, welches mit Drupal-Bordmitteln zur Erzeugung des XML-RPC-Posts sich mit dem System verbindet, einen User einloggt und wieder abmeldet.

Module

Nach dem Download des Services-Modul stehen einige Submodule zur Verfügung. Auf jeden Fall benötigen wir das Haupt-Modul Services. Des Weiteren wollen wir eine Key-Authentifizierung durchführen. Dies bedeutet, dass nur Anwendungen, welche über diesen Key verfügen, Zugriff auf unser System erhalten. Also muss auch das Modul Key Authentication aktiviert werden. Alle weiteren Module stellen die bereits erwähnten Services da. Für dieses Tutorial benötigen wir das System Service und das User Service Modul.

Zusammengefasst werden also folgende Module aktiviert:

  • Services
  • Key Authentication
  • System Service
  • User Service

Konfiguration

Nach der Aktivierung dieser Module steht ein neuer Menüpunkt unter /admin/build/services zur Verfügung. Hier kann nun die Konfiguration des Moduls vorgenommen werden. Zunächst kümmern wir uns um die Einstellungen. Unter /admin/build/services/settings wählen wir nun Key Authentication als Authentifizierungsmodul aus. Des Weiteren werden die Häkchen für die Nutzung für Keys und Session ID gesetzt.

Key erstellen

Unter /admin/build/services/key befindet sich eine Auflistung aller angelegten Keys. Mit Klick auf create key bzw. unter /admin/build/services/keys/add kann ein neuer Schlüssel hinzugefügt werden. Hier muss ein String für die Domain eingetragen (im Beispiel mein-service) und die zur Verfügung zu stellenden Services ausgewählt werden. Für unser Beispiel benötigen wir folgende Services:

  • system.connect
  • user.login
  • user.logout

Nach Speichern des Formulars steht ein neuer Schlüssel zur Verfügung.

Das Script

Für unser einfaches Beispiel erzeugen wir eine neue Datei (im Beispiel service.php) im Drupal-Root, sodass wir sie zum Beispiel mit der URL meine-seite.local/service.php aufrufen können. Um die XML-RPC-Funktionen von Drupal nutzen zu können, müssen wir die common.inc von Drupal inkludieren, da dort die Funktion xmlrpc() definiert ist, welche uns einen XML-RPC POST aus übergebenen Argumenten baut und an den übergebenen Host postet.

Funktionen

<?php
include_once 'includes/common.inc';
?>

Die oben genannte Funktion xmlrpc() erwartet mehrere Argumente:

<?php
/**
* $host: Die Adresse, an welche das XML gesendet werden soll.
* $command: Der Service, welcher ausgeführt werden soll.
* $hash: Ein Hashwert, welcher über alle Parameter mit dem oben erstellten
Key berechnet wird (s.u.).

* $domain: Die oben angegebene Domain für den Key.
* $timestamp: Der Zeitstempel als String. Wird im Beispiel mit (string) time(); erzeugt.
* $nonce: Eine beliebige zufällige Zeichenkette, welche nur einmal verwendet werden darf.
Wird im Beispiel mit uiqid() erzeugt.

* $sessid: Wird nicht für system.connect, aber für alle anderen Services benötigt.
* $arg1 ... N: Weitere optionale Parameter wie Benutzername und Passwort.
*/
xmlrpc($host, $command, $hash, $domain, $timestamp, $nonce, $sessid, $arg1, $arg2 ... $argN);
?>

Der Hashwert $hash wird mit der php-Funktion hash_hamc() gebildet, wobei der sha256-Algorithmus genutzt werden muss. Die Parameter gleichen den oben beschriebenen bis auf $key:

<?php
/**
* $key: Der im Services-Modul erzeugte Key.
*/
$hash = hash_hmac('sha256', $timestamp.';'.$domain.';'.$nonce.';'.$command, $key);
?>

system.conect

Aus den oben genannten Funktionen folgt also folgendes Script für die Nutzung des Service system.connect:

<?php
include_once 'includes/common.inc';

$host = 'meine-seite.local/xmlrpc.php';
$command = 'system.connect';
$domain = 'mein-service';
$timestamp = (string) time();
$nonce= uniqid();
$key = 'Eine lange Zahlen- und Zeichenkette';
$hash = hash_hmac('sha256', $timestamp.';'.$domain.';'.$nonce.';'.$command, $key);

$result = xmlrpc($host, $command, $hash, $domain, $timestamp, $nonce);

print
'<pre>' . print_r($result) . '<pre>';
?>

Nach Ausführen dieses Scriptes durch Aufruf der URL meine-seite.local/service.php sollte in etwa Folgendes zu sehen sein:

Array
(
    [sessid] => 16160c355ce490a43397a3c58f6363fa
    [user] => Array
        (
            [uid] => 0
            [hostname] => 127.0.0.1
            [roles] => Array
                (
                    [1] => anonymous user
                )

            [session] =>
            [cache] => 0
        )

)

Kapselung in eine Funktion

Um alles ein wenig übersichtlicher zu gestalten, habe ich das ganze in eine eigene Funktion ausgelagert:

<?php
include_once 'includes/common.inc';

function
xmlrpc_execute($host, $domain, $key, $command, $sessid = NULL, $arg1 = NULL, $arg2 =NULL) {
 
 
$nonce = uniqid();
 
$timestamp = (string) time();

 
$hash = hash_hmac('sha256', $timestamp.';'.$domain.';'.$nonce.';'.$command, $key);

  return
xmlrpc($host, $command, $hash, $domain, $timestamp, $nonce, $sessid, $arg1, $arg2);
}

$host = 'meine-seite.local/xmlrpc.php';
$domain = 'mein-service';
$key = 'Eine lange Zahlen- und Zeichenkette';

$result = xmlrpc_execute($host, $domain, $key, 'system.connect');
print
'<pre>' . print_r($result, TRUE) . '<pre>';
?>

An- und Abmelden eines Benutzers

Durch den Service system.connect erhalten wir eine Session-ID, welche wir für den Login eines Benutzers benötigen. Des Weiteren müssen die Zugangsdaten übermittelt werden. Das Script wird also folgendermaßen erweitert:

<?php
$sessid
= $result['sessid];
$username= '
Benutzername';
$userpassword = '
Geheim';

$result = xmlrpc_execute($host, $domain, $key, '
user.login', $sessid, $username, $userpassword);
print '
<pre>' . print_r($result, TRUE) . '<pre>';
?>

Das Ergebnis sollte nun das vollständinge User-Array enthalten. Alle nun folgenden Aktionen müssen die für den User neu generierte Session-ID enthalten &dash; so auch der Logout.

<?php
$sessid
= $result['sessid'];

$result = xmlrpc_execute($host, $domain, $key, 'user.logout', $sessid);
print
'<pre>' . print_r($result, TRUE) . '<pre>';
?>

Der Rückgabewert bei erfolgreichem Logout ist einfach nur '1'.

Vollständiges Script

<?php
include_once 'includes/common.inc';

function
xmlrpc_execute($host, $domain, $key, $command, $sessid = NULL, $arg1 = NULL, $arg2 =NULL) {
 
 
$nonce = uniqid();
 
$timestamp = (string) time();

 
$hash = hash_hmac('sha256', $timestamp.';'.$domain.';'.$nonce.';'.$command, $key);

  return
xmlrpc($host, $command, $hash, $domain, $timestamp, $nonce, $sessid, $arg1, $arg2);
}

$host = 'meine-seite.local/xmlrpc.php';
$domain = 'mein-service';
$key = 'Eine lange Zahlen- und Zeichenkette';
$username = 'Benutzername';
$userpassword = 'Geheim';

$result = xmlrpc_execute($host, $domain, $key, 'system.connect');
print
'<pre>' . print_r($result, TRUE) . '<pre>';

$sessid = $result['sessid'];

$result = xmlrpc_execute($host, $domain, $key, 'user.login', $sessid, $username, $userpassword);
print
'<pre>' . print_r($result, TRUE) . '<pre>';

$sessid = $result['sessid'];

$result = xmlrpc_execute($host, $domain, $key, 'user.logout', $sessid);
print
'<pre>' . print_r($result, TRUE) . '<pre>';
?>

Anmerkung

Zu beachten ist, dass einige Services für ihre Nutzung Berechtigungen verlangen. Der User muss diese also erhalten. Des Weiteren müssen auch weitere Berechtigungen gesetzt sein, um zum Beispiel Nodes zu bearbeiten.

Fazit

Services ist ein Modul, welches uns erlaubt, mit externen Anwendungen zu kommunizieren. Dies wird in Zukunft immer wichtiger und mit diesem Modul möglich.

Ich mag's