Added dependency on Session API; simplified validation 7.x-1.3
authorIsaac Sukin
Wed, 23 May 2012 11:59:11 +0000 (04:59 -0700)
committerIsaac Sukin
Wed, 23 May 2012 11:59:11 +0000 (04:59 -0700)
browserid.info
browserid.js
browserid.module

index 05e1ec8..b30e538 100644 (file)
@@ -2,6 +2,7 @@ name = BrowserID
 description = "Allows users to log in using Mozilla BrowserID."
 core = 7.x
 configure = admin/people/browserid
+dependencies[] = session_api
 
 files[] = browserid.module
 files[] = browserid.install
index f9601e4..cb6322f 100644 (file)
@@ -6,7 +6,7 @@ Drupal.behaviors.browserid = {
       if (assertion) {
         $.post(Drupal.settings.basePath +'index.php?q=browserid/verify', {
           'assertion': assertion,
-          'audience': window.location.host
+          'token': Drupal.settings.browserid.token
         }, function (data) {
           if (data.reload) {
             window.location.reload();
index 7cf11ef..dfb2999 100644 (file)
@@ -6,6 +6,13 @@
  */
 
 /**
+ * Status codes for BrowserID verification.
+ */
+define('BROWSERID_REGISTER', 1);
+define('BROWSERID_LOGIN', 2);
+define('BROWSERID_ACCESS_DENIED', 3);
+
+/**
  * Implements hook_menu().
  */
 function browserid_menu() {
@@ -46,6 +53,11 @@ function browserid_init() {
       'email' => $GLOBALS['user']->mail
     )), 'setting');
   }
+  else {
+    drupal_add_js(array('browserid' => array(
+      'token' => browserid_get_token('browserid-login-csrf'),
+    )), 'setting');
+  }
 }
 
 /**
@@ -106,11 +118,11 @@ function browserid_login_button($type = NULL, $all = FALSE) {
   $options = array(
     //'text' => l(t('Sign in with BrowserID'), $_REQUEST['q']),
     //'button' => t('Standard button'),
-    'red' => theme('image', $vars + array('path' => $path .'/images/sign_in_red.png')),
-    'blue' => theme('image', $vars + array('path' => $path .'/images/sign_in_blue.png')),
-    'orange' => theme('image', $vars + array('path' => $path .'/images/sign_in_orange.png')),
-    'green' => theme('image', $vars + array('path' => $path .'/images/sign_in_green.png')),
-    'grey' => theme('image', $vars + array('path' => $path .'/images/sign_in_grey.png')),
+    'red' => theme('image', $vars + array('path' => $path . '/images/sign_in_red.png')),
+    'blue' => theme('image', $vars + array('path' => $path . '/images/sign_in_blue.png')),
+    'orange' => theme('image', $vars + array('path' => $path . '/images/sign_in_orange.png')),
+    'green' => theme('image', $vars + array('path' => $path . '/images/sign_in_green.png')),
+    'grey' => theme('image', $vars + array('path' => $path . '/images/sign_in_grey.png')),
   );
   return $all ? $options : $options[$type];
 }
@@ -141,39 +153,36 @@ function browserid_form_user_login_block_alter(&$form, $form_state) {
  * Implements hook_form_FORM_ID_alter().
  */
 function browserid_form_user_register_form_alter(&$form, $form_state) {
-  if (isset($_REQUEST['assertion']) && isset($_REQUEST['audience'])) {
-    $response = drupal_http_request(
-      'https://browserid.org/verify?assertion=' . urlencode($_REQUEST['assertion']) . '&audience=' . urlencode($_REQUEST['audience']),
-      array('method' => 'POST')
-    );
-    if ($response->code == 200) {
-      $data = json_decode($response->data);
-      $account = user_load_by_mail($data->email);
-      if (!empty($account) && !empty($account->uid)) {
-        $form_state = array('uid' => $account->uid);
-        user_login_submit(array(), $form_state);
-        drupal_goto('user/'. $account->uid);
-      }
-      else {
-        $error = user_validate_mail($data->email);
-        if (!$error) {
-          drupal_set_message(
-            t('The email address %mail is not registered on @site.', array('%mail' => $data->email, '@site' => variable_get('site_name', 'Drupal'))) .' '.
-            t('You can register below, or try again with a different email address: !browserid', array('!browserid' => browserid_login_button()))
-          );
-          $form['account']['mail'] = array(
-            '#type' => 'value',
-            '#value' => $data->email,
-          );
-          $form['account']['mail_info'] = array(
-            '#type' => 'item',
-            '#title' => t('E-mail address'),
-            '#markup' => '<p>'. check_plain($data->email) .'</p>',
-            '#required' => TRUE,
-            '#weight' => -1,
-          );
-        }
-      }
+  $result = browserid_verify_request();
+  if ($result['code'] == BROWSERID_ACCESS_DENIED) {
+    return;
+  }
+  elseif ($result['code'] == BROWSERID_LOGIN) {
+    drupal_goto('user/' . $result['account']->uid);
+  }
+  elseif ($result['code'] == BROWSERID_REGISTER) {
+    $error = user_validate_mail($result['email']);
+    if (!$error) {
+      drupal_set_message(
+        t('The email address %mail is not registered on @site.', array(
+          '%mail' => $result['email'],
+          '@site' => variable_get('site_name', 'Drupal'),
+        )) . ' ' .
+        t('You can register below, or try again with a different email address: !browserid', array(
+          '!browserid' => browserid_login_button(),
+        ))
+      );
+      $form['account']['mail'] = array(
+        '#type' => 'value',
+        '#value' => $result['email'],
+      );
+      $form['account']['mail_info'] = array(
+        '#type' => 'item',
+        '#title' => t('E-mail address'),
+        '#markup' => '<p>' . check_plain($result['email']) . '</p>',
+        '#required' => TRUE,
+        '#weight' => -1,
+      );
     }
   }
 }
@@ -200,12 +209,43 @@ function browserid_admin($form, $form_state) {
  * Verifies assertions.
  */
 function browserid_verify() {
-  if ($GLOBALS['user']->uid || !isset($_REQUEST['assertion']) || !isset($_REQUEST['audience'])) {
+  $result = browserid_verify_request();
+  if ($result['code'] == BROWSERID_ACCESS_DENIED) {
     return MENU_ACCESS_DENIED;
   }
+  elseif ($result['code'] == BROWSERID_LOGIN) {
+    drupal_json_output((object) array('reload' => TRUE));
+  }
+  elseif ($result['code'] == BROWSERID_REGISTER) {
+    drupal_json_output((object) array(
+      'destination' => url('user/register', array('query' => array(
+        'assertion' => $result['assertion'],
+        'audience' => $result['audience'],
+        'token' => $result['token'],
+      ))),
+    ));
+  }
+}
+
+/**
+ * Verifies assertion and token.
+ */
+function browserid_verify_request() {
+  if ($GLOBALS['user']->uid ||
+      !isset($_REQUEST['assertion']) ||
+      !isset($_REQUEST['token']) ||
+      $_REQUEST['token'] != browserid_get_token('browserid-login-csrf')
+      ) {
+    return array('code' => BROWSERID_ACCESS_DENIED);
+  }
+  // The audience (hostname+port) should also be in $_SERVER['HTTP_HOST'] but that is not safe.
+  $audience = $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'];
   $response = drupal_http_request(
-    'https://browserid.org/verify?assertion=' . urlencode($_REQUEST['assertion']) . '&audience=' . urlencode($_REQUEST['audience']),
-    array('method' => 'POST')
+    'https://browserid.org/verify',
+    array(
+      'method' => 'POST',
+      'data' => 'assertion=' . drupal_encode_path($_REQUEST['assertion']) . '&audience=' . drupal_encode_path($audience),
+    )
   );
   if ($response->code == 200) {
     $data = json_decode($response->data);
@@ -213,12 +253,38 @@ function browserid_verify() {
     if (!empty($account) && !empty($account->uid)) {
       $form_state = array('uid' => $account->uid);
       user_login_submit(array(), $form_state);
-      drupal_json_output((object) array('reload' => TRUE));
+      return array(
+        'code' => BROWSERID_LOGIN,
+        'account' => $account,
+        'assertion' => $_REQUEST['assertion'],
+        'audience' => $audience,
+        'token' => $_REQUEST['token'], // no need to regenerate it, we've already checked it's safe
+      );
     }
     else {
-      drupal_json_output((object) array(
-        'destination' => url('user/register', array('query' => array('assertion' => $_REQUEST['assertion'], 'audience' => $_REQUEST['audience']))),
-      ));
+      return array(
+        'code' => BROWSERID_REGISTER,
+        'email' => $data->email,
+        'assertion' => $_REQUEST['assertion'],
+        'audience' => $audience,
+        'token' => $_REQUEST['token'], // no need to regenerate it, we've already checked it's safe
+      );
     }
   }
 }
+
+/**
+ * Generates a token based on $value, the user session, and the private key.
+ *
+ * Works for anonymous users as well as authenticated users.
+ *
+ * @param $value
+ *   An additional value to base the token on.
+ * @see drupal_get_token()
+ */
+function browserid_get_token($value = '') {
+  if (user_is_logged_in()) {
+    return drupal_get_token($value);
+  }
+  return drupal_hmac_base64($value, session_api_get_sid() . drupal_get_private_key() . drupal_get_hash_salt());
+}