'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); } }