Example: User login with API key using C#
The following example will explain how to connect to your Drupal site using the Services module with the usage of an API key and login a user. I will not go in great depth but provide an overview and enough basic information to get you started.
Assumptions
The following assumptions are made:
- A fully working Drupal 6 installation using Services 0.13 or above. (I have not tested it with previous releases, but it should be one containing the new API key handling.)
- Enough C# experience so I don't need to tell you which using statements you require.
Prerequisites
Since the .NET framework does not ship with a default way to handle XML-RPC we will be using an external library which can be found at http://www.xml-rpc.net/. Download the ZIP file and add the correct DLL file as a resource to your project. Read the README file for more information about the version you should use.
Helper Functions
We require some helper functions to help us converting and computing the required hashed data.
// Get the current Unix time stamp.
public string GetUnixTimestamp()
{
TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
return Convert.ToString(Convert.ToUInt64(ts.TotalSeconds));
}
// Similar to the 'user_password' function Drupal uses.
public string GetNonce(int length)
{
string allowedCharacters = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
StringBuilder password = new StringBuilder();
Random rand = new Random();
for (int i = 0; i < length; i++)
{
password.Append(allowedCharacters[rand.Next(0, (allowedCharacters.Length - 1))]);
}
return password.ToString();
}
// Compute the hash value.
public string GetHMAC(string message, string key)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
byte[] messageByte = encoding.GetBytes(message);
HMACSHA256 hmac = new HMACSHA256(keyByte);
byte[] hashMessageByte = hmac.ComputeHash(messageByte);
string sbinary = String.Empty;
for (int i = 0; i < hashMessageByte.Length; i++)
{
// Converting to hex, but using lowercase 'x' to get lowercase characters
sbinary += hashMessageByte[i].ToString("x2");
}
return sbinary;
}We also need two structs, one containing a field for every field in the user table and one existing of a string which shall contain the session ID and the Drupal user struct.
// Since we don't want an error to pop up if the returned Drupal user object contains a field which
// does not map to a field or struct has we need to add the MappingOption.
[XmlRpcMissingMapping(MappingAction.Ignore)]
public struct DrupalUser
{
// Fields
public string name;
public string email;
}
public struct Drupal
{
public string sessionid;
public DrupalUser user;
}We also need an interface which contains the list of functions we want to access.
[XmlRpcUrl("http://example.com/services/xmlrpc")] // Change this if you are not using Clean URL's!
public interface IServiceSystem : IXmlRpcProxy
{
[XmlRpcMethod("system.connect")]
Drupal Connect();
[XmlRpcMethod("user.login")]
Drupal Login(string hash, string timestamp, string domain, string nonce, string sessid, string username, string password);
}Main Program
static void Main(string[] args)
{
try
{
IServiceSystem iss = XmlRpcProxyGen.Create<IServiceSystem>();
Drupal cnct = iss.Connect();
string timestamp = GetUnixTimestamp();
string nonce = GetNonce(10);
string domain = "example.com";
string key = "your_api_key";
StringBuilder sb = new StringBuilder();
sb.Append(timestamp);
sb.Append(";");
sb.Append(domain);
sb.Append(";");
sb.Append(nonce);
sb.Append(";");
sb.Append("user.login");
string hash = GetHMAC(sb.ToString(), key);
Drupal lgn = iss.Login(hash, domain, timestamp, nonce, cr.sessid, "user", "password");
Console.WriteLine("username: {0}", lgn.user.name);
Console.WriteLine("timezone: {0}", lgn.user.timezone);
Console.WriteLine("email: {0}", lgn.user.mail);
Console.WriteLine("created: {0}", lgn.user.created);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Drupal Struct
If you go to the "Services" -> "Drupal.Connect" and click the "call method" button, you will see the available object properties. In the example here, the "Drupal" struct should at least be:
public struct Drupal{
public string sessid;
public DrupalUser user;
}
Thanks!
http://www.molinesoftware.com
Python implementation
Thanks for this, I found it after much hair-pulling and it steered me in the right direction. Here's a Python implementation:
<?php
// using php tags only for whitespace formatting.
#!/bin/python
"""
Working example of connecting and authenticating to the Drupal services module,
(Adapted from the C# example at <a href="http://drupal.org/node/308629" title="http://drupal.org/node/308629" rel="nofollow">http://drupal.org/node/308629</a>)
2009-04-28
andy.chase@proofgroup.com
Works against Drupal 6.10 and Services 6.x-0.13
"""
import xmlrpclib, time, hmac, string, random, hashlib, pprint
#Create a new instance of the XML-RPC server we want to connect to:
drupal = xmlrpclib.ServerProxy("http://server_hostname/services/xmlrpc")
#Call the services system.connect() method to establish a session:
session = drupal.system.connect()
#Set up items needed by the user.login() method:
timestamp = str(int(time.mktime(time.localtime())))
domain = "server_hostname"
#(Clever random string generation found in comments at <a href="http://ostas.blogspot.com/2006/12/python-generate-random-strings.html" title="http://ostas.blogspot.com/2006/12/python-generate-random-strings.html" rel="nofollow">http://ostas.blogspot.com/2006/12/python-generate-random-strings.html</a>)
nonce = "".join(random.sample(string.letters+string.digits, 10))
key = 'your_api_key'
user = 'your_username'
password = 'your_password'
hashMap = hmac.new(key, "%s;%s;%s;%s" % (timestamp, domain, nonce, "user.login"), hashlib.sha256)
try:
login = drupal.user.login(hashMap.hexdigest(), domain, timestamp, nonce, session['sessid'], user, password)
pprint.pprint(login)
except xmlrpclib.Fault, err:
print "A fault occurred"
print "Fault code: %d" % err.faultCode
print "Fault string: %s" % err.faultString
?>
(Sorry, the generic code formatter clobbers the whitespace in the try/except block - if you're copy/pasting this, be sure to add it back in.)I wrote some classes for
I wrote some classes for Drupal Services in Python which I pushed into github.
http://guaka.org/
Where are cr.sessid and the
Where are cr.sessid and the lgn.user.timezone, mail and created vairables declared?
1
First of all, you should use
First of all, you should use name 'sessid', instead 'sessionid' for Drupal struct, and then use this sessid, that Connect function returns, in Login function. so this cr.sessid is your cnct.sessid.
user.timezone as you can see is not in DrupalUser. So it'll be an error ...
Java Implementation
Here is my quick & dumb Java implementation, it needs Apache commons-codec and Apache xmlrpc-client libs to work.
I make it run with commons-codec-1.3 and xmlrpc-client-3.1.2
import java.net.URL;
import java.util.HashMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
public class TestXmlRpcAccess {
// Method names, don't touch this
private static final String USER_LOGIN_METHOD = "user.login";
private static final String SYSTEM_CONNECT_METHOD = "system.connect";
// Configuration info : adapt with to your stuff...
private static final String USER_LOGIN = "login";
private static final String USER_PASSWORD = "password";
private static final String SERVER_URL = "http://yourdomain/services/xmlrpc";
private static final String API_KEY = "your_api_key";
private static final String API_DOMAIN = "your_api_domain";
public static void main(String[] args) throws Throwable {
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL(SERVER_URL));
XmlRpcClient client = new XmlRpcClient();
client.setConfig(config);
Object[] params = new Object[] {};
HashMap<?, ?> sess = (HashMap<?, ?>) client.execute(SYSTEM_CONNECT_METHOD, params);
int nowValue = (int) (System.currentTimeMillis() / 1000);
String now = "" + nowValue;
String nonce = now;
String passwd = USER_PASSWORD;
String message = now + ";" + API_DOMAIN + ";" + nonce + ";" + USER_LOGIN_METHOD;
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec ks = new SecretKeySpec(API_KEY.getBytes(), mac.getAlgorithm());
mac.init(ks);
String hash = new String(Hex.encodeHex((mac.doFinal(message.getBytes()))));
params = new Object[] { hash, API_DOMAIN, now, nonce, sess.get("sessid"), USER_LOGIN, passwd };
Object user = client.execute(USER_LOGIN_METHOD, params);
System.out.println(user);
}
}
Hope it helps someone ;)
Sorry for the bad looking indent, but pre doesn't work here :(