Projet

Général

Profil

Paste
Télécharger (26 ko) Statistiques
| Branche: | Révision:

root / htmltest / modules / openid / openid.inc @ 85ad3d82

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

    
189
  // Return the LIBXML options to the previous state before returning.
190
  if ($disable_entity_loader) {
191
    libxml_disable_entity_loader($load_entities);
192
  }
193

    
194
  return $services;
195
}
196

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

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

    
235
      if ($type_priority
236
          && (!$selected_service
237
              || $type_priority < $selected_type_priority
238
              || ($type_priority == $selected_type_priority && $service['priority'] < $selected_service['priority']))) {
239
        $selected_service = $service;
240
        $selected_type_priority = $type_priority;
241
      }
242
    }
243
  }
244

    
245
  if ($selected_service) {
246
    // Unset SimpleXMLElement instances that cannot be saved in $_SESSION.
247
    unset($selected_service['xrd']);
248
    unset($selected_service['service']);
249
  }
250

    
251
  return $selected_service;
252
}
253

    
254
/**
255
 * Determine if the given identifier is an XRI ID.
256
 */
257
function _openid_is_xri($identifier) {
258
  // Strip the xri:// scheme from the identifier if present.
259
  if (stripos($identifier, 'xri://') === 0) {
260
    $identifier = substr($identifier, 6);
261
  }
262

    
263
  // Test whether the identifier starts with an XRI global context symbol or (.
264
  $firstchar = substr($identifier, 0, 1);
265
  if (strpos("=@+$!(", $firstchar) !== FALSE) {
266
    return TRUE;
267
  }
268

    
269
  return FALSE;
270
}
271

    
272
/**
273
 * Normalize the given identifier.
274
 *
275
 * The procedure is described in OpenID Authentication 2.0, section 7.2.
276
 */
277
function openid_normalize($identifier) {
278
  $methods = module_invoke_all('openid_normalization_method_info');
279
  drupal_alter('openid_normalization_method_info', $methods);
280

    
281
  // Execute each method in turn, stopping after the first method accepted
282
  // the identifier.
283
  foreach ($methods as $method) {
284
    $result = $method($identifier);
285
    if ($result !== NULL) {
286
      $identifier = $result;
287
      break;
288
    }
289
  }
290

    
291
  return $identifier;
292
}
293

    
294
/**
295
 * OpenID normalization method: normalize XRI identifiers.
296
 */
297
function _openid_xri_normalize($identifier) {
298
  if (_openid_is_xri($identifier)) {
299
    if (stristr($identifier, 'xri://') !== FALSE) {
300
      $identifier = substr($identifier, 6);
301
    }
302
    return $identifier;
303
  }
304
}
305

    
306
/**
307
 * OpenID normalization method: normalize URL identifiers.
308
 */
309
function _openid_url_normalize($url) {
310
  $normalized_url = $url;
311

    
312
  if (stristr($url, '://') === FALSE) {
313
    $normalized_url = 'http://' . $url;
314
  }
315

    
316
  // Strip the fragment and fragment delimiter if present.
317
  $normalized_url = strtok($normalized_url, '#');
318

    
319
  if (substr_count($normalized_url, '/') < 3) {
320
    $normalized_url .= '/';
321
  }
322

    
323
  return $normalized_url;
324
}
325

    
326
/**
327
 * Create a serialized message packet as per spec: $key:$value\n .
328
 */
329
function _openid_create_message($data) {
330
  $serialized = '';
331

    
332
  foreach ($data as $key => $value) {
333
    if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
334
      return NULL;
335
    }
336
    $serialized .= "$key:$value\n";
337
  }
338
  return $serialized;
339
}
340

    
341
/**
342
 * Encode a message from _openid_create_message for HTTP Post
343
 */
344
function _openid_encode_message($message) {
345
  $encoded_message = '';
346

    
347
  $items = explode("\n", $message);
348
  foreach ($items as $item) {
349
    $parts = explode(':', $item, 2);
350

    
351
    if (count($parts) == 2) {
352
      if ($encoded_message != '') {
353
        $encoded_message .= '&';
354
      }
355
      $encoded_message .= rawurlencode(trim($parts[0])) . '=' . rawurlencode(trim($parts[1]));
356
    }
357
  }
358

    
359
  return $encoded_message;
360
}
361

    
362
/**
363
 * Convert a direct communication message
364
 * into an associative array.
365
 */
366
function _openid_parse_message($message) {
367
  $parsed_message = array();
368

    
369
  $items = explode("\n", $message);
370
  foreach ($items as $item) {
371
    $parts = explode(':', $item, 2);
372

    
373
    if (count($parts) == 2) {
374
      $parsed_message[$parts[0]] = $parts[1];
375
    }
376
  }
377

    
378
  return $parsed_message;
379
}
380

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

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

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

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

    
435
  foreach ($keys_to_sign as $key) {
436
    if (isset($message_array['openid.' . $key])) {
437
      $sign_data[$key] = $message_array['openid.' . $key];
438
    }
439
  }
440

    
441
  $message = _openid_create_message($sign_data);
442
  $secret = base64_decode($association->mac_key);
443
  $signature = _openid_hmac($secret, $message);
444

    
445
  return base64_encode($signature);
446
}
447

    
448
function _openid_hmac($key, $text) {
449
  if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
450
    $key = sha1($key, TRUE);
451
  }
452

    
453
  $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
454
  $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
455
  $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
456
  $hash1 = sha1(($key ^ $ipad) . $text, TRUE);
457
  $hmac = sha1(($key ^ $opad) . $hash1, TRUE);
458

    
459
  return $hmac;
460
}
461

    
462
function _openid_dh_base64_to_long($str) {
463
  $b64 = base64_decode($str);
464

    
465
  return _openid_dh_binary_to_long($b64);
466
}
467

    
468
function _openid_dh_long_to_base64($str) {
469
  return base64_encode(_openid_dh_long_to_binary($str));
470
}
471

    
472
function _openid_dh_binary_to_long($str) {
473
  $bytes = array_merge(unpack('C*', $str));
474

    
475
  $n = 0;
476
  foreach ($bytes as $byte) {
477
    $n = _openid_math_mul($n, pow(2, 8));
478
    $n = _openid_math_add($n, $byte);
479
  }
480

    
481
  return $n;
482
}
483

    
484
function _openid_dh_long_to_binary($long) {
485
  $cmp = _openid_math_cmp($long, 0);
486
  if ($cmp < 0) {
487
    return FALSE;
488
  }
489

    
490
  if ($cmp == 0) {
491
    return "\x00";
492
  }
493

    
494
  $bytes = array();
495

    
496
  while (_openid_math_cmp($long, 0) > 0) {
497
    array_unshift($bytes, _openid_math_mod($long, 256));
498
    $long = _openid_math_div($long, pow(2, 8));
499
  }
500

    
501
  if ($bytes && ($bytes[0] > 127)) {
502
    array_unshift($bytes, 0);
503
  }
504

    
505
  $string = '';
506
  foreach ($bytes as $byte) {
507
    $string .= pack('C', $byte);
508
  }
509

    
510
  return $string;
511
}
512

    
513
function _openid_dh_xorsecret($shared, $secret) {
514
  $dh_shared_str = _openid_dh_long_to_binary($shared);
515
  $sha1_dh_shared = sha1($dh_shared_str, TRUE);
516
  $xsecret = "";
517
  for ($i = 0; $i < strlen($secret); $i++) {
518
    $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
519
  }
520

    
521
  return $xsecret;
522
}
523

    
524
function _openid_dh_rand($stop) {
525
  $duplicate_cache = &drupal_static(__FUNCTION__, array());
526

    
527
  // Used as the key for the duplicate cache
528
  $rbytes = _openid_dh_long_to_binary($stop);
529

    
530
  if (isset($duplicate_cache[$rbytes])) {
531
    list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
532
  }
533
  else {
534
    if ($rbytes[0] == "\x00") {
535
      $nbytes = strlen($rbytes) - 1;
536
    }
537
    else {
538
      $nbytes = strlen($rbytes);
539
    }
540

    
541
    $mxrand = _openid_math_pow(256, $nbytes);
542

    
543
    // If we get a number less than this, then it is in the
544
    // duplicated range.
545
    $duplicate = _openid_math_mod($mxrand, $stop);
546

    
547
    if (count($duplicate_cache) > 10) {
548
      $duplicate_cache = array();
549
    }
550

    
551
    $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
552
  }
553

    
554
  do {
555
    $bytes = "\x00" . drupal_random_bytes($nbytes);
556
    $n = _openid_dh_binary_to_long($bytes);
557
    // Keep looping if this value is in the low duplicated range.
558
  } while (_openid_math_cmp($n, $duplicate) < 0);
559

    
560
  return _openid_math_mod($n, $stop);
561
}
562

    
563
function _openid_get_bytes($num_bytes) {
564
  return drupal_random_bytes($num_bytes);
565
}
566

    
567
function _openid_response($str = NULL) {
568
  $data = array();
569

    
570
  if (isset($_SERVER['REQUEST_METHOD'])) {
571
    $data = _openid_get_params($_SERVER['QUERY_STRING']);
572

    
573
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
574
      $str = file_get_contents('php://input');
575

    
576
      $post = array();
577
      if ($str !== FALSE) {
578
        $post = _openid_get_params($str);
579
      }
580

    
581
      $data = array_merge($data, $post);
582
    }
583
  }
584

    
585
  return $data;
586
}
587

    
588
function _openid_get_params($str) {
589
  $chunks = explode("&", $str);
590

    
591
  $data = array();
592
  foreach ($chunks as $chunk) {
593
    $parts = explode("=", $chunk, 2);
594

    
595
    if (count($parts) == 2) {
596
      list($k, $v) = $parts;
597
      $data[$k] = urldecode($v);
598
    }
599
  }
600
  return $data;
601
}
602

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

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

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

    
673
  return $output;
674
}
675

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

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

    
719
  if (empty($library)) {
720
    if (function_exists('gmp_add')) {
721
      $library =  'gmp';
722
    }
723
    elseif (function_exists('bcadd')) {
724
      $library = 'bcmath';
725
    }
726
  }
727

    
728
  return $library;
729
}
730

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

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

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

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

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

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

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

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

    
842
  // Try to strip the fragment if it is present.
843
  if (strpos($fallback_identity, '#') !== FALSE) {
844
    $fallback_identity = preg_replace('/#.*/', '', $fallback_identity);
845
    $fallback_account = user_external_load($fallback_identity);
846
  }
847

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

    
855
  // Try to use original identifier.
856
  if (!$fallback_account && isset($_SESSION['openid']['user_login_values']['openid_identifier'])) {
857
    $fallback_identity = openid_normalize($_SESSION['openid']['user_login_values']['openid_identifier']);
858
    $fallback_account = user_external_load($fallback_identity);
859
  }
860

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

    
874
    // If this e-mail address is the same as the e-mail address found in user
875
    // account, login the user and update the claimed identifier.
876
    if ($email && ($email == $fallback_account->mail || $email == $fallback_account->init)) {
877
      $query = db_insert('authmap')
878
        ->fields(array(
879
          'authname' => $identity,
880
          'uid' => $fallback_account->uid,
881
          'module' => 'openid',
882
        ))
883
        ->execute();
884
      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')));
885
      // Set the account to the found one.
886
      $account = $fallback_account;
887
    }
888
    else {
889
      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');
890
    }
891
  }
892

    
893
  return $account;
894
}