LightOpenID::hostExists()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 4
nop 1
dl 0
loc 13
rs 10
c 0
b 0
f 0
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
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
110
            # because the developer should see the claimed identifier,
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
111
            # i.e. what he set as identity, not the op-local identifier (which is what we verify)
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type true.
Loading history...
137
        }
138
139
        return !!gethostbynamel($server);
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! ! gethostbynamel($server) returns the type boolean which is incompatible with the documented return type true.
Loading history...
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
Bug introduced by
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 ignore-type  annotation

176
            foreach(explode("\n", /** @scrutinizer ignore-type */ $response) as $header) {
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
228
            # but since get_headers doesn't accept $context parameter,
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
229
            # we have to change the defaults.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
230
            $default = stream_context_get_options(stream_context_get_default());
231
            stream_context_get_default(
232
                array('http' => array(
0 ignored issues
show
Coding Style introduced by
The first index in a multi-value array must be on a new line
Loading history...
233
                    'method' => 'HEAD',
234
                    'header' => 'Accept: application/xrds+xml, */*',
235
                    'ignore_errors' => true,
236
                ))
0 ignored issues
show
Coding Style introduced by
The closing parenthesis of an array declaration should be on a new line.
Loading history...
237
            );
238
239
            $url = $url . ($params ? '?' . $params : '');
240
            $headers_tmp = get_headers ($url);
241
            if(!$headers_tmp) {
0 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
242
                return array();
243
            }
244
245
            # Parsing headers.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
253
                # claimed_id change with them, because get_headers() will
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
254
                # follow redirections automatically.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
255
                # We ignore redirections with relative paths.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
256
                # If any known provider uses them, file a bug report.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Expected 1 space after "="; newline found
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
The variable $opts does not seem to be defined for all execution paths leading up to this point.
Loading history...
Coding Style introduced by
The first index in a multi-value array must be on a new line
Loading history...
277
                'verify_peer' => true,
278
                'capath'      => $this->capath,
279
                'cafile'      => $this->cainfo,
280
            ));
0 ignored issues
show
Coding Style introduced by
The closing parenthesis of an array declaration should be on a new line.
Loading history...
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
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
306
                 :"{$url['username']}:{$url['password']}@"))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
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
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
343
        # It can happen when we'll be lead to an XRDS document
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
344
        # which does not have any OpenID2 services.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
345
        $originalUrl = $url;
346
347
        # A flag to disable yadis discovery in case of failure in headers.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
348
        $yadis = true;
349
350
        # We'll jump a maximum of 5 times, to avoid endless redirections.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
366
                    # While it is against the spec, allowing this here shouldn't break
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
367
                    # compatibility with anything.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
368
                    # ---
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
369
                    # Found an XRDS document, now let's find the server, and optionally delegate.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
375
376
                        # OpenID 2
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
385
                            }
386
                            # Does the server advertise support for either AX or SREG?
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
407
                            }
408
                            # AX can be used only with OpenID 2.0, so checking only SREG
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
The variable $content does not seem to be defined for all execution paths leading up to this point.
Loading history...
439
440
            # At this point, the YADIS Discovery has failed, so we'll switch
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
441
            # to openid2 HTML discovery, then fallback to openid 1.1 discovery.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
455
                if ($delegate) {
456
                    # We have also found an OP-Local ID.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
472
        # That's because it's fully backwards compatibile with 1.0, and some providers
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
473
        # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
474
        $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1';
475
        if ($this->required) {
0 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
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
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
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
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
523
            # Google and possibly other providers refuse to support ax when one of these is empty.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
524
            if($required) {
0 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
introduced by
$required is an empty array, thus is always false.
Loading history...
525
                $params['openid.ax.required'] = implode(',', $required);
526
            }
527
            if($optional) {
0 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
introduced by
$optional is an empty array, thus is always false.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
538
        # we need to somehow preserve the claimed id between requests.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
539
        # The simplest way is to just send it along with the return_to url.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
571
            # in worst case we don't get anything in return.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
612
        # by presenting user_setup_url (for 1.1) or reporting
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
613
        # mode 'setup_needed' (for 2.0). Also catching all modes other than
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
614
        # id_res, in order to avoid throwing errors.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Bug Best Practice introduced by
The property mode does not exist on LightOpenID. Since you implemented __get, consider adding a @property annotation.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
632
            # Even though we should know location of the endpoint,
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
633
            # we still need to verify it by discovery, so $server is not set here
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
639
            # we have to append it to the returnUrl, like authUrl_v1 does.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
646
            # I'm assuing that noone will set the returnUrl to something that doesn't make sense.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
654
            # the function may fail if it is. For example, when fetching
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
655
            # AX namePerson, it might containg an apostrophe, which will be escaped.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
656
            # In such case, validation would fail, since we'd send different data than OP
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
657
            # wants to verify. stripslashes() should solve that problem, but we can't
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
658
            # use it when magic_quotes is off.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Bug introduced by
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 ignore-type  annotation

668
        return preg_match('/is_valid\s*:\s*true/i', /** @scrutinizer ignore-type */ $response);
Loading history...
Bug Best Practice introduced by
The expression return preg_match('/is_v...:\s*true/i', $response) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
677
            $alias = 'ax';
678
        } else {
679
            # 'ax' prefix is either undefined, or points to another extension,
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
680
            # so we search for another prefix
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
692
            # so there is no AX data in the OP's response
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
705
                # associated ns. This shouldn't happen, but it's better
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
706
                # to check, than cause an E_NOTICE.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
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
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
749
            # We search for both AX and SREG attributes, with AX taking precedence.
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
750
            return $this->getAxAttributes() + $this->getSregAttributes();
751
        }
752
        return $this->getSregAttributes();
753
    }
754
}
755