From 315c419322526294d9f53ab44bdbcc4bdef02e7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Hojtsy?= <gabor@hojtsy.hu>
Date: Wed, 30 Jan 2008 22:11:22 +0000
Subject: [PATCH] #216022 reported by johnnysxip, patch by walkah:
 (SA-2008-016) OpenID - Incorrect claimed_id returned for OpenID 2.0 and other
 minor OpenID 2.0 compliance fixes

---
 modules/openid/openid.css       |  8 ++--
 modules/openid/openid.inc       | 51 +++++++++++++++-------
 modules/openid/openid.js        | 14 +++---
 modules/openid/openid.module    | 75 +++++++++++++++++++--------------
 modules/openid/openid.pages.inc | 17 ++++----
 5 files changed, 98 insertions(+), 67 deletions(-)

diff --git a/modules/openid/openid.css b/modules/openid/openid.css
index 5437350ff217..5a05d63806b5 100644
--- a/modules/openid/openid.css
+++ b/modules/openid/openid.css
@@ -1,18 +1,18 @@
 /* $Id$ */
 
-#edit-openid-url {
+#edit-openid-identifier {
   background-image: url("login-bg.png");
   background-position: 0% 50%;
   background-repeat: no-repeat;
   padding-left: 20px;
 }
 
-div#edit-openid-url-wrapper {
+div#edit-openid-identifier-wrapper {
   display: block;
 }
 
-html.js #user-login-form div#edit-openid-url-wrapper,
-html.js #user-login div#edit-openid-url-wrapper {
+html.js #user-login-form div#edit-openid-identifier-wrapper,
+html.js #user-login div#edit-openid-identifier-wrapper {
   display: none;
 }
 
diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc
index 9ff4af5a2950..e20cf980668f 100644
--- a/modules/openid/openid.inc
+++ b/modules/openid/openid.inc
@@ -193,7 +193,7 @@ function _openid_link_href($rel, $html) {
   $rel = preg_quote($rel);
   preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iUs', $html, $matches);
   if (isset($matches[3])) {
-    preg_match('|href=["\']([^"]+)["\']|iU', $matches[0], $href);
+    preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
     return trim($href[1]);
   }
   return FALSE;
@@ -206,7 +206,9 @@ function _openid_meta_httpequiv($equiv, $html) {
   preg_match('|<meta\s+http-equiv=["\']'. $equiv .'["\'](.*)/?>|iUs', $html, $matches);
   if (isset($matches[1])) {
     preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
-    return $content[1];
+    if (isset($content[1])) {
+      return $content[1];
+    }
   }
   return FALSE;
 }
@@ -382,23 +384,40 @@ function _openid_get_bytes($num_bytes) {
   return $bytes;
 }
 
-/**
- * Fix PHP's habit of replacing '.' by '_' in posted data.
- */
-function _openid_fix_post(&$post) {
-  $extensions = module_invoke_all('openid', 'extension');
-  foreach ($post as $key => $value) {
-    if (strpos($key, 'openid_') === 0) {
-      $fixed_key = str_replace('openid_', 'openid.', $key);
-      $fixed_key = str_replace('openid.ns_', 'openid.ns.', $fixed_key);
-      $fixed_key = str_replace('openid.sreg_', 'openid.sreg.', $fixed_key);
-      foreach ($extensions as $ext) {
-        $fixed_key = str_replace('openid.'. $ext .'_', 'openid.'. $ext .'.', $fixed_key);
+function _openid_response($str = NULL) {
+  $data = array();
+  
+  if (isset($_SERVER['REQUEST_METHOD'])) {
+    $data = _openid_get_params($_SERVER['QUERY_STRING']);
+
+    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+      $str = file_get_contents('php://input');
+
+      $post = array();
+      if ($str !== false) {
+        $post = _openid_get_params($str);
       }
-      unset($post[$key]);
-      $post[$fixed_key] = $value;
+
+      $data = array_merge($data, $post);
+    }
+  }
+
+  return $data;
+}
+
+function _openid_get_params($str) {
+  $chunks = explode("&", $str);
+
+  $data = array();
+  foreach ($chunks as $chunk) {
+    $parts = explode("=", $chunk, 2);
+
+    if (count($parts) == 2) {
+      list($k, $v) = $parts;
+      $data[$k] = urldecode($v);
     }
   }
+  return $data;
 }
 
 /**
diff --git a/modules/openid/openid.js b/modules/openid/openid.js
index 9a7b9252e23e..bd2948dc9623 100644
--- a/modules/openid/openid.js
+++ b/modules/openid/openid.js
@@ -2,11 +2,11 @@
 
 Drupal.behaviors.openid = function (context) {
   var $loginElements = $("#edit-name-wrapper, #edit-pass-wrapper, li.openid-link");
-  var $openidElements = $("#edit-openid-url-wrapper, li.user-link");
+  var $openidElements = $("#edit-openid-identifier-wrapper, li.user-link");
 
   // This behavior attaches by ID, so is only valid once on a page.
-  if (!$("#edit-openid-url.openid-processed").size() && $("#edit-openid-url").val()) {
-    $("#edit-openid-url").addClass('openid-processed');
+  if (!$("#edit-openid-identifier.openid-processed").size() && $("#edit-openid-identifier").val()) {
+    $("#edit-openid-identifier").addClass('openid-processed');
     $loginElements.hide();
     // Use .css("display", "block") instead of .show() to be Konqueror friendly.
     $openidElements.css("display", "block");
@@ -19,8 +19,8 @@ Drupal.behaviors.openid = function (context) {
       // Remove possible error message.
       $("#edit-name, #edit-pass").removeClass("error");
       $("div.messages.error").hide();
-      // Set focus on OpenID URL field.
-      $("#edit-openid-url")[0].focus();
+      // Set focus on OpenID Identifier field.
+      $("#edit-openid-identifier")[0].focus();
       return false;
     });
   $("li.user-link:not(.openid-processed)", context)
@@ -28,8 +28,8 @@ Drupal.behaviors.openid = function (context) {
     .click(function() {
        $openidElements.hide();
        $loginElements.css("display", "block");
-      // Clear OpenID URL field and remove possible error message.
-      $("#edit-openid-url").val('').removeClass("error");
+      // Clear OpenID Identifier field and remove possible error message.
+      $("#edit-openid-identifier").val('').removeClass("error");
       $("div.messages.error").css("display", "block");
       // Set focus on username field.
       $("#edit-name")[0].focus();
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
index eb281d26dcc3..d77ee73b2d2e 100644
--- a/modules/openid/openid.module
+++ b/modules/openid/openid.module
@@ -62,7 +62,7 @@ function openid_help($path, $arg) {
  * Implementation of hook_user().
  */
 function openid_user($op, &$edit, &$account, $category = NULL) {
-  if ($op == 'insert' && isset($_SESSION['openid'])) {
+  if ($op == 'insert' && isset($_SESSION['openid']['values'])) {
     // The user has registered after trying to login via OpenID.
     if (variable_get('user_email_verification', TRUE)) {
       drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.'));
@@ -78,7 +78,7 @@ function openid_form_alter(&$form, $form_state, $form_id) {
   if ($form_id == 'user_login_block' || $form_id == 'user_login') {
     drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
     drupal_add_js(drupal_get_path('module', 'openid') .'/openid.js');
-    if (!empty($form_state['post']['openid_url'])) {
+    if (!empty($form_state['post']['openid_identifier'])) {
       $form['name']['#required'] = FALSE;
       $form['pass']['#required'] = FALSE;
       unset($form['#submit']);
@@ -102,7 +102,7 @@ function openid_form_alter(&$form, $form_state, $form_id) {
 
     $form['links']['#weight'] = 2;
 
-    $form['openid_url'] = array(
+    $form['openid_identifier'] = array(
       '#type' => 'textfield',
       '#title' => t('Log in using OpenID'),
       '#size' => ($form_id == 'user_login') ? 58 : 13,
@@ -115,15 +115,15 @@ function openid_form_alter(&$form, $form_state, $form_id) {
   elseif ($form_id == 'user_register' && isset($_SESSION['openid'])) {
     // We were unable to auto-register a new user. Prefill the registration
     // form with the values we have.
-    $form['name']['#default_value'] = $_SESSION['openid']['name'];
-    $form['mail']['#default_value'] = $_SESSION['openid']['mail'];
+    $form['name']['#default_value'] = $_SESSION['openid']['values']['name'];
+    $form['mail']['#default_value'] = $_SESSION['openid']['values']['mail'];
     // If user_email_verification is off, hide the password field and just fill
     // with random password to avoid confusion.
     if (!variable_get('user_email_verification', TRUE)) {
       $form['pass']['#type'] = 'hidden';
       $form['pass']['#value'] = user_password();
     }
-    $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['auth_openid']);
+    $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['values']['auth_openid']);
   }
   return $form;
 }
@@ -137,7 +137,7 @@ function openid_login_validate($form, &$form_state) {
     $return_to = url('', array('absolute' => TRUE));
   }
 
-  openid_begin($form_state['values']['openid_url'], $return_to, $form_state['values']);
+  openid_begin($form_state['values']['openid_identifier'], $return_to, $form_state['values']);
 }
 
 /**
@@ -157,19 +157,19 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
 
   $services = openid_discovery($claimed_id);
   if (count($services) == 0) {
-    form_set_error('openid_url', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
+    form_set_error('openid_identifier', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
     return;
   }
 
-  $op_endpoint = $services[0]['uri'];
-  // Store the discovered endpoint in the session (so we don't have to rediscover).
-  $_SESSION['openid_op_endpoint'] = $op_endpoint;
-  // Store the claimed_id in the session (for handling delegation).
-  $_SESSION['openid_claimed_id'] = $claimed_id;
+  // Store discovered information in the users' session so we don't have to rediscover.
+  $_SESSION['openid']['service'] = $services[0];
+  // Store the claimed id
+  $_SESSION['openid']['claimed_id'] = $claimed_id;
   // Store the login form values so we can pass them to
   // user_exteral_login later.
-  $_SESSION['openid_user_login_values'] = $form_values;
+  $_SESSION['openid']['user_login_values'] = $form_values;
 
+  $op_endpoint = $services[0]['uri'];
   // If bcmath is present, then create an association
   $assoc_handle = '';
   if (function_exists('bcadd')) {
@@ -191,8 +191,8 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
   }
 
   if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) {
-    $identity = 'http://openid.net/identifier_select/2.0';
-  }
+    $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
+  }  
   $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
 
   if ($services[0]['version'] == 2) {
@@ -207,29 +207,42 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
  * Completes OpenID authentication by validating returned data from the OpenID
  * Provider.
  *
- * @param $response Array of returned from the OpenID provider (typically $_REQUEST).
+ * @param $response Array of returned values from the OpenID Provider.
  *
  * @return $response Response values for further processing with
  *   $response['status'] set to one of 'success', 'failed' or 'cancel'.
  */
-function openid_complete($response) {
+function openid_complete($response = array()) {
   module_load_include('inc', 'openid');
 
+  if (count($response) == 0) {
+    $response = _openid_response();
+  }
+  
   // Default to failed response
   $response['status'] = 'failed';
-  if (isset($_SESSION['openid_op_endpoint']) && isset($_SESSION['openid_claimed_id'])) {
-    _openid_fix_post($response);
-    $op_endpoint = $_SESSION['openid_op_endpoint'];
-    $claimed_id = $_SESSION['openid_claimed_id'];
-    unset($_SESSION['openid_op_endpoint']);
-    unset($_SESSION['openid_claimed_id']);
+  if (isset($_SESSION['openid']['service']['uri']) && isset($_SESSION['openid']['claimed_id'])) {
+    $service = $_SESSION['openid']['service'];
+    $claimed_id = $_SESSION['openid']['claimed_id'];
+    unset($_SESSION['openid']['service']);
+    unset($_SESSION['openid']['claimed_id']);
     if (isset($response['openid.mode'])) {
       if ($response['openid.mode'] == 'cancel') {
         $response['status'] = 'cancel';
       }
       else {
-        if (openid_verify_assertion($op_endpoint, $response)) {
-          $response['openid.identity'] = $claimed_id;
+        if (openid_verify_assertion($service['uri'], $response)) {
+          // If the returned claimed_id is different from the session claimed_id,
+          // then we need to do discovery and make sure the op_endpoint matches.
+          if ($service['version'] == 2 && $response['openid.claimed_id'] != $claimed_id) {
+            $disco = openid_discovery($response['openid.claimed_id']);
+            if ($disco[0]['uri'] != $service['uri']) {
+              return $response;
+            }
+          }
+          else {
+            $response['openid.claimed_id'] = $claimed_id;
+          }
           $response['status'] = 'success';
         }
       }
@@ -371,12 +384,12 @@ function openid_association($op_endpoint) {
 function openid_authentication($response) {
   module_load_include('inc', 'openid');
 
-  $identity = $response['openid.identity'];
+  $identity = $response['openid.claimed_id'];
 
   $account = user_external_load($identity);
   if (isset($account->uid)) {
     if (!variable_get('user_email_verification', TRUE) || $account->login) {
-      user_external_login($account, $_SESSION['openid_user_login_values']);
+      user_external_login($account, $_SESSION['openid']['user_login_values']);
     }
     else {
       drupal_set_message(t('You must validate your email address for this account before logging in via OpenID'));
@@ -398,7 +411,7 @@ function openid_authentication($response) {
       // We were unable to register a valid new user, redirect to standard
       // user/register and prefill with the values we received.
       drupal_set_message(t('OpenID registration failed for the reasons listed. You may register now, or if you already have an account you can <a href="@login">log in</a> now and add your OpenID under "My Account"', array('@login' => url('user/login'))), 'error');
-      $_SESSION['openid'] = $form_state['values'];
+      $_SESSION['openid']['values'] = $form_state['values'];
       // We'll want to redirect back to the same place.
       $destination = drupal_get_destination();
       unset($_REQUEST['destination']);
@@ -443,8 +456,6 @@ function openid_association_request($public) {
 function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) {
   module_load_include('inc', 'openid');
 
-  $realm = ($return_to) ? $return_to : url('', array('absolute' => TRUE));
-
   $ns = ($version == 2) ? OPENID_NS_2_0 : OPENID_NS_1_0;
   $request =  array(
     'openid.ns' => $ns,
@@ -456,7 +467,7 @@ function openid_authentication_request($claimed_id, $identity, $return_to = '',
   );
 
   if ($version == 2) {
-    $request['openid.realm'] = $realm;
+    $request['openid.realm'] = url('', array('absolute' => TRUE));
   }
   else {
     $request['openid.trust_root'] = $realm;
diff --git a/modules/openid/openid.pages.inc b/modules/openid/openid.pages.inc
index 1dcfe04b7b85..981a6d11ba6b 100644
--- a/modules/openid/openid.pages.inc
+++ b/modules/openid/openid.pages.inc
@@ -10,7 +10,7 @@
  * Menu callback; Process an OpenID authentication.
  */
 function openid_authentication_page() {
-  $result = openid_complete($_REQUEST);
+  $result = openid_complete();
   switch ($result['status']) {
     case 'success':
       return openid_authentication($result);
@@ -32,10 +32,11 @@ function openid_user_identities($account) {
   drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
 
   // Check to see if we got a response
-  $result = openid_complete($_REQUEST);
+  $result = openid_complete();
   if ($result['status'] == 'success') {
-    db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','openid')", $account->uid, $result['openid.identity']);
-    drupal_set_message(t('Successfully added %identity', array('%identity' => $result['openid.identity'])));
+    $identity = $result['openid.claimed_id'];
+    db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','openid')", $account->uid, $identity);
+    drupal_set_message(t('Successfully added %identity', array('%identity' => $identity)));
   }
 
   $header = array(t('OpenID'), t('Operations'));
@@ -58,7 +59,7 @@ function openid_user_identities($account) {
  * @see openid_user_add_validate()
  */
 function openid_user_add() {
-  $form['openid_url'] = array(
+  $form['openid_identifier'] = array(
     '#type' => 'textfield',
     '#title' => t('OpenID'),
   );
@@ -68,13 +69,13 @@ function openid_user_add() {
 
 function openid_user_add_validate($form, &$form_state) {
   // Check for existing entries.
-  $claimed_id = _openid_normalize($form_state['values']['openid_url']);
+  $claimed_id = _openid_normalize($form_state['values']['openid_identifier']);
   if (db_result(db_query("SELECT authname FROM {authmap} WHERE authname='%s'", $claimed_id))) {
-    form_set_error('openid_url', t('That OpenID is already in use on this site.'));
+    form_set_error('openid_identifier', t('That OpenID is already in use on this site.'));
   }
   else {
     $return_to = url('user/'. arg(1) .'/openid', array('absolute' => TRUE));
-    openid_begin($form_state['values']['openid_url'], $return_to);
+    openid_begin($form_state['values']['openid_identifier'], $return_to);
   }
 }
 
-- 
GitLab