• Main Page
  • Related Pages
  • Modules
  • Classes
  • Files
  • Examples
  • File List
  • File Members

CAS/Client.php

Go to the documentation of this file.
00001 <?php
00002 
00003 /*
00004  * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
00005  * All rights reserved.
00006  *
00007  * Redistribution and use in source and binary forms, with or without
00008  * modification, are permitted provided that the following conditions are met:
00009  *
00010  *     * Redistributions of source code must retain the above copyright notice,
00011  *       this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright notice,
00013  *       this list of conditions and the following disclaimer in the documentation
00014  *       and/or other materials provided with the distribution.
00015  *     * Neither the name of the ESUP-Portail consortium & the JA-SIG
00016  *       Collaborative nor the names of its contributors may be used to endorse or
00017  *       promote products derived from this software without specific prior
00018  *       written permission.
00019 
00020  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00021  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00022  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00023  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
00024  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00025  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00026  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
00027  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00028  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00029  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00030  */
00031 
00037 // include internationalization stuff
00038 include_once(dirname(__FILE__).'/languages/languages.php');
00039 
00040 // include PGT storage classes
00041 include_once(dirname(__FILE__).'/PGTStorage/AbstractStorage.php');
00042 
00043 // include class for storing service cookies.
00044 include_once(dirname(__FILE__).'/CookieJar.php');
00045 
00046 // include class for fetching web requests.
00047 include_once(dirname(__FILE__).'/Request/CurlRequest.php');
00048 
00049 // include classes for proxying access to services
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 // include Exception classes
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         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
00072         // XX                                                                    XX
00073         // XX                          CONFIGURATION                             XX
00074         // XX                                                                    XX
00075         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
00076 
00077         // ########################################################################
00078         //  HTML OUTPUT
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         //  INTERNATIONALIZATION
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                 // call CASclient::getLang() to be sure the language is initialized
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                 // include the corresponding language file
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://' . $this->getServerHostname();
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                 // the URL is build only when needed
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                         // It is recommended that when the "renew" parameter is set, its value be "true"
00361                         $url = $this->buildQueryUrl($url, 'renew=true');
00362                 } elseif ($gateway) {
00363                         // It is recommended that when the "gateway" parameter is set, its value be "true"
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                 // the URL is build only when needed
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                 // the URL is build only when needed
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                 // the URL is build only when needed
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                 // the URL is build only when needed
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                 // the URL is build only when needed
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         //  Change the internal behaviour of phpcas
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         //  CONSTRUCTOR
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                 // skip Session Handling for logout requests and if don't want it'
00708                 if ($start_session && !$this->isLogoutRequest())
00709                 {
00710                         phpCAS :: trace("Starting a new session");
00711                         session_start();
00712                 }
00713 
00714 
00715                 // are we in proxy mode ?
00716                 $this->_proxy = $proxy;
00717 
00718                 // Make cookie handling available.
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                 //check version
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                 // check port
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                 // add leading and trailing `/' and remove doubles
00765                 $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
00766                 $this->_server['uri'] = $server_uri;
00767 
00768                 // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
00769                 if ( $this->isProxy() ) {
00770                         $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
00771                 }
00772 
00773                 if ( $this->isCallbackMode() ) {
00774                         //callback mode: check that phpCAS is secured
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                         //normal mode: get ticket and remove it from CGI parameters for developpers
00780                         $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
00781                         switch ($this->getServerVersion()) {
00782                                 case CAS_VERSION_1_0: // check for a Service Ticket
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: // check for a Service or Proxy Ticket
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: // SAML just does Service Tickets
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         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
00822         // XX                                                                    XX
00823         // XX                           Session Handling                         XX
00824         // XX                                                                    XX
00825         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
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         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
00852         // XX                                                                    XX
00853         // XX                           AUTHENTICATION                           XX
00854         // XX                                                                    XX
00855         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
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          * Atrributes section
00900          *
00901          * @author Matthias Crauwels <matthias.crauwels@ugent.be>, Ghent University, Belgium
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) ) { // if no user is set, there shouldn't be any attributes also...
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                 // Either way, the user is authenticated by CAS
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                         // the user is authenticated, nothing to be done.
00964                         phpCAS::trace('no need to authenticate');
00965                         $res = TRUE;
00966                 } else {
00967                         // the user is not authenticated, redirect to the CAS server
00968                         if (isset($_SESSION['phpCAS']['auth_checked'])) {
00969                                 unset($_SESSION['phpCAS']['auth_checked']);
00970                         }
00971                         $this->redirectToCas(FALSE/* no gateway */);
00972                         // never reached
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                         // the previous request has redirected the client to the CAS server with gateway=true
01010                         unset($_SESSION['phpCAS']['auth_checked']);
01011                         $res = FALSE;
01012                 } else {
01013                         // avoid a check against CAS on every request
01014                         if (! isset($_SESSION['phpCAS']['unauth_count']) )
01015                         $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
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/* gateway */);
01038                                 // never reached
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                                 // User has a additional ticket but was already authenticated
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                                 // the user has already (previously during the session) been
01068                                 // authenticated, nothing to be done.
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                                 // if a Service Ticket was given, validate it
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); // idem
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); // note: if it fails, it halts
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                                 // if we have a SAML ticket, validate it.
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                                 // no ticket given, not authenticated
01120                                 phpCAS::trace('no ticket found');
01121                         }
01122                         if ($res) {
01123                                 // Mark the auth-check as complete to allow post-authentication
01124                                 // callbacks to make use of phpCAS::getUser() and similar methods
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                                 // call the post-authenticate callback if registered.
01136                                 if ($this->_postAuthenticateCallbackFunction) {
01137                                         $args = $this->_postAuthenticateCallbackArgs;
01138                                         array_unshift($args, $logoutTicket);
01139                                         call_user_func_array($this->_postAuthenticateCallbackFunction, $args);
01140                                 }
01141                                 
01142                                 // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
01143                                 // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
01144                                 // remove the ticket as a security precaution to prevent a ticket in the HTTP_REFERRER
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                         // CAS proxy: username and PGT must be present
01188                         if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
01189                                 // authentication already done
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                                 // Include the list of proxies
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                                 // these two variables should be empty or not empty at the same time
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                                 // unset all tickets to enforce authentication
01215                                 unset($_SESSION['phpCAS']);
01216                                 $this->setST('');
01217                                 $this->setPT('');
01218                         } else {
01219                                 phpCAS::trace('neither user not PGT found');
01220                         }
01221                 } else {
01222                         // `simple' CAS client (not a proxy): username must be present
01223                         if ( $this->isSessionAuthenticated() ) {
01224                                 // authentication already done
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                                 // Include the list of proxies
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                 // Extract the ticket from the SAML Request
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                 // call the post-authenticate callback if registered.
01357                 if ($this->_signoutCallbackFunction) {
01358                         $args = $this->_signoutCallbackArgs;
01359                         array_unshift($args, $ticket2logout);
01360                         call_user_func_array($this->_signoutCallbackFunction, $args);
01361                 }
01362                 
01363                 // If phpCAS is managing the session, destroy it.
01364                 if ($this->_start_session) {
01365                         $session_id = preg_replace('/[^\w]/','',$ticket2logout);
01366                         phpCAS::trace("Session id: ".$session_id);
01367         
01368                         // destroy a possible application session created before phpcas
01369                         if(session_id() !== ""){
01370                                 session_unset();
01371                                 session_destroy();
01372                         }
01373                         // fix session ID
01374                         session_id($session_id);
01375                         $_COOKIE[session_name()]=$session_id;
01376                         $_GET[session_name()]=$session_id;
01377         
01378                         // Overwrite session
01379                         session_start();
01380                         session_unset();
01381                         session_destroy();
01382                 }
01383                 
01384                 printf("Disconnected!");
01385                 phpCAS::traceExit();
01386                 exit();
01387         }
01388 
01391         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01392         // XX                                                                    XX
01393         // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
01394         // XX                                                                    XX
01395         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01396 
01397         // ########################################################################
01398         //  ST
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         //  ST VALIDATION
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                 // build the URL to validate the ticket
01493                 $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
01494                 if ( $this->isProxy() ) {
01495                         // pass the callback url for CAS proxies
01496                         $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
01497                 }
01498 
01499                 // open and read the URL
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/*$no_response*/,
01560                                         TRUE/*$bad_response*/,
01561                                         $text_response);
01562                                 }
01563 
01564                                 if ( $tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
01565                                         // authentication succeded, extract the user name
01566                                         $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
01567                                         if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
01568                                                 // no user specified => error
01569                                                 $this->authError('ST not validated',
01570                                                 $validate_url,
01571                                                 FALSE/*$no_response*/,
01572                                                 TRUE/*$bad_response*/,
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                                         // authentication failed, extract the error code and message
01580                                         $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
01581                                         $this->authError('ST not validated',
01582                                         $validate_url,
01583                                         FALSE/*$no_response*/,
01584                                         FALSE/*$bad_response*/,
01585                                         $text_response,
01586                                         $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
01587                                         trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/);
01588                                 } else {
01589                                         phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
01590                                         $this->authError('ST not validated',
01591                                         $validate_url,
01592                                         FALSE/*$no_response*/,
01593                                         TRUE/*$bad_response*/,
01594                                         $text_response);
01595                                 }
01596                                 break;
01597                 }
01598                 $this->renameSession($this->getST());
01599                 // at this step, ST has been validated and $this->_user has been set,
01600                 phpCAS::traceEnd(TRUE);
01601                 return TRUE;
01602         }
01603         
01607         // ########################################################################
01608         //  SAML VALIDATION
01609         // ########################################################################
01626         public function validateSA($validate_url,&$text_response,&$tree_response)
01627         {
01628                 phpCAS::traceBegin();
01629 
01630                 // build the URL to validate the ticket
01631                 $validate_url = $this->getServerSamlValidateURL();
01632 
01633                 // open and read the URL
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/*$no_response*/,
01673                                         TRUE/*$bad_response*/,
01674                                         $text_response);
01675                                 }
01676                                 // check for the NameIdentifier tag in the SAML response
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/*$no_response*/,
01689                                         TRUE/*$bad_response*/,
01690                                         $text_response);
01691                                 }
01692                                 break;
01693                 }
01694                 $this->renameSession($this->getSA());
01695                 // at this step, ST has been validated and $this->_user has been set,
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                 // create new DOMDocument Object
01716                 $dom = new DOMDocument();
01717                 // Fix possible whitspace problems
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                                 // UGent addition...
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         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01780         // XX                                                                    XX
01781         // XX                     PROXY FEATURES (CAS 2.0)                       XX
01782         // XX                                                                    XX
01783         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01784 
01785         // ########################################################################
01786         //  PROXYING
01787         // ########################################################################
01797         private $_proxy;
01798 
01802         private $_serviceCookieJar;
01803 
01809         public function isProxy()
01810         {
01811                 return $this->_proxy;
01812         }
01813 
01815         // ########################################################################
01816         //  PGT
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         //  CALLBACK MODE
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                 // the URL is built when needed only
01915                 if ( empty($this->_callback_url) ) {
01916                         $final_uri = '';
01917                         // remove the ticket if present in the URL
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         //  PGT STORAGE
01968         // ########################################################################
01981         private $_pgt_storage = null;
01982 
01987         private function initPGTStorage()
01988         {
01989                 // if no SetPGTStorageXxx() has been used, default to file
01990                 if ( !is_object($this->_pgt_storage) ) {
01991                         $this->setPGTStorageFile();
01992                 }
01993 
01994                 // initializes the storage
01995                 $this->_pgt_storage->init();
01996         }
01997 
02004         private function storePGT($pgt,$pgt_iou)
02005         {
02006                 // ensure that storage is initialized
02007                 $this->initPGTStorage();
02008                 // writes the PGT
02009                 $this->_pgt_storage->write($pgt,$pgt_iou);
02010         }
02011 
02019         private function loadPGT($pgt_iou)
02020         {
02021                 // ensure that storage is initialized
02022                 $this->initPGTStorage();
02023                 // read the PGT
02024                 return $this->_pgt_storage->read($pgt_iou);
02025         }
02026 
02032         public function setPGTStorage($storage)
02033         {
02034                 // check that the storage has not already been set
02035                 if ( is_object($this->_pgt_storage) ) {
02036                         phpCAS::error('PGT storage already defined');
02037                 }
02038 
02039                 // check to make sure a valid storage object was specified
02040                 if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) {
02041                         phpCAS::error('Invalid PGT storage object');
02042                 }
02043 
02044                 // store the PGTStorage object
02045                 $this->_pgt_storage = $storage;
02046         }
02047 
02058         public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null)
02059         {
02060                 // create the storage object
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                 // create the storage object
02074                 $this->setPGTStorage(new CAS_PGTStorage_File($this,$path));
02075         }
02076 
02077 
02078         // ########################################################################
02079         //  PGT VALIDATION
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                         // authentication succeded, but no PGT Iou was transmitted
02098                         $this->authError('Ticket validated but no PGT Iou transmitted',
02099                         $validate_url,
02100                         FALSE/*$no_response*/,
02101                         FALSE/*$bad_response*/,
02102                         $text_response);
02103                 } else {
02104                         // PGT Iou transmitted, extract it
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/*$no_response*/,
02113                                         FALSE/*$bad_response*/,
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/*$no_response*/,
02122                                 FALSE/*$bad_response*/,
02123                                 $text_response);
02124                         }
02125                 }
02126                 phpCAS::traceEnd(TRUE);
02127                 return TRUE;
02128         }
02129 
02130         // ########################################################################
02131         //  PGT VALIDATION
02132         // ########################################################################
02133 
02143         public function retrievePT($target_service,&$err_code,&$err_msg)
02144         {
02145                 phpCAS::traceBegin();
02146 
02147                 // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
02148                 // set to false and $err_msg to an error message. At the end, if $pt is FALSE
02149                 // and $error_msg is still empty, it is set to 'invalid response' (the most
02150                 // commonly encountered error).
02151                 $err_msg = '';
02152 
02153                 // build the URL to retrieve the PT
02154                 $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT();
02155 
02156                 // open and read the URL
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         // READ CAS SERVER ANSWERS
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                 // add extra stuff if SAML
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                 //get the ticket
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         // ACCESS TO EXTERNAL SERVICES
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         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
02483         // XX                                                                    XX
02484         // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
02485         // XX                                                                    XX
02486         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
02487 
02488         // ########################################################################
02489         //  PT
02490         // ########################################################################
02503         private  $_pt = '';
02504 
02509         public function getPT()
02510         {
02511                 //      return 'ST'.substr($this->_pt, 2);
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         //  PT VALIDATION
02570         // ########################################################################
02581         public function validatePT(&$validate_url,&$text_response,&$tree_response)
02582         {
02583                 phpCAS::traceBegin();
02584                 phpCAS::trace($text_response);
02585                 // build the URL to validate the ticket
02586                 $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
02587 
02588                 if ( $this->isProxy() ) {
02589                         // pass the callback url for CAS proxies
02590                         $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
02591                 }
02592 
02593                 // open and read the URL
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://www.yale.edu/tp/cas'>
02700                 //              <cas:authenticationSuccess>
02701                 //                      <cas:user>jsmith</cas:user>
02702                 //                      <cas:attributes>
02703                 //                              <cas:attraStyle>RubyCAS</cas:attraStyle>
02704                 //                              <cas:surname>Smith</cas:surname>
02705                 //                              <cas:givenName>John</cas:givenName>
02706                 //                              <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
02707                 //                              <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
02708                 //                      </cas:attributes>
02709                 //                      <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
02710                 //              </cas:authenticationSuccess>
02711                 //      </cas:serviceResponse>
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                                 // Nested Attributes
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                 // "RubyCAS Style" attributes
02725                 // 
02726                 //      <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
02727                 //              <cas:authenticationSuccess>
02728                 //                      <cas:user>jsmith</cas:user>
02729                 //                      
02730                 //                      <cas:attraStyle>RubyCAS</cas:attraStyle>
02731                 //                      <cas:surname>Smith</cas:surname>
02732                 //                      <cas:givenName>John</cas:givenName>
02733                 //                      <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
02734                 //                      <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
02735                 //                      
02736                 //                      <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
02737                 //              </cas:authenticationSuccess>
02738                 //      </cas:serviceResponse>
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                 // "Name-Value" attributes.
02759                 // 
02760                 // Attribute format from these mailing list thread:
02761                 // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
02762                 // Note: This is a less widely used format, but in use by at least two institutions.
02763                 // 
02764                 //      <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
02765                 //              <cas:authenticationSuccess>
02766                 //                      <cas:user>jsmith</cas:user>
02767                 //                      
02768                 //                      <cas:attribute name='attraStyle' value='Name-Value' />
02769                 //                      <cas:attribute name='surname' value='Smith' />
02770                 //                      <cas:attribute name='givenName' value='John' />
02771                 //                      <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
02772                 //                      <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
02773                 //                      
02774                 //                      <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
02775                 //              </cas:authenticationSuccess>
02776                 //      </cas:serviceResponse>
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                                 // Nested Attributes
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                 // If multiple attributes exist, add as an array value
02808                 if (isset($attributeArray[$name])) {
02809                         // Initialize the array with the existing value
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         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
02824         // XX                                                                    XX
02825         // XX                               MISC                                 XX
02826         // XX                                                                    XX
02827         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
02828 
02834         // ########################################################################
02835         //  URL
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                 // the URL is built when needed only
02866                 if ( empty($this->_url) ) {
02867                         $final_uri = '';
02868                         // remove the ticket if present in the URL
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                                 // If the query string still has anything left, append it to the final URI
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                         // explode the host list separated by comma and use the first host
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                                 // set up a new session, of name based on the ticket
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         //  AUTHENTICATION ERROR HANDLING
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 ?>

Generated on Sun Jun 5 2011 19:05:26 for phpCAS by  doxygen 1.7.1