1 | <?php |
||||
2 | /** |
||||
3 | * This class provides a simple interface for OpenID (1.1 and 2.0) authentication. |
||||
4 | * Supports Yadis discovery. |
||||
5 | * The authentication process is stateless/dumb. |
||||
6 | * |
||||
7 | * Usage: |
||||
8 | * Sign-on with OpenID is a two step process: |
||||
9 | * Step one is authentication with the provider: |
||||
10 | * <code> |
||||
11 | * $openid = new LightOpenID; |
||||
12 | * $openid->identity = 'ID supplied by user'; |
||||
13 | * header('Location: ' . $openid->authUrl()); |
||||
14 | * </code> |
||||
15 | * The provider then sends various parameters via GET, one of them is openid_mode. |
||||
16 | * Step two is verification: |
||||
17 | * <code> |
||||
18 | * if ($this->data['openid_mode']) { |
||||
19 | * $openid = new LightOpenID; |
||||
20 | * echo $openid->validate() ? 'Logged in.' : 'Failed'; |
||||
21 | * } |
||||
22 | * </code> |
||||
23 | * |
||||
24 | * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias). |
||||
25 | * The default values for those are: |
||||
26 | * $openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; |
||||
27 | * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; |
||||
28 | * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess. |
||||
29 | * |
||||
30 | * AX and SREG extensions are supported. |
||||
31 | * To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl(). |
||||
32 | * These are arrays, with values being AX schema paths (the 'path' part of the URL). |
||||
33 | * For example: |
||||
34 | * $openid->required = array('namePerson/friendly', 'contact/email'); |
||||
35 | * $openid->optional = array('namePerson/first'); |
||||
36 | * If the server supports only SREG or OpenID 1.1, these are automaticaly |
||||
37 | * mapped to SREG names, so that user doesn't have to know anything about the server. |
||||
38 | * |
||||
39 | * To get the values, use $openid->getAttributes(). |
||||
40 | * |
||||
41 | * |
||||
42 | * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled. |
||||
43 | * @author Mewp |
||||
44 | * @copyright Copyright (c) 2010, Mewp |
||||
45 | * @license http://www.opensource.org/licenses/mit-license.php MIT |
||||
46 | */ |
||||
47 | class LightOpenID |
||||
48 | { |
||||
49 | public $returnUrl |
||||
50 | , $required = array() |
||||
51 | , $optional = array() |
||||
52 | , $verify_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. |
||||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||||
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, |
||||
0 ignored issues
–
show
|
|||||
110 | # because the developer should see the claimed identifier, |
||||
0 ignored issues
–
show
|
|||||
111 | # i.e. what he set as identity, not the op-local identifier (which is what we verify) |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
The type
url was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
125 | * @return true, if the server exists; false otherwise |
||||
126 | */ |
||||
127 | function hostExists($url) |
||||
128 | { |
||||
129 | if (strpos($url, '/') === false) { |
||||
130 | $server = $url; |
||||
131 | } else { |
||||
132 | $server = @parse_url($url, PHP_URL_HOST); |
||||
133 | } |
||||
134 | |||||
135 | if (!$server) { |
||||
136 | return false; |
||||
0 ignored issues
–
show
|
|||||
137 | } |
||||
138 | |||||
139 | return !!gethostbynamel($server); |
||||
0 ignored issues
–
show
|
|||||
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) { |
||||
0 ignored issues
–
show
It seems like
$response can also be of type true ; however, parameter $string of explode() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
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, |
||||
0 ignored issues
–
show
|
|||||
228 | # but since get_headers doesn't accept $context parameter, |
||||
0 ignored issues
–
show
|
|||||
229 | # we have to change the defaults. |
||||
0 ignored issues
–
show
|
|||||
230 | $default = stream_context_get_options(stream_context_get_default()); |
||||
231 | stream_context_get_default( |
||||
232 | array('http' => array( |
||||
0 ignored issues
–
show
|
|||||
233 | 'method' => 'HEAD', |
||||
234 | 'header' => 'Accept: application/xrds+xml, */*', |
||||
235 | 'ignore_errors' => true, |
||||
236 | )) |
||||
0 ignored issues
–
show
|
|||||
237 | ); |
||||
238 | |||||
239 | $url = $url . ($params ? '?' . $params : ''); |
||||
240 | $headers_tmp = get_headers ($url); |
||||
241 | if(!$headers_tmp) { |
||||
0 ignored issues
–
show
The expression
$headers_tmp of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
242 | return array(); |
||||
243 | } |
||||
244 | |||||
245 | # Parsing headers. |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
253 | # claimed_id change with them, because get_headers() will |
||||
0 ignored issues
–
show
|
|||||
254 | # follow redirections automatically. |
||||
0 ignored issues
–
show
|
|||||
255 | # We ignore redirections with relative paths. |
||||
0 ignored issues
–
show
|
|||||
256 | # If any known provider uses them, file a bug report. |
||||
0 ignored issues
–
show
|
|||||
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 = |
||||
0 ignored issues
–
show
|
|||||
263 | $this->claimed_id = $parsed_url['scheme'] . '://' |
||||
264 | . $parsed_url['host'] |
||||
265 | . $headers[$name]; |
||||
266 | } |
||||
267 | } |
||||
268 | } |
||||
269 | |||||
270 | # And restore them. |
||||
0 ignored issues
–
show
|
|||||
271 | stream_context_get_default($default); |
||||
272 | return $headers; |
||||
273 | } |
||||
274 | |||||
275 | if($this->verify_peer) { |
||||
276 | $opts += array('ssl' => array( |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
277 | 'verify_peer' => true, |
||||
278 | 'capath' => $this->capath, |
||||
279 | 'cafile' => $this->cainfo, |
||||
280 | )); |
||||
0 ignored issues
–
show
|
|||||
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']}@" |
||||
0 ignored issues
–
show
|
|||||
306 | :"{$url['username']}:{$url['password']}@")) |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
The type
Identity was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
343 | # It can happen when we'll be lead to an XRDS document |
||||
0 ignored issues
–
show
|
|||||
344 | # which does not have any OpenID2 services. |
||||
0 ignored issues
–
show
|
|||||
345 | $originalUrl = $url; |
||||
346 | |||||
347 | # A flag to disable yadis discovery in case of failure in headers. |
||||
0 ignored issues
–
show
|
|||||
348 | $yadis = true; |
||||
349 | |||||
350 | # We'll jump a maximum of 5 times, to avoid endless redirections. |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
366 | # While it is against the spec, allowing this here shouldn't break |
||||
0 ignored issues
–
show
|
|||||
367 | # compatibility with anything. |
||||
0 ignored issues
–
show
|
|||||
368 | # --- |
||||
0 ignored issues
–
show
|
|||||
369 | # Found an XRDS document, now let's find the server, and optionally delegate. |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
375 | |||||
376 | # OpenID 2 |
||||
0 ignored issues
–
show
|
|||||
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; |
||||
0 ignored issues
–
show
|
|||||
385 | } |
||||
386 | # Does the server advertise support for either AX or SREG? |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
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; |
||||
0 ignored issues
–
show
|
|||||
407 | } |
||||
408 | # AX can be used only with OpenID 2.0, so checking only SREG |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
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'); |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
439 | |||||
440 | # At this point, the YADIS Discovery has failed, so we'll switch |
||||
0 ignored issues
–
show
|
|||||
441 | # to openid2 HTML discovery, then fallback to openid 1.1 discovery. |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
455 | if ($delegate) { |
||||
456 | # We have also found an OP-Local ID. |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
472 | # That's because it's fully backwards compatibile with 1.0, and some providers |
||||
0 ignored issues
–
show
|
|||||
473 | # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com |
||||
0 ignored issues
–
show
|
|||||
474 | $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; |
||||
475 | if ($this->required) { |
||||
0 ignored issues
–
show
The expression
$this->required of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
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) { |
||||
0 ignored issues
–
show
The expression
$this->optional of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
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) { |
||||
0 ignored issues
–
show
The expression
$this->required of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() The expression
$this->optional of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
523 | # Google and possibly other providers refuse to support ax when one of these is empty. |
||||
0 ignored issues
–
show
|
|||||
524 | if($required) { |
||||
0 ignored issues
–
show
The expression
$required of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
525 | $params['openid.ax.required'] = implode(',', $required); |
||||
526 | } |
||||
527 | if($optional) { |
||||
0 ignored issues
–
show
The expression
$optional of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
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, |
||||
0 ignored issues
–
show
|
|||||
538 | # we need to somehow preserve the claimed id between requests. |
||||
0 ignored issues
–
show
|
|||||
539 | # The simplest way is to just send it along with the return_to url. |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
571 | # in worst case we don't get anything in return. |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
612 | # by presenting user_setup_url (for 1.1) or reporting |
||||
0 ignored issues
–
show
|
|||||
613 | # mode 'setup_needed' (for 2.0). Also catching all modes other than |
||||
0 ignored issues
–
show
|
|||||
614 | # id_res, in order to avoid throwing errors. |
||||
0 ignored issues
–
show
|
|||||
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') { |
||||
0 ignored issues
–
show
The property
mode does not exist on LightOpenID . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
632 | # Even though we should know location of the endpoint, |
||||
0 ignored issues
–
show
|
|||||
633 | # we still need to verify it by discovery, so $server is not set here |
||||
0 ignored issues
–
show
|
|||||
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, |
||||
0 ignored issues
–
show
|
|||||
639 | # we have to append it to the returnUrl, like authUrl_v1 does. |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
646 | # I'm assuing that noone will set the returnUrl to something that doesn't make sense. |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
654 | # the function may fail if it is. For example, when fetching |
||||
0 ignored issues
–
show
|
|||||
655 | # AX namePerson, it might containg an apostrophe, which will be escaped. |
||||
0 ignored issues
–
show
|
|||||
656 | # In such case, validation would fail, since we'd send different data than OP |
||||
0 ignored issues
–
show
|
|||||
657 | # wants to verify. stripslashes() should solve that problem, but we can't |
||||
0 ignored issues
–
show
|
|||||
658 | # use it when magic_quotes is off. |
||||
0 ignored issues
–
show
|
|||||
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); |
||||
0 ignored issues
–
show
It seems like
$response can also be of type true ; however, parameter $subject of preg_match() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
677 | $alias = 'ax'; |
||||
678 | } else { |
||||
679 | # 'ax' prefix is either undefined, or points to another extension, |
||||
0 ignored issues
–
show
|
|||||
680 | # so we search for another prefix |
||||
0 ignored issues
–
show
|
|||||
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, |
||||
0 ignored issues
–
show
|
|||||
692 | # so there is no AX data in the OP's response |
||||
0 ignored issues
–
show
|
|||||
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 |
||||
0 ignored issues
–
show
|
|||||
705 | # associated ns. This shouldn't happen, but it's better |
||||
0 ignored issues
–
show
|
|||||
706 | # to check, than cause an E_NOTICE. |
||||
0 ignored issues
–
show
|
|||||
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. |
||||
0 ignored issues
–
show
|
|||||
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() |
||||
745 | { |
||||
746 | if (isset($this->data['openid_ns']) |
||||
747 | && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' |
||||
748 | ) { # OpenID 2.0 |
||||
0 ignored issues
–
show
|
|||||
749 | # We search for both AX and SREG attributes, with AX taking precedence. |
||||
0 ignored issues
–
show
|
|||||
750 | return $this->getAxAttributes() + $this->getSregAttributes(); |
||||
751 | } |
||||
752 | return $this->getSregAttributes(); |
||||
753 | } |
||||
754 | } |
||||
755 |