Passed
Push — 1.10.x ( ef00db...25211a )
by Yannick
279:14 queued 234:31
created

CASClient::checkAuthentication()   C

Complexity

Conditions 9
Paths 8

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 26
c 1
b 0
f 0
nc 8
nop 0
dl 0
loc 46
rs 5.0942
1
<?php
2
3
/*
4
 * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
5
 * All rights reserved.
6
 * 
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions are met:
9
 * 
10
 *     * Redistributions of source code must retain the above copyright notice,
11
 *       this list of conditions and the following disclaimer.
12
 *     * Redistributions in binary form must reproduce the above copyright notice,
13
 *       this list of conditions and the following disclaimer in the documentation
14
 *       and/or other materials provided with the distribution.
15
 *     * Neither the name of the ESUP-Portail consortium & the JA-SIG
16
 *       Collaborative nor the names of its contributors may be used to endorse or
17
 *       promote products derived from this software without specific prior
18
 *       written permission.
19
20
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
 */
31
32
/**
33
 * @file CAS/client.php
34
 * Main class of the phpCAS library
35
 */
36
37
// include internationalization stuff
38
include_once(dirname(__FILE__) . '/languages/languages.php');
39
40
// include PGT storage classes
41
include_once(dirname(__FILE__) . '/PGTStorage/pgt-main.php');
42
43
/**
44
 * @class CASClient
45
 * The CASClient class is a client interface that provides CAS authentication
46
 * to PHP applications.
47
 *
48
 * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
49
 */
50
class CASClient
51
{
52
53
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
54
    // XX                                                                    XX
55
    // XX                          CONFIGURATION                             XX
56
    // XX                                                                    XX
57
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
58
59
    // ########################################################################
60
    //  HTML OUTPUT
61
    // ########################################################################
62
    /**
63
     * @addtogroup internalOutput
64
     * @{
65
     */
66
67
    /**
68
     * This method filters a string by replacing special tokens by appropriate values
69
     * and prints it. The corresponding tokens are taken into account:
70
     * - __CAS_VERSION__
71
     * - __PHPCAS_VERSION__
72
     * - __SERVER_BASE_URL__
73
     *
74
     * Used by CASClient::PrintHTMLHeader() and CASClient::printHTMLFooter().
75
     *
76
     * @param $str the string to filter and output
77
     *
78
     * @private
79
     */
80
    function HTMLFilterOutput($str)
81
    {
82
        $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str);
83
        $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str);
84
        $str = str_replace('__SERVER_BASE_URL__', $this->getServerBaseURL(), $str);
85
        echo $str;
86
    }
87
88
    /**
89
     * A string used to print the header of HTML pages. Written by CASClient::setHTMLHeader(),
90
     * read by CASClient::printHTMLHeader().
91
     *
92
     * @hideinitializer
93
     * @private
94
     * @see CASClient::setHTMLHeader, CASClient::printHTMLHeader()
95
     */
96
    var $_output_header = '';
97
98
    /**
99
     * This method prints the header of the HTML output (after filtering). If
100
     * CASClient::setHTMLHeader() was not used, a default header is output.
101
     *
102
     * @param $title the title of the page
103
     *
104
     * @see HTMLFilterOutput()
105
     * @private
106
     */
107
    function printHTMLHeader($title)
108
    {
109
        $this->HTMLFilterOutput(str_replace('__TITLE__',
110
                $title,
111
                (empty($this->_output_header)
112
                    ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
113
                    : $this->_output_header)
114
            )
115
        );
116
    }
117
118
    /**
119
     * A string used to print the footer of HTML pages. Written by CASClient::setHTMLFooter(),
120
     * read by printHTMLFooter().
121
     *
122
     * @hideinitializer
123
     * @private
124
     * @see CASClient::setHTMLFooter, CASClient::printHTMLFooter()
125
     */
126
    var $_output_footer = '';
127
128
    /**
129
     * This method prints the footer of the HTML output (after filtering). If
130
     * CASClient::setHTMLFooter() was not used, a default footer is output.
131
     *
132
     * @see HTMLFilterOutput()
133
     * @private
134
     */
135
    function printHTMLFooter()
136
    {
137
        $this->HTMLFilterOutput(empty($this->_output_footer)
138
            ? ('<hr><address>phpCAS __PHPCAS_VERSION__ ' . $this->getString(CAS_STR_USING_SERVER) . ' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>')
139
            : $this->_output_footer);
140
    }
141
142
    /**
143
     * This method set the HTML header used for all outputs.
144
     *
145
     * @param $header the HTML header.
146
     *
147
     * @public
148
     */
149
    function setHTMLHeader($header)
150
    {
151
        $this->_output_header = $header;
152
    }
153
154
    /**
155
     * This method set the HTML footer used for all outputs.
156
     *
157
     * @param $footer the HTML footer.
158
     *
159
     * @public
160
     */
161
    function setHTMLFooter($footer)
162
    {
163
        $this->_output_footer = $footer;
164
    }
165
166
    /** @} */
167
    // ########################################################################
168
    //  INTERNATIONALIZATION
169
    // ########################################################################
170
    /**
171
     * @addtogroup internalLang
172
     * @{
173
     */
174
    /**
175
     * A string corresponding to the language used by phpCAS. Written by
176
     * CASClient::setLang(), read by CASClient::getLang().
177
     * @note debugging information is always in english (debug purposes only).
178
     *
179
     * @hideinitializer
180
     * @private
181
     * @sa CASClient::_strings, CASClient::getString()
182
     */
183
    var $_lang = '';
184
185
    /**
186
     * This method returns the language used by phpCAS.
187
     *
188
     * @return a string representing the language
189
     *
190
     * @private
191
     */
192
    function getLang()
193
    {
194
        if (empty($this->_lang)) {
195
            $this->setLang(PHPCAS_LANG_DEFAULT);
196
        }
197
        return $this->_lang;
198
    }
199
200
    /**
201
     * array containing the strings used by phpCAS. Written by CASClient::setLang(), read by
202
     * CASClient::getString() and used by CASClient::setLang().
203
     *
204
     * @note This array is filled by instructions in CAS/languages/<$this->_lang>.php
205
     *
206
     * @private
207
     * @see CASClient::_lang, CASClient::getString(), CASClient::setLang(), CASClient::getLang()
208
     */
209
    var $_strings;
210
211
    /**
212
     * This method returns a string depending on the language.
213
     *
214
     * @param $str the index of the string in $_string.
215
     *
216
     * @return the string corresponding to $index in $string.
217
     *
218
     * @private
219
     */
220
    function getString($str)
221
    {
222
        // call CASclient::getLang() to be sure the language is initialized
223
        $this->getLang();
224
225
        if (!isset($this->_strings[$str])) {
226
            trigger_error('string `' . $str . '\' not defined for language `' . $this->getLang() . '\'', E_USER_ERROR);
227
        }
228
        return $this->_strings[$str];
229
    }
230
231
    /**
232
     * This method is used to set the language used by phpCAS.
233
     * @note Can be called only once.
234
     *
235
     * @param $lang a string representing the language.
236
     *
237
     * @public
238
     * @sa CAS_LANG_FRENCH, CAS_LANG_ENGLISH
239
     */
240
    function setLang($lang)
241
    {
242
        // include the corresponding language file
243
        include_once(dirname(__FILE__) . '/languages/' . $lang . '.php');
244
245
        if (!is_array($this->_strings)) {
246
            trigger_error('language `' . $lang . '\' is not implemented', E_USER_ERROR);
247
        }
248
        $this->_lang = $lang;
249
    }
250
251
    /** @} */
252
    // ########################################################################
253
    //  CAS SERVER CONFIG
254
    // ########################################################################
255
    /**
256
     * @addtogroup internalConfig
257
     * @{
258
     */
259
260
    /**
261
     * a record to store information about the CAS server.
262
     * - $_server["version"]: the version of the CAS server
263
     * - $_server["hostname"]: the hostname of the CAS server
264
     * - $_server["port"]: the port the CAS server is running on
265
     * - $_server["uri"]: the base URI the CAS server is responding on
266
     * - $_server["base_url"]: the base URL of the CAS server
267
     * - $_server["login_url"]: the login URL of the CAS server
268
     * - $_server["service_validate_url"]: the service validating URL of the CAS server
269
     * - $_server["proxy_url"]: the proxy URL of the CAS server
270
     * - $_server["proxy_validate_url"]: the proxy validating URL of the CAS server
271
     * - $_server["logout_url"]: the logout URL of the CAS server
272
     *
273
     * $_server["version"], $_server["hostname"], $_server["port"] and $_server["uri"]
274
     * are written by CASClient::CASClient(), read by CASClient::getServerVersion(),
275
     * CASClient::getServerHostname(), CASClient::getServerPort() and CASClient::getServerURI().
276
     *
277
     * The other fields are written and read by CASClient::getServerBaseURL(),
278
     * CASClient::getServerLoginURL(), CASClient::getServerServiceValidateURL(),
279
     * CASClient::getServerProxyValidateURL() and CASClient::getServerLogoutURL().
280
     *
281
     * @hideinitializer
282
     * @private
283
     */
284
    var $_server = array(
285
        'version' => -1,
286
        'hostname' => 'none',
287
        'port' => -1,
288
        'uri' => 'none'
289
    );
290
291
    /**
292
     * This method is used to retrieve the version of the CAS server.
293
     * @return the version of the CAS server.
294
     * @private
295
     */
296
    function getServerVersion()
297
    {
298
        return $this->_server['version'];
299
    }
300
301
    /**
302
     * This method is used to retrieve the hostname of the CAS server.
303
     * @return the hostname of the CAS server.
304
     * @private
305
     */
306
    function getServerHostname()
307
    {
308
        return $this->_server['hostname'];
309
    }
310
311
    /**
312
     * This method is used to retrieve the port of the CAS server.
313
     * @return the port of the CAS server.
314
     * @private
315
     */
316
    function getServerPort()
317
    {
318
        return $this->_server['port'];
319
    }
320
321
    /**
322
     * This method is used to retrieve the URI of the CAS server.
323
     * @return a URI.
324
     * @private
325
     */
326
    function getServerURI()
327
    {
328
        return $this->_server['uri'];
329
    }
330
331
    /**
332
     * This method is used to retrieve the base URL of the CAS server.
333
     * @return a URL.
334
     * @private
335
     */
336
    function getServerBaseURL()
337
    {
338
        // the URL is build only when needed
339
        if (empty($this->_server['base_url'])) {
340
            $this->_server['base_url'] = 'https://'
341
                . $this->getServerHostname()
342
                . ':'
343
                . $this->getServerPort()
344
                . $this->getServerURI();
345
        }
346
        return $this->_server['base_url'];
347
    }
348
349
    /**
350
     * This method is used to retrieve the login URL of the CAS server.
351
     * @param $gateway true to check authentication, false to force it
352
     * @param $renew true to force the authentication with the CAS server
353
     * NOTE : It is recommended that CAS implementations ignore the
354
     * "gateway" parameter if "renew" is set
355
     * @return a URL.
356
     * @private
357
     */
358
    function getServerLoginURL($gateway = false, $renew = false)
359
    {
360
        phpCAS::traceBegin();
361
        // the URL is build only when needed
362
        if (empty($this->_server['login_url'])) {
363
            $this->_server['login_url'] = $this->getServerBaseURL();
364
            $this->_server['login_url'] .= 'login?service=';
365
            // $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL());
366
            $this->_server['login_url'] .= urlencode($this->getURL());
367
            if ($renew) {
368
                // It is recommended that when the "renew" parameter is set, its value be "true"
369
                $this->_server['login_url'] .= '&renew=true';
370
            } elseif ($gateway) {
371
                // It is recommended that when the "gateway" parameter is set, its value be "true"
372
                $this->_server['login_url'] .= '&gateway=true';
373
            }
374
        }
375
        phpCAS::traceEnd($this->_server['login_url']);
376
        return $this->_server['login_url'];
377
    }
378
379
    /**
380
     * This method sets the login URL of the CAS server.
381
     * @param $url the login URL
382
     * @private
383
     * @since 0.4.21 by Wyman Chan
384
     */
385
    function setServerLoginURL($url)
386
    {
387
        return $this->_server['login_url'] = $url;
388
    }
389
390
391
    /**
392
     * This method sets the serviceValidate URL of the CAS server.
393
     * @param $url the serviceValidate URL
394
     * @private
395
     * @since 1.1.0 by Joachim Fritschi
396
     */
397
    function setServerServiceValidateURL($url)
398
    {
399
        return $this->_server['service_validate_url'] = $url;
400
    }
401
402
403
    /**
404
     * This method sets the proxyValidate URL of the CAS server.
405
     * @param $url the proxyValidate URL
406
     * @private
407
     * @since 1.1.0 by Joachim Fritschi
408
     */
409
    function setServerProxyValidateURL($url)
410
    {
411
        return $this->_server['proxy_validate_url'] = $url;
412
    }
413
414
415
    /**
416
     * This method sets the samlValidate URL of the CAS server.
417
     * @param $url the samlValidate URL
418
     * @private
419
     * @since 1.1.0 by Joachim Fritschi
420
     */
421
    function setServerSamlValidateURL($url)
422
    {
423
        return $this->_server['saml_validate_url'] = $url;
424
    }
425
426
427
    /**
428
     * This method is used to retrieve the service validating URL of the CAS server.
429
     * @return a URL.
430
     * @private
431
     */
432
    function getServerServiceValidateURL()
433
    {
434
        // the URL is build only when needed
435
        if (empty($this->_server['service_validate_url'])) {
436
            switch ($this->getServerVersion()) {
437
                case CAS_VERSION_1_0:
438
                    $this->_server['service_validate_url'] = $this->getServerBaseURL() . 'validate';
439
                    break;
440
                case CAS_VERSION_2_0:
441
                    $this->_server['service_validate_url'] = $this->getServerBaseURL() . 'serviceValidate';
442
                    break;
443
            }
444
        }
445
        //      return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
446
        return $this->_server['service_validate_url'] . '?service=' . urlencode($this->getURL());
447
    }
448
449
    /**
450
     * This method is used to retrieve the SAML validating URL of the CAS server.
451
     * @return a URL.
452
     * @private
453
     */
454
    function getServerSamlValidateURL()
455
    {
456
        phpCAS::traceBegin();
457
        // the URL is build only when needed
458
        if (empty($this->_server['saml_validate_url'])) {
459
            switch ($this->getServerVersion()) {
460
                case SAML_VERSION_1_1:
461
                    $this->_server['saml_validate_url'] = $this->getServerBaseURL() . 'samlValidate';
462
                    break;
463
            }
464
        }
465
        phpCAS::traceEnd($this->_server['saml_validate_url'] . '?TARGET=' . urlencode($this->getURL()));
466
        return $this->_server['saml_validate_url'] . '?TARGET=' . urlencode($this->getURL());
467
    }
468
469
    /**
470
     * This method is used to retrieve the proxy validating URL of the CAS server.
471
     * @return a URL.
472
     * @private
473
     */
474 View Code Duplication
    function getServerProxyValidateURL()
475
    {
476
        // the URL is build only when needed
477
        if (empty($this->_server['proxy_validate_url'])) {
478
            switch ($this->getServerVersion()) {
479
                case CAS_VERSION_1_0:
480
                    $this->_server['proxy_validate_url'] = '';
481
                    break;
482
                case CAS_VERSION_2_0:
483
                    $this->_server['proxy_validate_url'] = $this->getServerBaseURL() . 'proxyValidate';
484
                    break;
485
            }
486
        }
487
        //      return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
488
        return $this->_server['proxy_validate_url'] . '?service=' . urlencode($this->getURL());
489
    }
490
491
    /**
492
     * This method is used to retrieve the proxy URL of the CAS server.
493
     * @return a URL.
494
     * @private
495
     */
496 View Code Duplication
    function getServerProxyURL()
497
    {
498
        // the URL is build only when needed
499
        if (empty($this->_server['proxy_url'])) {
500
            switch ($this->getServerVersion()) {
501
                case CAS_VERSION_1_0:
502
                    $this->_server['proxy_url'] = '';
503
                    break;
504
                case CAS_VERSION_2_0:
505
                    $this->_server['proxy_url'] = $this->getServerBaseURL() . 'proxy';
506
                    break;
507
            }
508
        }
509
        return $this->_server['proxy_url'];
510
    }
511
512
    /**
513
     * This method is used to retrieve the logout URL of the CAS server.
514
     * @return a URL.
515
     * @private
516
     */
517
    function getServerLogoutURL()
518
    {
519
        // the URL is build only when needed
520
        if (empty($this->_server['logout_url'])) {
521
            $this->_server['logout_url'] = $this->getServerBaseURL() . 'logout';
522
        }
523
        return $this->_server['logout_url'];
524
    }
525
526
    /**
527
     * This method sets the logout URL of the CAS server.
528
     * @param $url the logout URL
529
     * @private
530
     * @since 0.4.21 by Wyman Chan
531
     */
532
    function setServerLogoutURL($url)
533
    {
534
        return $this->_server['logout_url'] = $url;
535
    }
536
537
    /**
538
     * An array to store extra curl options.
539
     */
540
    var $_curl_options = array();
541
542
    /**
543
     * This method is used to set additional user curl options.
544
     */
545
    function setExtraCurlOption($key, $value)
546
    {
547
        $this->_curl_options[$key] = $value;
548
    }
549
550
    /**
551
     * This method checks to see if the request is secured via HTTPS
552
     * @return true if https, false otherwise
553
     * @private
554
     */
555
    function isHttps()
556
    {
557
        //if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ) {
558
        //0.4.24 by Hinnack
559
        if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
560
            return true;
561
        } else {
562
            return false;
563
        }
564
    }
565
566
    // ########################################################################
567
    //  CONSTRUCTOR
568
    // ########################################################################
569
    /**
570
     * CASClient constructor.
571
     *
572
     * @param $server_version the version of the CAS server
573
     * @param $proxy TRUE if the CAS client is a CAS proxy, FALSE otherwise
574
     * @param $server_hostname the hostname of the CAS server
575
     * @param $server_port the port the CAS server is running on
576
     * @param $server_uri the URI the CAS server is responding on
577
     * @param $start_session Have phpCAS start PHP sessions (default true)
578
     *
579
     * @return a newly created CASClient object
580
     *
581
     * @public
582
     */
583
    function CASClient(
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
584
        $server_version,
585
        $proxy,
586
        $server_hostname,
587
        $server_port,
588
        $server_uri,
589
        $start_session = true
590
    ) {
591
592
        phpCAS::traceBegin();
593
594
        // the redirect header() call and DOM parsing code from domxml-php4-php5.php won't work in PHP4 compatibility mode
595
        if (version_compare(PHP_VERSION, '5', '>=') && ini_get('zend.ze1_compatibility_mode')) {
596
            phpCAS::error('phpCAS cannot support zend.ze1_compatibility_mode. Sorry.');
597
        }
598
        // skip Session Handling for logout requests and if don't want it'
599
        if ($start_session && !$this->isLogoutRequest()) {
600
            phpCAS::trace("Starting session handling");
601
            // Check for Tickets from the CAS server
602
            if (empty($_GET['ticket'])) {
603
                phpCAS::trace("No ticket found");
604
                // only create a session if necessary
605
                if (!session_id()) {
606
                    phpCAS::trace("No session found, creating new session");
607
                    session_start();
608
                }
609
            } else {
610
                phpCAS::trace("Ticket found");
611
                // We have to copy any old data before renaming the session
612
                if (session_id()) {
613
                    phpCAS::trace("Old active session found, saving old data and destroying session");
614
                    $old_session = $_SESSION;
615
                    session_destroy();
616
                } else {
617
                    session_start();
618
                    phpCAS::trace("Starting possible old session to copy variables");
619
                    $old_session = $_SESSION;
620
                    session_destroy();
621
                }
622
                // set up a new session, of name based on the ticket
623
                $session_id = preg_replace('/[^\w]/', '', $_GET['ticket']);
624
                phpCAS::LOG("Session ID: " . $session_id);
625
                session_id($session_id);
626
                session_start();
627
                // restore old session vars
628
                if (isset($old_session)) {
629
                    phpCAS::trace("Restoring old session vars");
630
                    $_SESSION = $old_session;
631
                }
632
            }
633
        } else {
634
            phpCAS::trace("Skipping session creation");
635
        }
636
637
638
        // are we in proxy mode ?
639
        $this->_proxy = $proxy;
640
641
        //check version
642
        switch ($server_version) {
643
            case CAS_VERSION_1_0:
644
                if ($this->isProxy()) {
645
                    phpCAS::error('CAS proxies are not supported in CAS '
646
                        . $server_version);
647
                }
648
                break;
649
            case CAS_VERSION_2_0:
650
                break;
651
            case SAML_VERSION_1_1:
652
                break;
653
            default:
654
                phpCAS::error('this version of CAS (`'
655
                    . $server_version
656
                    . '\') is not supported by phpCAS '
657
                    . phpCAS::getVersion());
658
        }
659
        $this->_server['version'] = $server_version;
660
661
        // check hostname
662
        if (empty($server_hostname)
663
            || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/', $server_hostname)
664
        ) {
665
            phpCAS::error('bad CAS server hostname (`' . $server_hostname . '\')');
666
        }
667
        $this->_server['hostname'] = $server_hostname;
668
669
        // check port
670
        if ($server_port == 0
671
            || !is_int($server_port)
672
        ) {
673
            phpCAS::error('bad CAS server port (`' . $server_hostname . '\')');
674
        }
675
        $this->_server['port'] = $server_port;
676
677
        // check URI
678
        if (!preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/', $server_uri)) {
679
            phpCAS::error('bad CAS server URI (`' . $server_uri . '\')');
680
        }
681
        // add leading and trailing `/' and remove doubles
682
        $server_uri = preg_replace('/\/\//', '/', '/' . $server_uri . '/');
683
        $this->_server['uri'] = $server_uri;
684
685
        // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
686
        if ($this->isProxy()) {
687
            $this->setCallbackMode(!empty($_GET['pgtIou']) && !empty($_GET['pgtId']));
688
        }
689
690
        if ($this->isCallbackMode()) {
691
            //callback mode: check that phpCAS is secured
692
            if (!$this->isHttps()) {
693
                phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
694
            }
695
        } else {
696
            //normal mode: get ticket and remove it from CGI parameters for developpers
697
            $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
698
            switch ($this->getServerVersion()) {
699 View Code Duplication
                case CAS_VERSION_1_0: // check for a Service Ticket
700
                    if (preg_match('/^ST-/', $ticket)) {
701
                        phpCAS::trace('ST \'' . $ticket . '\' found');
702
                        //ST present
703
                        $this->setST($ticket);
704
                        //ticket has been taken into account, unset it to hide it to applications
705
                        unset($_GET['ticket']);
706
                    } else {
707
                        if (!empty($ticket)) {
708
                            //ill-formed ticket, halt
709
                            phpCAS::error('ill-formed ticket found in the URL (ticket=`' . htmlentities($ticket) . '\')');
710
                        }
711
                    }
712
                    break;
713 View Code Duplication
                case CAS_VERSION_2_0: // check for a Service or Proxy Ticket
714
                    if (preg_match('/^[SP]T-/', $ticket)) {
715
                        phpCAS::trace('ST or PT \'' . $ticket . '\' found');
716
                        $this->setPT($ticket);
717
                        unset($_GET['ticket']);
718
                    } else {
719
                        if (!empty($ticket)) {
720
                            //ill-formed ticket, halt
721
                            phpCAS::error('ill-formed ticket found in the URL (ticket=`' . htmlentities($ticket) . '\')');
722
                        }
723
                    }
724
                    break;
725 View Code Duplication
                case SAML_VERSION_1_1: // SAML just does Service Tickets
726
                    if (preg_match('/^[SP]T-/', $ticket)) {
727
                        phpCAS::trace('SA \'' . $ticket . '\' found');
728
                        $this->setSA($ticket);
729
                        unset($_GET['ticket']);
730
                    } else {
731
                        if (!empty($ticket)) {
732
                            //ill-formed ticket, halt
733
                            phpCAS::error('ill-formed ticket found in the URL (ticket=`' . htmlentities($ticket) . '\')');
734
                        }
735
                    }
736
                    break;
737
            }
738
        }
739
        phpCAS::traceEnd();
740
    }
741
742
    /** @} */
743
744
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
745
    // XX                                                                    XX
746
    // XX                           AUTHENTICATION                           XX
747
    // XX                                                                    XX
748
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
749
750
    /**
751
     * @addtogroup internalAuthentication
752
     * @{
753
     */
754
755
    /**
756
     * The Authenticated user. Written by CASClient::setUser(), read by CASClient::getUser().
757
     * @attention client applications should use phpCAS::getUser().
758
     *
759
     * @hideinitializer
760
     * @private
761
     */
762
    var $_user = '';
763
764
    /**
765
     * This method sets the CAS user's login name.
766
     *
767
     * @param $user the login name of the authenticated user.
768
     *
769
     * @private
770
     */
771
    function setUser($user)
772
    {
773
        $this->_user = $user;
774
    }
775
776
    /**
777
     * This method returns the CAS user's login name.
778
     * @warning should be called only after CASClient::forceAuthentication() or
779
     * CASClient::isAuthenticated(), otherwise halt with an error.
780
     *
781
     * @return the login name of the authenticated user
782
     */
783 View Code Duplication
    function getUser()
784
    {
785
        if (empty($this->_user)) {
786
            phpCAS::error('this method should be used only after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
787
        }
788
        return $this->_user;
789
    }
790
791
792
793
    /***********************************************************************************************************************
794
     * Atrributes section
795
     *
796
     * @author Matthias Crauwels <[email protected]>, Ghent University, Belgium
797
     *
798
     ***********************************************************************************************************************/
799
    /**
800
     * The Authenticated users attributes. Written by CASClient::setAttributes(), read by CASClient::getAttributes().
801
     * @attention client applications should use phpCAS::getAttributes().
802
     *
803
     * @hideinitializer
804
     * @private
805
     */
806
    var $_attributes = array();
807
808
    function setAttributes($attributes)
809
    {
810
        $this->_attributes = $attributes;
811
    }
812
813 View Code Duplication
    function getAttributes()
814
    {
815
        if (empty($this->_user)) { // if no user is set, there shouldn't be any attributes also...
816
            phpCAS::error('this method should be used only after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
817
        }
818
        return $this->_attributes;
819
    }
820
821
    function hasAttributes()
822
    {
823
        return !empty($this->_attributes);
824
    }
825
826
    function hasAttribute($key)
827
    {
828
        return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes));
829
    }
830
831
    function getAttribute($key)
832
    {
833
        if ($this->hasAttribute($key)) {
834
            return $this->_attributes[$key];
835
        }
836
    }
837
838
    /**
839
     * This method is called to renew the authentication of the user
840
     * If the user is authenticated, renew the connection
841
     * If not, redirect to CAS
842
     * @public
843
     */
844
    function renewAuthentication()
845
    {
846
        phpCAS::traceBegin();
847
        // Either way, the user is authenticated by CAS
848
        if (isset($_SESSION['phpCAS']['auth_checked'])) {
849
            unset($_SESSION['phpCAS']['auth_checked']);
850
        }
851
        if ($this->isAuthenticated()) {
852
            phpCAS::trace('user already authenticated; renew');
853
            $this->redirectToCas(false, true);
854
        } else {
855
            $this->redirectToCas();
856
        }
857
        phpCAS::traceEnd();
858
    }
859
860
    /**
861
     * This method is called to be sure that the user is authenticated. When not
862
     * authenticated, halt by redirecting to the CAS server; otherwise return TRUE.
863
     * @return TRUE when the user is authenticated; otherwise halt.
864
     * @public
865
     */
866
    function forceAuthentication()
867
    {
868
        phpCAS::traceBegin();
869
870
        if ($this->isAuthenticated()) {
871
            // the user is authenticated, nothing to be done.
872
            phpCAS::trace('no need to authenticate');
873
            $res = true;
874
        } else {
875
            // the user is not authenticated, redirect to the CAS server
876
            if (isset($_SESSION['phpCAS']['auth_checked'])) {
877
                unset($_SESSION['phpCAS']['auth_checked']);
878
            }
879
            $this->redirectToCas(false/* no gateway */);
880
            // never reached
881
            $res = false;
882
        }
883
        phpCAS::traceEnd($res);
884
        return $res;
885
    }
886
887
    /**
888
     * An integer that gives the number of times authentication will be cached before rechecked.
889
     *
890
     * @hideinitializer
891
     * @private
892
     */
893
    var $_cache_times_for_auth_recheck = 0;
894
895
    /**
896
     * Set the number of times authentication will be cached before rechecked.
897
     *
898
     * @param $n an integer.
899
     *
900
     * @public
901
     */
902
    function setCacheTimesForAuthRecheck($n)
903
    {
904
        $this->_cache_times_for_auth_recheck = $n;
905
    }
906
907
    /**
908
     * This method is called to check whether the user is authenticated or not.
909
     * @return TRUE when the user is authenticated, FALSE otherwise.
910
     * @public
911
     */
912
    function checkAuthentication()
913
    {
914
        phpCAS::traceBegin();
915
        if ($this->isAuthenticated()) {
916
            phpCAS::trace('user is authenticated');
917
            $res = true;
918
        } else {
919
            if (isset($_SESSION['phpCAS']['auth_checked'])) {
920
                // the previous request has redirected the client to the CAS server with gateway=true
921
                // comment line bellow to
922
//			unset($_SESSION['phpCAS']['auth_checked']);
923
                $res = false;
924
            } else {
925
//        $_SESSION['phpCAS']['auth_checked'] = true;
926
                //	    $this->redirectToCas(TRUE/* gateway */);
927
                //	    // never reached
928
                //	    $res = FALSE;
929
                // avoid a check against CAS on every request
930
                if (!isset($_SESSION['phpCAS']['unauth_count'])) {
931
                    $_SESSION['phpCAS']['unauth_count'] = -2;
932
                } // uninitialized
933
934
                if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1)
935
                    || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)
936
                ) {
937
                    $res = false;
938
939
                    if ($this->_cache_times_for_auth_recheck != -1) {
940
                        $_SESSION['phpCAS']['unauth_count']++;
941
                        phpCAS::trace('user is not authenticated (cached for ' . $_SESSION['phpCAS']['unauth_count'] . ' times of ' . $this->_cache_times_for_auth_recheck . ')');
942
                    } else {
943
                        phpCAS::trace('user is not authenticated (cached for until login pressed)');
944
                    }
945
                } else {
946
                    $_SESSION['phpCAS']['unauth_count'] = 0;
947
                    $_SESSION['phpCAS']['auth_checked'] = true;
948
                    phpCAS::trace('user is not authenticated (cache reset)');
949
                    // $this->redirectToCas(TRUE/* gateway */);
950
                    // never reached
951
                    $res = false;
952
                }
953
            }
954
        }
955
        phpCAS::traceEnd($res);
956
        return $res;
957
    }
958
959
    /**
960
     * This method is called to check if the user is authenticated (previously or by
961
     * tickets given in the URL).
962
     *
963
     * @return TRUE when the user is authenticated. Also may redirect to the same URL without the ticket.
964
     *
965
     * @public
966
     */
967
    function isAuthenticated()
968
    {
969
        phpCAS::traceBegin();
970
        $res = false;
971
        $validate_url = '';
972
973
        if ($this->wasPreviouslyAuthenticated()) {
974
            // the user has already (previously during the session) been
975
            // authenticated, nothing to be done.
976
            phpCAS::trace('user was already authenticated, no need to look for tickets');
977
            $res = true;
978
        } else {
979
            if ($this->hasST()) {
980
                // if a Service Ticket was given, validate it
981
                phpCAS::trace('ST `' . $this->getST() . '\' is present');
982
                $this->validateST($validate_url, $text_response, $tree_response); // if it fails, it halts
983
                phpCAS::trace('ST `' . $this->getST() . '\' was validated');
984 View Code Duplication
                if ($this->isProxy()) {
985
                    $this->validatePGT($validate_url, $text_response, $tree_response); // idem
986
                    phpCAS::trace('PGT `' . $this->getPGT() . '\' was validated');
987
                    $_SESSION['phpCAS']['pgt'] = $this->getPGT();
988
                }
989
                $_SESSION['phpCAS']['user'] = $this->getUser();
990
                $res = true;
991
            } elseif ($this->hasPT()) {
992
                // if a Proxy Ticket was given, validate it
993
                phpCAS::trace('PT `' . $this->getPT() . '\' is present');
994
                $this->validatePT($validate_url, $text_response, $tree_response); // note: if it fails, it halts
995
                phpCAS::trace('PT `' . $this->getPT() . '\' was validated');
996 View Code Duplication
                if ($this->isProxy()) {
997
                    $this->validatePGT($validate_url, $text_response, $tree_response); // idem
998
                    phpCAS::trace('PGT `' . $this->getPGT() . '\' was validated');
999
                    $_SESSION['phpCAS']['pgt'] = $this->getPGT();
1000
                }
1001
                $_SESSION['phpCAS']['user'] = $this->getUser();
1002
                $res = true;
1003
            } elseif ($this->hasSA()) {
1004
                // if we have a SAML ticket, validate it.
1005
                phpCAS::trace('SA `' . $this->getSA() . '\' is present');
1006
                $this->validateSA($validate_url, $text_response, $tree_response); // if it fails, it halts
1007
                phpCAS::trace('SA `' . $this->getSA() . '\' was validated');
1008
                $_SESSION['phpCAS']['user'] = $this->getUser();
1009
                $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1010
                $res = true;
1011
            } else {
1012
                // no ticket given, not authenticated
1013
                phpCAS::trace('no ticket found');
1014
            }
1015
            if ($res) {
1016
                // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
1017
                // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
1018
                header('Location: ' . $this->getURL());
1019
                phpCAS::log("Prepare redirect to : " . $this->getURL());
1020
            }
1021
        }
1022
1023
        phpCAS::traceEnd($res);
1024
        return $res;
1025
    }
1026
1027
    /**
1028
     * This method tells if the current session is authenticated.
1029
     * @return true if authenticated based soley on $_SESSION variable
1030
     * @since 0.4.22 by Brendan Arnold
1031
     */
1032
    function isSessionAuthenticated()
1033
    {
1034
        return !empty($_SESSION['phpCAS']['user']);
1035
    }
1036
1037
    /**
1038
     * This method tells if the user has already been (previously) authenticated
1039
     * by looking into the session variables.
1040
     *
1041
     * @note This function switches to callback mode when needed.
1042
     *
1043
     * @return TRUE when the user has already been authenticated; FALSE otherwise.
1044
     *
1045
     * @private
1046
     */
1047
    function wasPreviouslyAuthenticated()
1048
    {
1049
        phpCAS::traceBegin();
1050
1051
        if ($this->isCallbackMode()) {
1052
            $this->callback();
1053
        }
1054
1055
        $auth = false;
1056
1057
        if ($this->isProxy()) {
1058
            // CAS proxy: username and PGT must be present
1059
            if ($this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt'])) {
1060
                // authentication already done
1061
                $this->setUser($_SESSION['phpCAS']['user']);
1062
                $this->setPGT($_SESSION['phpCAS']['pgt']);
1063
                phpCAS::trace('user = `' . $_SESSION['phpCAS']['user'] . '\', PGT = `' . $_SESSION['phpCAS']['pgt'] . '\'');
1064
                $auth = true;
1065
            } elseif ($this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt'])) {
1066
                // these two variables should be empty or not empty at the same time
1067
                phpCAS::trace('username found (`' . $_SESSION['phpCAS']['user'] . '\') but PGT is empty');
1068
                // unset all tickets to enforce authentication
1069
                unset($_SESSION['phpCAS']);
1070
                $this->setST('');
1071
                $this->setPT('');
1072
            } elseif (!$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt'])) {
1073
                // these two variables should be empty or not empty at the same time
1074
                phpCAS::trace('PGT found (`' . $_SESSION['phpCAS']['pgt'] . '\') but username is empty');
1075
                // unset all tickets to enforce authentication
1076
                unset($_SESSION['phpCAS']);
1077
                $this->setST('');
1078
                $this->setPT('');
1079
            } else {
1080
                phpCAS::trace('neither user not PGT found');
1081
            }
1082
        } else {
1083
            // `simple' CAS client (not a proxy): username must be present
1084
            if ($this->isSessionAuthenticated()) {
1085
                // authentication already done
1086
                $this->setUser($_SESSION['phpCAS']['user']);
1087
                if (isset($_SESSION['phpCAS']['attributes'])) {
1088
                    $this->setAttributes($_SESSION['phpCAS']['attributes']);
1089
                }
1090
                phpCAS::trace('user = `' . $_SESSION['phpCAS']['user'] . '\'');
1091
                $auth = true;
1092
            } else {
1093
                phpCAS::trace('no user found');
1094
            }
1095
        }
1096
1097
        phpCAS::traceEnd($auth);
1098
        return $auth;
1099
    }
1100
1101
    /**
1102
     * This method is used to redirect the client to the CAS server.
1103
     * It is used by CASClient::forceAuthentication() and CASClient::checkAuthentication().
1104
     * @param $gateway true to check authentication, false to force it
1105
     * @param $renew true to force the authentication with the CAS server
1106
     * @public
1107
     */
1108
    function redirectToCas($gateway = false, $renew = false)
1109
    {
1110
        phpCAS::traceBegin();
1111
        $cas_url = $this->getServerLoginURL($gateway, $renew);
1112
        header('Location: ' . $cas_url);
1113
        phpCAS::log("Redirect to : " . $cas_url);
1114
1115
        $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED));
1116
1117
        printf('<p>' . $this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED) . '</p>', $cas_url);
1118
        $this->printHTMLFooter();
1119
1120
        phpCAS::traceExit();
1121
        exit();
1122
    }
1123
1124
1125
    /**
1126
     * This method is used to logout from CAS.
1127
     * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server
1128
     * @public
1129
     */
1130
    function logout($params)
1131
    {
1132
        phpCAS::traceBegin();
1133
        $cas_url = $this->getServerLogoutURL();
1134
        $paramSeparator = '?';
1135 View Code Duplication
        if (isset($params['url'])) {
1136
            $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']);
1137
            $paramSeparator = '&';
1138
        }
1139 View Code Duplication
        if (isset($params['service'])) {
1140
            $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
1141
        }
1142
        header('Location: ' . $cas_url);
1143
        phpCAS::log("Prepare redirect to : " . $cas_url);
1144
1145
        session_unset();
1146
        session_destroy();
1147
1148
        $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
1149
        printf('<p>' . $this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED) . '</p>', $cas_url);
1150
        $this->printHTMLFooter();
1151
1152
        phpCAS::traceExit();
1153
        exit();
1154
    }
1155
1156
    /**
1157
     * @return true if the current request is a logout request.
1158
     * @private
1159
     */
1160
    function isLogoutRequest()
1161
    {
1162
        return !empty($_POST['logoutRequest']);
1163
    }
1164
1165
    /**
1166
     * @return true if a logout request is allowed.
1167
     * @private
1168
     */
1169
    function isLogoutRequestAllowed()
1170
    {
1171
    }
1172
1173
    /**
1174
     * This method handles logout requests.
1175
     * @param $check_client true to check the client bofore handling the request,
1176
     * false not to perform any access control. True by default.
1177
     * @param $allowed_clients an array of host names allowed to send logout requests.
1178
     * By default, only the CAs server (declared in the constructor) will be allowed.
1179
     * @public
1180
     */
1181
    function handleLogoutRequests($check_client = true, $allowed_clients = false)
1182
    {
1183
        phpCAS::traceBegin();
1184
        if (!$this->isLogoutRequest()) {
1185
            phpCAS::log("Not a logout request");
1186
            phpCAS::traceEnd();
1187
            return;
1188
        }
1189
        phpCAS::log("Logout requested");
1190
        phpCAS::log("SAML REQUEST: " . $_POST['logoutRequest']);
1191
        if ($check_client) {
1192
            if (!$allowed_clients) {
1193
                $allowed_clients = array($this->getServerHostname());
1194
            }
1195
            $client_ip = $_SERVER['REMOTE_ADDR'];
1196
            $client = gethostbyaddr($client_ip);
1197
            phpCAS::log("Client: " . $client . "/" . $client_ip);
1198
            $allowed = false;
1199
            foreach ($allowed_clients as $allowed_client) {
0 ignored issues
show
Bug introduced by
The expression $allowed_clients of type array<integer,object<the..."object<the>"}>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1200
                if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
1201
                    phpCAS::log("Allowed client '" . $allowed_client . "' matches, logout request is allowed");
1202
                    $allowed = true;
1203
                    break;
1204
                } else {
1205
                    phpCAS::log("Allowed client '" . $allowed_client . "' does not match");
1206
                }
1207
            }
1208
            if (!$allowed) {
1209
                phpCAS::error("Unauthorized logout request from client '" . $client . "'");
1210
                printf("Unauthorized!");
1211
                phpCAS::traceExit();
1212
                exit();
1213
            }
1214
        } else {
1215
            phpCAS::log("No access control set");
1216
        }
1217
        // Extract the ticket from the SAML Request
1218
        preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $_POST['logoutRequest'], $tick,
1219
            PREG_OFFSET_CAPTURE, 3);
1220
        $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|', '', $tick[0][0]);
1221
        $ticket2logout = preg_replace('|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex);
1222
        phpCAS::log("Ticket to logout: " . $ticket2logout);
1223
        $session_id = preg_replace('/[^\w]/', '', $ticket2logout);
1224
        phpCAS::log("Session id: " . $session_id);
1225
1226
        // destroy a possible application session created before phpcas
1227
        if (session_id()) {
1228
            session_unset();
1229
            session_destroy();
1230
        }
1231
        // fix session ID
1232
        session_id($session_id);
1233
        $_COOKIE[session_name()] = $session_id;
1234
        $_GET[session_name()] = $session_id;
1235
1236
        // Overwrite session
1237
        session_start();
1238
        session_unset();
1239
        session_destroy();
1240
        printf("Disconnected!");
1241
        phpCAS::traceExit();
1242
        exit();
1243
    }
1244
1245
    /** @} */
1246
1247
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1248
    // XX                                                                    XX
1249
    // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
1250
    // XX                                                                    XX
1251
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1252
1253
    // ########################################################################
1254
    //  ST
1255
    // ########################################################################
1256
    /**
1257
     * @addtogroup internalBasic
1258
     * @{
1259
     */
1260
1261
    /**
1262
     * the Service Ticket provided in the URL of the request if present
1263
     * (empty otherwise). Written by CASClient::CASClient(), read by
1264
     * CASClient::getST() and CASClient::hasPGT().
1265
     *
1266
     * @hideinitializer
1267
     * @private
1268
     */
1269
    var $_st = '';
1270
1271
    /**
1272
     * This method returns the Service Ticket provided in the URL of the request.
1273
     * @return The service ticket.
1274
     * @private
1275
     */
1276
    function getST()
1277
    {
1278
        return $this->_st;
1279
    }
1280
1281
    /**
1282
     * This method stores the Service Ticket.
1283
     * @param $st The Service Ticket.
1284
     * @private
1285
     */
1286
    function setST($st)
1287
    {
1288
        $this->_st = $st;
1289
    }
1290
1291
    /**
1292
     * This method tells if a Service Ticket was stored.
1293
     * @return TRUE if a Service Ticket has been stored.
1294
     * @private
1295
     */
1296
    function hasST()
1297
    {
1298
        return !empty($this->_st);
1299
    }
1300
1301
    /** @} */
1302
1303
    // ########################################################################
1304
    //  ST VALIDATION
1305
    // ########################################################################
1306
    /**
1307
     * @addtogroup internalBasic
1308
     * @{
1309
     */
1310
1311
    /**
1312
     * the certificate of the CAS server.
1313
     *
1314
     * @hideinitializer
1315
     * @private
1316
     */
1317
    var $_cas_server_cert = '';
1318
1319
    /**
1320
     * the certificate of the CAS server CA.
1321
     *
1322
     * @hideinitializer
1323
     * @private
1324
     */
1325
    var $_cas_server_ca_cert = '';
1326
1327
    /**
1328
     * Set to true not to validate the CAS server.
1329
     *
1330
     * @hideinitializer
1331
     * @private
1332
     */
1333
    var $_no_cas_server_validation = false;
1334
1335
    /**
1336
     * Set the certificate of the CAS server.
1337
     *
1338
     * @param $cert the PEM certificate
1339
     */
1340
    function setCasServerCert($cert)
1341
    {
1342
        $this->_cas_server_cert = $cert;
1343
    }
1344
1345
    /**
1346
     * Set the CA certificate of the CAS server.
1347
     *
1348
     * @param $cert the PEM certificate of the CA that emited the cert of the server
1349
     */
1350
    function setCasServerCACert($cert)
1351
    {
1352
        $this->_cas_server_ca_cert = $cert;
1353
    }
1354
1355
    /**
1356
     * Set no SSL validation for the CAS server.
1357
     */
1358
    function setNoCasServerValidation()
1359
    {
1360
        $this->_no_cas_server_validation = true;
1361
    }
1362
1363
    /**
1364
     * This method is used to validate a ST; halt on failure, and sets $validate_url,
1365
     * $text_reponse and $tree_response on success. These parameters are used later
1366
     * by CASClient::validatePGT() for CAS proxies.
1367
     * Used for all CAS 1.0 validations
1368
     * @param $validate_url the URL of the request to the CAS server.
1369
     * @param $text_response the response of the CAS server, as is (XML text).
1370
     * @param $tree_response the response of the CAS server, as a DOM XML tree.
1371
     *
1372
     * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1373
     *
1374
     * @private
1375
     */
1376
    function validateST($validate_url, &$text_response, &$tree_response)
1377
    {
1378
        phpCAS::traceBegin();
1379
        // build the URL to validate the ticket
1380
        $validate_url = $this->getServerServiceValidateURL() . '&ticket=' . $this->getST();
1381
        if ($this->isProxy()) {
1382
            // pass the callback url for CAS proxies
1383
            $validate_url .= '&pgtUrl=' . $this->getCallbackURL();
1384
        }
1385
1386
        // open and read the URL
1387 View Code Duplication
        if (!$this->readURL($validate_url, ''/*cookies*/, $headers, $text_response, $err_msg)) {
1388
            phpCAS::trace('could not open URL \'' . $validate_url . '\' to validate (' . $err_msg . ')');
1389
            $this->authError('ST not validated',
1390
                $validate_url,
1391
                true/*$no_response*/);
1392
        }
1393
1394
        // analyze the result depending on the version
1395
        switch ($this->getServerVersion()) {
1396
            case CAS_VERSION_1_0:
1397
                if (preg_match('/^no\n/', $text_response)) {
1398
                    phpCAS::trace('ST has not been validated');
1399
                    $this->authError('ST not validated',
1400
                        $validate_url,
1401
                        false/*$no_response*/,
1402
                        false/*$bad_response*/,
1403
                        $text_response);
1404
                }
1405
                if (!preg_match('/^yes\n/', $text_response)) {
1406
                    phpCAS::trace('ill-formed response');
1407
                    $this->authError('ST not validated',
1408
                        $validate_url,
1409
                        false/*$no_response*/,
1410
                        true/*$bad_response*/,
1411
                        $text_response);
1412
                }
1413
                // ST has been validated, extract the user name
1414
                $arr = preg_split('/\n/', $text_response);
1415
                $this->setUser(trim($arr[1]));
1416
                break;
1417
            case CAS_VERSION_2_0:
1418
                // read the response of the CAS server into a DOM object
1419 View Code Duplication
                if (!($dom = domxml_open_mem($text_response))) {
1420
                    phpCAS::trace('domxml_open_mem() failed');
1421
                    $this->authError('ST not validated',
1422
                        $validate_url,
1423
                        false/*$no_response*/,
1424
                        true/*$bad_response*/,
1425
                        $text_response);
1426
                }
1427
                // read the root node of the XML tree
1428 View Code Duplication
                if (!($tree_response = $dom->document_element())) {
1429
                    phpCAS::trace('document_element() failed');
1430
                    $this->authError('ST not validated',
1431
                        $validate_url,
1432
                        false/*$no_response*/,
1433
                        true/*$bad_response*/,
1434
                        $text_response);
1435
                }
1436
                // insure that tag name is 'serviceResponse'
1437 View Code Duplication
                if ($tree_response->node_name() != 'serviceResponse') {
1438
                    phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `' . $tree_response->node_name() . '\'');
1439
                    $this->authError('ST not validated',
1440
                        $validate_url,
1441
                        false/*$no_response*/,
1442
                        true/*$bad_response*/,
1443
                        $text_response);
1444
                }
1445
                if (sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
1446
                    // authentication succeded, extract the user name
1447
                    if (sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) {
1448
                        phpCAS::trace('<authenticationSuccess> found, but no <user>');
1449
                        $this->authError('ST not validated',
1450
                            $validate_url,
1451
                            false/*$no_response*/,
1452
                            true/*$bad_response*/,
1453
                            $text_response);
1454
                    }
1455
                    $user = trim($user_elements[0]->get_content());
1456
                    phpCAS::trace('user = `' . $user);
1457
                    $this->setUser($user);
1458
1459
                } else {
1460
                    if (sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
1461
                        phpCAS::trace('<authenticationFailure> found');
1462
                        // authentication failed, extract the error code and message
1463
                        $this->authError('ST not validated',
1464
                            $validate_url,
1465
                            false/*$no_response*/,
1466
                            false/*$bad_response*/,
1467
                            $text_response,
1468
                            $failure_elements[0]->get_attribute('code')/*$err_code*/,
1469
                            trim($failure_elements[0]->get_content())/*$err_msg*/);
1470
                    } else {
1471
                        phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
1472
                        $this->authError('ST not validated',
1473
                            $validate_url,
1474
                            false/*$no_response*/,
1475
                            true/*$bad_response*/,
1476
                            $text_response);
1477
                    }
1478
                }
1479
                break;
1480
        }
1481
1482
        // at this step, ST has been validated and $this->_user has been set,
1483
        phpCAS::traceEnd(true);
1484
        return true;
1485
    }
1486
1487
    // ########################################################################
1488
    //  SAML VALIDATION
1489
    // ########################################################################
1490
    /**
1491
     * @addtogroup internalBasic
1492
     * @{
1493
     */
1494
1495
    /**
1496
     * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
1497
     * $text_reponse and $tree_response on success. These parameters are used later
1498
     * by CASClient::validatePGT() for CAS proxies.
1499
     *
1500
     * @param $validate_url the URL of the request to the CAS server.
1501
     * @param $text_response the response of the CAS server, as is (XML text).
1502
     * @param $tree_response the response of the CAS server, as a DOM XML tree.
1503
     *
1504
     * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1505
     *
1506
     * @private
1507
     */
1508
    function validateSA($validate_url, &$text_response, &$tree_response)
1509
    {
1510
        phpCAS::traceBegin();
1511
1512
        // build the URL to validate the ticket
1513
        $validate_url = $this->getServerSamlValidateURL();
1514
1515
        // open and read the URL
1516 View Code Duplication
        if (!$this->readURL($validate_url, ''/*cookies*/, $headers, $text_response, $err_msg)) {
1517
            phpCAS::trace('could not open URL \'' . $validate_url . '\' to validate (' . $err_msg . ')');
1518
            $this->authError('SA not validated', $validate_url, true/*$no_response*/);
1519
        }
1520
1521
        phpCAS::trace('server version: ' . $this->getServerVersion());
1522
1523
        // analyze the result depending on the version
1524
        switch ($this->getServerVersion()) {
1525
            case SAML_VERSION_1_1:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1526
1527
                // read the response of the CAS server into a DOM object
1528 View Code Duplication
                if (!($dom = domxml_open_mem($text_response))) {
1529
                    phpCAS::trace('domxml_open_mem() failed');
1530
                    $this->authError('SA not validated',
1531
                        $validate_url,
1532
                        false/*$no_response*/,
1533
                        true/*$bad_response*/,
1534
                        $text_response);
1535
                }
1536
                // read the root node of the XML tree
1537 View Code Duplication
                if (!($tree_response = $dom->document_element())) {
1538
                    phpCAS::trace('document_element() failed');
1539
                    $this->authError('SA not validated',
1540
                        $validate_url,
1541
                        false/*$no_response*/,
1542
                        true/*$bad_response*/,
1543
                        $text_response);
1544
                }
1545
                // insure that tag name is 'Envelope'
1546 View Code Duplication
                if ($tree_response->node_name() != 'Envelope') {
1547
                    phpCAS::trace('bad XML root node (should be `Envelope\' instead of `' . $tree_response->node_name() . '\'');
1548
                    $this->authError('SA not validated',
1549
                        $validate_url,
1550
                        false/*$no_response*/,
1551
                        true/*$bad_response*/,
1552
                        $text_response);
1553
                }
1554
                // check for the NameIdentifier tag in the SAML response
1555
                if (sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
1556
                    phpCAS::trace('NameIdentifier found');
1557
                    $user = trim($success_elements[0]->get_content());
1558
                    phpCAS::trace('user = `' . $user . '`');
1559
                    $this->setUser($user);
1560
                    $this->setSessionAttributes($text_response);
1561
                } else {
1562
                    phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
1563
                    $this->authError('SA not validated',
1564
                        $validate_url,
1565
                        false/*$no_response*/,
1566
                        true/*$bad_response*/,
1567
                        $text_response);
1568
                }
1569
                break;
1570
        }
1571
1572
        // at this step, ST has been validated and $this->_user has been set,
1573
        phpCAS::traceEnd(true);
1574
        return true;
1575
    }
1576
1577
    /**
1578
     * This method will parse the DOM and pull out the attributes from the SAML
1579
     * payload and put them into an array, then put the array into the session.
1580
     *
1581
     * @param $text_response the SAML payload.
1582
     * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1583
     *
1584
     * @private
1585
     */
1586
    function setSessionAttributes($text_response)
1587
    {
1588
        phpCAS::traceBegin();
1589
1590
        $result = false;
1591
1592
        if (isset($_SESSION[SAML_ATTRIBUTES])) {
1593
            phpCAS::trace("session attrs already set.");  //testbml - do we care?
1594
        }
1595
1596
        $attr_array = array();
1597
1598
        if (($dom = domxml_open_mem($text_response))) {
1599
            $xPath = $dom->xpath_new_context();
1600
            $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
1601
            $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
1602
            $nodelist = $xPath->xpath_eval("//saml:Attribute");
1603
            $attrs = $nodelist->nodeset;
1604
            phpCAS::trace($text_response);
1605
            foreach ($attrs as $attr) {
1606
                $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
1607
                $name = $attr->get_attribute("AttributeName");
1608
                $value_array = array();
1609
                foreach ($xres->nodeset as $node) {
1610
                    $value_array[] = $node->get_content();
1611
1612
                }
1613
                phpCAS::trace("* " . $name . "=" . $value_array);
1614
                $attr_array[$name] = $value_array;
1615
            }
1616
            $_SESSION[SAML_ATTRIBUTES] = $attr_array;
1617
            // UGent addition...
1618
            foreach ($attr_array as $attr_key => $attr_value) {
1619
                if (count($attr_value) > 1) {
1620
                    $this->_attributes[$attr_key] = $attr_value;
1621
                } else {
1622
                    $this->_attributes[$attr_key] = $attr_value[0];
1623
                }
1624
            }
1625
            $result = true;
1626
        }
1627
        phpCAS::traceEnd($result);
1628
        return $result;
1629
    }
1630
1631
    /** @} */
1632
1633
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1634
    // XX                                                                    XX
1635
    // XX                     PROXY FEATURES (CAS 2.0)                       XX
1636
    // XX                                                                    XX
1637
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1638
1639
    // ########################################################################
1640
    //  PROXYING
1641
    // ########################################################################
1642
    /**
1643
     * @addtogroup internalProxy
1644
     * @{
1645
     */
1646
1647
    /**
1648
     * A boolean telling if the client is a CAS proxy or not. Written by CASClient::CASClient(),
1649
     * read by CASClient::isProxy().
1650
     *
1651
     * @private
1652
     */
1653
    var $_proxy;
1654
1655
    /**
1656
     * Tells if a CAS client is a CAS proxy or not
1657
     *
1658
     * @return TRUE when the CAS client is a CAs proxy, FALSE otherwise
1659
     *
1660
     * @private
1661
     */
1662
    function isProxy()
1663
    {
1664
        return $this->_proxy;
1665
    }
1666
1667
    /** @} */
1668
    // ########################################################################
1669
    //  PGT
1670
    // ########################################################################
1671
    /**
1672
     * @addtogroup internalProxy
1673
     * @{
1674
     */
1675
1676
    /**
1677
     * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
1678
     * Written by CASClient::setPGT(), read by CASClient::getPGT() and CASClient::hasPGT().
1679
     *
1680
     * @hideinitializer
1681
     * @private
1682
     */
1683
    var $_pgt = '';
1684
1685
    /**
1686
     * This method returns the Proxy Granting Ticket given by the CAS server.
1687
     * @return The Proxy Granting Ticket.
1688
     * @private
1689
     */
1690
    function getPGT()
1691
    {
1692
        return $this->_pgt;
1693
    }
1694
1695
    /**
1696
     * This method stores the Proxy Granting Ticket.
1697
     * @param $pgt The Proxy Granting Ticket.
1698
     * @private
1699
     */
1700
    function setPGT($pgt)
1701
    {
1702
        $this->_pgt = $pgt;
1703
    }
1704
1705
    /**
1706
     * This method tells if a Proxy Granting Ticket was stored.
1707
     * @return TRUE if a Proxy Granting Ticket has been stored.
1708
     * @private
1709
     */
1710
    function hasPGT()
1711
    {
1712
        return !empty($this->_pgt);
1713
    }
1714
1715
    /** @} */
1716
1717
    // ########################################################################
1718
    //  CALLBACK MODE
1719
    // ########################################################################
1720
    /**
1721
     * @addtogroup internalCallback
1722
     * @{
1723
     */
1724
    /**
1725
     * each PHP script using phpCAS in proxy mode is its own callback to get the
1726
     * PGT back from the CAS server. callback_mode is detected by the constructor
1727
     * thanks to the GET parameters.
1728
     */
1729
1730
    /**
1731
     * a boolean to know if the CAS client is running in callback mode. Written by
1732
     * CASClient::setCallBackMode(), read by CASClient::isCallbackMode().
1733
     *
1734
     * @hideinitializer
1735
     * @private
1736
     */
1737
    var $_callback_mode = false;
1738
1739
    /**
1740
     * This method sets/unsets callback mode.
1741
     *
1742
     * @param $callback_mode TRUE to set callback mode, FALSE otherwise.
1743
     *
1744
     * @private
1745
     */
1746
    function setCallbackMode($callback_mode)
1747
    {
1748
        $this->_callback_mode = $callback_mode;
1749
    }
1750
1751
    /**
1752
     * This method returns TRUE when the CAs client is running i callback mode,
1753
     * FALSE otherwise.
1754
     *
1755
     * @return A boolean.
1756
     *
1757
     * @private
1758
     */
1759
    function isCallbackMode()
1760
    {
1761
        return $this->_callback_mode;
1762
    }
1763
1764
    /**
1765
     * the URL that should be used for the PGT callback (in fact the URL of the
1766
     * current request without any CGI parameter). Written and read by
1767
     * CASClient::getCallbackURL().
1768
     *
1769
     * @hideinitializer
1770
     * @private
1771
     */
1772
    var $_callback_url = '';
1773
1774
    /**
1775
     * This method returns the URL that should be used for the PGT callback (in
1776
     * fact the URL of the current request without any CGI parameter, except if
1777
     * phpCAS::setFixedCallbackURL() was used).
1778
     *
1779
     * @return The callback URL
1780
     *
1781
     * @private
1782
     */
1783
    function getCallbackURL()
1784
    {
1785
        // the URL is built when needed only
1786
        if (empty($this->_callback_url)) {
1787
            $final_uri = '';
1788
            // remove the ticket if present in the URL
1789
            $final_uri = 'https://';
1790
            /* replaced by Julien Marchal - v0.4.6
1791
			 * $this->uri .= $_SERVER['SERVER_NAME'];
1792
			 */
1793
            if (empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
1794
                /* replaced by teedog - v0.4.12
1795
				 * $final_uri .= $_SERVER['SERVER_NAME'];
1796
				 */
1797 View Code Duplication
                if (empty($_SERVER['SERVER_NAME'])) {
1798
                    $final_uri .= $_SERVER['HTTP_HOST'];
1799
                } else {
1800
                    $final_uri .= $_SERVER['SERVER_NAME'];
1801
                }
1802
            } else {
1803
                $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1804
            }
1805 View Code Duplication
            if (($this->isHttps() && $_SERVER['SERVER_PORT'] != 443)
1806
                || (!$this->isHttps() && $_SERVER['SERVER_PORT'] != 80)
1807
            ) {
1808
                $final_uri .= ':';
1809
                $final_uri .= $_SERVER['SERVER_PORT'];
1810
            }
1811
            $request_uri = $_SERVER['REQUEST_URI'];
1812
            $request_uri = preg_replace('/\?.*$/', '', $request_uri);
1813
            $final_uri .= $request_uri;
1814
            $this->setCallbackURL($final_uri);
1815
        }
1816
        return $this->_callback_url;
1817
    }
1818
1819
    /**
1820
     * This method sets the callback url.
1821
     *
1822
     * @param $callback_url url to set callback
1823
     *
1824
     * @private
1825
     */
1826
    function setCallbackURL($url)
1827
    {
1828
        return $this->_callback_url = $url;
1829
    }
1830
1831
    /**
1832
     * This method is called by CASClient::CASClient() when running in callback
1833
     * mode. It stores the PGT and its PGT Iou, prints its output and halts.
1834
     *
1835
     * @private
1836
     */
1837
    function callback()
1838
    {
1839
        phpCAS::traceBegin();
1840
        $this->printHTMLHeader('phpCAS callback');
1841
        $pgt_iou = $_GET['pgtIou'];
1842
        $pgt = $_GET['pgtId'];
1843
        phpCAS::trace('Storing PGT `' . $pgt . '\' (id=`' . $pgt_iou . '\')');
1844
        echo '<p>Storing PGT `' . $pgt . '\' (id=`' . $pgt_iou . '\').</p>';
1845
        $this->storePGT($pgt, $pgt_iou);
1846
        $this->printHTMLFooter();
1847
        phpCAS::traceExit();
1848
        exit();
1849
    }
1850
1851
    /** @} */
1852
1853
    // ########################################################################
1854
    //  PGT STORAGE
1855
    // ########################################################################
1856
    /**
1857
     * @addtogroup internalPGTStorage
1858
     * @{
1859
     */
1860
1861
    /**
1862
     * an instance of a class inheriting of PGTStorage, used to deal with PGT
1863
     * storage. Created by CASClient::setPGTStorageFile() or CASClient::setPGTStorageDB(), used
1864
     * by CASClient::setPGTStorageFile(), CASClient::setPGTStorageDB() and CASClient::initPGTStorage().
1865
     *
1866
     * @hideinitializer
1867
     * @private
1868
     */
1869
    var $_pgt_storage = null;
1870
1871
    /**
1872
     * This method is used to initialize the storage of PGT's.
1873
     * Halts on error.
1874
     *
1875
     * @private
1876
     */
1877
    function initPGTStorage()
1878
    {
1879
        // if no SetPGTStorageXxx() has been used, default to file
1880
        if (!is_object($this->_pgt_storage)) {
1881
            $this->setPGTStorageFile();
1882
        }
1883
1884
        // initializes the storage
1885
        $this->_pgt_storage->init();
1886
    }
1887
1888
    /**
1889
     * This method stores a PGT. Halts on error.
1890
     *
1891
     * @param $pgt the PGT to store
1892
     * @param $pgt_iou its corresponding Iou
1893
     *
1894
     * @private
1895
     */
1896
    function storePGT($pgt, $pgt_iou)
1897
    {
1898
        // ensure that storage is initialized
1899
        $this->initPGTStorage();
1900
        // writes the PGT
1901
        $this->_pgt_storage->write($pgt, $pgt_iou);
1902
    }
1903
1904
    /**
1905
     * This method reads a PGT from its Iou and deletes the corresponding storage entry.
1906
     *
1907
     * @param $pgt_iou the PGT Iou
1908
     *
1909
     * @return The PGT corresponding to the Iou, FALSE when not found.
1910
     *
1911
     * @private
1912
     */
1913
    function loadPGT($pgt_iou)
1914
    {
1915
        // ensure that storage is initialized
1916
        $this->initPGTStorage();
1917
        // read the PGT
1918
        return $this->_pgt_storage->read($pgt_iou);
1919
    }
1920
1921
    /**
1922
     * This method is used to tell phpCAS to store the response of the
1923
     * CAS server to PGT requests onto the filesystem.
1924
     *
1925
     * @param $format the format used to store the PGT's (`plain' and `xml' allowed)
1926
     * @param $path the path where the PGT's should be stored
1927
     *
1928
     * @public
1929
     */
1930
    function setPGTStorageFile(
1931
        $format = '',
1932
        $path = ''
1933
    ) {
1934
        // check that the storage has not already been set
1935
        if (is_object($this->_pgt_storage)) {
1936
            phpCAS::error('PGT storage already defined');
1937
        }
1938
1939
        // create the storage object
1940
        $this->_pgt_storage = new PGTStorageFile($this, $format, $path);
1941
    }
1942
1943
    /**
1944
     * This method is used to tell phpCAS to store the response of the
1945
     * CAS server to PGT requests into a database.
1946
     * @note The connection to the database is done only when needed.
1947
     * As a consequence, bad parameters are detected only when
1948
     * initializing PGT storage.
1949
     *
1950
     * @param $user the user to access the data with
1951
     * @param $password the user's password
1952
     * @param $database_type the type of the database hosting the data
1953
     * @param $hostname the server hosting the database
1954
     * @param $port the port the server is listening on
1955
     * @param $database the name of the database
1956
     * @param $table the name of the table storing the data
1957
     *
1958
     * @public
1959
     */
1960
    function setPGTStorageDB(
1961
        $user,
1962
        $password,
1963
        $database_type,
1964
        $hostname,
1965
        $port,
1966
        $database,
1967
        $table
1968
    ) {
1969
        // check that the storage has not already been set
1970
        if (is_object($this->_pgt_storage)) {
1971
            phpCAS::error('PGT storage already defined');
1972
        }
1973
1974
        // warn the user that he should use file storage...
1975
        trigger_error('PGT storage into database is an experimental feature, use at your own risk', E_USER_WARNING);
1976
1977
        // create the storage object
1978
        $this->_pgt_storage = new PGTStorageDB($this, $user, $password, $database_type, $hostname, $port, $database,
1979
            $table);
1980
    }
1981
1982
    // ########################################################################
1983
    //  PGT VALIDATION
1984
    // ########################################################################
1985
    /**
1986
     * This method is used to validate a PGT; halt on failure.
1987
     *
1988
     * @param $validate_url the URL of the request to the CAS server.
1989
     * @param $text_response the response of the CAS server, as is (XML text); result
1990
     * of CASClient::validateST() or CASClient::validatePT().
1991
     * @param $tree_response the response of the CAS server, as a DOM XML tree; result
1992
     * of CASClient::validateST() or CASClient::validatePT().
1993
     *
1994
     * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1995
     *
1996
     * @private
1997
     */
1998
    function validatePGT(&$validate_url, $text_response, $tree_response)
1999
    {
2000
        // here cannot use phpCAS::traceBegin(); alongside domxml-php4-to-php5.php
2001
        phpCAS::log('start validatePGT()');
2002
        if (sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
2003
            phpCAS::trace('<proxyGrantingTicket> not found');
2004
            // authentication succeded, but no PGT Iou was transmitted
2005
            $this->authError('Ticket validated but no PGT Iou transmitted',
2006
                $validate_url,
2007
                false/*$no_response*/,
2008
                false/*$bad_response*/,
2009
                $text_response);
2010
        } else {
2011
            // PGT Iou transmitted, extract it
2012
            $pgt_iou = trim($arr[0]->get_content());
2013
            $pgt = $this->loadPGT($pgt_iou);
2014 View Code Duplication
            if ($pgt == false) {
2015
                phpCAS::trace('could not load PGT');
2016
                $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
2017
                    $validate_url,
2018
                    false/*$no_response*/,
2019
                    false/*$bad_response*/,
2020
                    $text_response);
2021
            }
2022
            $this->setPGT($pgt);
2023
        }
2024
        // here, cannot use	phpCAS::traceEnd(TRUE); alongside domxml-php4-to-php5.php
2025
        phpCAS::log('end validatePGT()');
2026
        return true;
2027
    }
2028
2029
    // ########################################################################
2030
    //  PGT VALIDATION
2031
    // ########################################################################
2032
2033
    /**
2034
     * This method is used to retrieve PT's from the CAS server thanks to a PGT.
2035
     *
2036
     * @param $target_service the service to ask for with the PT.
2037
     * @param $err_code an error code (PHPCAS_SERVICE_OK on success).
2038
     * @param $err_msg an error message (empty on success).
2039
     *
2040
     * @return a Proxy Ticket, or FALSE on error.
2041
     *
2042
     * @private
2043
     */
2044
    function retrievePT($target_service, &$err_code, &$err_msg)
2045
    {
2046
        phpCAS::traceBegin();
2047
2048
        // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
2049
        // set to false and $err_msg to an error message. At the end, if $pt is FALSE
2050
        // and $error_msg is still empty, it is set to 'invalid response' (the most
2051
        // commonly encountered error).
2052
        $err_msg = '';
2053
2054
        // build the URL to retrieve the PT
2055
        //      $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT();
2056
        $cas_url = $this->getServerProxyURL() . '?targetService=' . urlencode($target_service) . '&pgt=' . $this->getPGT();
2057
2058
        // open and read the URL
2059
        if (!$this->readURL($cas_url, ''/*cookies*/, $headers, $cas_response, $err_msg)) {
2060
            phpCAS::trace('could not open URL \'' . $cas_url . '\' to validate (' . $err_msg . ')');
2061
            $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2062
            $err_msg = 'could not retrieve PT (no response from the CAS server)';
2063
            phpCAS::traceEnd(false);
2064
            return false;
2065
        }
2066
2067
        $bad_response = false;
2068
2069
        if (!$bad_response) {
2070
            // read the response of the CAS server into a DOM object
2071
            if (!($dom = @domxml_open_mem($cas_response))) {
2072
                phpCAS::trace('domxml_open_mem() failed');
2073
                // read failed
2074
                $bad_response = true;
2075
            }
2076
        }
2077
2078
        if (!$bad_response) {
2079
            // read the root node of the XML tree
2080
            if (!($root = $dom->document_element())) {
2081
                phpCAS::trace('document_element() failed');
2082
                // read failed
2083
                $bad_response = true;
2084
            }
2085
        }
2086
2087
        if (!$bad_response) {
2088
            // insure that tag name is 'serviceResponse'
2089
            if ($root->node_name() != 'serviceResponse') {
2090
                phpCAS::trace('node_name() failed');
2091
                // bad root node
2092
                $bad_response = true;
2093
            }
2094
        }
2095
2096
        if (!$bad_response) {
2097
            // look for a proxySuccess tag
2098
            if (sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) {
2099
                // authentication succeded, look for a proxyTicket tag
2100
                if (sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) {
2101
                    $err_code = PHPCAS_SERVICE_OK;
2102
                    $err_msg = '';
2103
                    phpCAS::trace('original PT: ' . trim($arr[0]->get_content()));
2104
                    $pt = trim($arr[0]->get_content());
2105
                    phpCAS::traceEnd($pt);
2106
                    return $pt;
2107
                } else {
2108
                    phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
2109
                }
2110
            } // look for a proxyFailure tag
2111
            else {
2112
                if (sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) {
2113
                    // authentication failed, extract the error
2114
                    $err_code = PHPCAS_SERVICE_PT_FAILURE;
2115
                    $err_msg = 'PT retrieving failed (code=`'
2116
                        . $arr[0]->get_attribute('code')
2117
                        . '\', message=`'
2118
                        . trim($arr[0]->get_content())
2119
                        . '\')';
2120
                    phpCAS::traceEnd(false);
2121
                    return false;
2122
                } else {
2123
                    phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
2124
                }
2125
            }
2126
        }
2127
2128
        // at this step, we are sure that the response of the CAS server was ill-formed
2129
        $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
2130
        $err_msg = 'Invalid response from the CAS server (response=`' . $cas_response . '\')';
2131
2132
        phpCAS::traceEnd(false);
2133
        return false;
2134
    }
2135
2136
    // ########################################################################
2137
    // ACCESS TO EXTERNAL SERVICES
2138
    // ########################################################################
2139
2140
    /**
2141
     * This method is used to acces a remote URL.
2142
     *
2143
     * @param $url the URL to access.
2144
     * @param $cookies an array containing cookies strings such as 'name=val'
2145
     * @param $headers an array containing the HTTP header lines of the response
2146
     * (an empty array on failure).
2147
     * @param $body the body of the response, as a string (empty on failure).
2148
     * @param $err_msg an error message, filled on failure.
2149
     *
2150
     * @return TRUE on success, FALSE otherwise (in this later case, $err_msg
2151
     * contains an error message).
2152
     *
2153
     * @private
2154
     */
2155
    function readURL($url, $cookies, &$headers, &$body, &$err_msg)
2156
    {
2157
        phpCAS::traceBegin();
2158
        $headers = '';
2159
        $body = '';
2160
        $err_msg = '';
2161
2162
        $res = true;
2163
2164
        // initialize the CURL session
2165
        $ch = curl_init($url);
2166
2167
        if (version_compare(PHP_VERSION, '5.1.3', '>=')) {
2168
            //only avaible in php5
2169
            curl_setopt_array($ch, $this->_curl_options);
2170
        } else {
2171
            foreach ($this->_curl_options as $key => $value) {
2172
                curl_setopt($ch, $key, $value);
2173
            }
2174
        }
2175
2176
        if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) {
2177
            phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
2178
        }
2179
        if ($this->_cas_server_cert != '' && $this->_cas_server_ca_cert != '') {
2180
            // This branch added by IDMS. Seems phpCAS implementor got a bit confused about the curl options CURLOPT_SSLCERT and CURLOPT_CAINFO
2181
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2182
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
2183
            curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
2184
            curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
2185
            curl_setopt($ch, CURLOPT_VERBOSE, '1');
2186
            phpCAS::trace('CURL: Set all required opts for mutual authentication ------');
2187
        } else {
2188
            if ($this->_cas_server_cert != '') {
2189
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2190
                curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
2191
            } else {
2192
                if ($this->_cas_server_ca_cert != '') {
2193
                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2194
                    curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
2195
                } else {
2196
                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
2197
                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
2198
                }
2199
            }
2200
        }
2201
2202
        // return the CURL output into a variable
2203
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
2204
        // get the HTTP header with a callback
2205
        $this->_curl_headers = array(); // empty the headers array
2206
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curl_read_headers'));
2207
        // add cookies headers
2208
        if (is_array($cookies)) {
2209
            curl_setopt($ch, CURLOPT_COOKIE, implode(';', $cookies));
2210
        }
2211
        // add extra stuff if SAML
2212
        if ($this->hasSA()) {
2213
            $more_headers = array(
2214
                "soapaction: http://www.oasis-open.org/committees/security",
2215
                "cache-control: no-cache",
2216
                "pragma: no-cache",
2217
                "accept: text/xml",
2218
                "connection: keep-alive",
2219
                "content-type: text/xml"
2220
            );
2221
2222
            curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
2223
            curl_setopt($ch, CURLOPT_POST, 1);
2224
            $data = $this->buildSAMLPayload();
2225
            //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
2226
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
2227
        }
2228
        // perform the query
2229
        $buf = curl_exec($ch);
2230
        //phpCAS::trace('CURL: Call completed. Response body is: \''.$buf.'\'');
2231
        if ($buf === false) {
2232
            phpCAS::trace('curl_exec() failed');
2233
            $err_msg = 'CURL error #' . curl_errno($ch) . ': ' . curl_error($ch);
2234
            //phpCAS::trace('curl error: '.$err_msg);
2235
            // close the CURL session
2236
            curl_close($ch);
2237
            $res = false;
2238
        } else {
2239
            // close the CURL session
2240
            curl_close($ch);
2241
2242
            $headers = $this->_curl_headers;
2243
            $body = $buf;
2244
        }
2245
2246
        phpCAS::traceEnd($res);
2247
        return $res;
2248
    }
2249
2250
    /**
2251
     * This method is used to build the SAML POST body sent to /samlValidate URL.
2252
     *
2253
     * @return the SOAP-encased SAMLP artifact (the ticket).
2254
     *
2255
     * @private
2256
     */
2257
    function buildSAMLPayload()
2258
    {
2259
        phpCAS::traceBegin();
2260
2261
        //get the ticket
2262
        $sa = $this->getSA();
2263
        //phpCAS::trace("SA: ".$sa);
2264
2265
        $body = SAML_SOAP_ENV . SAML_SOAP_BODY . SAMLP_REQUEST . SAML_ASSERTION_ARTIFACT . $sa . SAML_ASSERTION_ARTIFACT_CLOSE . SAMLP_REQUEST_CLOSE . SAML_SOAP_BODY_CLOSE . SAML_SOAP_ENV_CLOSE;
2266
2267
        phpCAS::traceEnd($body);
2268
        return ($body);
2269
    }
2270
2271
    /**
2272
     * This method is the callback used by readURL method to request HTTP headers.
2273
     */
2274
    var $_curl_headers = array();
2275
2276
    function _curl_read_headers($ch, $header)
2277
    {
2278
        $this->_curl_headers[] = $header;
2279
        return strlen($header);
2280
    }
2281
2282
    /**
2283
     * This method is used to access an HTTP[S] service.
2284
     *
2285
     * @param $url the service to access.
2286
     * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
2287
     * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
2288
     * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE.
2289
     * @param $output the output of the service (also used to give an error
2290
     * message on failure).
2291
     *
2292
     * @return TRUE on success, FALSE otherwise (in this later case, $err_code
2293
     * gives the reason why it failed and $output contains an error message).
2294
     *
2295
     * @public
2296
     */
2297
    function serviceWeb($url, &$err_code, &$output)
2298
    {
2299
        phpCAS::traceBegin();
2300
        // at first retrieve a PT
2301
        $pt = $this->retrievePT($url, $err_code, $output);
2302
2303
        $res = true;
2304
2305
        // test if PT was retrieved correctly
2306
        if (!$pt) {
2307
            // note: $err_code and $err_msg are filled by CASClient::retrievePT()
2308
            phpCAS::trace('PT was not retrieved correctly');
2309
            $res = false;
2310
        } else {
2311
            // add cookies if necessary
2312
            if (is_array($_SESSION['phpCAS']['services'][$url]['cookies'])) {
2313
                foreach ($_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val) {
2314
                    $cookies[] = $name . '=' . $val;
2315
                }
2316
            }
2317
2318
            // build the URL including the PT
2319
            if (strstr($url, '?') === false) {
2320
                $service_url = $url . '?ticket=' . $pt;
2321
            } else {
2322
                $service_url = $url . '&ticket=' . $pt;
2323
            }
2324
2325
            phpCAS::trace('reading URL`' . $service_url . '\'');
2326
            if (!$this->readURL($service_url, $cookies, $headers, $output, $err_msg)) {
2327
                phpCAS::trace('could not read URL`' . $service_url . '\'');
2328
                $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2329
                // give an error message
2330
                $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2331
                    $service_url,
2332
                    $err_msg);
2333
                $res = false;
2334
            } else {
2335
                // URL has been fetched, extract the cookies
2336
                phpCAS::trace('URL`' . $service_url . '\' has been read, storing cookies:');
2337
                foreach ($headers as $header) {
2338
                    // test if the header is a cookie
2339
                    if (preg_match('/^Set-Cookie:/', $header)) {
2340
                        // the header is a cookie, remove the beginning
2341
                        $header_val = preg_replace('/^Set-Cookie: */', '', $header);
2342
                        // extract interesting information
2343
                        $name_val = strtok($header_val, '; ');
2344
                        // extract the name and the value of the cookie
2345
                        $cookie_name = strtok($name_val, '=');
2346
                        $cookie_val = strtok('=');
2347
                        // store the cookie
2348
                        $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val;
2349
                        phpCAS::trace($cookie_name . ' -> ' . $cookie_val);
2350
                    }
2351
                }
2352
            }
2353
        }
2354
2355
        phpCAS::traceEnd($res);
2356
        return $res;
2357
    }
2358
2359
    /**
2360
     * This method is used to access an IMAP/POP3/NNTP service.
2361
     *
2362
     * @param $url a string giving the URL of the service, including the mailing box
2363
     * for IMAP URLs, as accepted by imap_open().
2364
     * @param $service a string giving for CAS retrieve Proxy ticket
2365
     * @param $flags options given to imap_open().
2366
     * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
2367
     * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
2368
     * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE.
2369
     * @param $err_msg an error message on failure
2370
     * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL
2371
     * on success, FALSE on error).
2372
     *
2373
     * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code
2374
     * gives the reason why it failed and $err_msg contains an error message).
2375
     *
2376
     * @public
2377
     */
2378
    function serviceMail($url, $service, $flags, &$err_code, &$err_msg, &$pt)
2379
    {
2380
        phpCAS::traceBegin();
2381
        // at first retrieve a PT
2382
        $pt = $this->retrievePT($service, $err_code, $output);
2383
2384
        $stream = false;
2385
2386
        // test if PT was retrieved correctly
2387
        if (!$pt) {
2388
            // note: $err_code and $err_msg are filled by CASClient::retrievePT()
2389
            phpCAS::trace('PT was not retrieved correctly');
2390
        } else {
2391
            phpCAS::trace('opening IMAP URL `' . $url . '\'...');
2392
            $stream = @imap_open($url, $this->getUser(), $pt, $flags);
2393
            if (!$stream) {
2394
                phpCAS::trace('could not open URL');
2395
                $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2396
                // give an error message
2397
                $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2398
                    $service_url,
0 ignored issues
show
Bug introduced by
The variable $service_url does not exist. Did you mean $service?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
2399
                    var_export(imap_errors(), true));
2400
                $pt = false;
2401
                $stream = false;
2402
            } else {
2403
                phpCAS::trace('ok');
2404
            }
2405
        }
2406
2407
        phpCAS::traceEnd($stream);
2408
        return $stream;
2409
    }
2410
2411
    /** @} */
2412
2413
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2414
    // XX                                                                    XX
2415
    // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
2416
    // XX                                                                    XX
2417
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2418
2419
    // ########################################################################
2420
    //  PT
2421
    // ########################################################################
2422
    /**
2423
     * @addtogroup internalProxied
2424
     * @{
2425
     */
2426
2427
    /**
2428
     * the Proxy Ticket provided in the URL of the request if present
2429
     * (empty otherwise). Written by CASClient::CASClient(), read by
2430
     * CASClient::getPT() and CASClient::hasPGT().
2431
     *
2432
     * @hideinitializer
2433
     * @private
2434
     */
2435
    var $_pt = '';
2436
2437
    /**
2438
     * This method returns the Proxy Ticket provided in the URL of the request.
2439
     * @return The proxy ticket.
2440
     * @private
2441
     */
2442
    function getPT()
2443
    {
2444
        //      return 'ST'.substr($this->_pt, 2);
2445
        return $this->_pt;
2446
    }
2447
2448
    /**
2449
     * This method stores the Proxy Ticket.
2450
     * @param $pt The Proxy Ticket.
2451
     * @private
2452
     */
2453
    function setPT($pt)
2454
    {
2455
        $this->_pt = $pt;
2456
    }
2457
2458
    /**
2459
     * This method tells if a Proxy Ticket was stored.
2460
     * @return TRUE if a Proxy Ticket has been stored.
2461
     * @private
2462
     */
2463
    function hasPT()
2464
    {
2465
        return !empty($this->_pt);
2466
    }
2467
2468
    /**
2469
     * This method returns the SAML Ticket provided in the URL of the request.
2470
     * @return The SAML ticket.
2471
     * @private
2472
     */
2473
    function getSA()
2474
    {
2475
        return 'ST' . substr($this->_sa, 2);
2476
    }
2477
2478
    /**
2479
     * This method stores the SAML Ticket.
2480
     * @param $sa The SAML Ticket.
2481
     * @private
2482
     */
2483
    function setSA($sa)
2484
    {
2485
        $this->_sa = $sa;
2486
    }
2487
2488
    /**
2489
     * This method tells if a SAML Ticket was stored.
2490
     * @return TRUE if a SAML Ticket has been stored.
2491
     * @private
2492
     */
2493
    function hasSA()
2494
    {
2495
        return !empty($this->_sa);
2496
    }
2497
2498
    /** @} */
2499
    // ########################################################################
2500
    //  PT VALIDATION
2501
    // ########################################################################
2502
    /**
2503
     * @addtogroup internalProxied
2504
     * @{
2505
     */
2506
2507
    /**
2508
     * This method is used to validate a ST or PT; halt on failure
2509
     * Used for all CAS 2.0 validations
2510
     * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
2511
     *
2512
     * @private
2513
     */
2514
    function validatePT(&$validate_url, &$text_response, &$tree_response)
2515
    {
2516
        phpCAS::traceBegin();
2517
        // build the URL to validate the ticket
2518
        $validate_url = $this->getServerProxyValidateURL() . '&ticket=' . $this->getPT();
2519
2520
        if ($this->isProxy()) {
2521
            // pass the callback url for CAS proxies
2522
            $validate_url .= '&pgtUrl=' . $this->getCallbackURL();
2523
        }
2524
2525
        // open and read the URL
2526 View Code Duplication
        if (!$this->readURL($validate_url, ''/*cookies*/, $headers, $text_response, $err_msg)) {
2527
            phpCAS::trace('could not open URL \'' . $validate_url . '\' to validate (' . $err_msg . ')');
2528
            $this->authError('PT not validated',
2529
                $validate_url,
2530
                true/*$no_response*/);
2531
        }
2532
2533
        // read the response of the CAS server into a DOM object
2534 View Code Duplication
        if (!($dom = domxml_open_mem($text_response))) {
2535
            // read failed
2536
            $this->authError('PT not validated',
2537
                $validate_url,
2538
                false/*$no_response*/,
2539
                true/*$bad_response*/,
2540
                $text_response);
2541
        }
2542
        // read the root node of the XML tree
2543
        if (!($tree_response = $dom->document_element())) {
2544
            // read failed
2545
            $this->authError('PT not validated',
2546
                $validate_url,
2547
                false/*$no_response*/,
2548
                true/*$bad_response*/,
2549
                $text_response);
2550
        }
2551
        // insure that tag name is 'serviceResponse'
2552
        if ($tree_response->node_name() != 'serviceResponse') {
2553
            // bad root node
2554
            $this->authError('PT not validated',
2555
                $validate_url,
2556
                false/*$no_response*/,
2557
                true/*$bad_response*/,
2558
                $text_response);
2559
        }
2560
        if (sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
2561
            // authentication succeded, extract the user name
2562
            if (sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) {
2563
                // no user specified => error
2564
                $this->authError('PT not validated',
2565
                    $validate_url,
2566
                    false/*$no_response*/,
2567
                    true/*$bad_response*/,
2568
                    $text_response);
2569
            }
2570
            $this->setUser(trim($arr[0]->get_content()));
2571
2572
        } else {
2573
            if (sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
2574
                // authentication succeded, extract the error code and message
2575
                $this->authError('PT not validated',
2576
                    $validate_url,
2577
                    false/*$no_response*/,
2578
                    false/*$bad_response*/,
2579
                    $text_response,
2580
                    $arr[0]->get_attribute('code')/*$err_code*/,
2581
                    trim($arr[0]->get_content())/*$err_msg*/);
2582
            } else {
2583
                $this->authError('PT not validated',
2584
                    $validate_url,
2585
                    false/*$no_response*/,
2586
                    true/*$bad_response*/,
2587
                    $text_response);
2588
            }
2589
        }
2590
2591
        // at this step, PT has been validated and $this->_user has been set,
2592
2593
        phpCAS::traceEnd(true);
2594
        return true;
2595
    }
2596
2597
    /** @} */
2598
2599
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2600
    // XX                                                                    XX
2601
    // XX                               MISC                                 XX
2602
    // XX                                                                    XX
2603
    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2604
2605
    /**
2606
     * @addtogroup internalMisc
2607
     * @{
2608
     */
2609
2610
    // ########################################################################
2611
    //  URL
2612
    // ########################################################################
2613
    /**
2614
     * the URL of the current request (without any ticket CGI parameter). Written
2615
     * and read by CASClient::getURL().
2616
     *
2617
     * @hideinitializer
2618
     * @private
2619
     */
2620
    var $_url = '';
2621
2622
    /**
2623
     * This method returns the URL of the current request (without any ticket
2624
     * CGI parameter).
2625
     *
2626
     * @return The URL
2627
     *
2628
     * @private
2629
     */
2630
    function getURL()
2631
    {
2632
        phpCAS::traceBegin();
2633
        // the URL is built when needed only
2634
        if (empty($this->_url)) {
2635
            $final_uri = '';
2636
            // remove the ticket if present in the URL
2637
            $final_uri = ($this->isHttps()) ? 'https' : 'http';
2638
            $final_uri .= '://';
2639
            /* replaced by Julien Marchal - v0.4.6
2640
			 * $this->_url .= $_SERVER['SERVER_NAME'];
2641
			 */
2642
            if (empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
2643
                /* replaced by teedog - v0.4.12
2644
				 * $this->_url .= $_SERVER['SERVER_NAME'];
2645
				 */
2646 View Code Duplication
                if (empty($_SERVER['SERVER_NAME'])) {
2647
                    $server_name = $_SERVER['HTTP_HOST'];
2648
                } else {
2649
                    $server_name = $_SERVER['SERVER_NAME'];
2650
                }
2651
            } else {
2652
                $server_name = $_SERVER['HTTP_X_FORWARDED_SERVER'];
2653
            }
2654
            $final_uri .= $server_name;
2655 View Code Duplication
            if (!strpos($server_name, ':')) {
2656
                if (($this->isHttps() && $_SERVER['SERVER_PORT'] != 443)
2657
                    || (!$this->isHttps() && $_SERVER['SERVER_PORT'] != 80)
2658
                ) {
2659
                    $final_uri .= ':';
2660
                    $final_uri .= $_SERVER['SERVER_PORT'];
2661
                }
2662
            }
2663
2664
            $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
2665
            $final_uri .= $request_uri[0];
2666
2667
            if (isset($request_uri[1]) && $request_uri[1]) {
2668
                $query_string = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
2669
2670
                // If the query string still has anything left, append it to the final URI
2671
                if ($query_string !== '') {
2672
                    $final_uri .= "?$query_string";
2673
                }
2674
2675
            }
2676
2677
            phpCAS::trace("Final URI: $final_uri");
2678
            $this->setURL($final_uri);
2679
        }
2680
        phpCAS::traceEnd($this->_url);
2681
        return $this->_url;
2682
    }
2683
2684
2685
    /**
2686
     * Removes a parameter from a query string
2687
     *
2688
     * @param string $parameterName
2689
     * @param string $queryString
2690
     * @return string
2691
     *
2692
     * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
2693
     */
2694
    function removeParameterFromQueryString($parameterName, $queryString)
2695
    {
2696
        $parameterName = preg_quote($parameterName);
2697
        return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
2698
    }
2699
2700
2701
    /**
2702
     * This method sets the URL of the current request
2703
     *
2704
     * @param $url url to set for service
2705
     *
2706
     * @private
2707
     */
2708
    function setURL($url)
2709
    {
2710
        $this->_url = $url;
2711
    }
2712
2713
    // ########################################################################
2714
    //  AUTHENTICATION ERROR HANDLING
2715
    // ########################################################################
2716
    /**
2717
     * This method is used to print the HTML output when the user was not authenticated.
2718
     *
2719
     * @param $failure the failure that occured
2720
     * @param $cas_url the URL the CAS server was asked for
2721
     * @param $no_response the response from the CAS server (other
2722
     * parameters are ignored if TRUE)
2723
     * @param $bad_response bad response from the CAS server ($err_code
2724
     * and $err_msg ignored if TRUE)
2725
     * @param $cas_response the response of the CAS server
2726
     * @param $err_code the error code given by the CAS server
2727
     * @param $err_msg the error message given by the CAS server
2728
     *
2729
     * @private
2730
     */
2731
    function authError(
2732
        $failure,
2733
        $cas_url,
2734
        $no_response,
2735
        $bad_response = '',
2736
        $cas_response = '',
2737
        $err_code = '',
2738
        $err_msg = ''
2739
    ) {
2740
        phpCAS::traceBegin();
2741
2742
        $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
2743
        printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED), htmlentities($this->getURL()),
2744
            $_SERVER['SERVER_ADMIN']);
2745
        phpCAS::trace('CAS URL: ' . $cas_url);
2746
        phpCAS::trace('Authentication failure: ' . $failure);
2747
        if ($no_response) {
2748
            phpCAS::trace('Reason: no response from the CAS server');
2749
        } else {
2750
            if ($bad_response) {
2751
                phpCAS::trace('Reason: bad response from the CAS server');
2752
            } else {
2753
                switch ($this->getServerVersion()) {
2754
                    case CAS_VERSION_1_0:
2755
                        phpCAS::trace('Reason: CAS error');
2756
                        break;
2757
                    case CAS_VERSION_2_0:
2758
                        if (empty($err_code)) {
2759
                            phpCAS::trace('Reason: no CAS error');
2760
                        } else {
2761
                            phpCAS::trace('Reason: [' . $err_code . '] CAS error: ' . $err_msg);
2762
                        }
2763
                        break;
2764
                }
2765
            }
2766
            phpCAS::trace('CAS response: ' . $cas_response);
2767
        }
2768
        $this->printHTMLFooter();
2769
        phpCAS::traceExit();
2770
        exit();
2771
    }
2772
2773
    /** @} */
2774
}
2775