PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/4.9/includes/OpenID/openid.inc.php

http://miacms.googlecode.com/
PHP | 627 lines | 413 code | 98 blank | 116 comment | 69 complexity | 77d839653428af4beb83ffc8253ebc47 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, LGPL-2.0
  1. <?php
  2. /**
  3. * @package MiaCMS
  4. * @author MiaCMS see README.php
  5. * @copyright see README.php
  6. * See COPYRIGHT.php for copyright notices and details.
  7. * @license GNU/GPL Version 2, see LICENSE.php
  8. * MiaCMS is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; version 2 of the License.
  11. *
  12. * This file was taken mostly from Drupal project. http://drupal.org
  13. * Kudos to the folks at Drupal.
  14. *
  15. **/
  16. /** ensure this file is being included by a parent file */
  17. defined( '_VALID_MOS' ) or die( 'Direct Access to this location is not allowed.' );
  18. // $Id: openid.inc,v 1.8 2008/01/30 22:11:22 goba Exp $
  19. /**
  20. * @file
  21. * OpenID utility functions.
  22. */
  23. // Diffie-Hellman Key Exchange Default Value.
  24. define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'.
  25. '966915404479707795314057629378541917580651227423698188993727816152646631'.
  26. '438561595825688188889951272158842675419950341258706556549803580104870537'.
  27. '681476726513255747040765857479291291572334510643245094715007229621094194'.
  28. '349783925984760375594985848253359305585439638443');
  29. // Constants for Diffie-Hellman key exchange computations.
  30. define('OPENID_DH_DEFAULT_GEN', '2');
  31. define('OPENID_SHA1_BLOCKSIZE', 64);
  32. define('OPENID_RAND_SOURCE', '/dev/urandom');
  33. // OpenID namespace URLs
  34. define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
  35. define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
  36. define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
  37. /**
  38. * Performs an HTTP 302 redirect (for the 1.x protocol).
  39. */
  40. function openid_redirect_http($url, $message) {
  41. $query = array();
  42. foreach ($message as $key => $val) {
  43. $query[] = $key .'='. urlencode($val);
  44. }
  45. $sep = (strpos($url, '?') === FALSE) ? '?' : '&';
  46. header('Location: '. $url . $sep . implode('&', $query), TRUE, 302);
  47. exit;
  48. }
  49. /**
  50. * Creates a js auto-submit redirect for (for the 2.x protocol)
  51. */
  52. function openid_redirect($url, $message) {
  53. $output = '<html><head><title>'. T_('OpenID redirect') ."</title></head>\n<body>";
  54. $form = "<form method='post' action='$url' id='openid-redirect-form'>";
  55. $i = 0;
  56. foreach ($message as $key => $value) {
  57. $form .= "<input type='hidden' name='$key' value='$value' id='$key-$i'/>";
  58. $i++;
  59. }
  60. $form .= "<noscript><input type='submit' name='sendopenidform' id='edit-submit' value='Send'/></noscript>";
  61. $form .= "</form>";
  62. $output .= $form;
  63. $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>';
  64. $output .= "</body></html>\n";
  65. print $output;
  66. exit;
  67. }
  68. function openid_redirect_form(&$form_state, $url, $message) {
  69. $form = array();
  70. $form['#action'] = $url;
  71. $form['#method'] = "post";
  72. foreach ($message as $key => $value) {
  73. $form[$key] = array(
  74. '#type' => 'hidden',
  75. '#name' => $key,
  76. '#value' => $value,
  77. );
  78. }
  79. $form['submit'] = array(
  80. '#type' => 'submit',
  81. '#prefix' => '<noscript>',
  82. '#suffix' => '</noscript>',
  83. '#value' => T_('Send'),
  84. );
  85. return $form;
  86. }
  87. /**
  88. * Determine if the given identifier is an XRI ID.
  89. */
  90. function _openid_is_xri($identifier) {
  91. $firstchar = substr($identifier, 0, 1);
  92. if ($firstchar == "@" || $firstchar == "=")
  93. return TRUE;
  94. if (stristr($identifier, 'xri://') !== FALSE) {
  95. return TRUE;
  96. }
  97. return FALSE;
  98. }
  99. /**
  100. * Normalize the given identifier as per spec.
  101. */
  102. function _openid_normalize($identifier) {
  103. if (_openid_is_xri($identifier)) {
  104. return _openid_normalize_xri($identifier);
  105. }
  106. else {
  107. return _openid_normalize_url($identifier);
  108. }
  109. }
  110. function _openid_normalize_xri($xri) {
  111. $normalized_xri = $xri;
  112. if (stristr($xri, 'xri://') !== FALSE) {
  113. $normalized_xri = substr($xri, 6);
  114. }
  115. return $normalized_xri;
  116. }
  117. function _openid_normalize_url($url) {
  118. $normalized_url = $url;
  119. if (stristr($url, '://') === FALSE) {
  120. $normalized_url = 'http://'. $url;
  121. }
  122. if (substr_count($normalized_url, '/') < 3) {
  123. $normalized_url .= '/';
  124. }
  125. return $normalized_url;
  126. }
  127. /**
  128. * Create a serialized message packet as per spec: $key:$value\n .
  129. */
  130. function _openid_create_message($data) {
  131. $serialized = '';
  132. foreach ($data as $key => $value) {
  133. if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
  134. return null;
  135. }
  136. $serialized .= "$key:$value\n";
  137. }
  138. return $serialized;
  139. }
  140. /**
  141. * Encode a message from _openid_create_message for HTTP Post
  142. */
  143. function _openid_encode_message($message) {
  144. $encoded_message = '';
  145. $items = explode("\n", $message);
  146. foreach ($items as $item) {
  147. $parts = explode(':', $item, 2);
  148. if (count($parts) == 2) {
  149. if ($encoded_message != '') {
  150. $encoded_message .= '&';
  151. }
  152. $encoded_message .= rawurlencode(trim($parts[0])) .'='. rawurlencode(trim($parts[1]));
  153. }
  154. }
  155. return $encoded_message;
  156. }
  157. /**
  158. * Convert a direct communication message
  159. * into an associative array.
  160. */
  161. function _openid_parse_message($message) {
  162. $parsed_message = array();
  163. $items = explode("\n", $message);
  164. foreach ($items as $item) {
  165. $parts = explode(':', $item, 2);
  166. if (count($parts) == 2) {
  167. $parsed_message[$parts[0]] = $parts[1];
  168. }
  169. }
  170. return $parsed_message;
  171. }
  172. /**
  173. * Return a nonce value - formatted per OpenID spec.
  174. */
  175. function _openid_nonce() {
  176. // YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars
  177. return gmstrftime('%Y-%m-%dT%H:%M:%S%Z') .
  178. chr(mt_rand(0, 25) + 65) .
  179. chr(mt_rand(0, 25) + 65) .
  180. chr(mt_rand(0, 25) + 65) .
  181. chr(mt_rand(0, 25) + 65);
  182. }
  183. /**
  184. * Pull the href attribute out of an html link element.
  185. */
  186. function _openid_link_href($rel, $html) {
  187. $rel = preg_quote($rel);
  188. preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iUs', $html, $matches);
  189. if (isset($matches[3])) {
  190. preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
  191. return trim($href[1]);
  192. }
  193. return FALSE;
  194. }
  195. /**
  196. * Pull the http-equiv attribute out of an html meta element
  197. */
  198. function _openid_meta_httpequiv($equiv, $html) {
  199. preg_match('|<meta\s+http-equiv=["\']'. $equiv .'["\'](.*)/?>|iUs', $html, $matches);
  200. if (isset($matches[1])) {
  201. preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
  202. if (isset($content[1])) {
  203. return $content[1];
  204. }
  205. }
  206. return FALSE;
  207. }
  208. /**
  209. * Sign certain keys in a message
  210. * @param $association - object loaded from openid_association or openid_server_association table
  211. * - important fields are ->assoc_type and ->mac_key
  212. * @param $message_array - array of entire message about to be sent
  213. * @param $keys_to_sign - keys in the message to include in signature (without
  214. * 'openid.' appended)
  215. */
  216. function _openid_signature($association, $message_array, $keys_to_sign) {
  217. $signature = '';
  218. $sign_data = array();
  219. foreach ($keys_to_sign as $key) {
  220. if (isset($message_array['openid.'. $key])) {
  221. $sign_data[$key] = $message_array['openid.'. $key];
  222. }
  223. }
  224. $message = _openid_create_message($sign_data);
  225. $secret = base64_decode($association->mac_key);
  226. $signature = _openid_hmac($secret, $message);
  227. return base64_encode($signature);
  228. }
  229. function _openid_hmac($key, $text) {
  230. if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
  231. $key = _openid_sha1($key, true);
  232. }
  233. $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
  234. $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
  235. $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
  236. $hash1 = _openid_sha1(($key ^ $ipad) . $text, true);
  237. $hmac = _openid_sha1(($key ^ $opad) . $hash1, true);
  238. return $hmac;
  239. }
  240. function _openid_sha1($text) {
  241. $hex = sha1($text);
  242. $raw = '';
  243. for ($i = 0; $i < 40; $i += 2) {
  244. $hexcode = substr($hex, $i, 2);
  245. $charcode = (int)base_convert($hexcode, 16, 10);
  246. $raw .= chr($charcode);
  247. }
  248. return $raw;
  249. }
  250. function _openid_dh_base64_to_long($str) {
  251. $b64 = base64_decode($str);
  252. return _openid_dh_binary_to_long($b64);
  253. }
  254. function _openid_dh_long_to_base64($str) {
  255. return base64_encode(_openid_dh_long_to_binary($str));
  256. }
  257. function _openid_dh_binary_to_long($str) {
  258. $bytes = array_merge(unpack('C*', $str));
  259. $n = 0;
  260. foreach ($bytes as $byte) {
  261. $n = bcmul($n, pow(2, 8));
  262. $n = bcadd($n, $byte);
  263. }
  264. return $n;
  265. }
  266. function _openid_dh_long_to_binary($long) {
  267. $cmp = bccomp($long, 0);
  268. if ($cmp < 0) {
  269. return FALSE;
  270. }
  271. if ($cmp == 0) {
  272. return "\x00";
  273. }
  274. $bytes = array();
  275. while (bccomp($long, 0) > 0) {
  276. array_unshift($bytes, bcmod($long, 256));
  277. $long = bcdiv($long, pow(2, 8));
  278. }
  279. if ($bytes && ($bytes[0] > 127)) {
  280. array_unshift($bytes, 0);
  281. }
  282. $string = '';
  283. foreach ($bytes as $byte) {
  284. $string .= pack('C', $byte);
  285. }
  286. return $string;
  287. }
  288. function _openid_dh_xorsecret($shared, $secret) {
  289. $dh_shared_str = _openid_dh_long_to_binary($shared);
  290. $sha1_dh_shared = _openid_sha1($dh_shared_str);
  291. $xsecret = "";
  292. for ($i = 0; $i < strlen($secret); $i++) {
  293. $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
  294. }
  295. return $xsecret;
  296. }
  297. function _openid_dh_rand($stop) {
  298. static $duplicate_cache = array();
  299. // Used as the key for the duplicate cache
  300. $rbytes = _openid_dh_long_to_binary($stop);
  301. if (array_key_exists($rbytes, $duplicate_cache)) {
  302. list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
  303. }
  304. else {
  305. if ($rbytes[0] == "\x00") {
  306. $nbytes = strlen($rbytes) - 1;
  307. }
  308. else {
  309. $nbytes = strlen($rbytes);
  310. }
  311. $mxrand = bcpow(256, $nbytes);
  312. // If we get a number less than this, then it is in the
  313. // duplicated range.
  314. $duplicate = bcmod($mxrand, $stop);
  315. if (count($duplicate_cache) > 10) {
  316. $duplicate_cache = array();
  317. }
  318. $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
  319. }
  320. do {
  321. $bytes = "\x00". _openid_get_bytes($nbytes);
  322. $n = _openid_dh_binary_to_long($bytes);
  323. // Keep looping if this value is in the low duplicated range.
  324. } while (bccomp($n, $duplicate) < 0);
  325. return bcmod($n, $stop);
  326. }
  327. function _openid_get_bytes($num_bytes) {
  328. static $f = null;
  329. $bytes = '';
  330. if (!isset($f)) {
  331. $f = @fopen(OPENID_RAND_SOURCE, "r");
  332. }
  333. if (!$f) {
  334. // pseudorandom used
  335. $bytes = '';
  336. for ($i = 0; $i < $num_bytes; $i += 4) {
  337. $bytes .= pack('L', mt_rand());
  338. }
  339. $bytes = substr($bytes, 0, $num_bytes);
  340. }
  341. else {
  342. $bytes = fread($f, $num_bytes);
  343. }
  344. return $bytes;
  345. }
  346. function _openid_response($str = NULL) {
  347. $data = array();
  348. if (isset($_SERVER['REQUEST_METHOD'])) {
  349. $data = _openid_get_params($_SERVER['QUERY_STRING']);
  350. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  351. $str = file_get_contents('php://input');
  352. $post = array();
  353. if ($str !== false) {
  354. $post = _openid_get_params($str);
  355. }
  356. $data = array_merge($data, $post);
  357. }
  358. }
  359. return $data;
  360. }
  361. function _openid_get_params($str) {
  362. $chunks = explode("&", $str);
  363. $data = array();
  364. foreach ($chunks as $chunk) {
  365. $parts = explode("=", $chunk, 2);
  366. if (count($parts) == 2) {
  367. list($k, $v) = $parts;
  368. $data[$k] = urldecode($v);
  369. }
  370. }
  371. return $data;
  372. }
  373. /**
  374. * Provide bcpowmod support for PHP4.
  375. */
  376. if (!function_exists('bcpowmod')) {
  377. function bcpowmod($base, $exp, $mod) {
  378. $square = bcmod($base, $mod);
  379. $result = 1;
  380. while (bccomp($exp, 0) > 0) {
  381. if (bcmod($exp, 2)) {
  382. $result = bcmod(bcmul($result, $square), $mod);
  383. }
  384. $square = bcmod(bcmul($square, $square), $mod);
  385. $exp = bcdiv($exp, 2);
  386. }
  387. return $result;
  388. }
  389. }
  390. /**
  391. * Perform an HTTP request.
  392. *
  393. * This is a flexible and powerful HTTP client implementation. Correctly handles
  394. * GET, POST, PUT or any other HTTP requests. Handles redirects.
  395. *
  396. * @param $url
  397. * A string containing a fully qualified URI.
  398. * @param $headers
  399. * An array containing an HTTP header => value pair.
  400. * @param $method
  401. * A string defining the HTTP request to use.
  402. * @param $data
  403. * A string containing data to include in the request.
  404. * @param $retry
  405. * An integer representing how many times to retry the request in case of a
  406. * redirect.
  407. * @return
  408. * An object containing the HTTP request headers, response code, headers,
  409. * data and redirect status.
  410. *
  411. * Function grabbed from Drupal - name changed for consistency.
  412. *
  413. */
  414. function miacms_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
  415. static $self_test = FALSE;
  416. $result = new stdClass();
  417. // Parse the URL and make sure we can handle the schema.
  418. $uri = parse_url($url);
  419. switch ($uri['scheme']) {
  420. case 'http':
  421. $port = isset($uri['port']) ? $uri['port'] : 80;
  422. $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
  423. $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
  424. break;
  425. case 'https':
  426. // Note: Only works for PHP 4.3 compiled with OpenSSL.
  427. $port = isset($uri['port']) ? $uri['port'] : 443;
  428. $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
  429. $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
  430. break;
  431. default:
  432. $result->error = 'invalid schema '. $uri['scheme'];
  433. return $result;
  434. }
  435. // Make sure the socket opened properly.
  436. if (!$fp) {
  437. // When a network error occurs, we use a negative number so it does not
  438. // clash with the HTTP status codes.
  439. $result->code = -$errno;
  440. $result->error = trim($errstr);
  441. return $result;
  442. }
  443. // Construct the path to act on.
  444. $path = isset($uri['path']) ? $uri['path'] : '/';
  445. if (isset($uri['query'])) {
  446. $path .= '?'. $uri['query'];
  447. }
  448. // Create HTTP request.
  449. $defaults = array(
  450. // RFC 2616: "non-standard ports MUST, default ports MAY be included".
  451. // We don't add the port to prevent from breaking rewrite rules checking the
  452. // host that do not take into account the port number.
  453. 'Host' => "Host: $host",
  454. 'User-Agent' => 'User-Agent: MiaCMS (+http://miacms.org/)',
  455. 'Content-Length' => 'Content-Length: '. strlen($data)
  456. );
  457. // If the server url has a user then attempt to use basic authentication
  458. if (isset($uri['user'])) {
  459. $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
  460. }
  461. foreach ($headers as $header => $value) {
  462. $defaults[$header] = $header .': '. $value;
  463. }
  464. $request = $method .' '. $path ." HTTP/1.0\r\n";
  465. $request .= implode("\r\n", $defaults);
  466. $request .= "\r\n\r\n";
  467. if ($data) {
  468. $request .= $data ."\r\n";
  469. }
  470. $result->request = $request;
  471. fwrite($fp, $request);
  472. // Fetch response.
  473. $response = '';
  474. while (!feof($fp) && $chunk = fread($fp, 1024)) {
  475. $response .= $chunk;
  476. }
  477. fclose($fp);
  478. // Parse response.
  479. list($split, $result->data) = explode("\r\n\r\n", $response, 2);
  480. $split = preg_split("/\r\n|\n|\r/", $split);
  481. list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3);
  482. $result->headers = array();
  483. // Parse headers.
  484. while ($line = trim(array_shift($split))) {
  485. list($header, $value) = explode(':', $line, 2);
  486. if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
  487. // RFC 2109: the Set-Cookie response header comprises the token Set-
  488. // Cookie:, followed by a comma-separated list of one or more cookies.
  489. $result->headers[$header] .= ','. trim($value);
  490. }
  491. else {
  492. $result->headers[$header] = trim($value);
  493. }
  494. }
  495. $responses = array(
  496. 100 => 'Continue', 101 => 'Switching Protocols',
  497. 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
  498. 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
  499. 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
  500. 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
  501. );
  502. // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  503. // base code in their class.
  504. if (!isset($responses[$code])) {
  505. $code = floor($code / 100) * 100;
  506. }
  507. switch ($code) {
  508. case 200: // OK
  509. case 304: // Not modified
  510. break;
  511. case 301: // Moved permanently
  512. case 302: // Moved temporarily
  513. case 307: // Moved temporarily
  514. $location = $result->headers['Location'];
  515. if ($retry) {
  516. $result = miacms_http_request($result->headers['Location'], $headers, $method, $data, --$retry);
  517. $result->redirect_code = $result->code;
  518. }
  519. $result->redirect_url = $location;
  520. break;
  521. default:
  522. $result->error = $text;
  523. }
  524. $result->code = $code;
  525. return $result;
  526. }
  527. /**
  528. * @} End of "HTTP handling".
  529. */