[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/auth/cas/CAS/CAS/ -> Client.php (source)

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1