[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |