Projet

Général

Profil

Paste
Télécharger (95,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / libraries / CAS.dgeo / CAS / Client.php @ 5a7e6170

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/AbstractStorage.php');
42

    
43
// include class for storing service cookies.
44
include_once(dirname(__FILE__).'/CookieJar.php');
45

    
46
// include class for fetching web requests.
47
include_once(dirname(__FILE__).'/Request/CurlRequest.php');
48

    
49
// include classes for proxying access to services
50
include_once(dirname(__FILE__).'/ProxiedService/Http/Get.php');
51
include_once(dirname(__FILE__).'/ProxiedService/Http/Post.php');
52
include_once(dirname(__FILE__).'/ProxiedService/Imap.php');
53

    
54
// include Exception classes
55
include_once(dirname(__FILE__).'/ProxiedService/Exception.php');
56
include_once(dirname(__FILE__).'/ProxyTicketException.php');
57
include_once(dirname(__FILE__).'/InvalidArgumentException.php');
58

    
59

    
60
/**
61
 * @class CAS_Client
62
 * The CAS_Client class is a client interface that provides CAS authentication
63
 * to PHP applications.
64
 *
65
 * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
66
 */
67

    
68
class CAS_Client
69
{
70

    
71
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
72
        // XX                                                                    XX
73
        // XX                          CONFIGURATION                             XX
74
        // XX                                                                    XX
75
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
76

    
77
        // ########################################################################
78
        //  HTML OUTPUT
79
        // ########################################################################
80
        /**
81
        * @addtogroup internalOutput
82
        * @{
83
        */
84

    
85
        /**
86
         * This method filters a string by replacing special tokens by appropriate values
87
         * and prints it. The corresponding tokens are taken into account:
88
         * - __CAS_VERSION__
89
         * - __PHPCAS_VERSION__
90
         * - __SERVER_BASE_URL__
91
         *
92
         * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter().
93
         *
94
         * @param $str the string to filter and output
95
         */
96
        private function HTMLFilterOutput($str)
97
        {
98
                $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
99
                $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
100
                $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
101
                echo $str;
102
        }
103

    
104
        /**
105
         * A string used to print the header of HTML pages. Written by CAS_Client::setHTMLHeader(),
106
         * read by CAS_Client::printHTMLHeader().
107
         *
108
         * @hideinitializer
109
         * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader()
110
         */
111
        private $_output_header = '';
112

    
113
        /**
114
         * This method prints the header of the HTML output (after filtering). If
115
         * CAS_Client::setHTMLHeader() was not used, a default header is output.
116
         *
117
         * @param $title the title of the page
118
         *
119
         * @see HTMLFilterOutput()
120
         */
121
        private function printHTMLHeader($title)
122
        {
123
                $this->HTMLFilterOutput(str_replace('__TITLE__',
124
                $title,
125
                (empty($this->_output_header)
126
                ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
127
                : $this->_output_header)
128
                )
129
                );
130
        }
131

    
132
        /**
133
         * A string used to print the footer of HTML pages. Written by CAS_Client::setHTMLFooter(),
134
         * read by printHTMLFooter().
135
         *
136
         * @hideinitializer
137
         * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter()
138
         */
139
        private $_output_footer = '';
140

    
141
        /**
142
         * This method prints the footer of the HTML output (after filtering). If
143
         * CAS_Client::setHTMLFooter() was not used, a default footer is output.
144
         *
145
         * @see HTMLFilterOutput()
146
         */
147
        private function printHTMLFooter()
148
        {
149
                $this->HTMLFilterOutput(empty($this->_output_footer)
150
                ?('<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>')
151
                :$this->_output_footer);
152
        }
153

    
154
        /**
155
         * This method set the HTML header used for all outputs.
156
         *
157
         * @param $header the HTML header.
158
         */
159
        public function setHTMLHeader($header)
160
        {
161
                $this->_output_header = $header;
162
        }
163

    
164
        /**
165
         * This method set the HTML footer used for all outputs.
166
         *
167
         * @param $footer the HTML footer.
168
         */
169
        public function setHTMLFooter($footer)
170
        {
171
                $this->_output_footer = $footer;
172
        }
173
        
174
        
175
        /** @} */
176

    
177

    
178
        // ########################################################################
179
        //  INTERNATIONALIZATION
180
        // ########################################################################
181
        /**
182
        * @addtogroup internalLang
183
        * @{
184
        */
185
        /**
186
         * A string corresponding to the language used by phpCAS. Written by
187
         * CAS_Client::setLang(), read by CAS_Client::getLang().
188

189
         * @note debugging information is always in english (debug purposes only).
190
         *
191
         * @hideinitializer
192
         * @sa CAS_Client::_strings, CAS_Client::getString()
193
         */
194
        private $_lang = '';
195

    
196
        /**
197
         * This method returns the language used by phpCAS.
198
         *
199
         * @return a string representing the language
200
         */
201
        private function getLang()
202
        {
203
                if ( empty($this->_lang) )
204
                $this->setLang(PHPCAS_LANG_DEFAULT);
205
                return $this->_lang;
206
        }
207

    
208
        /**
209
         * array containing the strings used by phpCAS. Written by CAS_Client::setLang(), read by
210
         * CAS_Client::getString() and used by CAS_Client::setLang().
211
         *
212
         * @note This array is filled by instructions in CAS/languages/<$this->_lang>.php
213
         *
214
         * @see CAS_Client::_lang, CAS_Client::getString(), CAS_Client::setLang(), CAS_Client::getLang()
215
         */
216
        private $_strings;
217

    
218
        /**
219
         * This method returns a string depending on the language.
220
         *
221
         * @param $str the index of the string in $_string.
222
         *
223
         * @return the string corresponding to $index in $string.
224
         *
225
         */
226
        private function getString($str)
227
        {
228
                // call CASclient::getLang() to be sure the language is initialized
229
                $this->getLang();
230

    
231
                if ( !isset($this->_strings[$str]) ) {
232
                        trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
233
                }
234
                return $this->_strings[$str];
235
        }
236

    
237
        /**
238
         * This method is used to set the language used by phpCAS.
239
         * @note Can be called only once.
240
         *
241
         * @param $lang a string representing the language.
242
         *
243
         * @sa CAS_LANG_FRENCH, CAS_LANG_ENGLISH
244
         */
245
        public function setLang($lang)
246
        {
247
                // include the corresponding language file
248
                include(dirname(__FILE__).'/languages/'.$lang.'.php');
249

    
250
                if ( !is_array($this->_strings) ) {
251
                        trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
252
                }
253
                $this->_lang = $lang;
254
        }
255

    
256
        /** @} */
257
        // ########################################################################
258
        //  CAS SERVER CONFIG
259
        // ########################################################################
260
        /**
261
        * @addtogroup internalConfig
262
        * @{
263
        */
264

    
265
        /**
266
         * a record to store information about the CAS server.
267
         * - $_server["version"]: the version of the CAS server
268
         * - $_server["hostname"]: the hostname of the CAS server
269
         * - $_server["port"]: the port the CAS server is running on
270
         * - $_server["uri"]: the base URI the CAS server is responding on
271
         * - $_server["base_url"]: the base URL of the CAS server
272
         * - $_server["login_url"]: the login URL of the CAS server
273
         * - $_server["service_validate_url"]: the service validating URL of the CAS server
274
         * - $_server["proxy_url"]: the proxy URL of the CAS server
275
         * - $_server["proxy_validate_url"]: the proxy validating URL of the CAS server
276
         * - $_server["logout_url"]: the logout URL of the CAS server
277
         *
278
         * $_server["version"], $_server["hostname"], $_server["port"] and $_server["uri"]
279
         * are written by CAS_Client::CAS_Client(), read by CAS_Client::getServerVersion(),
280
         * CAS_Client::getServerHostname(), CAS_Client::getServerPort() and CAS_Client::getServerURI().
281
         *
282
         * The other fields are written and read by CAS_Client::getServerBaseURL(),
283
         * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(),
284
         * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL().
285
         *
286
         * @hideinitializer
287
         */
288
        private $_server = array(
289
                'version' => -1,
290
                'hostname' => 'none',
291
                'port' => -1,
292
                'uri' => 'none');
293

    
294
        /**
295
         * This method is used to retrieve the version of the CAS server.
296
         * @return the version of the CAS server.
297
         */
298
        private function getServerVersion()
299
        {
300
                return $this->_server['version'];
301
        }
302

    
303
        /**
304
         * This method is used to retrieve the hostname of the CAS server.
305
         * @return the hostname of the CAS server.
306
         */
307
        private function getServerHostname()
308
        { return $this->_server['hostname']; }
309

    
310
        /**
311
         * This method is used to retrieve the port of the CAS server.
312
         * @return the port of the CAS server.
313
         */
314
        private function getServerPort()
315
        { return $this->_server['port']; }
316

    
317
        /**
318
         * This method is used to retrieve the URI of the CAS server.
319
         * @return a URI.
320
         */
321
        private function getServerURI()
322
        { return $this->_server['uri']; }
323

    
324
        /**
325
         * This method is used to retrieve the base URL of the CAS server.
326
         * @return a URL.
327
         */
328
        private function getServerBaseURL()
329
        {
330
                // the URL is build only when needed
331
                if ( empty($this->_server['base_url']) ) {
332
                        $this->_server['base_url'] = 'https://' . $this->getServerHostname();
333
                        if ($this->getServerPort()!=443) {
334
                                $this->_server['base_url'] .= ':'
335
                                .$this->getServerPort();
336
                        }
337
                        $this->_server['base_url'] .= $this->getServerURI();
338
                }
339
                return $this->_server['base_url'];
340
        }
341

    
342
        /**
343
         * This method is used to retrieve the login URL of the CAS server.
344
         * @param $gateway true to check authentication, false to force it
345
         * @param $renew true to force the authentication with the CAS server
346
         * NOTE : It is recommended that CAS implementations ignore the
347
         "gateway" parameter if "renew" is set
348
         * @return a URL.
349
         */
350
        public function getServerLoginURL($gateway=false,$renew=false) {
351
                phpCAS::traceBegin();
352
                // the URL is build only when needed
353
                if ( empty($this->_server['login_url']) ) {
354
                        $this->_server['login_url'] = $this->getServerBaseURL();
355
                        $this->_server['login_url'] .= 'login?service=';
356
                        $this->_server['login_url'] .= urlencode($this->getURL());
357
                }
358
                $url = $this->_server['login_url'];
359
                if($renew) {
360
                        // It is recommended that when the "renew" parameter is set, its value be "true"
361
                        $url = $this->buildQueryUrl($url, 'renew=true');
362
                } elseif ($gateway) {
363
                        // It is recommended that when the "gateway" parameter is set, its value be "true"
364
                        $url = $this->buildQueryUrl($url, 'gateway=true');
365
                }
366
                phpCAS::traceEnd($url);
367
                return $url;
368
        }
369

    
370
        /**
371
         * This method sets the login URL of the CAS server.
372
         * @param $url the login URL
373
         * @since 0.4.21 by Wyman Chan
374
         */
375
        public function setServerLoginURL($url)
376
        {
377
                return $this->_server['login_url'] = $url;
378
        }
379

    
380

    
381
        /**
382
         * This method sets the serviceValidate URL of the CAS server.
383
         * @param $url the serviceValidate URL
384
         * @since 1.1.0 by Joachim Fritschi
385
         */
386
        public function setServerServiceValidateURL($url)
387
        {
388
                return $this->_server['service_validate_url'] = $url;
389
        }
390

    
391

    
392
        /**
393
         * This method sets the proxyValidate URL of the CAS server.
394
         * @param $url the proxyValidate URL
395
         * @since 1.1.0 by Joachim Fritschi
396
         */
397
        public function setServerProxyValidateURL($url)
398
        {
399
                return $this->_server['proxy_validate_url'] = $url;
400
        }
401

    
402

    
403
        /**
404
         * This method sets the samlValidate URL of the CAS server.
405
         * @param $url the samlValidate URL
406
         * @since 1.1.0 by Joachim Fritschi
407
         */
408
        public function setServerSamlValidateURL($url)
409
        {
410
                return $this->_server['saml_validate_url'] = $url;
411
        }
412

    
413

    
414
        /**
415
         * This method is used to retrieve the service validating URL of the CAS server.
416
         * @return a URL.
417
         */
418
        public function getServerServiceValidateURL()
419
        {
420
                phpCAS::traceBegin();
421
                // the URL is build only when needed
422
                if ( empty($this->_server['service_validate_url']) ) {
423
                        switch ($this->getServerVersion()) {
424
                                case CAS_VERSION_1_0:
425
                                        $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
426
                                        break;
427
                                case CAS_VERSION_2_0:
428
                                        $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
429
                                        break;
430
                        }
431
                }
432
                $url = $this->buildQueryUrl($this->_server['service_validate_url'], 'service='.urlencode($this->getURL()));
433
                phpCAS::traceEnd($url);
434
                return $url;
435
        }
436
        /**
437
         * This method is used to retrieve the SAML validating URL of the CAS server.
438
         * @return a URL.
439
         */
440
        public function getServerSamlValidateURL()
441
        {
442
                phpCAS::traceBegin();
443
                // the URL is build only when needed
444
                if ( empty($this->_server['saml_validate_url']) ) {
445
                        switch ($this->getServerVersion()) {
446
                                case SAML_VERSION_1_1:
447
                                        $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
448
                                        break;
449
                        }
450
                }
451
                
452
                $url = $this->buildQueryUrl($this->_server['saml_validate_url'], 'TARGET='.urlencode($this->getURL()));
453
                phpCAS::traceEnd($url);
454
                return $url;
455
        }
456
        
457
        /**
458
         * This method is used to retrieve the proxy validating URL of the CAS server.
459
         * @return a URL.
460
         */
461
        public function getServerProxyValidateURL()
462
        {
463
                phpCAS::traceBegin();
464
                // the URL is build only when needed
465
                if ( empty($this->_server['proxy_validate_url']) ) {
466
                        switch ($this->getServerVersion()) {
467
                                case CAS_VERSION_1_0:
468
                                        $this->_server['proxy_validate_url'] = '';
469
                                        break;
470
                                case CAS_VERSION_2_0:
471
                                        $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
472
                                        break;
473
                        }
474
                }
475
                $url = $this->buildQueryUrl($this->_server['proxy_validate_url'], 'service='.urlencode($this->getURL()));
476
                phpCAS::traceEnd($url);
477
                return $url;
478
        }
479
        
480

    
481
        /**
482
         * This method is used to retrieve the proxy URL of the CAS server.
483
         * @return a URL.
484
         */
485
        public function getServerProxyURL()
486
        {
487
                // the URL is build only when needed
488
                if ( empty($this->_server['proxy_url']) ) {
489
                        switch ($this->getServerVersion()) {
490
                                case CAS_VERSION_1_0:
491
                                        $this->_server['proxy_url'] = '';
492
                                        break;
493
                                case CAS_VERSION_2_0:
494
                                        $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
495
                                        break;
496
                        }
497
                }
498
                return $this->_server['proxy_url'];
499
        }
500

    
501
        /**
502
         * This method is used to retrieve the logout URL of the CAS server.
503
         * @return a URL.
504
         */
505
        public function getServerLogoutURL()
506
        {
507
                // the URL is build only when needed
508
                if ( empty($this->_server['logout_url']) ) {
509
                        $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
510
                }
511
                return $this->_server['logout_url'];
512
        }
513

    
514
        /**
515
         * This method sets the logout URL of the CAS server.
516
         * @param $url the logout URL
517
         * @since 0.4.21 by Wyman Chan
518
         */
519
        public function setServerLogoutURL($url)
520
        {
521
                return $this->_server['logout_url'] = $url;
522
        }
523

    
524
        /**
525
         * An array to store extra curl options.
526
         */
527
        private $_curl_options = array();
528

    
529
        /**
530
         * This method is used to set additional user curl options.
531
         */
532
        public function setExtraCurlOption($key, $value)
533
        {
534
                $this->_curl_options[$key] = $value;
535
        }
536
        
537
        /** @} */
538

    
539
        // ########################################################################
540
        //  Change the internal behaviour of phpcas
541
        // ########################################################################
542
        
543
        /**
544
        * @addtogroup internalBehave
545
        * @{
546
        */
547
        
548
        /**
549
         * The class to instantiate for making web requests in readUrl().
550
         * The class specified must implement the CAS_RequestInterface.
551
         * By default CAS_CurlRequest is used, but this may be overridden to
552
         * supply alternate request mechanisms for testing.
553
         */
554
        private $_requestImplementation = 'CAS_CurlRequest';
555

    
556
        /**
557
         * Override the default implementation used to make web requests in readUrl().
558
         * This class must implement the CAS_RequestInterface.
559
         *
560
         * @param string $className
561
         * @return void
562
         */
563
        public function setRequestImplementation ($className) {
564
                $obj = new $className;
565
                if (!($obj instanceof CAS_RequestInterface))
566
                throw new CAS_InvalidArgumentException('$className must implement the CAS_RequestInterface');
567

    
568
                $this->_requestImplementation = $className;
569
        }
570
                
571
        /**
572
         * @var boolean $_exitOnAuthError; If true, phpCAS will exit on an authentication error.
573
         */
574
        private $_exitOnAuthError = true;
575

    
576
        /**
577
         * Configure the client to not call exit() when an authentication failure occurs.
578
         *
579
         * Needed for testing proper failure handling.
580
         *
581
         * @return void
582
         */
583
        public function setNoExitOnAuthError () {
584
                $this->_exitOnAuthError = false;
585
        }
586
        
587
        /**
588
         * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session tickets from the URL.
589
         * After a successful authentication.
590
         */
591
        private $_clearTicketsFromUrl = true;
592
        
593
        /**
594
         * Configure the client to not send redirect headers and call exit() on authentication
595
         * success. The normal redirect is used to remove the service ticket from the
596
         * client's URL, but for running unit tests we need to continue without exiting.
597
         *
598
         * Needed for testing authentication
599
         *
600
         * @return void
601
         */
602
        public function setNoClearTicketsFromUrl () {
603
                $this->_clearTicketsFromUrl = false;
604
        }
605
        
606
        /**
607
         * @var callback $_postAuthenticateCallbackFunction;  
608
         */
609
        private $_postAuthenticateCallbackFunction = null;
610
        
611
        /**
612
         * @var array $_postAuthenticateCallbackArgs;  
613
         */
614
        private $_postAuthenticateCallbackArgs = array();
615
        
616
        /**
617
         * Set a callback function to be run when a user authenticates.
618
         *
619
         * The callback function will be passed a $logoutTicket as its first parameter,
620
         * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
621
         * opaque string that can be used to map a session-id to the logout request in order
622
         * to support single-signout in applications that manage their own sessions 
623
         * (rather than letting phpCAS start the session).
624
         *
625
         * phpCAS::forceAuthentication() will always exit and forward client unless
626
         * they are already authenticated. To perform an action at the moment the user
627
         * logs in (such as registering an account, performing logging, etc), register
628
         * a callback function here.
629
         * 
630
         * @param callback $function
631
         * @param optional array $additionalArgs
632
         * @return void
633
         */
634
        public function setPostAuthenticateCallback ($function, array $additionalArgs = array()) {
635
                $this->_postAuthenticateCallbackFunction = $function;
636
                $this->_postAuthenticateCallbackArgs = $additionalArgs;
637
        }
638
        
639
        /**
640
         * @var callback $_signoutCallbackFunction;  
641
         */
642
        private $_signoutCallbackFunction = null;
643
        
644
        /**
645
         * @var array $_signoutCallbackArgs;  
646
         */
647
        private $_signoutCallbackArgs = array();
648
        
649
        /**
650
         * Set a callback function to be run when a single-signout request is received.
651
         *
652
         * The callback function will be passed a $logoutTicket as its first parameter,
653
         * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
654
         * opaque string that can be used to map a session-id to the logout request in order
655
         * to support single-signout in applications that manage their own sessions 
656
         * (rather than letting phpCAS start and destroy the session).
657
         * 
658
         * @param callback $function
659
         * @param optional array $additionalArgs
660
         * @return void
661
         */
662
        public function setSingleSignoutCallback ($function, array $additionalArgs = array()) {
663
                $this->_signoutCallbackFunction = $function;
664
                $this->_signoutCallbackArgs = $additionalArgs;
665
        }
666
        
667
        /** @} */
668

    
669
        // ########################################################################
670
        //  CONSTRUCTOR
671
        // ########################################################################
672
        /**
673
        * @addtogroup internalConfig
674
        * @{
675
        */
676
        
677
        /**
678
        * CAS_Client constructor.
679
        *
680
        * @param $server_version the version of the CAS server
681
        * @param $proxy TRUE if the CAS client is a CAS proxy, FALSE otherwise
682
        * @param $server_hostname the hostname of the CAS server
683
        * @param $server_port the port the CAS server is running on
684
        * @param $server_uri the URI the CAS server is responding on
685
        * @param $start_session Have phpCAS start PHP sessions (default true)
686
        *
687
        * @return a newly created CAS_Client object
688
        */
689
        public function __construct(
690
        $server_version,
691
        $proxy,
692
        $server_hostname,
693
        $server_port,
694
        $server_uri,
695
        $start_session = true) {
696

    
697
                phpCAS::traceBegin();
698

    
699
                $this->_start_session = $start_session;
700

    
701
                if ($this->_start_session && session_id() !== "")
702
                {
703
                        phpCAS :: error("Another session was started before phpcas. Either disable the session" .
704
                                " handling for phpcas in the client() call or modify your application to leave" .
705
                                " session handling to phpcas");                        
706
                }
707
                // skip Session Handling for logout requests and if don't want it'
708
                if ($start_session && !$this->isLogoutRequest())
709
                {
710
                        phpCAS :: trace("Starting a new session");
711
                        session_start();
712
                }
713

    
714

    
715
                // are we in proxy mode ?
716
                $this->_proxy = $proxy;
717

    
718
                // Make cookie handling available.
719
                if ($this->isProxy()) {
720
                        if (!isset($_SESSION['phpCAS']))
721
                        $_SESSION['phpCAS'] = array();
722
                        if (!isset($_SESSION['phpCAS']['service_cookies']))
723
                        $_SESSION['phpCAS']['service_cookies'] = array();
724
                        $this->_serviceCookieJar = new CAS_CookieJar($_SESSION['phpCAS']['service_cookies']);
725
                }
726

    
727
                //check version
728
                switch ($server_version) {
729
                        case CAS_VERSION_1_0:
730
                                if ( $this->isProxy() )
731
                                phpCAS::error('CAS proxies are not supported in CAS '
732
                                .$server_version);
733
                                break;
734
                        case CAS_VERSION_2_0:
735
                                break;
736
                        case SAML_VERSION_1_1:
737
                                break;
738
                        default:
739
                                phpCAS::error('this version of CAS (`'
740
                                .$server_version
741
                                .'\') is not supported by phpCAS '
742
                                .phpCAS::getVersion());
743
                }
744
                $this->_server['version'] = $server_version;
745

    
746
                // check hostname
747
                if ( empty($server_hostname)
748
                || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
749
                        phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
750
                }
751
                $this->_server['hostname'] = $server_hostname;
752

    
753
                // check port
754
                if ( $server_port == 0
755
                || !is_int($server_port) ) {
756
                        phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
757
                }
758
                $this->_server['port'] = $server_port;
759

    
760
                // check URI
761
                if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
762
                        phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
763
                }
764
                // add leading and trailing `/' and remove doubles
765
                $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
766
                $this->_server['uri'] = $server_uri;
767

    
768
                // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
769
                if ( $this->isProxy() ) {
770
                        $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
771
                }
772

    
773
                if ( $this->isCallbackMode() ) {
774
                        //callback mode: check that phpCAS is secured
775
                        if ( !$this->isHttps() ) {
776
                                phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
777
                        }
778
                } else {
779
                        //normal mode: get ticket and remove it from CGI parameters for developpers
780
                        $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
781
                        switch ($this->getServerVersion()) {
782
                                case CAS_VERSION_1_0: // check for a Service Ticket
783
                                        if( preg_match('/^ST-/',$ticket) ) {
784
                                                phpCAS::trace('ST \''.$ticket.'\' found');
785
                                                //ST present
786
                                                $this->setST($ticket);
787
                                                //ticket has been taken into account, unset it to hide it to applications
788
                                                unset($_GET['ticket']);
789
                                        } else if ( !empty($ticket) ) {
790
                                                //ill-formed ticket, halt
791
                                                phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
792
                                        }
793
                                        break;
794
                                case CAS_VERSION_2_0: // check for a Service or Proxy Ticket
795
                                        if( preg_match('/^[SP]T-/',$ticket) ) {
796
                                                phpCAS::trace('ST or PT \''.$ticket.'\' found');
797
                                                $this->setPT($ticket);
798
                                                unset($_GET['ticket']);
799
                                        } else if ( !empty($ticket) ) {
800
                                                //ill-formed ticket, halt
801
                                                phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
802
                                        }
803
                                        break;
804
                                case SAML_VERSION_1_1: // SAML just does Service Tickets
805
                                        if( preg_match('/^[SP]T-/',$ticket) ) {
806
                                                phpCAS::trace('SA \''.$ticket.'\' found');
807
                                                $this->setSA($ticket);
808
                                                unset($_GET['ticket']);
809
                                        } else if ( !empty($ticket) ) {
810
                                                //ill-formed ticket, halt
811
                                                phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
812
                                        }
813
                                        break;
814
                        }
815
                }
816
                phpCAS::traceEnd();
817
        }
818

    
819
        /** @} */
820

    
821
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
822
        // XX                                                                    XX
823
        // XX                           Session Handling                         XX
824
        // XX                                                                    XX
825
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
826

    
827
        /**
828
        * @addtogroup internalConfig
829
        * @{
830
        */
831
        
832
        
833
        /**
834
         * A variable to whether phpcas will use its own session handling. Default = true
835
         * @hideinitializer
836
         */
837
        private $_start_session = true;
838

    
839
        private function setStartSession($session)
840
        {
841
                $this->_start_session = session;
842
        }
843

    
844
        public function getStartSession($session)
845
        {
846
                $this->_start_session = session;
847
        }
848

    
849
        /** @} */
850
        
851
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
852
        // XX                                                                    XX
853
        // XX                           AUTHENTICATION                           XX
854
        // XX                                                                    XX
855
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
856

    
857
        /**
858
         * @addtogroup internalAuthentication
859
         * @{
860
         */
861

    
862
        /**
863
         * The Authenticated user. Written by CAS_Client::setUser(), read by CAS_Client::getUser().
864
         * @attention client applications should use phpCAS::getUser().
865
         *
866
         * @hideinitializer
867
         */
868
        private $_user = '';
869

    
870
        /**
871
         * This method sets the CAS user's login name.
872
         *
873
         * @param $user the login name of the authenticated user.
874
         *
875
         */
876
        private function setUser($user)
877
        {
878
                $this->_user = $user;
879
        }
880

    
881
        /**
882
         * This method returns the CAS user's login name.
883
         * @warning should be called only after CAS_Client::forceAuthentication() or
884
         * CAS_Client::isAuthenticated(), otherwise halt with an error.
885
         *
886
         * @return the login name of the authenticated user
887
         */
888
        public function getUser()
889
        {
890
                if ( empty($this->_user) ) {
891
                        phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
892
                }
893
                return $this->_user;
894
        }
895

    
896

    
897

    
898
        /***********************************************************************************************************************
899
         * Atrributes section
900
         *
901
         * @author Matthias Crauwels <matthias.crauwels@ugent.be>, Ghent University, Belgium
902
         *
903
         ***********************************************************************************************************************/
904
        /**
905
         * The Authenticated users attributes. Written by CAS_Client::setAttributes(), read by CAS_Client::getAttributes().
906
         * @attention client applications should use phpCAS::getAttributes().
907
         *
908
         * @hideinitializer
909
         */
910
        private $_attributes = array();
911

    
912
        public function setAttributes($attributes)
913
        { $this->_attributes = $attributes; }
914

    
915
        public function getAttributes() {
916
                if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also...
917
                        phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
918
                }
919
                return $this->_attributes;
920
        }
921

    
922
        public function hasAttributes()
923
        { return !empty($this->_attributes); }
924

    
925
        public function hasAttribute($key)
926
        { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
927

    
928
        public function getAttribute($key)        {
929
                if($this->hasAttribute($key)) {
930
                        return $this->_attributes[$key];
931
                }
932
        }
933

    
934
        /**
935
         * This method is called to renew the authentication of the user
936
         * If the user is authenticated, renew the connection
937
         * If not, redirect to CAS
938
         */
939
        public function renewAuthentication(){
940
                phpCAS::traceBegin();
941
                // Either way, the user is authenticated by CAS
942
                if( isset( $_SESSION['phpCAS']['auth_checked'] ) )
943
                unset($_SESSION['phpCAS']['auth_checked']);
944
                if ( $this->isAuthenticated() ) {
945
                        phpCAS::trace('user already authenticated; renew');
946
                        $this->redirectToCas(false,true);
947
                } else {
948
                        $this->redirectToCas();
949
                }
950
                phpCAS::traceEnd();
951
        }
952

    
953
        /**
954
         * This method is called to be sure that the user is authenticated. When not
955
         * authenticated, halt by redirecting to the CAS server; otherwise return TRUE.
956
         * @return TRUE when the user is authenticated; otherwise halt.
957
         */
958
        public function forceAuthentication()
959
        {
960
                phpCAS::traceBegin();
961

    
962
                if ( $this->isAuthenticated() ) {
963
                        // the user is authenticated, nothing to be done.
964
                        phpCAS::trace('no need to authenticate');
965
                        $res = TRUE;
966
                } else {
967
                        // the user is not authenticated, redirect to the CAS server
968
                        if (isset($_SESSION['phpCAS']['auth_checked'])) {
969
                                unset($_SESSION['phpCAS']['auth_checked']);
970
                        }
971
                        $this->redirectToCas(FALSE/* no gateway */);
972
                        // never reached
973
                        $res = FALSE;
974
                }
975
                phpCAS::traceEnd($res);
976
                return $res;
977
        }
978

    
979
        /**
980
         * An integer that gives the number of times authentication will be cached before rechecked.
981
         *
982
         * @hideinitializer
983
         */
984
        private $_cache_times_for_auth_recheck = 0;
985

    
986
        /**
987
         * Set the number of times authentication will be cached before rechecked.
988
         *
989
         * @param $n an integer.
990
         */
991
        public function setCacheTimesForAuthRecheck($n)
992
        {
993
                $this->_cache_times_for_auth_recheck = $n;
994
        }
995

    
996
        /**
997
         * This method is called to check whether the user is authenticated or not.
998
         * @return TRUE when the user is authenticated, FALSE when a previous gateway login failed or
999
         * the function will not return if the user is redirected to the cas server for a gateway login attempt
1000
         */
1001
        public function checkAuthentication()
1002
        {
1003
                phpCAS::traceBegin();
1004

    
1005
                if ( $this->isAuthenticated() ) {
1006
                        phpCAS::trace('user is authenticated');
1007
                        $res = TRUE;
1008
                } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
1009
                        // the previous request has redirected the client to the CAS server with gateway=true
1010
                        unset($_SESSION['phpCAS']['auth_checked']);
1011
                        $res = FALSE;
1012
                } else {
1013
                        // avoid a check against CAS on every request
1014
                        if (! isset($_SESSION['phpCAS']['unauth_count']) )
1015
                        $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
1016
                                
1017
                        if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1)
1018
                        || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck))
1019
                        {
1020
                                $res = FALSE;
1021

    
1022
                                if ($this->_cache_times_for_auth_recheck != -1)
1023
                                {
1024
                                        $_SESSION['phpCAS']['unauth_count']++;
1025
                                        phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')');
1026
                                }
1027
                                else
1028
                                {
1029
                                        phpCAS::trace('user is not authenticated (cached for until login pressed)');
1030
                                }
1031
                        }
1032
                        else
1033
                        {
1034
                                $_SESSION['phpCAS']['unauth_count'] = 0;
1035
                                $_SESSION['phpCAS']['auth_checked'] = true;
1036
                                phpCAS::trace('user is not authenticated (cache reset)');
1037
                                $this->redirectToCas(TRUE/* gateway */);
1038
                                // never reached
1039
                                $res = FALSE;
1040
                        }
1041
                }
1042
                phpCAS::traceEnd($res);
1043
                return $res;
1044
        }
1045

    
1046
        /**
1047
         * This method is called to check if the user is authenticated (previously or by
1048
         * tickets given in the URL).
1049
         *
1050
         * @return TRUE when the user is authenticated. Also may redirect to the same URL without the ticket.
1051
         */
1052
        public function isAuthenticated()
1053
        {
1054
                phpCAS::traceBegin();
1055
                $res = FALSE;
1056
                $validate_url = '';
1057

    
1058
                if ( $this->wasPreviouslyAuthenticated() ) {
1059
                        if($this->hasST() || $this->hasPT() || $this->hasSA()){
1060
                                // User has a additional ticket but was already authenticated
1061
                                phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
1062
                                header('Location: '.$this->getURL());
1063
                                phpCAS::trace( "Prepare redirect to remove ticket: ".$this->getURL() );
1064
                                phpCAS::traceExit();
1065
                                exit();
1066
                        }else{
1067
                                // the user has already (previously during the session) been
1068
                                // authenticated, nothing to be done.
1069
                                phpCAS::trace('user was already authenticated, no need to look for tickets');
1070
                                $res = TRUE;
1071
                        }
1072
                }
1073
                else {
1074
                        if ( $this->hasST() ) {
1075
                                // if a Service Ticket was given, validate it
1076
                                phpCAS::trace('ST `'.$this->getST().'\' is present');
1077
                                $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
1078
                                phpCAS::trace('ST `'.$this->getST().'\' was validated');
1079
                                if ( $this->isProxy() ) {
1080
                                        $this->validatePGT($validate_url,$text_response,$tree_response); // idem
1081
                                        phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
1082
                                        $_SESSION['phpCAS']['pgt'] = $this->getPGT();
1083
                                }
1084
                                $_SESSION['phpCAS']['user'] = $this->getUser();
1085
                                if($this->hasAttributes()){
1086
                                        $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1087
                                }
1088
                                $res = TRUE;
1089
                                $logoutTicket = $this->getST();
1090
                        }
1091
                        elseif ( $this->hasPT() ) {
1092
                                // if a Proxy Ticket was given, validate it
1093
                                phpCAS::trace('PT `'.$this->getPT().'\' is present');
1094
                                $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
1095
                                phpCAS::trace('PT `'.$this->getPT().'\' was validated');
1096
                                if ( $this->isProxy() ) {
1097
                                        $this->validatePGT($validate_url,$text_response,$tree_response); // idem
1098
                                        phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
1099
                                        $_SESSION['phpCAS']['pgt'] = $this->getPGT();
1100
                                }
1101
                                $_SESSION['phpCAS']['user'] = $this->getUser();
1102
                                if($this->hasAttributes()){
1103
                                        $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1104
                                }
1105
                                $res = TRUE;
1106
                                $logoutTicket = $this->getPT();
1107
                        }
1108
                        elseif ( $this->hasSA() ) {
1109
                                // if we have a SAML ticket, validate it.
1110
                                phpCAS::trace('SA `'.$this->getSA().'\' is present');
1111
                                $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
1112
                                phpCAS::trace('SA `'.$this->getSA().'\' was validated');
1113
                                $_SESSION['phpCAS']['user'] = $this->getUser();
1114
                                $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1115
                                $res = TRUE;
1116
                                $logoutTicket = $this->getSA();
1117
                        }
1118
                        else {
1119
                                // no ticket given, not authenticated
1120
                                phpCAS::trace('no ticket found');
1121
                        }
1122
                        if ($res) {
1123
                                // Mark the auth-check as complete to allow post-authentication
1124
                                // callbacks to make use of phpCAS::getUser() and similar methods
1125
                                $dbg = debug_backtrace();
1126
                                global $PHPCAS_AUTH_CHECK_CALL;
1127
                                $PHPCAS_AUTH_CHECK_CALL = array (
1128
                                        'done' => TRUE,
1129
                                        'file' => $dbg[0]['file'],
1130
                                        'line' => $dbg[0]['line'],
1131
                                        'method' => __CLASS__ . '::' . __FUNCTION__,
1132
                                        'result' => $res
1133
                                );
1134
                                
1135
                                // call the post-authenticate callback if registered.
1136
                                if ($this->_postAuthenticateCallbackFunction) {
1137
                                        $args = $this->_postAuthenticateCallbackArgs;
1138
                                        array_unshift($args, $logoutTicket);
1139
                                        call_user_func_array($this->_postAuthenticateCallbackFunction, $args);
1140
                                }
1141
                                
1142
                                // 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)
1143
                                // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
1144
                                // remove the ticket as a security precaution to prevent a ticket in the HTTP_REFERRER
1145
                                if ($this->_clearTicketsFromUrl) {
1146
                                        header('Location: '.$this->getURL());
1147
                                        phpCAS::trace( "Prepare redirect to : ".$this->getURL() );
1148
                                        phpCAS::traceExit();
1149
                                        exit();
1150
                                }
1151
                        }
1152
                }
1153

    
1154
                phpCAS::traceEnd($res);
1155
                return $res;
1156
        }
1157

    
1158
        /**
1159
         * This method tells if the current session is authenticated.
1160
         * @return true if authenticated based soley on $_SESSION variable
1161
         * @since 0.4.22 by Brendan Arnold
1162
         */
1163
        public function isSessionAuthenticated ()
1164
        {
1165
                return !empty($_SESSION['phpCAS']['user']);
1166
        }
1167

    
1168
        /**
1169
         * This method tells if the user has already been (previously) authenticated
1170
         * by looking into the session variables.
1171
         *
1172
         * @note This function switches to callback mode when needed.
1173
         *
1174
         * @return TRUE when the user has already been authenticated; FALSE otherwise.
1175
         */
1176
        private function wasPreviouslyAuthenticated()
1177
        {
1178
                phpCAS::traceBegin();
1179

    
1180
                if ( $this->isCallbackMode() ) {
1181
                        $this->callback();
1182
                }
1183

    
1184
                $auth = FALSE;
1185

    
1186
                if ( $this->isProxy() ) {
1187
                        // CAS proxy: username and PGT must be present
1188
                        if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1189
                                // authentication already done
1190
                                $this->setUser($_SESSION['phpCAS']['user']);
1191
                                if(isset($_SESSION['phpCAS']['attributes'])){
1192
                                        $this->setAttributes($_SESSION['phpCAS']['attributes']);
1193
                                }
1194
                                $this->setPGT($_SESSION['phpCAS']['pgt']);
1195
                                phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
1196
                                
1197
                                // Include the list of proxies
1198
                                if (isset($_SESSION['phpCAS']['proxies'])) {
1199
                                        $this->setProxies($_SESSION['phpCAS']['proxies']);
1200
                                        phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"'); 
1201
                                }
1202
                                
1203
                                $auth = TRUE;
1204
                        } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
1205
                                // these two variables should be empty or not empty at the same time
1206
                                phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
1207
                                // unset all tickets to enforce authentication
1208
                                unset($_SESSION['phpCAS']);
1209
                                $this->setST('');
1210
                                $this->setPT('');
1211
                        } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1212
                                // these two variables should be empty or not empty at the same time
1213
                                phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
1214
                                // unset all tickets to enforce authentication
1215
                                unset($_SESSION['phpCAS']);
1216
                                $this->setST('');
1217
                                $this->setPT('');
1218
                        } else {
1219
                                phpCAS::trace('neither user not PGT found');
1220
                        }
1221
                } else {
1222
                        // `simple' CAS client (not a proxy): username must be present
1223
                        if ( $this->isSessionAuthenticated() ) {
1224
                                // authentication already done
1225
                                $this->setUser($_SESSION['phpCAS']['user']);
1226
                                if(isset($_SESSION['phpCAS']['attributes'])){
1227
                                        $this->setAttributes($_SESSION['phpCAS']['attributes']);
1228
                                }
1229
                                phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
1230
                                
1231
                                // Include the list of proxies
1232
                                if (isset($_SESSION['phpCAS']['proxies'])) {
1233
                                        $this->setProxies($_SESSION['phpCAS']['proxies']);
1234
                                        phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"'); 
1235
                                }
1236
                                
1237
                                $auth = TRUE;
1238
                        } else {
1239
                                phpCAS::trace('no user found');
1240
                        }
1241
                }
1242

    
1243
                phpCAS::traceEnd($auth);
1244
                return $auth;
1245
        }
1246

    
1247
        /**
1248
         * This method is used to redirect the client to the CAS server.
1249
         * It is used by CAS_Client::forceAuthentication() and CAS_Client::checkAuthentication().
1250
         * @param $gateway true to check authentication, false to force it
1251
         * @param $renew true to force the authentication with the CAS server
1252
         */
1253
        public function redirectToCas($gateway=false,$renew=false){
1254
                phpCAS::traceBegin();
1255
                $cas_url = $this->getServerLoginURL($gateway,$renew);
1256
                header('Location: '.$cas_url);
1257
                phpCAS::trace( "Redirect to : ".$cas_url );
1258

    
1259
                $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED));
1260

    
1261
                printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
1262
                $this->printHTMLFooter();
1263

    
1264
                phpCAS::traceExit();
1265
                exit();
1266
        }
1267

    
1268

    
1269
        /**
1270
         * This method is used to logout from CAS.
1271
         * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server
1272
         */
1273
        public function logout($params) {
1274
                phpCAS::traceBegin();
1275
                $cas_url = $this->getServerLogoutURL();
1276
                $paramSeparator = '?';
1277
                if (isset($params['url'])) {
1278
                        $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']);
1279
                        $paramSeparator = '&';
1280
                }
1281
                if (isset($params['service'])) {
1282
                        $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
1283
                }
1284
                header('Location: '.$cas_url);
1285
                phpCAS::trace( "Prepare redirect to : ".$cas_url );
1286

    
1287
                session_unset();
1288
                session_destroy();
1289

    
1290
                $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
1291
                printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
1292
                $this->printHTMLFooter();
1293

    
1294
                phpCAS::traceExit();
1295
                exit();
1296
        }
1297

    
1298
        /**
1299
         * @return true if the current request is a logout request.
1300
         */
1301
        private function isLogoutRequest() {
1302
                return !empty($_POST['logoutRequest']);
1303
        }
1304

    
1305
        /**
1306
         * This method handles logout requests.
1307
         * @param $check_client true to check the client bofore handling the request,
1308
         * false not to perform any access control. True by default.
1309
         * @param $allowed_clients an array of host names allowed to send logout requests.
1310
         * By default, only the CAs server (declared in the constructor) will be allowed.
1311
         */
1312
        public function handleLogoutRequests($check_client=true, $allowed_clients=false) {
1313
                phpCAS::traceBegin();
1314
                if (!$this->isLogoutRequest()) {
1315
                        phpCAS::trace("Not a logout request");
1316
                        phpCAS::traceEnd();
1317
                        return;
1318
                }
1319
                if(!$this->_start_session && is_null($this->_signoutCallbackFunction)){
1320
                        phpCAS::trace("phpCAS can't handle logout requests if it does not manage the session.");
1321
                }
1322
                phpCAS::trace("Logout requested");
1323
                phpCAS::trace("SAML REQUEST: ".$_POST['logoutRequest']);
1324
                if ($check_client) {
1325
                        if (!$allowed_clients) {
1326
                                $allowed_clients = array( $this->getServerHostname() );
1327
                        }
1328
                        $client_ip = $_SERVER['REMOTE_ADDR'];
1329
                        $client = gethostbyaddr($client_ip);
1330
                        phpCAS::trace("Client: ".$client."/".$client_ip);
1331
                        $allowed = false;
1332
                        foreach ($allowed_clients as $allowed_client) {
1333
                                if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
1334
                                        phpCAS::trace("Allowed client '".$allowed_client."' matches, logout request is allowed");
1335
                                        $allowed = true;
1336
                                        break;
1337
                                } else {
1338
                                        phpCAS::trace("Allowed client '".$allowed_client."' does not match");
1339
                                }
1340
                        }
1341
                        if (!$allowed) {
1342
                                phpCAS::error("Unauthorized logout request from client '".$client."'");
1343
                                printf("Unauthorized!");
1344
                                phpCAS::traceExit();
1345
                                exit();
1346
                        }
1347
                } else {
1348
                        phpCAS::trace("No access control set");
1349
                }
1350
                // Extract the ticket from the SAML Request
1351
                preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3);
1352
                $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|','',$tick[0][0]);
1353
                $ticket2logout = preg_replace('|</samlp:SessionIndex>|','',$wrappedSamlSessionIndex);
1354
                phpCAS::trace("Ticket to logout: ".$ticket2logout);
1355
                
1356
                // call the post-authenticate callback if registered.
1357
                if ($this->_signoutCallbackFunction) {
1358
                        $args = $this->_signoutCallbackArgs;
1359
                        array_unshift($args, $ticket2logout);
1360
                        call_user_func_array($this->_signoutCallbackFunction, $args);
1361
                }
1362
                
1363
                // If phpCAS is managing the session, destroy it.
1364
                if ($this->_start_session) {
1365
                        $session_id = preg_replace('/[^\w]/','',$ticket2logout);
1366
                        phpCAS::trace("Session id: ".$session_id);
1367
        
1368
                        // destroy a possible application session created before phpcas
1369
                        if(session_id() !== ""){
1370
                                session_unset();
1371
                                session_destroy();
1372
                        }
1373
                        // fix session ID
1374
                        session_id($session_id);
1375
                        $_COOKIE[session_name()]=$session_id;
1376
                        $_GET[session_name()]=$session_id;
1377
        
1378
                        // Overwrite session
1379
                        session_start();
1380
                        session_unset();
1381
                        session_destroy();
1382
                }
1383
                
1384
                printf("Disconnected!");
1385
                phpCAS::traceExit();
1386
                exit();
1387
        }
1388

    
1389
        /** @} */
1390

    
1391
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1392
        // XX                                                                    XX
1393
        // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
1394
        // XX                                                                    XX
1395
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1396

    
1397
        // ########################################################################
1398
        //  ST
1399
        // ########################################################################
1400
        /**
1401
        * @addtogroup internalBasic
1402
        * @{
1403
        */
1404

    
1405
        /**
1406
         * the Service Ticket provided in the URL of the request if present
1407
         * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
1408
         * CAS_Client::getST() and CAS_Client::hasPGT().
1409
         *
1410
         * @hideinitializer
1411
         */
1412
        private $_st = '';
1413

    
1414
        /**
1415
         * This method returns the Service Ticket provided in the URL of the request.
1416
         * @return The service ticket.
1417
         */
1418
        public  function getST()
1419
        { return $this->_st; }
1420

    
1421
        /**
1422
         * This method stores the Service Ticket.
1423
         * @param $st The Service Ticket.
1424
         */
1425
        public function setST($st)
1426
        { $this->_st = $st; }
1427

    
1428
        /**
1429
         * This method tells if a Service Ticket was stored.
1430
         * @return TRUE if a Service Ticket has been stored.
1431
         */
1432
        public function hasST()
1433
        { return !empty($this->_st); }
1434

    
1435
        /** @} */
1436

    
1437
        // ########################################################################
1438
        //  ST VALIDATION
1439
        // ########################################################################
1440
        /**
1441
        * @addtogroup internalBasic
1442
        * @{
1443
        */
1444

    
1445
        /**
1446
         * the certificate of the CAS server CA.
1447
         *
1448
         * @hideinitializer
1449
         */
1450
        private $_cas_server_ca_cert = '';
1451

    
1452
        /**
1453
         * Set to true not to validate the CAS server.
1454
         *
1455
         * @hideinitializer
1456
         */
1457
        private $_no_cas_server_validation = false;
1458

    
1459

    
1460
        /**
1461
         * Set the CA certificate of the CAS server.
1462
         *
1463
         * @param $cert the PEM certificate of the CA that emited the cert of the server
1464
         */
1465
        public function setCasServerCACert($cert)
1466
        {
1467
                $this->_cas_server_ca_cert = $cert;
1468
        }
1469

    
1470
        /**
1471
         * Set no SSL validation for the CAS server.
1472
         */
1473
        public function setNoCasServerValidation()
1474
        {
1475
                $this->_no_cas_server_validation = true;
1476
        }
1477

    
1478
        /**
1479
         * This method is used to validate a ST; halt on failure, and sets $validate_url,
1480
         * $text_reponse and $tree_response on success. These parameters are used later
1481
         * by CAS_Client::validatePGT() for CAS proxies.
1482
         * Used for all CAS 1.0 validations
1483
         * @param $validate_url the URL of the request to the CAS server.
1484
         * @param $text_response the response of the CAS server, as is (XML text).
1485
         * @param $tree_response the response of the CAS server, as a DOM XML tree.
1486
         *
1487
         * @return bool TRUE when successfull, halt otherwise by calling CAS_Client::authError().
1488
         */
1489
        public function validateST($validate_url,&$text_response,&$tree_response)
1490
        {
1491
                phpCAS::traceBegin();
1492
                // build the URL to validate the ticket
1493
                $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
1494
                if ( $this->isProxy() ) {
1495
                        // pass the callback url for CAS proxies
1496
                        $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
1497
                }
1498

    
1499
                // open and read the URL
1500
                if ( !$this->readURL($validate_url,$headers,$text_response,$err_msg) ) {
1501
                        phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1502
                        $this->authError('ST not validated',
1503
                        $validate_url,
1504
                        TRUE/*$no_response*/);
1505
                }
1506

    
1507
                // analyze the result depending on the version
1508
                switch ($this->getServerVersion()) {
1509
                        case CAS_VERSION_1_0:
1510
                                if (preg_match('/^no\n/',$text_response)) {
1511
                                        phpCAS::trace('ST has not been validated');
1512
                                        $this->authError('ST not validated',
1513
                                        $validate_url,
1514
                                        FALSE/*$no_response*/,
1515
                                        FALSE/*$bad_response*/,
1516
                                        $text_response);
1517
                                }
1518
                                if (!preg_match('/^yes\n/',$text_response)) {
1519
                                        phpCAS::trace('ill-formed response');
1520
                                        $this->authError('ST not validated',
1521
                                        $validate_url,
1522
                                        FALSE/*$no_response*/,
1523
                                        TRUE/*$bad_response*/,
1524
                                        $text_response);
1525
                                }
1526
                                // ST has been validated, extract the user name
1527
                                $arr = preg_split('/\n/',$text_response);
1528
                                $this->setUser(trim($arr[1]));
1529
                                break;
1530
                        case CAS_VERSION_2_0:
1531

    
1532
                                // create new DOMDocument object
1533
                                $dom = new DOMDocument();
1534
                                // Fix possible whitspace problems
1535
                                $dom->preserveWhiteSpace = false;
1536
                                // read the response of the CAS server into a DOM object
1537
                                if ( !($dom->loadXML($text_response))) {
1538
                                        phpCAS::trace('dom->loadXML() failed');
1539
                                        $this->authError('ST not validated',
1540
                                        $validate_url,
1541
                                        FALSE/*$no_response*/,
1542
                                        TRUE/*$bad_response*/,
1543
                                        $text_response);
1544
                                }
1545
                                // read the root node of the XML tree
1546
                                if ( !($tree_response = $dom->documentElement) ) {
1547
                                        phpCAS::trace('documentElement() failed');
1548
                                        $this->authError('ST not validated',
1549
                                        $validate_url,
1550
                                        FALSE/*$no_response*/,
1551
                                        TRUE/*$bad_response*/,
1552
                                        $text_response);
1553
                                }
1554
                                // insure that tag name is 'serviceResponse'
1555
                                if ( $tree_response->localName != 'serviceResponse' ) {
1556
                                        phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->localName.'\'');
1557
                                        $this->authError('ST not validated',
1558
                                        $validate_url,
1559
                                        FALSE/*$no_response*/,
1560
                                        TRUE/*$bad_response*/,
1561
                                        $text_response);
1562
                                }
1563

    
1564
                                if ( $tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
1565
                                        // authentication succeded, extract the user name
1566
                                        $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
1567
                                        if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
1568
                                                // no user specified => error
1569
                                                $this->authError('ST not validated',
1570
                                                $validate_url,
1571
                                                FALSE/*$no_response*/,
1572
                                                TRUE/*$bad_response*/,
1573
                                                $text_response);
1574
                                        }
1575
                                        $this->setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue));
1576
                                        $this->readExtraAttributesCas20($success_elements);
1577
                                } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
1578
                                        phpCAS::trace('<authenticationFailure> found');
1579
                                        // authentication failed, extract the error code and message
1580
                                        $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
1581
                                        $this->authError('ST not validated',
1582
                                        $validate_url,
1583
                                        FALSE/*$no_response*/,
1584
                                        FALSE/*$bad_response*/,
1585
                                        $text_response,
1586
                                        $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
1587
                                        trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/);
1588
                                } else {
1589
                                        phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
1590
                                        $this->authError('ST not validated',
1591
                                        $validate_url,
1592
                                        FALSE/*$no_response*/,
1593
                                        TRUE/*$bad_response*/,
1594
                                        $text_response);
1595
                                }
1596
                                break;
1597
                }
1598
                $this->renameSession($this->getST());
1599
                // at this step, ST has been validated and $this->_user has been set,
1600
                phpCAS::traceEnd(TRUE);
1601
                return TRUE;
1602
        }
1603
        
1604
        /** @} */
1605

    
1606
        
1607
        // ########################################################################
1608
        //  SAML VALIDATION
1609
        // ########################################################################
1610
        /**
1611
        * @addtogroup internalSAML
1612
        * @{
1613
        */
1614

    
1615
        /**
1616
         * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
1617
         * $text_reponse and $tree_response on success. These parameters are used later
1618
         * by CAS_Client::validatePGT() for CAS proxies.
1619
         *
1620
         * @param $validate_url the URL of the request to the CAS server.
1621
         * @param $text_response the response of the CAS server, as is (XML text).
1622
         * @param $tree_response the response of the CAS server, as a DOM XML tree.
1623
         *
1624
         * @return bool TRUE when successfull, halt otherwise by calling CAS_Client::authError().
1625
         */
1626
        public function validateSA($validate_url,&$text_response,&$tree_response)
1627
        {
1628
                phpCAS::traceBegin();
1629

    
1630
                // build the URL to validate the ticket
1631
                $validate_url = $this->getServerSamlValidateURL();
1632

    
1633
                // open and read the URL
1634
                if ( !$this->readURL($validate_url,$headers,$text_response,$err_msg) ) {
1635
                        phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1636
                        $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
1637
                }
1638

    
1639
                phpCAS::trace('server version: '.$this->getServerVersion());
1640

    
1641
                // analyze the result depending on the version
1642
                switch ($this->getServerVersion()) {
1643
                        case SAML_VERSION_1_1:
1644

    
1645
                                // create new DOMDocument Object
1646
                                $dom = new DOMDocument();
1647
                                // Fix possible whitspace problems
1648
                                $dom->preserveWhiteSpace = false;
1649
                                // read the response of the CAS server into a DOM object
1650
                                if ( !($dom->loadXML($text_response))) {
1651
                                        phpCAS::trace('dom->loadXML() failed');
1652
                                        $this->authError('SA not validated',
1653
                                        $validate_url,
1654
                                        FALSE/*$no_response*/,
1655
                                        TRUE/*$bad_response*/,
1656
                                        $text_response);
1657
                                }
1658
                                // read the root node of the XML tree
1659
                                if ( !($tree_response = $dom->documentElement) ) {
1660
                                        phpCAS::trace('documentElement() failed');
1661
                                        $this->authError('SA not validated',
1662
                                        $validate_url,
1663
                                        FALSE/*$no_response*/,
1664
                                        TRUE/*$bad_response*/,
1665
                                        $text_response);
1666
                                }
1667
                                // insure that tag name is 'Envelope'
1668
                                if ( $tree_response->localName != 'Envelope' ) {
1669
                                        phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->localName.'\'');
1670
                                        $this->authError('SA not validated',
1671
                                        $validate_url,
1672
                                        FALSE/*$no_response*/,
1673
                                        TRUE/*$bad_response*/,
1674
                                        $text_response);
1675
                                }
1676
                                // check for the NameIdentifier tag in the SAML response
1677
                                if ( $tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
1678
                                        $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
1679
                                        phpCAS::trace('NameIdentifier found');
1680
                                        $user = trim($success_elements->item(0)->nodeValue);
1681
                                        phpCAS::trace('user = `'.$user.'`');
1682
                                        $this->setUser($user);
1683
                                        $this->setSessionAttributes($text_response);
1684
                                } else {
1685
                                        phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
1686
                                        $this->authError('SA not validated',
1687
                                        $validate_url,
1688
                                        FALSE/*$no_response*/,
1689
                                        TRUE/*$bad_response*/,
1690
                                        $text_response);
1691
                                }
1692
                                break;
1693
                }
1694
                $this->renameSession($this->getSA());
1695
                // at this step, ST has been validated and $this->_user has been set,
1696
                phpCAS::traceEnd(TRUE);
1697
                return TRUE;
1698
        }
1699

    
1700
        /**
1701
         * This method will parse the DOM and pull out the attributes from the SAML
1702
         * payload and put them into an array, then put the array into the session.
1703
         *
1704
         * @param $text_response the SAML payload.
1705
         * @return bool TRUE when successfull and FALSE if no attributes a found
1706
         */
1707
        private function setSessionAttributes($text_response)
1708
        {
1709
                phpCAS::traceBegin();
1710

    
1711
                $result = FALSE;
1712

    
1713
                $attr_array = array();
1714

    
1715
                // create new DOMDocument Object
1716
                $dom = new DOMDocument();
1717
                // Fix possible whitspace problems
1718
                $dom->preserveWhiteSpace = false;
1719
                if (($dom->loadXML($text_response))) {
1720
                        $xPath = new DOMXpath($dom);
1721
                        $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
1722
                        $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
1723
                        $nodelist = $xPath->query("//saml:Attribute");
1724

    
1725
                        if($nodelist){
1726
                                foreach($nodelist as $node){
1727
                                        $xres = $xPath->query("saml:AttributeValue", $node);
1728
                                        $name = $node->getAttribute("AttributeName");
1729
                                        $value_array = array();
1730
                                        foreach($xres as $node2){
1731
                                                $value_array[] = $node2->nodeValue;
1732
                                        }
1733
                                        $attr_array[$name] = $value_array;
1734
                                }
1735
                                // UGent addition...
1736
                                foreach($attr_array as $attr_key => $attr_value) {
1737
                                        if(count($attr_value) > 1) {
1738
                                                $this->_attributes[$attr_key] = $attr_value;
1739
                                                phpCAS::trace("* " . $attr_key . "=" . $attr_value);
1740
                                        }
1741
                                        else {
1742
                                                $this->_attributes[$attr_key] = $attr_value[0];
1743
                                                phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
1744
                                        }
1745
                                }
1746
                                $result = TRUE;
1747
                        }else{
1748
                                phpCAS::trace("SAML Attributes are empty");
1749
                                $result = FALSE;
1750
                        }
1751
                }
1752
                phpCAS::traceEnd($result);
1753
                return $result;
1754
        }
1755
        
1756
        /**
1757
         * This method returns the SAML Ticket provided in the URL of the request.
1758
         * @return The SAML ticket.
1759
         */
1760
        public function getSA()
1761
        { return 'ST'.substr($this->_sa, 2); }
1762

    
1763
        /**
1764
         * This method stores the SAML Ticket.
1765
         * @param $sa The SAML Ticket.
1766
         */
1767
        public function setSA($sa)
1768
        { $this->_sa = $sa; }
1769

    
1770
        /**
1771
         * This method tells if a SAML Ticket was stored.
1772
         * @return TRUE if a SAML Ticket has been stored.
1773
         */
1774
        public function hasSA()
1775
        { return !empty($this->_sa); }
1776

    
1777
        /** @} */
1778

    
1779
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1780
        // XX                                                                    XX
1781
        // XX                     PROXY FEATURES (CAS 2.0)                       XX
1782
        // XX                                                                    XX
1783
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1784

    
1785
        // ########################################################################
1786
        //  PROXYING
1787
        // ########################################################################
1788
        /**
1789
        * @addtogroup internalProxy
1790
        * @{
1791
        */
1792

    
1793
        /**
1794
         * A boolean telling if the client is a CAS proxy or not. Written by CAS_Client::CAS_Client(),
1795
         * read by CAS_Client::isProxy().
1796
         */
1797
        private $_proxy;
1798

    
1799
        /**
1800
         * Handler for managing service cookies.
1801
         */
1802
        private $_serviceCookieJar;
1803

    
1804
        /**
1805
         * Tells if a CAS client is a CAS proxy or not
1806
         *
1807
         * @return TRUE when the CAS client is a CAs proxy, FALSE otherwise
1808
         */
1809
        public function isProxy()
1810
        {
1811
                return $this->_proxy;
1812
        }
1813

    
1814
        /** @} */
1815
        // ########################################################################
1816
        //  PGT
1817
        // ########################################################################
1818
        /**
1819
        * @addtogroup internalProxy
1820
        * @{
1821
        */
1822

    
1823
        /**
1824
         * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
1825
         * Written by CAS_Client::setPGT(), read by CAS_Client::getPGT() and CAS_Client::hasPGT().
1826
         *
1827
         * @hideinitializer
1828
         */
1829
        private $_pgt = '';
1830

    
1831
        /**
1832
         * This method returns the Proxy Granting Ticket given by the CAS server.
1833
         * @return The Proxy Granting Ticket.
1834
         */
1835
        private function getPGT()
1836
        { return $this->_pgt; }
1837

    
1838
        /**
1839
         * This method stores the Proxy Granting Ticket.
1840
         * @param $pgt The Proxy Granting Ticket.
1841
         */
1842
        private function setPGT($pgt)
1843
        { $this->_pgt = $pgt; }
1844

    
1845
        /**
1846
         * This method tells if a Proxy Granting Ticket was stored.
1847
         * @return TRUE if a Proxy Granting Ticket has been stored.
1848
         */
1849
        private function hasPGT()
1850
        { return !empty($this->_pgt); }
1851

    
1852
        /** @} */
1853

    
1854
        // ########################################################################
1855
        //  CALLBACK MODE
1856
        // ########################################################################
1857
        /**
1858
        * @addtogroup internalCallback
1859
        * @{
1860
        */
1861
        /**
1862
         * each PHP script using phpCAS in proxy mode is its own callback to get the
1863
         * PGT back from the CAS server. callback_mode is detected by the constructor
1864
         * thanks to the GET parameters.
1865
         */
1866

    
1867
        /**
1868
         * a boolean to know if the CAS client is running in callback mode. Written by
1869
         * CAS_Client::setCallBackMode(), read by CAS_Client::isCallbackMode().
1870
         *
1871
         * @hideinitializer
1872
         */
1873
        private $_callback_mode = FALSE;
1874

    
1875
        /**
1876
         * This method sets/unsets callback mode.
1877
         *
1878
         * @param $callback_mode TRUE to set callback mode, FALSE otherwise.
1879
         */
1880
        private function setCallbackMode($callback_mode)
1881
        {
1882
                $this->_callback_mode = $callback_mode;
1883
        }
1884

    
1885
        /**
1886
         * This method returns TRUE when the CAs client is running i callback mode,
1887
         * FALSE otherwise.
1888
         *
1889
         * @return A boolean.
1890
         */
1891
        private function isCallbackMode()
1892
        {
1893
                return $this->_callback_mode;
1894
        }
1895

    
1896
        /**
1897
         * the URL that should be used for the PGT callback (in fact the URL of the
1898
         * current request without any CGI parameter). Written and read by
1899
         * CAS_Client::getCallbackURL().
1900
         *
1901
         * @hideinitializer
1902
         */
1903
        private $_callback_url = '';
1904

    
1905
        /**
1906
         * This method returns the URL that should be used for the PGT callback (in
1907
         * fact the URL of the current request without any CGI parameter, except if
1908
         * phpCAS::setFixedCallbackURL() was used).
1909
         *
1910
         * @return The callback URL
1911
         */
1912
        private function getCallbackURL()
1913
        {
1914
                // the URL is built when needed only
1915
                if ( empty($this->_callback_url) ) {
1916
                        $final_uri = '';
1917
                        // remove the ticket if present in the URL
1918
                        $final_uri = 'https://';
1919
                        $final_uri .= $this->getServerUrl();
1920
                        $request_uri = $_SERVER['REQUEST_URI'];
1921
                        $request_uri = preg_replace('/\?.*$/','',$request_uri);
1922
                        $final_uri .= $request_uri;
1923
                        $this->setCallbackURL($final_uri);
1924
                }
1925
                return $this->_callback_url;
1926
        }
1927

    
1928
        /**
1929
         * This method sets the callback url.
1930
         *
1931
         * @param $callback_url url to set callback
1932
         */
1933
        public function setCallbackURL($url)
1934
        {
1935
                return $this->_callback_url = $url;
1936
        }
1937

    
1938
        /**
1939
         * This method is called by CAS_Client::CAS_Client() when running in callback
1940
         * mode. It stores the PGT and its PGT Iou, prints its output and halts.
1941
         */
1942
        private function callback()
1943
        {
1944
                phpCAS::traceBegin();
1945
                if (preg_match('/PGTIOU-[\.\-\w]/', $_GET['pgtIou'])){
1946
                        if(preg_match('/[PT]GT-[\.\-\w]/', $_GET['pgtId'])){
1947
                                $this->printHTMLHeader('phpCAS callback');
1948
                                $pgt_iou = $_GET['pgtIou'];
1949
                                $pgt = $_GET['pgtId'];
1950
                                phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
1951
                                echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
1952
                                $this->storePGT($pgt,$pgt_iou);
1953
                                $this->printHTMLFooter();
1954
                        }else{
1955
                                phpCAS::error('PGT format invalid' . $_GET['pgtId']);
1956
                        }
1957
                }else{
1958
                        phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
1959
                }
1960
                phpCAS::traceExit();
1961
                exit();
1962
        }
1963

    
1964
        /** @} */
1965

    
1966
        // ########################################################################
1967
        //  PGT STORAGE
1968
        // ########################################################################
1969
        /**
1970
        * @addtogroup internalPGTStorage
1971
        * @{
1972
        */
1973

    
1974
        /**
1975
         * an instance of a class inheriting of PGTStorage, used to deal with PGT
1976
         * storage. Created by CAS_Client::setPGTStorageFile(), used
1977
         * by CAS_Client::setPGTStorageFile() and CAS_Client::initPGTStorage().
1978
         *
1979
         * @hideinitializer
1980
         */
1981
        private $_pgt_storage = null;
1982

    
1983
        /**
1984
         * This method is used to initialize the storage of PGT's.
1985
         * Halts on error.
1986
         */
1987
        private function initPGTStorage()
1988
        {
1989
                // if no SetPGTStorageXxx() has been used, default to file
1990
                if ( !is_object($this->_pgt_storage) ) {
1991
                        $this->setPGTStorageFile();
1992
                }
1993

    
1994
                // initializes the storage
1995
                $this->_pgt_storage->init();
1996
        }
1997

    
1998
        /**
1999
         * This method stores a PGT. Halts on error.
2000
         *
2001
         * @param $pgt the PGT to store
2002
         * @param $pgt_iou its corresponding Iou
2003
         */
2004
        private function storePGT($pgt,$pgt_iou)
2005
        {
2006
                // ensure that storage is initialized
2007
                $this->initPGTStorage();
2008
                // writes the PGT
2009
                $this->_pgt_storage->write($pgt,$pgt_iou);
2010
        }
2011

    
2012
        /**
2013
         * This method reads a PGT from its Iou and deletes the corresponding storage entry.
2014
         *
2015
         * @param $pgt_iou the PGT Iou
2016
         *
2017
         * @return The PGT corresponding to the Iou, FALSE when not found.
2018
         */
2019
        private function loadPGT($pgt_iou)
2020
        {
2021
                // ensure that storage is initialized
2022
                $this->initPGTStorage();
2023
                // read the PGT
2024
                return $this->_pgt_storage->read($pgt_iou);
2025
        }
2026

    
2027
        /**
2028
         * This method can be used to set a custom PGT storage object.
2029
         *
2030
         * @param $storage a PGT storage object that inherits from the CAS_PGTStorage class
2031
         */
2032
        public function setPGTStorage($storage)
2033
        {
2034
                // check that the storage has not already been set
2035
                if ( is_object($this->_pgt_storage) ) {
2036
                        phpCAS::error('PGT storage already defined');
2037
                }
2038

    
2039
                // check to make sure a valid storage object was specified
2040
                if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) {
2041
                        phpCAS::error('Invalid PGT storage object');
2042
                }
2043

    
2044
                // store the PGTStorage object
2045
                $this->_pgt_storage = $storage;
2046
        }
2047

    
2048
        /**
2049
         * This method is used to tell phpCAS to store the response of the
2050
         * CAS server to PGT requests in a database.
2051
         *
2052
         * @param $dsn_or_pdo a dsn string to use for creating a PDO object or a PDO object
2053
         * @param $username the username to use when connecting to the database
2054
         * @param $password the password to use when connecting to the database
2055
         * @param $table the table to use for storing and retrieving PGT's
2056
         * @param $driver_options any driver options to use when connecting to the database
2057
         */
2058
        public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null)
2059
        {
2060
                // create the storage object
2061
                $this->setPGTStorage(new CAS_PGTStorage_Db($this, $dsn_or_pdo, $username, $password, $table, $driver_options));
2062
        }
2063

    
2064
        /**
2065
         * This method is used to tell phpCAS to store the response of the
2066
         * CAS server to PGT requests onto the filesystem.
2067
         *
2068
         * @param $format the format used to store the PGT's (`plain' and `xml' allowed)
2069
         * @param $path the path where the PGT's should be stored
2070
         */
2071
        public function setPGTStorageFile($path='')
2072
        {
2073
                // create the storage object
2074
                $this->setPGTStorage(new CAS_PGTStorage_File($this,$path));
2075
        }
2076

    
2077

    
2078
        // ########################################################################
2079
        //  PGT VALIDATION
2080
        // ########################################################################
2081
        /**
2082
        * This method is used to validate a PGT; halt on failure.
2083
        *
2084
        * @param $validate_url the URL of the request to the CAS server.
2085
        * @param $text_response the response of the CAS server, as is (XML text); result
2086
        * of CAS_Client::validateST() or CAS_Client::validatePT().
2087
        * @param $tree_response the response of the CAS server, as a DOM XML tree; result
2088
        * of CAS_Client::validateST() or CAS_Client::validatePT().
2089
        *
2090
        * @return bool TRUE when successfull, halt otherwise by calling CAS_Client::authError().
2091
        */
2092
        private function validatePGT(&$validate_url,$text_response,$tree_response)
2093
        {
2094
                phpCAS::traceBegin();
2095
                if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
2096
                        phpCAS::trace('<proxyGrantingTicket> not found');
2097
                        // authentication succeded, but no PGT Iou was transmitted
2098
                        $this->authError('Ticket validated but no PGT Iou transmitted',
2099
                        $validate_url,
2100
                        FALSE/*$no_response*/,
2101
                        FALSE/*$bad_response*/,
2102
                        $text_response);
2103
                } else {
2104
                        // PGT Iou transmitted, extract it
2105
                        $pgt_iou = trim($tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue);
2106
                        if(preg_match('/PGTIOU-[\.\-\w]/',$pgt_iou)){
2107
                                $pgt = $this->loadPGT($pgt_iou);
2108
                                if ( $pgt == FALSE ) {
2109
                                        phpCAS::trace('could not load PGT');
2110
                                        $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
2111
                                        $validate_url,
2112
                                        FALSE/*$no_response*/,
2113
                                        FALSE/*$bad_response*/,
2114
                                        $text_response);
2115
                                }
2116
                                $this->setPGT($pgt);
2117
                        }else{
2118
                                phpCAS::trace('PGTiou format error');
2119
                                $this->authError('PGT Iou was transmitted but has wrong fromat',
2120
                                $validate_url,
2121
                                FALSE/*$no_response*/,
2122
                                FALSE/*$bad_response*/,
2123
                                $text_response);
2124
                        }
2125
                }
2126
                phpCAS::traceEnd(TRUE);
2127
                return TRUE;
2128
        }
2129

    
2130
        // ########################################################################
2131
        //  PGT VALIDATION
2132
        // ########################################################################
2133

    
2134
        /**
2135
         * This method is used to retrieve PT's from the CAS server thanks to a PGT.
2136
         *
2137
         * @param $target_service the service to ask for with the PT.
2138
         * @param $err_code an error code (PHPCAS_SERVICE_OK on success).
2139
         * @param $err_msg an error message (empty on success).
2140
         *
2141
         * @return a Proxy Ticket, or FALSE on error.
2142
         */
2143
        public function retrievePT($target_service,&$err_code,&$err_msg)
2144
        {
2145
                phpCAS::traceBegin();
2146

    
2147
                // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
2148
                // set to false and $err_msg to an error message. At the end, if $pt is FALSE
2149
                // and $error_msg is still empty, it is set to 'invalid response' (the most
2150
                // commonly encountered error).
2151
                $err_msg = '';
2152

    
2153
                // build the URL to retrieve the PT
2154
                $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT();
2155

    
2156
                // open and read the URL
2157
                if ( !$this->readURL($cas_url,$headers,$cas_response,$err_msg) ) {
2158
                        phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
2159
                        $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2160
                        $err_msg = 'could not retrieve PT (no response from the CAS server)';
2161
                        phpCAS::traceEnd(FALSE);
2162
                        return FALSE;
2163
                }
2164

    
2165
                $bad_response = FALSE;
2166

    
2167
                if ( !$bad_response ) {
2168
                        // create new DOMDocument object
2169
                        $dom = new DOMDocument();
2170
                        // Fix possible whitspace problems
2171
                        $dom->preserveWhiteSpace = false;
2172
                        // read the response of the CAS server into a DOM object
2173
                        if ( !($dom->loadXML($cas_response))) {
2174
                                phpCAS::trace('dom->loadXML() failed');
2175
                                // read failed
2176
                                $bad_response = TRUE;
2177
                        }
2178
                }
2179

    
2180
                if ( !$bad_response ) {
2181
                        // read the root node of the XML tree
2182
                        if ( !($root = $dom->documentElement) ) {
2183
                                phpCAS::trace('documentElement failed');
2184
                                // read failed
2185
                                $bad_response = TRUE;
2186
                        }
2187
                }
2188

    
2189
                if ( !$bad_response ) {
2190
                        // insure that tag name is 'serviceResponse'
2191
                        if ( $root->localName != 'serviceResponse' ) {
2192
                                phpCAS::trace('localName failed');
2193
                                // bad root node
2194
                                $bad_response = TRUE;
2195
                        }
2196
                }
2197

    
2198
                if ( !$bad_response ) {
2199
                        // look for a proxySuccess tag
2200
                        if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
2201
                                $proxy_success_list = $root->getElementsByTagName("proxySuccess");
2202

    
2203
                                // authentication succeded, look for a proxyTicket tag
2204
                                if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
2205
                                        $err_code = PHPCAS_SERVICE_OK;
2206
                                        $err_msg = '';
2207
                                        $pt = trim($proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue);
2208
                                        phpCAS::trace('original PT: '.trim($pt));
2209
                                        phpCAS::traceEnd($pt);
2210
                                        return $pt;
2211
                                } else {
2212
                                        phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
2213
                                }
2214
                        }
2215
                        // look for a proxyFailure tag
2216
                        else if ( $root->getElementsByTagName("proxyFailure")->length != 0) {
2217
                                $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
2218

    
2219
                                // authentication failed, extract the error
2220
                                $err_code = PHPCAS_SERVICE_PT_FAILURE;
2221
                                $err_msg = 'PT retrieving failed (code=`'
2222
                                .$proxy_failure_list->item(0)->getAttribute('code')
2223
                                .'\', message=`'
2224
                                .trim($proxy_failure_list->item(0)->nodeValue)
2225
                                .'\')';
2226
                                phpCAS::traceEnd(FALSE);
2227
                                return FALSE;
2228
                        } else {
2229
                                phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
2230
                        }
2231
                }
2232

    
2233
                // at this step, we are sure that the response of the CAS server was ill-formed
2234
                $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
2235
                $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
2236

    
2237
                phpCAS::traceEnd(FALSE);
2238
                return FALSE;
2239
        }
2240
        
2241
                /** @} */
2242

    
2243
        // ########################################################################
2244
        // READ CAS SERVER ANSWERS
2245
        // ########################################################################
2246
        
2247
        /**
2248
        * @addtogroup internalMisc
2249
        * @{
2250
        */
2251
        
2252
        /**
2253
         * This method is used to acces a remote URL.
2254
         *
2255
         * @param $url the URL to access.
2256
         * @param $headers an array containing the HTTP header lines of the response
2257
         * (an empty array on failure).
2258
         * @param $body the body of the response, as a string (empty on failure).
2259
         * @param $err_msg an error message, filled on failure.
2260
         *
2261
         * @return TRUE on success, FALSE otherwise (in this later case, $err_msg
2262
         * contains an error message).
2263
         */
2264
        private function readURL($url, &$headers, &$body, &$err_msg)
2265
        {
2266
                $className = $this->_requestImplementation;
2267
                $request = new $className();
2268

    
2269
                if (count($this->_curl_options)) {
2270
                        $request->setCurlOptions($this->_curl_options);
2271
                }
2272

    
2273
                $request->setUrl($url);
2274
                
2275
                if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
2276
                        phpCAS::error('one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
2277
                }
2278
                if ($this->_cas_server_ca_cert != '') {
2279
                        $request->setSslCaCert($this->_cas_server_ca_cert);
2280
                }
2281

    
2282
                // add extra stuff if SAML
2283
                if ($this->hasSA()) {
2284
                        $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
2285
                        $request->addHeader("cache-control: no-cache");
2286
                        $request->addHeader("pragma: no-cache");
2287
                        $request->addHeader("accept: text/xml");
2288
                        $request->addHeader("connection: keep-alive");
2289
                        $request->addHeader("content-type: text/xml");
2290
                        $request->makePost();
2291
                        $request->setPostBody($this->buildSAMLPayload());
2292
                }
2293

    
2294
                if ($request->send()) {
2295
                        $headers = $request->getResponseHeaders();
2296
                        $body = $request->getResponseBody();
2297
                        $err_msg = '';
2298
                        return true;
2299
                } else {
2300
                        $headers = '';
2301
                        $body = '';
2302
                        $err_msg = $request->getErrorMessage();
2303
                        return false;
2304
                }
2305
        }
2306

    
2307
        /**
2308
         * This method is used to build the SAML POST body sent to /samlValidate URL.
2309
         *
2310
         * @return the SOAP-encased SAMLP artifact (the ticket).
2311
         */
2312
        private function buildSAMLPayload()
2313
        {
2314
                phpCAS::traceBegin();
2315

    
2316
                //get the ticket
2317
                $sa = $this->getSA();
2318

    
2319
                $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;
2320

    
2321
                phpCAS::traceEnd($body);
2322
                return ($body);
2323
        }
2324

    
2325
        private  $_curl_headers = array();
2326
        /**
2327
         * This method is the callback used by readURL method to request HTTP headers.
2328
         */
2329
        public function _curl_read_headers($ch, $header)
2330
        {
2331
                $this->_curl_headers[] = $header;
2332
                return strlen($header);
2333
        }
2334
        
2335
        /** @} **/
2336
        
2337
        // ########################################################################
2338
        // ACCESS TO EXTERNAL SERVICES
2339
        // ########################################################################
2340
        
2341
        /**
2342
        * @addtogroup internalProxyServices
2343
        * @{
2344
        */
2345
        
2346
        
2347
        /**
2348
         * Answer a proxy-authenticated service handler.
2349
         * 
2350
         * @param string $type The service type. One of:
2351
         *                        PHPCAS_PROXIED_SERVICE_HTTP_GET
2352
         *                        PHPCAS_PROXIED_SERVICE_HTTP_POST
2353
         *                        PHPCAS_PROXIED_SERVICE_IMAP
2354
         *                        
2355
         *                
2356
         * @return CAS_ProxiedService
2357
         * @throws InvalidArgumentException If the service type is unknown.
2358
         */
2359
        public function getProxiedService ($type) {
2360
                switch ($type) {
2361
                        case PHPCAS_PROXIED_SERVICE_HTTP_GET:
2362
                        case PHPCAS_PROXIED_SERVICE_HTTP_POST:
2363
                                $requestClass = $this->_requestImplementation;
2364
                                $request = new $requestClass();        
2365
                                if (count($this->_curl_options)) {
2366
                                        $request->setCurlOptions($this->_curl_options);
2367
                                }
2368
                                $proxiedService = new $type($request, $this->_serviceCookieJar);
2369
                                if ($proxiedService instanceof CAS_ProxiedService_Testable)
2370
                                        $proxiedService->setCasClient($this);
2371
                                return $proxiedService;
2372
                        case PHPCAS_PROXIED_SERVICE_IMAP;
2373
                                $proxiedService = new CAS_ProxiedService_Imap($this->getUser());
2374
                                if ($proxiedService instanceof CAS_ProxiedService_Testable)
2375
                                        $proxiedService->setCasClient($this);
2376
                                return $proxiedService;
2377
                        default:
2378
                                throw new CAS_InvalidArgumentException("Unknown proxied-service type, $type.");
2379
                }
2380
        }
2381
        
2382
        /**
2383
         * Initialize a proxied-service handler with the proxy-ticket it should use.
2384
         * 
2385
         * @param CAS_ProxiedService $proxiedService
2386
         * @return void
2387
         * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
2388
         *                The code of the Exception will be one of: 
2389
         *                        PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE 
2390
         *                        PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
2391
         *                        PHPCAS_SERVICE_PT_FAILURE
2392
         * @throws CAS_ProxiedService_Exception If there is a failure getting the url from the proxied service.
2393
         */
2394
        public function initializeProxiedService (CAS_ProxiedService $proxiedService) {
2395
                $url = $proxiedService->getServiceUrl();
2396
                if (!is_string($url))
2397
                        throw new CAS_ProxiedService_Exception("Proxied Service ".get_class($proxiedService)."->getServiceUrl() should have returned a string, returned a ".gettype($url)." instead.");
2398
                
2399
                $pt = $this->retrievePT($url, $err_code, $err_msg);
2400
                if (!$pt)
2401
                        throw new CAS_ProxyTicketException($err_msg, $err_code);
2402
                $proxiedService->setProxyTicket($pt);
2403
        }
2404

    
2405
        /**
2406
         * This method is used to access an HTTP[S] service.
2407
         *
2408
         * @param $url the service to access.
2409
         * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
2410
         * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
2411
         * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT_AVAILABLE.
2412
         * @param $output the output of the service (also used to give an error
2413
         * message on failure).
2414
         *
2415
         * @return TRUE on success, FALSE otherwise (in this later case, $err_code
2416
         * gives the reason why it failed and $output contains an error message).
2417
         */
2418
        public function serviceWeb($url,&$err_code,&$output)
2419
        {
2420
                try {
2421
                        $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
2422
                        $service->setUrl($url);
2423
                        $service->send();
2424
                        $output = $service->getResponseBody();
2425
                        $err_code = PHPCAS_SERVICE_OK;
2426
                        return TRUE;
2427
                } catch (CAS_ProxyTicketException $e) {
2428
                        $err_code = $e->getCode();
2429
                        $output = $e->getMessage();
2430
                        return FALSE;
2431
                } catch (CAS_ProxiedService_Exception $e) {
2432
                        $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), $url, $e->getMessage());
2433
                        $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2434
                        return FALSE;
2435
                }
2436
        }
2437

    
2438
        /**
2439
         * This method is used to access an IMAP/POP3/NNTP service.
2440
         *
2441
         * @param $url a string giving the URL of the service, including the mailing box
2442
         * for IMAP URLs, as accepted by imap_open().
2443
         * @param $service a string giving for CAS retrieve Proxy ticket
2444
         * @param $flags options given to imap_open().
2445
         * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
2446
         * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
2447
         * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT_AVAILABLE.
2448
         * @param $err_msg an error message on failure
2449
         * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL
2450
         * on success, FALSE on error).
2451
         *
2452
         * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code
2453
         * gives the reason why it failed and $err_msg contains an error message).
2454
         */
2455
        public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
2456
        {
2457
                try {
2458
                        $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
2459
                        $service->setServiceUrl($serviceUrl);
2460
                        $service->setMailbox($url);
2461
                        $service->setOptions($flags);
2462
                        
2463
                        $stream = $service->open();
2464
                        $err_code = PHPCAS_SERVICE_OK;
2465
                        $pt = $service->getImapProxyTicket();
2466
                        return $stream;
2467
                } catch (CAS_ProxyTicketException $e) {
2468
                        $err_msg = $e->getMessage();
2469
                        $err_code = $e->getCode();
2470
                        $pt = FALSE;
2471
                        return FALSE;
2472
                } catch (CAS_ProxiedService_Exception $e) {
2473
                        $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), $url, $e->getMessage());
2474
                        $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2475
                        $pt = FALSE;
2476
                        return FALSE;
2477
                }
2478
        }
2479

    
2480
        /** @} **/
2481

    
2482
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2483
        // XX                                                                    XX
2484
        // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
2485
        // XX                                                                    XX
2486
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2487

    
2488
        // ########################################################################
2489
        //  PT
2490
        // ########################################################################
2491
        /**
2492
        * @addtogroup internalProxied
2493
        * @{
2494
        */
2495

    
2496
        /**
2497
         * the Proxy Ticket provided in the URL of the request if present
2498
         * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
2499
         * CAS_Client::getPT() and CAS_Client::hasPGT().
2500
         *
2501
         * @hideinitializer
2502
         */
2503
        private  $_pt = '';
2504

    
2505
        /**
2506
         * This method returns the Proxy Ticket provided in the URL of the request.
2507
         * @return The proxy ticket.
2508
         */
2509
        public function getPT()
2510
        {
2511
                //      return 'ST'.substr($this->_pt, 2);
2512
                return $this->_pt;
2513
        }
2514

    
2515
        /**
2516
         * This method stores the Proxy Ticket.
2517
         * @param $pt The Proxy Ticket.
2518
         */
2519
        public function setPT($pt)
2520
        { $this->_pt = $pt; }
2521

    
2522
        /**
2523
         * This method tells if a Proxy Ticket was stored.
2524
         * @return TRUE if a Proxy Ticket has been stored.
2525
         */
2526
        public function hasPT()
2527
        { return !empty($this->_pt); }
2528
        
2529
        
2530
        /**
2531
         * This array will store a list of proxies in front of this application. This
2532
         * property will only be populated if this script is being proxied rather than
2533
         * accessed directly.
2534
         *
2535
         * It is set in CAS_Client::validatePT() and can be read by CAS_Client::getProxies()
2536
         * @access private
2537
         */
2538
        private $_proxies = array();
2539
        
2540
        /**
2541
         * Answer an array of proxies that are sitting in front of this application.
2542
         *
2543
         * This method will only return a non-empty array if we have received and validated
2544
         * a Proxy Ticket.
2545
         * 
2546
         * @return array
2547
         * @access public
2548
         * @since 6/25/09
2549
         */
2550
        public function getProxies () {
2551
                return $this->_proxies;
2552
        }
2553
        
2554
        /**
2555
         * Set the Proxy array, probably from persistant storage.
2556
         * 
2557
         * @param array $proxies
2558
         * @return void
2559
         * @access private
2560
         * @since 6/25/09
2561
         */
2562
        private function setProxies ($proxies) {
2563
                $this->_proxies = $proxies;
2564
        }
2565
        
2566

    
2567
        /** @} */
2568
        // ########################################################################
2569
        //  PT VALIDATION
2570
        // ########################################################################
2571
        /**
2572
        * @addtogroup internalProxied
2573
        * @{
2574
        */
2575

    
2576
        /**
2577
         * This method is used to validate a ST or PT; halt on failure
2578
         * Used for all CAS 2.0 validations
2579
         * @return bool TRUE when successfull, halt otherwise by calling CAS_Client::authError().
2580
         */
2581
        public function validatePT(&$validate_url,&$text_response,&$tree_response)
2582
        {
2583
                phpCAS::traceBegin();
2584
                phpCAS::trace($text_response);
2585
                // build the URL to validate the ticket
2586
                $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
2587

    
2588
                if ( $this->isProxy() ) {
2589
                        // pass the callback url for CAS proxies
2590
                        $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
2591
                }
2592

    
2593
                // open and read the URL
2594
                if ( !$this->readURL($validate_url,$headers,$text_response,$err_msg) ) {
2595
                        phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
2596
                        $this->authError('PT not validated',
2597
                        $validate_url,
2598
                        TRUE/*$no_response*/);
2599
                }
2600

    
2601
                // create new DOMDocument object
2602
                $dom = new DOMDocument();
2603
                // Fix possible whitspace problems
2604
                $dom->preserveWhiteSpace = false;
2605
                // read the response of the CAS server into a DOMDocument object
2606
                if ( !($dom->loadXML($text_response))) {
2607
                        // read failed
2608
                        $this->authError('PT not validated',
2609
                        $validate_url,
2610
                        FALSE/*$no_response*/,
2611
                        TRUE/*$bad_response*/,
2612
                        $text_response);
2613
                }
2614

    
2615
                // read the root node of the XML tree
2616
                if ( !($tree_response = $dom->documentElement) ) {
2617
                        // read failed
2618
                        $this->authError('PT not validated',
2619
                        $validate_url,
2620
                        FALSE/*$no_response*/,
2621
                        TRUE/*$bad_response*/,
2622
                        $text_response);
2623
                }
2624
                // insure that tag name is 'serviceResponse'
2625
                if ( $tree_response->localName != 'serviceResponse' ) {
2626
                        // bad root node
2627
                        $this->authError('PT not validated',
2628
                        $validate_url,
2629
                        FALSE/*$no_response*/,
2630
                        TRUE/*$bad_response*/,
2631
                        $text_response);
2632
                }
2633
                if ( $tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
2634
                        // authentication succeded, extract the user name
2635
                        $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
2636
                        if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
2637
                                // no user specified => error
2638
                                $this->authError('PT not validated',
2639
                                $validate_url,
2640
                                FALSE/*$no_response*/,
2641
                                TRUE/*$bad_response*/,
2642
                                $text_response);
2643
                        }
2644

    
2645
                        $this->setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue));
2646
                        $this->readExtraAttributesCas20($success_elements);
2647
                        
2648
                        // Store the proxies we are sitting behind for authorization checking
2649
                        if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
2650
                                foreach ($arr as $proxyElem) {
2651
                                        phpCAS::trace("Storing Proxy: ".$proxyElem->nodeValue);
2652
                                        $this->_proxies[] = trim($proxyElem->nodeValue);
2653
                                }
2654
                                $_SESSION['phpCAS']['proxies'] = $this->_proxies;
2655
                        }
2656
                        
2657
                } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
2658
                        // authentication succeded, extract the error code and message
2659
                        $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
2660
                        $this->authError('PT not validated',
2661
                        $validate_url,
2662
                        FALSE/*$no_response*/,
2663
                        FALSE/*$bad_response*/,
2664
                        $text_response,
2665
                        $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
2666
                        trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/);
2667
                } else {
2668
                        $this->authError('PT not validated',
2669
                        $validate_url,
2670
                        FALSE/*$no_response*/,
2671
                        TRUE/*$bad_response*/,
2672
                        $text_response);
2673
                }
2674

    
2675
                $this->renameSession($this->getPT());
2676
                // at this step, PT has been validated and $this->_user has been set,
2677

    
2678
                phpCAS::traceEnd(TRUE);
2679
                return TRUE;
2680
        }
2681
        
2682
        
2683
        /**
2684
         * This method will parse the DOM and pull out the attributes from the XML
2685
         * payload and put them into an array, then put the array into the session.
2686
         *
2687
         * @param $text_response the XML payload.
2688
         * @return bool TRUE when successfull, halt otherwise by calling CAS_Client::authError().
2689
         */
2690
        private function readExtraAttributesCas20($success_elements)
2691
        {
2692
                # PHPCAS-43 add CAS-2.0 extra attributes
2693
                phpCAS::traceBegin();
2694

    
2695
                $extra_attributes = array();
2696
                
2697
                // "Jasig Style" Attributes:
2698
                // 
2699
                //         <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2700
                //                 <cas:authenticationSuccess>
2701
                //                         <cas:user>jsmith</cas:user>
2702
                //                         <cas:attributes>
2703
                //                                 <cas:attraStyle>RubyCAS</cas:attraStyle>
2704
                //                                 <cas:surname>Smith</cas:surname>
2705
                //                                 <cas:givenName>John</cas:givenName>
2706
                //                                 <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
2707
                //                                 <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
2708
                //                         </cas:attributes>
2709
                //                         <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
2710
                //                 </cas:authenticationSuccess>
2711
                //         </cas:serviceResponse>
2712
                // 
2713
                if ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
2714
                        $attr_nodes = $success_elements->item(0)->getElementsByTagName("attributes");
2715
                        phpCas :: trace("Found nested jasig style attributes");
2716
                        if($attr_nodes->item(0)->hasChildNodes()){
2717
                                // Nested Attributes
2718
                                foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
2719
                                        phpCas :: trace("Attribute [".$attr_child->localName."] = ".$attr_child->nodeValue);
2720
                                        $this->addAttributeToArray($extra_attributes, $attr_child->localName, $attr_child->nodeValue);
2721
                                }
2722
                        }
2723
                } 
2724
                // "RubyCAS Style" attributes
2725
                // 
2726
                //         <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2727
                //                 <cas:authenticationSuccess>
2728
                //                         <cas:user>jsmith</cas:user>
2729
                //                         
2730
                //                         <cas:attraStyle>RubyCAS</cas:attraStyle>
2731
                //                         <cas:surname>Smith</cas:surname>
2732
                //                         <cas:givenName>John</cas:givenName>
2733
                //                         <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
2734
                //                         <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
2735
                //                         
2736
                //                         <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
2737
                //                 </cas:authenticationSuccess>
2738
                //         </cas:serviceResponse>
2739
                // 
2740
                else {
2741
                        phpCas :: trace("Testing for rubycas style attributes");
2742
                        $childnodes = $success_elements->item(0)->childNodes;
2743
                        foreach ($childnodes as $attr_node) {
2744
                                switch ($attr_node->localName) {
2745
                                        case 'user':
2746
                                        case 'proxies':
2747
                                        case 'proxyGrantingTicket':
2748
                                                continue;
2749
                                        default:
2750
                                                if (strlen(trim($attr_node->nodeValue))) {
2751
                                                        phpCas :: trace("Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue);
2752
                                                        $this->addAttributeToArray($extra_attributes, $attr_node->localName, $attr_node->nodeValue);
2753
                                                }
2754
                                }
2755
                        }
2756
                }
2757
                
2758
                // "Name-Value" attributes.
2759
                // 
2760
                // Attribute format from these mailing list thread:
2761
                // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
2762
                // Note: This is a less widely used format, but in use by at least two institutions.
2763
                // 
2764
                //         <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2765
                //                 <cas:authenticationSuccess>
2766
                //                         <cas:user>jsmith</cas:user>
2767
                //                         
2768
                //                         <cas:attribute name='attraStyle' value='Name-Value' />
2769
                //                         <cas:attribute name='surname' value='Smith' />
2770
                //                         <cas:attribute name='givenName' value='John' />
2771
                //                         <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
2772
                //                         <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
2773
                //                         
2774
                //                         <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
2775
                //                 </cas:authenticationSuccess>
2776
                //         </cas:serviceResponse>
2777
                // 
2778
                if (!count($extra_attributes) && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0) {
2779
                        $attr_nodes = $success_elements->item(0)->getElementsByTagName("attribute");
2780
                        $firstAttr = $attr_nodes->item(0);
2781
                        if (!$firstAttr->hasChildNodes() && $firstAttr->hasAttribute('name') && $firstAttr->hasAttribute('value')) {
2782
                                phpCas :: trace("Found Name-Value style attributes");
2783
                                // Nested Attributes
2784
                                foreach ($attr_nodes as $attr_node) {
2785
                                        if ($attr_node->hasAttribute('name') && $attr_node->hasAttribute('value')) {
2786
                                                phpCas :: trace("Attribute [".$attr_node->getAttribute('name')."] = ".$attr_node->getAttribute('value'));
2787
                                                $this->addAttributeToArray($extra_attributes, $attr_node->getAttribute('name'), $attr_node->getAttribute('value'));
2788
                                        }
2789
                                }
2790
                        }
2791
                }
2792
                
2793
                $this->setAttributes($extra_attributes);
2794
                phpCAS::traceEnd();
2795
                return TRUE;
2796
        }
2797
        
2798
        /**
2799
         * Add an attribute value to an array of attributes.
2800
         * 
2801
         * @param ref array $array
2802
         * @param string $name
2803
         * @param string $value
2804
         * @return void
2805
         */
2806
        private function addAttributeToArray (array &$attributeArray, $name, $value) {
2807
                // If multiple attributes exist, add as an array value
2808
                if (isset($attributeArray[$name])) {
2809
                        // Initialize the array with the existing value
2810
                        if (!is_array($attributeArray[$name])) {
2811
                                $existingValue = $attributeArray[$name];
2812
                                $attributeArray[$name] = array($existingValue);
2813
                        }
2814
                        
2815
                        $attributeArray[$name][] = trim($value);
2816
                } else {
2817
                        $attributeArray[$name] = trim($value);
2818
                }
2819
        }
2820

    
2821
        /** @} */
2822

    
2823
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2824
        // XX                                                                    XX
2825
        // XX                               MISC                                 XX
2826
        // XX                                                                    XX
2827
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2828

    
2829
        /**
2830
         * @addtogroup internalMisc
2831
         * @{
2832
         */
2833

    
2834
        // ########################################################################
2835
        //  URL
2836
        // ########################################################################
2837
        /**
2838
        * the URL of the current request (without any ticket CGI parameter). Written
2839
        * and read by CAS_Client::getURL().
2840
        *
2841
        * @hideinitializer
2842
        */
2843
        private $_url = '';
2844

    
2845
        
2846
        /**
2847
         * This method sets the URL of the current request
2848
         *
2849
         * @param $url url to set for service
2850
         */
2851
        public function setURL($url)
2852
        {
2853
                $this->_url = $url;
2854
        }
2855
        
2856
        /**
2857
         * This method returns the URL of the current request (without any ticket
2858
         * CGI parameter).
2859
         *
2860
         * @return The URL
2861
         */
2862
        public function getURL()
2863
        {
2864
                phpCAS::traceBegin();
2865
                // the URL is built when needed only
2866
                if ( empty($this->_url) ) {
2867
                        $final_uri = '';
2868
                        // remove the ticket if present in the URL
2869
                        $final_uri = ($this->isHttps()) ? 'https' : 'http';
2870
                        $final_uri .= '://';
2871

    
2872
                        $final_uri .= $this->getServerUrl();
2873
                        $request_uri        = explode('?', $_SERVER['REQUEST_URI'], 2);
2874
                        $final_uri                .= $request_uri[0];
2875
                                
2876
                        if (isset($request_uri[1]) && $request_uri[1])
2877
                        {
2878
                                $query_string        = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
2879

    
2880
                                // If the query string still has anything left, append it to the final URI
2881
                                if ($query_string !== '')
2882
                                $final_uri        .= "?$query_string";
2883

    
2884
                        }
2885
                                
2886
                        phpCAS::trace("Final URI: $final_uri");
2887
                        $this->setURL($final_uri);
2888
                }
2889
                phpCAS::traceEnd($this->_url);
2890
                return $this->_url;
2891
        }
2892
        
2893

    
2894
        /**
2895
         * Try to figure out the server URL with possible Proxys / Ports etc.
2896
         * @return Server URL with domain:port
2897
         */
2898
        private function getServerUrl(){
2899
                $server_url = '';
2900
                if(!empty($_SERVER['HTTP_X_FORWARDED_HOST'])){
2901
                        // explode the host list separated by comma and use the first host
2902
                        $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
2903
                        $server_url = $hosts[0];
2904
                }else if(!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
2905
                        $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
2906
                }else{
2907
                        if (empty($_SERVER['SERVER_NAME'])) {
2908
                                $server_url = $_SERVER['HTTP_HOST'];
2909
                        } else {
2910
                                $server_url = $_SERVER['SERVER_NAME'];
2911
                        }
2912
                }
2913
                if (!strpos($server_url, ':')) {
2914
                        if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
2915
                        || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
2916
                                $server_url .= ':';
2917
                                $server_url .= $_SERVER['SERVER_PORT'];
2918
                        }
2919
                }
2920
                return $server_url;
2921
        }
2922

    
2923
        /**
2924
         * This method checks to see if the request is secured via HTTPS
2925
         * @return true if https, false otherwise
2926
         */
2927
        private function isHttps() {
2928
                if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
2929
                        return true;
2930
                } else {
2931
                        return false;
2932
                }
2933
        }
2934

    
2935
        /**
2936
         * Removes a parameter from a query string
2937
         *
2938
         * @param string $parameterName
2939
         * @param string $queryString
2940
         * @return string
2941
         *
2942
         * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
2943
         */
2944
        private function removeParameterFromQueryString($parameterName, $queryString)
2945
        {
2946
                $parameterName        = preg_quote($parameterName);
2947
                return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
2948
        }
2949
        
2950
        /**
2951
         * This method is used to append query parameters to an url. Since the url
2952
         * might already contain parameter it has to be detected and to build a proper
2953
         * URL
2954
         * @param $url  base url to add the query params to
2955
         * @param $query params in query form with & separated
2956
         * @return url with query params 
2957
         */
2958
        private function buildQueryUrl($url, $query) {
2959
                $url .= (strstr($url,'?') === FALSE) ? '?' : '&';
2960
                $url .= $query;
2961
                return $url;
2962
        }
2963
        
2964
        /**
2965
         * Renaming the session
2966
         */
2967
        private function renameSession($ticket)
2968
        {
2969
                phpCAS::traceBegin();
2970
                if($this->_start_session){
2971
                        if (!empty ($this->_user))
2972
                        {
2973
                                $old_session = $_SESSION;
2974
                                session_destroy();
2975
                                // set up a new session, of name based on the ticket
2976
                                $session_id = preg_replace('/[^\w]/', '', $ticket);
2977
                                phpCAS :: trace("Session ID: ".$session_id);
2978
                                session_id($session_id);
2979
                                session_start();
2980
                                phpCAS :: trace("Restoring old session vars");
2981
                                $_SESSION = $old_session;
2982
                        } else
2983
                        {
2984
                                phpCAS :: error('Session should only be renamed after successfull authentication');
2985
                        }
2986
                }else{
2987
                        phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
2988
                }
2989
                phpCAS::traceEnd();
2990
        }
2991

    
2992
        
2993
        // ########################################################################
2994
        //  AUTHENTICATION ERROR HANDLING
2995
        // ########################################################################
2996
        /**
2997
        * This method is used to print the HTML output when the user was not authenticated.
2998
        *
2999
        * @param $failure the failure that occured
3000
        * @param $cas_url the URL the CAS server was asked for
3001
        * @param $no_response the response from the CAS server (other
3002
        * parameters are ignored if TRUE)
3003
        * @param $bad_response bad response from the CAS server ($err_code
3004
        * and $err_msg ignored if TRUE)
3005
        * @param $cas_response the response of the CAS server
3006
        * @param $err_code the error code given by the CAS server
3007
        * @param $err_msg the error message given by the CAS server
3008
        */
3009
        private function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
3010
        {
3011
                phpCAS::traceBegin();
3012

    
3013
                $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
3014
                printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
3015
                phpCAS::trace('CAS URL: '.$cas_url);
3016
                phpCAS::trace('Authentication failure: '.$failure);
3017
                if ( $no_response ) {
3018
                        phpCAS::trace('Reason: no response from the CAS server');
3019
                } else {
3020
                        if ( $bad_response ) {
3021
                                phpCAS::trace('Reason: bad response from the CAS server');
3022
                        } else {
3023
                                switch ($this->getServerVersion()) {
3024
                                        case CAS_VERSION_1_0:
3025
                                                phpCAS::trace('Reason: CAS error');
3026
                                                break;
3027
                                        case CAS_VERSION_2_0:
3028
                                                if ( empty($err_code) )
3029
                                                phpCAS::trace('Reason: no CAS error');
3030
                                                else
3031
                                                phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
3032
                                                break;
3033
                                }
3034
                        }
3035
                        phpCAS::trace('CAS response: '.$cas_response);
3036
                }
3037
                $this->printHTMLFooter();
3038
                phpCAS::traceExit();
3039

    
3040
                if ($this->_exitOnAuthError)
3041
                exit();
3042
        }
3043

    
3044
        /** @} */
3045
}
3046

    
3047
?>