You are here

bakery_profile.module in Bakery Single Sign-On System 7.4

File

bakery_profile.module
View source
<?php

/**
 * Implements hook_menu().
 */
function bakery_profile_menu() {
  $items = array();
  if (variable_get('bakery_is_master', 0)) {
    $items['bakery/validate'] = array(
      'title' => 'Validate',
      'access callback' => 'bakery_profile_taste_thinmint_cookie',
      'page callback' => 'bakery_profile_eat_thinmint_cookie',
      'type' => MENU_CALLBACK,
    );
    $items['bakery/create'] = array(
      'title' => 'Bakery create',
      'access callback' => 'bakery_profile_taste_gingerbread_cookie',
      'page callback' => 'bakery_profile_eat_gingerbread_cookie',
      'type' => MENU_CALLBACK,
    );
  }
  else {
    $items['bakery/update'] = array(
      'title' => 'Update',
      'access callback' => 'bakery_profile_taste_stroopwafel_cookie',
      'page callback' => 'bakery_profile_eat_stroopwafel_cookie',
      'type' => MENU_CALLBACK,
    );
    $items['admin/config/people/bakery'] = array(
      'title' => 'Pull Bakery user',
      'description' => 'Request an account from the master site',
      'access arguments' => array(
        'administer users',
      ),
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'bakery_profile_pull_form',
      ),
      'type' => MENU_NORMAL_ITEM,
    );
  }
  return $items;
}

/**
 * Implements hook_user_update().
 */
function bakery_profile_user_update(&$edit, $account, $category) {
  global $user;

  // We need to push changes.
  if (variable_get('bakery_is_master', 0) && isset($_SESSION['bakery'])) {
    $type = 'stroopwafel';
    $key = variable_get('bakery_key', '');
    $payload['data'] = serialize($_SESSION['bakery']);
    $payload['timestamp'] = $_SERVER['REQUEST_TIME'];
    $payload['uid'] = $account->uid;
    $payload['category'] = $category;
    $payload['type'] = $type;
    $data = bakery_bake_data($payload);

    // Respond with signed account information.
    $payload = drupal_http_build_query(array(
      $type => $data,
    ));
    unset($_SESSION['bakery']);

    // Now, update the slaves.
    $slaves = variable_get('bakery_slaves', array());
    foreach ($slaves as $slave) {
      $options = array(
        'headers' => array(
          'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
        ),
        'method' => 'POST',
        'data' => $payload,
      );
      $result = drupal_http_request($slave . 'bakery/update', $options);
      if ($result->code != 200) {
        drupal_set_message(t('Error %error for site at %url', array(
          '%error' => $result->code . ' ' . $result->error,
          '%url' => $slave,
        )));
      }
      else {
        drupal_set_message($result->data);

        // TODO: Roll back the change.
      }
    }
  }
}

/**
 * Respond with account information.
 */
function bakery_profile_eat_gingerbread_cookie() {

  // Session was set in validate.
  $name = $_SESSION['bakery']['name'];
  unset($_SESSION['bakery']['name']);
  $or_email = $_SESSION['bakery']['or_email'];
  unset($_SESSION['bakery']['or_email']);
  $slave = $_SESSION['bakery']['slave'];
  unset($_SESSION['bakery']['slave']);
  $slave_uid = $_SESSION['bakery']['uid'];
  unset($_SESSION['bakery']['uid']);
  $key = variable_get('bakery_key', '');
  $account = user_load_by_name($name);
  if (!$account && $or_email) {
    $account = user_load_by_mail($name);
  }
  if ($account) {
    _bakery_save_slave_uid($account, $slave, $slave_uid);
    $payload = array();
    $payload['name'] = $account->name;
    $payload['mail'] = $account->mail;
    $payload['uid'] = $account->uid;

    // For use in slave init field.
    // Add any synced fields.
    foreach (variable_get('bakery_supported_fields', array(
      'mail' => 'mail',
      'name' => 'name',
    )) as $type => $enabled) {
      if ($enabled && $account->{$type}) {
        $payload[$type] = $account->{$type};
      }
    }

    // Invoke implementations of hook_bakery_transmit() for syncing arbitrary
    // data.
    $payload['data'] = module_invoke_all('bakery_transmit', NULL, $account);
    $payload['timestamp'] = $_SERVER['REQUEST_TIME'];

    // Respond with signed account information.
    $message = bakery_bake_data($payload);
  }
  else {
    $message = t('No account found');
    header('HTTP/1.1 409 Conflict');
  }
  module_invoke_all('exit');
  print $message;
  exit;
}

/**
 * Menu callback, invoked on the slave
 */
function bakery_profile_eat_stroopwafel_cookie() {

  // the session got set during validation
  $stroopwafel = $_SESSION['bakery'];
  unset($_SESSION['bakery']);
  $init = _bakery_init_field($stroopwafel['uid']);

  // check if the user exists.
  $account = user_load_multiple(array(), array(
    'init' => $init,
  ));
  if (empty($account)) {

    // user not present
    $message = t('Account not found on %slave.', array(
      '%slave' => variable_get('site_name', ''),
    ));
  }
  else {
    $account = reset($account);
    drupal_add_http_header('X-Drupal-bakery-UID', $account->uid);

    // If profile field is enabled we manually save profile fields along the way.
    $fields = array();
    foreach (variable_get('bakery_supported_fields', array(
      'mail' => 'mail',
      'name' => 'name',
    )) as $type => $value) {
      if ($value) {

        // If the field is set in the cookie it's being updated, otherwise we'll
        // populate $fields with the existing values so nothing is lost.
        if (isset($stroopwafel[$type])) {
          $fields[$type] = $stroopwafel[$type];
        }
        else {
          $fields[$type] = $account->{$type};
        }
      }
    }
    $status = user_save($account, $fields);
    if ($status === FALSE) {
      watchdog('bakery', 'User update from name %name_old to %name_new, mail %mail_old to %mail_new failed.', array(
        '%name_old' => $account->name,
        '%name_new' => $stroopwafel['name'],
        '%mail_old' => $account->mail,
        '%mail_new' => $stroopwafel['mail'],
      ), WATCHDOG_ERROR);
      $message = t('There was a problem updating your account on %slave. Please contact the administrator.', array(
        '%slave' => variable_get('site_name', ''),
      ));
      header('HTTP/1.1 409 Conflict');
    }
    else {
      watchdog('bakery', 'user updated name %name_old to %name_new, mail %mail_old to %mail_new.', array(
        '%name_old' => $account->name,
        '%name_new' => $stroopwafel['name'],
        '%mail_old' => $account->mail,
        '%mail_new' => $stroopwafel['mail'],
      ));
      $message = t('Successfully updated account on %slave.', array(
        '%slave' => variable_get('site_name', ''),
      ));
    }

    // Invoke hook_bakery_receive().
    module_invoke_all('bakery_receive', $account, $stroopwafel);
  }
  module_invoke_all('exit');
  print $message;
  exit;
}

/**
 * Verify the validation request.
 */
function bakery_profile_taste_thinmint_cookie() {
  $type = 'thinmint';
  if (empty($_POST[$type])) {
    return FALSE;
  }
  if (($cookie = bakery_validate_data($_POST[$type], $type)) === FALSE) {
    return FALSE;
  }
  $_SESSION['bakery']['name'] = $cookie['name'];
  $_SESSION['bakery']['slave'] = $cookie['slave'];
  $_SESSION['bakery']['uid'] = $cookie['uid'];
  return TRUE;
}

/**
 * Update the user's login time to reflect them validating their email address.
 */
function bakery_profile_eat_thinmint_cookie() {

  // Session was set in validate.
  $name = $_SESSION['bakery']['name'];
  unset($_SESSION['bakery']['name']);
  $slave = $_SESSION['bakery']['slave'];
  unset($_SESSION['bakery']['slave']);
  $uid = $_SESSION['bakery']['uid'];
  unset($_SESSION['bakery']['uid']);
  $account = user_load_by_name($name);
  if ($account) {

    // @todo
    db_query("UPDATE {users} SET login = :login WHERE uid = :uid", array(
      ':login' => $_SERVER['REQUEST_TIME'],
      ':uid' => $account->uid,
    ));

    // Save UID provided by slave site.
    _bakery_save_slave_uid($account, $slave, $uid);
  }
}

/**
 * Implements hook_user_presave().
 */
function bakery_profile_user_presave(&$edit, $account, $category) {
  if (variable_get('bakery_is_master', 0)) {

    // Invoke implementations of hook_bakery_transmit() for syncing arbitrary
    // data.
    $_SESSION['bakery']['data'] = module_invoke_all('bakery_transmit', $edit, $account, $category);

    // We store email/name if they changed. We want to wait with doing
    // anything else until the changes are saved locally.
    foreach (variable_get('bakery_supported_fields', array(
      'mail' => 'mail',
      'name' => 'name',
    )) as $type => $enabled) {
      if ($enabled && isset($edit[$type]) && isset($account->{$type}) && $account->{$type} != $edit[$type]) {
        $_SESSION['bakery'][$type] = $edit[$type];
      }
    }
  }
}

/**
 * Validate update request.
 */
function bakery_profile_taste_stroopwafel_cookie() {
  $type = 'stroopwafel';
  if (empty($_POST[$type])) {
    return FALSE;
  }
  if (($payload = bakery_validate_data($_POST[$type], $type)) === FALSE) {
    return FALSE;
  }
  $_SESSION['bakery'] = unserialize($payload['data']);
  $_SESSION['bakery']['uid'] = $payload['uid'];
  $_SESSION['bakery']['category'] = $payload['category'];
  return TRUE;
}

/**
 * Validate the account information request.
 */
function bakery_profile_taste_gingerbread_cookie() {
  $type = 'gingerbread';
  if (empty($_POST[$type])) {
    return FALSE;
  }
  if (($cookie = bakery_validate_data($_POST[$type], $type)) === FALSE) {
    return FALSE;
  }
  $_SESSION['bakery']['name'] = $cookie['name'];
  $_SESSION['bakery']['or_email'] = $cookie['or_email'];
  $_SESSION['bakery']['slave'] = $cookie['slave'];
  $_SESSION['bakery']['uid'] = $cookie['uid'];
  return TRUE;
}

/**
 * Request account information from master to create account locally.
 *
 * @param string $name the username or e-mail to request information for to create.
 * @param boolean $or_email load account by name or email. Useful for getting
 *  account data from a password request where you get name or email.
 * @return The newly created local UID or FALSE.
 */
function bakery_profile_request_account($name, $or_email = FALSE) {
  global $base_url;
  $existing_account = user_load_by_name($name);
  if (!$existing_account && $or_email) {
    $account = user_load_by_mail($name);
  }

  // We return FALSE in cases that the account already exists locally or if
  // there was an error along the way of requesting and creating it.
  if ($existing_account) {
    return FALSE;
  }
  $master = variable_get('bakery_master', 'https://drupal.org/');
  $key = variable_get('bakery_key', '');

  // Save a stub account so we have a slave UID to send.
  $new_account = array(
    'name' => $name,
    'pass' => user_password(),
    'status' => 1,
    'init' => 'bakery_temp/' . mt_rand(),
  );
  $account = user_save(NULL, $new_account);
  if (!$account) {
    watchdog('bakery', 'Unable to create stub account for @name', array(
      '@name' => $name,
    ), WATCHDOG_ERROR);
    return FALSE;
  }
  $stub_uid = $account->uid;
  $type = 'gingerbread';
  $payload = array();
  $payload['name'] = $name;
  $payload['or_email'] = $or_email;
  $payload['slave'] = rtrim($base_url, '/') . '/';

  // Match how slaves are set on the master.
  $payload['uid'] = $account->uid;
  $payload['timestamp'] = $_SERVER['REQUEST_TIME'];
  $payload['type'] = $type;
  $data = bakery_bake_data($payload);
  $payload = drupal_http_build_query(array(
    $type => $data,
  ));

  // Make request to master for account information.
  $http_options = array(
    'method' => 'POST',
    'data' => $payload,
    'headers' => array(
      'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
    ),
  );
  $result = drupal_http_request($master . 'bakery/create', $http_options);

  // Parse result and create account.
  if ($result->code != 200) {
    $message = $result->data;
    watchdog('bakery', 'Received response !code from master with message @message', array(
      '!code' => $result->code,
      '@message' => $message,
    ), WATCHDOG_ERROR);
    user_delete($stub_uid);
    return FALSE;
  }
  if (($cookie = bakery_validate_data($result->data)) === FALSE) {

    // Invalid response.
    watchdog('bakery', 'Invalid response from master when attempting to create local account for @name', array(
      '@name' => $name,
    ), WATCHDOG_ERROR);
    user_delete($stub_uid);
    return FALSE;
  }

  // Valid response. Fill in details from master.
  $new_account = array(
    'name' => $cookie['name'],
    'pass' => user_password(),
    'mail' => $cookie['mail'],
    'init' => _bakery_init_field($cookie['uid']),
  );

  // Add any supported sync fields.
  foreach (variable_get('bakery_supported_fields', array(
    'mail' => 'mail',
    'name' => 'name',
  )) as $type => $enabled) {
    if ($enabled && isset($cookie[$type])) {
      $new_account[$type] = $cookie[$type];
    }
  }

  // Create account.
  $account = user_save($account, $new_account);
  if ($account) {
    watchdog('bakery', 'Created account for @name', array(
      '@name' => $name,
    ));

    // Invoke hook_bakery_receive().
    module_invoke_all('bakery_receive', $account, $cookie);
    return $account->uid;
  }
  watchdog('bakery', 'Unable to create account for @name', array(
    '@name' => $name,
  ), WATCHDOG_ERROR);
  user_delete($stub_uid);
  return FALSE;
}

/**
 * Form for admins to pull accounts.
 */
function bakery_profile_pull_form($form, &$form_state) {
  $form['or_email'] = array(
    '#type' => 'radios',
    '#options' => array(
      0 => t('Username'),
      1 => t('Username or email'),
    ),
    '#default_value' => 0,
  );
  $form['name'] = array(
    '#type' => 'textfield',
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Request account'),
  );
  return $form;
}

/**
 * Make sure we are not trying to request an existing user.
 */
function bakery_profile_pull_form_validate($form, &$form_state) {
  $existing_account = user_load_by_name($form_state['values']['name']);
  if (!$existing_account && $form_state['values']['or_email']) {
    $existing_account = user_load_by_mail($form_state['values']['name']);
  }

  // Raise an error in case the account already exists locally.
  if ($existing_account) {
    form_set_error('name', t('Account !link exists.', array(
      '!link' => theme('username', array(
        'account' => $existing_account,
      )),
    )));
  }
}

/**
 * If the request succeeds, go to the user page. Otherwise, show an error.
 */
function bakery_profile_pull_form_submit($form, &$form_state) {
  $result = bakery_profile_request_account($form_state['values']['name'], $form_state['values']['or_email']);
  if ($result === FALSE) {
    drupal_set_message(t("Pulling account %name failed: maybe there is a typo or they don't exist on the master site.", array(
      '%name' => $form_state['values']['name'],
    )), 'error');
  }
  else {
    $form_state['redirect'] = 'user/' . $result;
  }
}

Functions

Namesort descending Description
bakery_profile_eat_gingerbread_cookie Respond with account information.
bakery_profile_eat_stroopwafel_cookie Menu callback, invoked on the slave
bakery_profile_eat_thinmint_cookie Update the user's login time to reflect them validating their email address.
bakery_profile_menu Implements hook_menu().
bakery_profile_pull_form Form for admins to pull accounts.
bakery_profile_pull_form_submit If the request succeeds, go to the user page. Otherwise, show an error.
bakery_profile_pull_form_validate Make sure we are not trying to request an existing user.
bakery_profile_request_account Request account information from master to create account locally.
bakery_profile_taste_gingerbread_cookie Validate the account information request.
bakery_profile_taste_stroopwafel_cookie Validate update request.
bakery_profile_taste_thinmint_cookie Verify the validation request.
bakery_profile_user_presave Implements hook_user_presave().
bakery_profile_user_update Implements hook_user_update().