OpenID.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <?php
  2. /**
  3. * This is the PHP OpenID library by JanRain, Inc.
  4. *
  5. * This module contains core utility functionality used by the
  6. * library. See Consumer.php and Server.php for the consumer and
  7. * server implementations.
  8. *
  9. * PHP versions 4 and 5
  10. *
  11. * LICENSE: See the COPYING file included in this distribution.
  12. *
  13. * @package OpenID
  14. * @author JanRain, Inc. <openid@janrain.com>
  15. * @copyright 2005-2008 Janrain, Inc.
  16. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  17. */
  18. /**
  19. * The library version string
  20. */
  21. define('Auth_OpenID_VERSION', '2.2.2');
  22. /**
  23. * Require the fetcher code.
  24. */
  25. require_once "Auth/Yadis/PlainHTTPFetcher.php";
  26. require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
  27. require_once "Auth/OpenID/BigMath.php";
  28. require_once "Auth/OpenID/URINorm.php";
  29. /**
  30. * Status code returned by the server when the only option is to show
  31. * an error page, since we do not have enough information to redirect
  32. * back to the consumer. The associated value is an error message that
  33. * should be displayed on an HTML error page.
  34. *
  35. * @see Auth_OpenID_Server
  36. */
  37. define('Auth_OpenID_LOCAL_ERROR', 'local_error');
  38. /**
  39. * Status code returned when there is an error to return in key-value
  40. * form to the consumer. The caller should return a 400 Bad Request
  41. * response with content-type text/plain and the value as the body.
  42. *
  43. * @see Auth_OpenID_Server
  44. */
  45. define('Auth_OpenID_REMOTE_ERROR', 'remote_error');
  46. /**
  47. * Status code returned when there is a key-value form OK response to
  48. * the consumer. The value associated with this code is the
  49. * response. The caller should return a 200 OK response with
  50. * content-type text/plain and the value as the body.
  51. *
  52. * @see Auth_OpenID_Server
  53. */
  54. define('Auth_OpenID_REMOTE_OK', 'remote_ok');
  55. /**
  56. * Status code returned when there is a redirect back to the
  57. * consumer. The value is the URL to redirect back to. The caller
  58. * should return a 302 Found redirect with a Location: header
  59. * containing the URL.
  60. *
  61. * @see Auth_OpenID_Server
  62. */
  63. define('Auth_OpenID_REDIRECT', 'redirect');
  64. /**
  65. * Status code returned when the caller needs to authenticate the
  66. * user. The associated value is a {@link Auth_OpenID_ServerRequest}
  67. * object that can be used to complete the authentication. If the user
  68. * has taken some authentication action, use the retry() method of the
  69. * {@link Auth_OpenID_ServerRequest} object to complete the request.
  70. *
  71. * @see Auth_OpenID_Server
  72. */
  73. define('Auth_OpenID_DO_AUTH', 'do_auth');
  74. /**
  75. * Status code returned when there were no OpenID arguments
  76. * passed. This code indicates that the caller should return a 200 OK
  77. * response and display an HTML page that says that this is an OpenID
  78. * server endpoint.
  79. *
  80. * @see Auth_OpenID_Server
  81. */
  82. define('Auth_OpenID_DO_ABOUT', 'do_about');
  83. /**
  84. * Defines for regexes and format checking.
  85. */
  86. define('Auth_OpenID_letters',
  87. "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
  88. define('Auth_OpenID_digits',
  89. "0123456789");
  90. define('Auth_OpenID_punct',
  91. "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
  92. Auth_OpenID_include_init();
  93. /**
  94. * The OpenID utility function class.
  95. *
  96. * @package OpenID
  97. * @access private
  98. */
  99. class Auth_OpenID {
  100. /**
  101. * Return true if $thing is an Auth_OpenID_FailureResponse object;
  102. * false if not.
  103. *
  104. * @access private
  105. */
  106. static function isFailure($thing)
  107. {
  108. return is_a($thing, 'Auth_OpenID_FailureResponse');
  109. }
  110. /**
  111. * Gets the query data from the server environment based on the
  112. * request method used. If GET was used, this looks at
  113. * $_SERVER['QUERY_STRING'] directly. If POST was used, this
  114. * fetches data from the special php://input file stream.
  115. *
  116. * Returns an associative array of the query arguments.
  117. *
  118. * Skips invalid key/value pairs (i.e. keys with no '=value'
  119. * portion).
  120. *
  121. * Returns an empty array if neither GET nor POST was used, or if
  122. * POST was used but php://input cannot be opened.
  123. *
  124. * See background:
  125. * http://lists.openidenabled.com/pipermail/dev/2007-March/000395.html
  126. *
  127. * @access private
  128. */
  129. static function getQuery($query_str=null)
  130. {
  131. $data = array();
  132. if ($query_str !== null) {
  133. $data = Auth_OpenID::params_from_string($query_str);
  134. } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
  135. // Do nothing.
  136. } else {
  137. // XXX HACK FIXME HORRIBLE.
  138. //
  139. // POSTing to a URL with query parameters is acceptable, but
  140. // we don't have a clean way to distinguish those parameters
  141. // when we need to do things like return_to verification
  142. // which only want to look at one kind of parameter. We're
  143. // going to emulate the behavior of some other environments
  144. // by defaulting to GET and overwriting with POST if POST
  145. // data is available.
  146. $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']);
  147. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  148. $str = file_get_contents('php://input');
  149. if ($str === false) {
  150. $post = array();
  151. } else {
  152. $post = Auth_OpenID::params_from_string($str);
  153. }
  154. $data = array_merge($data, $post);
  155. }
  156. }
  157. return $data;
  158. }
  159. static function params_from_string($str)
  160. {
  161. $chunks = explode("&", $str);
  162. $data = array();
  163. foreach ($chunks as $chunk) {
  164. $parts = explode("=", $chunk, 2);
  165. if (count($parts) != 2) {
  166. continue;
  167. }
  168. list($k, $v) = $parts;
  169. $data[urldecode($k)] = urldecode($v);
  170. }
  171. return $data;
  172. }
  173. /**
  174. * Create dir_name as a directory if it does not exist. If it
  175. * exists, make sure that it is, in fact, a directory. Returns
  176. * true if the operation succeeded; false if not.
  177. *
  178. * @access private
  179. */
  180. static function ensureDir($dir_name)
  181. {
  182. if (is_dir($dir_name) || @mkdir($dir_name)) {
  183. return true;
  184. } else {
  185. $parent_dir = dirname($dir_name);
  186. // Terminal case; there is no parent directory to create.
  187. if ($parent_dir == $dir_name) {
  188. return true;
  189. }
  190. return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name));
  191. }
  192. }
  193. /**
  194. * Adds a string prefix to all values of an array. Returns a new
  195. * array containing the prefixed values.
  196. *
  197. * @access private
  198. */
  199. static function addPrefix($values, $prefix)
  200. {
  201. $new_values = array();
  202. foreach ($values as $s) {
  203. $new_values[] = $prefix . $s;
  204. }
  205. return $new_values;
  206. }
  207. /**
  208. * Convenience function for getting array values. Given an array
  209. * $arr and a key $key, get the corresponding value from the array
  210. * or return $default if the key is absent.
  211. *
  212. * @access private
  213. */
  214. static function arrayGet($arr, $key, $fallback = null)
  215. {
  216. if (is_array($arr)) {
  217. if (array_key_exists($key, $arr)) {
  218. return $arr[$key];
  219. } else {
  220. return $fallback;
  221. }
  222. } else {
  223. trigger_error("Auth_OpenID::arrayGet (key = ".$key.") expected " .
  224. "array as first parameter, got " .
  225. gettype($arr), E_USER_WARNING);
  226. return false;
  227. }
  228. }
  229. /**
  230. * Replacement for PHP's broken parse_str.
  231. */
  232. static function parse_str($query)
  233. {
  234. if ($query === null) {
  235. return null;
  236. }
  237. $parts = explode('&', $query);
  238. $new_parts = array();
  239. for ($i = 0; $i < count($parts); $i++) {
  240. $pair = explode('=', $parts[$i]);
  241. if (count($pair) != 2) {
  242. continue;
  243. }
  244. list($key, $value) = $pair;
  245. $new_parts[urldecode($key)] = urldecode($value);
  246. }
  247. return $new_parts;
  248. }
  249. /**
  250. * Implements the PHP 5 'http_build_query' functionality.
  251. *
  252. * @access private
  253. * @param array $data Either an array key/value pairs or an array
  254. * of arrays, each of which holding two values: a key and a value,
  255. * sequentially.
  256. * @return string $result The result of url-encoding the key/value
  257. * pairs from $data into a URL query string
  258. * (e.g. "username=bob&id=56").
  259. */
  260. static function httpBuildQuery($data)
  261. {
  262. $pairs = array();
  263. foreach ($data as $key => $value) {
  264. if (is_array($value)) {
  265. $pairs[] = urlencode($value[0])."=".urlencode($value[1]);
  266. } else {
  267. $pairs[] = urlencode($key)."=".urlencode($value);
  268. }
  269. }
  270. return implode("&", $pairs);
  271. }
  272. /**
  273. * "Appends" query arguments onto a URL. The URL may or may not
  274. * already have arguments (following a question mark).
  275. *
  276. * @access private
  277. * @param string $url A URL, which may or may not already have
  278. * arguments.
  279. * @param array $args Either an array key/value pairs or an array of
  280. * arrays, each of which holding two values: a key and a value,
  281. * sequentially. If $args is an ordinary key/value array, the
  282. * parameters will be added to the URL in sorted alphabetical order;
  283. * if $args is an array of arrays, their order will be preserved.
  284. * @return string $url The original URL with the new parameters added.
  285. *
  286. */
  287. static function appendArgs($url, $args)
  288. {
  289. if (count($args) == 0) {
  290. return $url;
  291. }
  292. // Non-empty array; if it is an array of arrays, use
  293. // multisort; otherwise use sort.
  294. if (array_key_exists(0, $args) &&
  295. is_array($args[0])) {
  296. // Do nothing here.
  297. } else {
  298. $keys = array_keys($args);
  299. sort($keys);
  300. $new_args = array();
  301. foreach ($keys as $key) {
  302. $new_args[] = array($key, $args[$key]);
  303. }
  304. $args = $new_args;
  305. }
  306. $sep = '?';
  307. if (strpos($url, '?') !== false) {
  308. $sep = '&';
  309. }
  310. return $url . $sep . Auth_OpenID::httpBuildQuery($args);
  311. }
  312. /**
  313. * Implements python's urlunparse, which is not available in PHP.
  314. * Given the specified components of a URL, this function rebuilds
  315. * and returns the URL.
  316. *
  317. * @access private
  318. * @param string $scheme The scheme (e.g. 'http'). Defaults to 'http'.
  319. * @param string $host The host. Required.
  320. * @param string $port The port.
  321. * @param string $path The path.
  322. * @param string $query The query.
  323. * @param string $fragment The fragment.
  324. * @return string $url The URL resulting from assembling the
  325. * specified components.
  326. */
  327. static function urlunparse($scheme, $host, $port = null, $path = '/',
  328. $query = '', $fragment = '')
  329. {
  330. if (!$scheme) {
  331. $scheme = 'http';
  332. }
  333. if (!$host) {
  334. return false;
  335. }
  336. if (!$path) {
  337. $path = '';
  338. }
  339. $result = $scheme . "://" . $host;
  340. if ($port) {
  341. $result .= ":" . $port;
  342. }
  343. $result .= $path;
  344. if ($query) {
  345. $result .= "?" . $query;
  346. }
  347. if ($fragment) {
  348. $result .= "#" . $fragment;
  349. }
  350. return $result;
  351. }
  352. /**
  353. * Given a URL, this "normalizes" it by adding a trailing slash
  354. * and / or a leading http:// scheme where necessary. Returns
  355. * null if the original URL is malformed and cannot be normalized.
  356. *
  357. * @access private
  358. * @param string $url The URL to be normalized.
  359. * @return mixed $new_url The URL after normalization, or null if
  360. * $url was malformed.
  361. */
  362. static function normalizeUrl($url)
  363. {
  364. @$parsed = parse_url($url);
  365. if (!$parsed) {
  366. return null;
  367. }
  368. if (isset($parsed['scheme']) &&
  369. isset($parsed['host'])) {
  370. $scheme = strtolower($parsed['scheme']);
  371. if (!in_array($scheme, array('http', 'https'))) {
  372. return null;
  373. }
  374. } else {
  375. $url = 'http://' . $url;
  376. }
  377. $normalized = Auth_OpenID_urinorm($url);
  378. if ($normalized === null) {
  379. return null;
  380. }
  381. list($defragged, $frag) = Auth_OpenID::urldefrag($normalized);
  382. return $defragged;
  383. }
  384. /**
  385. * Replacement (wrapper) for PHP's intval() because it's broken.
  386. *
  387. * @access private
  388. */
  389. static function intval($value)
  390. {
  391. $re = "/^\\d+$/";
  392. if (!preg_match($re, $value)) {
  393. return false;
  394. }
  395. return intval($value);
  396. }
  397. /**
  398. * Count the number of bytes in a string independently of
  399. * multibyte support conditions.
  400. *
  401. * @param string $str The string of bytes to count.
  402. * @return int The number of bytes in $str.
  403. */
  404. static function bytes($str)
  405. {
  406. return strlen(bin2hex($str)) / 2;
  407. }
  408. /**
  409. * Get the bytes in a string independently of multibyte support
  410. * conditions.
  411. */
  412. static function toBytes($str)
  413. {
  414. $hex = bin2hex($str);
  415. if (!$hex) {
  416. return array();
  417. }
  418. $b = array();
  419. for ($i = 0; $i < strlen($hex); $i += 2) {
  420. $b[] = chr(base_convert(substr($hex, $i, 2), 16, 10));
  421. }
  422. return $b;
  423. }
  424. static function urldefrag($url)
  425. {
  426. $parts = explode("#", $url, 2);
  427. if (count($parts) == 1) {
  428. return array($parts[0], "");
  429. } else {
  430. return $parts;
  431. }
  432. }
  433. static function filter($callback, &$sequence)
  434. {
  435. $result = array();
  436. foreach ($sequence as $item) {
  437. if (call_user_func_array($callback, array($item))) {
  438. $result[] = $item;
  439. }
  440. }
  441. return $result;
  442. }
  443. static function update(&$dest, &$src)
  444. {
  445. foreach ($src as $k => $v) {
  446. $dest[$k] = $v;
  447. }
  448. }
  449. /**
  450. * Wrap PHP's standard error_log functionality. Use this to
  451. * perform all logging. It will interpolate any additional
  452. * arguments into the format string before logging.
  453. *
  454. * @param string $format_string The sprintf format for the message
  455. */
  456. static function log($format_string)
  457. {
  458. $args = func_get_args();
  459. $message = call_user_func_array('sprintf', $args);
  460. error_log($message);
  461. }
  462. static function autoSubmitHTML($form, $title="OpenId transaction in progress")
  463. {
  464. return("<html>".
  465. "<head><title>".
  466. $title .
  467. "</title></head>".
  468. "<body onload='document.forms[0].submit();'>".
  469. $form .
  470. "<script>".
  471. "var elements = document.forms[0].elements;".
  472. "for (var i = 0; i < elements.length; i++) {".
  473. " elements[i].style.display = \"none\";".
  474. "}".
  475. "</script>".
  476. "</body>".
  477. "</html>");
  478. }
  479. }
  480. /*
  481. * Function to run when this file is included.
  482. * Abstracted to a function to make life easier
  483. * for some PHP optimizers.
  484. */
  485. function Auth_OpenID_include_init() {
  486. if (Auth_OpenID_getMathLib() === null) {
  487. Auth_OpenID_setNoMathSupport();
  488. }
  489. }