Index: services.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/services/Attic/services.install,v
retrieving revision 1.3.2.17
diff -u -r1.3.2.17 services.install
--- services.install	6 Sep 2008 23:17:30 -0000	1.3.2.17
+++ services.install	11 Jan 2009 12:08:03 -0000
@@ -34,6 +34,20 @@
         'not null' => TRUE,
         'default' => ''
       ),
+      'secret' => array(
+        'description' => 'The service key secret.',
+        'type' => 'char',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => ''
+      ),
+      'uid' => array(
+        'description' => 'Who can view and regenerate the secret?',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('kid')
   );
@@ -55,18 +69,15 @@
         'not null'    => TRUE,
         'default'     => ''
       ),
-      'domain' => array(
-        'description' => 'The domain that submitted the request.',
-        'type'        => 'varchar',
-        'length'      => 255,
+      'kid' => array(
+        'description' => 'The service key ID.',
+        'type'        => 'char',
+        'length'      => 32,
         'not null'    => TRUE,
         'default'     => ''
       ),
     ),
-    'indexes' => array( 
-       'timestamp' => array('timestamp'), 
-    ), 
-    'primary key' => array('nonce'),    
+    'unique key' => array('timestamp', 'nonce', 'kid'),    
   );
   return $schema;
 }
@@ -124,3 +135,37 @@
   db_create_table($update, 'services_timestamp_nonce', $schema['services_timestamp_nonce']);
   return $update;
 }
+
+function services_update_6002() {
+  menu_rebuild();
+
+  $secret = array(
+    'description' => 'The service key secret.',
+    'type' => 'char',
+    'length' => 32,
+    'not null' => TRUE,
+    'default' => ''
+  );
+  $uid = array(
+    'description' => 'Who can view and regenerate the secret?',
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  $kid = array(
+    'description' => 'The service key ID.',
+    'type' => 'char',
+    'length' => 32,
+    'not null' => TRUE,
+    'default' => ''
+  );
+  $new_keys = array('unique key' => array('timestamp', 'nonce', 'kid'));
+
+  $update = array();
+  db_add_field($update, 'services_keys', 'secret', $secret);
+  db_add_field($update, 'services_keys', 'uid', $uid);
+  db_drop_unique_key($update, 'services_timestamp_nonce');
+  db_change_field($update, 'services_timestamp_nonce', 'domain', 'kid', $kid, $new_keys);
+  return $update;
+}
Index: services.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/services/Attic/services.module,v
retrieving revision 1.8.2.73
diff -u -r1.8.2.73 services.module
--- services.module	6 Sep 2008 23:17:30 -0000	1.8.2.73
+++ services.module	11 Jan 2009 13:05:56 -0000
@@ -140,10 +140,30 @@
     'page arguments'    => array(1),
     'type'              => MENU_CALLBACK,
   );
+  $items['user/%user/services_key'] = array(
+    'title'             => 'Services API key',
+    'page callback'     => 'drupal_get_form',
+    'page arguments'    => array('services_admin_keys_user_regenerate_secret'),
+    'access callback'   => 'services_user_key_access',
+    'type'              => MENU_LOCAL_TASK,
+    'file'              => 'services_admin_keys.inc',
+  );
 
   return $items;
 }
 
+function services_user_key_access() {
+  global $user;
+  if ($user->uid) {
+    $has_key = db_result(db_query("SELECT COUNT(*) FROM {services_keys} WHERE uid = %d", $user->uid));
+  	if ($has_key) {
+  		return TRUE;
+  	}
+  }
+
+  return FALSE;
+}
+
 /**
  * Implementation of hook_theme().
  */
@@ -279,7 +299,7 @@
 
   if ($method['#key'] and variable_get('services_use_key', TRUE)) {
     $hash = array_shift($args);
-    $domain = array_shift($args);
+    $api_key = array_shift($args);
     $timestamp = array_shift($args);
     $nonce = array_shift($args);
 
@@ -291,19 +311,17 @@
 
     // Still in time but has it been used before
     if (db_result(db_query("SELECT count(*) FROM {services_timestamp_nonce}
-        WHERE domain = '%s' AND timestamp = %d AND nonce = '%s'",
-        $domain, $timestamp, $nonce))) {
+        WHERE kid = '%s' AND timestamp = %d AND nonce = '%s'",
+        $api_key, $timestamp, $nonce))) {
       return services_error(t('Token has been used previously for a request.'));
     }
     else{
-      db_query("INSERT INTO {services_timestamp_nonce} (domain, timestamp, nonce)
-        VALUES ('%s', %d, '%s')", $domain, $timestamp, $nonce);
+      db_query("INSERT INTO {services_timestamp_nonce} (kid, timestamp, nonce)
+        VALUES ('%s', %d, '%s')", $api_key, $timestamp, $nonce);
     }
 
-    $api_key = db_result(db_query("SELECT kid FROM {services_keys} WHERE domain = '%s'", $domain));
-
-    if (!services_validate_key($api_key, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash)) {
-      return services_error(t('Invalid API key.'));
+    if ($hash != services_get_hash($timestamp, $api_key, $nonce, $method_name, $hash_parameters)) {
+      return services_error(t('Invalid API key hash.'));
     }
   }
 
@@ -357,37 +375,34 @@
   if (!isset($methods_cache)) {
     $methods = module_invoke_all('service');
 
-    // api_key arg
-    $arg_api_key = array(
+    $arg_hash = array(
       '#name' => 'hash',
       '#type' => 'string',
-      '#description' => t('A valid API key.'),
+      '#description' => t('A valid hash.'),
     );
 
-    // sessid arg
     $arg_sessid = array(
       '#name' => 'sessid',
       '#type' => 'string',
       '#description' => t('A valid sessid.'),
     );
 
-    // domain arg
-    $arg_domain_name = array(
-      '#name' => 'domain_name',
+    $arg_api_key = array(
+      '#name' => 'api_key',
       '#type' => 'string',
-      '#description' => t('A valid domain for the API key.'),
+      '#description' => t('A valid API key.'),
     );
 
-    $arg_domain_time_stamp = array(
-      '#name' => 'domain_time_stamp',
+    $arg_time_stamp = array(
+      '#name' => 'time_stamp',
       '#type' => 'string',
-      '#description' => t('Time stamp used to hash key.'),
+      '#description' => t('Time stamp.'),
     );
 
     $arg_nonce = array(
       '#name' => 'nonce',
       '#type' => 'string',
-      '#description' => t('One time use nonce also used hash key.'),
+      '#description' => t('One time use nonce.'),
     );
 
     foreach ($methods as $key => $method) {
@@ -418,9 +433,9 @@
 
       if ($methods[$key]['#key'] and variable_get('services_use_key', TRUE)) {
         $methods[$key]['#args'] = array_merge(array($arg_nonce), $methods[$key]['#args']);
-        $methods[$key]['#args'] = array_merge(array($arg_domain_time_stamp), $methods[$key]['#args']);
-        $methods[$key]['#args'] = array_merge(array($arg_domain_name), $methods[$key]['#args']);
+        $methods[$key]['#args'] = array_merge(array($arg_time_stamp), $methods[$key]['#args']);
         $methods[$key]['#args'] = array_merge(array($arg_api_key), $methods[$key]['#args']);
+        $methods[$key]['#args'] = array_merge(array($arg_hash), $methods[$key]['#args']);
       }
 
       // set defaults for args
@@ -467,10 +482,10 @@
   return $method_cache[$method_name];
 }
 
-function services_validate_key($kid, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash) {
-  $hash_parameters = array_merge(array($timestamp, $domain, $nonce, $method_name), $hash_parameters);
-  $rehash = hash_hmac("sha256", implode(';', $hash_parameters), $kid);
-  return ($rehash == $hash) ? TRUE : FALSE;
+function services_get_hash($timestamp, $kid, $nonce, $method_name, $hash_parameters) {
+  $hash_parameters = array_merge(array($timestamp, $kid, $nonce, $method_name), $hash_parameters);
+  $secret = db_result(db_query("SELECT secret FROM {services_keys} WHERE kid = '%s'", $kid));
+  return hash_hmac("sha256", implode(';', $hash_parameters), $secret);
 }
 
 function services_get_key($kid) {
Index: services_admin_browse.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/services/Attic/services_admin_browse.inc,v
retrieving revision 1.5.2.34
diff -u -r1.5.2.34 services_admin_browse.inc
--- services_admin_browse.inc	6 Sep 2008 04:13:07 -0000	1.5.2.34
+++ services_admin_browse.inc	11 Jan 2009 13:03:49 -0000
@@ -95,11 +95,13 @@
 
 function services_admin_browse_test() {
   $form = array();
-  $method = services_method_get(arg(4));
+  $method_name = arg(4);
+  $method = services_method_get($method_name);
 
   $form['arg'] = array('#tree' => TRUE);
   $timestamp = time();
   $nonce = user_password();
+  $api_key = services_admin_browse_get_first_key();
   
   foreach ($method['#args'] as $key => $arg) {      
     $form['name'][$key]         = array(
@@ -114,7 +116,7 @@
         $form['arg'][$key] = array(
           '#title'          => 'Hash',
           '#type'           => 'textfield',
-          '#default_value'  => hash_hmac('sha256', $timestamp .';'. $_SERVER['HTTP_HOST'] .';'. $nonce .';'. arg(4), services_admin_browse_get_first_key())
+          '#default_value'  => services_get_hash($timestamp, $api_key, $nonce, $method_name, array()), 
         );
         break;
 
@@ -126,15 +128,15 @@
         );
         break;
 
-      case 'domain_name':
+      case 'api_key':
         $form['arg'][$key] = array(
-          '#title'          => 'Domain name',
+          '#title'          => 'API key',
           '#type'           => 'textfield',
-          '#default_value'  => $_SERVER['HTTP_HOST']
+          '#default_value'  => $api_key
         );
         break;
 
-      case 'domain_time_stamp':
+      case 'time_stamp':
         $form['arg'][$key] = array(
           '#title'          => 'Timestamp',
           '#type'           => 'textfield',
Index: services_admin_keys.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/services/Attic/services_admin_keys.inc,v
retrieving revision 1.3.2.13
diff -u -r1.3.2.13 services_admin_keys.inc
--- services_admin_keys.inc	6 Sep 2008 04:13:07 -0000	1.3.2.13
+++ services_admin_keys.inc	11 Jan 2009 12:14:02 -0000
@@ -45,17 +45,26 @@
   $kid = arg(4);
 
   $key = db_fetch_object(db_query("SELECT * FROM {services_keys} WHERE kid = '%s'", $kid));
+  $user_name = ($key->uid) ? db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $key->uid)) : '';
 
   $form['kid'] = array(
     '#type'           => 'hidden',
     '#default_value'  => $key->kid,
   );
+  $form['secret'] = array(
+    '#type'           => 'hidden',
+    '#default_value'  => $key->secret,
+  );
 
   if ($key->kid) {
-    $form['key'] = array(
-      '#type'           => 'markup',
-      '#title'          => t('Key'),
-      '#value'          => '<strong>'. t('API Key') .':</strong> '. $key->kid,
+    $form['key_secret'] = array(
+      '#value'          => '<p><strong>'. t('API Key') .':</strong> '. $key->kid .'<br /><strong>'. t('API Key\'s secret') .':</strong> '. $key->secret .'</p>',
+    );
+    $form['secret_regenerate'] = array(
+      '#title'          => t('Regenerate API key\'s secret'),
+      '#type'           => 'checkbox',
+      '#default_value'  => 0,
+      '#description'    => t('If you do, access to the API with the current secret will be denied. This operation cannot be undone.'),
     );
   }
 
@@ -72,6 +81,15 @@
     '#description'    => t('External domain allowed to use this key.'),
   );
 
+  $form['user_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Owner'),
+    '#default_value' => $user_name,
+    '#autocomplete_path' => 'user/autocomplete',
+    '#maxlength' => '60',
+    '#description' => 'This user will be able to view his/her API key and secret. And regenerate the secret.',
+  );
+
   $form['submit'] = array(
     '#type'           => 'submit',
     '#value'          => $key->title ? t('Save key') : t('Create key'),
@@ -91,19 +109,54 @@
   $key['kid'] = !empty($key['kid']) ? $key['kid'] : md5(uniqid(mt_rand(), TRUE));
   $is_existing =  db_result(db_query("SELECT count(*) FROM {services_keys}
     WHERE kid = '%s'", $key['kid']));
+  if (empty($key['secret']) || $key['secret_regenerate']) {
+    $key['secret'] = md5(uniqid(mt_rand(), TRUE));
+  }
+	$uid = 0;
+  if (!empty($key['user_name'])) {
+    $uid = (int) db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $key['user_name']));
+  }
 
   if ($is_existing) {
-    db_query("UPDATE {services_keys} SET title = '%s', domain = '%s'
-      WHERE kid = '%s'", $key['title'], $key['domain'], $key['kid']);
+    db_query("UPDATE {services_keys} SET title = '%s', domain = '%s', secret = '%s', uid = %d 
+      WHERE kid = '%s'", $key['title'], $key['domain'], $key['secret'], $uid, $key['kid']);
     return SAVED_UPDATED;
   }
   else {
-    db_query("INSERT INTO {services_keys} (kid, title, domain)
-      VALUES ('%s', '%s', '%s')", $key['kid'], $key['title'], $key['domain']);
+    db_query("INSERT INTO {services_keys} (kid, title, domain, secret, uid)
+      VALUES ('%s', '%s', '%s', '%s', %d)", $key['kid'], $key['title'], $key['domain'], $key['secret'], $uid);
     return SAVED_NEW;
   }
 }
 
+function services_admin_keys_user_regenerate_secret() {
+  global $user;
+
+  $key = db_fetch_object(db_query("SELECT * FROM {services_keys} WHERE uid = %d", $user->uid));
+  
+  $form['kid'] = array(
+    '#type'           => 'hidden',
+    '#default_value'  => $key->kid,
+  );
+  $form['key_secret'] = array(
+    '#value'          => '<p>'. t('API Key') .': '. $key->kid .'<br />'. t('API Key\'s secret') .': '. $key->secret .'</p><p><strong>Regeneration of the secret can not be undone</strong> and has an impact on all webservice requests you make.</p>',
+  );
+  $form['submit'] = array(
+    '#type'           => 'submit',
+    '#value'          => t('Regenerate secret'),
+  );
+
+  return $form;
+}
+
+function services_admin_keys_user_regenerate_secret_submit($form, &$form_state) {
+  $key = $form_state['values']['kid'];
+  $secret = md5(uniqid(mt_rand(), TRUE));
+  db_query("UPDATE {services_keys} SET secret = '%s' WHERE kid = '%s'", $secret, $key);
+  
+  drupal_set_message('Key regenerated');
+}
+
 function services_admin_keys_delete($kid) {
   db_query("DELETE FROM {services_keys} WHERE kid = '%s'", $kid);
 }
