Last updated February 9, 2012. Created on May 11, 2009.
Edited by xjm, muldos, add1sun. Log in to edit this page.

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('

The SimpleDB domain name where users will be stored.

'), '#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('

The salt used for passwords encryption.

'), '#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.

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.