'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' => "
",
'#suffix' => "
",
'#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}
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)
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}
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);
}
}