Project

General

Profile

Paste
Download (26.2 KB) Statistics
| Branch: | Revision:

root / drupal7 / modules / openid / openid.inc @ db2d93dd

1
<?php
2

    
3
/**
4
 * @file
5
 * OpenID utility functions.
6
 */
7

    
8
/**
9
 * Diffie-Hellman Key Exchange Default Value.
10
 *
11
 * This is used to establish an association between the Relying Party and the
12
 * OpenID Provider.
13
 *
14
 * See RFC 2631: http://www.ietf.org/rfc/rfc2631.txt
15
 */
16
define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801' .
17
       '966915404479707795314057629378541917580651227423698188993727816152646631' .
18
       '438561595825688188889951272158842675419950341258706556549803580104870537' .
19
       '681476726513255747040765857479291291572334510643245094715007229621094194' .
20
       '349783925984760375594985848253359305585439638443');
21

    
22
/**
23
 * Diffie-Hellman generator; used for Diffie-Hellman key exchange computations.
24
 */
25
define('OPENID_DH_DEFAULT_GEN', '2');
26

    
27
/**
28
 * SHA-1 hash block size; used for Diffie-Hellman key exchange computations.
29
 */
30
define('OPENID_SHA1_BLOCKSIZE', 64);
31

    
32
/**
33
 * Random number generator; used for Diffie-Hellman key exchange computations.
34
 */
35
define('OPENID_RAND_SOURCE', '/dev/urandom');
36

    
37
/**
38
 * OpenID Authentication 2.0 namespace URL.
39
 */
40
define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
41

    
42
/**
43
 * OpenID Authentication 1.1 namespace URL; used for backwards-compatibility.
44
 */
45
define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
46

    
47
/**
48
 * OpenID Authentication 1.0 namespace URL; used for backwards-compatibility.
49
 */
50
define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
51

    
52
/**
53
 * OpenID namespace used in Yadis documents.
54
 */
55
define('OPENID_NS_OPENID', 'http://openid.net/xmlns/1.0');
56

    
57
/**
58
 * OpenID Simple Registration extension.
59
 */
60
define('OPENID_NS_SREG', 'http://openid.net/extensions/sreg/1.1');
61

    
62
/**
63
 * OpenID Attribute Exchange extension.
64
 */
65
define('OPENID_NS_AX', 'http://openid.net/srv/ax/1.0');
66

    
67
/**
68
 * Extensible Resource Descriptor documents.
69
 */
70
define('OPENID_NS_XRD', 'xri://$xrd*($v*2.0)');
71

    
72
/**
73
 * Performs an HTTP 302 redirect (for the 1.x protocol).
74
 */
75
function openid_redirect_http($url, $message) {
76
  $query = array();
77
  foreach ($message as $key => $val) {
78
    $query[] = $key . '=' . urlencode($val);
79
  }
80

    
81
  $sep = (strpos($url, '?') === FALSE) ? '?' : '&';
82
  header('Location: ' . $url . $sep . implode('&', $query), TRUE, 302);
83

    
84
  drupal_exit();
85
}
86

    
87
/**
88
 * Creates a js auto-submit redirect for (for the 2.x protocol)
89
 */
90
function openid_redirect($url, $message) {
91
  global $language;
92

    
93
  $output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
94
  $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $language->language . '" lang="' . $language->language . '">' . "\n";
95
  $output .= "<head>\n";
96
  $output .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
97
  $output .= "<title>" . t('OpenID redirect') . "</title>\n";
98
  $output .= "</head>\n";
99
  $output .= "<body>\n";
100
  $elements = drupal_get_form('openid_redirect_form', $url, $message);
101
  $output .= drupal_render($elements);
102
  $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>' . "\n";
103
  $output .= "</body>\n";
104
  $output .= "</html>\n";
105
  print $output;
106

    
107
  drupal_exit();
108
}
109

    
110
function openid_redirect_form($form, &$form_state, $url, $message) {
111
  $form['#action'] = $url;
112
  $form['#method'] = "post";
113
  foreach ($message as $key => $value) {
114
    $form[$key] = array(
115
      '#type' => 'hidden',
116
      '#name' => $key,
117
      '#value' => $value,
118
    );
119
  }
120
  $form['actions'] = array('#type' => 'actions');
121
  $form['actions']['submit'] = array(
122
    '#type' => 'submit',
123
    '#prefix' => '<noscript><div>',
124
    '#suffix' => '</div></noscript>',
125
    '#value' => t('Send'),
126
  );
127

    
128
  return $form;
129
}
130

    
131
/**
132
 * Parse an XRDS document.
133
 *
134
 * @param $raw_xml
135
 *   A string containing the XRDS document.
136
 * @return
137
 *   An array of service entries.
138
 */
139
function _openid_xrds_parse($raw_xml) {
140
  $services = array();
141

    
142
  // For PHP version >= 5.2.11, we can use this function to protect against
143
  // malicious doctype declarations and other unexpected entity loading.
144
  // However, we will not rely on it, and reject any XML with a DOCTYPE.
145
  $disable_entity_loader = function_exists('libxml_disable_entity_loader');
146
  if ($disable_entity_loader) {
147
    $load_entities = libxml_disable_entity_loader(TRUE);
148
  }
149

    
150
  // Load the XML into a DOM document.
151
  $dom = new DOMDocument();
152
  @$dom->loadXML($raw_xml);
153

    
154
  // Since DOCTYPE declarations from an untrusted source could be malicious, we
155
  // stop parsing here and treat the XML as invalid since XRDS documents do not
156
  // require, and are not expected to have, a DOCTYPE.
157
  if (isset($dom->doctype)) {
158
    return array();
159
  }
160

    
161
  // Also stop parsing if there is an unreasonably large number of tags.
162
  if ($dom->getElementsByTagName('*')->length > variable_get('openid_xrds_maximum_tag_count', 30000)) {
163
    return array();
164
  }
165

    
166
  // Parse the DOM document for the information we need.
167
  if ($xml = simplexml_import_dom($dom)) {
168
    foreach ($xml->children(OPENID_NS_XRD)->XRD as $xrd) {
169
      foreach ($xrd->children(OPENID_NS_XRD)->Service as $service_element) {
170
        $service = array(
171
          'priority' => $service_element->attributes()->priority ? (int)$service_element->attributes()->priority : PHP_INT_MAX,
172
          'types' => array(),
173
          'uri' => (string)$service_element->children(OPENID_NS_XRD)->URI,
174
          'service' => $service_element,
175
          'xrd' => $xrd,
176
        );
177
        foreach ($service_element->Type as $type) {
178
          $service['types'][] = (string)$type;
179
        }
180
        if ($service_element->children(OPENID_NS_XRD)->LocalID) {
181
          $service['identity'] = (string)$service_element->children(OPENID_NS_XRD)->LocalID;
182
        }
183
        elseif ($service_element->children(OPENID_NS_OPENID)->Delegate) {
184
          $service['identity'] = (string)$service_element->children(OPENID_NS_OPENID)->Delegate;
185
        }
186
        else {
187
          $service['identity'] = FALSE;
188
        }
189
        $services[] = $service;
190
      }
191
    }
192
  }
193

    
194
  // Return the LIBXML options to the previous state before returning.
195
  if ($disable_entity_loader) {
196
    libxml_disable_entity_loader($load_entities);
197
  }
198

    
199
  return $services;
200
}
201

    
202
/**
203
 * Select a service element.
204
 *
205
 * The procedure is described in OpenID Authentication 2.0, section 7.3.2.
206
 *
207
 * A new entry is added to the returned array with the key 'version' and the
208
 * value 1 or 2 specifying the protocol version used by the service.
209
 *
210
 * @param $services
211
 *   An array of service arrays as returned by openid_discovery().
212
 * @return
213
 *   The selected service array, or NULL if no valid services were found.
214
 */
215
function _openid_select_service(array $services) {
216
  // Extensible Resource Identifier (XRI) Resolution Version 2.0, section 4.3.3:
217
  // Find the service with the highest priority (lowest integer value). If there
218
  // is a tie, select a random one, not just the first in the XML document.
219
  shuffle($services);
220
  $selected_service = NULL;
221
  $selected_type_priority = FALSE;
222

    
223
  // Search for an OP Identifier Element.
224
  foreach ($services as $service) {
225
    if (!empty($service['uri'])) {
226
      $type_priority = FALSE;
227
      if (in_array('http://specs.openid.net/auth/2.0/server', $service['types'])) {
228
        $service['version'] = 2;
229
        $type_priority = 1;
230
      }
231
      elseif (in_array('http://specs.openid.net/auth/2.0/signon', $service['types'])) {
232
        $service['version'] = 2;
233
        $type_priority = 2;
234
      }
235
      elseif (in_array(OPENID_NS_1_0, $service['types']) || in_array(OPENID_NS_1_1, $service['types'])) {
236
        $service['version'] = 1;
237
        $type_priority = 3;
238
      }
239

    
240
      if ($type_priority
241
          && (!$selected_service
242
              || $type_priority < $selected_type_priority
243
              || ($type_priority == $selected_type_priority && $service['priority'] < $selected_service['priority']))) {
244
        $selected_service = $service;
245
        $selected_type_priority = $type_priority;
246
      }
247
    }
248
  }
249

    
250
  if ($selected_service) {
251
    // Unset SimpleXMLElement instances that cannot be saved in $_SESSION.
252
    unset($selected_service['xrd']);
253
    unset($selected_service['service']);
254
  }
255

    
256
  return $selected_service;
257
}
258

    
259
/**
260
 * Determine if the given identifier is an XRI ID.
261
 */
262
function _openid_is_xri($identifier) {
263
  // Strip the xri:// scheme from the identifier if present.
264
  if (stripos($identifier, 'xri://') === 0) {
265
    $identifier = substr($identifier, 6);
266
  }
267

    
268
  // Test whether the identifier starts with an XRI global context symbol or (.
269
  $firstchar = substr($identifier, 0, 1);
270
  if (strpos("=@+$!(", $firstchar) !== FALSE) {
271
    return TRUE;
272
  }
273

    
274
  return FALSE;
275
}
276

    
277
/**
278
 * Normalize the given identifier.
279
 *
280
 * The procedure is described in OpenID Authentication 2.0, section 7.2.
281
 */
282
function openid_normalize($identifier) {
283
  $methods = module_invoke_all('openid_normalization_method_info');
284
  drupal_alter('openid_normalization_method_info', $methods);
285

    
286
  // Execute each method in turn, stopping after the first method accepted
287
  // the identifier.
288
  foreach ($methods as $method) {
289
    $result = $method($identifier);
290
    if ($result !== NULL) {
291
      $identifier = $result;
292
      break;
293
    }
294
  }
295

    
296
  return $identifier;
297
}
298

    
299
/**
300
 * OpenID normalization method: normalize XRI identifiers.
301
 */
302
function _openid_xri_normalize($identifier) {
303
  if (_openid_is_xri($identifier)) {
304
    if (stristr($identifier, 'xri://') !== FALSE) {
305
      $identifier = substr($identifier, 6);
306
    }
307
    return $identifier;
308
  }
309
}
310

    
311
/**
312
 * OpenID normalization method: normalize URL identifiers.
313
 */
314
function _openid_url_normalize($url) {
315
  $normalized_url = $url;
316

    
317
  if (stristr($url, '://') === FALSE) {
318
    $normalized_url = 'http://' . $url;
319
  }
320

    
321
  // Strip the fragment and fragment delimiter if present.
322
  $normalized_url = strtok($normalized_url, '#');
323

    
324
  if (substr_count($normalized_url, '/') < 3) {
325
    $normalized_url .= '/';
326
  }
327

    
328
  return $normalized_url;
329
}
330

    
331
/**
332
 * Create a serialized message packet as per spec: $key:$value\n .
333
 */
334
function _openid_create_message($data) {
335
  $serialized = '';
336

    
337
  foreach ($data as $key => $value) {
338
    if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
339
      return NULL;
340
    }
341
    $serialized .= "$key:$value\n";
342
  }
343
  return $serialized;
344
}
345

    
346
/**
347
 * Encode a message from _openid_create_message for HTTP Post
348
 */
349
function _openid_encode_message($message) {
350
  $encoded_message = '';
351

    
352
  $items = explode("\n", $message);
353
  foreach ($items as $item) {
354
    $parts = explode(':', $item, 2);
355

    
356
    if (count($parts) == 2) {
357
      if ($encoded_message != '') {
358
        $encoded_message .= '&';
359
      }
360
      $encoded_message .= rawurlencode(trim($parts[0])) . '=' . rawurlencode(trim($parts[1]));
361
    }
362
  }
363

    
364
  return $encoded_message;
365
}
366

    
367
/**
368
 * Convert a direct communication message
369
 * into an associative array.
370
 */
371
function _openid_parse_message($message) {
372
  $parsed_message = array();
373

    
374
  $items = explode("\n", $message);
375
  foreach ($items as $item) {
376
    $parts = explode(':', $item, 2);
377

    
378
    if (count($parts) == 2) {
379
      $parsed_message[$parts[0]] = $parts[1];
380
    }
381
  }
382

    
383
  return $parsed_message;
384
}
385

    
386
/**
387
 * Return a nonce value - formatted per OpenID spec.
388
 *
389
 * NOTE: This nonce is not cryptographically secure and only suitable for use
390
 * by the test framework.
391
 */
392
function _openid_nonce() {
393
  // YYYY-MM-DDThh:mm:ssZ, plus some optional extra unique characters.
394
  return gmdate('Y-m-d\TH:i:s\Z') .
395
    chr(mt_rand(0, 25) + 65) .
396
    chr(mt_rand(0, 25) + 65) .
397
    chr(mt_rand(0, 25) + 65) .
398
    chr(mt_rand(0, 25) + 65);
399
}
400

    
401
/**
402
 * Pull the href attribute out of an html link element.
403
 */
404
function _openid_link_href($rel, $html) {
405
  $rel = preg_quote($rel);
406
  preg_match('|<link\s+rel=["\'](.*)' . $rel . '(.*)["\'](.*)/?>|iUs', $html, $matches);
407
  if (isset($matches[3])) {
408
    preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
409
    return trim($href[1]);
410
  }
411
  return FALSE;
412
}
413

    
414
/**
415
 * Pull the http-equiv attribute out of an html meta element
416
 */
417
function _openid_meta_httpequiv($equiv, $html) {
418
  preg_match('|<meta\s+http-equiv=["\']' . $equiv . '["\'](.*)/?>|iUs', $html, $matches);
419
  if (isset($matches[1])) {
420
    preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
421
    if (isset($content[1])) {
422
      return $content[1];
423
    }
424
  }
425
  return FALSE;
426
}
427

    
428
/**
429
 * Sign certain keys in a message
430
 * @param $association - object loaded from openid_association or openid_server_association table
431
 *              - important fields are ->assoc_type and ->mac_key
432
 * @param $message_array - array of entire message about to be sent
433
 * @param $keys_to_sign - keys in the message to include in signature (without
434
 *  'openid.' appended)
435
 */
436
function _openid_signature($association, $message_array, $keys_to_sign) {
437
  $signature = '';
438
  $sign_data = array();
439

    
440
  foreach ($keys_to_sign as $key) {
441
    if (isset($message_array['openid.' . $key])) {
442
      $sign_data[$key] = $message_array['openid.' . $key];
443
    }
444
  }
445

    
446
  $message = _openid_create_message($sign_data);
447
  $secret = base64_decode($association->mac_key);
448
  $signature = _openid_hmac($secret, $message);
449

    
450
  return base64_encode($signature);
451
}
452

    
453
function _openid_hmac($key, $text) {
454
  if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
455
    $key = sha1($key, TRUE);
456
  }
457

    
458
  $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
459
  $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
460
  $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
461
  $hash1 = sha1(($key ^ $ipad) . $text, TRUE);
462
  $hmac = sha1(($key ^ $opad) . $hash1, TRUE);
463

    
464
  return $hmac;
465
}
466

    
467
function _openid_dh_base64_to_long($str) {
468
  $b64 = base64_decode($str);
469

    
470
  return _openid_dh_binary_to_long($b64);
471
}
472

    
473
function _openid_dh_long_to_base64($str) {
474
  return base64_encode(_openid_dh_long_to_binary($str));
475
}
476

    
477
function _openid_dh_binary_to_long($str) {
478
  $bytes = array_merge(unpack('C*', $str));
479

    
480
  $n = 0;
481
  foreach ($bytes as $byte) {
482
    $n = _openid_math_mul($n, pow(2, 8));
483
    $n = _openid_math_add($n, $byte);
484
  }
485

    
486
  return $n;
487
}
488

    
489
function _openid_dh_long_to_binary($long) {
490
  $cmp = _openid_math_cmp($long, 0);
491
  if ($cmp < 0) {
492
    return FALSE;
493
  }
494

    
495
  if ($cmp == 0) {
496
    return "\x00";
497
  }
498

    
499
  $bytes = array();
500

    
501
  while (_openid_math_cmp($long, 0) > 0) {
502
    array_unshift($bytes, _openid_math_mod($long, 256));
503
    $long = _openid_math_div($long, pow(2, 8));
504
  }
505

    
506
  if ($bytes && ($bytes[0] > 127)) {
507
    array_unshift($bytes, 0);
508
  }
509

    
510
  $string = '';
511
  foreach ($bytes as $byte) {
512
    $string .= pack('C', $byte);
513
  }
514

    
515
  return $string;
516
}
517

    
518
function _openid_dh_xorsecret($shared, $secret) {
519
  $dh_shared_str = _openid_dh_long_to_binary($shared);
520
  $sha1_dh_shared = sha1($dh_shared_str, TRUE);
521
  $xsecret = "";
522
  for ($i = 0; $i < strlen($secret); $i++) {
523
    $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
524
  }
525

    
526
  return $xsecret;
527
}
528

    
529
function _openid_dh_rand($stop) {
530
  $duplicate_cache = &drupal_static(__FUNCTION__, array());
531

    
532
  // Used as the key for the duplicate cache
533
  $rbytes = _openid_dh_long_to_binary($stop);
534

    
535
  if (isset($duplicate_cache[$rbytes])) {
536
    list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
537
  }
538
  else {
539
    if ($rbytes[0] == "\x00") {
540
      $nbytes = strlen($rbytes) - 1;
541
    }
542
    else {
543
      $nbytes = strlen($rbytes);
544
    }
545

    
546
    $mxrand = _openid_math_pow(256, $nbytes);
547

    
548
    // If we get a number less than this, then it is in the
549
    // duplicated range.
550
    $duplicate = _openid_math_mod($mxrand, $stop);
551

    
552
    if (count($duplicate_cache) > 10) {
553
      $duplicate_cache = array();
554
    }
555

    
556
    $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
557
  }
558

    
559
  do {
560
    $bytes = "\x00" . drupal_random_bytes($nbytes);
561
    $n = _openid_dh_binary_to_long($bytes);
562
    // Keep looping if this value is in the low duplicated range.
563
  } while (_openid_math_cmp($n, $duplicate) < 0);
564

    
565
  return _openid_math_mod($n, $stop);
566
}
567

    
568
function _openid_get_bytes($num_bytes) {
569
  return drupal_random_bytes($num_bytes);
570
}
571

    
572
function _openid_response($str = NULL) {
573
  $data = array();
574

    
575
  if (isset($_SERVER['REQUEST_METHOD'])) {
576
    $data = _openid_get_params($_SERVER['QUERY_STRING']);
577

    
578
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
579
      $str = file_get_contents('php://input');
580

    
581
      $post = array();
582
      if ($str !== FALSE) {
583
        $post = _openid_get_params($str);
584
      }
585

    
586
      $data = array_merge($data, $post);
587
    }
588
  }
589

    
590
  return $data;
591
}
592

    
593
function _openid_get_params($str) {
594
  $chunks = explode("&", $str);
595

    
596
  $data = array();
597
  foreach ($chunks as $chunk) {
598
    $parts = explode("=", $chunk, 2);
599

    
600
    if (count($parts) == 2) {
601
      list($k, $v) = $parts;
602
      $data[$k] = urldecode($v);
603
    }
604
  }
605
  return $data;
606
}
607

    
608
/**
609
 * Extract all the parameters belonging to an extension in a response message.
610
 *
611
 * OpenID 2.0 defines a simple extension mechanism, based on a namespace prefix.
612
 *
613
 * Each request or response can define a prefix using:
614
 * @code
615
 *   openid.ns.[prefix] = [extension_namespace]
616
 *   openid.[prefix].[key1] = [value1]
617
 *   openid.[prefix].[key2] = [value2]
618
 *   ...
619
 * @endcode
620
 *
621
 * This function extracts all the keys belonging to an extension namespace in a
622
 * response, optionally using a fallback prefix if none is provided in the response.
623
 *
624
 * Note that you cannot assume that a given extension namespace will use the same
625
 * prefix on the response and the request: each party may use a different prefix
626
 * to refer to the same namespace.
627
 *
628
 * @param $response
629
 *   The response array.
630
 * @param $extension_namespace
631
 *   The namespace of the extension.
632
 * @param $fallback_prefix
633
 *   An optional prefix that will be used in case no prefix is found for the
634
 *   target extension namespace.
635
 * @param $only_signed
636
 *   Return only keys that are included in the message signature in openid.sig.
637
 *   Unsigned fields may have been modified or added by other parties than the
638
 *   OpenID Provider.
639
 *
640
 * @return
641
 *   An associative array containing all the parameters in the response message
642
 *   that belong to the extension. The keys are stripped from their namespace
643
 *   prefix.
644
 *
645
 * @see http://openid.net/specs/openid-authentication-2_0.html#extensions
646
 */
647
function openid_extract_namespace($response, $extension_namespace, $fallback_prefix = NULL, $only_signed = FALSE) {
648
  $signed_keys = explode(',', $response['openid.signed']);
649

    
650
  // Find the namespace prefix.
651
  $prefix = $fallback_prefix;
652
  foreach ($response as $key => $value) {
653
    if ($value == $extension_namespace && preg_match('/^openid\.ns\.([^.]+)$/', $key, $matches)) {
654
      $prefix = $matches[1];
655
      if ($only_signed && !in_array('ns.' . $matches[1], $signed_keys)) {
656
        // The namespace was defined but was not signed as required. In this
657
        // case we do not fall back to $fallback_prefix.
658
        $prefix = NULL;
659
      }
660
      break;
661
    }
662
  }
663

    
664
  // Now extract the namespace keys from the response.
665
  $output = array();
666
  if (!isset($prefix)) {
667
    return $output;
668
  }
669
  foreach ($response as $key => $value) {
670
    if (preg_match('/^openid\.' . $prefix . '\.(.+)$/', $key, $matches)) {
671
      $local_key = $matches[1];
672
      if (!$only_signed || in_array($prefix . '.' . $local_key, $signed_keys)) {
673
        $output[$local_key] = $value;
674
      }
675
    }
676
  }
677

    
678
  return $output;
679
}
680

    
681
/**
682
 * Extracts values from an OpenID AX Response.
683
 *
684
 * The values can be returned in two forms:
685
 *   - only openid.ax.value.<alias> (for single-valued answers)
686
 *   - both openid.ax.count.<alias> and openid.ax.value.<alias>.<count> (for both
687
 *     single and multiple-valued answers)
688
 *
689
 * @param $values
690
 *   An array as returned by openid_extract_namespace(..., OPENID_NS_AX).
691
 * @param $uris
692
 *   An array of identifier URIs.
693
 * @return
694
 *   An array of values.
695
 * @see http://openid.net/specs/openid-attribute-exchange-1_0.html#fetch_response
696
 */
697
function openid_extract_ax_values($values, $uris) {
698
  $output = array();
699
  foreach ($values as $key => $value) {
700
    if (in_array($value, $uris) && preg_match('/^type\.([^.]+)$/', $key, $matches)) {
701
      $alias = $matches[1];
702
      if (isset($values['count.' . $alias])) {
703
        for ($i = 1; $i <= $values['count.' . $alias]; $i++) {
704
          $output[] = $values['value.' . $alias . '.' . $i];
705
        }
706
      }
707
      elseif (isset($values['value.' . $alias])) {
708
        $output[] = $values['value.' . $alias];
709
      }
710
      break;
711
    }
712
  }
713
  return $output;
714
}
715

    
716
/**
717
 * Determine the available math library GMP vs. BCMath, favouring GMP for performance.
718
 */
719
function _openid_get_math_library() {
720
  // Not drupal_static(), because a function is not going to disappear and
721
  // change the output of this under any circumstances.
722
  static $library;
723

    
724
  if (empty($library)) {
725
    if (function_exists('gmp_add')) {
726
      $library =  'gmp';
727
    }
728
    elseif (function_exists('bcadd')) {
729
      $library = 'bcmath';
730
    }
731
  }
732

    
733
  return $library;
734
}
735

    
736
/**
737
 * Calls the add function from the available math library for OpenID.
738
 */
739
function _openid_math_add($x, $y) {
740
  $library = _openid_get_math_library();
741
  switch ($library) {
742
    case 'gmp':
743
      return gmp_strval(gmp_add($x, $y));
744
    case 'bcmath':
745
      return bcadd($x, $y);
746
  }
747
}
748

    
749
/**
750
 * Calls the mul function from the available math library for OpenID.
751
 */
752
function _openid_math_mul($x, $y) {
753
  $library = _openid_get_math_library();
754
  switch ($library) {
755
    case 'gmp':
756
      return gmp_mul($x, $y);
757
    case 'bcmath':
758
      return bcmul($x, $y);
759
  }
760
}
761

    
762
/**
763
 * Calls the div function from the available math library for OpenID.
764
 */
765
function _openid_math_div($x, $y) {
766
  $library = _openid_get_math_library();
767
  switch ($library) {
768
    case 'gmp':
769
      return gmp_div($x, $y);
770
    case 'bcmath':
771
      return bcdiv($x, $y);
772
  }
773
}
774

    
775
/**
776
 * Calls the cmp function from the available math library for OpenID.
777
 */
778
function _openid_math_cmp($x, $y) {
779
  $library = _openid_get_math_library();
780
  switch ($library) {
781
    case 'gmp':
782
      return gmp_cmp($x, $y);
783
    case 'bcmath':
784
      return bccomp($x, $y);
785
  }
786
}
787

    
788
/**
789
 * Calls the mod function from the available math library for OpenID.
790
 */
791
function _openid_math_mod($x, $y) {
792
  $library = _openid_get_math_library();
793
  switch ($library) {
794
    case 'gmp':
795
      return gmp_mod($x, $y);
796
    case 'bcmath':
797
      return bcmod($x, $y);
798
  }
799
}
800

    
801
/**
802
 * Calls the pow function from the available math library for OpenID.
803
 */
804
function _openid_math_pow($x, $y) {
805
  $library = _openid_get_math_library();
806
  switch ($library) {
807
    case 'gmp':
808
      return gmp_pow($x, $y);
809
    case 'bcmath':
810
      return bcpow($x, $y);
811
  }
812
}
813

    
814
/**
815
 * Calls the mul function from the available math library for OpenID.
816
 */
817
function _openid_math_powmod($x, $y, $z) {
818
  $library = _openid_get_math_library();
819
  switch ($library) {
820
    case 'gmp':
821
      return gmp_powm($x, $y, $z);
822
    case 'bcmath':
823
      return bcpowmod($x, $y, $z);
824
  }
825
}
826

    
827
/**
828
 * Provides transition for accounts with possibly invalid OpenID identifiers in authmap.
829
 *
830
 * This function provides a less safe but more unobtrusive procedure for users
831
 * who cannot login with their OpenID identifiers. OpenID identifiers in the
832
 * authmap could be incomplete due to invalid OpenID implementation in previous
833
 * versions of Drupal (e.g. fragment part of the identifier could be missing).
834
 * For more information see http://drupal.org/node/1120290.
835
 *
836
 * @param string $identity
837
 *   The user's claimed OpenID identifier.
838
 *
839
 * @return
840
 *   A fully-loaded user object if the user is found or FALSE if not found.
841
 */
842
function _openid_invalid_openid_transition($identity, $response) {
843
  $account = FALSE;
844
  $fallback_account = NULL;
845
  $fallback_identity = $identity;
846

    
847
  // Try to strip the fragment if it is present.
848
  if (strpos($fallback_identity, '#') !== FALSE) {
849
    $fallback_identity = preg_replace('/#.*/', '', $fallback_identity);
850
    $fallback_account = user_external_load($fallback_identity);
851
  }
852

    
853
  // Try to replace HTTPS with HTTP. OpenID providers often redirect
854
  // from http to https, but Drupal didn't follow the redirect.
855
  if (!$fallback_account && strpos($fallback_identity, 'https://') !== FALSE) {
856
    $fallback_identity = str_replace('https://', 'http://', $fallback_identity);
857
    $fallback_account = user_external_load($fallback_identity);
858
  }
859

    
860
  // Try to use original identifier.
861
  if (!$fallback_account && isset($_SESSION['openid']['user_login_values']['openid_identifier'])) {
862
    $fallback_identity = openid_normalize($_SESSION['openid']['user_login_values']['openid_identifier']);
863
    $fallback_account = user_external_load($fallback_identity);
864
  }
865

    
866
  if ($fallback_account) {
867
    // Try to extract e-mail address from Simple Registration (SREG) or
868
    // Attribute Exchanges (AX) keys.
869
    $email = '';
870
    $sreg_values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', TRUE);
871
    $ax_values = openid_extract_namespace($response, OPENID_NS_AX, 'ax', TRUE);
872
    if (!empty($sreg_values['email']) && valid_email_address($sreg_values['email'])) {
873
      $email = $sreg_values['email'];
874
    }
875
    elseif ($ax_mail_values = openid_extract_ax_values($ax_values, array('http://axschema.org/contact/email', 'http://schema.openid.net/contact/email'))) {
876
      $email = current($ax_mail_values);
877
    }
878

    
879
    // If this e-mail address is the same as the e-mail address found in user
880
    // account, login the user and update the claimed identifier.
881
    if ($email && ($email == $fallback_account->mail || $email == $fallback_account->init)) {
882
      $query = db_insert('authmap')
883
        ->fields(array(
884
          'authname' => $identity,
885
          'uid' => $fallback_account->uid,
886
          'module' => 'openid',
887
        ))
888
        ->execute();
889
      drupal_set_message(t('New OpenID identifier %identity was added as a replacement for invalid identifier %invalid_identity. To finish the invalid OpenID transition process, please go to your <a href="@openid_url">OpenID identities page</a> and remove the old identifier %invalid_identity.', array('%invalid_identity' => $fallback_identity, '%identity' => $identity, '@openid_url' => 'user/' . $fallback_account->uid . '/openid')));
890
      // Set the account to the found one.
891
      $account = $fallback_account;
892
    }
893
    else {
894
      drupal_set_message(t('There is already an existing account associated with the OpenID identifier that you have provided. However, due to a bug in the previous version of the authentication system, we can\'t be sure that this account belongs to you. If you are new on this site, please continue registering the new user account. If you already have a registered account on this site associated with the provided OpenID identifier, please try to <a href="@url_password">reset the password</a> or contact the site administrator.', array('@url_password' => 'user/password')), 'warning');
895
    }
896
  }
897

    
898
  return $account;
899
}