/[drupal]/drupal/modules/openid/openid.inc
ViewVC logotype

Contents of /drupal/modules/openid/openid.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.21 - (show annotations) (download) (as text)
Tue Oct 13 21:16:43 2009 UTC (6 weeks, 2 days ago) by dries
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10
Changes since 1.20: +3 -9 lines
File MIME type: text/x-php
- Patch #601570 by effulgentsia: hook_exit() and other cleanup needs to happen for AJAX requests too.
1 <?php
2 // $Id: openid.inc,v 1.20 2009/09/18 00:12:47 webchick Exp $
3
4 /**
5 * @file
6 * OpenID utility functions.
7 */
8
9 /**
10 * Diffie-Hellman Key Exchange Default Value.
11 *
12 * This is used to establish an association between the Relying Party and the
13 * OpenID Provider.
14 *
15 * See RFC 2631: http://www.ietf.org/rfc/rfc2631.txt
16 */
17 define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801' .
18 '966915404479707795314057629378541917580651227423698188993727816152646631' .
19 '438561595825688188889951272158842675419950341258706556549803580104870537' .
20 '681476726513255747040765857479291291572334510643245094715007229621094194' .
21 '349783925984760375594985848253359305585439638443');
22
23 /**
24 * Diffie-Hellman generator; used for Diffie-Hellman key exchange computations.
25 */
26 define('OPENID_DH_DEFAULT_GEN', '2');
27
28 /**
29 * SHA-1 hash block size; used for Diffie-Hellman key exchange computations.
30 */
31 define('OPENID_SHA1_BLOCKSIZE', 64);
32
33 /**
34 * Random number generator; used for Diffie-Hellman key exchange computations.
35 */
36 define('OPENID_RAND_SOURCE', '/dev/urandom');
37
38 /**
39 * OpenID Authentication 2.0 namespace URL.
40 */
41 define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
42
43 /**
44 * OpenID Authentication 1.1 namespace URL; used for backwards-compatibility.
45 */
46 define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
47
48 /**
49 * OpenID Authentication 1.0 namespace URL; used for backwards-compatibility.
50 */
51 define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
52
53 /**
54 * Performs an HTTP 302 redirect (for the 1.x protocol).
55 */
56 function openid_redirect_http($url, $message) {
57 $query = array();
58 foreach ($message as $key => $val) {
59 $query[] = $key . '=' . urlencode($val);
60 }
61
62 $sep = (strpos($url, '?') === FALSE) ? '?' : '&';
63 header('Location: ' . $url . $sep . implode('&', $query), TRUE, 302);
64
65 drupal_exit();
66 }
67
68 /**
69 * Creates a js auto-submit redirect for (for the 2.x protocol)
70 */
71 function openid_redirect($url, $message) {
72 $output = '<html><head><title>' . t('OpenID redirect') . "</title></head>\n<body>";
73 $output .= drupal_render(drupal_get_form('openid_redirect_form', $url, $message));
74 $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>';
75 $output .= "</body></html>\n";
76 print $output;
77
78 drupal_exit();
79 }
80
81 function openid_redirect_form($form, &$form_state, $url, $message) {
82 $form['#action'] = $url;
83 $form['#method'] = "post";
84 foreach ($message as $key => $value) {
85 $form[$key] = array(
86 '#type' => 'hidden',
87 '#name' => $key,
88 '#value' => $value,
89 );
90 }
91 $form['submit'] = array(
92 '#type' => 'submit',
93 '#prefix' => '<noscript>',
94 '#suffix' => '</noscript>',
95 '#value' => t('Send'),
96 );
97
98 return $form;
99 }
100
101 /**
102 * Determine if the given identifier is an XRI ID.
103 */
104 function _openid_is_xri($identifier) {
105 // Strip the xri:// scheme from the identifier if present.
106 if (stripos($identifier, 'xri://') !== FALSE) {
107 $identifier = substr($identifier, 6);
108 }
109
110
111 // Test whether the identifier starts with an XRI global context symbol or (.
112 $firstchar = substr($identifier, 0, 1);
113 if (strpos("=@+$!(", $firstchar) !== FALSE) {
114 return TRUE;
115 }
116
117 return FALSE;
118 }
119
120 /**
121 * Normalize the given identifier as per spec.
122 */
123 function _openid_normalize($identifier) {
124 if (_openid_is_xri($identifier)) {
125 return _openid_normalize_xri($identifier);
126 }
127 else {
128 return _openid_normalize_url($identifier);
129 }
130 }
131
132 function _openid_normalize_xri($xri) {
133 $normalized_xri = $xri;
134 if (stristr($xri, 'xri://') !== FALSE) {
135 $normalized_xri = substr($xri, 6);
136 }
137 return $normalized_xri;
138 }
139
140 function _openid_normalize_url($url) {
141 $normalized_url = $url;
142
143 if (stristr($url, '://') === FALSE) {
144 $normalized_url = 'http://' . $url;
145 }
146
147 if (substr_count($normalized_url, '/') < 3) {
148 $normalized_url .= '/';
149 }
150
151 return $normalized_url;
152 }
153
154 /**
155 * Create a serialized message packet as per spec: $key:$value\n .
156 */
157 function _openid_create_message($data) {
158 $serialized = '';
159
160 foreach ($data as $key => $value) {
161 if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
162 return NULL;
163 }
164 $serialized .= "$key:$value\n";
165 }
166 return $serialized;
167 }
168
169 /**
170 * Encode a message from _openid_create_message for HTTP Post
171 */
172 function _openid_encode_message($message) {
173 $encoded_message = '';
174
175 $items = explode("\n", $message);
176 foreach ($items as $item) {
177 $parts = explode(':', $item, 2);
178
179 if (count($parts) == 2) {
180 if ($encoded_message != '') {
181 $encoded_message .= '&';
182 }
183 $encoded_message .= rawurlencode(trim($parts[0])) . '=' . rawurlencode(trim($parts[1]));
184 }
185 }
186
187 return $encoded_message;
188 }
189
190 /**
191 * Convert a direct communication message
192 * into an associative array.
193 */
194 function _openid_parse_message($message) {
195 $parsed_message = array();
196
197 $items = explode("\n", $message);
198 foreach ($items as $item) {
199 $parts = explode(':', $item, 2);
200
201 if (count($parts) == 2) {
202 $parsed_message[$parts[0]] = $parts[1];
203 }
204 }
205
206 return $parsed_message;
207 }
208
209 /**
210 * Return a nonce value - formatted per OpenID spec.
211 */
212 function _openid_nonce() {
213 // YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars
214 return gmstrftime('%Y-%m-%dT%H:%M:%S%Z') .
215 chr(mt_rand(0, 25) + 65) .
216 chr(mt_rand(0, 25) + 65) .
217 chr(mt_rand(0, 25) + 65) .
218 chr(mt_rand(0, 25) + 65);
219 }
220
221 /**
222 * Pull the href attribute out of an html link element.
223 */
224 function _openid_link_href($rel, $html) {
225 $rel = preg_quote($rel);
226 preg_match('|<link\s+rel=["\'](.*)' . $rel . '(.*)["\'](.*)/?>|iUs', $html, $matches);
227 if (isset($matches[3])) {
228 preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
229 return trim($href[1]);
230 }
231 return FALSE;
232 }
233
234 /**
235 * Pull the http-equiv attribute out of an html meta element
236 */
237 function _openid_meta_httpequiv($equiv, $html) {
238 preg_match('|<meta\s+http-equiv=["\']' . $equiv . '["\'](.*)/?>|iUs', $html, $matches);
239 if (isset($matches[1])) {
240 preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
241 if (isset($content[1])) {
242 return $content[1];
243 }
244 }
245 return FALSE;
246 }
247
248 /**
249 * Sign certain keys in a message
250 * @param $association - object loaded from openid_association or openid_server_association table
251 * - important fields are ->assoc_type and ->mac_key
252 * @param $message_array - array of entire message about to be sent
253 * @param $keys_to_sign - keys in the message to include in signature (without
254 * 'openid.' appended)
255 */
256 function _openid_signature($association, $message_array, $keys_to_sign) {
257 $signature = '';
258 $sign_data = array();
259
260 foreach ($keys_to_sign as $key) {
261 if (isset($message_array['openid.' . $key])) {
262 $sign_data[$key] = $message_array['openid.' . $key];
263 }
264 }
265
266 $message = _openid_create_message($sign_data);
267 $secret = base64_decode($association->mac_key);
268 $signature = _openid_hmac($secret, $message);
269
270 return base64_encode($signature);
271 }
272
273 function _openid_hmac($key, $text) {
274 if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
275 $key = sha1($key, TRUE);
276 }
277
278 $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
279 $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
280 $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
281 $hash1 = sha1(($key ^ $ipad) . $text, TRUE);
282 $hmac = sha1(($key ^ $opad) . $hash1, TRUE);
283
284 return $hmac;
285 }
286
287 function _openid_dh_base64_to_long($str) {
288 $b64 = base64_decode($str);
289
290 return _openid_dh_binary_to_long($b64);
291 }
292
293 function _openid_dh_long_to_base64($str) {
294 return base64_encode(_openid_dh_long_to_binary($str));
295 }
296
297 function _openid_dh_binary_to_long($str) {
298 $bytes = array_merge(unpack('C*', $str));
299
300 $n = 0;
301 foreach ($bytes as $byte) {
302 $n = bcmul($n, pow(2, 8));
303 $n = bcadd($n, $byte);
304 }
305
306 return $n;
307 }
308
309 function _openid_dh_long_to_binary($long) {
310 $cmp = bccomp($long, 0);
311 if ($cmp < 0) {
312 return FALSE;
313 }
314
315 if ($cmp == 0) {
316 return "\x00";
317 }
318
319 $bytes = array();
320
321 while (bccomp($long, 0) > 0) {
322 array_unshift($bytes, bcmod($long, 256));
323 $long = bcdiv($long, pow(2, 8));
324 }
325
326 if ($bytes && ($bytes[0] > 127)) {
327 array_unshift($bytes, 0);
328 }
329
330 $string = '';
331 foreach ($bytes as $byte) {
332 $string .= pack('C', $byte);
333 }
334
335 return $string;
336 }
337
338 function _openid_dh_xorsecret($shared, $secret) {
339 $dh_shared_str = _openid_dh_long_to_binary($shared);
340 $sha1_dh_shared = sha1($dh_shared_str, TRUE);
341 $xsecret = "";
342 for ($i = 0; $i < strlen($secret); $i++) {
343 $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
344 }
345
346 return $xsecret;
347 }
348
349 function _openid_dh_rand($stop) {
350 $duplicate_cache = &drupal_static(__FUNCTION__, array());
351
352 // Used as the key for the duplicate cache
353 $rbytes = _openid_dh_long_to_binary($stop);
354
355 if (array_key_exists($rbytes, $duplicate_cache)) {
356 list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
357 }
358 else {
359 if ($rbytes[0] == "\x00") {
360 $nbytes = strlen($rbytes) - 1;
361 }
362 else {
363 $nbytes = strlen($rbytes);
364 }
365
366 $mxrand = bcpow(256, $nbytes);
367
368 // If we get a number less than this, then it is in the
369 // duplicated range.
370 $duplicate = bcmod($mxrand, $stop);
371
372 if (count($duplicate_cache) > 10) {
373 $duplicate_cache = array();
374 }
375
376 $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
377 }
378
379 do {
380 $bytes = "\x00" . _openid_get_bytes($nbytes);
381 $n = _openid_dh_binary_to_long($bytes);
382 // Keep looping if this value is in the low duplicated range.
383 } while (bccomp($n, $duplicate) < 0);
384
385 return bcmod($n, $stop);
386 }
387
388 function _openid_get_bytes($num_bytes) {
389 $f = &drupal_static(__FUNCTION__);
390 $bytes = '';
391 if (!isset($f)) {
392 $f = @fopen(OPENID_RAND_SOURCE, "r");
393 }
394 if (!$f) {
395 // pseudorandom used
396 $bytes = '';
397 for ($i = 0; $i < $num_bytes; $i += 4) {
398 $bytes .= pack('L', mt_rand());
399 }
400 $bytes = substr($bytes, 0, $num_bytes);
401 }
402 else {
403 $bytes = fread($f, $num_bytes);
404 }
405 return $bytes;
406 }
407
408 function _openid_response($str = NULL) {
409 $data = array();
410
411 if (isset($_SERVER['REQUEST_METHOD'])) {
412 $data = _openid_get_params($_SERVER['QUERY_STRING']);
413
414 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
415 $str = file_get_contents('php://input');
416
417 $post = array();
418 if ($str !== FALSE) {
419 $post = _openid_get_params($str);
420 }
421
422 $data = array_merge($data, $post);
423 }
424 }
425
426 return $data;
427 }
428
429 function _openid_get_params($str) {
430 $chunks = explode("&", $str);
431
432 $data = array();
433 foreach ($chunks as $chunk) {
434 $parts = explode("=", $chunk, 2);
435
436 if (count($parts) == 2) {
437 list($k, $v) = $parts;
438 $data[$k] = urldecode($v);
439 }
440 }
441 return $data;
442 }

  ViewVC Help
Powered by ViewVC 1.1.2