00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00037
00038 include_once(dirname(__FILE__).'/languages/languages.php');
00039
00040
00041 include_once(dirname(__FILE__).'/PGTStorage/AbstractStorage.php');
00042
00043
00044 include_once(dirname(__FILE__).'/CookieJar.php');
00045
00046
00047 include_once(dirname(__FILE__).'/Request/CurlRequest.php');
00048
00049
00050 include_once(dirname(__FILE__).'/ProxiedService/Http/Get.php');
00051 include_once(dirname(__FILE__).'/ProxiedService/Http/Post.php');
00052 include_once(dirname(__FILE__).'/ProxiedService/Imap.php');
00053
00054
00055 include_once(dirname(__FILE__).'/ProxiedService/Exception.php');
00056 include_once(dirname(__FILE__).'/ProxyTicketException.php');
00057 include_once(dirname(__FILE__).'/InvalidArgumentException.php');
00058
00059
00068 class CAS_Client
00069 {
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00096 private function HTMLFilterOutput($str)
00097 {
00098 $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
00099 $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
00100 $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
00101 echo $str;
00102 }
00103
00111 private $_output_header = '';
00112
00121 private function printHTMLHeader($title)
00122 {
00123 $this->HTMLFilterOutput(str_replace('__TITLE__',
00124 $title,
00125 (empty($this->_output_header)
00126 ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
00127 : $this->_output_header)
00128 )
00129 );
00130 }
00131
00139 private $_output_footer = '';
00140
00147 private function printHTMLFooter()
00148 {
00149 $this->HTMLFilterOutput(empty($this->_output_footer)
00150 ?('<hr><address>phpCAS __PHPCAS_VERSION__ '.$this->getString(CAS_STR_USING_SERVER).' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>')
00151 :$this->_output_footer);
00152 }
00153
00159 public function setHTMLHeader($header)
00160 {
00161 $this->_output_header = $header;
00162 }
00163
00169 public function setHTMLFooter($footer)
00170 {
00171 $this->_output_footer = $footer;
00172 }
00173
00174
00178
00179
00180
00194 private $_lang = '';
00195
00201 private function getLang()
00202 {
00203 if ( empty($this->_lang) )
00204 $this->setLang(PHPCAS_LANG_DEFAULT);
00205 return $this->_lang;
00206 }
00207
00216 private $_strings;
00217
00226 private function getString($str)
00227 {
00228
00229 $this->getLang();
00230
00231 if ( !isset($this->_strings[$str]) ) {
00232 trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
00233 }
00234 return $this->_strings[$str];
00235 }
00236
00245 public function setLang($lang)
00246 {
00247
00248 include(dirname(__FILE__).'/languages/'.$lang.'.php');
00249
00250 if ( !is_array($this->_strings) ) {
00251 trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
00252 }
00253 $this->_lang = $lang;
00254 }
00255
00257 // ########################################################################
00258 // CAS SERVER CONFIG
00259 // ########################################################################
00288 private $_server = array(
00289 'version' => -1,
00290 'hostname' => 'none',
00291 'port' => -1,
00292 'uri' => 'none');
00293
00298 private function getServerVersion()
00299 {
00300 return $this->_server['version'];
00301 }
00302
00307 private function getServerHostname()
00308 { return $this->_server['hostname']; }
00309
00314 private function getServerPort()
00315 { return $this->_server['port']; }
00316
00321 private function getServerURI()
00322 { return $this->_server['uri']; }
00323
00328 private function getServerBaseURL()
00329 {
00330 // the URL is build only when needed
00331 if ( empty($this->_server['base_url']) ) {
00332 $this->_server['base_url'] = 'https:
00333 if ($this->getServerPort()!=443) {
00334 $this->_server['base_url'] .= ':'
00335 .$this->getServerPort();
00336 }
00337 $this->_server['base_url'] .= $this->getServerURI();
00338 }
00339 return $this->_server['base_url'];
00340 }
00341
00350 public function getServerLoginURL($gateway=false,$renew=false) {
00351 phpCAS::traceBegin();
00352
00353 if ( empty($this->_server['login_url']) ) {
00354 $this->_server['login_url'] = $this->getServerBaseURL();
00355 $this->_server['login_url'] .= 'login?service=';
00356 $this->_server['login_url'] .= urlencode($this->getURL());
00357 }
00358 $url = $this->_server['login_url'];
00359 if($renew) {
00360
00361 $url = $this->buildQueryUrl($url, 'renew=true');
00362 } elseif ($gateway) {
00363
00364 $url = $this->buildQueryUrl($url, 'gateway=true');
00365 }
00366 phpCAS::traceEnd($url);
00367 return $url;
00368 }
00369
00375 public function setServerLoginURL($url)
00376 {
00377 return $this->_server['login_url'] = $url;
00378 }
00379
00380
00386 public function setServerServiceValidateURL($url)
00387 {
00388 return $this->_server['service_validate_url'] = $url;
00389 }
00390
00391
00397 public function setServerProxyValidateURL($url)
00398 {
00399 return $this->_server['proxy_validate_url'] = $url;
00400 }
00401
00402
00408 public function setServerSamlValidateURL($url)
00409 {
00410 return $this->_server['saml_validate_url'] = $url;
00411 }
00412
00413
00418 public function getServerServiceValidateURL()
00419 {
00420 phpCAS::traceBegin();
00421
00422 if ( empty($this->_server['service_validate_url']) ) {
00423 switch ($this->getServerVersion()) {
00424 case CAS_VERSION_1_0:
00425 $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
00426 break;
00427 case CAS_VERSION_2_0:
00428 $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
00429 break;
00430 }
00431 }
00432 $url = $this->buildQueryUrl($this->_server['service_validate_url'], 'service='.urlencode($this->getURL()));
00433 phpCAS::traceEnd($url);
00434 return $url;
00435 }
00440 public function getServerSamlValidateURL()
00441 {
00442 phpCAS::traceBegin();
00443
00444 if ( empty($this->_server['saml_validate_url']) ) {
00445 switch ($this->getServerVersion()) {
00446 case SAML_VERSION_1_1:
00447 $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
00448 break;
00449 }
00450 }
00451
00452 $url = $this->buildQueryUrl($this->_server['saml_validate_url'], 'TARGET='.urlencode($this->getURL()));
00453 phpCAS::traceEnd($url);
00454 return $url;
00455 }
00456
00461 public function getServerProxyValidateURL()
00462 {
00463 phpCAS::traceBegin();
00464
00465 if ( empty($this->_server['proxy_validate_url']) ) {
00466 switch ($this->getServerVersion()) {
00467 case CAS_VERSION_1_0:
00468 $this->_server['proxy_validate_url'] = '';
00469 break;
00470 case CAS_VERSION_2_0:
00471 $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
00472 break;
00473 }
00474 }
00475 $url = $this->buildQueryUrl($this->_server['proxy_validate_url'], 'service='.urlencode($this->getURL()));
00476 phpCAS::traceEnd($url);
00477 return $url;
00478 }
00479
00480
00485 public function getServerProxyURL()
00486 {
00487
00488 if ( empty($this->_server['proxy_url']) ) {
00489 switch ($this->getServerVersion()) {
00490 case CAS_VERSION_1_0:
00491 $this->_server['proxy_url'] = '';
00492 break;
00493 case CAS_VERSION_2_0:
00494 $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
00495 break;
00496 }
00497 }
00498 return $this->_server['proxy_url'];
00499 }
00500
00505 public function getServerLogoutURL()
00506 {
00507
00508 if ( empty($this->_server['logout_url']) ) {
00509 $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
00510 }
00511 return $this->_server['logout_url'];
00512 }
00513
00519 public function setServerLogoutURL($url)
00520 {
00521 return $this->_server['logout_url'] = $url;
00522 }
00523
00527 private $_curl_options = array();
00528
00532 public function setExtraCurlOption($key, $value)
00533 {
00534 $this->_curl_options[$key] = $value;
00535 }
00536
00539
00540
00541
00542
00554 private $_requestImplementation = 'CAS_CurlRequest';
00555
00563 public function setRequestImplementation ($className) {
00564 $obj = new $className;
00565 if (!($obj instanceof CAS_RequestInterface))
00566 throw new CAS_InvalidArgumentException('$className must implement the CAS_RequestInterface');
00567
00568 $this->_requestImplementation = $className;
00569 }
00570
00574 private $_exitOnAuthError = true;
00575
00583 public function setNoExitOnAuthError () {
00584 $this->_exitOnAuthError = false;
00585 }
00586
00591 private $_clearTicketsFromUrl = true;
00592
00602 public function setNoClearTicketsFromUrl () {
00603 $this->_clearTicketsFromUrl = false;
00604 }
00605
00609 private $_postAuthenticateCallbackFunction = null;
00610
00614 private $_postAuthenticateCallbackArgs = array();
00615
00634 public function setPostAuthenticateCallback ($function, array $additionalArgs = array()) {
00635 $this->_postAuthenticateCallbackFunction = $function;
00636 $this->_postAuthenticateCallbackArgs = $additionalArgs;
00637 }
00638
00642 private $_signoutCallbackFunction = null;
00643
00647 private $_signoutCallbackArgs = array();
00648
00662 public function setSingleSignoutCallback ($function, array $additionalArgs = array()) {
00663 $this->_signoutCallbackFunction = $function;
00664 $this->_signoutCallbackArgs = $additionalArgs;
00665 }
00666
00669
00670
00671
00689 public function __construct(
00690 $server_version,
00691 $proxy,
00692 $server_hostname,
00693 $server_port,
00694 $server_uri,
00695 $start_session = true) {
00696
00697 phpCAS::traceBegin();
00698
00699 $this->_start_session = $start_session;
00700
00701 if ($this->_start_session && session_id() !== "")
00702 {
00703 phpCAS :: error("Another session was started before phpcas. Either disable the session" .
00704 " handling for phpcas in the client() call or modify your application to leave" .
00705 " session handling to phpcas");
00706 }
00707
00708 if ($start_session && !$this->isLogoutRequest())
00709 {
00710 phpCAS :: trace("Starting a new session");
00711 session_start();
00712 }
00713
00714
00715
00716 $this->_proxy = $proxy;
00717
00718
00719 if ($this->isProxy()) {
00720 if (!isset($_SESSION['phpCAS']))
00721 $_SESSION['phpCAS'] = array();
00722 if (!isset($_SESSION['phpCAS']['service_cookies']))
00723 $_SESSION['phpCAS']['service_cookies'] = array();
00724 $this->_serviceCookieJar = new CAS_CookieJar($_SESSION['phpCAS']['service_cookies']);
00725 }
00726
00727
00728 switch ($server_version) {
00729 case CAS_VERSION_1_0:
00730 if ( $this->isProxy() )
00731 phpCAS::error('CAS proxies are not supported in CAS '
00732 .$server_version);
00733 break;
00734 case CAS_VERSION_2_0:
00735 break;
00736 case SAML_VERSION_1_1:
00737 break;
00738 default:
00739 phpCAS::error('this version of CAS (`'
00740 .$server_version
00741 .'\') is not supported by phpCAS '
00742 .phpCAS::getVersion());
00743 }
00744 $this->_server['version'] = $server_version;
00745
00746 // check hostname
00747 if ( empty($server_hostname)
00748 || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
00749 phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
00750 }
00751 $this->_server['hostname'] = $server_hostname;
00752
00753
00754 if ( $server_port == 0
00755 || !is_int($server_port) ) {
00756 phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
00757 }
00758 $this->_server['port'] = $server_port;
00759
00760 // check URI
00761 if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
00762 phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
00763 }
00764
00765 $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
00766 $this->_server['uri'] = $server_uri;
00767
00768
00769 if ( $this->isProxy() ) {
00770 $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
00771 }
00772
00773 if ( $this->isCallbackMode() ) {
00774
00775 if ( !$this->isHttps() ) {
00776 phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
00777 }
00778 } else {
00779
00780 $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
00781 switch ($this->getServerVersion()) {
00782 case CAS_VERSION_1_0:
00783 if( preg_match('/^ST-/',$ticket) ) {
00784 phpCAS::trace('ST \''.$ticket.'\' found');
00785 //ST present
00786 $this->setST($ticket);
00787 //ticket has been taken into account, unset it to hide it to applications
00788 unset($_GET['ticket']);
00789 } else if ( !empty($ticket) ) {
00790 //ill-formed ticket, halt
00791 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
00792 }
00793 break;
00794 case CAS_VERSION_2_0:
00795 if( preg_match('/^[SP]T-/',$ticket) ) {
00796 phpCAS::trace('ST or PT \''.$ticket.'\' found');
00797 $this->setPT($ticket);
00798 unset($_GET['ticket']);
00799 } else if ( !empty($ticket) ) {
00800 //ill-formed ticket, halt
00801 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
00802 }
00803 break;
00804 case SAML_VERSION_1_1:
00805 if( preg_match('/^[SP]T-/',$ticket) ) {
00806 phpCAS::trace('SA \''.$ticket.'\' found');
00807 $this->setSA($ticket);
00808 unset($_GET['ticket']);
00809 } else if ( !empty($ticket) ) {
00810 //ill-formed ticket, halt
00811 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
00812 }
00813 break;
00814 }
00815 }
00816 phpCAS::traceEnd();
00817 }
00818
00821
00822
00823
00824
00825
00826
00837 private $_start_session = true;
00838
00839 private function setStartSession($session)
00840 {
00841 $this->_start_session = session;
00842 }
00843
00844 public function getStartSession($session)
00845 {
00846 $this->_start_session = session;
00847 }
00848
00851
00852
00853
00854
00855
00856
00868 private $_user = '';
00869
00876 private function setUser($user)
00877 {
00878 $this->_user = $user;
00879 }
00880
00888 public function getUser()
00889 {
00890 if ( empty($this->_user) ) {
00891 phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
00892 }
00893 return $this->_user;
00894 }
00895
00896
00897
00898
00899
00900
00901
00902
00903
00910 private $_attributes = array();
00911
00912 public function setAttributes($attributes)
00913 { $this->_attributes = $attributes; }
00914
00915 public function getAttributes() {
00916 if ( empty($this->_user) ) {
00917 phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
00918 }
00919 return $this->_attributes;
00920 }
00921
00922 public function hasAttributes()
00923 { return !empty($this->_attributes); }
00924
00925 public function hasAttribute($key)
00926 { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
00927
00928 public function getAttribute($key) {
00929 if($this->hasAttribute($key)) {
00930 return $this->_attributes[$key];
00931 }
00932 }
00933
00939 public function renewAuthentication(){
00940 phpCAS::traceBegin();
00941
00942 if( isset( $_SESSION['phpCAS']['auth_checked'] ) )
00943 unset($_SESSION['phpCAS']['auth_checked']);
00944 if ( $this->isAuthenticated() ) {
00945 phpCAS::trace('user already authenticated; renew');
00946 $this->redirectToCas(false,true);
00947 } else {
00948 $this->redirectToCas();
00949 }
00950 phpCAS::traceEnd();
00951 }
00952
00958 public function forceAuthentication()
00959 {
00960 phpCAS::traceBegin();
00961
00962 if ( $this->isAuthenticated() ) {
00963
00964 phpCAS::trace('no need to authenticate');
00965 $res = TRUE;
00966 } else {
00967
00968 if (isset($_SESSION['phpCAS']['auth_checked'])) {
00969 unset($_SESSION['phpCAS']['auth_checked']);
00970 }
00971 $this->redirectToCas(FALSE);
00972
00973 $res = FALSE;
00974 }
00975 phpCAS::traceEnd($res);
00976 return $res;
00977 }
00978
00984 private $_cache_times_for_auth_recheck = 0;
00985
00991 public function setCacheTimesForAuthRecheck($n)
00992 {
00993 $this->_cache_times_for_auth_recheck = $n;
00994 }
00995
01001 public function checkAuthentication()
01002 {
01003 phpCAS::traceBegin();
01004
01005 if ( $this->isAuthenticated() ) {
01006 phpCAS::trace('user is authenticated');
01007 $res = TRUE;
01008 } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
01009
01010 unset($_SESSION['phpCAS']['auth_checked']);
01011 $res = FALSE;
01012 } else {
01013
01014 if (! isset($_SESSION['phpCAS']['unauth_count']) )
01015 $_SESSION['phpCAS']['unauth_count'] = -2;
01016
01017 if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1)
01018 || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck))
01019 {
01020 $res = FALSE;
01021
01022 if ($this->_cache_times_for_auth_recheck != -1)
01023 {
01024 $_SESSION['phpCAS']['unauth_count']++;
01025 phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')');
01026 }
01027 else
01028 {
01029 phpCAS::trace('user is not authenticated (cached for until login pressed)');
01030 }
01031 }
01032 else
01033 {
01034 $_SESSION['phpCAS']['unauth_count'] = 0;
01035 $_SESSION['phpCAS']['auth_checked'] = true;
01036 phpCAS::trace('user is not authenticated (cache reset)');
01037 $this->redirectToCas(TRUE);
01038
01039 $res = FALSE;
01040 }
01041 }
01042 phpCAS::traceEnd($res);
01043 return $res;
01044 }
01045
01052 public function isAuthenticated()
01053 {
01054 phpCAS::traceBegin();
01055 $res = FALSE;
01056 $validate_url = '';
01057
01058 if ( $this->wasPreviouslyAuthenticated() ) {
01059 if($this->hasST() || $this->hasPT() || $this->hasSA()){
01060
01061 phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
01062 header('Location: '.$this->getURL());
01063 phpCAS::trace( "Prepare redirect to remove ticket: ".$this->getURL() );
01064 phpCAS::traceExit();
01065 exit();
01066 }else{
01067
01068
01069 phpCAS::trace('user was already authenticated, no need to look for tickets');
01070 $res = TRUE;
01071 }
01072 }
01073 else {
01074 if ( $this->hasST() ) {
01075
01076 phpCAS::trace('ST `'.$this->getST().'\' is present');
01077 $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
01078 phpCAS::trace('ST `'.$this->getST().'\' was validated');
01079 if ( $this->isProxy() ) {
01080 $this->validatePGT($validate_url,$text_response,$tree_response);
01081 phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
01082 $_SESSION['phpCAS']['pgt'] = $this->getPGT();
01083 }
01084 $_SESSION['phpCAS']['user'] = $this->getUser();
01085 if($this->hasAttributes()){
01086 $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
01087 }
01088 $res = TRUE;
01089 $logoutTicket = $this->getST();
01090 }
01091 elseif ( $this->hasPT() ) {
01092 // if a Proxy Ticket was given, validate it
01093 phpCAS::trace('PT `'.$this->getPT().'\' is present');
01094 $this->validatePT($validate_url,$text_response,$tree_response);
01095 phpCAS::trace('PT `'.$this->getPT().'\' was validated');
01096 if ( $this->isProxy() ) {
01097 $this->validatePGT($validate_url,$text_response,$tree_response); // idem
01098 phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
01099 $_SESSION['phpCAS']['pgt'] = $this->getPGT();
01100 }
01101 $_SESSION['phpCAS']['user'] = $this->getUser();
01102 if($this->hasAttributes()){
01103 $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
01104 }
01105 $res = TRUE;
01106 $logoutTicket = $this->getPT();
01107 }
01108 elseif ( $this->hasSA() ) {
01109
01110 phpCAS::trace('SA `'.$this->getSA().'\' is present');
01111 $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
01112 phpCAS::trace('SA `'.$this->getSA().'\' was validated');
01113 $_SESSION['phpCAS']['user'] = $this->getUser();
01114 $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
01115 $res = TRUE;
01116 $logoutTicket = $this->getSA();
01117 }
01118 else {
01119
01120 phpCAS::trace('no ticket found');
01121 }
01122 if ($res) {
01123
01124
01125 $dbg = debug_backtrace();
01126 global $PHPCAS_AUTH_CHECK_CALL;
01127 $PHPCAS_AUTH_CHECK_CALL = array (
01128 'done' => TRUE,
01129 'file' => $dbg[0]['file'],
01130 'line' => $dbg[0]['line'],
01131 'method' => __CLASS__ . '::' . __FUNCTION__,
01132 'result' => $res
01133 );
01134
01135
01136 if ($this->_postAuthenticateCallbackFunction) {
01137 $args = $this->_postAuthenticateCallbackArgs;
01138 array_unshift($args, $logoutTicket);
01139 call_user_func_array($this->_postAuthenticateCallbackFunction, $args);
01140 }
01141
01142
01143
01144
01145 if ($this->_clearTicketsFromUrl) {
01146 header('Location: '.$this->getURL());
01147 phpCAS::trace( "Prepare redirect to : ".$this->getURL() );
01148 phpCAS::traceExit();
01149 exit();
01150 }
01151 }
01152 }
01153
01154 phpCAS::traceEnd($res);
01155 return $res;
01156 }
01157
01163 public function isSessionAuthenticated ()
01164 {
01165 return !empty($_SESSION['phpCAS']['user']);
01166 }
01167
01176 private function wasPreviouslyAuthenticated()
01177 {
01178 phpCAS::traceBegin();
01179
01180 if ( $this->isCallbackMode() ) {
01181 $this->callback();
01182 }
01183
01184 $auth = FALSE;
01185
01186 if ( $this->isProxy() ) {
01187
01188 if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
01189
01190 $this->setUser($_SESSION['phpCAS']['user']);
01191 if(isset($_SESSION['phpCAS']['attributes'])){
01192 $this->setAttributes($_SESSION['phpCAS']['attributes']);
01193 }
01194 $this->setPGT($_SESSION['phpCAS']['pgt']);
01195 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
01196
01197
01198 if (isset($_SESSION['phpCAS']['proxies'])) {
01199 $this->setProxies($_SESSION['phpCAS']['proxies']);
01200 phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"');
01201 }
01202
01203 $auth = TRUE;
01204 } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
01205
01206 phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
01207 // unset all tickets to enforce authentication
01208 unset($_SESSION['phpCAS']);
01209 $this->setST('');
01210 $this->setPT('');
01211 } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
01212 // these two variables should be empty or not empty at the same time
01213 phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
01214
01215 unset($_SESSION['phpCAS']);
01216 $this->setST('');
01217 $this->setPT('');
01218 } else {
01219 phpCAS::trace('neither user not PGT found');
01220 }
01221 } else {
01222
01223 if ( $this->isSessionAuthenticated() ) {
01224
01225 $this->setUser($_SESSION['phpCAS']['user']);
01226 if(isset($_SESSION['phpCAS']['attributes'])){
01227 $this->setAttributes($_SESSION['phpCAS']['attributes']);
01228 }
01229 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
01230
01231
01232 if (isset($_SESSION['phpCAS']['proxies'])) {
01233 $this->setProxies($_SESSION['phpCAS']['proxies']);
01234 phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"');
01235 }
01236
01237 $auth = TRUE;
01238 } else {
01239 phpCAS::trace('no user found');
01240 }
01241 }
01242
01243 phpCAS::traceEnd($auth);
01244 return $auth;
01245 }
01246
01253 public function redirectToCas($gateway=false,$renew=false){
01254 phpCAS::traceBegin();
01255 $cas_url = $this->getServerLoginURL($gateway,$renew);
01256 header('Location: '.$cas_url);
01257 phpCAS::trace( "Redirect to : ".$cas_url );
01258
01259 $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED));
01260
01261 printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
01262 $this->printHTMLFooter();
01263
01264 phpCAS::traceExit();
01265 exit();
01266 }
01267
01268
01273 public function logout($params) {
01274 phpCAS::traceBegin();
01275 $cas_url = $this->getServerLogoutURL();
01276 $paramSeparator = '?';
01277 if (isset($params['url'])) {
01278 $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']);
01279 $paramSeparator = '&';
01280 }
01281 if (isset($params['service'])) {
01282 $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
01283 }
01284 header('Location: '.$cas_url);
01285 phpCAS::trace( "Prepare redirect to : ".$cas_url );
01286
01287 session_unset();
01288 session_destroy();
01289
01290 $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
01291 printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
01292 $this->printHTMLFooter();
01293
01294 phpCAS::traceExit();
01295 exit();
01296 }
01297
01301 private function isLogoutRequest() {
01302 return !empty($_POST['logoutRequest']);
01303 }
01304
01312 public function handleLogoutRequests($check_client=true, $allowed_clients=false) {
01313 phpCAS::traceBegin();
01314 if (!$this->isLogoutRequest()) {
01315 phpCAS::trace("Not a logout request");
01316 phpCAS::traceEnd();
01317 return;
01318 }
01319 if(!$this->_start_session && is_null($this->_signoutCallbackFunction)){
01320 phpCAS::trace("phpCAS can't handle logout requests if it does not manage the session.");
01321 }
01322 phpCAS::trace("Logout requested");
01323 phpCAS::trace("SAML REQUEST: ".$_POST['logoutRequest']);
01324 if ($check_client) {
01325 if (!$allowed_clients) {
01326 $allowed_clients = array( $this->getServerHostname() );
01327 }
01328 $client_ip = $_SERVER['REMOTE_ADDR'];
01329 $client = gethostbyaddr($client_ip);
01330 phpCAS::trace("Client: ".$client."/".$client_ip);
01331 $allowed = false;
01332 foreach ($allowed_clients as $allowed_client) {
01333 if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
01334 phpCAS::trace("Allowed client '".$allowed_client."' matches, logout request is allowed");
01335 $allowed = true;
01336 break;
01337 } else {
01338 phpCAS::trace("Allowed client '".$allowed_client."' does not match");
01339 }
01340 }
01341 if (!$allowed) {
01342 phpCAS::error("Unauthorized logout request from client '".$client."'");
01343 printf("Unauthorized!");
01344 phpCAS::traceExit();
01345 exit();
01346 }
01347 } else {
01348 phpCAS::trace("No access control set");
01349 }
01350
01351 preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3);
01352 $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|','',$tick[0][0]);
01353 $ticket2logout = preg_replace('|</samlp:SessionIndex>|','',$wrappedSamlSessionIndex);
01354 phpCAS::trace("Ticket to logout: ".$ticket2logout);
01355
01356
01357 if ($this->_signoutCallbackFunction) {
01358 $args = $this->_signoutCallbackArgs;
01359 array_unshift($args, $ticket2logout);
01360 call_user_func_array($this->_signoutCallbackFunction, $args);
01361 }
01362
01363
01364 if ($this->_start_session) {
01365 $session_id = preg_replace('/[^\w]/','',$ticket2logout);
01366 phpCAS::trace("Session id: ".$session_id);
01367
01368
01369 if(session_id() !== ""){
01370 session_unset();
01371 session_destroy();
01372 }
01373
01374 session_id($session_id);
01375 $_COOKIE[session_name()]=$session_id;
01376 $_GET[session_name()]=$session_id;
01377
01378
01379 session_start();
01380 session_unset();
01381 session_destroy();
01382 }
01383
01384 printf("Disconnected!");
01385 phpCAS::traceExit();
01386 exit();
01387 }
01388
01391
01392
01393
01394
01395
01396
01397
01398
01399
01412 private $_st = '';
01413
01418 public function getST()
01419 { return $this->_st; }
01420
01425 public function setST($st)
01426 { $this->_st = $st; }
01427
01432 public function hasST()
01433 { return !empty($this->_st); }
01434
01437
01438
01439
01450 private $_cas_server_ca_cert = '';
01451
01457 private $_no_cas_server_validation = false;
01458
01459
01465 public function setCasServerCACert($cert)
01466 {
01467 $this->_cas_server_ca_cert = $cert;
01468 }
01469
01473 public function setNoCasServerValidation()
01474 {
01475 $this->_no_cas_server_validation = true;
01476 }
01477
01489 public function validateST($validate_url,&$text_response,&$tree_response)
01490 {
01491 phpCAS::traceBegin();
01492
01493 $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
01494 if ( $this->isProxy() ) {
01495
01496 $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
01497 }
01498
01499
01500 if ( !$this->readURL($validate_url,$headers,$text_response,$err_msg) ) {
01501 phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
01502 $this->authError('ST not validated',
01503 $validate_url,
01504 TRUE/*$no_response*/);
01505 }
01506
01507 // analyze the result depending on the version
01508 switch ($this->getServerVersion()) {
01509 case CAS_VERSION_1_0:
01510 if (preg_match('/^no\n/',$text_response)) {
01511 phpCAS::trace('ST has not been validated');
01512 $this->authError('ST not validated',
01513 $validate_url,
01514 FALSE/*$no_response*/,
01515 FALSE/*$bad_response*/,
01516 $text_response);
01517 }
01518 if (!preg_match('/^yes\n/',$text_response)) {
01519 phpCAS::trace('ill-formed response');
01520 $this->authError('ST not validated',
01521 $validate_url,
01522 FALSE/*$no_response*/,
01523 TRUE/*$bad_response*/,
01524 $text_response);
01525 }
01526 // ST has been validated, extract the user name
01527 $arr = preg_split('/\n/',$text_response);
01528 $this->setUser(trim($arr[1]));
01529 break;
01530 case CAS_VERSION_2_0:
01531
01532 // create new DOMDocument object
01533 $dom = new DOMDocument();
01534 // Fix possible whitspace problems
01535 $dom->preserveWhiteSpace = false;
01536 // read the response of the CAS server into a DOM object
01537 if ( !($dom->loadXML($text_response))) {
01538 phpCAS::trace('dom->loadXML() failed');
01539 $this->authError('ST not validated',
01540 $validate_url,
01541 FALSE/*$no_response*/,
01542 TRUE/*$bad_response*/,
01543 $text_response);
01544 }
01545 // read the root node of the XML tree
01546 if ( !($tree_response = $dom->documentElement) ) {
01547 phpCAS::trace('documentElement() failed');
01548 $this->authError('ST not validated',
01549 $validate_url,
01550 FALSE/*$no_response*/,
01551 TRUE/*$bad_response*/,
01552 $text_response);
01553 }
01554 // insure that tag name is 'serviceResponse'
01555 if ( $tree_response->localName != 'serviceResponse' ) {
01556 phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->localName.'\'');
01557 $this->authError('ST not validated',
01558 $validate_url,
01559 FALSE,
01560 TRUE,
01561 $text_response);
01562 }
01563
01564 if ( $tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
01565
01566 $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
01567 if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
01568
01569 $this->authError('ST not validated',
01570 $validate_url,
01571 FALSE,
01572 TRUE,
01573 $text_response);
01574 }
01575 $this->setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue));
01576 $this->readExtraAttributesCas20($success_elements);
01577 } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
01578 phpCAS::trace('<authenticationFailure> found');
01579
01580 $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
01581 $this->authError('ST not validated',
01582 $validate_url,
01583 FALSE,
01584 FALSE,
01585 $text_response,
01586 $auth_fail_list->item(0)->getAttribute('code'),
01587 trim($auth_fail_list->item(0)->nodeValue));
01588 } else {
01589 phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
01590 $this->authError('ST not validated',
01591 $validate_url,
01592 FALSE,
01593 TRUE,
01594 $text_response);
01595 }
01596 break;
01597 }
01598 $this->renameSession($this->getST());
01599
01600 phpCAS::traceEnd(TRUE);
01601 return TRUE;
01602 }
01603
01607
01608
01609
01626 public function validateSA($validate_url,&$text_response,&$tree_response)
01627 {
01628 phpCAS::traceBegin();
01629
01630
01631 $validate_url = $this->getServerSamlValidateURL();
01632
01633
01634 if ( !$this->readURL($validate_url,$headers,$text_response,$err_msg) ) {
01635 phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
01636 $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
01637 }
01638
01639 phpCAS::trace('server version: '.$this->getServerVersion());
01640
01641 // analyze the result depending on the version
01642 switch ($this->getServerVersion()) {
01643 case SAML_VERSION_1_1:
01644
01645 // create new DOMDocument Object
01646 $dom = new DOMDocument();
01647 // Fix possible whitspace problems
01648 $dom->preserveWhiteSpace = false;
01649 // read the response of the CAS server into a DOM object
01650 if ( !($dom->loadXML($text_response))) {
01651 phpCAS::trace('dom->loadXML() failed');
01652 $this->authError('SA not validated',
01653 $validate_url,
01654 FALSE/*$no_response*/,
01655 TRUE/*$bad_response*/,
01656 $text_response);
01657 }
01658 // read the root node of the XML tree
01659 if ( !($tree_response = $dom->documentElement) ) {
01660 phpCAS::trace('documentElement() failed');
01661 $this->authError('SA not validated',
01662 $validate_url,
01663 FALSE/*$no_response*/,
01664 TRUE/*$bad_response*/,
01665 $text_response);
01666 }
01667 // insure that tag name is 'Envelope'
01668 if ( $tree_response->localName != 'Envelope' ) {
01669 phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->localName.'\'');
01670 $this->authError('SA not validated',
01671 $validate_url,
01672 FALSE,
01673 TRUE,
01674 $text_response);
01675 }
01676
01677 if ( $tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
01678 $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
01679 phpCAS::trace('NameIdentifier found');
01680 $user = trim($success_elements->item(0)->nodeValue);
01681 phpCAS::trace('user = `'.$user.'`');
01682 $this->setUser($user);
01683 $this->setSessionAttributes($text_response);
01684 } else {
01685 phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
01686 $this->authError('SA not validated',
01687 $validate_url,
01688 FALSE,
01689 TRUE,
01690 $text_response);
01691 }
01692 break;
01693 }
01694 $this->renameSession($this->getSA());
01695
01696 phpCAS::traceEnd(TRUE);
01697 return TRUE;
01698 }
01699
01707 private function setSessionAttributes($text_response)
01708 {
01709 phpCAS::traceBegin();
01710
01711 $result = FALSE;
01712
01713 $attr_array = array();
01714
01715
01716 $dom = new DOMDocument();
01717
01718 $dom->preserveWhiteSpace = false;
01719 if (($dom->loadXML($text_response))) {
01720 $xPath = new DOMXpath($dom);
01721 $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
01722 $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
01723 $nodelist = $xPath->query("//saml:Attribute");
01724
01725 if($nodelist){
01726 foreach($nodelist as $node){
01727 $xres = $xPath->query("saml:AttributeValue", $node);
01728 $name = $node->getAttribute("AttributeName");
01729 $value_array = array();
01730 foreach($xres as $node2){
01731 $value_array[] = $node2->nodeValue;
01732 }
01733 $attr_array[$name] = $value_array;
01734 }
01735
01736 foreach($attr_array as $attr_key => $attr_value) {
01737 if(count($attr_value) > 1) {
01738 $this->_attributes[$attr_key] = $attr_value;
01739 phpCAS::trace("* " . $attr_key . "=" . $attr_value);
01740 }
01741 else {
01742 $this->_attributes[$attr_key] = $attr_value[0];
01743 phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
01744 }
01745 }
01746 $result = TRUE;
01747 }else{
01748 phpCAS::trace("SAML Attributes are empty");
01749 $result = FALSE;
01750 }
01751 }
01752 phpCAS::traceEnd($result);
01753 return $result;
01754 }
01755
01760 public function getSA()
01761 { return 'ST'.substr($this->_sa, 2); }
01762
01767 public function setSA($sa)
01768 { $this->_sa = $sa; }
01769
01774 public function hasSA()
01775 { return !empty($this->_sa); }
01776
01779
01780
01781
01782
01783
01784
01785
01786
01787
01797 private $_proxy;
01798
01802 private $_serviceCookieJar;
01803
01809 public function isProxy()
01810 {
01811 return $this->_proxy;
01812 }
01813
01815
01816
01817
01829 private $_pgt = '';
01830
01835 private function getPGT()
01836 { return $this->_pgt; }
01837
01842 private function setPGT($pgt)
01843 { $this->_pgt = $pgt; }
01844
01849 private function hasPGT()
01850 { return !empty($this->_pgt); }
01851
01854
01855
01856
01873 private $_callback_mode = FALSE;
01874
01880 private function setCallbackMode($callback_mode)
01881 {
01882 $this->_callback_mode = $callback_mode;
01883 }
01884
01891 private function isCallbackMode()
01892 {
01893 return $this->_callback_mode;
01894 }
01895
01903 private $_callback_url = '';
01904
01912 private function getCallbackURL()
01913 {
01914
01915 if ( empty($this->_callback_url) ) {
01916 $final_uri = '';
01917
01918 $final_uri = 'https://';
01919 $final_uri .= $this->getServerUrl();
01920 $request_uri = $_SERVER['REQUEST_URI'];
01921 $request_uri = preg_replace('/\?.*$/','',$request_uri);
01922 $final_uri .= $request_uri;
01923 $this->setCallbackURL($final_uri);
01924 }
01925 return $this->_callback_url;
01926 }
01927
01933 public function setCallbackURL($url)
01934 {
01935 return $this->_callback_url = $url;
01936 }
01937
01942 private function callback()
01943 {
01944 phpCAS::traceBegin();
01945 if (preg_match('/PGTIOU-[\.\-\w]/', $_GET['pgtIou'])){
01946 if(preg_match('/[PT]GT-[\.\-\w]/', $_GET['pgtId'])){
01947 $this->printHTMLHeader('phpCAS callback');
01948 $pgt_iou = $_GET['pgtIou'];
01949 $pgt = $_GET['pgtId'];
01950 phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
01951 echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
01952 $this->storePGT($pgt,$pgt_iou);
01953 $this->printHTMLFooter();
01954 }else{
01955 phpCAS::error('PGT format invalid' . $_GET['pgtId']);
01956 }
01957 }else{
01958 phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
01959 }
01960 phpCAS::traceExit();
01961 exit();
01962 }
01963
01966
01967
01968
01981 private $_pgt_storage = null;
01982
01987 private function initPGTStorage()
01988 {
01989
01990 if ( !is_object($this->_pgt_storage) ) {
01991 $this->setPGTStorageFile();
01992 }
01993
01994
01995 $this->_pgt_storage->init();
01996 }
01997
02004 private function storePGT($pgt,$pgt_iou)
02005 {
02006
02007 $this->initPGTStorage();
02008
02009 $this->_pgt_storage->write($pgt,$pgt_iou);
02010 }
02011
02019 private function loadPGT($pgt_iou)
02020 {
02021
02022 $this->initPGTStorage();
02023
02024 return $this->_pgt_storage->read($pgt_iou);
02025 }
02026
02032 public function setPGTStorage($storage)
02033 {
02034
02035 if ( is_object($this->_pgt_storage) ) {
02036 phpCAS::error('PGT storage already defined');
02037 }
02038
02039
02040 if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) {
02041 phpCAS::error('Invalid PGT storage object');
02042 }
02043
02044
02045 $this->_pgt_storage = $storage;
02046 }
02047
02058 public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null)
02059 {
02060
02061 $this->setPGTStorage(new CAS_PGTStorage_Db($this, $dsn_or_pdo, $username, $password, $table, $driver_options));
02062 }
02063
02071 public function setPGTStorageFile($path='')
02072 {
02073
02074 $this->setPGTStorage(new CAS_PGTStorage_File($this,$path));
02075 }
02076
02077
02078
02079
02080
02092 private function validatePGT(&$validate_url,$text_response,$tree_response)
02093 {
02094 phpCAS::traceBegin();
02095 if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
02096 phpCAS::trace('<proxyGrantingTicket> not found');
02097
02098 $this->authError('Ticket validated but no PGT Iou transmitted',
02099 $validate_url,
02100 FALSE,
02101 FALSE,
02102 $text_response);
02103 } else {
02104
02105 $pgt_iou = trim($tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue);
02106 if(preg_match('/PGTIOU-[\.\-\w]/',$pgt_iou)){
02107 $pgt = $this->loadPGT($pgt_iou);
02108 if ( $pgt == FALSE ) {
02109 phpCAS::trace('could not load PGT');
02110 $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
02111 $validate_url,
02112 FALSE,
02113 FALSE,
02114 $text_response);
02115 }
02116 $this->setPGT($pgt);
02117 }else{
02118 phpCAS::trace('PGTiou format error');
02119 $this->authError('PGT Iou was transmitted but has wrong fromat',
02120 $validate_url,
02121 FALSE,
02122 FALSE,
02123 $text_response);
02124 }
02125 }
02126 phpCAS::traceEnd(TRUE);
02127 return TRUE;
02128 }
02129
02130
02131
02132
02133
02143 public function retrievePT($target_service,&$err_code,&$err_msg)
02144 {
02145 phpCAS::traceBegin();
02146
02147
02148
02149
02150
02151 $err_msg = '';
02152
02153
02154 $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT();
02155
02156
02157 if ( !$this->readURL($cas_url,$headers,$cas_response,$err_msg) ) {
02158 phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
02159 $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
02160 $err_msg = 'could not retrieve PT (no response from the CAS server)';
02161 phpCAS::traceEnd(FALSE);
02162 return FALSE;
02163 }
02164
02165 $bad_response = FALSE;
02166
02167 if ( !$bad_response ) {
02168 // create new DOMDocument object
02169 $dom = new DOMDocument();
02170 // Fix possible whitspace problems
02171 $dom->preserveWhiteSpace = false;
02172 // read the response of the CAS server into a DOM object
02173 if ( !($dom->loadXML($cas_response))) {
02174 phpCAS::trace('dom->loadXML() failed');
02175 // read failed
02176 $bad_response = TRUE;
02177 }
02178 }
02179
02180 if ( !$bad_response ) {
02181 // read the root node of the XML tree
02182 if ( !($root = $dom->documentElement) ) {
02183 phpCAS::trace('documentElement failed');
02184 // read failed
02185 $bad_response = TRUE;
02186 }
02187 }
02188
02189 if ( !$bad_response ) {
02190 // insure that tag name is 'serviceResponse'
02191 if ( $root->localName != 'serviceResponse' ) {
02192 phpCAS::trace('localName failed');
02193 // bad root node
02194 $bad_response = TRUE;
02195 }
02196 }
02197
02198 if ( !$bad_response ) {
02199 // look for a proxySuccess tag
02200 if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
02201 $proxy_success_list = $root->getElementsByTagName("proxySuccess");
02202
02203 // authentication succeded, look for a proxyTicket tag
02204 if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
02205 $err_code = PHPCAS_SERVICE_OK;
02206 $err_msg = '';
02207 $pt = trim($proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue);
02208 phpCAS::trace('original PT: '.trim($pt));
02209 phpCAS::traceEnd($pt);
02210 return $pt;
02211 } else {
02212 phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
02213 }
02214 }
02215 // look for a proxyFailure tag
02216 else if ( $root->getElementsByTagName("proxyFailure")->length != 0) {
02217 $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
02218
02219 // authentication failed, extract the error
02220 $err_code = PHPCAS_SERVICE_PT_FAILURE;
02221 $err_msg = 'PT retrieving failed (code=`'
02222 .$proxy_failure_list->item(0)->getAttribute('code')
02223 .'\', message=`'
02224 .trim($proxy_failure_list->item(0)->nodeValue)
02225 .'\')';
02226 phpCAS::traceEnd(FALSE);
02227 return FALSE;
02228 } else {
02229 phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
02230 }
02231 }
02232
02233 // at this step, we are sure that the response of the CAS server was ill-formed
02234 $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
02235 $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
02236
02237 phpCAS::traceEnd(FALSE);
02238 return FALSE;
02239 }
02240
02243
02244
02245
02246
02264 private function readURL($url, &$headers, &$body, &$err_msg)
02265 {
02266 $className = $this->_requestImplementation;
02267 $request = new $className();
02268
02269 if (count($this->_curl_options)) {
02270 $request->setCurlOptions($this->_curl_options);
02271 }
02272
02273 $request->setUrl($url);
02274
02275 if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
02276 phpCAS::error('one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
02277 }
02278 if ($this->_cas_server_ca_cert != '') {
02279 $request->setSslCaCert($this->_cas_server_ca_cert);
02280 }
02281
02282
02283 if ($this->hasSA()) {
02284 $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
02285 $request->addHeader("cache-control: no-cache");
02286 $request->addHeader("pragma: no-cache");
02287 $request->addHeader("accept: text/xml");
02288 $request->addHeader("connection: keep-alive");
02289 $request->addHeader("content-type: text/xml");
02290 $request->makePost();
02291 $request->setPostBody($this->buildSAMLPayload());
02292 }
02293
02294 if ($request->send()) {
02295 $headers = $request->getResponseHeaders();
02296 $body = $request->getResponseBody();
02297 $err_msg = '';
02298 return true;
02299 } else {
02300 $headers = '';
02301 $body = '';
02302 $err_msg = $request->getErrorMessage();
02303 return false;
02304 }
02305 }
02306
02312 private function buildSAMLPayload()
02313 {
02314 phpCAS::traceBegin();
02315
02316
02317 $sa = $this->getSA();
02318
02319 $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
02320
02321 phpCAS::traceEnd($body);
02322 return ($body);
02323 }
02324
02325 private $_curl_headers = array();
02329 public function _curl_read_headers($ch, $header)
02330 {
02331 $this->_curl_headers[] = $header;
02332 return strlen($header);
02333 }
02334
02337
02338
02339
02340
02359 public function getProxiedService ($type) {
02360 switch ($type) {
02361 case PHPCAS_PROXIED_SERVICE_HTTP_GET:
02362 case PHPCAS_PROXIED_SERVICE_HTTP_POST:
02363 $requestClass = $this->_requestImplementation;
02364 $request = new $requestClass();
02365 if (count($this->_curl_options)) {
02366 $request->setCurlOptions($this->_curl_options);
02367 }
02368 $proxiedService = new $type($request, $this->_serviceCookieJar);
02369 if ($proxiedService instanceof CAS_ProxiedService_Testable)
02370 $proxiedService->setCasClient($this);
02371 return $proxiedService;
02372 case PHPCAS_PROXIED_SERVICE_IMAP;
02373 $proxiedService = new CAS_ProxiedService_Imap($this->getUser());
02374 if ($proxiedService instanceof CAS_ProxiedService_Testable)
02375 $proxiedService->setCasClient($this);
02376 return $proxiedService;
02377 default:
02378 throw new CAS_InvalidArgumentException("Unknown proxied-service type, $type.");
02379 }
02380 }
02381
02394 public function initializeProxiedService (CAS_ProxiedService $proxiedService) {
02395 $url = $proxiedService->getServiceUrl();
02396 if (!is_string($url))
02397 throw new CAS_ProxiedService_Exception("Proxied Service ".get_class($proxiedService)."->getServiceUrl() should have returned a string, returned a ".gettype($url)." instead.");
02398
02399 $pt = $this->retrievePT($url, $err_code, $err_msg);
02400 if (!$pt)
02401 throw new CAS_ProxyTicketException($err_msg, $err_code);
02402 $proxiedService->setProxyTicket($pt);
02403 }
02404
02418 public function serviceWeb($url,&$err_code,&$output)
02419 {
02420 try {
02421 $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
02422 $service->setUrl($url);
02423 $service->send();
02424 $output = $service->getResponseBody();
02425 $err_code = PHPCAS_SERVICE_OK;
02426 return TRUE;
02427 } catch (CAS_ProxyTicketException $e) {
02428 $err_code = $e->getCode();
02429 $output = $e->getMessage();
02430 return FALSE;
02431 } catch (CAS_ProxiedService_Exception $e) {
02432 $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), $url, $e->getMessage());
02433 $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
02434 return FALSE;
02435 }
02436 }
02437
02455 public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
02456 {
02457 try {
02458 $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
02459 $service->setServiceUrl($serviceUrl);
02460 $service->setMailbox($url);
02461 $service->setOptions($flags);
02462
02463 $stream = $service->open();
02464 $err_code = PHPCAS_SERVICE_OK;
02465 $pt = $service->getImapProxyTicket();
02466 return $stream;
02467 } catch (CAS_ProxyTicketException $e) {
02468 $err_msg = $e->getMessage();
02469 $err_code = $e->getCode();
02470 $pt = FALSE;
02471 return FALSE;
02472 } catch (CAS_ProxiedService_Exception $e) {
02473 $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), $url, $e->getMessage());
02474 $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
02475 $pt = FALSE;
02476 return FALSE;
02477 }
02478 }
02479
02482
02483
02484
02485
02486
02487
02488
02489
02490
02503 private $_pt = '';
02504
02509 public function getPT()
02510 {
02511
02512 return $this->_pt;
02513 }
02514
02519 public function setPT($pt)
02520 { $this->_pt = $pt; }
02521
02526 public function hasPT()
02527 { return !empty($this->_pt); }
02528
02529
02538 private $_proxies = array();
02539
02550 public function getProxies () {
02551 return $this->_proxies;
02552 }
02553
02562 private function setProxies ($proxies) {
02563 $this->_proxies = $proxies;
02564 }
02565
02566
02568
02569
02570
02581 public function validatePT(&$validate_url,&$text_response,&$tree_response)
02582 {
02583 phpCAS::traceBegin();
02584 phpCAS::trace($text_response);
02585
02586 $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
02587
02588 if ( $this->isProxy() ) {
02589
02590 $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
02591 }
02592
02593
02594 if ( !$this->readURL($validate_url,$headers,$text_response,$err_msg) ) {
02595 phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
02596 $this->authError('PT not validated',
02597 $validate_url,
02598 TRUE/*$no_response*/);
02599 }
02600
02601 // create new DOMDocument object
02602 $dom = new DOMDocument();
02603 // Fix possible whitspace problems
02604 $dom->preserveWhiteSpace = false;
02605 // read the response of the CAS server into a DOMDocument object
02606 if ( !($dom->loadXML($text_response))) {
02607 // read failed
02608 $this->authError('PT not validated',
02609 $validate_url,
02610 FALSE/*$no_response*/,
02611 TRUE/*$bad_response*/,
02612 $text_response);
02613 }
02614
02615 // read the root node of the XML tree
02616 if ( !($tree_response = $dom->documentElement) ) {
02617 // read failed
02618 $this->authError('PT not validated',
02619 $validate_url,
02620 FALSE/*$no_response*/,
02621 TRUE/*$bad_response*/,
02622 $text_response);
02623 }
02624 // insure that tag name is 'serviceResponse'
02625 if ( $tree_response->localName != 'serviceResponse' ) {
02626 // bad root node
02627 $this->authError('PT not validated',
02628 $validate_url,
02629 FALSE/*$no_response*/,
02630 TRUE/*$bad_response*/,
02631 $text_response);
02632 }
02633 if ( $tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
02634 // authentication succeded, extract the user name
02635 $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
02636 if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
02637 // no user specified => error
02638 $this->authError('PT not validated',
02639 $validate_url,
02640 FALSE/*$no_response*/,
02641 TRUE/*$bad_response*/,
02642 $text_response);
02643 }
02644
02645 $this->setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue));
02646 $this->readExtraAttributesCas20($success_elements);
02647
02648 // Store the proxies we are sitting behind for authorization checking
02649 if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
02650 foreach ($arr as $proxyElem) {
02651 phpCAS::trace("Storing Proxy: ".$proxyElem->nodeValue);
02652 $this->_proxies[] = trim($proxyElem->nodeValue);
02653 }
02654 $_SESSION['phpCAS']['proxies'] = $this->_proxies;
02655 }
02656
02657 } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
02658 // authentication succeded, extract the error code and message
02659 $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
02660 $this->authError('PT not validated',
02661 $validate_url,
02662 FALSE/*$no_response*/,
02663 FALSE/*$bad_response*/,
02664 $text_response,
02665 $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
02666 trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/);
02667 } else {
02668 $this->authError('PT not validated',
02669 $validate_url,
02670 FALSE/*$no_response*/,
02671 TRUE/*$bad_response*/,
02672 $text_response);
02673 }
02674
02675 $this->renameSession($this->getPT());
02676 // at this step, PT has been validated and $this->_user has been set,
02677
02678 phpCAS::traceEnd(TRUE);
02679 return TRUE;
02680 }
02681
02682
02690 private function readExtraAttributesCas20($success_elements)
02691 {
02692 # PHPCAS-43 add CAS-2.0 extra attributes
02693 phpCAS::traceBegin();
02694
02695 $extra_attributes = array();
02696
02697 // "Jasig Style" Attributes:
02698 //
02699 // <cas:serviceResponse xmlns:cas='http:
02700
02701
02702
02703
02704
02705
02706
02707
02708
02709
02710
02711
02712
02713 if ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
02714 $attr_nodes = $success_elements->item(0)->getElementsByTagName("attributes");
02715 phpCas :: trace("Found nested jasig style attributes");
02716 if($attr_nodes->item(0)->hasChildNodes()){
02717
02718 foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
02719 phpCas :: trace("Attribute [".$attr_child->localName."] = ".$attr_child->nodeValue);
02720 $this->addAttributeToArray($extra_attributes, $attr_child->localName, $attr_child->nodeValue);
02721 }
02722 }
02723 }
02724
02725
02726
02727
02728
02729
02730
02731
02732
02733
02734
02735
02736
02737
02738
02739
02740 else {
02741 phpCas :: trace("Testing for rubycas style attributes");
02742 $childnodes = $success_elements->item(0)->childNodes;
02743 foreach ($childnodes as $attr_node) {
02744 switch ($attr_node->localName) {
02745 case 'user':
02746 case 'proxies':
02747 case 'proxyGrantingTicket':
02748 continue;
02749 default:
02750 if (strlen(trim($attr_node->nodeValue))) {
02751 phpCas :: trace("Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue);
02752 $this->addAttributeToArray($extra_attributes, $attr_node->localName, $attr_node->nodeValue);
02753 }
02754 }
02755 }
02756 }
02757
02758
02759
02760
02761
02762
02763
02764
02765
02766
02767
02768
02769
02770
02771
02772
02773
02774
02775
02776
02777
02778 if (!count($extra_attributes) && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0) {
02779 $attr_nodes = $success_elements->item(0)->getElementsByTagName("attribute");
02780 $firstAttr = $attr_nodes->item(0);
02781 if (!$firstAttr->hasChildNodes() && $firstAttr->hasAttribute('name') && $firstAttr->hasAttribute('value')) {
02782 phpCas :: trace("Found Name-Value style attributes");
02783
02784 foreach ($attr_nodes as $attr_node) {
02785 if ($attr_node->hasAttribute('name') && $attr_node->hasAttribute('value')) {
02786 phpCas :: trace("Attribute [".$attr_node->getAttribute('name')."] = ".$attr_node->getAttribute('value'));
02787 $this->addAttributeToArray($extra_attributes, $attr_node->getAttribute('name'), $attr_node->getAttribute('value'));
02788 }
02789 }
02790 }
02791 }
02792
02793 $this->setAttributes($extra_attributes);
02794 phpCAS::traceEnd();
02795 return TRUE;
02796 }
02797
02806 private function addAttributeToArray (array &$attributeArray, $name, $value) {
02807
02808 if (isset($attributeArray[$name])) {
02809
02810 if (!is_array($attributeArray[$name])) {
02811 $existingValue = $attributeArray[$name];
02812 $attributeArray[$name] = array($existingValue);
02813 }
02814
02815 $attributeArray[$name][] = trim($value);
02816 } else {
02817 $attributeArray[$name] = trim($value);
02818 }
02819 }
02820
02823
02824
02825
02826
02827
02828
02834
02835
02836
02843 private $_url = '';
02844
02845
02851 public function setURL($url)
02852 {
02853 $this->_url = $url;
02854 }
02855
02862 public function getURL()
02863 {
02864 phpCAS::traceBegin();
02865
02866 if ( empty($this->_url) ) {
02867 $final_uri = '';
02868
02869 $final_uri = ($this->isHttps()) ? 'https' : 'http';
02870 $final_uri .= '://';
02871
02872 $final_uri .= $this->getServerUrl();
02873 $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
02874 $final_uri .= $request_uri[0];
02875
02876 if (isset($request_uri[1]) && $request_uri[1])
02877 {
02878 $query_string = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
02879
02880
02881 if ($query_string !== '')
02882 $final_uri .= "?$query_string";
02883
02884 }
02885
02886 phpCAS::trace("Final URI: $final_uri");
02887 $this->setURL($final_uri);
02888 }
02889 phpCAS::traceEnd($this->_url);
02890 return $this->_url;
02891 }
02892
02893
02898 private function getServerUrl(){
02899 $server_url = '';
02900 if(!empty($_SERVER['HTTP_X_FORWARDED_HOST'])){
02901
02902 $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
02903 $server_url = $hosts[0];
02904 }else if(!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
02905 $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
02906 }else{
02907 if (empty($_SERVER['SERVER_NAME'])) {
02908 $server_url = $_SERVER['HTTP_HOST'];
02909 } else {
02910 $server_url = $_SERVER['SERVER_NAME'];
02911 }
02912 }
02913 if (!strpos($server_url, ':')) {
02914 if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
02915 || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
02916 $server_url .= ':';
02917 $server_url .= $_SERVER['SERVER_PORT'];
02918 }
02919 }
02920 return $server_url;
02921 }
02922
02927 private function isHttps() {
02928 if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
02929 return true;
02930 } else {
02931 return false;
02932 }
02933 }
02934
02944 private function removeParameterFromQueryString($parameterName, $queryString)
02945 {
02946 $parameterName = preg_quote($parameterName);
02947 return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
02948 }
02949
02958 private function buildQueryUrl($url, $query) {
02959 $url .= (strstr($url,'?') === FALSE) ? '?' : '&';
02960 $url .= $query;
02961 return $url;
02962 }
02963
02967 private function renameSession($ticket)
02968 {
02969 phpCAS::traceBegin();
02970 if($this->_start_session){
02971 if (!empty ($this->_user))
02972 {
02973 $old_session = $_SESSION;
02974 session_destroy();
02975
02976 $session_id = preg_replace('/[^\w]/', '', $ticket);
02977 phpCAS :: trace("Session ID: ".$session_id);
02978 session_id($session_id);
02979 session_start();
02980 phpCAS :: trace("Restoring old session vars");
02981 $_SESSION = $old_session;
02982 } else
02983 {
02984 phpCAS :: error('Session should only be renamed after successfull authentication');
02985 }
02986 }else{
02987 phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
02988 }
02989 phpCAS::traceEnd();
02990 }
02991
02992
02993
02994
02995
03009 private function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
03010 {
03011 phpCAS::traceBegin();
03012
03013 $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
03014 printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
03015 phpCAS::trace('CAS URL: '.$cas_url);
03016 phpCAS::trace('Authentication failure: '.$failure);
03017 if ( $no_response ) {
03018 phpCAS::trace('Reason: no response from the CAS server');
03019 } else {
03020 if ( $bad_response ) {
03021 phpCAS::trace('Reason: bad response from the CAS server');
03022 } else {
03023 switch ($this->getServerVersion()) {
03024 case CAS_VERSION_1_0:
03025 phpCAS::trace('Reason: CAS error');
03026 break;
03027 case CAS_VERSION_2_0:
03028 if ( empty($err_code) )
03029 phpCAS::trace('Reason: no CAS error');
03030 else
03031 phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
03032 break;
03033 }
03034 }
03035 phpCAS::trace('CAS response: '.$cas_response);
03036 }
03037 $this->printHTMLFooter();
03038 phpCAS::traceExit();
03039
03040 if ($this->_exitOnAuthError)
03041 exit();
03042 }
03043
03045 }
03046
03047 ?>