Accessing a Drupal xmlrpc server using api key and session id from php – a working example you can actually use

So Drupal has this amazing services module that I have used and written on before in earlier versions. Now I am upgrading an important site to Drupal 6 which uses an xmlrpc server to receive published articles, and so an upgrade is in order. However, despite copious and varied handbook documentation for this module, I just could not find clear directions on getting this to work in a secure fashion without adopting relatively complex solutions. Others have also stated that they wanted to save people hours of futzing about but they leave out the all important sessionid, for example. Where's it supposed to go exactly? So here is a working example (attached as text) you can actually use, even from a Drupal 5 site. We will:

 

So Drupal has this amazing services module that I have used and written on before in earlier versions. Now I am upgrading an important site to Drupal 6 which uses an xmlrpc server to receive published articles, and so an upgrade is in order. However, despite copious and varied handbook documentation for this module, I just could not find clear directions on getting this to work in a secure fashion without adopting relatively complex solutions. Others have also stated that they wanted to save people hours of futzing about but they leave out the all important sessionid, for example. Where’s it supposed to go exactly? So here is a working example (attached as text) you can actually use, even from a Drupal 5 site. We will:

 

Install and configure the services module on the Drupal 6 server.

 

Now to install the new version! We are going with the stable and recommended 6.x-2.2 version of the services module. Install as you would any module (# drush dl services – yeah!). 

(A side note which you can probably safely skip: In my case, the server site had an old version of the services module installed, since there is no data involved and I have my custom services modules committed to a version control repository, the slickest thing I can do is to uninstall the prior services module and its database. So after downloading but not actually enabling the new version, I go to Administer > Site building > Modules and click on the uninstall tab and effectively rid my database of any knowledge of the previous Drupal 5 version of the services module, or at least I will be able to avoid “table already exists errors” when I enable the new version).

Which services modules to enable?

I enabled the following at this time, for the purposes of this example:

 

  • Services
  • Key authentication
  • XMLRPC Server
  • Node service
  • System service

What permissions to apply to which roles and why?

As explained in all to broad terms (although there is a nice example in Java if you want to use Java) in the Handbook’s Overview of Services, the basic workflow is:

session = system.connect();
user = user.login(session, 'MyName');
node.save(session, mynode);

Now, the most straightforward example for API authentication in Php ( http://drupal.org/node/762092 ) explains that “When using API key authentication, Services runs as the anonymous user, so you will need to modify the anonymous user’s permissions as necessary.” Of course, we can use the user.login service to make use of a special authenticated user in line with the best practices workflow. But let’s get it going as a reasonably secure solution with the session id, nonce (random string used only once)  and timestamp solution. Once that’s working we could refactor to add in user login.

So, we need to set the following permissions:

module permission role
node_service load node data anonymous
services administer services administrator
system_service [none for anonymous for this example] anonymous
user_service [none for anonymous for this example] anonymous
     

 

Create an application key on the server

First stop: Administer > Site building > Services. Here is a test page you can try out once we finish the configuration. Click on the Keys tab. As yet, of course, no API key has been generated. So click on the Create key tab. I entered the following:

Field Content Notes
Application title getnode can be anything of course
Allowed domain example.com type in your client domain,
in example.com format (no “http://…”
Method access node.get  

Click on the Create key button and of course take note of the key, for example: bf182cf462cfd7d8a43e29e737e9b19a

Configure Services Options

Now hit the Options tab and select “Key authentication” as the Authentication module. Use keys checkbox: selected. Leave 30 seconds as token expiration time for the nonce token. Use sessid: checked. Save options. And we are done.

Create a reusable snippet of code that connects to the server, gets a session id and then uses it together with the application key to view a node.

In another Drupal installation, type the following (attached as text file) into a Devel module Execute Php box:

// connect and get session id
$domain = 'example.com'; // the domain you are sending the request from, which is specified also in the api key.
$timestamp = (string) time();
$nonce = user_password();
$hash = hash_hmac('sha256', $timestamp .';'.$domain .';'. $nonce .';'.'system.connect', 'da8cdeba9ccb7298a1934b1e779f624f');  // replace last parameter with your api key
// replace server.com with the name of the drupal site that has the services module installed and configured as server
$xmlrpc_result = xmlrpc('http://server.com/services/xmlrpc', 'system.connect');
//dsm($xmlrpc_result);
$sid = $xmlrpc_result['sessid'];

$timestamp = (string) time();
$hash = hash_hmac('sha256', $timestamp .';'.$domain .';'. $nonce .';'.'node.get', 'da8cdeba9ccb7298a1934b1e779f624f');  // replace last parameter with your api key

// replace server.com with the name of the drupal site that has the services module installed and configured as server
$xmlrpc_result = xmlrpc('http://server.com/services/xmlrpc', 'node.get', $hash, $domain, $timestamp, $nonce, $sid, 12345, array());

if ($xmlrpc_result === FALSE) {
  print '<pre>' . print_r(xmlrpc_error(), TRUE) . '<pre>';
}
else {
  print '<pre>' . print_r($xmlrpc_result, TRUE) . '<pre>';
}

Execute, and you should see something like the following:

<pre>Array
(
    [nid] => 25606
    [type] => article
    [language] => es
    [uid] => 2
    [status] => 1
    [created] => 1281088749
    [changed] => 1281088749
    [comment] => 2
    [promote] => 0
    [moderate] => 0
    [sticky] => 0
    [tnid] => 0
    [translate] => 0
    [vid] => 33854
    [revision_uid] => 2
    [title] => Nos movilizamos al Congreso por el 82% móvil
    [body] => 
    [teaser] => 
    [log] => 
    [revision_timestamp] => 1281088749
    [format] => 0
    [name] => elviejo
    [picture] => 
    [data] => a:4:{s:7:"contact";i:1;s:14:"picture_delete";s:0:"";s:14:"picture_upload";s:0:"";s:14:"tinymce_status";s:5:"false";}
    [field_article_publication_issue] => Array
        (
            [0] => Array
                (
                    [nid] => 12345
                )

        )

....
<rest of the fields>
....

    [last_comment_timestamp] => 1281088749
    [last_comment_name] => 
    [comment_count] => 0
    [taxonomy] => Array
        (
        )

    [files] => Array
        (
        )

)
<pre>