| 1 |
<?php |
| 2 |
/*! |
| 3 |
* This file is part of the LightOpenID PHP Library (https://github.com/iignatov/LightOpenID) |
| 4 |
* |
| 5 |
* LightOpenID is an open source software available under the MIT License. |
| 6 |
* |
| 7 |
* Updated: 52f9910 on 4 Mar 2016. |
| 8 |
*/ |
| 9 |
|
| 10 |
namespace Hybridauth\Thirdparty\OpenID; |
| 11 |
|
| 12 |
use Hybridauth\Exception\Exception; |
| 13 |
use Hybridauth\Exception\ExceptionInterface; |
| 14 |
|
| 15 |
/** |
| 16 |
* Class ErrorException |
| 17 |
* |
| 18 |
* @package Hybridauth\Thirdparty\OpenID |
| 19 |
*/ |
| 20 |
class ErrorException extends Exception implements ExceptionInterface |
| 21 |
{ |
| 22 |
} |
| 23 |
|
| 24 |
/** |
| 25 |
* This class provides a simple interface for OpenID 1.1/2.0 authentication. |
| 26 |
* |
| 27 |
* It requires PHP >= 5.1.2 with cURL or HTTP/HTTPS stream wrappers enabled. |
| 28 |
* |
| 29 |
* @version v1.3.1 (2016-03-04) |
| 30 |
* @link https://code.google.com/p/lightopenid/ Project URL |
| 31 |
* @link https://github.com/iignatov/LightOpenID GitHub Repo |
| 32 |
* @author Mewp <mewp151 at gmail dot com> |
| 33 |
* @copyright Copyright (c) 2013 Mewp |
| 34 |
* @license http://opensource.org/licenses/mit-license.php MIT License |
| 35 |
*/ |
| 36 |
class LightOpenID |
| 37 |
{ |
| 38 |
public $returnUrl |
| 39 |
; |
| 40 |
public $required = array() |
| 41 |
; |
| 42 |
public $optional = array() |
| 43 |
; |
| 44 |
public $verify_peer = null |
| 45 |
; |
| 46 |
public $capath = null |
| 47 |
; |
| 48 |
public $cainfo = null |
| 49 |
; |
| 50 |
public $cnmatch = null |
| 51 |
; |
| 52 |
public $data |
| 53 |
; |
| 54 |
public $oauth = array() |
| 55 |
; |
| 56 |
public $curl_time_out = 30 // in seconds |
| 57 |
; |
| 58 |
public $curl_connect_time_out = 30; // in seconds |
| 59 |
private $identity; |
| 60 |
private $claimed_id; |
| 61 |
protected $server; |
| 62 |
protected $version; |
| 63 |
protected $trustRoot; |
| 64 |
protected $aliases; |
| 65 |
protected $identifier_select = false |
| 66 |
; |
| 67 |
protected $ax = false; |
| 68 |
protected $sreg = false; |
| 69 |
protected $setup_url = null; |
| 70 |
protected $headers = array() |
| 71 |
; |
| 72 |
protected $proxy = null; |
| 73 |
protected $user_agent = 'LightOpenID' |
| 74 |
; |
| 75 |
protected $xrds_override_pattern = null; |
| 76 |
protected $xrds_override_replacement = null; |
| 77 |
protected static $ax_to_sreg = array( |
| 78 |
'namePerson/friendly' => 'nickname', |
| 79 |
'contact/email' => 'email', |
| 80 |
'namePerson' => 'fullname', |
| 81 |
'birthDate' => 'dob', |
| 82 |
'person/gender' => 'gender', |
| 83 |
'contact/postalCode/home' => 'postcode', |
| 84 |
'contact/country/home' => 'country', |
| 85 |
'pref/language' => 'language', |
| 86 |
'pref/timezone' => 'timezone', |
| 87 |
); |
| 88 |
|
| 89 |
/** |
| 90 |
* LightOpenID constructor. |
| 91 |
* |
| 92 |
* @param $host |
| 93 |
* @param null $proxy |
| 94 |
* |
| 95 |
* @throws ErrorException |
| 96 |
*/ |
| 97 |
public function __construct($host, $proxy = null) |
| 98 |
{ |
| 99 |
$this->set_realm($host); |
| 100 |
$this->set_proxy($proxy); |
| 101 |
|
| 102 |
$uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); |
| 103 |
$this->returnUrl = $this->trustRoot . $uri; |
| 104 |
|
| 105 |
$this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; |
| 106 |
|
| 107 |
if (!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { |
| 108 |
throw new ErrorException('You must have either https wrappers or curl enabled.'); |
| 109 |
} |
| 110 |
} |
| 111 |
|
| 112 |
/** |
| 113 |
* @param $name |
| 114 |
* |
| 115 |
* @return bool |
| 116 |
*/ |
| 117 |
public function __isset($name) |
| 118 |
{ |
| 119 |
return in_array($name, array('identity', 'trustRoot', 'realm', 'xrdsOverride', 'mode')); |
| 120 |
} |
| 121 |
|
| 122 |
/** |
| 123 |
* @param $name |
| 124 |
* @param $value |
| 125 |
*/ |
| 126 |
public function __set($name, $value) |
| 127 |
{ |
| 128 |
switch ($name) { |
| 129 |
case 'identity': |
| 130 |
if (strlen($value = trim((String) $value))) { |
| 131 |
if (preg_match('#^xri:/*#i', $value, $m)) { |
| 132 |
$value = substr($value, strlen($m[0])); |
| 133 |
} elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { |
| 134 |
$value = "http://$value"; |
| 135 |
} |
| 136 |
if (preg_match('#^https?://[^/]+$#i', $value, $m)) { |
| 137 |
$value .= '/'; |
| 138 |
} |
| 139 |
} |
| 140 |
$this->$name = $this->claimed_id = $value; |
| 141 |
break; |
| 142 |
case 'trustRoot': |
| 143 |
case 'realm': |
| 144 |
$this->trustRoot = trim($value); |
| 145 |
break; |
| 146 |
case 'xrdsOverride': |
| 147 |
if (is_array($value)) { |
| 148 |
list($pattern, $replacement) = $value; |
| 149 |
$this->xrds_override_pattern = $pattern; |
| 150 |
$this->xrds_override_replacement = $replacement; |
| 151 |
} else { |
| 152 |
trigger_error('Invalid value specified for "xrdsOverride".', E_USER_ERROR); |
| 153 |
} |
| 154 |
break; |
| 155 |
} |
| 156 |
} |
| 157 |
|
| 158 |
/** |
| 159 |
* @param $name |
| 160 |
* |
| 161 |
* @return |null |
| 162 |
*/ |
| 163 |
public function __get($name) |
| 164 |
{ |
| 165 |
switch ($name) { |
| 166 |
case 'identity': |
| 167 |
# We return claimed_id instead of identity, |
| 168 |
# because the developer should see the claimed identifier, |
| 169 |
# i.e. what he set as identity, not the op-local identifier (which is what we verify) |
| 170 |
return $this->claimed_id; |
| 171 |
case 'trustRoot': |
| 172 |
case 'realm': |
| 173 |
return $this->trustRoot; |
| 174 |
case 'mode': |
| 175 |
return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; |
| 176 |
} |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* @param $proxy |
| 181 |
* |
| 182 |
* @throws ErrorException |
| 183 |
*/ |
| 184 |
public function set_proxy($proxy) |
| 185 |
{ |
| 186 |
if (!empty($proxy)) { |
| 187 |
// When the proxy is a string - try to parse it. |
| 188 |
if (!is_array($proxy)) { |
| 189 |
$proxy = parse_url($proxy); |
| 190 |
} |
| 191 |
|
| 192 |
// Check if $proxy is valid after the parsing. |
| 193 |
if ($proxy && !empty($proxy['host'])) { |
| 194 |
// Make sure that a valid port number is specified. |
| 195 |
if (array_key_exists('port', $proxy)) { |
| 196 |
if (!is_int($proxy['port'])) { |
| 197 |
$proxy['port'] = is_numeric($proxy['port']) ? intval($proxy['port']) : 0; |
| 198 |
} |
| 199 |
|
| 200 |
if ($proxy['port'] <= 0) { |
| 201 |
throw new ErrorException('The specified proxy port number is invalid.'); |
| 202 |
} |
| 203 |
} |
| 204 |
|
| 205 |
$this->proxy = $proxy; |
| 206 |
} |
| 207 |
} |
| 208 |
} |
| 209 |
|
| 210 |
/** |
| 211 |
* Checks if the server specified in the url exists. |
| 212 |
* |
| 213 |
* @param $url string url to check |
| 214 |
* @return true, if the server exists; false otherwise |
| 215 |
*/ |
| 216 |
public function hostExists($url) |
| 217 |
{ |
| 218 |
if (strpos($url, '/') === false) { |
| 219 |
$server = $url; |
| 220 |
} else { |
| 221 |
$server = @parse_url($url, PHP_URL_HOST); |
| 222 |
} |
| 223 |
|
| 224 |
if (!$server) { |
| 225 |
return false; |
| 226 |
} |
| 227 |
|
| 228 |
return !!gethostbynamel($server); |
| 229 |
} |
| 230 |
|
| 231 |
/** |
| 232 |
* @param $uri |
| 233 |
*/ |
| 234 |
protected function set_realm($uri) |
| 235 |
{ |
| 236 |
$realm = ''; |
| 237 |
|
| 238 |
# Set a protocol, if not specified. |
| 239 |
$realm .= (($offset = strpos($uri, '://')) === false) ? $this->get_realm_protocol() : ''; |
| 240 |
|
| 241 |
# Set the offset properly. |
| 242 |
$offset = (($offset !== false) ? $offset + 3 : 0); |
| 243 |
|
| 244 |
# Get only the root, without the path. |
| 245 |
$realm .= (($end = strpos($uri, '/', $offset)) === false) ? $uri : substr($uri, 0, $end); |
| 246 |
|
| 247 |
$this->trustRoot = $realm; |
| 248 |
} |
| 249 |
|
| 250 |
/** |
| 251 |
* @return string |
| 252 |
*/ |
| 253 |
protected function get_realm_protocol() |
| 254 |
{ |
| 255 |
if (!empty($_SERVER['HTTPS'])) { |
| 256 |
$use_secure_protocol = ($_SERVER['HTTPS'] !== 'off'); |
| 257 |
} elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { |
| 258 |
$use_secure_protocol = ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); |
| 259 |
} elseif (isset($_SERVER['HTTP__WSSC'])) { |
| 260 |
$use_secure_protocol = ($_SERVER['HTTP__WSSC'] == 'https'); |
| 261 |
} else { |
| 262 |
$use_secure_protocol = false; |
| 263 |
} |
| 264 |
|
| 265 |
return $use_secure_protocol ? 'https://' : 'http://'; |
| 266 |
} |
| 267 |
|
| 268 |
/** |
| 269 |
* @param $url |
| 270 |
* @param string $method |
| 271 |
* @param array $params |
| 272 |
* @param $update_claimed_id |
| 273 |
* |
| 274 |
* @return array|bool|string |
| 275 |
* @throws ErrorException |
| 276 |
*/ |
| 277 |
protected function request_curl($url, $method='GET', $params=array(), $update_claimed_id=false) |
| 278 |
{ |
| 279 |
$params = http_build_query($params, '', '&'); |
| 280 |
$curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); |
| 281 |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); |
| 282 |
curl_setopt($curl, CURLOPT_HEADER, false); |
| 283 |
curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); |
| 284 |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); |
| 285 |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
| 286 |
|
| 287 |
if ($method == 'POST') { |
| 288 |
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); |
| 289 |
} else { |
| 290 |
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); |
| 291 |
} |
| 292 |
|
| 293 |
curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_time_out); // defaults to infinite |
| 294 |
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->curl_connect_time_out); // defaults to 300s |
| 295 |
|
| 296 |
if (!empty($this->proxy)) { |
| 297 |
curl_setopt($curl, CURLOPT_PROXY, $this->proxy['host']); |
| 298 |
|
| 299 |
if (!empty($this->proxy['port'])) { |
| 300 |
curl_setopt($curl, CURLOPT_PROXYPORT, $this->proxy['port']); |
| 301 |
} |
| 302 |
|
| 303 |
if (!empty($this->proxy['user'])) { |
| 304 |
curl_setopt($curl, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); |
| 305 |
} |
| 306 |
} |
| 307 |
|
| 308 |
if ($this->verify_peer !== null) { |
| 309 |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); |
| 310 |
if ($this->capath) { |
| 311 |
curl_setopt($curl, CURLOPT_CAPATH, $this->capath); |
| 312 |
} |
| 313 |
|
| 314 |
if ($this->cainfo) { |
| 315 |
curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); |
| 316 |
} |
| 317 |
} |
| 318 |
|
| 319 |
if ($method == 'POST') { |
| 320 |
curl_setopt($curl, CURLOPT_POST, true); |
| 321 |
curl_setopt($curl, CURLOPT_POSTFIELDS, $params); |
| 322 |
} elseif ($method == 'HEAD') { |
| 323 |
curl_setopt($curl, CURLOPT_HEADER, true); |
| 324 |
curl_setopt($curl, CURLOPT_NOBODY, true); |
| 325 |
} else { |
| 326 |
curl_setopt($curl, CURLOPT_HEADER, true); |
| 327 |
curl_setopt($curl, CURLOPT_HTTPGET, true); |
| 328 |
} |
| 329 |
$response = curl_exec($curl); |
| 330 |
|
| 331 |
if ($method == 'HEAD' && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 405) { |
| 332 |
curl_setopt($curl, CURLOPT_HTTPGET, true); |
| 333 |
$response = curl_exec($curl); |
| 334 |
$response = substr($response, 0, strpos($response, "\r\n\r\n")); |
| 335 |
} |
| 336 |
|
| 337 |
if ($method == 'HEAD' || $method == 'GET') { |
| 338 |
$header_response = $response; |
| 339 |
|
| 340 |
# If it's a GET request, we want to only parse the header part. |
| 341 |
if ($method == 'GET') { |
| 342 |
$header_response = substr($response, 0, strpos($response, "\r\n\r\n")); |
| 343 |
} |
| 344 |
|
| 345 |
$headers = array(); |
| 346 |
foreach (explode("\n", $header_response) as $header) { |
| 347 |
$pos = strpos($header, ':'); |
| 348 |
if ($pos !== false) { |
| 349 |
$name = strtolower(trim(substr($header, 0, $pos))); |
| 350 |
$headers[$name] = trim(substr($header, $pos+1)); |
| 351 |
} |
| 352 |
} |
| 353 |
|
| 354 |
if ($update_claimed_id) { |
| 355 |
# Update the claimed_id value in case of redirections. |
| 356 |
$effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); |
| 357 |
# Ignore the fragment (some cURL versions don't handle it well). |
| 358 |
if (strtok($effective_url, '#') != strtok($url, '#')) { |
| 359 |
$this->identity = $this->claimed_id = $effective_url; |
| 360 |
} |
| 361 |
} |
| 362 |
|
| 363 |
if ($method == 'HEAD') { |
| 364 |
return $headers; |
| 365 |
} else { |
| 366 |
$this->headers = $headers; |
| 367 |
} |
| 368 |
} |
| 369 |
|
| 370 |
if (curl_errno($curl)) { |
| 371 |
throw new ErrorException(curl_error($curl), curl_errno($curl)); |
| 372 |
} |
| 373 |
|
| 374 |
return $response; |
| 375 |
} |
| 376 |
|
| 377 |
/** |
| 378 |
* @param $array |
| 379 |
* @param $update_claimed_id |
| 380 |
* |
| 381 |
* @return array |
| 382 |
*/ |
| 383 |
protected function parse_header_array($array, $update_claimed_id) |
| 384 |
{ |
| 385 |
$headers = array(); |
| 386 |
foreach ($array as $header) { |
| 387 |
$pos = strpos($header, ':'); |
| 388 |
if ($pos !== false) { |
| 389 |
$name = strtolower(trim(substr($header, 0, $pos))); |
| 390 |
$headers[$name] = trim(substr($header, $pos+1)); |
| 391 |
|
| 392 |
# Following possible redirections. The point is just to have |
| 393 |
# claimed_id change with them, because the redirections |
| 394 |
# are followed automatically. |
| 395 |
# We ignore redirections with relative paths. |
| 396 |
# If any known provider uses them, file a bug report. |
| 397 |
if ($name == 'location' && $update_claimed_id) { |
| 398 |
if (strpos($headers[$name], 'http') === 0) { |
| 399 |
$this->identity = $this->claimed_id = $headers[$name]; |
| 400 |
} elseif ($headers[$name][0] == '/') { |
| 401 |
$parsed_url = parse_url($this->claimed_id); |
| 402 |
$this->identity = |
| 403 |
$this->claimed_id = $parsed_url['scheme'] . '://' |
| 404 |
. $parsed_url['host'] |
| 405 |
. $headers[$name]; |
| 406 |
} |
| 407 |
} |
| 408 |
} |
| 409 |
} |
| 410 |
return $headers; |
| 411 |
} |
| 412 |
|
| 413 |
/** |
| 414 |
* @param $url |
| 415 |
* @param string $method |
| 416 |
* @param array $params |
| 417 |
* @param $update_claimed_id |
| 418 |
* |
| 419 |
* @return array|false|string |
| 420 |
* @throws ErrorException |
| 421 |
*/ |
| 422 |
protected function request_streams($url, $method='GET', $params=array(), $update_claimed_id=false) |
| 423 |
{ |
| 424 |
if (!$this->hostExists($url)) { |
| 425 |
throw new ErrorException("Could not connect to $url.", 404); |
| 426 |
} |
| 427 |
|
| 428 |
if (empty($this->cnmatch)) { |
| 429 |
$this->cnmatch = parse_url($url, PHP_URL_HOST); |
| 430 |
} |
| 431 |
|
| 432 |
$params = http_build_query($params, '', '&'); |
| 433 |
switch ($method) { |
| 434 |
case 'GET': |
| 435 |
$opts = array( |
| 436 |
'http' => array( |
| 437 |
'method' => 'GET', |
| 438 |
'header' => 'Accept: application/xrds+xml, */*', |
| 439 |
'user_agent' => $this->user_agent, |
| 440 |
'ignore_errors' => true, |
| 441 |
), |
| 442 |
'ssl' => array( |
| 443 |
'CN_match' => $this->cnmatch |
| 444 |
) |
| 445 |
); |
| 446 |
$url = $url . ($params ? '?' . $params : ''); |
| 447 |
if (!empty($this->proxy)) { |
| 448 |
$opts['http']['proxy'] = $this->proxy_url(); |
| 449 |
} |
| 450 |
break; |
| 451 |
case 'POST': |
| 452 |
$opts = array( |
| 453 |
'http' => array( |
| 454 |
'method' => 'POST', |
| 455 |
'header' => 'Content-type: application/x-www-form-urlencoded', |
| 456 |
'user_agent' => $this->user_agent, |
| 457 |
'content' => $params, |
| 458 |
'ignore_errors' => true, |
| 459 |
), |
| 460 |
'ssl' => array( |
| 461 |
'CN_match' => $this->cnmatch |
| 462 |
) |
| 463 |
); |
| 464 |
if (!empty($this->proxy)) { |
| 465 |
$opts['http']['proxy'] = $this->proxy_url(); |
| 466 |
} |
| 467 |
break; |
| 468 |
case 'HEAD': |
| 469 |
// We want to send a HEAD request, but since get_headers() doesn't |
| 470 |
// accept $context parameter, we have to change the defaults. |
| 471 |
$default = stream_context_get_options(stream_context_get_default()); |
| 472 |
|
| 473 |
// PHP does not reset all options. Instead, it just sets the options |
| 474 |
// available in the passed array, therefore set the defaults manually. |
| 475 |
$default += array( |
| 476 |
'http' => array(), |
| 477 |
'ssl' => array() |
| 478 |
); |
| 479 |
$default['http'] += array( |
| 480 |
'method' => 'GET', |
| 481 |
'header' => '', |
| 482 |
'user_agent' => '', |
| 483 |
'ignore_errors' => false |
| 484 |
); |
| 485 |
$default['ssl'] += array( |
| 486 |
'CN_match' => '' |
| 487 |
); |
| 488 |
|
| 489 |
$opts = array( |
| 490 |
'http' => array( |
| 491 |
'method' => 'HEAD', |
| 492 |
'header' => 'Accept: application/xrds+xml, */*', |
| 493 |
'user_agent' => $this->user_agent, |
| 494 |
'ignore_errors' => true, |
| 495 |
), |
| 496 |
'ssl' => array( |
| 497 |
'CN_match' => $this->cnmatch |
| 498 |
) |
| 499 |
); |
| 500 |
|
| 501 |
// Enable validation of the SSL certificates. |
| 502 |
if ($this->verify_peer) { |
| 503 |
$default['ssl'] += array( |
| 504 |
'verify_peer' => false, |
| 505 |
'capath' => '', |
| 506 |
'cafile' => '' |
| 507 |
); |
| 508 |
$opts['ssl'] += array( |
| 509 |
'verify_peer' => true, |
| 510 |
'capath' => $this->capath, |
| 511 |
'cafile' => $this->cainfo |
| 512 |
); |
| 513 |
} |
| 514 |
|
| 515 |
// Change the stream context options. |
| 516 |
stream_context_get_default($opts); |
| 517 |
|
| 518 |
$headers = get_headers($url . ($params ? '?' . $params : '')); |
| 519 |
|
| 520 |
// Restore the stream context options. |
| 521 |
stream_context_get_default($default); |
| 522 |
|
| 523 |
if (!empty($headers)) { |
| 524 |
if (intval(substr($headers[0], strlen('HTTP/1.1 '))) == 405) { |
| 525 |
// The server doesn't support HEAD - emulate it with a GET. |
| 526 |
$args = func_get_args(); |
| 527 |
$args[1] = 'GET'; |
| 528 |
call_user_func_array(array($this, 'request_streams'), $args); |
| 529 |
$headers = $this->headers; |
| 530 |
} else { |
| 531 |
$headers = $this->parse_header_array($headers, $update_claimed_id); |
| 532 |
} |
| 533 |
} else { |
| 534 |
$headers = array(); |
| 535 |
} |
| 536 |
|
| 537 |
return $headers; |
| 538 |
} |
| 539 |
|
| 540 |
if ($this->verify_peer) { |
| 541 |
$opts['ssl'] += array( |
| 542 |
'verify_peer' => true, |
| 543 |
'capath' => $this->capath, |
| 544 |
'cafile' => $this->cainfo |
| 545 |
); |
| 546 |
} |
| 547 |
|
| 548 |
$context = stream_context_create($opts); |
| 549 |
$data = file_get_contents($url, false, $context); |
| 550 |
# This is a hack for providers who don't support HEAD requests. |
| 551 |
# It just creates the headers array for the last request in $this->headers. |
| 552 |
if (isset($http_response_header)) { |
| 553 |
$this->headers = $this->parse_header_array($http_response_header, $update_claimed_id); |
| 554 |
} |
| 555 |
|
| 556 |
return $data; |
| 557 |
} |
| 558 |
|
| 559 |
/** |
| 560 |
* @param $url |
| 561 |
* @param string $method |
| 562 |
* @param array $params |
| 563 |
* @param bool $update_claimed_id |
| 564 |
* |
| 565 |
* @return array|bool|false|string |
| 566 |
* @throws ErrorException |
| 567 |
*/ |
| 568 |
protected function request($url, $method='GET', $params=array(), $update_claimed_id=false) |
| 569 |
{ |
| 570 |
$use_curl = false; |
| 571 |
|
| 572 |
if (function_exists('curl_init')) { |
| 573 |
if (!$use_curl) { |
| 574 |
# When allow_url_fopen is disabled, PHP streams will not work. |
| 575 |
$use_curl = !ini_get('allow_url_fopen'); |
| 576 |
} |
| 577 |
|
| 578 |
if (!$use_curl) { |
| 579 |
# When there is no HTTPS wrapper, PHP streams cannott be used. |
| 580 |
$use_curl = !in_array('https', stream_get_wrappers()); |
| 581 |
} |
| 582 |
|
| 583 |
if (!$use_curl) { |
| 584 |
# With open_basedir or safe_mode set, cURL can't follow redirects. |
| 585 |
$use_curl = !(ini_get('safe_mode') || ini_get('open_basedir')); |
| 586 |
} |
| 587 |
} |
| 588 |
|
| 589 |
return |
| 590 |
$use_curl |
| 591 |
? $this->request_curl($url, $method, $params, $update_claimed_id) |
| 592 |
: $this->request_streams($url, $method, $params, $update_claimed_id); |
| 593 |
} |
| 594 |
|
| 595 |
/** |
| 596 |
* @return string |
| 597 |
*/ |
| 598 |
protected function proxy_url() |
| 599 |
{ |
| 600 |
$result = ''; |
| 601 |
|
| 602 |
if (!empty($this->proxy)) { |
| 603 |
$result = $this->proxy['host']; |
| 604 |
|
| 605 |
if (!empty($this->proxy['port'])) { |
| 606 |
$result = $result . ':' . $this->proxy['port']; |
| 607 |
} |
| 608 |
|
| 609 |
if (!empty($this->proxy['user'])) { |
| 610 |
$result = $this->proxy['user'] . ':' . $this->proxy['pass'] . '@' . $result; |
| 611 |
} |
| 612 |
|
| 613 |
$result = 'http://' . $result; |
| 614 |
} |
| 615 |
|
| 616 |
return $result; |
| 617 |
} |
| 618 |
|
| 619 |
/** |
| 620 |
* @param $url |
| 621 |
* @param $parts |
| 622 |
* |
| 623 |
* @return string |
| 624 |
*/ |
| 625 |
protected function build_url($url, $parts) |
| 626 |
{ |
| 627 |
if (isset($url['query'], $parts['query'])) { |
| 628 |
$parts['query'] = $url['query'] . '&' . $parts['query']; |
| 629 |
} |
| 630 |
|
| 631 |
$url = $parts + $url; |
| 632 |
$url = $url['scheme'] . '://' |
| 633 |
. (empty($url['username'])?'' |
| 634 |
:(empty($url['password'])? "{$url['username']}@" |
| 635 |
:"{$url['username']}:{$url['password']}@")) |
| 636 |
. $url['host'] |
| 637 |
. (empty($url['port'])?'':":{$url['port']}") |
| 638 |
. (empty($url['path'])?'':$url['path']) |
| 639 |
. (empty($url['query'])?'':"?{$url['query']}") |
| 640 |
. (empty($url['fragment'])?'':"#{$url['fragment']}"); |
| 641 |
return $url; |
| 642 |
} |
| 643 |
|
| 644 |
/** |
| 645 |
* Helper function used to scan for <meta>/<link> tags and extract information |
| 646 |
* from them |
| 647 |
* |
| 648 |
* @param $content |
| 649 |
* @param $tag |
| 650 |
* @param $attrName |
| 651 |
* @param $attrValue |
| 652 |
* @param $valueName |
| 653 |
* |
| 654 |
* @return bool |
| 655 |
*/ |
| 656 |
protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName) |
| 657 |
{ |
| 658 |
preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); |
| 659 |
preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2); |
| 660 |
|
| 661 |
$result = array_merge($matches1[1], $matches2[1]); |
| 662 |
return empty($result)?false:$result[0]; |
| 663 |
} |
| 664 |
|
| 665 |
/** |
| 666 |
* Performs Yadis and HTML discovery. Normally not used. |
| 667 |
* @param $url Identity URL. |
| 668 |
* @return String OP Endpoint (i.e. OpenID provider address). |
| 669 |
* @throws ErrorException |
| 670 |
*/ |
| 671 |
public function discover($url) |
| 672 |
{ |
| 673 |
if (!$url) { |
| 674 |
throw new ErrorException('No identity supplied.'); |
| 675 |
} |
| 676 |
# Use xri.net proxy to resolve i-name identities |
| 677 |
if (!preg_match('#^https?:#', $url)) { |
| 678 |
$url = "https://xri.net/$url"; |
| 679 |
} |
| 680 |
|
| 681 |
# We save the original url in case of Yadis discovery failure. |
| 682 |
# It can happen when we'll be lead to an XRDS document |
| 683 |
# which does not have any OpenID2 services. |
| 684 |
$originalUrl = $url; |
| 685 |
|
| 686 |
# A flag to disable yadis discovery in case of failure in headers. |
| 687 |
$yadis = true; |
| 688 |
|
| 689 |
# Allows optional regex replacement of the URL, e.g. to use Google Apps |
| 690 |
# as an OpenID provider without setting up XRDS on the domain hosting. |
| 691 |
if (!is_null($this->xrds_override_pattern) && !is_null($this->xrds_override_replacement)) { |
| 692 |
$url = preg_replace($this->xrds_override_pattern, $this->xrds_override_replacement, $url); |
| 693 |
} |
| 694 |
|
| 695 |
# We'll jump a maximum of 5 times, to avoid endless redirections. |
| 696 |
for ($i = 0; $i < 5; $i ++) { |
| 697 |
if ($yadis) { |
| 698 |
$headers = $this->request($url, 'HEAD', array(), true); |
| 699 |
|
| 700 |
$next = false; |
| 701 |
if (isset($headers['x-xrds-location'])) { |
| 702 |
$url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); |
| 703 |
$next = true; |
| 704 |
} |
| 705 |
|
| 706 |
if (isset($headers['content-type']) && $this->is_allowed_type($headers['content-type'])) { |
| 707 |
# Found an XRDS document, now let's find the server, and optionally delegate. |
| 708 |
$content = $this->request($url, 'GET'); |
| 709 |
|
| 710 |
preg_match_all('#<Service.*?>(.*?)</Service>#s', $content, $m); |
| 711 |
foreach ($m[1] as $content) { |
| 712 |
$content = ' ' . $content; # The space is added, so that strpos doesn't return 0. |
| 713 |
|
| 714 |
# OpenID 2 |
| 715 |
$ns = preg_quote('http://specs.openid.net/auth/2.0/', '#'); |
| 716 |
if (preg_match('#<Type>\s*'.$ns.'(server|signon)\s*</Type>#s', $content, $type)) { |
| 717 |
if ($type[1] == 'server') { |
| 718 |
$this->identifier_select = true; |
| 719 |
} |
| 720 |
|
| 721 |
preg_match('#<URI.*?>(.*)</URI>#', $content, $server); |
| 722 |
preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate); |
| 723 |
if (empty($server)) { |
| 724 |
return false; |
| 725 |
} |
| 726 |
# Does the server advertise support for either AX or SREG? |
| 727 |
$this->ax = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>'); |
| 728 |
$this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') |
| 729 |
|| strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>'); |
| 730 |
|
| 731 |
$server = $server[1]; |
| 732 |
if (isset($delegate[2])) { |
| 733 |
$this->identity = trim($delegate[2]); |
| 734 |
} |
| 735 |
$this->version = 2; |
| 736 |
|
| 737 |
$this->server = $server; |
| 738 |
return $server; |
| 739 |
} |
| 740 |
|
| 741 |
# OpenID 1.1 |
| 742 |
$ns = preg_quote('http://openid.net/signon/1.1', '#'); |
| 743 |
if (preg_match('#<Type>\s*'.$ns.'\s*</Type>#s', $content)) { |
| 744 |
preg_match('#<URI.*?>(.*)</URI>#', $content, $server); |
| 745 |
preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate); |
| 746 |
if (empty($server)) { |
| 747 |
return false; |
| 748 |
} |
| 749 |
# AX can be used only with OpenID 2.0, so checking only SREG |
| 750 |
$this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') |
| 751 |
|| strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>'); |
| 752 |
|
| 753 |
$server = $server[1]; |
| 754 |
if (isset($delegate[1])) { |
| 755 |
$this->identity = $delegate[1]; |
| 756 |
} |
| 757 |
$this->version = 1; |
| 758 |
|
| 759 |
$this->server = $server; |
| 760 |
return $server; |
| 761 |
} |
| 762 |
} |
| 763 |
|
| 764 |
$next = true; |
| 765 |
$yadis = false; |
| 766 |
$url = $originalUrl; |
| 767 |
$content = null; |
| 768 |
break; |
| 769 |
} |
| 770 |
if ($next) { |
| 771 |
continue; |
| 772 |
} |
| 773 |
|
| 774 |
# There are no relevant information in headers, so we search the body. |
| 775 |
$content = $this->request($url, 'GET', array(), true); |
| 776 |
|
| 777 |
if (isset($this->headers['x-xrds-location'])) { |
| 778 |
$url = $this->build_url(parse_url($url), parse_url(trim($this->headers['x-xrds-location']))); |
| 779 |
continue; |
| 780 |
} |
| 781 |
|
| 782 |
$location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); |
| 783 |
if ($location) { |
| 784 |
$url = $this->build_url(parse_url($url), parse_url($location)); |
| 785 |
continue; |
| 786 |
} |
| 787 |
} |
| 788 |
|
| 789 |
if (!$content) { |
| 790 |
$content = $this->request($url, 'GET'); |
| 791 |
} |
| 792 |
|
| 793 |
# At this point, the YADIS Discovery has failed, so we'll switch |
| 794 |
# to openid2 HTML discovery, then fallback to openid 1.1 discovery. |
| 795 |
$server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); |
| 796 |
$delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href'); |
| 797 |
$this->version = 2; |
| 798 |
|
| 799 |
if (!$server) { |
| 800 |
# The same with openid 1.1 |
| 801 |
$server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href'); |
| 802 |
$delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href'); |
| 803 |
$this->version = 1; |
| 804 |
} |
| 805 |
|
| 806 |
if ($server) { |
| 807 |
# We found an OpenID2 OP Endpoint |
| 808 |
if ($delegate) { |
| 809 |
# We have also found an OP-Local ID. |
| 810 |
$this->identity = $delegate; |
| 811 |
} |
| 812 |
$this->server = $server; |
| 813 |
return $server; |
| 814 |
} |
| 815 |
|
| 816 |
throw new ErrorException("No OpenID Server found at $url", 404); |
| 817 |
} |
| 818 |
throw new ErrorException('Endless redirection!', 500); |
| 819 |
} |
| 820 |
|
| 821 |
/** |
| 822 |
* @param $content_type |
| 823 |
* |
| 824 |
* @return bool |
| 825 |
*/ |
| 826 |
protected function is_allowed_type($content_type) |
| 827 |
{ |
| 828 |
# Apparently, some providers return XRDS documents as text/html. |
| 829 |
# While it is against the spec, allowing this here shouldn't break |
| 830 |
# compatibility with anything. |
| 831 |
$allowed_types = array('application/xrds+xml', 'text/xml'); |
| 832 |
|
| 833 |
# Only allow text/html content type for the Yahoo logins, since |
| 834 |
# it might cause an endless redirection for the other providers. |
| 835 |
if ($this->get_provider_name($this->claimed_id) == 'yahoo') { |
| 836 |
$allowed_types[] = 'text/html'; |
| 837 |
} |
| 838 |
|
| 839 |
foreach ($allowed_types as $type) { |
| 840 |
if (strpos($content_type, $type) !== false) { |
| 841 |
return true; |
| 842 |
} |
| 843 |
} |
| 844 |
|
| 845 |
return false; |
| 846 |
} |
| 847 |
|
| 848 |
/** |
| 849 |
* @param $provider_url |
| 850 |
* |
| 851 |
* @return string |
| 852 |
*/ |
| 853 |
protected function get_provider_name($provider_url) |
| 854 |
{ |
| 855 |
$result = ''; |
| 856 |
|
| 857 |
if (!empty($provider_url)) { |
| 858 |
$tokens = array_reverse( |
| 859 |
explode('.', parse_url($provider_url, PHP_URL_HOST)) |
| 860 |
); |
| 861 |
$result = strtolower( |
| 862 |
(count($tokens) > 1 && strlen($tokens[1]) > 3) |
| 863 |
? $tokens[1] |
| 864 |
: (count($tokens) > 2 ? $tokens[2] : '') |
| 865 |
); |
| 866 |
} |
| 867 |
|
| 868 |
return $result; |
| 869 |
} |
| 870 |
|
| 871 |
/** |
| 872 |
* @return array |
| 873 |
*/ |
| 874 |
protected function sregParams() |
| 875 |
{ |
| 876 |
$params = array(); |
| 877 |
# We always use SREG 1.1, even if the server is advertising only support for 1.0. |
| 878 |
# That's because it's fully backwards compatible with 1.0, and some providers |
| 879 |
# advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com |
| 880 |
$params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; |
| 881 |
if ($this->required) { |
| 882 |
$params['openid.sreg.required'] = array(); |
| 883 |
foreach ($this->required as $required) { |
| 884 |
if (!isset(self::$ax_to_sreg[$required])) { |
| 885 |
continue; |
| 886 |
} |
| 887 |
$params['openid.sreg.required'][] = self::$ax_to_sreg[$required]; |
| 888 |
} |
| 889 |
$params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); |
| 890 |
} |
| 891 |
|
| 892 |
if ($this->optional) { |
| 893 |
$params['openid.sreg.optional'] = array(); |
| 894 |
foreach ($this->optional as $optional) { |
| 895 |
if (!isset(self::$ax_to_sreg[$optional])) { |
| 896 |
continue; |
| 897 |
} |
| 898 |
$params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional]; |
| 899 |
} |
| 900 |
$params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); |
| 901 |
} |
| 902 |
return $params; |
| 903 |
} |
| 904 |
|
| 905 |
/** |
| 906 |
* @return array |
| 907 |
*/ |
| 908 |
protected function axParams() |
| 909 |
{ |
| 910 |
$params = array(); |
| 911 |
if ($this->required || $this->optional) { |
| 912 |
$params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; |
| 913 |
$params['openid.ax.mode'] = 'fetch_request'; |
| 914 |
$this->aliases = array(); |
| 915 |
$counts = array(); |
| 916 |
$required = array(); |
| 917 |
$optional = array(); |
| 918 |
foreach (array('required','optional') as $type) { |
| 919 |
foreach ($this->$type as $alias => $field) { |
| 920 |
if (is_int($alias)) { |
| 921 |
$alias = strtr($field, '/', '_'); |
| 922 |
} |
| 923 |
$this->aliases[$alias] = 'http://axschema.org/' . $field; |
| 924 |
if (empty($counts[$alias])) { |
| 925 |
$counts[$alias] = 0; |
| 926 |
} |
| 927 |
$counts[$alias] += 1; |
| 928 |
${$type}[] = $alias; |
| 929 |
} |
| 930 |
} |
| 931 |
foreach ($this->aliases as $alias => $ns) { |
| 932 |
$params['openid.ax.type.' . $alias] = $ns; |
| 933 |
} |
| 934 |
foreach ($counts as $alias => $count) { |
| 935 |
if ($count == 1) { |
| 936 |
continue; |
| 937 |
} |
| 938 |
$params['openid.ax.count.' . $alias] = $count; |
| 939 |
} |
| 940 |
|
| 941 |
# Don't send empty ax.required and ax.if_available. |
| 942 |
# Google and possibly other providers refuse to support ax when one of these is empty. |
| 943 |
if ($required) { |
| 944 |
$params['openid.ax.required'] = implode(',', $required); |
| 945 |
} |
| 946 |
if ($optional) { |
| 947 |
$params['openid.ax.if_available'] = implode(',', $optional); |
| 948 |
} |
| 949 |
} |
| 950 |
return $params; |
| 951 |
} |
| 952 |
|
| 953 |
/** |
| 954 |
* @param $immediate |
| 955 |
* |
| 956 |
* @return string |
| 957 |
*/ |
| 958 |
protected function authUrl_v1($immediate) |
| 959 |
{ |
| 960 |
$returnUrl = $this->returnUrl; |
| 961 |
# If we have an openid.delegate that is different from our claimed id, |
| 962 |
# we need to somehow preserve the claimed id between requests. |
| 963 |
# The simplest way is to just send it along with the return_to url. |
| 964 |
if ($this->identity != $this->claimed_id) { |
| 965 |
$returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; |
| 966 |
} |
| 967 |
|
| 968 |
$params = array( |
| 969 |
'openid.return_to' => $returnUrl, |
| 970 |
'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', |
| 971 |
'openid.identity' => $this->identity, |
| 972 |
'openid.trust_root' => $this->trustRoot, |
| 973 |
) + $this->sregParams(); |
| 974 |
|
| 975 |
return $this->build_url(parse_url($this->server), array('query' => http_build_query($params, '', '&'))); |
| 976 |
} |
| 977 |
|
| 978 |
/** |
| 979 |
* @param $immediate |
| 980 |
* |
| 981 |
* @return string |
| 982 |
*/ |
| 983 |
protected function authUrl_v2($immediate) |
| 984 |
{ |
| 985 |
$params = array( |
| 986 |
'openid.ns' => 'http://specs.openid.net/auth/2.0', |
| 987 |
'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', |
| 988 |
'openid.return_to' => $this->returnUrl, |
| 989 |
'openid.realm' => $this->trustRoot, |
| 990 |
); |
| 991 |
|
| 992 |
if ($this->ax) { |
| 993 |
$params += $this->axParams(); |
| 994 |
} |
| 995 |
|
| 996 |
if ($this->sreg) { |
| 997 |
$params += $this->sregParams(); |
| 998 |
} |
| 999 |
|
| 1000 |
if (!$this->ax && !$this->sreg) { |
| 1001 |
# If OP doesn't advertise either SREG, nor AX, let's send them both |
| 1002 |
# in worst case we don't get anything in return. |
| 1003 |
$params += $this->axParams() + $this->sregParams(); |
| 1004 |
} |
| 1005 |
|
| 1006 |
if (!empty($this->oauth) && is_array($this->oauth)) { |
| 1007 |
$params['openid.ns.oauth'] = 'http://specs.openid.net/extensions/oauth/1.0'; |
| 1008 |
$params['openid.oauth.consumer'] = str_replace(array('http://', 'https://'), '', $this->trustRoot); |
| 1009 |
$params['openid.oauth.scope'] = implode(' ', $this->oauth); |
| 1010 |
} |
| 1011 |
|
| 1012 |
if ($this->identifier_select) { |
| 1013 |
$params['openid.identity'] = $params['openid.claimed_id'] |
| 1014 |
= 'http://specs.openid.net/auth/2.0/identifier_select'; |
| 1015 |
} else { |
| 1016 |
$params['openid.identity'] = $this->identity; |
| 1017 |
$params['openid.claimed_id'] = $this->claimed_id; |
| 1018 |
} |
| 1019 |
|
| 1020 |
return $this->build_url(parse_url($this->server), array('query' => http_build_query($params, '', '&'))); |
| 1021 |
} |
| 1022 |
|
| 1023 |
/** |
| 1024 |
* Returns authentication url. Usually, you want to redirect your user to it. |
| 1025 |
* @param bool $immediate |
| 1026 |
* @return String The authentication url. |
| 1027 |
* @throws ErrorException |
| 1028 |
*/ |
| 1029 |
public function authUrl($immediate = false) |
| 1030 |
{ |
| 1031 |
if ($this->setup_url && !$immediate) { |
| 1032 |
return $this->setup_url; |
| 1033 |
} |
| 1034 |
if (!$this->server) { |
| 1035 |
$this->discover($this->identity); |
| 1036 |
} |
| 1037 |
|
| 1038 |
if ($this->version == 2) { |
| 1039 |
return $this->authUrl_v2($immediate); |
| 1040 |
} |
| 1041 |
return $this->authUrl_v1($immediate); |
| 1042 |
} |
| 1043 |
|
| 1044 |
/** |
| 1045 |
* Performs OpenID verification with the OP. |
| 1046 |
* @return Bool Whether the verification was successful. |
| 1047 |
* @throws ErrorException |
| 1048 |
*/ |
| 1049 |
public function validate() |
| 1050 |
{ |
| 1051 |
# If the request was using immediate mode, a failure may be reported |
| 1052 |
# by presenting user_setup_url (for 1.1) or reporting |
| 1053 |
# mode 'setup_needed' (for 2.0). Also catching all modes other than |
| 1054 |
# id_res, in order to avoid throwing errors. |
| 1055 |
if (isset($this->data['openid_user_setup_url'])) { |
| 1056 |
$this->setup_url = $this->data['openid_user_setup_url']; |
| 1057 |
return false; |
| 1058 |
} |
| 1059 |
if ($this->mode != 'id_res') { |
| 1060 |
return false; |
| 1061 |
} |
| 1062 |
|
| 1063 |
$this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; |
| 1064 |
$params = array( |
| 1065 |
'openid.assoc_handle' => $this->data['openid_assoc_handle'], |
| 1066 |
'openid.signed' => $this->data['openid_signed'], |
| 1067 |
'openid.sig' => $this->data['openid_sig'], |
| 1068 |
); |
| 1069 |
|
| 1070 |
if (isset($this->data['openid_ns'])) { |
| 1071 |
# We're dealing with an OpenID 2.0 server, so let's set an ns |
| 1072 |
# Even though we should know location of the endpoint, |
| 1073 |
# we still need to verify it by discovery, so $server is not set here |
| 1074 |
$params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; |
| 1075 |
} elseif (isset($this->data['openid_claimed_id']) |
| 1076 |
&& $this->data['openid_claimed_id'] != $this->data['openid_identity'] |
| 1077 |
) { |
| 1078 |
# If it's an OpenID 1 provider, and we've got claimed_id, |
| 1079 |
# we have to append it to the returnUrl, like authUrl_v1 does. |
| 1080 |
$this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') |
| 1081 |
. 'openid.claimed_id=' . $this->claimed_id; |
| 1082 |
} |
| 1083 |
|
| 1084 |
if ($this->data['openid_return_to'] != $this->returnUrl) { |
| 1085 |
# The return_to url must match the url of current request. |
| 1086 |
# I'm assuming that no one will set the returnUrl to something that doesn't make sense. |
| 1087 |
return false; |
| 1088 |
} |
| 1089 |
|
| 1090 |
$server = $this->discover($this->claimed_id); |
| 1091 |
|
| 1092 |
foreach (explode(',', $this->data['openid_signed']) as $item) { |
| 1093 |
$value = $this->data['openid_' . str_replace('.', '_', $item)]; |
| 1094 |
$params['openid.' . $item] = $value; |
| 1095 |
} |
| 1096 |
|
| 1097 |
$params['openid.mode'] = 'check_authentication'; |
| 1098 |
|
| 1099 |
$response = $this->request($server, 'POST', $params); |
| 1100 |
|
| 1101 |
return preg_match('/is_valid\s*:\s*true/i', $response); |
| 1102 |
} |
| 1103 |
|
| 1104 |
/** |
| 1105 |
* @return array |
| 1106 |
*/ |
| 1107 |
protected function getAxAttributes() |
| 1108 |
{ |
| 1109 |
$result = array(); |
| 1110 |
|
| 1111 |
if ($alias = $this->getNamespaceAlias('http://openid.net/srv/ax/1.0', 'ax')) { |
| 1112 |
$prefix = 'openid_' . $alias; |
| 1113 |
$length = strlen('http://axschema.org/'); |
| 1114 |
|
| 1115 |
foreach (explode(',', $this->data['openid_signed']) as $key) { |
| 1116 |
$keyMatch = $alias . '.type.'; |
| 1117 |
|
| 1118 |
if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { |
| 1119 |
continue; |
| 1120 |
} |
| 1121 |
|
| 1122 |
$key = substr($key, strlen($keyMatch)); |
| 1123 |
$idv = $prefix . '_value_' . $key; |
| 1124 |
$idc = $prefix . '_count_' . $key; |
| 1125 |
$key = substr($this->getItem($prefix . '_type_' . $key), $length); |
| 1126 |
|
| 1127 |
if (!empty($key)) { |
| 1128 |
if (($count = intval($this->getItem($idc))) > 0) { |
| 1129 |
$value = array(); |
| 1130 |
|
| 1131 |
for ($i = 1; $i <= $count; $i++) { |
| 1132 |
$value[] = $this->getItem($idv . '_' . $i); |
| 1133 |
} |
| 1134 |
|
| 1135 |
$value = ($count == 1) ? reset($value) : $value; |
| 1136 |
} else { |
| 1137 |
$value = $this->getItem($idv); |
| 1138 |
} |
| 1139 |
|
| 1140 |
if (!is_null($value)) { |
| 1141 |
$result[$key] = $value; |
| 1142 |
} |
| 1143 |
} |
| 1144 |
} |
| 1145 |
} else { |
| 1146 |
// No alias for the AX schema has been found, |
| 1147 |
// so there is no AX data in the OP's response. |
| 1148 |
} |
| 1149 |
|
| 1150 |
return $result; |
| 1151 |
} |
| 1152 |
|
| 1153 |
/** |
| 1154 |
* @return array |
| 1155 |
*/ |
| 1156 |
protected function getSregAttributes() |
| 1157 |
{ |
| 1158 |
$attributes = array(); |
| 1159 |
$sreg_to_ax = array_flip(self::$ax_to_sreg); |
| 1160 |
if ($alias = $this->getNamespaceAlias('http://openid.net/extensions/sreg/1.1', 'sreg')) { |
| 1161 |
foreach (explode(',', $this->data['openid_signed']) as $key) { |
| 1162 |
$keyMatch = $alias . '.'; |
| 1163 |
if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { |
| 1164 |
continue; |
| 1165 |
} |
| 1166 |
$key = substr($key, strlen($keyMatch)); |
| 1167 |
if (!isset($sreg_to_ax[$key])) { |
| 1168 |
# The field name isn't part of the SREG spec, so we ignore it. |
| 1169 |
continue; |
| 1170 |
} |
| 1171 |
$attributes[$sreg_to_ax[$key]] = $this->data['openid_' . $alias . '_' . $key]; |
| 1172 |
} |
| 1173 |
} |
| 1174 |
return $attributes; |
| 1175 |
} |
| 1176 |
|
| 1177 |
/** |
| 1178 |
* Gets AX/SREG attributes provided by OP. should be used only after successful validation. |
| 1179 |
* Note that it does not guarantee that any of the required/optional parameters will be present, |
| 1180 |
* or that there will be no other attributes besides those specified. |
| 1181 |
* In other words. OP may provide whatever information it wants to. |
| 1182 |
* * SREG names will be mapped to AX names. |
| 1183 |
* * |
| 1184 |
* @return array Array of attributes with keys being the AX schema names, e.g. 'contact/email' @see http://www.axschema.org/types/ |
| 1185 |
*/ |
| 1186 |
public function getAttributes() |
| 1187 |
{ |
| 1188 |
if (isset($this->data['openid_ns']) |
| 1189 |
&& $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' |
| 1190 |
) { # OpenID 2.0 |
| 1191 |
# We search for both AX and SREG attributes, with AX taking precedence. |
| 1192 |
return $this->getAxAttributes() + $this->getSregAttributes(); |
| 1193 |
} |
| 1194 |
return $this->getSregAttributes(); |
| 1195 |
} |
| 1196 |
|
| 1197 |
/** |
| 1198 |
* Gets an OAuth request token if the OpenID+OAuth hybrid protocol has been used. |
| 1199 |
* |
| 1200 |
* In order to use the OpenID+OAuth hybrid protocol, you need to add at least one |
| 1201 |
* scope to the $openid->oauth array before you get the call to getAuthUrl(), e.g.: |
| 1202 |
* $openid->oauth[] = 'https://www.googleapis.com/auth/plus.me'; |
| 1203 |
* |
| 1204 |
* Furthermore the registered consumer name must fit the OpenID realm. |
| 1205 |
* To register an OpenID consumer at Google use: https://www.google.com/accounts/ManageDomains |
| 1206 |
* |
| 1207 |
* @return string|bool OAuth request token on success, FALSE if no token was provided. |
| 1208 |
*/ |
| 1209 |
public function getOAuthRequestToken() |
| 1210 |
{ |
| 1211 |
$alias = $this->getNamespaceAlias('http://specs.openid.net/extensions/oauth/1.0'); |
| 1212 |
|
| 1213 |
return !empty($alias) ? $this->data['openid_' . $alias . '_request_token'] : false; |
| 1214 |
} |
| 1215 |
|
| 1216 |
/** |
| 1217 |
* Gets the alias for the specified namespace, if it's present. |
| 1218 |
* |
| 1219 |
* @param string $namespace The namespace for which an alias is needed. |
| 1220 |
* @param string $hint Common alias of this namespace, used for optimization. |
| 1221 |
* @return string|null The namespace alias if found, otherwise - NULL. |
| 1222 |
*/ |
| 1223 |
private function getNamespaceAlias($namespace, $hint = null) |
| 1224 |
{ |
| 1225 |
$result = null; |
| 1226 |
|
| 1227 |
if (empty($hint) || $this->getItem('openid_ns_' . $hint) != $namespace) { |
| 1228 |
// The common alias is either undefined or points to |
| 1229 |
// some other extension - search for another alias.. |
| 1230 |
$prefix = 'openid_ns_'; |
| 1231 |
$length = strlen($prefix); |
| 1232 |
|
| 1233 |
foreach ($this->data as $key => $val) { |
| 1234 |
if (strncmp($key, $prefix, $length) === 0 && $val === $namespace) { |
| 1235 |
$result = trim(substr($key, $length)); |
| 1236 |
break; |
| 1237 |
} |
| 1238 |
} |
| 1239 |
} else { |
| 1240 |
$result = $hint; |
| 1241 |
} |
| 1242 |
|
| 1243 |
return $result; |
| 1244 |
} |
| 1245 |
|
| 1246 |
/** |
| 1247 |
* Gets an item from the $data array by the specified id. |
| 1248 |
* |
| 1249 |
* @param string $id The id of the desired item. |
| 1250 |
* @return string|null The item if found, otherwise - NULL. |
| 1251 |
*/ |
| 1252 |
private function getItem($id) |
| 1253 |
{ |
| 1254 |
return isset($this->data[$id]) ? $this->data[$id] : null; |
| 1255 |
} |
| 1256 |
} |
| 1257 |
|