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