The creeper module offers a wrapper to the tarzan API, so let's start by a code example :
We will code a module that plug the drupal authentication system on an Amazon SimpleDB domain.
A few consideration before we start :
You have installed the creeper module, and set up the AWS keys in the administration > Site configuration > Tarzan settings.
We will named the module simpledbusers.
Now let's implements a very simple external authentication module based on amazon simpleDB
We begin by the .info file.
here is the simpledbusers.info file :
name = simpleDB Users module
description = Module for using Amazon Simple DB as storage for users data
core = 6.x
dependencies[] = creeper
The important thing here is the line
dependencies[] = creeper
Which will allow you to use easily the tarzan API in this module code.
This module is so simple that it needn't a .install file, so move on studying the .module file :
//////////////////////////////// DRUPAL HOOKS IMPLEMENTATION ///////////////////////////
/**
* Implementation of hook_menu()
* a simple admin settings screen which will
* allow to set up the salt for encoding users passwords
*/
function simpledbusers_menu() {
$items = array();
$items['admin/settings/simpledbusers/settings'] = array(
'title' => 'Simple DB users Settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('simpledbusers_admin_settings'),
'description' => t('Configure Simple DB Users settings'),
'access arguments' => array('administer site configuration'),
'file' => 'simpledbusers.admin.inc'
);
return $items;
}
/**
* implements hook_form_alter()
* in order to change the validator of the default login form
*/
function simpledbusers_form_alter(&$form, $form_state, $form_id) {
// Replace the drupal authenticate function is it's used as validation.
if($form_id == 'user_login' || $form_id == 'user_login_block'){
if (is_array($form['#validate']) && ($key = array_search('user_login_authenticate_validate', $form['#validate']))){
$form['#validate'][$key] = 'simpledbusers_login_validate';
}
else if ($key === FALSE) {
// Could not find it. Some other module must have run form_alter().
// We will simply add our validation just before the final validator.
$final_validator = array_pop($form['#validate']);
$form['#validate'][] = 'simpledbusers_login_validate';
$form['#validate'][] = $final_validator;
}
}
}
/**
* Main user validation function.
*
* If successful, sets the global $user object.
*
*
*/
function simpledbusers_login_validate($form, &$form_state) {
global $user;
if (!empty($user->uid)) {
// Another module has already handled authentication.
return;
}
$values = $form_state['values'];
$pass = trim($values['pass']);
// (Design decision) uid=1 (admin user) must always authenticate to local database
// this user is critical for all drupal admin and upgrade operations so it is best
// left with drupal's native authentication.
$result = db_query("SELECT uid FROM {users} WHERE name = '%s'", $values['name']);
$account = db_fetch_object($result);
if ($account->uid == 1 ) {
$account = user_load(array('name' => $values['name'], 'pass' => trim($values['pass']), 'status' => 1));
$user = $account;
//drupal way to handle properly an external authentication
user_authenticate_finalize($values);
return;
}
// Authenticate on simpledb the user.
if(!simpledbusers_auth($values['name'], $pass)) {
form_set_error('name',t('Unrecognized username/password'));
}
return;
}
/**
* Implements hook_user().
* mainly to allow synchro between the drupal user ref and the simpleDB user ref.
*/
function simpledbusers_user($op, &$edit, &$account, $category = NULL) {
switch ($op) {
//submit op allow to modify the account before it gets saved
//so each time the account is modified we sync with the SimpleDB domain
case 'submit':
if($account->uid != 1){
$account->pass=$edit['pass'];
simpledbusers_update_account_infos($account,$edit);
}
break;
//the delete op is called After the delete op in the database
case 'delete' :
if(!$registration_delete){
simpledbusers_user_delete($account);
}
break;
case 'insert':
// New user was just added; if we authenticate the user via simpledb, we sync the user's data
// because the user has been auto created in drupal db if we are here.
// we set the uid generated by drupal in the users domain and the email field from simpledb
global $simpledbusers_authenticated;
if ($simpledbusers_authenticated) {
simpledbusers_create_content_profile($account);
//add the validated user role
$roles = $account->roles + array(6 => 'validated user');
user_save($account, array('roles' => $roles));
simpledbusers_update_uid($account);
}
else {//creation of user by registration or admin creation
$account->pass = $edit['pass']; // we need the clear password
//we set a false password in database for the user
db_query("UPDATE {users} SET pass = '%s' WHERE uid = %d", user_password(), $account->uid);
//we create the simpledb item
simpledbusers_register_user(array('account' => $account));
}
break;
}
}
//////////////////// SIMPLE DB INTERACTIONS FUNCTIONS //////////////////////////
/**
* Authenticate the user against SimpleDB.
*
* @param $name
* A username.
* @param $pass
* A password.
*
* @return
* TRUE if success, FALSE otherwise.
*/
function simpledbusers_auth($name, $pass) {
global $simpledbusers_authenticated;
// Don't allow empty passwords because they cause problems on some setups.
if (empty($pass))
return FALSE;
$sha265_password = simpledbusers_encode_password($pass);
// Try to authenticate.
$sdb = new AmazonSDB();
$selection = $sdb->select("select name from users where name = '".$name."' and password= '".$sha265_password."'");
if(!$selection->isOK()) {
form_set_error('name',t('Problem during authentication request'));
return FALSE;
}
if(!isset($selection->body->SelectResult->Item))
return FALSE;
//authentication ok :
$simpledbusers_authenticated = TRUE;
user_external_login_register($name, 'simpledbusers');
return TRUE;
}
/*
* Delete a user item in the simpledb users domain.
*/
function simpledbusers_user_delete($account) {
$item_name = $account->name;
$sdb = new AmazonSDB();
$del = $sdb->delete_attributes(variable_get('simpledbusers_simpledb_domain','users'),$item_name);
//todo add error handling here....
}
/*
* Update in simpledb the uid user attribute
*
*/
function simpledbusers_update_uid($account)
{
$sdb = new AmazonSDB();
$keypairs = array(
'drupal_uid' => $account->uid,
);
$put = $sdb->put_attributes(variable_get('simpledbusers_simpledb_domain','users'),$account->name,$keypairs,TRUE);
}
/*
* Update simpledb mail and password user attribute
*
*/
function simpledbusers_update_account_infos($account,$edit)
{ global $external_account_update_ok;
$external_account_update_ok = TRUE;
$sdb = new AmazonSDB();
if(isset($edit['mail'])){
$keypairs = array('email' => $edit['mail']);
if(isset($edit['pass'])){
$keypairs = $keypairs + array('password' => simpledbusers_encode_password($edit['pass']));
}
$put = $sdb->put_attributes(variable_get('simpledbusers_simpledb_domain','users'),$account->name,$keypairs,TRUE);
$external_account_update_ok = $put->isOK();
}
}
/*
* Create an item in the simpledb users domain for a new user
*/
function simpledbusers_register_user($args) {
$account = $args['account'];
//the password is availaible in clear text at the moment (cf the hook_user code)
$account_clear_pwd = $account->pass;
$account = user_save($account, array('authname_simpledbusers' => $account->name));
$sdb = new AmazonSDB();
$keypairs = array(
'name' => $account->name,
'email' => $account->mail,
'password' => simpledbusers_encode_password($account_clear_pwd),
//sample additional field
'time_purchased' => time() + (6 * 30 * 24 * 60 * 60),
);
$put = $sdb->put_attributes(variable_get('simpledbusers_simpledb_domain','users'),$account->name,$keypairs);
global $external_registration_ok;//global var that other modules ca use to check elsewhere to see if anything strange occured
if(!$put->isOK())
$external_registration_ok = FALSE;//a problem occur if we are here so we set the flag
}
function simpledbusers_encode_password($clear_password) {
return hash('sha256',$clear_password.variable_get('simpledbusers_salt','mys4ltH3r3'));
}
The important things here are the simpleDB interactions functions, with for example :
function simpledbusers_register_user($args) {
$account = $args['account'];
//the password is availaible in clear text at the moment (cf the hook_user code)
$account_clear_pwd = $account->pass;
$account = user_save($account, array('authname_simpledbusers' => $account->name));
$sdb = new AmazonSDB();
$keypairs = array(
'name' => $account->name,
'email' => $account->mail,
'password' => simpledbusers_encode_password($account_clear_pwd),
//sample additional field
'time_purchased' => time() + (6 * 30 * 24 * 60 * 60),
);
$put = $sdb->put_attributes(variable_get('simpledbusers_simpledb_domain','users'),$account->name,$keypairs);
global $external_registration_ok;//global var that other modules ca use to check elsewhere to see if anything strange occured
if(!$put->isOK())
$external_registration_ok = FALSE;//a problem occur so set the flag
}
Or
/*
* Delete a user item in the simpledb users domain.
*/
function simpledbusers_user_delete($account) {
$item_name = $account->name;
$sdb = new AmazonSDB();
$del = $sdb->delete_attributes(variable_get('simpledbusers_simpledb_domain','users'),$item_name);
//todo add error handling here, depending on your needs....
}
You can notice the use of the AmazonSDB class, the entrance point to the SimpleDB API of the tarzan framework :
$sdb = new AmazonSDB();
Simple isn't it ?
the simpleDB domain (a domain is mainly an SQL table equivalent in Amazon simpleDB) used here include a name, a password a drupal_uid field and an additional "time_purchased" field as example.
for the records here is the simpledbusers.admin.inc but it's not the point to focus on for this example :
function simpledbusers_admin_settings(&$form_state) {
$form = array();
$form['simpledb-settings'] = array(
'#type' => 'fieldset',
'#title' => t('Simple DB settings'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['simpledb-settings']['simpledbusers_simpledb_domain'] = array(
'#type' => 'textfield',
'#title' => t('Domain Name'),
'#default_value' => variable_get('simpledbusers_simpledb_domain','users'),
'#size' => 50,
'#maxlength' => 255,
'#description' => t('<p>The SimpleDB domain name where users will be stored.</p>'),
'#required' => TRUE,
);
$form['simpledb-settings']['simpledbusers_salt'] = array(
'#type' => 'textfield',
'#title' => t('Password Salt'),
'#default_value' => variable_get('simpledbusers_salt','mys4ltH3r3'),
'#size' => 50,
'#maxlength' => 255,
'#description' => t('<p>The salt used for passwords encryption.</p>'),
'#required' => TRUE,
);
return system_settings_form($form);
}
function simpledbusers_admin_settings_submit($form,&$form_state) {
variable_set('simpledbusers_simpledb_domain',$form_state['values']['simpledbusers_simpledb_domain']);
variable_set('simpledbusers_salt',$form_state['values']['simpledbusers_salt']);
}
Conclusion
This simple example demonstrate that when you declare a dependencie to the creeper module, all you need to worry to interact with AWS is the Cloudfusion API : http://getcloudfusion.com/docs
I have found drupal dev with cloudfusion very pleasant because you use 2 good APIs and quickly make powerfull things easily.