Index: modules/openid/openid.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/openid.module,v
retrieving revision 1.19.2.11
diff -u -9 -p -r1.19.2.11 openid.module
--- modules/openid/openid.module	11 Aug 2010 20:35:48 -0000	1.19.2.11
+++ modules/openid/openid.module	1 Dec 2010 22:47:45 -0000
@@ -528,49 +528,68 @@ function openid_authentication_request($
 /**
  * Attempt to verify the response received from the OpenID Provider.
  *
  * @param $service
  *   Array describing the OpenID provider.
  * @param $response
  *   Array of response values from the provider.
  *
  * @return boolean
+ * @see http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4
  */
 function openid_verify_assertion($service, $response) {
   module_load_include('inc', 'openid');
 
   // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.3
   // Check the Nonce to protect against replay attacks.
   if (!openid_verify_assertion_nonce($service, $response)) {
     return FALSE;
   }
 
   // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4
   // Verify the signatures.
   $valid = FALSE;
-  $association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE assoc_handle = '%s'", $response['openid.assoc_handle']));
+  $association = FALSE;
+
+  // If the OP returned a openid.invalidate_handle, we have to proceed with
+  // direct verification: ignore the openid.assoc_handle, even if present.
+  // See http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.1
+  if (!empty($response['openid.assoc_handle']) && empty($response['openid.invalidate_handle'])) {
+    $association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE assoc_handle = '%s'", $response['openid.assoc_handle']));
+  }
+
   if ($association && isset($association->session_type)) {
     // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.2
     // Verification using an association.
     $valid = openid_verify_assertion_signature($service, $association, $response);
   }
   else {
     // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.3
     // Direct verification.
+    // The verification requests contain all the fields from the response,
+    // except openid.mode.
     $request = $response;
     $request['openid.mode'] = 'check_authentication';
     $message = _openid_create_message($request);
     $headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
     $result = drupal_http_request($service['uri'], $headers, 'POST', _openid_encode_message($message));
     if (!isset($result->error)) {
       $response = _openid_parse_message($result->data);
+      $response['invalidate_handle'] = '{HMAC-SHA1}{4cec4944}{N+vsHA==}';
+
       if (strtolower(trim($response['is_valid'])) == 'true') {
         $valid = TRUE;
+        if (!empty($response['invalidate_handle'])) {
+          // This association handle has expired on the OP side, remove it from the
+          // database to avoid reusing it again on a subsequent authentication request.
+          // See http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.2.2
+          db_query("DELETE FROM {openid_association} WHERE assoc_handle = '%s'", $response['invalidate_handle']);
+        }
       }
       else {
         $valid = FALSE;
       }
     }
   }
   return $valid;
 }
 
