source-class-CAS_Client

It appears that you are using AdBlocking software. The cost of running this website is covered by advertisements. If you like it please feel free to a small amount of money to secure the future of this website.
Overview

Classes

Interfaces

Exceptions

Functions

   1: <?php
   2: 
   3: /**
   4:  * Licensed to Jasig under one or more contributor license
   5:  * agreements. See the NOTICE file distributed with this work for
   6:  * additional information regarding copyright ownership.
   7:  *
   8:  * Jasig licenses this file to you under the Apache License,
   9:  * Version 2.0 (the "License"); you may not use this file except in
  10:  * compliance with the License. You may obtain a copy of the License at:
  11:  *
  12:  * http://www.apache.org/licenses/LICENSE-2.0
  13:  *
  14:  * Unless required by applicable law or agreed to in writing, software
  15:  * distributed under the License is distributed on an "AS IS" BASIS,
  16:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17:  * See the License for the specific language governing permissions and
  18:  * limitations under the License.
  19:  *
  20:  * PHP Version 5
  21:  *
  22:  * @file     CAS/Client.php
  23:  * @category Authentication
  24:  * @package  PhpCAS
  25:  * @author   Pascal Aubry <pascal.aubry@univ-rennes1.fr>
  26:  * @author   Olivier Berger <olivier.berger@it-sudparis.eu>
  27:  * @author   Brett Bieber <brett.bieber@gmail.com>
  28:  * @author   Joachim Fritschi <jfritschi@freenet.de>
  29:  * @author   Adam Franco <afranco@middlebury.edu>
  30:  * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
  31:  * @link     https://wiki.jasig.org/display/CASC/phpCAS
  32:  */
  33: 
  34: /**
  35:  * The CAS_Client class is a client interface that provides CAS authentication
  36:  * to PHP applications.
  37:  *
  38:  * @class    CAS_Client
  39:  * @category Authentication
  40:  * @package  PhpCAS
  41:  * @author   Pascal Aubry <pascal.aubry@univ-rennes1.fr>
  42:  * @author   Olivier Berger <olivier.berger@it-sudparis.eu>
  43:  * @author   Brett Bieber <brett.bieber@gmail.com>
  44:  * @author   Joachim Fritschi <jfritschi@freenet.de>
  45:  * @author   Adam Franco <afranco@middlebury.edu>
  46:  * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
  47:  * @link     https://wiki.jasig.org/display/CASC/phpCAS
  48:  *
  49:  */
  50: 
  51: class CAS_Client
  52: {
  53: 
  54:     // ########################################################################
  55:     //  HTML OUTPUT
  56:     // ########################################################################
  57:     /**
  58:     * @addtogroup internalOutput
  59:     * @{
  60:     */
  61: 
  62:     /**
  63:      * This method filters a string by replacing special tokens by appropriate values
  64:      * and prints it. The corresponding tokens are taken into account:
  65:      * - __CAS_VERSION__
  66:      * - __PHPCAS_VERSION__
  67:      * - __SERVER_BASE_URL__
  68:      *
  69:      * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter().
  70:      *
  71:      * @param string $str the string to filter and output
  72:      *
  73:      * @return void
  74:      */
  75:     private function _htmlFilterOutput($str)
  76:     {
  77:         $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str);
  78:         $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str);
  79:         $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str);
  80:         echo $str;
  81:     }
  82: 
  83:     /**
  84:      * A string used to print the header of HTML pages. Written by
  85:      * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader().
  86:      *
  87:      * @hideinitializer
  88:      * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader()
  89:      */
  90:     private $_output_header = '';
  91: 
  92:     /**
  93:      * This method prints the header of the HTML output (after filtering). If
  94:      * CAS_Client::setHTMLHeader() was not used, a default header is output.
  95:      *
  96:      * @param string $title the title of the page
  97:      *
  98:      * @return void
  99:      * @see _htmlFilterOutput()
 100:      */
 101:     public function printHTMLHeader($title)
 102:     {
 103:         $this->_htmlFilterOutput(
 104:             str_replace(
 105:                 '__TITLE__', $title,
 106:                 (empty($this->_output_header)
 107:                 ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
 108:                 : $this->_output_header)
 109:             )
 110:         );
 111:     }
 112: 
 113:     /**
 114:      * A string used to print the footer of HTML pages. Written by
 115:      * CAS_Client::setHTMLFooter(), read by printHTMLFooter().
 116:      *
 117:      * @hideinitializer
 118:      * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter()
 119:      */
 120:     private $_output_footer = '';
 121: 
 122:     /**
 123:      * This method prints the footer of the HTML output (after filtering). If
 124:      * CAS_Client::setHTMLFooter() was not used, a default footer is output.
 125:      *
 126:      * @return void
 127:      * @see _htmlFilterOutput()
 128:      */
 129:     public function printHTMLFooter()
 130:     {
 131:         $lang = $this->getLangObj();
 132:         $this->_htmlFilterOutput(
 133:             empty($this->_output_footer)?
 134:             (phpcas::getVerbose())?
 135:                 '<hr><address>phpCAS __PHPCAS_VERSION__ '
 136:                 .$lang->getUsingServer()
 137:                 .' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>'
 138:                 :'</body></html>'
 139:             :$this->_output_footer
 140:         );
 141:     }
 142: 
 143:     /**
 144:      * This method set the HTML header used for all outputs.
 145:      *
 146:      * @param string $header the HTML header.
 147:      *
 148:      * @return void
 149:      */
 150:     public function setHTMLHeader($header)
 151:     {
 152:         // Argument Validation
 153:         if (gettype($header) != 'string')
 154:             throw new CAS_TypeMismatchException($header, '$header', 'string');
 155: 
 156:         $this->_output_header = $header;
 157:     }
 158: 
 159:     /**
 160:      * This method set the HTML footer used for all outputs.
 161:      *
 162:      * @param string $footer the HTML footer.
 163:      *
 164:      * @return void
 165:      */
 166:     public function setHTMLFooter($footer)
 167:     {
 168:         // Argument Validation
 169:         if (gettype($footer) != 'string')
 170:             throw new CAS_TypeMismatchException($footer, '$footer', 'string');
 171: 
 172:         $this->_output_footer = $footer;
 173:     }
 174: 
 175: 
 176:     /** @} */
 177: 
 178: 
 179:     // ########################################################################
 180:     //  INTERNATIONALIZATION
 181:     // ########################################################################
 182:     /**
 183:     * @addtogroup internalLang
 184:     * @{
 185:     */
 186:     /**
 187:      * A string corresponding to the language used by phpCAS. Written by
 188:      * CAS_Client::setLang(), read by CAS_Client::getLang().
 189: 
 190:      * @note debugging information is always in english (debug purposes only).
 191:      */
 192:     private $_lang = PHPCAS_LANG_DEFAULT;
 193: 
 194:     /**
 195:      * This method is used to set the language used by phpCAS.
 196:      *
 197:      * @param string $lang representing the language.
 198:      *
 199:      * @return void
 200:      */
 201:     public function setLang($lang)
 202:     {
 203:         // Argument Validation
 204:         if (gettype($lang) != 'string')
 205:             throw new CAS_TypeMismatchException($lang, '$lang', 'string');
 206: 
 207:         phpCAS::traceBegin();
 208:         $obj = new $lang();
 209:         if (!($obj instanceof CAS_Languages_LanguageInterface)) {
 210:             throw new CAS_InvalidArgumentException(
 211:                 '$className must implement the CAS_Languages_LanguageInterface'
 212:             );
 213:         }
 214:         $this->_lang = $lang;
 215:         phpCAS::traceEnd();
 216:     }
 217:     /**
 218:      * Create the language
 219:      *
 220:      * @return CAS_Languages_LanguageInterface object implementing the class
 221:      */
 222:     public function getLangObj()
 223:     {
 224:         $classname = $this->_lang;
 225:         return new $classname();
 226:     }
 227: 
 228:     /** @} */
 229:     // ########################################################################
 230:     //  CAS SERVER CONFIG
 231:     // ########################################################################
 232:     /**
 233:     * @addtogroup internalConfig
 234:     * @{
 235:     */
 236: 
 237:     /**
 238:      * a record to store information about the CAS server.
 239:      * - $_server['version']: the version of the CAS server
 240:      * - $_server['hostname']: the hostname of the CAS server
 241:      * - $_server['port']: the port the CAS server is running on
 242:      * - $_server['uri']: the base URI the CAS server is responding on
 243:      * - $_server['base_url']: the base URL of the CAS server
 244:      * - $_server['login_url']: the login URL of the CAS server
 245:      * - $_server['service_validate_url']: the service validating URL of the
 246:      *   CAS server
 247:      * - $_server['proxy_url']: the proxy URL of the CAS server
 248:      * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server
 249:      * - $_server['logout_url']: the logout URL of the CAS server
 250:      *
 251:      * $_server['version'], $_server['hostname'], $_server['port'] and
 252:      * $_server['uri'] are written by CAS_Client::CAS_Client(), read by
 253:      * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(),
 254:      * CAS_Client::_getServerPort() and CAS_Client::_getServerURI().
 255:      *
 256:      * The other fields are written and read by CAS_Client::_getServerBaseURL(),
 257:      * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(),
 258:      * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL().
 259:      *
 260:      * @hideinitializer
 261:      */
 262:     private $_server = array(
 263:         'version' => -1,
 264:         'hostname' => 'none',
 265:         'port' => -1,
 266:         'uri' => 'none');
 267: 
 268:     /**
 269:      * This method is used to retrieve the version of the CAS server.
 270:      *
 271:      * @return string the version of the CAS server.
 272:      */
 273:     public function getServerVersion()
 274:     {
 275:         return $this->_server['version'];
 276:     }
 277: 
 278:     /**
 279:      * This method is used to retrieve the hostname of the CAS server.
 280:      *
 281:      * @return string the hostname of the CAS server.
 282:      */
 283:     private function _getServerHostname()
 284:     {
 285:         return $this->_server['hostname'];
 286:     }
 287: 
 288:     /**
 289:      * This method is used to retrieve the port of the CAS server.
 290:      *
 291:      * @return string the port of the CAS server.
 292:      */
 293:     private function _getServerPort()
 294:     {
 295:         return $this->_server['port'];
 296:     }
 297: 
 298:     /**
 299:      * This method is used to retrieve the URI of the CAS server.
 300:      *
 301:      * @return string a URI.
 302:      */
 303:     private function _getServerURI()
 304:     {
 305:         return $this->_server['uri'];
 306:     }
 307: 
 308:     /**
 309:      * This method is used to retrieve the base URL of the CAS server.
 310:      *
 311:      * @return string a URL.
 312:      */
 313:     private function _getServerBaseURL()
 314:     {
 315:         // the URL is build only when needed
 316:         if ( empty($this->_server['base_url']) ) {
 317:             $this->_server['base_url'] = 'https://' . $this->_getServerHostname();
 318:             if ($this->_getServerPort()!=443) {
 319:                 $this->_server['base_url'] .= ':'
 320:                 .$this->_getServerPort();
 321:             }
 322:             $this->_server['base_url'] .= $this->_getServerURI();
 323:         }
 324:         return $this->_server['base_url'];
 325:     }
 326: 
 327:     /**
 328:      * This method is used to retrieve the login URL of the CAS server.
 329:      *
 330:      * @param bool $gateway true to check authentication, false to force it
 331:      * @param bool $renew   true to force the authentication with the CAS server
 332:      *
 333:      * @return a URL.
 334:      * @note It is recommended that CAS implementations ignore the "gateway"
 335:      * parameter if "renew" is set
 336:      */
 337:     public function getServerLoginURL($gateway=false,$renew=false)
 338:     {
 339:         phpCAS::traceBegin();
 340:         // the URL is build only when needed
 341:         if ( empty($this->_server['login_url']) ) {
 342:             $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL()));
 343:         }
 344:         $url = $this->_server['login_url'];
 345:         if ($renew) {
 346:             // It is recommended that when the "renew" parameter is set, its
 347:             // value be "true"
 348:             $url = $this->_buildQueryUrl($url, 'renew=true');
 349:         } elseif ($gateway) {
 350:             // It is recommended that when the "gateway" parameter is set, its
 351:             // value be "true"
 352:             $url = $this->_buildQueryUrl($url, 'gateway=true');
 353:         }
 354:         phpCAS::traceEnd($url);
 355:         return $url;
 356:     }
 357: 
 358:     /**
 359:      * This method sets the login URL of the CAS server.
 360:      *
 361:      * @param string $url the login URL
 362:      *
 363:      * @return string login url
 364:      */
 365:     public function setServerLoginURL($url)
 366:     {
 367:         // Argument Validation
 368:         if (gettype($url) != 'string')
 369:             throw new CAS_TypeMismatchException($url, '$url', 'string');
 370: 
 371:         return $this->_server['login_url'] = $url;
 372:     }
 373: 
 374: 
 375:     /**
 376:      * This method sets the serviceValidate URL of the CAS server.
 377:      *
 378:      * @param string $url the serviceValidate URL
 379:      *
 380:      * @return string serviceValidate URL
 381:      */
 382:     public function setServerServiceValidateURL($url)
 383:     {
 384:         // Argument Validation
 385:         if (gettype($url) != 'string')
 386:             throw new CAS_TypeMismatchException($url, '$url', 'string');
 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:      *
 395:      * @param string $url the proxyValidate URL
 396:      *
 397:      * @return string proxyValidate URL
 398:      */
 399:     public function setServerProxyValidateURL($url)
 400:     {
 401:         // Argument Validation
 402:         if (gettype($url) != 'string')
 403:             throw new CAS_TypeMismatchException($url, '$url', 'string');
 404: 
 405:         return $this->_server['proxy_validate_url'] = $url;
 406:     }
 407: 
 408: 
 409:     /**
 410:      * This method sets the samlValidate URL of the CAS server.
 411:      *
 412:      * @param string $url the samlValidate URL
 413:      *
 414:      * @return string samlValidate URL
 415:      */
 416:     public function setServerSamlValidateURL($url)
 417:     {
 418:         // Argument Validation
 419:         if (gettype($url) != 'string')
 420:             throw new CAS_TypeMismatchException($url, '$url', 'string');
 421: 
 422:         return $this->_server['saml_validate_url'] = $url;
 423:     }
 424: 
 425: 
 426:     /**
 427:      * This method is used to retrieve the service validating URL of the CAS server.
 428:      *
 429:      * @return string serviceValidate URL.
 430:      */
 431:     public function getServerServiceValidateURL()
 432:     {
 433:         phpCAS::traceBegin();
 434:         // the URL is build only when needed
 435:         if ( empty($this->_server['service_validate_url']) ) {
 436:             switch ($this->getServerVersion()) {
 437:             case CAS_VERSION_1_0:
 438:                 $this->_server['service_validate_url'] = $this->_getServerBaseURL()
 439:                 .'validate';
 440:                 break;
 441:             case CAS_VERSION_2_0:
 442:                 $this->_server['service_validate_url'] = $this->_getServerBaseURL()
 443:                 .'serviceValidate';
 444:                 break;
 445:             case CAS_VERSION_3_0:
 446:                 $this->_server['service_validate_url'] = $this->_getServerBaseURL()
 447:                 .'p3/serviceValidate';
 448:                 break;
 449:             }
 450:         }
 451:         $url = $this->_buildQueryUrl(
 452:             $this->_server['service_validate_url'],
 453:             'service='.urlencode($this->getURL())
 454:         );
 455:         phpCAS::traceEnd($url);
 456:         return $url;
 457:     }
 458:     /**
 459:      * This method is used to retrieve the SAML validating URL of the CAS server.
 460:      *
 461:      * @return string samlValidate URL.
 462:      */
 463:     public function getServerSamlValidateURL()
 464:     {
 465:         phpCAS::traceBegin();
 466:         // the URL is build only when needed
 467:         if ( empty($this->_server['saml_validate_url']) ) {
 468:             switch ($this->getServerVersion()) {
 469:             case SAML_VERSION_1_1:
 470:                 $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate';
 471:                 break;
 472:             }
 473:         }
 474: 
 475:         $url = $this->_buildQueryUrl(
 476:             $this->_server['saml_validate_url'],
 477:             'TARGET='.urlencode($this->getURL())
 478:         );
 479:         phpCAS::traceEnd($url);
 480:         return $url;
 481:     }
 482: 
 483:     /**
 484:      * This method is used to retrieve the proxy validating URL of the CAS server.
 485:      *
 486:      * @return string proxyValidate URL.
 487:      */
 488:     public function getServerProxyValidateURL()
 489:     {
 490:         phpCAS::traceBegin();
 491:         // the URL is build only when needed
 492:         if ( empty($this->_server['proxy_validate_url']) ) {
 493:             switch ($this->getServerVersion()) {
 494:             case CAS_VERSION_1_0:
 495:                 $this->_server['proxy_validate_url'] = '';
 496:                 break;
 497:             case CAS_VERSION_2_0:
 498:                 $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate';
 499:                 break;
 500:             case CAS_VERSION_3_0:
 501:                 $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate';
 502:                 break;
 503:             }
 504:         }
 505:         $url = $this->_buildQueryUrl(
 506:             $this->_server['proxy_validate_url'],
 507:             'service='.urlencode($this->getURL())
 508:         );
 509:         phpCAS::traceEnd($url);
 510:         return $url;
 511:     }
 512: 
 513: 
 514:     /**
 515:      * This method is used to retrieve the proxy URL of the CAS server.
 516:      *
 517:      * @return  string proxy URL.
 518:      */
 519:     public function getServerProxyURL()
 520:     {
 521:         // the URL is build only when needed
 522:         if ( empty($this->_server['proxy_url']) ) {
 523:             switch ($this->getServerVersion()) {
 524:             case CAS_VERSION_1_0:
 525:                 $this->_server['proxy_url'] = '';
 526:                 break;
 527:             case CAS_VERSION_2_0:
 528:             case CAS_VERSION_3_0:
 529:                 $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy';
 530:                 break;
 531:             }
 532:         }
 533:         return $this->_server['proxy_url'];
 534:     }
 535: 
 536:     /**
 537:      * This method is used to retrieve the logout URL of the CAS server.
 538:      *
 539:      * @return string logout URL.
 540:      */
 541:     public function getServerLogoutURL()
 542:     {
 543:         // the URL is build only when needed
 544:         if ( empty($this->_server['logout_url']) ) {
 545:             $this->_server['logout_url'] = $this->_getServerBaseURL().'logout';
 546:         }
 547:         return $this->_server['logout_url'];
 548:     }
 549: 
 550:     /**
 551:      * This method sets the logout URL of the CAS server.
 552:      *
 553:      * @param string $url the logout URL
 554:      *
 555:      * @return string logout url
 556:      */
 557:     public function setServerLogoutURL($url)
 558:     {
 559:         // Argument Validation
 560:         if (gettype($url) != 'string')
 561:             throw new CAS_TypeMismatchException($url, '$url', 'string');
 562: 
 563:         return $this->_server['logout_url'] = $url;
 564:     }
 565: 
 566:     /**
 567:      * An array to store extra curl options.
 568:      */
 569:     private $_curl_options = array();
 570: 
 571:     /**
 572:      * This method is used to set additional user curl options.
 573:      *
 574:      * @param string $key   name of the curl option
 575:      * @param string $value value of the curl option
 576:      *
 577:      * @return void
 578:      */
 579:     public function setExtraCurlOption($key, $value)
 580:     {
 581:         $this->_curl_options[$key] = $value;
 582:     }
 583: 
 584:     /** @} */
 585: 
 586:     // ########################################################################
 587:     //  Change the internal behaviour of phpcas
 588:     // ########################################################################
 589: 
 590:     /**
 591:      * @addtogroup internalBehave
 592:      * @{
 593:      */
 594: 
 595:     /**
 596:      * The class to instantiate for making web requests in readUrl().
 597:      * The class specified must implement the CAS_Request_RequestInterface.
 598:      * By default CAS_Request_CurlRequest is used, but this may be overridden to
 599:      * supply alternate request mechanisms for testing.
 600:      */
 601:     private $_requestImplementation = 'CAS_Request_CurlRequest';
 602: 
 603:     /**
 604:      * Override the default implementation used to make web requests in readUrl().
 605:      * This class must implement the CAS_Request_RequestInterface.
 606:      *
 607:      * @param string $className name of the RequestImplementation class
 608:      *
 609:      * @return void
 610:      */
 611:     public function setRequestImplementation ($className)
 612:     {
 613:         $obj = new $className;
 614:         if (!($obj instanceof CAS_Request_RequestInterface)) {
 615:             throw new CAS_InvalidArgumentException(
 616:                 '$className must implement the CAS_Request_RequestInterface'
 617:             );
 618:         }
 619:         $this->_requestImplementation = $className;
 620:     }
 621: 
 622:     /**
 623:      * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session
 624:      * tickets from the URL after a successful authentication.
 625:      */
 626:     private $_clearTicketsFromUrl = true;
 627: 
 628:     /**
 629:      * Configure the client to not send redirect headers and call exit() on
 630:      * authentication success. The normal redirect is used to remove the service
 631:      * ticket from the client's URL, but for running unit tests we need to
 632:      * continue without exiting.
 633:      *
 634:      * Needed for testing authentication
 635:      *
 636:      * @return void
 637:      */
 638:     public function setNoClearTicketsFromUrl ()
 639:     {
 640:         $this->_clearTicketsFromUrl = false;
 641:     }
 642: 
 643:     /**
 644:      * @var callback $_attributeParserCallbackFunction;
 645:      */
 646:     private $_casAttributeParserCallbackFunction = null;
 647: 
 648:     /**
 649:      * @var array $_attributeParserCallbackArgs;
 650:      */
 651:     private $_casAttributeParserCallbackArgs = array();
 652: 
 653:     /**
 654:      * Set a callback function to be run when parsing CAS attributes
 655:      *
 656:      * The callback function will be passed a XMLNode as its first parameter,
 657:      * followed by any $additionalArgs you pass.
 658:      *
 659:      * @param string $function       callback function to call
 660:      * @param array  $additionalArgs optional array of arguments
 661:      *
 662:      * @return void
 663:      */
 664:     public function setCasAttributeParserCallback($function, array $additionalArgs = array())
 665:     {
 666:         $this->_casAttributeParserCallbackFunction = $function;
 667:         $this->_casAttributeParserCallbackArgs = $additionalArgs;
 668:     }
 669: 
 670:     /** @var callback $_postAuthenticateCallbackFunction;
 671:      */
 672:     private $_postAuthenticateCallbackFunction = null;
 673: 
 674:     /**
 675:      * @var array $_postAuthenticateCallbackArgs;
 676:      */
 677:     private $_postAuthenticateCallbackArgs = array();
 678: 
 679:     /**
 680:      * Set a callback function to be run when a user authenticates.
 681:      *
 682:      * The callback function will be passed a $logoutTicket as its first parameter,
 683:      * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
 684:      * opaque string that can be used to map a session-id to the logout request
 685:      * in order to support single-signout in applications that manage their own
 686:      * sessions (rather than letting phpCAS start the session).
 687:      *
 688:      * phpCAS::forceAuthentication() will always exit and forward client unless
 689:      * they are already authenticated. To perform an action at the moment the user
 690:      * logs in (such as registering an account, performing logging, etc), register
 691:      * a callback function here.
 692:      *
 693:      * @param string $function       callback function to call
 694:      * @param array  $additionalArgs optional array of arguments
 695:      *
 696:      * @return void
 697:      */
 698:     public function setPostAuthenticateCallback ($function, array $additionalArgs = array())
 699:     {
 700:         $this->_postAuthenticateCallbackFunction = $function;
 701:         $this->_postAuthenticateCallbackArgs = $additionalArgs;
 702:     }
 703: 
 704:     /**
 705:      * @var callback $_signoutCallbackFunction;
 706:      */
 707:     private $_signoutCallbackFunction = null;
 708: 
 709:     /**
 710:      * @var array $_signoutCallbackArgs;
 711:      */
 712:     private $_signoutCallbackArgs = array();
 713: 
 714:     /**
 715:      * Set a callback function to be run when a single-signout request is received.
 716:      *
 717:      * The callback function will be passed a $logoutTicket as its first parameter,
 718:      * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
 719:      * opaque string that can be used to map a session-id to the logout request in
 720:      * order to support single-signout in applications that manage their own sessions
 721:      * (rather than letting phpCAS start and destroy the session).
 722:      *
 723:      * @param string $function       callback function to call
 724:      * @param array  $additionalArgs optional array of arguments
 725:      *
 726:      * @return void
 727:      */
 728:     public function setSingleSignoutCallback ($function, array $additionalArgs = array())
 729:     {
 730:         $this->_signoutCallbackFunction = $function;
 731:         $this->_signoutCallbackArgs = $additionalArgs;
 732:     }
 733: 
 734:     // ########################################################################
 735:     //  Methods for supplying code-flow feedback to integrators.
 736:     // ########################################################################
 737: 
 738:     /**
 739:      * Ensure that this is actually a proxy object or fail with an exception
 740:      *
 741:      * @throws CAS_OutOfSequenceBeforeProxyException
 742:      *
 743:      * @return void
 744:      */
 745:     public function ensureIsProxy()
 746:     {
 747:         if (!$this->isProxy()) {
 748:             throw new CAS_OutOfSequenceBeforeProxyException();
 749:         }
 750:     }
 751: 
 752:     /**
 753:      * Mark the caller of authentication. This will help client integraters determine
 754:      * problems with their code flow if they call a function such as getUser() before
 755:      * authentication has occurred.
 756:      *
 757:      * @param bool $auth True if authentication was successful, false otherwise.
 758:      *
 759:      * @return null
 760:      */
 761:     public function markAuthenticationCall ($auth)
 762:     {
 763:         // store where the authentication has been checked and the result
 764:         $dbg = debug_backtrace();
 765:         $this->_authentication_caller = array (
 766:             'file' => $dbg[1]['file'],
 767:             'line' => $dbg[1]['line'],
 768:             'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
 769:             'result' => (boolean)$auth
 770:         );
 771:     }
 772:     private $_authentication_caller;
 773: 
 774:     /**
 775:      * Answer true if authentication has been checked.
 776:      *
 777:      * @return bool
 778:      */
 779:     public function wasAuthenticationCalled ()
 780:     {
 781:         return !empty($this->_authentication_caller);
 782:     }
 783: 
 784:     /**
 785:      * Ensure that authentication was checked. Terminate with exception if no
 786:      * authentication was performed
 787:      *
 788:      * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
 789:      *
 790:      * @return void
 791:      */
 792:     private function _ensureAuthenticationCalled()
 793:     {
 794:         if (!$this->wasAuthenticationCalled()) {
 795:             throw new CAS_OutOfSequenceBeforeAuthenticationCallException();
 796:         }
 797:     }
 798: 
 799:     /**
 800:      * Answer the result of the authentication call.
 801:      *
 802:      * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
 803:      * and markAuthenticationCall() didn't happen.
 804:      *
 805:      * @return bool
 806:      */
 807:     public function wasAuthenticationCallSuccessful ()
 808:     {
 809:         $this->_ensureAuthenticationCalled();
 810:         return $this->_authentication_caller['result'];
 811:     }
 812: 
 813: 
 814:     /**
 815:      * Ensure that authentication was checked. Terminate with exception if no
 816:      * authentication was performed
 817:      *
 818:      * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
 819:      *
 820:      * @return void
 821:      */
 822:     public function ensureAuthenticationCallSuccessful()
 823:     {
 824:         $this->_ensureAuthenticationCalled();
 825:         if (!$this->_authentication_caller['result']) {
 826:             throw new CAS_OutOfSequenceException(
 827:                 'authentication was checked (by '
 828:                 . $this->getAuthenticationCallerMethod()
 829:                 . '() at ' . $this->getAuthenticationCallerFile()
 830:                 . ':' . $this->getAuthenticationCallerLine()
 831:                 . ') but the method returned false'
 832:             );
 833:         }
 834:     }
 835: 
 836:     /**
 837:      * Answer information about the authentication caller.
 838:      *
 839:      * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
 840:      * and markAuthenticationCall() didn't happen.
 841:      *
 842:      * @return array Keys are 'file', 'line', and 'method'
 843:      */
 844:     public function getAuthenticationCallerFile ()
 845:     {
 846:         $this->_ensureAuthenticationCalled();
 847:         return $this->_authentication_caller['file'];
 848:     }
 849: 
 850:     /**
 851:      * Answer information about the authentication caller.
 852:      *
 853:      * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
 854:      * and markAuthenticationCall() didn't happen.
 855:      *
 856:      * @return array Keys are 'file', 'line', and 'method'
 857:      */
 858:     public function getAuthenticationCallerLine ()
 859:     {
 860:         $this->_ensureAuthenticationCalled();
 861:         return $this->_authentication_caller['line'];
 862:     }
 863: 
 864:     /**
 865:      * Answer information about the authentication caller.
 866:      *
 867:      * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
 868:      * and markAuthenticationCall() didn't happen.
 869:      *
 870:      * @return array Keys are 'file', 'line', and 'method'
 871:      */
 872:     public function getAuthenticationCallerMethod ()
 873:     {
 874:         $this->_ensureAuthenticationCalled();
 875:         return $this->_authentication_caller['method'];
 876:     }
 877: 
 878:     /** @} */
 879: 
 880:     // ########################################################################
 881:     //  CONSTRUCTOR
 882:     // ########################################################################
 883:     /**
 884:     * @addtogroup internalConfig
 885:     * @{
 886:     */
 887: 
 888:     /**
 889:      * CAS_Client constructor.
 890:      *
 891:      * @param string $server_version  the version of the CAS server
 892:      * @param bool   $proxy           true if the CAS client is a CAS proxy
 893:      * @param string $server_hostname the hostname of the CAS server
 894:      * @param int    $server_port     the port the CAS server is running on
 895:      * @param string $server_uri      the URI the CAS server is responding on
 896:      * @param bool   $changeSessionID Allow phpCAS to change the session_id
 897:      *                                (Single Sign Out/handleLogoutRequests
 898:      *                                is based on that change)
 899:      *
 900:      * @return a newly created CAS_Client object
 901:      */
 902:     public function __construct(
 903:         $server_version,
 904:         $proxy,
 905:         $server_hostname,
 906:         $server_port,
 907:         $server_uri,
 908:         $changeSessionID = true
 909:     ) {
 910:         // Argument validation
 911:         if (gettype($server_version) != 'string')
 912:             throw new CAS_TypeMismatchException($server_version, '$server_version', 'string');
 913:         if (gettype($proxy) != 'boolean')
 914:             throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean');
 915:         if (gettype($server_hostname) != 'string')
 916:             throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string');
 917:         if (gettype($server_port) != 'integer')
 918:             throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer');
 919:         if (gettype($server_uri) != 'string')
 920:             throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string');
 921:         if (gettype($changeSessionID) != 'boolean')
 922:             throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean');
 923: 
 924:         phpCAS::traceBegin();
 925:         // true : allow to change the session_id(), false session_id won't be
 926:         // change and logout won't be handle because of that
 927:         $this->_setChangeSessionID($changeSessionID);
 928: 
 929:         // skip Session Handling for logout requests and if don't want it'
 930:         if (session_id()=="" && !$this->_isLogoutRequest()) {
 931:             session_start();
 932:             phpCAS :: trace("Starting a new session " . session_id());
 933:         }
 934:         // Only for debug purposes
 935:         if ($this->isSessionAuthenticated()){
 936:             phpCAS :: trace("Session is authenticated as: " . $_SESSION['phpCAS']['user']);
 937:         } else {
 938:             phpCAS :: trace("Session is not authenticated");
 939:         }
 940:         // are we in proxy mode ?
 941:         $this->_proxy = $proxy;
 942: 
 943:         // Make cookie handling available.
 944:         if ($this->isProxy()) {
 945:             if (!isset($_SESSION['phpCAS'])) {
 946:                 $_SESSION['phpCAS'] = array();
 947:             }
 948:             if (!isset($_SESSION['phpCAS']['service_cookies'])) {
 949:                 $_SESSION['phpCAS']['service_cookies'] = array();
 950:             }
 951:             $this->_serviceCookieJar = new CAS_CookieJar(
 952:                 $_SESSION['phpCAS']['service_cookies']
 953:             );
 954:         }
 955: 
 956:         //check version
 957:         switch ($server_version) {
 958:         case CAS_VERSION_1_0:
 959:             if ( $this->isProxy() ) {
 960:                 phpCAS::error(
 961:                     'CAS proxies are not supported in CAS '.$server_version
 962:                 );
 963:             }
 964:             break;
 965:         case CAS_VERSION_2_0:
 966:         case CAS_VERSION_3_0:
 967:             break;
 968:         case SAML_VERSION_1_1:
 969:             break;
 970:         default:
 971:             phpCAS::error(
 972:                 'this version of CAS (`'.$server_version
 973:                 .'\') is not supported by phpCAS '.phpCAS::getVersion()
 974:             );
 975:         }
 976:         $this->_server['version'] = $server_version;
 977: 
 978:         // check hostname
 979:         if ( empty($server_hostname)
 980:             || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/', $server_hostname)
 981:         ) {
 982:             phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
 983:         }
 984:         $this->_server['hostname'] = $server_hostname;
 985: 
 986:         // check port
 987:         if ( $server_port == 0
 988:             || !is_int($server_port)
 989:         ) {
 990:             phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
 991:         }
 992:         $this->_server['port'] = $server_port;
 993: 
 994:         // check URI
 995:         if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/', $server_uri) ) {
 996:             phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
 997:         }
 998:         // add leading and trailing `/' and remove doubles
 999:         if(strstr($server_uri, '?') === false) $server_uri .= '/';
1000:         $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri);
1001:         $this->_server['uri'] = $server_uri;
1002: 
1003:         // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
1004:         if ( $this->isProxy() ) {
1005:             $this->_setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
1006:         }
1007: 
1008:         if ( $this->_isCallbackMode() ) {
1009:             //callback mode: check that phpCAS is secured
1010:             if ( !$this->_isHttps() ) {
1011:                 phpCAS::error(
1012:                     'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'
1013:                 );
1014:             }
1015:         } else {
1016:             //normal mode: get ticket and remove it from CGI parameters for
1017:             // developers
1018:             $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
1019:             if (preg_match('/^[SP]T-/', $ticket) ) {
1020:                 phpCAS::trace('Ticket \''.$ticket.'\' found');
1021:                 $this->setTicket($ticket);
1022:                 unset($_GET['ticket']);
1023:             } else if ( !empty($ticket) ) {
1024:                 //ill-formed ticket, halt
1025:                 phpCAS::error(
1026:                     'ill-formed ticket found in the URL (ticket=`'
1027:                     .htmlentities($ticket).'\')'
1028:                 );
1029:             }
1030: 
1031:         }
1032:         phpCAS::traceEnd();
1033:     }
1034: 
1035:     /** @} */
1036: 
1037:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1038:     // XX                                                                    XX
1039:     // XX                           Session Handling                         XX
1040:     // XX                                                                    XX
1041:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1042: 
1043:     /**
1044:      * @addtogroup internalConfig
1045:      * @{
1046:      */
1047: 
1048: 
1049:     /**
1050:      * A variable to whether phpcas will use its own session handling. Default = true
1051:      * @hideinitializer
1052:      */
1053:     private $_change_session_id = true;
1054: 
1055:     /**
1056:      * Set a parameter whether to allow phpCas to change session_id
1057:      *
1058:      * @param bool $allowed allow phpCas to change session_id
1059:      *
1060:      * @return void
1061:      */
1062:     private function _setChangeSessionID($allowed)
1063:     {
1064:         $this->_change_session_id = $allowed;
1065:     }
1066: 
1067:     /**
1068:      * Get whether phpCas is allowed to change session_id
1069:      *
1070:      * @return bool
1071:      */
1072:     public function getChangeSessionID()
1073:     {
1074:         return $this->_change_session_id;
1075:     }
1076: 
1077:     /** @} */
1078: 
1079:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1080:     // XX                                                                    XX
1081:     // XX                           AUTHENTICATION                           XX
1082:     // XX                                                                    XX
1083:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1084: 
1085:     /**
1086:      * @addtogroup internalAuthentication
1087:      * @{
1088:      */
1089: 
1090:     /**
1091:      * The Authenticated user. Written by CAS_Client::_setUser(), read by
1092:      * CAS_Client::getUser().
1093:      *
1094:      * @hideinitializer
1095:      */
1096:     private $_user = '';
1097: 
1098:     /**
1099:      * This method sets the CAS user's login name.
1100:      *
1101:      * @param string $user the login name of the authenticated user.
1102:      *
1103:      * @return void
1104:      */
1105:     private function _setUser($user)
1106:     {
1107:         $this->_user = $user;
1108:     }
1109: 
1110:     /**
1111:      * This method returns the CAS user's login name.
1112:      *
1113:      * @return string the login name of the authenticated user
1114:      *
1115:      * @warning should be called only after CAS_Client::forceAuthentication() or
1116:      * CAS_Client::isAuthenticated(), otherwise halt with an error.
1117:      */
1118:     public function getUser()
1119:     {
1120:         // Sequence validation
1121:         $this->ensureAuthenticationCallSuccessful();
1122: 
1123:         return $this->_getUser();
1124:     }
1125: 
1126:     /**
1127:      * This method returns the CAS user's login name.
1128:      *
1129:      * @return string the login name of the authenticated user
1130:      *
1131:      * @warning should be called only after CAS_Client::forceAuthentication() or
1132:      * CAS_Client::isAuthenticated(), otherwise halt with an error.
1133:      */
1134:     private function _getUser()
1135:     {
1136:         // This is likely a duplicate check that could be removed....
1137:         if ( empty($this->_user) ) {
1138:             phpCAS::error(
1139:                 'this method should be used only after '.__CLASS__
1140:                 .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
1141:             );
1142:         }
1143:         return $this->_user;
1144:     }
1145: 
1146:     /**
1147:      * The Authenticated users attributes. Written by
1148:      * CAS_Client::setAttributes(), read by CAS_Client::getAttributes().
1149:      * @attention client applications should use phpCAS::getAttributes().
1150:      *
1151:      * @hideinitializer
1152:      */
1153:     private $_attributes = array();
1154: 
1155:     /**
1156:      * Set an array of attributes
1157:      *
1158:      * @param array $attributes a key value array of attributes
1159:      *
1160:      * @return void
1161:      */
1162:     public function setAttributes($attributes)
1163:     {
1164:         $this->_attributes = $attributes;
1165:     }
1166: 
1167:     /**
1168:      * Get an key values arry of attributes
1169:      *
1170:      * @return arry of attributes
1171:      */
1172:     public function getAttributes()
1173:     {
1174:         // Sequence validation
1175:         $this->ensureAuthenticationCallSuccessful();
1176:         // This is likely a duplicate check that could be removed....
1177:         if ( empty($this->_user) ) {
1178:             // if no user is set, there shouldn't be any attributes also...
1179:             phpCAS::error(
1180:                 'this method should be used only after '.__CLASS__
1181:                 .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
1182:             );
1183:         }
1184:         return $this->_attributes;
1185:     }
1186: 
1187:     /**
1188:      * Check whether attributes are available
1189:      *
1190:      * @return bool attributes available
1191:      */
1192:     public function hasAttributes()
1193:     {
1194:         // Sequence validation
1195:         $this->ensureAuthenticationCallSuccessful();
1196: 
1197:         return !empty($this->_attributes);
1198:     }
1199:     /**
1200:      * Check whether a specific attribute with a name is available
1201:      *
1202:      * @param string $key name of attribute
1203:      *
1204:      * @return bool is attribute available
1205:      */
1206:     public function hasAttribute($key)
1207:     {
1208:         // Sequence validation
1209:         $this->ensureAuthenticationCallSuccessful();
1210: 
1211:         return $this->_hasAttribute($key);
1212:     }
1213: 
1214:     /**
1215:      * Check whether a specific attribute with a name is available
1216:      *
1217:      * @param string $key name of attribute
1218:      *
1219:      * @return bool is attribute available
1220:      */
1221:     private function _hasAttribute($key)
1222:     {
1223:         return (is_array($this->_attributes)
1224:             && array_key_exists($key, $this->_attributes));
1225:     }
1226: 
1227:     /**
1228:      * Get a specific attribute by name
1229:      *
1230:      * @param string $key name of attribute
1231:      *
1232:      * @return string attribute values
1233:      */
1234:     public function getAttribute($key)
1235:     {
1236:         // Sequence validation
1237:         $this->ensureAuthenticationCallSuccessful();
1238: 
1239:         if ($this->_hasAttribute($key)) {
1240:             return $this->_attributes[$key];
1241:         }
1242:     }
1243: 
1244:     /**
1245:      * This method is called to renew the authentication of the user
1246:      * If the user is authenticated, renew the connection
1247:      * If not, redirect to CAS
1248:      *
1249:      * @return  true when the user is authenticated; otherwise halt.
1250:      */
1251:     public function renewAuthentication()
1252:     {
1253:         phpCAS::traceBegin();
1254:         // Either way, the user is authenticated by CAS
1255:         if (isset( $_SESSION['phpCAS']['auth_checked'])) {
1256:             unset($_SESSION['phpCAS']['auth_checked']);
1257:         }
1258:         if ( $this->isAuthenticated(true) ) {
1259:             phpCAS::trace('user already authenticated');
1260:             $res = true;
1261:         } else {
1262:             $this->redirectToCas(false, true);
1263:             // never reached
1264:             $res = false;
1265:         }
1266:         phpCAS::traceEnd();
1267:         return $res;
1268:     }
1269: 
1270:     /**
1271:      * This method is called to be sure that the user is authenticated. When not
1272:      * authenticated, halt by redirecting to the CAS server; otherwise return true.
1273:      *
1274:      * @return true when the user is authenticated; otherwise halt.
1275:      */
1276:     public function forceAuthentication()
1277:     {
1278:         phpCAS::traceBegin();
1279: 
1280:         if ( $this->isAuthenticated() ) {
1281:             // the user is authenticated, nothing to be done.
1282:             phpCAS::trace('no need to authenticate');
1283:             $res = true;
1284:         } else {
1285:             // the user is not authenticated, redirect to the CAS server
1286:             if (isset($_SESSION['phpCAS']['auth_checked'])) {
1287:                 unset($_SESSION['phpCAS']['auth_checked']);
1288:             }
1289:             $this->redirectToCas(false/* no gateway */);
1290:             // never reached
1291:             $res = false;
1292:         }
1293:         phpCAS::traceEnd($res);
1294:         return $res;
1295:     }
1296: 
1297:     /**
1298:      * An integer that gives the number of times authentication will be cached
1299:      * before rechecked.
1300:      *
1301:      * @hideinitializer
1302:      */
1303:     private $_cache_times_for_auth_recheck = 0;
1304: 
1305:     /**
1306:      * Set the number of times authentication will be cached before rechecked.
1307:      *
1308:      * @param int $n number of times to wait for a recheck
1309:      *
1310:      * @return void
1311:      */
1312:     public function setCacheTimesForAuthRecheck($n)
1313:     {
1314:         if (gettype($n) != 'integer')
1315:             throw new CAS_TypeMismatchException($n, '$n', 'string');
1316: 
1317:         $this->_cache_times_for_auth_recheck = $n;
1318:     }
1319: 
1320:     /**
1321:      * This method is called to check whether the user is authenticated or not.
1322:      *
1323:      * @return true when the user is authenticated, false when a previous
1324:      * gateway login failed or  the function will not return if the user is
1325:      * redirected to the cas server for a gateway login attempt
1326:      */
1327:     public function checkAuthentication()
1328:     {
1329:         phpCAS::traceBegin();
1330:         $res = false;
1331:         if ( $this->isAuthenticated() ) {
1332:             phpCAS::trace('user is authenticated');
1333:             /* The 'auth_checked' variable is removed just in case it's set. */
1334:             unset($_SESSION['phpCAS']['auth_checked']);
1335:             $res = true;
1336:         } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
1337:             // the previous request has redirected the client to the CAS server
1338:             // with gateway=true
1339:             unset($_SESSION['phpCAS']['auth_checked']);
1340:             $res = false;
1341:         } else {
1342:             // avoid a check against CAS on every request
1343:             if (!isset($_SESSION['phpCAS']['unauth_count'])) {
1344:                 $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
1345:             }
1346: 
1347:             if (($_SESSION['phpCAS']['unauth_count'] != -2
1348:                 && $this->_cache_times_for_auth_recheck == -1)
1349:                 || ($_SESSION['phpCAS']['unauth_count'] >= 0
1350:                 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)
1351:             ) {
1352:                 $res = false;
1353: 
1354:                 if ($this->_cache_times_for_auth_recheck != -1) {
1355:                     $_SESSION['phpCAS']['unauth_count']++;
1356:                     phpCAS::trace(
1357:                         'user is not authenticated (cached for '
1358:                         .$_SESSION['phpCAS']['unauth_count'].' times of '
1359:                         .$this->_cache_times_for_auth_recheck.')'
1360:                     );
1361:                 } else {
1362:                     phpCAS::trace(
1363:                         'user is not authenticated (cached for until login pressed)'
1364:                     );
1365:                 }
1366:             } else {
1367:                 $_SESSION['phpCAS']['unauth_count'] = 0;
1368:                 $_SESSION['phpCAS']['auth_checked'] = true;
1369:                 phpCAS::trace('user is not authenticated (cache reset)');
1370:                 $this->redirectToCas(true/* gateway */);
1371:                 // never reached
1372:                 $res = false;
1373:             }
1374:         }
1375:         phpCAS::traceEnd($res);
1376:         return $res;
1377:     }
1378: 
1379:     /**
1380:      * This method is called to check if the user is authenticated (previously or by
1381:      * tickets given in the URL).
1382:      *
1383:      * @param bool $renew true to force the authentication with the CAS server
1384:      *
1385:      * @return true when the user is authenticated. Also may redirect to the
1386:      * same URL without the ticket.
1387:      */
1388:     public function isAuthenticated($renew=false)
1389:     {
1390:         phpCAS::traceBegin();
1391:         $res = false;
1392:         $validate_url = '';
1393:         if ( $this->_wasPreviouslyAuthenticated() ) {
1394:             if ($this->hasTicket()) {
1395:                 // User has a additional ticket but was already authenticated
1396:                 phpCAS::trace(
1397:                     'ticket was present and will be discarded, use renewAuthenticate()'
1398:                 );
1399:                 if ($this->_clearTicketsFromUrl) {
1400:                     phpCAS::trace("Prepare redirect to : ".$this->getURL());
1401:                     session_write_close();
1402:                     header('Location: '.$this->getURL());
1403:                     flush();
1404:                     phpCAS::traceExit();
1405:                     throw new CAS_GracefullTerminationException();
1406:                 } else {
1407:                     phpCAS::trace(
1408:                         'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.'
1409:                     );
1410:                     $res = true;
1411:                 }
1412:             } else {
1413:                 // the user has already (previously during the session) been
1414:                 // authenticated, nothing to be done.
1415:                 phpCAS::trace(
1416:                     'user was already authenticated, no need to look for tickets'
1417:                 );
1418:                 $res = true;
1419:             }
1420: 
1421:             // Mark the auth-check as complete to allow post-authentication
1422:             // callbacks to make use of phpCAS::getUser() and similar methods
1423:             $this->markAuthenticationCall($res);
1424:         } else {
1425:             if ($this->hasTicket()) {
1426:                 switch ($this->getServerVersion()) {
1427:                 case CAS_VERSION_1_0:
1428:                     // if a Service Ticket was given, validate it
1429:                     phpCAS::trace(
1430:                         'CAS 1.0 ticket `'.$this->getTicket().'\' is present'
1431:                     );
1432:                     $this->validateCAS10(
1433:                         $validate_url, $text_response, $tree_response, $renew
1434:                     ); // if it fails, it halts
1435:                     phpCAS::trace(
1436:                         'CAS 1.0 ticket `'.$this->getTicket().'\' was validated'
1437:                     );
1438:                     $_SESSION['phpCAS']['user'] = $this->_getUser();
1439:                     $res = true;
1440:                     $logoutTicket = $this->getTicket();
1441:                     break;
1442:                 case CAS_VERSION_2_0:
1443:                 case CAS_VERSION_3_0:
1444:                     // if a Proxy Ticket was given, validate it
1445:                     phpCAS::trace(
1446:                         'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present'
1447:                     );
1448:                     $this->validateCAS20(
1449:                         $validate_url, $text_response, $tree_response, $renew
1450:                     ); // note: if it fails, it halts
1451:                     phpCAS::trace(
1452:                         'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated'
1453:                     );
1454:                     if ( $this->isProxy() ) {
1455:                         $this->_validatePGT(
1456:                             $validate_url, $text_response, $tree_response
1457:                         ); // idem
1458:                         phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated');
1459:                         $_SESSION['phpCAS']['pgt'] = $this->_getPGT();
1460:                     }
1461:                     $_SESSION['phpCAS']['user'] = $this->_getUser();
1462:                     if (!empty($this->_attributes)) {
1463:                         $_SESSION['phpCAS']['attributes'] = $this->_attributes;
1464:                     }
1465:                     $proxies = $this->getProxies();
1466:                     if (!empty($proxies)) {
1467:                         $_SESSION['phpCAS']['proxies'] = $this->getProxies();
1468:                     }
1469:                     $res = true;
1470:                     $logoutTicket = $this->getTicket();
1471:                     break;
1472:                 case SAML_VERSION_1_1:
1473:                     // if we have a SAML ticket, validate it.
1474:                     phpCAS::trace(
1475:                         'SAML 1.1 ticket `'.$this->getTicket().'\' is present'
1476:                     );
1477:                     $this->validateSA(
1478:                         $validate_url, $text_response, $tree_response, $renew
1479:                     ); // if it fails, it halts
1480:                     phpCAS::trace(
1481:                         'SAML 1.1 ticket `'.$this->getTicket().'\' was validated'
1482:                     );
1483:                     $_SESSION['phpCAS']['user'] = $this->_getUser();
1484:                     $_SESSION['phpCAS']['attributes'] = $this->_attributes;
1485:                     $res = true;
1486:                     $logoutTicket = $this->getTicket();
1487:                     break;
1488:                 default:
1489:                     phpCAS::trace('Protocoll error');
1490:                     break;
1491:                 }
1492:             } else {
1493:                 // no ticket given, not authenticated
1494:                 phpCAS::trace('no ticket found');
1495:             }
1496: 
1497:             // Mark the auth-check as complete to allow post-authentication
1498:             // callbacks to make use of phpCAS::getUser() and similar methods
1499:             $this->markAuthenticationCall($res);
1500: 
1501:             if ($res) {
1502:                 // call the post-authenticate callback if registered.
1503:                 if ($this->_postAuthenticateCallbackFunction) {
1504:                     $args = $this->_postAuthenticateCallbackArgs;
1505:                     array_unshift($args, $logoutTicket);
1506:                     call_user_func_array(
1507:                         $this->_postAuthenticateCallbackFunction, $args
1508:                     );
1509:                 }
1510: 
1511:                 // if called with a ticket parameter, we need to redirect to the
1512:                 // app without the ticket so that CAS-ification is transparent
1513:                 // to the browser (for later POSTS) most of the checks and
1514:                 // errors should have been made now, so we're safe for redirect
1515:                 // without masking error messages. remove the ticket as a
1516:                 // security precaution to prevent a ticket in the HTTP_REFERRER
1517:                 if ($this->_clearTicketsFromUrl) {
1518:                     phpCAS::trace("Prepare redirect to : ".$this->getURL());
1519:                     session_write_close();
1520:                     header('Location: '.$this->getURL());
1521:                     flush();
1522:                     phpCAS::traceExit();
1523:                     throw new CAS_GracefullTerminationException();
1524:                 }
1525:             }
1526:         }
1527:         phpCAS::traceEnd($res);
1528:         return $res;
1529:     }
1530: 
1531:     /**
1532:      * This method tells if the current session is authenticated.
1533:      *
1534:      * @return true if authenticated based soley on $_SESSION variable
1535:      */
1536:     public function isSessionAuthenticated ()
1537:     {
1538:         return !empty($_SESSION['phpCAS']['user']);
1539:     }
1540: 
1541:     /**
1542:      * This method tells if the user has already been (previously) authenticated
1543:      * by looking into the session variables.
1544:      *
1545:      * @note This function switches to callback mode when needed.
1546:      *
1547:      * @return true when the user has already been authenticated; false otherwise.
1548:      */
1549:     private function _wasPreviouslyAuthenticated()
1550:     {
1551:         phpCAS::traceBegin();
1552: 
1553:         if ( $this->_isCallbackMode() ) {
1554:             // Rebroadcast the pgtIou and pgtId to all nodes
1555:             if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) {
1556:                 $this->_rebroadcast(self::PGTIOU);
1557:             }
1558:             $this->_callback();
1559:         }
1560: 
1561:         $auth = false;
1562: 
1563:         if ( $this->isProxy() ) {
1564:             // CAS proxy: username and PGT must be present
1565:             if ( $this->isSessionAuthenticated()
1566:                 && !empty($_SESSION['phpCAS']['pgt'])
1567:             ) {
1568:                 // authentication already done
1569:                 $this->_setUser($_SESSION['phpCAS']['user']);
1570:                 if (isset($_SESSION['phpCAS']['attributes'])) {
1571:                     $this->setAttributes($_SESSION['phpCAS']['attributes']);
1572:                 }
1573:                 $this->_setPGT($_SESSION['phpCAS']['pgt']);
1574:                 phpCAS::trace(
1575:                     'user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'
1576:                     .$_SESSION['phpCAS']['pgt'].'\''
1577:                 );
1578: 
1579:                 // Include the list of proxies
1580:                 if (isset($_SESSION['phpCAS']['proxies'])) {
1581:                     $this->_setProxies($_SESSION['phpCAS']['proxies']);
1582:                     phpCAS::trace(
1583:                         'proxies = "'
1584:                         .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
1585:                     );
1586:                 }
1587: 
1588:                 $auth = true;
1589:             } elseif ( $this->isSessionAuthenticated()
1590:                 && empty($_SESSION['phpCAS']['pgt'])
1591:             ) {
1592:                 // these two variables should be empty or not empty at the same time
1593:                 phpCAS::trace(
1594:                     'username found (`'.$_SESSION['phpCAS']['user']
1595:                     .'\') but PGT is empty'
1596:                 );
1597:                 // unset all tickets to enforce authentication
1598:                 unset($_SESSION['phpCAS']);
1599:                 $this->setTicket('');
1600:             } elseif ( !$this->isSessionAuthenticated()
1601:                 && !empty($_SESSION['phpCAS']['pgt'])
1602:             ) {
1603:                 // these two variables should be empty or not empty at the same time
1604:                 phpCAS::trace(
1605:                     'PGT found (`'.$_SESSION['phpCAS']['pgt']
1606:                     .'\') but username is empty'
1607:                 );
1608:                 // unset all tickets to enforce authentication
1609:                 unset($_SESSION['phpCAS']);
1610:                 $this->setTicket('');
1611:             } else {
1612:                 phpCAS::trace('neither user nor PGT found');
1613:             }
1614:         } else {
1615:             // `simple' CAS client (not a proxy): username must be present
1616:             if ( $this->isSessionAuthenticated() ) {
1617:                 // authentication already done
1618:                 $this->_setUser($_SESSION['phpCAS']['user']);
1619:                 if (isset($_SESSION['phpCAS']['attributes'])) {
1620:                     $this->setAttributes($_SESSION['phpCAS']['attributes']);
1621:                 }
1622:                 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
1623: 
1624:                 // Include the list of proxies
1625:                 if (isset($_SESSION['phpCAS']['proxies'])) {
1626:                     $this->_setProxies($_SESSION['phpCAS']['proxies']);
1627:                     phpCAS::trace(
1628:                         'proxies = "'
1629:                         .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
1630:                     );
1631:                 }
1632: 
1633:                 $auth = true;
1634:             } else {
1635:                 phpCAS::trace('no user found');
1636:             }
1637:         }
1638: 
1639:         phpCAS::traceEnd($auth);
1640:         return $auth;
1641:     }
1642: 
1643:     /**
1644:      * This method is used to redirect the client to the CAS server.
1645:      * It is used by CAS_Client::forceAuthentication() and
1646:      * CAS_Client::checkAuthentication().
1647:      *
1648:      * @param bool $gateway true to check authentication, false to force it
1649:      * @param bool $renew   true to force the authentication with the CAS server
1650:      *
1651:      * @return void
1652:      */
1653:     public function redirectToCas($gateway=false,$renew=false)
1654:     {
1655:         phpCAS::traceBegin();
1656:         $cas_url = $this->getServerLoginURL($gateway, $renew);
1657:         session_write_close();
1658:         if (php_sapi_name() === 'cli') {
1659:             @header('Location: '.$cas_url);
1660:         } else {
1661:             header('Location: '.$cas_url);
1662:         }
1663:         phpCAS::trace("Redirect to : ".$cas_url);
1664:         $lang = $this->getLangObj();
1665:         $this->printHTMLHeader($lang->getAuthenticationWanted());
1666:         printf('<p>'. $lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
1667:         $this->printHTMLFooter();
1668:         phpCAS::traceExit();
1669:         throw new CAS_GracefullTerminationException();
1670:     }
1671: 
1672: 
1673:     /**
1674:      * This method is used to logout from CAS.
1675:      *
1676:      * @param array $params an array that contains the optional url and service
1677:      * parameters that will be passed to the CAS server
1678:      *
1679:      * @return void
1680:      */
1681:     public function logout($params)
1682:     {
1683:         phpCAS::traceBegin();
1684:         $cas_url = $this->getServerLogoutURL();
1685:         $paramSeparator = '?';
1686:         if (isset($params['url'])) {
1687:             $cas_url = $cas_url . $paramSeparator . "url="
1688:                 . urlencode($params['url']);
1689:             $paramSeparator = '&';
1690:         }
1691:         if (isset($params['service'])) {
1692:             $cas_url = $cas_url . $paramSeparator . "service="
1693:                 . urlencode($params['service']);
1694:         }
1695:         header('Location: '.$cas_url);
1696:         phpCAS::trace("Prepare redirect to : ".$cas_url);
1697: 
1698:         phpCAS::trace("Destroying session : ".session_id());
1699:         session_unset();
1700:         session_destroy();
1701:         if (session_status() === PHP_SESSION_NONE) {
1702:             phpCAS::trace("Session terminated");
1703:         } else {
1704:             phpCAS::error("Session was not terminated");
1705:             phpCAS::trace("Session was not terminated");
1706:         }
1707:         $lang = $this->getLangObj();
1708:         $this->printHTMLHeader($lang->getLogout());
1709:         printf('<p>'.$lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
1710:         $this->printHTMLFooter();
1711:         phpCAS::traceExit();
1712:         throw new CAS_GracefullTerminationException();
1713:     }
1714: 
1715:     /**
1716:      * Check of the current request is a logout request
1717:      *
1718:      * @return bool is logout request.
1719:      */
1720:     private function _isLogoutRequest()
1721:     {
1722:         return !empty($_POST['logoutRequest']);
1723:     }
1724: 
1725:     /**
1726:      * This method handles logout requests.
1727:      *
1728:      * @param bool $check_client    true to check the client bofore handling
1729:      * the request, false not to perform any access control. True by default.
1730:      * @param bool $allowed_clients an array of host names allowed to send
1731:      * logout requests.
1732:      *
1733:      * @return void
1734:      */
1735:     public function handleLogoutRequests($check_client=true, $allowed_clients=false)
1736:     {
1737:         phpCAS::traceBegin();
1738:         if (!$this->_isLogoutRequest()) {
1739:             phpCAS::trace("Not a logout request");
1740:             phpCAS::traceEnd();
1741:             return;
1742:         }
1743:         if (!$this->getChangeSessionID()
1744:             && is_null($this->_signoutCallbackFunction)
1745:         ) {
1746:             phpCAS::trace(
1747:                 "phpCAS can't handle logout requests if it is not allowed to change session_id."
1748:             );
1749:         }
1750:         phpCAS::trace("Logout requested");
1751:         $decoded_logout_rq = urldecode($_POST['logoutRequest']);
1752:         phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq);
1753:         $allowed = false;
1754:         if ($check_client) {
1755:             if (!$allowed_clients) {
1756:                 $allowed_clients = array( $this->_getServerHostname() );
1757:             }
1758:             $client_ip = $_SERVER['REMOTE_ADDR'];
1759:             $client = gethostbyaddr($client_ip);
1760:             phpCAS::trace("Client: ".$client."/".$client_ip);
1761:             foreach ($allowed_clients as $allowed_client) {
1762:                 if (($client == $allowed_client)
1763:                     || ($client_ip == $allowed_client)
1764:                 ) {
1765:                     phpCAS::trace(
1766:                         "Allowed client '".$allowed_client
1767:                         ."' matches, logout request is allowed"
1768:                     );
1769:                     $allowed = true;
1770:                     break;
1771:                 } else {
1772:                     phpCAS::trace(
1773:                         "Allowed client '".$allowed_client."' does not match"
1774:                     );
1775:                 }
1776:             }
1777:         } else {
1778:             phpCAS::trace("No access control set");
1779:             $allowed = true;
1780:         }
1781:         // If Logout command is permitted proceed with the logout
1782:         if ($allowed) {
1783:             phpCAS::trace("Logout command allowed");
1784:             // Rebroadcast the logout request
1785:             if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) {
1786:                 $this->_rebroadcast(self::LOGOUT);
1787:             }
1788:             // Extract the ticket from the SAML Request
1789:             preg_match(
1790:                 "|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|",
1791:                 $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3
1792:             );
1793:             $wrappedSamlSessionIndex = preg_replace(
1794:                 '|<samlp:SessionIndex>|', '', $tick[0][0]
1795:             );
1796:             $ticket2logout = preg_replace(
1797:                 '|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex
1798:             );
1799:             phpCAS::trace("Ticket to logout: ".$ticket2logout);
1800: 
1801:             // call the post-authenticate callback if registered.
1802:             if ($this->_signoutCallbackFunction) {
1803:                 $args = $this->_signoutCallbackArgs;
1804:                 array_unshift($args, $ticket2logout);
1805:                 call_user_func_array($this->_signoutCallbackFunction, $args);
1806:             }
1807: 
1808:             // If phpCAS is managing the session_id, destroy session thanks to
1809:             // session_id.
1810:             if ($this->getChangeSessionID()) {
1811:                 $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket2logout);
1812:                 phpCAS::trace("Session id: ".$session_id);
1813: 
1814:                 // destroy a possible application session created before phpcas
1815:                 if (session_id() !== "") {
1816:                     session_unset();
1817:                     session_destroy();
1818:                 }
1819:                 // fix session ID
1820:                 session_id($session_id);
1821:                 $_COOKIE[session_name()]=$session_id;
1822:                 $_GET[session_name()]=$session_id;
1823: 
1824:                 // Overwrite session
1825:                 session_start();
1826:                 session_unset();
1827:                 session_destroy();
1828:                 phpCAS::trace("Session ". $session_id . " destroyed");
1829:             }
1830:         } else {
1831:             phpCAS::error("Unauthorized logout request from client '".$client."'");
1832:             phpCAS::trace("Unauthorized logout request from client '".$client."'");
1833:         }
1834:         flush();
1835:         phpCAS::traceExit();
1836:         throw new CAS_GracefullTerminationException();
1837: 
1838:     }
1839: 
1840:     /** @} */
1841: 
1842:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1843:     // XX                                                                    XX
1844:     // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
1845:     // XX                                                                    XX
1846:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1847: 
1848:     // ########################################################################
1849:     //  ST
1850:     // ########################################################################
1851:     /**
1852:     * @addtogroup internalBasic
1853:     * @{
1854:     */
1855: 
1856:     /**
1857:      * The Ticket provided in the URL of the request if present
1858:      * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
1859:      * CAS_Client::getTicket() and CAS_Client::_hasPGT().
1860:      *
1861:      * @hideinitializer
1862:      */
1863:     private $_ticket = '';
1864: 
1865:     /**
1866:      * This method returns the Service Ticket provided in the URL of the request.
1867:      *
1868:      * @return string service ticket.
1869:      */
1870:     public  function getTicket()
1871:     {
1872:         return $this->_ticket;
1873:     }
1874: 
1875:     /**
1876:      * This method stores the Service Ticket.
1877:      *
1878:      * @param string $st The Service Ticket.
1879:      *
1880:      * @return void
1881:      */
1882:     public function setTicket($st)
1883:     {
1884:         $this->_ticket = $st;
1885:     }
1886: 
1887:     /**
1888:      * This method tells if a Service Ticket was stored.
1889:      *
1890:      * @return bool if a Service Ticket has been stored.
1891:      */
1892:     public function hasTicket()
1893:     {
1894:         return !empty($this->_ticket);
1895:     }
1896: 
1897:     /** @} */
1898: 
1899:     // ########################################################################
1900:     //  ST VALIDATION
1901:     // ########################################################################
1902:     /**
1903:     * @addtogroup internalBasic
1904:     * @{
1905:     */
1906: 
1907:     /**
1908:      * the certificate of the CAS server CA.
1909:      *
1910:      * @hideinitializer
1911:      */
1912:     private $_cas_server_ca_cert = null;
1913: 
1914: 
1915:     /**
1916: 
1917:      * validate CN of the CAS server certificate
1918: 
1919:      *
1920: 
1921:      * @hideinitializer
1922: 
1923:      */
1924: 
1925:     private $_cas_server_cn_validate = true;
1926: 
1927:     /**
1928:      * Set to true not to validate the CAS server.
1929:      *
1930:      * @hideinitializer
1931:      */
1932:     private $_no_cas_server_validation = false;
1933: 
1934: 
1935:     /**
1936:      * Set the CA certificate of the CAS server.
1937:      *
1938:      * @param string $cert        the PEM certificate file name of the CA that emited
1939:      * the cert of the server
1940:      * @param bool   $validate_cn valiate CN of the CAS server certificate
1941:      *
1942:      * @return void
1943:      */
1944:     public function setCasServerCACert($cert, $validate_cn)
1945:     {
1946:     // Argument validation
1947:         if (gettype($cert) != 'string') {
1948:             throw new CAS_TypeMismatchException($cert, '$cert', 'string');
1949:         }
1950:         if (gettype($validate_cn) != 'boolean') {
1951:             throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean');
1952:         }
1953:         if ( !file_exists($cert) && $this->_requestImplementation !== 'CAS_TestHarness_DummyRequest'){
1954:             throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation);
1955:         }
1956:         $this->_cas_server_ca_cert = $cert;
1957:         $this->_cas_server_cn_validate = $validate_cn;
1958:     }
1959: 
1960:     /**
1961:      * Set no SSL validation for the CAS server.
1962:      *
1963:      * @return void
1964:      */
1965:     public function setNoCasServerValidation()
1966:     {
1967:         $this->_no_cas_server_validation = true;
1968:     }
1969: 
1970:     /**
1971:      * This method is used to validate a CAS 1,0 ticket; halt on failure, and
1972:      * sets $validate_url, $text_reponse and $tree_response on success.
1973:      *
1974:      * @param string &$validate_url  reference to the the URL of the request to
1975:      * the CAS server.
1976:      * @param string &$text_response reference to the response of the CAS
1977:      * server, as is (XML text).
1978:      * @param string &$tree_response reference to the response of the CAS
1979:      * server, as a DOM XML tree.
1980:      * @param bool   $renew          true to force the authentication with the CAS server
1981:      *
1982:      * @return bool true when successfull and issue a CAS_AuthenticationException
1983:      * and false on an error
1984:      */
1985:     public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false)
1986:     {
1987:         phpCAS::traceBegin();
1988:         $result = false;
1989:         // build the URL to validate the ticket
1990:         $validate_url = $this->getServerServiceValidateURL()
1991:             .'&ticket='.urlencode($this->getTicket());
1992: 
1993:         if ( $renew ) {
1994:             // pass the renew
1995:             $validate_url .= '&renew=true';
1996:         }
1997: 
1998:         // open and read the URL
1999:         if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
2000:             phpCAS::trace(
2001:                 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
2002:             );
2003:             throw new CAS_AuthenticationException(
2004:                 $this, 'CAS 1.0 ticket not validated', $validate_url,
2005:                 true/*$no_response*/
2006:             );
2007:             $result = false;
2008:         }
2009: 
2010:         if (preg_match('/^no\n/', $text_response)) {
2011:             phpCAS::trace('Ticket has not been validated');
2012:             throw new CAS_AuthenticationException(
2013:                 $this, 'ST not validated', $validate_url, false/*$no_response*/,
2014:                 false/*$bad_response*/, $text_response
2015:             );
2016:             $result = false;
2017:         } else if (!preg_match('/^yes\n/', $text_response)) {
2018:             phpCAS::trace('ill-formed response');
2019:             throw new CAS_AuthenticationException(
2020:                 $this, 'Ticket not validated', $validate_url,
2021:                 false/*$no_response*/, true/*$bad_response*/, $text_response
2022:             );
2023:             $result = false;
2024:         }
2025:         // ticket has been validated, extract the user name
2026:         $arr = preg_split('/\n/', $text_response);
2027:         $this->_setUser(trim($arr[1]));
2028:         $result = true;
2029: 
2030:         if ($result) {
2031:             $this->_renameSession($this->getTicket());
2032:         }
2033:         // at this step, ticket has been validated and $this->_user has been set,
2034:         phpCAS::traceEnd(true);
2035:         return true;
2036:     }
2037: 
2038:     /** @} */
2039: 
2040: 
2041:     // ########################################################################
2042:     //  SAML VALIDATION
2043:     // ########################################################################
2044:     /**
2045:     * @addtogroup internalSAML
2046:     * @{
2047:     */
2048: 
2049:     /**
2050:      * This method is used to validate a SAML TICKET; halt on failure, and sets
2051:      * $validate_url, $text_reponse and $tree_response on success. These
2052:      * parameters are used later by CAS_Client::_validatePGT() for CAS proxies.
2053:      *
2054:      * @param string &$validate_url  reference to the the URL of the request to
2055:      * the CAS server.
2056:      * @param string &$text_response reference to the response of the CAS
2057:      * server, as is (XML text).
2058:      * @param string &$tree_response reference to the response of the CAS
2059:      * server, as a DOM XML tree.
2060:      * @param bool   $renew          true to force the authentication with the CAS server
2061:      *
2062:      * @return bool true when successfull and issue a CAS_AuthenticationException
2063:      * and false on an error
2064:      */
2065:     public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false)
2066:     {
2067:         phpCAS::traceBegin();
2068:         $result = false;
2069:         // build the URL to validate the ticket
2070:         $validate_url = $this->getServerSamlValidateURL();
2071: 
2072:         if ( $renew ) {
2073:             // pass the renew
2074:             $validate_url .= '&renew=true';
2075:         }
2076: 
2077:         // open and read the URL
2078:         if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
2079:             phpCAS::trace(
2080:                 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
2081:             );
2082:             throw new CAS_AuthenticationException(
2083:                 $this, 'SA not validated', $validate_url, true/*$no_response*/
2084:             );
2085:         }
2086: 
2087:         phpCAS::trace('server version: '.$this->getServerVersion());
2088: 
2089:         // analyze the result depending on the version
2090:         switch ($this->getServerVersion()) {
2091:         case SAML_VERSION_1_1:
2092:             // create new DOMDocument Object
2093:             $dom = new DOMDocument();
2094:             // Fix possible whitspace problems
2095:             $dom->preserveWhiteSpace = false;
2096:             // read the response of the CAS server into a DOM object
2097:             if (!($dom->loadXML($text_response))) {
2098:                 phpCAS::trace('dom->loadXML() failed');
2099:                 throw new CAS_AuthenticationException(
2100:                     $this, 'SA not validated', $validate_url,
2101:                     false/*$no_response*/, true/*$bad_response*/,
2102:                     $text_response
2103:                 );
2104:                 $result = false;
2105:             }
2106:             // read the root node of the XML tree
2107:             if (!($tree_response = $dom->documentElement)) {
2108:                 phpCAS::trace('documentElement() failed');
2109:                 throw new CAS_AuthenticationException(
2110:                     $this, 'SA not validated', $validate_url,
2111:                     false/*$no_response*/, true/*$bad_response*/,
2112:                     $text_response
2113:                 );
2114:                 $result = false;
2115:             } else if ( $tree_response->localName != 'Envelope' ) {
2116:                 // insure that tag name is 'Envelope'
2117:                 phpCAS::trace(
2118:                     'bad XML root node (should be `Envelope\' instead of `'
2119:                     .$tree_response->localName.'\''
2120:                 );
2121:                 throw new CAS_AuthenticationException(
2122:                     $this, 'SA not validated', $validate_url,
2123:                     false/*$no_response*/, true/*$bad_response*/,
2124:                     $text_response
2125:                 );
2126:                 $result = false;
2127:             } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
2128:                 // check for the NameIdentifier tag in the SAML response
2129:                 $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
2130:                 phpCAS::trace('NameIdentifier found');
2131:                 $user = trim($success_elements->item(0)->nodeValue);
2132:                 phpCAS::trace('user = `'.$user.'`');
2133:                 $this->_setUser($user);
2134:                 $this->_setSessionAttributes($text_response);
2135:                 $result = true;
2136:             } else {
2137:                 phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
2138:                 throw new CAS_AuthenticationException(
2139:                     $this, 'SA not validated', $validate_url,
2140:                     false/*$no_response*/, true/*$bad_response*/,
2141:                     $text_response
2142:                 );
2143:                 $result = false;
2144:             }
2145:         }
2146:         if ($result) {
2147:             $this->_renameSession($this->getTicket());
2148:         }
2149:         // at this step, ST has been validated and $this->_user has been set,
2150:         phpCAS::traceEnd($result);
2151:         return $result;
2152:     }
2153: 
2154:     /**
2155:      * This method will parse the DOM and pull out the attributes from the SAML
2156:      * payload and put them into an array, then put the array into the session.
2157:      *
2158:      * @param string $text_response the SAML payload.
2159:      *
2160:      * @return bool true when successfull and false if no attributes a found
2161:      */
2162:     private function _setSessionAttributes($text_response)
2163:     {
2164:         phpCAS::traceBegin();
2165: 
2166:         $result = false;
2167: 
2168:         $attr_array = array();
2169: 
2170:         // create new DOMDocument Object
2171:         $dom = new DOMDocument();
2172:         // Fix possible whitspace problems
2173:         $dom->preserveWhiteSpace = false;
2174:         if (($dom->loadXML($text_response))) {
2175:             $xPath = new DOMXpath($dom);
2176:             $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
2177:             $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
2178:             $nodelist = $xPath->query("//saml:Attribute");
2179: 
2180:             if ($nodelist) {
2181:                 foreach ($nodelist as $node) {
2182:                     $xres = $xPath->query("saml:AttributeValue", $node);
2183:                     $name = $node->getAttribute("AttributeName");
2184:                     $value_array = array();
2185:                     foreach ($xres as $node2) {
2186:                         $value_array[] = $node2->nodeValue;
2187:                     }
2188:                     $attr_array[$name] = $value_array;
2189:                 }
2190:                 // UGent addition...
2191:                 foreach ($attr_array as $attr_key => $attr_value) {
2192:                     if (count($attr_value) > 1) {
2193:                         $this->_attributes[$attr_key] = $attr_value;
2194:                         phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true));
2195:                     } else {
2196:                         $this->_attributes[$attr_key] = $attr_value[0];
2197:                         phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
2198:                     }
2199:                 }
2200:                 $result = true;
2201:             } else {
2202:                 phpCAS::trace("SAML Attributes are empty");
2203:                 $result = false;
2204:             }
2205:         }
2206:         phpCAS::traceEnd($result);
2207:         return $result;
2208:     }
2209: 
2210:     /** @} */
2211: 
2212:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2213:     // XX                                                                    XX
2214:     // XX                     PROXY FEATURES (CAS 2.0)                       XX
2215:     // XX                                                                    XX
2216:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2217: 
2218:     // ########################################################################
2219:     //  PROXYING
2220:     // ########################################################################
2221:     /**
2222:     * @addtogroup internalProxy
2223:     * @{
2224:     */
2225: 
2226:     /**
2227:      * A boolean telling if the client is a CAS proxy or not. Written by
2228:      * CAS_Client::CAS_Client(), read by CAS_Client::isProxy().
2229:      */
2230:     private $_proxy;
2231: 
2232:     /**
2233:      * Handler for managing service cookies.
2234:      */
2235:     private $_serviceCookieJar;
2236: 
2237:     /**
2238:      * Tells if a CAS client is a CAS proxy or not
2239:      *
2240:      * @return true when the CAS client is a CAs proxy, false otherwise
2241:      */
2242:     public function isProxy()
2243:     {
2244:         return $this->_proxy;
2245:     }
2246: 
2247: 
2248:     /** @} */
2249:     // ########################################################################
2250:     //  PGT
2251:     // ########################################################################
2252:     /**
2253:     * @addtogroup internalProxy
2254:     * @{
2255:     */
2256: 
2257:     /**
2258:      * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
2259:      * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and
2260:      * CAS_Client::_hasPGT().
2261:      *
2262:      * @hideinitializer
2263:      */
2264:     private $_pgt = '';
2265: 
2266:     /**
2267:      * This method returns the Proxy Granting Ticket given by the CAS server.
2268:      *
2269:      * @return string the Proxy Granting Ticket.
2270:      */
2271:     private function _getPGT()
2272:     {
2273:         return $this->_pgt;
2274:     }
2275: 
2276:     /**
2277:      * This method stores the Proxy Granting Ticket.
2278:      *
2279:      * @param string $pgt The Proxy Granting Ticket.
2280:      *
2281:      * @return void
2282:      */
2283:     private function _setPGT($pgt)
2284:     {
2285:         $this->_pgt = $pgt;
2286:     }
2287: 
2288:     /**
2289:      * This method tells if a Proxy Granting Ticket was stored.
2290:      *
2291:      * @return true if a Proxy Granting Ticket has been stored.
2292:      */
2293:     private function _hasPGT()
2294:     {
2295:         return !empty($this->_pgt);
2296:     }
2297: 
2298:     /** @} */
2299: 
2300:     // ########################################################################
2301:     //  CALLBACK MODE
2302:     // ########################################################################
2303:     /**
2304:     * @addtogroup internalCallback
2305:     * @{
2306:     */
2307:     /**
2308:      * each PHP script using phpCAS in proxy mode is its own callback to get the
2309:      * PGT back from the CAS server. callback_mode is detected by the constructor
2310:      * thanks to the GET parameters.
2311:      */
2312: 
2313:     /**
2314:      * a boolean to know if the CAS client is running in callback mode. Written by
2315:      * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode().
2316:      *
2317:      * @hideinitializer
2318:      */
2319:     private $_callback_mode = false;
2320: 
2321:     /**
2322:      * This method sets/unsets callback mode.
2323:      *
2324:      * @param bool $callback_mode true to set callback mode, false otherwise.
2325:      *
2326:      * @return void
2327:      */
2328:     private function _setCallbackMode($callback_mode)
2329:     {
2330:         $this->_callback_mode = $callback_mode;
2331:     }
2332: 
2333:     /**
2334:      * This method returns true when the CAs client is running i callback mode,
2335:      * false otherwise.
2336:      *
2337:      * @return A boolean.
2338:      */
2339:     private function _isCallbackMode()
2340:     {
2341:         return $this->_callback_mode;
2342:     }
2343: 
2344:     /**
2345:      * the URL that should be used for the PGT callback (in fact the URL of the
2346:      * current request without any CGI parameter). Written and read by
2347:      * CAS_Client::_getCallbackURL().
2348:      *
2349:      * @hideinitializer
2350:      */
2351:     private $_callback_url = '';
2352: 
2353:     /**
2354:      * This method returns the URL that should be used for the PGT callback (in
2355:      * fact the URL of the current request without any CGI parameter, except if
2356:      * phpCAS::setFixedCallbackURL() was used).
2357:      *
2358:      * @return The callback URL
2359:      */
2360:     private function _getCallbackURL()
2361:     {
2362:         // the URL is built when needed only
2363:         if ( empty($this->_callback_url) ) {
2364:             $final_uri = '';
2365:             // remove the ticket if present in the URL
2366:             $final_uri = 'https://';
2367:             $final_uri .= $this->_getClientUrl();
2368:             $request_uri = $_SERVER['REQUEST_URI'];
2369:             $request_uri = preg_replace('/\?.*$/', '', $request_uri);
2370:             $final_uri .= $request_uri;
2371:             $this->_callback_url = $final_uri;
2372:         }
2373:         return $this->_callback_url;
2374:     }
2375: 
2376:     /**
2377:      * This method sets the callback url.
2378:      *
2379:      * @param string $url url to set callback
2380:      *
2381:      * @return void
2382:      */
2383:     public function setCallbackURL($url)
2384:     {
2385:         // Sequence validation
2386:         $this->ensureIsProxy();
2387:         // Argument Validation
2388:         if (gettype($url) != 'string')
2389:             throw new CAS_TypeMismatchException($url, '$url', 'string');
2390: 
2391:         return $this->_callback_url = $url;
2392:     }
2393: 
2394:     /**
2395:      * This method is called by CAS_Client::CAS_Client() when running in callback
2396:      * mode. It stores the PGT and its PGT Iou, prints its output and halts.
2397:      *
2398:      * @return void
2399:      */
2400:     private function _callback()
2401:     {
2402:         phpCAS::traceBegin();
2403:         if (preg_match('/PGTIOU-[\.\-\w]/', $_GET['pgtIou'])) {
2404:             if (preg_match('/[PT]GT-[\.\-\w]/', $_GET['pgtId'])) {
2405:                 $this->printHTMLHeader('phpCAS callback');
2406:                 $pgt_iou = $_GET['pgtIou'];
2407:                 $pgt = $_GET['pgtId'];
2408:                 phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
2409:                 echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
2410:                 $this->_storePGT($pgt, $pgt_iou);
2411:                 $this->printHTMLFooter();
2412:                 phpCAS::traceExit("Successfull Callback");
2413:             } else {
2414:                 phpCAS::error('PGT format invalid' . $_GET['pgtId']);
2415:                 phpCAS::traceExit('PGT format invalid' . $_GET['pgtId']);
2416:             }
2417:         } else {
2418:             phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
2419:             phpCAS::traceExit('PGTiou format invalid' . $_GET['pgtIou']);
2420:         }
2421: 
2422:         // Flush the buffer to prevent from sending anything other then a 200
2423:         // Success Status back to the CAS Server. The Exception would normally
2424:         // report as a 500 error.
2425:         flush();
2426:         throw new CAS_GracefullTerminationException();
2427:     }
2428: 
2429: 
2430:     /** @} */
2431: 
2432:     // ########################################################################
2433:     //  PGT STORAGE
2434:     // ########################################################################
2435:     /**
2436:     * @addtogroup internalPGTStorage
2437:     * @{
2438:     */
2439: 
2440:     /**
2441:      * an instance of a class inheriting of PGTStorage, used to deal with PGT
2442:      * storage. Created by CAS_Client::setPGTStorageFile(), used
2443:      * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage().
2444:      *
2445:      * @hideinitializer
2446:      */
2447:     private $_pgt_storage = null;
2448: 
2449:     /**
2450:      * This method is used to initialize the storage of PGT's.
2451:      * Halts on error.
2452:      *
2453:      * @return void
2454:      */
2455:     private function _initPGTStorage()
2456:     {
2457:         // if no SetPGTStorageXxx() has been used, default to file
2458:         if ( !is_object($this->_pgt_storage) ) {
2459:             $this->setPGTStorageFile();
2460:         }
2461: 
2462:         // initializes the storage
2463:         $this->_pgt_storage->init();
2464:     }
2465: 
2466:     /**
2467:      * This method stores a PGT. Halts on error.
2468:      *
2469:      * @param string $pgt     the PGT to store
2470:      * @param string $pgt_iou its corresponding Iou
2471:      *
2472:      * @return void
2473:      */
2474:     private function _storePGT($pgt,$pgt_iou)
2475:     {
2476:         // ensure that storage is initialized
2477:         $this->_initPGTStorage();
2478:         // writes the PGT
2479:         $this->_pgt_storage->write($pgt, $pgt_iou);
2480:     }
2481: 
2482:     /**
2483:      * This method reads a PGT from its Iou and deletes the corresponding
2484:      * storage entry.
2485:      *
2486:      * @param string $pgt_iou the PGT Iou
2487:      *
2488:      * @return mul The PGT corresponding to the Iou, false when not found.
2489:      */
2490:     private function _loadPGT($pgt_iou)
2491:     {
2492:         // ensure that storage is initialized
2493:         $this->_initPGTStorage();
2494:         // read the PGT
2495:         return $this->_pgt_storage->read($pgt_iou);
2496:     }
2497: 
2498:     /**
2499:      * This method can be used to set a custom PGT storage object.
2500:      *
2501:      * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that
2502:      * inherits from the CAS_PGTStorage_AbstractStorage class
2503:      *
2504:      * @return void
2505:      */
2506:     public function setPGTStorage($storage)
2507:     {
2508:         // Sequence validation
2509:         $this->ensureIsProxy();
2510: 
2511:         // check that the storage has not already been set
2512:         if ( is_object($this->_pgt_storage) ) {
2513:             phpCAS::error('PGT storage already defined');
2514:         }
2515: 
2516:         // check to make sure a valid storage object was specified
2517:         if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) )
2518:             throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object');
2519: 
2520:         // store the PGTStorage object
2521:         $this->_pgt_storage = $storage;
2522:     }
2523: 
2524:     /**
2525:      * This method is used to tell phpCAS to store the response of the
2526:      * CAS server to PGT requests in a database.
2527:      *
2528:      * @param string $dsn_or_pdo     a dsn string to use for creating a PDO
2529:      * object or a PDO object
2530:      * @param string $username       the username to use when connecting to the
2531:      * database
2532:      * @param string $password       the password to use when connecting to the
2533:      * database
2534:      * @param string $table          the table to use for storing and retrieving
2535:      * PGTs
2536:      * @param string $driver_options any driver options to use when connecting
2537:      * to the database
2538:      *
2539:      * @return void
2540:      */
2541:     public function setPGTStorageDb(
2542:         $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null
2543:     ) {
2544:         // Sequence validation
2545:         $this->ensureIsProxy();
2546: 
2547:         // Argument validation
2548:         if ((is_object($dsn_or_pdo) && !($dsn_or_pdo instanceof PDO)) || gettype($dsn_or_pdo) != 'string')
2549:             throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object');
2550:         if (gettype($username) != 'string')
2551:             throw new CAS_TypeMismatchException($username, '$username', 'string');
2552:         if (gettype($password) != 'string')
2553:             throw new CAS_TypeMismatchException($password, '$password', 'string');
2554:         if (gettype($table) != 'string')
2555:             throw new CAS_TypeMismatchException($table, '$password', 'string');
2556: 
2557:         // create the storage object
2558:         $this->setPGTStorage(
2559:             new CAS_PGTStorage_Db(
2560:                 $this, $dsn_or_pdo, $username, $password, $table, $driver_options
2561:             )
2562:         );
2563:     }
2564: 
2565:     /**
2566:      * This method is used to tell phpCAS to store the response of the
2567:      * CAS server to PGT requests onto the filesystem.
2568:      *
2569:      * @param string $path the path where the PGT's should be stored
2570:      *
2571:      * @return void
2572:      */
2573:     public function setPGTStorageFile($path='')
2574:     {
2575:         // Sequence validation
2576:         $this->ensureIsProxy();
2577: 
2578:         // Argument validation
2579:         if (gettype($path) != 'string')
2580:             throw new CAS_TypeMismatchException($path, '$path', 'string');
2581: 
2582:         // create the storage object
2583:         $this->setPGTStorage(new CAS_PGTStorage_File($this, $path));
2584:     }
2585: 
2586: 
2587:     // ########################################################################
2588:     //  PGT VALIDATION
2589:     // ########################################################################
2590:     /**
2591:     * This method is used to validate a PGT; halt on failure.
2592:     *
2593:     * @param string &$validate_url the URL of the request to the CAS server.
2594:     * @param string $text_response the response of the CAS server, as is
2595:     *                              (XML text); result of
2596:     *                              CAS_Client::validateCAS10() or
2597:     *                              CAS_Client::validateCAS20().
2598:     * @param string $tree_response the response of the CAS server, as a DOM XML
2599:     * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
2600:     *
2601:     * @return bool true when successfull and issue a CAS_AuthenticationException
2602:     * and false on an error
2603:     */
2604:     private function _validatePGT(&$validate_url,$text_response,$tree_response)
2605:     {
2606:         phpCAS::traceBegin();
2607:         if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
2608:             phpCAS::trace('<proxyGrantingTicket> not found');
2609:             // authentication succeded, but no PGT Iou was transmitted
2610:             throw new CAS_AuthenticationException(
2611:                 $this, 'Ticket validated but no PGT Iou transmitted',
2612:                 $validate_url, false/*$no_response*/, false/*$bad_response*/,
2613:                 $text_response
2614:             );
2615:         } else {
2616:             // PGT Iou transmitted, extract it
2617:             $pgt_iou = trim(
2618:                 $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue
2619:             );
2620:             if (preg_match('/PGTIOU-[\.\-\w]/', $pgt_iou)) {
2621:                 $pgt = $this->_loadPGT($pgt_iou);
2622:                 if ( $pgt == false ) {
2623:                     phpCAS::trace('could not load PGT');
2624:                     throw new CAS_AuthenticationException(
2625:                         $this,
2626:                         'PGT Iou was transmitted but PGT could not be retrieved',
2627:                         $validate_url, false/*$no_response*/,
2628:                         false/*$bad_response*/, $text_response
2629:                     );
2630:                 }
2631:                 $this->_setPGT($pgt);
2632:             } else {
2633:                 phpCAS::trace('PGTiou format error');
2634:                 throw new CAS_AuthenticationException(
2635:                     $this, 'PGT Iou was transmitted but has wrong format',
2636:                     $validate_url, false/*$no_response*/, false/*$bad_response*/,
2637:                     $text_response
2638:                 );
2639:             }
2640:         }
2641:         phpCAS::traceEnd(true);
2642:         return true;
2643:     }
2644: 
2645:     // ########################################################################
2646:     //  PGT VALIDATION
2647:     // ########################################################################
2648: 
2649:     /**
2650:      * This method is used to retrieve PT's from the CAS server thanks to a PGT.
2651:      *
2652:      * @param string $target_service the service to ask for with the PT.
2653:      * @param string &$err_code      an error code (PHPCAS_SERVICE_OK on success).
2654:      * @param string &$err_msg       an error message (empty on success).
2655:      *
2656:      * @return a Proxy Ticket, or false on error.
2657:      */
2658:     public function retrievePT($target_service,&$err_code,&$err_msg)
2659:     {
2660:         // Argument validation
2661:         if (gettype($target_service) != 'string')
2662:             throw new CAS_TypeMismatchException($target_service, '$target_service', 'string');
2663: 
2664:         phpCAS::traceBegin();
2665: 
2666:         // by default, $err_msg is set empty and $pt to true. On error, $pt is
2667:         // set to false and $err_msg to an error message. At the end, if $pt is false
2668:         // and $error_msg is still empty, it is set to 'invalid response' (the most
2669:         // commonly encountered error).
2670:         $err_msg = '';
2671: 
2672:         // build the URL to retrieve the PT
2673:         $cas_url = $this->getServerProxyURL().'?targetService='
2674:             .urlencode($target_service).'&pgt='.$this->_getPGT();
2675: 
2676:         // open and read the URL
2677:         if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) {
2678:             phpCAS::trace(
2679:                 'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'
2680:             );
2681:             $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2682:             $err_msg = 'could not retrieve PT (no response from the CAS server)';
2683:             phpCAS::traceEnd(false);
2684:             return false;
2685:         }
2686: 
2687:         $bad_response = false;
2688: 
2689:         if ( !$bad_response ) {
2690:             // create new DOMDocument object
2691:             $dom = new DOMDocument();
2692:             // Fix possible whitspace problems
2693:             $dom->preserveWhiteSpace = false;
2694:             // read the response of the CAS server into a DOM object
2695:             if ( !($dom->loadXML($cas_response))) {
2696:                 phpCAS::trace('dom->loadXML() failed');
2697:                 // read failed
2698:                 $bad_response = true;
2699:             }
2700:         }
2701: 
2702:         if ( !$bad_response ) {
2703:             // read the root node of the XML tree
2704:             if ( !($root = $dom->documentElement) ) {
2705:                 phpCAS::trace('documentElement failed');
2706:                 // read failed
2707:                 $bad_response = true;
2708:             }
2709:         }
2710: 
2711:         if ( !$bad_response ) {
2712:             // insure that tag name is 'serviceResponse'
2713:             if ( $root->localName != 'serviceResponse' ) {
2714:                 phpCAS::trace('localName failed');
2715:                 // bad root node
2716:                 $bad_response = true;
2717:             }
2718:         }
2719: 
2720:         if ( !$bad_response ) {
2721:             // look for a proxySuccess tag
2722:             if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
2723:                 $proxy_success_list = $root->getElementsByTagName("proxySuccess");
2724: 
2725:                 // authentication succeded, look for a proxyTicket tag
2726:                 if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
2727:                     $err_code = PHPCAS_SERVICE_OK;
2728:                     $err_msg = '';
2729:                     $pt = trim(
2730:                         $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue
2731:                     );
2732:                     phpCAS::trace('original PT: '.trim($pt));
2733:                     phpCAS::traceEnd($pt);
2734:                     return $pt;
2735:                 } else {
2736:                     phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
2737:                 }
2738:             } else if ($root->getElementsByTagName("proxyFailure")->length != 0) {
2739:                 // look for a proxyFailure tag
2740:                 $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
2741: 
2742:                 // authentication failed, extract the error
2743:                 $err_code = PHPCAS_SERVICE_PT_FAILURE;
2744:                 $err_msg = 'PT retrieving failed (code=`'
2745:                 .$proxy_failure_list->item(0)->getAttribute('code')
2746:                 .'\', message=`'
2747:                 .trim($proxy_failure_list->item(0)->nodeValue)
2748:                 .'\')';
2749:                 phpCAS::traceEnd(false);
2750:                 return false;
2751:             } else {
2752:                 phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
2753:             }
2754:         }
2755: 
2756:         // at this step, we are sure that the response of the CAS server was
2757:         // illformed
2758:         $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
2759:         $err_msg = 'Invalid response from the CAS server (response=`'
2760:             .$cas_response.'\')';
2761: 
2762:         phpCAS::traceEnd(false);
2763:         return false;
2764:     }
2765: 
2766:     /** @} */
2767: 
2768:     // ########################################################################
2769:     // READ CAS SERVER ANSWERS
2770:     // ########################################################################
2771: 
2772:     /**
2773:      * @addtogroup internalMisc
2774:      * @{
2775:      */
2776: 
2777:     /**
2778:      * This method is used to acces a remote URL.
2779:      *
2780:      * @param string $url      the URL to access.
2781:      * @param string &$headers an array containing the HTTP header lines of the
2782:      * response (an empty array on failure).
2783:      * @param string &$body    the body of the response, as a string (empty on
2784:      * failure).
2785:      * @param string &$err_msg an error message, filled on failure.
2786:      *
2787:      * @return true on success, false otherwise (in this later case, $err_msg
2788:      * contains an error message).
2789:      */
2790:     private function _readURL($url, &$headers, &$body, &$err_msg)
2791:     {
2792:         phpCAS::traceBegin();
2793:         $className = $this->_requestImplementation;
2794:         $request = new $className();
2795: 
2796:         if (count($this->_curl_options)) {
2797:             $request->setCurlOptions($this->_curl_options);
2798:         }
2799: 
2800:         $request->setUrl($url);
2801: 
2802:         if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
2803:             phpCAS::error(
2804:                 'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'
2805:             );
2806:         }
2807:         if ($this->_cas_server_ca_cert != '') {
2808:             $request->setSslCaCert(
2809:                 $this->_cas_server_ca_cert, $this->_cas_server_cn_validate
2810:             );
2811:         }
2812: 
2813:         // add extra stuff if SAML
2814:         if ($this->getServerVersion() == SAML_VERSION_1_1) {
2815:             $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
2816:             $request->addHeader("cache-control: no-cache");
2817:             $request->addHeader("pragma: no-cache");
2818:             $request->addHeader("accept: text/xml");
2819:             $request->addHeader("connection: keep-alive");
2820:             $request->addHeader("content-type: text/xml");
2821:             $request->makePost();
2822:             $request->setPostBody($this->_buildSAMLPayload());
2823:         }
2824: 
2825:         if ($request->send()) {
2826:             $headers = $request->getResponseHeaders();
2827:             $body = $request->getResponseBody();
2828:             $err_msg = '';
2829:             phpCAS::traceEnd(true);
2830:             return true;
2831:         } else {
2832:             $headers = '';
2833:             $body = '';
2834:             $err_msg = $request->getErrorMessage();
2835:             phpCAS::traceEnd(false);
2836:             return false;
2837:         }
2838:     }
2839: 
2840:     /**
2841:      * This method is used to build the SAML POST body sent to /samlValidate URL.
2842:      *
2843:      * @return the SOAP-encased SAMLP artifact (the ticket).
2844:      */
2845:     private function _buildSAMLPayload()
2846:     {
2847:         phpCAS::traceBegin();
2848: 
2849:         //get the ticket
2850:         $sa = urlencode($this->getTicket());
2851: 
2852:         $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST
2853:             .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE
2854:             .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
2855: 
2856:         phpCAS::traceEnd($body);
2857:         return ($body);
2858:     }
2859: 
2860:     /** @} **/
2861: 
2862:     // ########################################################################
2863:     // ACCESS TO EXTERNAL SERVICES
2864:     // ########################################################################
2865: 
2866:     /**
2867:      * @addtogroup internalProxyServices
2868:      * @{
2869:      */
2870: 
2871: 
2872:     /**
2873:      * Answer a proxy-authenticated service handler.
2874:      *
2875:      * @param string $type The service type. One of:
2876:      * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST,
2877:      * PHPCAS_PROXIED_SERVICE_IMAP
2878:      *
2879:      * @return CAS_ProxiedService
2880:      * @throws InvalidArgumentException If the service type is unknown.
2881:      */
2882:     public function getProxiedService ($type)
2883:     {
2884:         // Sequence validation
2885:         $this->ensureIsProxy();
2886:         $this->ensureAuthenticationCallSuccessful();
2887: 
2888:         // Argument validation
2889:         if (gettype($type) != 'string')
2890:             throw new CAS_TypeMismatchException($type, '$type', 'string');
2891: 
2892:         switch ($type) {
2893:         case PHPCAS_PROXIED_SERVICE_HTTP_GET:
2894:         case PHPCAS_PROXIED_SERVICE_HTTP_POST:
2895:             $requestClass = $this->_requestImplementation;
2896:             $request = new $requestClass();
2897:             if (count($this->_curl_options)) {
2898:                 $request->setCurlOptions($this->_curl_options);
2899:             }
2900:             $proxiedService = new $type($request, $this->_serviceCookieJar);
2901:             if ($proxiedService instanceof CAS_ProxiedService_Testable) {
2902:                 $proxiedService->setCasClient($this);
2903:             }
2904:             return $proxiedService;
2905:         case PHPCAS_PROXIED_SERVICE_IMAP;
2906:             $proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
2907:             if ($proxiedService instanceof CAS_ProxiedService_Testable) {
2908:                 $proxiedService->setCasClient($this);
2909:             }
2910:             return $proxiedService;
2911:         default:
2912:             throw new CAS_InvalidArgumentException(
2913:                 "Unknown proxied-service type, $type."
2914:             );
2915:         }
2916:     }
2917: 
2918:     /**
2919:      * Initialize a proxied-service handler with the proxy-ticket it should use.
2920:      *
2921:      * @param CAS_ProxiedService $proxiedService service handler
2922:      *
2923:      * @return void
2924:      *
2925:      * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
2926:      *      The code of the Exception will be one of:
2927:      *          PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
2928:      *          PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
2929:      *          PHPCAS_SERVICE_PT_FAILURE
2930:      * @throws CAS_ProxiedService_Exception If there is a failure getting the
2931:      * url from the proxied service.
2932:      */
2933:     public function initializeProxiedService (CAS_ProxiedService $proxiedService)
2934:     {
2935:         // Sequence validation
2936:         $this->ensureIsProxy();
2937:         $this->ensureAuthenticationCallSuccessful();
2938: 
2939:         $url = $proxiedService->getServiceUrl();
2940:         if (!is_string($url)) {
2941:             throw new CAS_ProxiedService_Exception(
2942:                 "Proxied Service ".get_class($proxiedService)
2943:                 ."->getServiceUrl() should have returned a string, returned a "
2944:                 .gettype($url)." instead."
2945:             );
2946:         }
2947:         $pt = $this->retrievePT($url, $err_code, $err_msg);
2948:         if (!$pt) {
2949:             throw new CAS_ProxyTicketException($err_msg, $err_code);
2950:         }
2951:         $proxiedService->setProxyTicket($pt);
2952:     }
2953: 
2954:     /**
2955:      * This method is used to access an HTTP[S] service.
2956:      *
2957:      * @param string $url       the service to access.
2958:      * @param int    &$err_code an error code Possible values are
2959:      * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
2960:      * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
2961:      * PHPCAS_SERVICE_NOT_AVAILABLE.
2962:      * @param string &$output   the output of the service (also used to give an error
2963:      * message on failure).
2964:      *
2965:      * @return true on success, false otherwise (in this later case, $err_code
2966:      * gives the reason why it failed and $output contains an error message).
2967:      */
2968:     public function serviceWeb($url,&$err_code,&$output)
2969:     {
2970:         // Sequence validation
2971:         $this->ensureIsProxy();
2972:         $this->ensureAuthenticationCallSuccessful();
2973: 
2974:         // Argument validation
2975:         if (gettype($url) != 'string')
2976:             throw new CAS_TypeMismatchException($url, '$url', 'string');
2977: 
2978:         try {
2979:             $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
2980:             $service->setUrl($url);
2981:             $service->send();
2982:             $output = $service->getResponseBody();
2983:             $err_code = PHPCAS_SERVICE_OK;
2984:             return true;
2985:         } catch (CAS_ProxyTicketException $e) {
2986:             $err_code = $e->getCode();
2987:             $output = $e->getMessage();
2988:             return false;
2989:         } catch (CAS_ProxiedService_Exception $e) {
2990:             $lang = $this->getLangObj();
2991:             $output = sprintf(
2992:                 $lang->getServiceUnavailable(), $url, $e->getMessage()
2993:             );
2994:             $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2995:             return false;
2996:         }
2997:     }
2998: 
2999:     /**
3000:      * This method is used to access an IMAP/POP3/NNTP service.
3001:      *
3002:      * @param string $url        a string giving the URL of the service, including
3003:      * the mailing box for IMAP URLs, as accepted by imap_open().
3004:      * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket
3005:      * @param string $flags      options given to imap_open().
3006:      * @param int    &$err_code  an error code Possible values are
3007:      * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
3008:      * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
3009:      *  PHPCAS_SERVICE_NOT_AVAILABLE.
3010:      * @param string &$err_msg   an error message on failure
3011:      * @param string &$pt        the Proxy Ticket (PT) retrieved from the CAS
3012:      * server to access the URL on success, false on error).
3013:      *
3014:      * @return object an IMAP stream on success, false otherwise (in this later
3015:      *  case, $err_code gives the reason why it failed and $err_msg contains an
3016:      *  error message).
3017:      */
3018:     public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
3019:     {
3020:         // Sequence validation
3021:         $this->ensureIsProxy();
3022:         $this->ensureAuthenticationCallSuccessful();
3023: 
3024:         // Argument validation
3025:         if (gettype($url) != 'string')
3026:             throw new CAS_TypeMismatchException($url, '$url', 'string');
3027:         if (gettype($serviceUrl) != 'string')
3028:             throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string');
3029:         if (gettype($flags) != 'integer')
3030:             throw new CAS_TypeMismatchException($flags, '$flags', 'string');
3031: 
3032:         try {
3033:             $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
3034:             $service->setServiceUrl($serviceUrl);
3035:             $service->setMailbox($url);
3036:             $service->setOptions($flags);
3037: 
3038:             $stream = $service->open();
3039:             $err_code = PHPCAS_SERVICE_OK;
3040:             $pt = $service->getImapProxyTicket();
3041:             return $stream;
3042:         } catch (CAS_ProxyTicketException $e) {
3043:             $err_msg = $e->getMessage();
3044:             $err_code = $e->getCode();
3045:             $pt = false;
3046:             return false;
3047:         } catch (CAS_ProxiedService_Exception $e) {
3048:             $lang = $this->getLangObj();
3049:             $err_msg = sprintf(
3050:                 $lang->getServiceUnavailable(),
3051:                 $url,
3052:                 $e->getMessage()
3053:             );
3054:             $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
3055:             $pt = false;
3056:             return false;
3057:         }
3058:     }
3059: 
3060:     /** @} **/
3061: 
3062:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3063:     // XX                                                                    XX
3064:     // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
3065:     // XX                                                                    XX
3066:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3067: 
3068:     // ########################################################################
3069:     //  PT
3070:     // ########################################################################
3071:     /**
3072:     * @addtogroup internalService
3073:     * @{
3074:     */
3075: 
3076:     /**
3077:      * This array will store a list of proxies in front of this application. This
3078:      * property will only be populated if this script is being proxied rather than
3079:      * accessed directly.
3080:      *
3081:      * It is set in CAS_Client::validateCAS20() and can be read by
3082:      * CAS_Client::getProxies()
3083:      *
3084:      * @access private
3085:      */
3086:     private $_proxies = array();
3087: 
3088:     /**
3089:      * Answer an array of proxies that are sitting in front of this application.
3090:      *
3091:      * This method will only return a non-empty array if we have received and
3092:      * validated a Proxy Ticket.
3093:      *
3094:      * @return array
3095:      * @access public
3096:      */
3097:     public function getProxies()
3098:     {
3099:         return $this->_proxies;
3100:     }
3101: 
3102:     /**
3103:      * Set the Proxy array, probably from persistant storage.
3104:      *
3105:      * @param array $proxies An array of proxies
3106:      *
3107:      * @return void
3108:      * @access private
3109:      */
3110:     private function _setProxies($proxies)
3111:     {
3112:         $this->_proxies = $proxies;
3113:         if (!empty($proxies)) {
3114:             // For proxy-authenticated requests people are not viewing the URL
3115:             // directly since the client is another application making a
3116:             // web-service call.
3117:             // Because of this, stripping the ticket from the URL is unnecessary
3118:             // and causes another web-service request to be performed. Additionally,
3119:             // if session handling on either the client or the server malfunctions
3120:             // then the subsequent request will not complete successfully.
3121:             $this->setNoClearTicketsFromUrl();
3122:         }
3123:     }
3124: 
3125:     /**
3126:      * A container of patterns to be allowed as proxies in front of the cas client.
3127:      *
3128:      * @var CAS_ProxyChain_AllowedList
3129:      */
3130:     private $_allowed_proxy_chains;
3131: 
3132:     /**
3133:      * Answer the CAS_ProxyChain_AllowedList object for this client.
3134:      *
3135:      * @return CAS_ProxyChain_AllowedList
3136:      */
3137:     public function getAllowedProxyChains ()
3138:     {
3139:         if (empty($this->_allowed_proxy_chains)) {
3140:             $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList();
3141:         }
3142:         return $this->_allowed_proxy_chains;
3143:     }
3144: 
3145:     /** @} */
3146:     // ########################################################################
3147:     //  PT VALIDATION
3148:     // ########################################################################
3149:     /**
3150:     * @addtogroup internalProxied
3151:     * @{
3152:     */
3153: 
3154:     /**
3155:      * This method is used to validate a cas 2.0 ST or PT; halt on failure
3156:      * Used for all CAS 2.0 validations
3157:      *
3158:      * @param string &$validate_url  the url of the reponse
3159:      * @param string &$text_response the text of the repsones
3160:      * @param string &$tree_response the domxml tree of the respones
3161:      * @param bool   $renew          true to force the authentication with the CAS server
3162:      *
3163:      * @return bool true when successfull and issue a CAS_AuthenticationException
3164:      * and false on an error
3165:      */
3166:     public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false)
3167:     {
3168:         phpCAS::traceBegin();
3169:         phpCAS::trace($text_response);
3170:         $result = false;
3171:         // build the URL to validate the ticket
3172:         if ($this->getAllowedProxyChains()->isProxyingAllowed()) {
3173:             $validate_url = $this->getServerProxyValidateURL().'&ticket='
3174:                 .urlencode($this->getTicket());
3175:         } else {
3176:             $validate_url = $this->getServerServiceValidateURL().'&ticket='
3177:                 .urlencode($this->getTicket());
3178:         }
3179: 
3180:         if ( $this->isProxy() ) {
3181:             // pass the callback url for CAS proxies
3182:             $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL());
3183:         }
3184: 
3185:         if ( $renew ) {
3186:             // pass the renew
3187:             $validate_url .= '&renew=true';
3188:         }
3189: 
3190:         // open and read the URL
3191:         if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
3192:             phpCAS::trace(
3193:                 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
3194:             );
3195:             throw new CAS_AuthenticationException(
3196:                 $this, 'Ticket not validated', $validate_url,
3197:                 true/*$no_response*/
3198:             );
3199:             $result = false;
3200:         }
3201: 
3202:         // create new DOMDocument object
3203:         $dom = new DOMDocument();
3204:         // Fix possible whitspace problems
3205:         $dom->preserveWhiteSpace = false;
3206:         // CAS servers should only return data in utf-8
3207:         $dom->encoding = "utf-8";
3208:         // read the response of the CAS server into a DOMDocument object
3209:         if ( !($dom->loadXML($text_response))) {
3210:             // read failed
3211:             throw new CAS_AuthenticationException(
3212:                 $this, 'Ticket not validated', $validate_url,
3213:                 false/*$no_response*/, true/*$bad_response*/, $text_response
3214:             );
3215:             $result = false;
3216:         } else if ( !($tree_response = $dom->documentElement) ) {
3217:             // read the root node of the XML tree
3218:             // read failed
3219:             throw new CAS_AuthenticationException(
3220:                 $this, 'Ticket not validated', $validate_url,
3221:                 false/*$no_response*/, true/*$bad_response*/, $text_response
3222:             );
3223:             $result = false;
3224:         } else if ($tree_response->localName != 'serviceResponse') {
3225:             // insure that tag name is 'serviceResponse'
3226:             // bad root node
3227:             throw new CAS_AuthenticationException(
3228:                 $this, 'Ticket not validated', $validate_url,
3229:                 false/*$no_response*/, true/*$bad_response*/, $text_response
3230:             );
3231:             $result = false;
3232:         } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
3233:             // authentication failed, extract the error code and message and throw exception
3234:             $auth_fail_list = $tree_response
3235:                 ->getElementsByTagName("authenticationFailure");
3236:             throw new CAS_AuthenticationException(
3237:                 $this, 'Ticket not validated', $validate_url,
3238:                 false/*$no_response*/, false/*$bad_response*/,
3239:                 $text_response,
3240:                 $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
3241:                 trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
3242:             );
3243:             $result = false;
3244:         } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
3245:             // authentication succeded, extract the user name
3246:             $success_elements = $tree_response
3247:                 ->getElementsByTagName("authenticationSuccess");
3248:             if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
3249:                 // no user specified => error
3250:                 throw new CAS_AuthenticationException(
3251:                     $this, 'Ticket not validated', $validate_url,
3252:                     false/*$no_response*/, true/*$bad_response*/, $text_response
3253:                 );
3254:                 $result = false;
3255:             } else {
3256:                 $this->_setUser(
3257:                     trim(
3258:                         $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue
3259:                     )
3260:                 );
3261:                 $this->_readExtraAttributesCas20($success_elements);
3262:                 // Store the proxies we are sitting behind for authorization checking
3263:                 $proxyList = array();
3264:                 if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
3265:                     foreach ($arr as $proxyElem) {
3266:                         phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue);
3267:                         $proxyList[] = trim($proxyElem->nodeValue);
3268:                     }
3269:                     $this->_setProxies($proxyList);
3270:                     phpCAS::trace("Storing Proxy List");
3271:                 }
3272:                 // Check if the proxies in front of us are allowed
3273:                 if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) {
3274:                     throw new CAS_AuthenticationException(
3275:                         $this, 'Proxy not allowed', $validate_url,
3276:                         false/*$no_response*/, true/*$bad_response*/,
3277:                         $text_response
3278:                     );
3279:                     $result = false;
3280:                 } else {
3281:                     $result = true;
3282:                 }
3283:             }
3284:         } else {
3285:             throw new CAS_AuthenticationException(
3286:                 $this, 'Ticket not validated', $validate_url,
3287:                 false/*$no_response*/, true/*$bad_response*/,
3288:                 $text_response
3289:             );
3290:             $result = false;
3291:         }
3292:         if ($result) {
3293:             $this->_renameSession($this->getTicket());
3294:         }
3295:         // at this step, Ticket has been validated and $this->_user has been set,
3296: 
3297:         phpCAS::traceEnd($result);
3298:         return $result;
3299:     }
3300: 
3301: 
3302:     /**
3303:      * This method will parse the DOM and pull out the attributes from the XML
3304:      * payload and put them into an array, then put the array into the session.
3305:      *
3306:      * @param string $success_elements payload of the response
3307:      *
3308:      * @return bool true when successfull, halt otherwise by calling
3309:      * CAS_Client::_authError().
3310:      */
3311:     private function _readExtraAttributesCas20($success_elements)
3312:     {
3313:         phpCAS::traceBegin();
3314: 
3315:         $extra_attributes = array();
3316: 
3317:         // "Jasig Style" Attributes:
3318:         //
3319:         //  <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3320:         //      <cas:authenticationSuccess>
3321:         //          <cas:user>jsmith</cas:user>
3322:         //          <cas:attributes>
3323:         //              <cas:attraStyle>RubyCAS</cas:attraStyle>
3324:         //              <cas:surname>Smith</cas:surname>
3325:         //              <cas:givenName>John</cas:givenName>
3326:         //              <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
3327:         //              <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
3328:         //          </cas:attributes>
3329:         //          <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3330:         //      </cas:authenticationSuccess>
3331:         //  </cas:serviceResponse>
3332:         //
3333:         if ($this->_casAttributeParserCallbackFunction !== null
3334:             && is_callable($this->_casAttributeParserCallbackFunction)
3335:         ) {
3336:             array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0));
3337:             phpCas :: trace("Calling attritubeParser callback");
3338:             $extra_attributes =  call_user_func_array(
3339:                 $this->_casAttributeParserCallbackFunction,
3340:                 $this->_casAttributeParserCallbackArgs
3341:             );
3342:         } elseif ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
3343:             $attr_nodes = $success_elements->item(0)
3344:                 ->getElementsByTagName("attributes");
3345:             phpCas :: trace("Found nested jasig style attributes");
3346:             if ($attr_nodes->item(0)->hasChildNodes()) {
3347:                 // Nested Attributes
3348:                 foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
3349:                     phpCas :: trace(
3350:                         "Attribute [".$attr_child->localName."] = "
3351:                         .$attr_child->nodeValue
3352:                     );
3353:                     $this->_addAttributeToArray(
3354:                         $extra_attributes, $attr_child->localName,
3355:                         $attr_child->nodeValue
3356:                     );
3357:                 }
3358:             }
3359:         } else {
3360:             // "RubyCAS Style" attributes
3361:             //
3362:             //  <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3363:             //      <cas:authenticationSuccess>
3364:             //          <cas:user>jsmith</cas:user>
3365:             //
3366:             //          <cas:attraStyle>RubyCAS</cas:attraStyle>
3367:             //          <cas:surname>Smith</cas:surname>
3368:             //          <cas:givenName>John</cas:givenName>
3369:             //          <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
3370:             //          <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
3371:             //
3372:             //          <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3373:             //      </cas:authenticationSuccess>
3374:             //  </cas:serviceResponse>
3375:             //
3376:             phpCas :: trace("Testing for rubycas style attributes");
3377:             $childnodes = $success_elements->item(0)->childNodes;
3378:             foreach ($childnodes as $attr_node) {
3379:                 switch ($attr_node->localName) {
3380:                 case 'user':
3381:                 case 'proxies':
3382:                 case 'proxyGrantingTicket':
3383:                     continue;
3384:                 default:
3385:                     if (strlen(trim($attr_node->nodeValue))) {
3386:                         phpCas :: trace(
3387:                             "Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue
3388:                         );
3389:                         $this->_addAttributeToArray(
3390:                             $extra_attributes, $attr_node->localName,
3391:                             $attr_node->nodeValue
3392:                         );
3393:                     }
3394:                 }
3395:             }
3396:         }
3397: 
3398:         // "Name-Value" attributes.
3399:         //
3400:         // Attribute format from these mailing list thread:
3401:         // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
3402:         // Note: This is a less widely used format, but in use by at least two institutions.
3403:         //
3404:         //  <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3405:         //      <cas:authenticationSuccess>
3406:         //          <cas:user>jsmith</cas:user>
3407:         //
3408:         //          <cas:attribute name='attraStyle' value='Name-Value' />
3409:         //          <cas:attribute name='surname' value='Smith' />
3410:         //          <cas:attribute name='givenName' value='John' />
3411:         //          <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
3412:         //          <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
3413:         //
3414:         //          <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3415:         //      </cas:authenticationSuccess>
3416:         //  </cas:serviceResponse>
3417:         //
3418:         if (!count($extra_attributes)
3419:             && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0
3420:         ) {
3421:             $attr_nodes = $success_elements->item(0)
3422:                 ->getElementsByTagName("attribute");
3423:             $firstAttr = $attr_nodes->item(0);
3424:             if (!$firstAttr->hasChildNodes()
3425:                 && $firstAttr->hasAttribute('name')
3426:                 && $firstAttr->hasAttribute('value')
3427:             ) {
3428:                 phpCas :: trace("Found Name-Value style attributes");
3429:                 // Nested Attributes
3430:                 foreach ($attr_nodes as $attr_node) {
3431:                     if ($attr_node->hasAttribute('name')
3432:                         && $attr_node->hasAttribute('value')
3433:                     ) {
3434:                         phpCas :: trace(
3435:                             "Attribute [".$attr_node->getAttribute('name')
3436:                             ."] = ".$attr_node->getAttribute('value')
3437:                         );
3438:                         $this->_addAttributeToArray(
3439:                             $extra_attributes, $attr_node->getAttribute('name'),
3440:                             $attr_node->getAttribute('value')
3441:                         );
3442:                     }
3443:                 }
3444:             }
3445:         }
3446: 
3447:         $this->setAttributes($extra_attributes);
3448:         phpCAS::traceEnd();
3449:         return true;
3450:     }
3451: 
3452:     /**
3453:      * Add an attribute value to an array of attributes.
3454:      *
3455:      * @param array  &$attributeArray reference to array
3456:      * @param string $name            name of attribute
3457:      * @param string $value           value of attribute
3458:      *
3459:      * @return void
3460:      */
3461:     private function _addAttributeToArray(array &$attributeArray, $name, $value)
3462:     {
3463:         // If multiple attributes exist, add as an array value
3464:         if (isset($attributeArray[$name])) {
3465:             // Initialize the array with the existing value
3466:             if (!is_array($attributeArray[$name])) {
3467:                 $existingValue = $attributeArray[$name];
3468:                 $attributeArray[$name] = array($existingValue);
3469:             }
3470: 
3471:             $attributeArray[$name][] = trim($value);
3472:         } else {
3473:             $attributeArray[$name] = trim($value);
3474:         }
3475:     }
3476: 
3477:     /** @} */
3478: 
3479:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3480:     // XX                                                                    XX
3481:     // XX                               MISC                                 XX
3482:     // XX                                                                    XX
3483:     // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3484: 
3485:     /**
3486:      * @addtogroup internalMisc
3487:      * @{
3488:      */
3489: 
3490:     // ########################################################################
3491:     //  URL
3492:     // ########################################################################
3493:     /**
3494:     * the URL of the current request (without any ticket CGI parameter). Written
3495:     * and read by CAS_Client::getURL().
3496:     *
3497:     * @hideinitializer
3498:     */
3499:     private $_url = '';
3500: 
3501: 
3502:     /**
3503:      * This method sets the URL of the current request
3504:      *
3505:      * @param string $url url to set for service
3506:      *
3507:      * @return void
3508:      */
3509:     public function setURL($url)
3510:     {
3511:         // Argument Validation
3512:         if (gettype($url) != 'string')
3513:             throw new CAS_TypeMismatchException($url, '$url', 'string');
3514: 
3515:         $this->_url = $url;
3516:     }
3517: 
3518:     /**
3519:      * This method returns the URL of the current request (without any ticket
3520:      * CGI parameter).
3521:      *
3522:      * @return The URL
3523:      */
3524:     public function getURL()
3525:     {
3526:         phpCAS::traceBegin();
3527:         // the URL is built when needed only
3528:         if ( empty($this->_url) ) {
3529:             $final_uri = '';
3530:             // remove the ticket if present in the URL
3531:             $final_uri = ($this->_isHttps()) ? 'https' : 'http';
3532:             $final_uri .= '://';
3533: 
3534:             $final_uri .= $this->_getClientUrl();
3535:             $request_uri    = explode('?', $_SERVER['REQUEST_URI'], 2);
3536:             $final_uri      .= $request_uri[0];
3537: 
3538:             if (isset($request_uri[1]) && $request_uri[1]) {
3539:                 $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]);
3540: 
3541:                 // If the query string still has anything left,
3542:                 // append it to the final URI
3543:                 if ($query_string !== '') {
3544:                     $final_uri  .= "?$query_string";
3545:                 }
3546:             }
3547: 
3548:             phpCAS::trace("Final URI: $final_uri");
3549:             $this->setURL($final_uri);
3550:         }
3551:         phpCAS::traceEnd($this->_url);
3552:         return $this->_url;
3553:     }
3554: 
3555:     /**
3556:      * This method sets the base URL of the CAS server.
3557:      *
3558:      * @param string $url the base URL
3559:      *
3560:      * @return string base url
3561:      */
3562:     public function setBaseURL($url)
3563:     {
3564:         // Argument Validation
3565:         if (gettype($url) != 'string')
3566:             throw new CAS_TypeMismatchException($url, '$url', 'string');
3567: 
3568:         return $this->_server['base_url'] = $url;
3569:     }
3570: 
3571: 
3572:     /**
3573:      * Try to figure out the phpCas client URL with possible Proxys / Ports etc.
3574:      *
3575:      * @return string Server URL with domain:port
3576:      */
3577:     private function _getClientUrl()
3578:     {
3579:         $server_url = '';
3580:         if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
3581:             // explode the host list separated by comma and use the first host
3582:             $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
3583:             // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default
3584:             return $hosts[0];
3585:         } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
3586:             $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
3587:         } else {
3588:             if (empty($_SERVER['SERVER_NAME'])) {
3589:                 $server_url = $_SERVER['HTTP_HOST'];
3590:             } else {
3591:                 $server_url = $_SERVER['SERVER_NAME'];
3592:             }
3593:         }
3594:         if (!strpos($server_url, ':')) {
3595:             if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
3596:                 $server_port = $_SERVER['SERVER_PORT'];
3597:             } else {
3598:                 $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
3599:                 $server_port = $ports[0];
3600:             }
3601: 
3602:             if ( ($this->_isHttps() && $server_port!=443)
3603:                 || (!$this->_isHttps() && $server_port!=80)
3604:             ) {
3605:                 $server_url .= ':';
3606:                 $server_url .= $server_port;
3607:             }
3608:         }
3609:         return $server_url;
3610:     }
3611: 
3612:     /**
3613:      * This method checks to see if the request is secured via HTTPS
3614:      *
3615:      * @return bool true if https, false otherwise
3616:      */
3617:     private function _isHttps()
3618:     {
3619:         if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
3620:             return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
3621:         } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) {
3622:             return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https');
3623:         } elseif ( isset($_SERVER['HTTPS'])
3624:             && !empty($_SERVER['HTTPS'])
3625:             && strcasecmp($_SERVER['HTTPS'], 'off') !== 0
3626:         ) {
3627:             return true;
3628:         }
3629:         return false;
3630: 
3631:     }
3632: 
3633:     /**
3634:      * Removes a parameter from a query string
3635:      *
3636:      * @param string $parameterName name of parameter
3637:      * @param string $queryString   query string
3638:      *
3639:      * @return string new query string
3640:      *
3641:      * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
3642:      */
3643:     private function _removeParameterFromQueryString($parameterName, $queryString)
3644:     {
3645:         $parameterName  = preg_quote($parameterName);
3646:         return preg_replace(
3647:             "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/",
3648:             '', $queryString
3649:         );
3650:     }
3651: 
3652:     /**
3653:      * This method is used to append query parameters to an url. Since the url
3654:      * might already contain parameter it has to be detected and to build a proper
3655:      * URL
3656:      *
3657:      * @param string $url   base url to add the query params to
3658:      * @param string $query params in query form with & separated
3659:      *
3660:      * @return url with query params
3661:      */
3662:     private function _buildQueryUrl($url, $query)
3663:     {
3664:         $url .= (strstr($url, '?') === false) ? '?' : '&';
3665:         $url .= $query;
3666:         return $url;
3667:     }
3668: 
3669:     /**
3670:      * Renaming the session
3671:      *
3672:      * @param string $ticket name of the ticket
3673:      *
3674:      * @return void
3675:      */
3676:     private function _renameSession($ticket)
3677:     {
3678:         phpCAS::traceBegin();
3679:         if ($this->getChangeSessionID()) {
3680:             if (!empty($this->_user)) {
3681:                 $old_session = $_SESSION;
3682:                 phpCAS :: trace("Killing session: ". session_id());
3683:                 session_destroy();
3684:                 // set up a new session, of name based on the ticket
3685:                 $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket);
3686:                 phpCAS :: trace("Starting session: ". $session_id);
3687:                 session_id($session_id);
3688:                 session_start();
3689:                 phpCAS :: trace("Restoring old session vars");
3690:                 $_SESSION = $old_session;
3691:             } else {
3692:                 phpCAS :: trace (
3693:                     'Session should only be renamed after successfull authentication'
3694:                 );
3695:             }
3696:         } else {
3697:             phpCAS :: trace(
3698:                 "Skipping session rename since phpCAS is not handling the session."
3699:             );
3700:         }
3701:         phpCAS::traceEnd();
3702:     }
3703: 
3704: 
3705:     // ########################################################################
3706:     //  AUTHENTICATION ERROR HANDLING
3707:     // ########################################################################
3708:     /**
3709:     * This method is used to print the HTML output when the user was not
3710:     * authenticated.
3711:     *
3712:     * @param string $failure      the failure that occured
3713:     * @param string $cas_url      the URL the CAS server was asked for
3714:     * @param bool   $no_response  the response from the CAS server (other
3715:     * parameters are ignored if true)
3716:     * @param bool   $bad_response bad response from the CAS server ($err_code
3717:     * and $err_msg ignored if true)
3718:     * @param string $cas_response the response of the CAS server
3719:     * @param int    $err_code     the error code given by the CAS server
3720:     * @param string $err_msg      the error message given by the CAS server
3721:     *
3722:     * @return void
3723:     */
3724:     private function _authError(
3725:         $failure,
3726:         $cas_url,
3727:         $no_response,
3728:         $bad_response='',
3729:         $cas_response='',
3730:         $err_code='',
3731:         $err_msg=''
3732:     ) {
3733:         phpCAS::traceBegin();
3734:         $lang = $this->getLangObj();
3735:         $this->printHTMLHeader($lang->getAuthenticationFailed());
3736:         printf(
3737:             $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()),
3738:             isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:''
3739:         );
3740:         phpCAS::trace('CAS URL: '.$cas_url);
3741:         phpCAS::trace('Authentication failure: '.$failure);
3742:         if ( $no_response ) {
3743:             phpCAS::trace('Reason: no response from the CAS server');
3744:         } else {
3745:             if ( $bad_response ) {
3746:                 phpCAS::trace('Reason: bad response from the CAS server');
3747:             } else {
3748:                 switch ($this->getServerVersion()) {
3749:                 case CAS_VERSION_1_0:
3750:                     phpCAS::trace('Reason: CAS error');
3751:                     break;
3752:                 case CAS_VERSION_2_0:
3753:                 case CAS_VERSION_3_0:
3754:                     if ( empty($err_code) ) {
3755:                         phpCAS::trace('Reason: no CAS error');
3756:                     } else {
3757:                         phpCAS::trace(
3758:                             'Reason: ['.$err_code.'] CAS error: '.$err_msg
3759:                         );
3760:                     }
3761:                     break;
3762:                 }
3763:             }
3764:             phpCAS::trace('CAS response: '.$cas_response);
3765:         }
3766:         $this->printHTMLFooter();
3767:         phpCAS::traceExit();
3768:         throw new CAS_GracefullTerminationException();
3769:     }
3770: 
3771:     // ########################################################################
3772:     //  PGTIOU/PGTID and logoutRequest rebroadcasting
3773:     // ########################################################################
3774: 
3775:     /**
3776:      * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and
3777:      * array of the nodes.
3778:      */
3779:     private $_rebroadcast = false;
3780:     private $_rebroadcast_nodes = array();
3781: 
3782:     /**
3783:      * Constants used for determining rebroadcast node type.
3784:      */
3785:     const HOSTNAME = 0;
3786:     const IP = 1;
3787: 
3788:     /**
3789:      * Determine the node type from the URL.
3790:      *
3791:      * @param String $nodeURL The node URL.
3792:      *
3793:      * @return string hostname
3794:      *
3795:      */
3796:     private function _getNodeType($nodeURL)
3797:     {
3798:         phpCAS::traceBegin();
3799:         if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) {
3800:             phpCAS::traceEnd(self::IP);
3801:             return self::IP;
3802:         } else {
3803:             phpCAS::traceEnd(self::HOSTNAME);
3804:             return self::HOSTNAME;
3805:         }
3806:     }
3807: 
3808:     /**
3809:      * Store the rebroadcast node for pgtIou/pgtId and logout requests.
3810:      *
3811:      * @param string $rebroadcastNodeUrl The rebroadcast node URL.
3812:      *
3813:      * @return void
3814:      */
3815:     public function addRebroadcastNode($rebroadcastNodeUrl)
3816:     {
3817:         // Argument validation
3818:         if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl))
3819:             throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url');
3820: 
3821:         // Store the rebroadcast node and set flag
3822:         $this->_rebroadcast = true;
3823:         $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl;
3824:     }
3825: 
3826:     /**
3827:      * An array to store extra rebroadcast curl options.
3828:      */
3829:     private $_rebroadcast_headers = array();
3830: 
3831:     /**
3832:      * This method is used to add header parameters when rebroadcasting
3833:      * pgtIou/pgtId or logoutRequest.
3834:      *
3835:      * @param string $header Header to send when rebroadcasting.
3836:      *
3837:      * @return void
3838:      */
3839:     public function addRebroadcastHeader($header)
3840:     {
3841:         if (gettype($header) != 'string')
3842:             throw new CAS_TypeMismatchException($header, '$header', 'string');
3843: 
3844:         $this->_rebroadcast_headers[] = $header;
3845:     }
3846: 
3847:     /**
3848:      * Constants used for determining rebroadcast type (logout or pgtIou/pgtId).
3849:      */
3850:     const LOGOUT = 0;
3851:     const PGTIOU = 1;
3852: 
3853:     /**
3854:      * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU
3855:      *
3856:      * @param int $type type of rebroadcasting.
3857:      *
3858:      * @return void
3859:      */
3860:     private function _rebroadcast($type)
3861:     {
3862:         phpCAS::traceBegin();
3863: 
3864:         $rebroadcast_curl_options = array(
3865:         CURLOPT_FAILONERROR => 1,
3866:         CURLOPT_FOLLOWLOCATION => 1,
3867:         CURLOPT_RETURNTRANSFER => 1,
3868:         CURLOPT_CONNECTTIMEOUT => 1,
3869:         CURLOPT_TIMEOUT => 4);
3870: 
3871:         // Try to determine the IP address of the server
3872:         if (!empty($_SERVER['SERVER_ADDR'])) {
3873:             $ip = $_SERVER['SERVER_ADDR'];
3874:         } else if (!empty($_SERVER['LOCAL_ADDR'])) {
3875:             // IIS 7
3876:             $ip = $_SERVER['LOCAL_ADDR'];
3877:         }
3878:         // Try to determine the DNS name of the server
3879:         if (!empty($ip)) {
3880:             $dns = gethostbyaddr($ip);
3881:         }
3882:         $multiClassName = 'CAS_Request_CurlMultiRequest';
3883:         $multiRequest = new $multiClassName();
3884: 
3885:         for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) {
3886:             if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false))
3887:                 || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))
3888:             ) {
3889:                 phpCAS::trace(
3890:                     'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i]
3891:                     .$_SERVER['REQUEST_URI']
3892:                 );
3893:                 $className = $this->_requestImplementation;
3894:                 $request = new $className();
3895: 
3896:                 $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI'];
3897:                 $request->setUrl($url);
3898: 
3899:                 if (count($this->_rebroadcast_headers)) {
3900:                     $request->addHeaders($this->_rebroadcast_headers);
3901:                 }
3902: 
3903:                 $request->makePost();
3904:                 if ($type == self::LOGOUT) {
3905:                     // Logout request
3906:                     $request->setPostBody(
3907:                         'rebroadcast=false&logoutRequest='.$_POST['logoutRequest']
3908:                     );
3909:                 } else if ($type == self::PGTIOU) {
3910:                     // pgtIou/pgtId rebroadcast
3911:                     $request->setPostBody('rebroadcast=false');
3912:                 }
3913: 
3914:                 $request->setCurlOptions($rebroadcast_curl_options);
3915: 
3916:                 $multiRequest->addRequest($request);
3917:             } else {
3918:                 phpCAS::trace(
3919:                     'Rebroadcast not sent to self: '
3920:                     .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'')
3921:                     .'/'.(!empty($dns)?$dns:'')
3922:                 );
3923:             }
3924:         }
3925:         // We need at least 1 request
3926:         if ($multiRequest->getNumRequests() > 0) {
3927:             $multiRequest->send();
3928:         }
3929:         phpCAS::traceEnd();
3930:     }
3931: 
3932:     /** @} */
3933: }
3934: 
3935: ?>
3936: 
 

© 2004-2018 – Nicola Asuni - Tecnick.com - All rights reserved.
about - disclaimer - privacy