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 |