You are here

services_keyauth.inc in Services 6.2

Same filename and directory in other branches
  1. 7 auth/services_keyauth/services_keyauth.inc

The implementation of the key authentication scheme

File

auth/services_keyauth/services_keyauth.inc
View source
<?php

/**
 * @file
 *  The implementation of the key authentication scheme
 */

/**
 * AHAH callback for keyauth settings form piece.
 */
function _services_keyauth_security_settings() {
  $form['services_use_key'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use keys'),
    '#default_value' => variable_get('services_use_key', TRUE),
    '#description' => t('When enabled all method calls need to provide a validation token to authenticate themselves with the server.'),
  );
  $form['services_key_expiry'] = array(
    '#type' => 'textfield',
    '#prefix' => "<div id='services-key-expiry'>",
    '#suffix' => "</div>",
    '#title' => t('Token expiry time'),
    '#default_value' => variable_get('services_key_expiry', 30),
    '#description' => t('The time frame for which the token will be valid. Default is 30 secs'),
  );
  $form['services_use_sessid'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use sessid'),
    '#default_value' => variable_get('services_use_sessid', TRUE),
    '#description' => t('When enabled, all method calls must include a valid sessid. Only disable this setting if the application will use browser-based cookies.'),
  );
  return $form;
}

/**
 * Validate callback for keyauth security settings form.
 */
function _services_keyauth_security_settings_validate($form_state) {
  if (!preg_match('/^\\d+$/', $form_state['values']['services_key_expiry'])) {
    form_set_error('services_key_expiry', t('The token expiry time must specified in whole seconds as a number'));
  }
}

/**
 * Submit callback for keyauth security settings form.
 */
function _services_keyauth_security_settings_submit($form_state) {

  // Store all values from "our" form as variables.
  foreach (_services_keyauth_security_settings() as $key => $field) {
    variable_set($key, $form_state['values'][$key]);
  }
}

/**
 * Alter method signatures to add required arguments for key authentication.
 *
 * @param $methods
 *   An array of all enabled service methods.
 */
function _services_keyauth_alter_methods(&$methods) {

  // Skip this if no services have been activated
  if (!is_array($methods) || empty($methods)) {
    return;
  }

  // sessid arg
  $arg_sessid = array(
    'name' => 'sessid',
    'type' => 'string',
    'description' => t('A valid sessid.'),
    'source' => array(
      'param' => 'sessid',
    ),
  );
  $arg_domain_time_stamp = array(
    'name' => 'domain_time_stamp',
    'type' => 'string',
    'description' => t('Time stamp used to hash key.'),
    'source' => array(
      'param' => 'domain_time_stamp',
    ),
  );
  $arg_nonce = array(
    'name' => 'nonce',
    'type' => 'string',
    'description' => t('One-time-use nonce also used to hash key.'),
    'source' => array(
      'param' => 'nonce',
    ),
  );

  // domain arg
  $arg_domain_name = array(
    'name' => 'domain_name',
    'type' => 'string',
    'description' => t('A valid domain for the API key.'),
    'source' => array(
      'param' => 'domain_name',
    ),
  );

  // api_key arg
  $arg_api_key = array(
    'name' => 'hash',
    'type' => 'string',
    'description' => t('An SHA-256 hash of the timestamp, domain, nonce, and method name delimited by semicolons and using the remote API key as the shared key.'),
    'source' => array(
      'param' => 'hash',
    ),
  );
  foreach ($methods as $key => &$method) {

    // set method defaults
    switch ($method['method']) {
      case 'system.connect':
        $method['key'] = FALSE;
        $method['auth'] = FALSE;
        break;
      default:
        if (!isset($method['key'])) {
          $method['key'] = TRUE;
        }
        if (!isset($method['auth'])) {
          $method['auth'] = TRUE;
        }
        break;
    }
    if ($method['auth'] && variable_get('services_use_sessid', TRUE)) {
      array_unshift($method['args'], $arg_sessid);
    }
    if ($method['key'] && variable_get('services_use_key', TRUE)) {
      array_unshift($method['args'], $arg_nonce);
      array_unshift($method['args'], $arg_domain_time_stamp);
      array_unshift($method['args'], $arg_domain_name);
      array_unshift($method['args'], $arg_api_key);
    }
  }
}

/**
 * Alter service browser form to add required arguments for key authentication.
 *
 * @param $form
 *   The services browser form.
 * @param $method
 *   The method currently being browsed.
 */
function _services_keyauth_alter_browse_form(&$form, $method) {
  foreach ($method['args'] as $key => $arg) {
    switch ($arg['name']) {
      case 'hash':
        $form['arg'][$key] = array(
          '#title' => t('Hash'),
          '#type' => 'textfield',
          '#value' => t('Gets generated after form submission'),
          '#disabled' => TRUE,
        );
        break;
      case 'sessid':
        $form['arg'][$key]['#title'] = t('Session ID');
        $form['arg'][$key]['#default_value'] = session_id();
        break;
      case 'domain_name':
        $form['arg'][$key]['#title'] = t('Domain name');
        $form['arg'][$key]['#default_value'] = $_SERVER['HTTP_HOST'];
        break;
      case 'domain_time_stamp':
        $form['arg'][$key] = array(
          '#title' => t('Timestamp'),
          '#type' => 'textfield',
          '#value' => t('Gets generated after form submission'),
          '#disabled' => TRUE,
        );
        break;
      case 'nonce':
        $form['arg'][$key]['#title'] = t('Nonce');
        $form['arg'][$key]['#value'] = user_password();
        break;
    }
  }
}

/**
 * Authenticate a services method call with key authentication.
 *
 * @param $method
 *   A method definition as returned by services_method_get().
 * @param $method_name
 *   The name of the method being called
 * @param $args
 *   An array of arguments required by key authentication. These are
 *   hash, domain, timestamp, and nonce. They must be in the array in
 *   this order.
 */
function _services_keyauth_authenticate_call($method, $method_name, &$args) {
  if ($method['key'] && variable_get('services_use_key', TRUE)) {
    $hash = array_shift($args);
    $domain = array_shift($args);
    $timestamp = array_shift($args);
    $nonce = array_shift($args);
    $expiry_time = $timestamp + variable_get('services_key_expiry', 30);
    if ($expiry_time < time()) {
      return services_error(t('Token has expired.'), 401);
    }

    // Still in time but has it been used before
    if (db_result(db_query("SELECT count(*) FROM {services_timestamp_nonce}\n        WHERE domain = '%s' AND nonce = '%s'", $domain, $nonce))) {
      return services_error(t('Token has been used previously for a request. Re-try with another nonce key.'), 401);
    }
    else {
      db_query("INSERT INTO {services_timestamp_nonce} (domain, timestamp, nonce)\n        VALUES ('%s', %d, '%s')", $domain, $timestamp, $nonce);
    }
    $api_key = db_result(db_query("SELECT kid FROM {services_keys} WHERE domain = '%s'", $domain));

    //if (!services_keyauth_validate_key($api_key, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash)) {
    if ($hash != services_get_hash($timestamp, $domain, $nonce, $method, $args)) {
      return services_error(t('Invalid API key.'), 401);
    }
    if (!db_result(db_query("SELECT COUNT(*) FROM {services_key_permissions}\n        WHERE kid = '%s' AND method = '%s'", $api_key, $method_name))) {
      return services_error(t('Access denied.'), 401);
    }
  }

  // Add additonal processing for methods requiring session
  $session_backup = NULL;
  if ($method['auth'] && variable_get('services_use_sessid', TRUE)) {
    $sessid = array_shift($args);
    if (empty($sessid)) {
      return services_error(t('Invalid sessid.'), 401);
    }
    $session_backup = services_session_load($sessid);
  }
}

/**
 * Submit callback for services browse form.
 */
function _services_keyauth_alter_browse_form_submit($method, &$args) {
  if ($method['key'] && variable_get('services_use_key', TRUE)) {
    $args_stripped = $args;
    for ($i = 1; $i <= 4; $i++) {
      array_shift($args_stripped);
    }
    $args[2] = time();
    $args[0] = services_get_hash($args[2], $args[1], $args[3], $method, $args_stripped);
  }
}

Functions

Namesort descending Description
_services_keyauth_alter_browse_form Alter service browser form to add required arguments for key authentication.
_services_keyauth_alter_browse_form_submit Submit callback for services browse form.
_services_keyauth_alter_methods Alter method signatures to add required arguments for key authentication.
_services_keyauth_authenticate_call Authenticate a services method call with key authentication.
_services_keyauth_security_settings AHAH callback for keyauth settings form piece.
_services_keyauth_security_settings_submit Submit callback for keyauth security settings form.
_services_keyauth_security_settings_validate Validate callback for keyauth security settings form.