Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

725 lines
29 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. <?php
  2. /**
  3. * This class provides a simple interface for OpenID (1.1 and 2.0) authentication.
  4. * Supports Yadis discovery.
  5. * The authentication process is stateless/dumb.
  6. *
  7. * Usage:
  8. * Sign-on with OpenID is a two step process:
  9. * Step one is authentication with the provider:
  10. * <code>
  11. * $openid = new LightOpenID;
  12. * $openid->identity = 'ID supplied by user';
  13. * header('Location: ' . $openid->authUrl());
  14. * </code>
  15. * The provider then sends various parameters via GET, one of them is openid_mode.
  16. * Step two is verification:
  17. * <code>
  18. * if ($this->data['openid_mode']) {
  19. * $openid = new LightOpenID;
  20. * echo $openid->validate() ? 'Logged in.' : 'Failed';
  21. * }
  22. * </code>
  23. *
  24. * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias).
  25. * The default values for those are:
  26. * $openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
  27. * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; # without the query part, if present
  28. * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess.
  29. *
  30. * AX and SREG extensions are supported.
  31. * To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl().
  32. * These are arrays, with values being AX schema paths (the 'path' part of the URL).
  33. * For example:
  34. * $openid->required = array('namePerson/friendly', 'contact/email');
  35. * $openid->optional = array('namePerson/first');
  36. * If the server supports only SREG or OpenID 1.1, these are automaticaly
  37. * mapped to SREG names, so that user doesn't have to know anything about the server.
  38. *
  39. * To get the values, use $openid->getAttributes().
  40. *
  41. *
  42. * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled..
  43. * @author Mewp
  44. * @copyright Copyright (c) 2010, Mewp
  45. * @license http://www.opensource.org/licenses/mit-license.php MIT
  46. */
  47. class LightOpenID
  48. {
  49. public $returnUrl
  50. , $required = array()
  51. , $optional = array()
  52. , $verify_perr = null
  53. , $capath = null;
  54. private $identity, $claimed_id;
  55. protected $server, $version, $trustRoot, $aliases, $identifier_select = false
  56. , $ax = false, $sreg = false, $data;
  57. static protected $ax_to_sreg = array(
  58. 'namePerson/friendly' => 'nickname',
  59. 'contact/email' => 'email',
  60. 'namePerson' => 'fullname',
  61. 'birthDate' => 'dob',
  62. 'person/gender' => 'gender',
  63. 'contact/postalCode/home' => 'postcode',
  64. 'contact/country/home' => 'country',
  65. 'pref/language' => 'language',
  66. 'pref/timezone' => 'timezone',
  67. );
  68. function __construct()
  69. {
  70. $this->trustRoot = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
  71. $uri = $_SERVER['REQUEST_URI'];
  72. $uri = strpos($uri, '?') ? substr($uri, 0, strpos($uri, '?')) : $uri;
  73. $this->returnUrl = $this->trustRoot . $uri;
  74. $this->data = $_POST + $_GET; # OPs may send data as POST or GET.
  75. }
  76. function __set($name, $value)
  77. {
  78. switch ($name) {
  79. case 'identity':
  80. if (strlen($value = trim((String) $value))) {
  81. if (preg_match('#^xri:/*#i', $value, $m)) {
  82. $value = substr($value, strlen($m[0]));
  83. } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) {
  84. $value = "http://$value";
  85. }
  86. if (preg_match('#^https?://[^/]+$#i', $value, $m)) {
  87. $value .= '/';
  88. }
  89. }
  90. $this->$name = $this->claimed_id = $value;
  91. break;
  92. case 'trustRoot':
  93. case 'realm':
  94. $this->trustRoot = trim($value);
  95. }
  96. }
  97. function __get($name)
  98. {
  99. switch ($name) {
  100. case 'identity':
  101. # We return claimed_id instead of identity,
  102. # because the developer should see the claimed identifier,
  103. # i.e. what he set as identity, not the op-local identifier (which is what we verify)
  104. return $this->claimed_id;
  105. case 'trustRoot':
  106. case 'realm':
  107. return $this->trustRoot;
  108. }
  109. }
  110. /**
  111. * Checks if the server specified in the url exists.
  112. *
  113. * @param $url url to check
  114. * @return true, if the server exists; false otherwise
  115. */
  116. function hostExists($url)
  117. {
  118. if (strpos($url, '/') === false) {
  119. $server = $url;
  120. } else {
  121. $server = @parse_url($url, PHP_URL_HOST);
  122. }
  123. if (!$server) {
  124. return false;
  125. }
  126. return !!gethostbynamel($server);
  127. }
  128. protected function request_curl($url, $method='GET', $params=array())
  129. {
  130. $params = http_build_query($params, '', '&');
  131. $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : ''));
  132. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  133. curl_setopt($curl, CURLOPT_HEADER, false);
  134. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  135. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  136. curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*'));
  137. if($this->verify_perr !== null) {
  138. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer);
  139. if($this->capath) {
  140. curl_setopt($curl, CURLOPT_CAPATH, $this->capath);
  141. }
  142. }
  143. if ($method == 'POST') {
  144. curl_setopt($curl, CURLOPT_POST, true);
  145. curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
  146. } elseif ($method == 'HEAD') {
  147. curl_setopt($curl, CURLOPT_HEADER, true);
  148. curl_setopt($curl, CURLOPT_NOBODY, true);
  149. } else {
  150. curl_setopt($curl, CURLOPT_HTTPGET, true);
  151. }
  152. $response = curl_exec($curl);
  153. if($method == 'HEAD') {
  154. $headers = array();
  155. foreach(explode("\n", $response) as $header) {
  156. $pos = strpos($header,':');
  157. $name = strtolower(trim(substr($header, 0, $pos)));
  158. $headers[$name] = trim(substr($header, $pos+1));
  159. }
  160. # Updating claimed_id in case of redirections.
  161. $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
  162. if($effective_url != $url) {
  163. $this->identity = $this->claimed_id = $effective_url;
  164. }
  165. return $headers;
  166. }
  167. if (curl_errno($curl)) {
  168. throw new ErrorException(curl_error($curl), curl_errno($curl));
  169. }
  170. return $response;
  171. }
  172. protected function request_streams($url, $method='GET', $params=array())
  173. {
  174. if(!$this->hostExists($url)) {
  175. throw new ErrorException('Invalid request.');
  176. }
  177. $params = http_build_query($params, '', '&');
  178. switch($method) {
  179. case 'GET':
  180. $opts = array(
  181. 'http' => array(
  182. 'method' => 'GET',
  183. 'header' => 'Accept: application/xrds+xml, */*',
  184. 'ignore_errors' => true,
  185. )
  186. );
  187. $url = $url . ($params ? '?' . $params : '');
  188. break;
  189. case 'POST':
  190. $opts = array(
  191. 'http' => array(
  192. 'method' => 'POST',
  193. 'header' => 'Content-type: application/x-www-form-urlencoded',
  194. 'content' => $params,
  195. 'ignore_errors' => true,
  196. )
  197. );
  198. break;
  199. case 'HEAD':
  200. # We want to send a HEAD request,
  201. # but since get_headers doesn't accept $context parameter,
  202. # we have to change the defaults.
  203. $default = stream_context_get_options(stream_context_get_default());
  204. stream_context_get_default(
  205. array('http' => array(
  206. 'method' => 'HEAD',
  207. 'header' => 'Accept: application/xrds+xml, */*',
  208. 'ignore_errors' => true,
  209. ))
  210. );
  211. $url = $url . ($params ? '?' . $params : '');
  212. $headers_tmp = get_headers ($url);
  213. if(!$headers_tmp) {
  214. return array();
  215. }
  216. # Parsing headers.
  217. $headers = array();
  218. foreach($headers_tmp as $header) {
  219. $pos = strpos($header,':');
  220. $name = strtolower(trim(substr($header, 0, $pos)));
  221. $headers[$name] = trim(substr($header, $pos+1));
  222. # Following possible redirections. The point is just to have
  223. # claimed_id change with them, because get_headers() will
  224. # follow redirections automatically.
  225. # We ignore redirections with relative paths.
  226. # If any known provider uses them, file a bug report.
  227. if($name == 'location') {
  228. if(strpos($headers[$name], 'http') === 0) {
  229. $this->identity = $this->claimed_id = $headers[$name];
  230. } elseif($headers[$name][0] == '/') {
  231. $parsed_url = parse_url($this->claimed_id);
  232. $this->identity =
  233. $this->claimed_id = $parsed_url['scheme'] . '://'
  234. . $parsed_url['host']
  235. . $headers[$name];
  236. }
  237. }
  238. }
  239. # And restore them.
  240. stream_context_get_default($default);
  241. return $headers;
  242. }
  243. if($this->verify_peer) {
  244. $opts += array('ssl' => array(
  245. 'verify_peer' => true,
  246. 'capath' => $this->capath,
  247. ));
  248. }
  249. $context = stream_context_create ($opts);
  250. return file_get_contents($url, false, $context);
  251. }
  252. protected function request($url, $method='GET', $params=array())
  253. {
  254. if(function_exists('curl_init') && !ini_get('safe_mode') && (! strlen(ini_get('open_basedir')))) {
  255. return $this->request_curl($url, $method, $params);
  256. }
  257. return $this->request_streams($url, $method, $params);
  258. }
  259. protected function build_url($url, $parts)
  260. {
  261. if (isset($url['query'], $parts['query'])) {
  262. $parts['query'] = $url['query'] . '&' . $parts['query'];
  263. }
  264. $url = $parts + $url;
  265. $url = $url['scheme'] . '://'
  266. . (empty($url['username'])?''
  267. :(empty($url['password'])? "{$url['username']}@"
  268. :"{$url['username']}:{$url['password']}@"))
  269. . $url['host']
  270. . (empty($url['port'])?'':":{$url['port']}")
  271. . (empty($url['path'])?'':$url['path'])
  272. . (empty($url['query'])?'':"?{$url['query']}")
  273. . (empty($url['fragment'])?'':":{$url['fragment']}");
  274. return $url;
  275. }
  276. /**
  277. * Helper function used to scan for <meta>/<link> tags and extract information
  278. * from them
  279. */
  280. protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName)
  281. {
  282. preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1);
  283. preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2);
  284. $result = array_merge($matches1[1], $matches2[1]);
  285. return empty($result)?false:$result[0];
  286. }
  287. /**
  288. * Performs Yadis and HTML discovery. Normally not used.
  289. * @param $url Identity URL.
  290. * @return String OP Endpoint (i.e. OpenID provider address).
  291. * @throws ErrorException
  292. */
  293. function discover($url)
  294. {
  295. if (!$url) throw new ErrorException('No identity supplied.');
  296. # Use xri.net proxy to resolve i-name identities
  297. if (!preg_match('#^https?:#', $url)) {
  298. $url = "https://xri.net/$url";
  299. }
  300. # We save the original url in case of Yadis discovery failure.
  301. # It can happen when we'll be lead to an XRDS document
  302. # which does not have any OpenID2 services.
  303. $originalUrl = $url;
  304. # A flag to disable yadis discovery in case of failure in headers.
  305. $yadis = true;
  306. # We'll jump a maximum of 5 times, to avoid endless redirections.
  307. for ($i = 0; $i < 5; $i ++) {
  308. if ($yadis) {
  309. $headers = $this->request($url, 'HEAD');
  310. $next = false;
  311. if (isset($headers['x-xrds-location'])) {
  312. $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location'])));
  313. $next = true;
  314. }
  315. if (isset($headers['content-type'])
  316. && ((strpos($headers['content-type'], 'application/xrds+xml') !== false
  317. ) || (strpos($headers['content-type'], 'text/xml') !== false))) {
  318. # Found an XRDS document, now let's find the server, and optionally delegate.
  319. $content = $this->request($url, 'GET');
  320. preg_match_all('#<Service.*?>(.*?)</Service>#s', $content, $m);
  321. foreach($m[1] as $content) {
  322. $content = ' ' . $content; # The space is added, so that strpos doesn't return 0.
  323. # OpenID 2
  324. $ns = preg_quote('http://specs.openid.net/auth/2.0/');
  325. if(preg_match('#<Type>\s*'.$ns.'(server|signon)\s*</Type>#s', $content, $type)) {
  326. if ($type[1] == 'server') $this->identifier_select = true;
  327. preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
  328. preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate);
  329. if (empty($server)) {
  330. return false;
  331. }
  332. # Does the server advertise support for either AX or SREG?
  333. $this->ax = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>');
  334. $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
  335. || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
  336. $server = $server[1];
  337. if (isset($delegate[2])) $this->identity = trim($delegate[2]);
  338. $this->version = 2;
  339. #logger('Server: ' . $server);
  340. $this->server = $server;
  341. return $server;
  342. }
  343. # OpenID 1.1
  344. $ns = preg_quote('http://openid.net/signon/1.1');
  345. if (preg_match('#<Type>\s*'.$ns.'\s*</Type>#s', $content)) {
  346. preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
  347. preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate);
  348. if (empty($server)) {
  349. return false;
  350. }
  351. # AX can be used only with OpenID 2.0, so checking only SREG
  352. $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
  353. || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
  354. $server = $server[1];
  355. if (isset($delegate[1])) $this->identity = $delegate[1];
  356. $this->version = 1;
  357. $this->server = $server;
  358. return $server;
  359. }
  360. }
  361. $next = true;
  362. $yadis = false;
  363. $url = $originalUrl;
  364. $content = null;
  365. break;
  366. }
  367. if ($next) continue;
  368. # There are no relevant information in headers, so we search the body.
  369. $content = $this->request($url, 'GET');
  370. if ($location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content')) {
  371. $url = $this->build_url(parse_url($url), parse_url($location));
  372. continue;
  373. }
  374. }
  375. if (!$content) $content = $this->request($url, 'GET');
  376. logger('openid' . $content);
  377. # At this point, the YADIS Discovery has failed, so we'll switch
  378. # to openid2 HTML discovery, then fallback to openid 1.1 discovery.
  379. $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href');
  380. $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href');
  381. $this->version = 2;
  382. if (!$server) {
  383. # The same with openid 1.1
  384. $server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href');
  385. $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href');
  386. $this->version = 1;
  387. }
  388. if ($server) {
  389. # We found an OpenID2 OP Endpoint
  390. if ($delegate) {
  391. # We have also found an OP-Local ID.
  392. $this->identity = $delegate;
  393. }
  394. $this->server = $server;
  395. return $server;
  396. }
  397. throw new ErrorException('No servers found!');
  398. }
  399. throw new ErrorException('Endless redirection!');
  400. }
  401. protected function sregParams()
  402. {
  403. $params = array();
  404. # We always use SREG 1.1, even if the server is advertising only support for 1.0.
  405. # That's because it's fully backwards compatibile with 1.0, and some providers
  406. # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com
  407. $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1';
  408. if ($this->required) {
  409. $params['openid.sreg.required'] = array();
  410. foreach ($this->required as $required) {
  411. if (!isset(self::$ax_to_sreg[$required])) continue;
  412. $params['openid.sreg.required'][] = self::$ax_to_sreg[$required];
  413. }
  414. $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']);
  415. }
  416. if ($this->optional) {
  417. $params['openid.sreg.optional'] = array();
  418. foreach ($this->optional as $optional) {
  419. if (!isset(self::$ax_to_sreg[$optional])) continue;
  420. $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional];
  421. }
  422. $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']);
  423. }
  424. return $params;
  425. }
  426. protected function axParams()
  427. {
  428. $params = array();
  429. if ($this->required || $this->optional) {
  430. $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0';
  431. $params['openid.ax.mode'] = 'fetch_request';
  432. $this->aliases = array();
  433. $counts = array();
  434. $required = array();
  435. $optional = array();
  436. foreach (array('required','optional') as $type) {
  437. foreach ($this->$type as $alias => $field) {
  438. if (is_int($alias)) $alias = strtr($field, '/', '_');
  439. $this->aliases[$alias] = 'http://axschema.org/' . $field;
  440. if (empty($counts[$alias])) $counts[$alias] = 0;
  441. $counts[$alias] += 1;
  442. ${$type}[] = $alias;
  443. }
  444. }
  445. foreach ($this->aliases as $alias => $ns) {
  446. $params['openid.ax.type.' . $alias] = $ns;
  447. }
  448. foreach ($counts as $alias => $count) {
  449. if ($count == 1) continue;
  450. $params['openid.ax.count.' . $alias] = $count;
  451. }
  452. # Don't send empty ax.requied and ax.if_available.
  453. # Google and possibly other providers refuse to support ax when one of these is empty.
  454. if($required) {
  455. $params['openid.ax.required'] = implode(',', $required);
  456. }
  457. if($optional) {
  458. $params['openid.ax.if_available'] = implode(',', $optional);
  459. }
  460. }
  461. return $params;
  462. }
  463. protected function authUrl_v1()
  464. {
  465. $returnUrl = $this->returnUrl;
  466. # If we have an openid.delegate that is different from our claimed id,
  467. # we need to somehow preserve the claimed id between requests.
  468. # The simplest way is to just send it along with the return_to url.
  469. if($this->identity != $this->claimed_id) {
  470. $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id;
  471. }
  472. $params = array(
  473. 'openid.return_to' => $returnUrl,
  474. 'openid.mode' => 'checkid_setup',
  475. 'openid.identity' => $this->identity,
  476. 'openid.trust_root' => $this->trustRoot,
  477. ) + $this->sregParams();
  478. return $this->build_url(parse_url($this->server)
  479. , array('query' => http_build_query($params, '', '&')));
  480. }
  481. protected function authUrl_v2($identifier_select)
  482. {
  483. $params = array(
  484. 'openid.ns' => 'http://specs.openid.net/auth/2.0',
  485. 'openid.mode' => 'checkid_setup',
  486. 'openid.return_to' => $this->returnUrl,
  487. 'openid.realm' => $this->trustRoot,
  488. );
  489. if ($this->ax) {
  490. $params += $this->axParams();
  491. }
  492. if ($this->sreg) {
  493. $params += $this->sregParams();
  494. }
  495. if (!$this->ax && !$this->sreg) {
  496. # If OP doesn't advertise either SREG, nor AX, let's send them both
  497. # in worst case we don't get anything in return.
  498. $params += $this->axParams() + $this->sregParams();
  499. }
  500. if ($identifier_select) {
  501. $params['openid.identity'] = $params['openid.claimed_id']
  502. = 'http://specs.openid.net/auth/2.0/identifier_select';
  503. } else {
  504. $params['openid.identity'] = $this->identity;
  505. $params['openid.claimed_id'] = $this->claimed_id;
  506. }
  507. return $this->build_url(parse_url($this->server)
  508. , array('query' => http_build_query($params, '', '&')));
  509. }
  510. /**
  511. * Returns authentication url. Usually, you want to redirect your user to it.
  512. * @return String The authentication url.
  513. * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1.
  514. * @throws ErrorException
  515. */
  516. function authUrl($identifier_select = null)
  517. {
  518. if (!$this->server) $this->discover($this->identity);
  519. if ($this->version == 2) {
  520. if ($identifier_select === null) {
  521. return $this->authUrl_v2($this->identifier_select);
  522. }
  523. return $this->authUrl_v2($identifier_select);
  524. }
  525. return $this->authUrl_v1();
  526. }
  527. /**
  528. * Performs OpenID verification with the OP.
  529. * @return Bool Whether the verification was successful.
  530. * @throws ErrorException
  531. */
  532. function validate()
  533. {
  534. $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity'];
  535. $params = array(
  536. 'openid.assoc_handle' => $this->data['openid_assoc_handle'],
  537. 'openid.signed' => $this->data['openid_signed'],
  538. 'openid.sig' => $this->data['openid_sig'],
  539. );
  540. if (isset($this->data['openid_ns'])) {
  541. # We're dealing with an OpenID 2.0 server, so let's set an ns
  542. # Even though we should know location of the endpoint,
  543. # we still need to verify it by discovery, so $server is not set here
  544. $params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
  545. } elseif(isset($this->data['openid_claimed_id'])) {
  546. # If it's an OpenID 1 provider, and we've got claimed_id,
  547. # we have to append it to the returnUrl, like authUrl_v1 does.
  548. $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?')
  549. . 'openid.claimed_id=' . $this->claimed_id;
  550. }
  551. if ($this->data['openid_return_to'] != $this->returnUrl) {
  552. # The return_to url must match the url of current request.
  553. # I'm assuing that noone will set the returnUrl to something that doesn't make sense.
  554. return false;
  555. }
  556. $server = $this->discover($this->claimed_id);
  557. foreach (explode(',', $this->data['openid_signed']) as $item) {
  558. # Checking whether magic_quotes_gpc is turned on, because
  559. # the function may fail if it is. For example, when fetching
  560. # AX namePerson, it might containg an apostrophe, which will be escaped.
  561. # In such case, validation would fail, since we'd send different data than OP
  562. # wants to verify. stripslashes() should solve that problem, but we can't
  563. # use it when magic_quotes is off.
  564. $value = $this->data['openid_' . str_replace('.','_',$item)];
  565. $params['openid.' . $item] = get_magic_quotes_gpc() ? stripslashes($value) : $value;
  566. }
  567. $params['openid.mode'] = 'check_authentication';
  568. $response = $this->request($server, 'POST', $params);
  569. return preg_match('/is_valid\s*:\s*true/i', $response);
  570. }
  571. protected function getAxAttributes()
  572. {
  573. $alias = null;
  574. if (isset($this->data['openid_ns_ax'])
  575. && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0'
  576. ) { # It's the most likely case, so we'll check it before
  577. $alias = 'ax';
  578. } else {
  579. # 'ax' prefix is either undefined, or points to another extension,
  580. # so we search for another prefix
  581. foreach ($this->data as $key => $val) {
  582. if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_'
  583. && $val == 'http://openid.net/srv/ax/1.0'
  584. ) {
  585. $alias = substr($key, strlen('openid_ns_'));
  586. break;
  587. }
  588. }
  589. }
  590. if (!$alias) {
  591. # An alias for AX schema has not been found,
  592. # so there is no AX data in the OP's response
  593. return array();
  594. }
  595. $attributes = array();
  596. foreach ($this->data as $key => $value) {
  597. $keyMatch = 'openid_' . $alias . '_value_';
  598. if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
  599. continue;
  600. }
  601. $key = substr($key, strlen($keyMatch));
  602. if (!isset($this->data['openid_' . $alias . '_type_' . $key])) {
  603. # OP is breaking the spec by returning a field without
  604. # associated ns. This shouldn't happen, but it's better
  605. # to check, than cause an E_NOTICE.
  606. continue;
  607. }
  608. $key = substr($this->data['openid_' . $alias . '_type_' . $key],
  609. strlen('http://axschema.org/'));
  610. $attributes[$key] = $value;
  611. }
  612. return $attributes;
  613. }
  614. protected function getSregAttributes()
  615. {
  616. $attributes = array();
  617. $sreg_to_ax = array_flip(self::$ax_to_sreg);
  618. foreach ($this->data as $key => $value) {
  619. $keyMatch = 'openid_sreg_';
  620. if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
  621. continue;
  622. }
  623. $key = substr($key, strlen($keyMatch));
  624. if (!isset($sreg_to_ax[$key])) {
  625. # The field name isn't part of the SREG spec, so we ignore it.
  626. continue;
  627. }
  628. $attributes[$sreg_to_ax[$key]] = $value;
  629. }
  630. return $attributes;
  631. }
  632. /**
  633. * Gets AX/SREG attributes provided by OP. should be used only after successful validaton.
  634. * Note that it does not guarantee that any of the required/optional parameters will be present,
  635. * or that there will be no other attributes besides those specified.
  636. * In other words. OP may provide whatever information it wants to.
  637. * * SREG names will be mapped to AX names.
  638. * * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email'
  639. * @see http://www.axschema.org/types/
  640. */
  641. function getAttributes()
  642. {
  643. if (isset($this->data['openid_ns'])
  644. && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0'
  645. ) { # OpenID 2.0
  646. # We search for both AX and SREG attributes, with AX taking precedence.
  647. return $this->getAxAttributes() + $this->getSregAttributes();
  648. }
  649. return $this->getSregAttributes();
  650. }
  651. }