4 * Licensed to Jasig under one or more contributor license
5 * agreements. See the NOTICE file distributed with this work for
6 * additional information regarding copyright ownership.
8 * Jasig licenses this file to you under the Apache License,
9 * Version 2.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at:
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
22 * @file CAS/Client.php
23 * @category Authentication
25 * @author Pascal Aubry <pascal.aubry@univ-rennes1.fr>
26 * @author Olivier Berger <olivier.berger@it-sudparis.eu>
27 * @author Brett Bieber <brett.bieber@gmail.com>
28 * @author Joachim Fritschi <jfritschi@freenet.de>
29 * @author Adam Franco <afranco@middlebury.edu>
30 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
31 * @link https://wiki.jasig.org/display/CASC/phpCAS
35 * The CAS_Client class is a client interface that provides CAS authentication
36 * to PHP applications.
39 * @category Authentication
41 * @author Pascal Aubry <pascal.aubry@univ-rennes1.fr>
42 * @author Olivier Berger <olivier.berger@it-sudparis.eu>
43 * @author Brett Bieber <brett.bieber@gmail.com>
44 * @author Joachim Fritschi <jfritschi@freenet.de>
45 * @author Adam Franco <afranco@middlebury.edu>
46 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
47 * @link https://wiki.jasig.org/display/CASC/phpCAS
54 // ########################################################################
56 // ########################################################################
58 * @addtogroup internalOutput
63 * This method filters a string by replacing special tokens by appropriate values
64 * and prints it. The corresponding tokens are taken into account:
66 * - __PHPCAS_VERSION__
67 * - __SERVER_BASE_URL__
69 * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter().
71 * @param string $str the string to filter and output
75 private function _htmlFilterOutput($str)
77 $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str);
78 $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str);
79 $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str);
84 * A string used to print the header of HTML pages. Written by
85 * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader().
88 * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader()
90 private $_output_header = '';
93 * This method prints the header of the HTML output (after filtering). If
94 * CAS_Client::setHTMLHeader() was not used, a default header is output.
96 * @param string $title the title of the page
99 * @see _htmlFilterOutput()
101 public function printHTMLHeader($title)
103 $this->_htmlFilterOutput(
106 (empty($this->_output_header)
107 ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
108 : $this->_output_header)
114 * A string used to print the footer of HTML pages. Written by
115 * CAS_Client::setHTMLFooter(), read by printHTMLFooter().
118 * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter()
120 private $_output_footer = '';
123 * This method prints the footer of the HTML output (after filtering). If
124 * CAS_Client::setHTMLFooter() was not used, a default footer is output.
127 * @see _htmlFilterOutput()
129 public function printHTMLFooter()
131 $lang = $this->getLangObj();
132 $this->_htmlFilterOutput(
133 empty($this->_output_footer)?
134 ('<hr><address>phpCAS __PHPCAS_VERSION__ '
135 .$lang->getUsingServer()
136 .' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>')
137 :$this->_output_footer
142 * This method set the HTML header used for all outputs.
144 * @param string $header the HTML header.
148 public function setHTMLHeader($header)
150 $this->_output_header = $header;
154 * This method set the HTML footer used for all outputs.
156 * @param string $footer the HTML footer.
160 public function setHTMLFooter($footer)
162 $this->_output_footer = $footer;
169 // ########################################################################
170 // INTERNATIONALIZATION
171 // ########################################################################
173 * @addtogroup internalLang
177 * A string corresponding to the language used by phpCAS. Written by
178 * CAS_Client::setLang(), read by CAS_Client::getLang().
180 * @note debugging information is always in english (debug purposes only).
182 private $_lang = PHPCAS_LANG_DEFAULT;
185 * This method is used to set the language used by phpCAS.
187 * @param string $lang representing the language.
191 public function setLang($lang)
193 phpCAS::traceBegin();
195 if (!($obj instanceof CAS_Languages_LanguageInterface)) {
196 throw new CAS_InvalidArgumentException('$className must implement the CAS_Languages_LanguageInterface');
198 $this->_lang = $lang;
202 * Create the language
204 * @return CAS_Languages_LanguageInterface object implementing the class
206 public function getLangObj()
208 $classname = $this->_lang;
209 return new $classname();
213 // ########################################################################
215 // ########################################################################
217 * @addtogroup internalConfig
222 * a record to store information about the CAS server.
223 * - $_server['version']: the version of the CAS server
224 * - $_server['hostname']: the hostname of the CAS server
225 * - $_server['port']: the port the CAS server is running on
226 * - $_server['uri']: the base URI the CAS server is responding on
227 * - $_server['base_url']: the base URL of the CAS server
228 * - $_server['login_url']: the login URL of the CAS server
229 * - $_server['service_validate_url']: the service validating URL of the
231 * - $_server['proxy_url']: the proxy URL of the CAS server
232 * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server
233 * - $_server['logout_url']: the logout URL of the CAS server
235 * $_server['version'], $_server['hostname'], $_server['port'] and
236 * $_server['uri'] are written by CAS_Client::CAS_Client(), read by
237 * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(),
238 * CAS_Client::_getServerPort() and CAS_Client::_getServerURI().
240 * The other fields are written and read by CAS_Client::_getServerBaseURL(),
241 * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(),
242 * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL().
246 private $_server = array(
248 'hostname' => 'none',
253 * This method is used to retrieve the version of the CAS server.
255 * @return string the version of the CAS server.
257 public function getServerVersion()
259 return $this->_server['version'];
263 * This method is used to retrieve the hostname of the CAS server.
265 * @return string the hostname of the CAS server.
267 private function _getServerHostname()
269 return $this->_server['hostname'];
273 * This method is used to retrieve the port of the CAS server.
275 * @return string the port of the CAS server.
277 private function _getServerPort()
279 return $this->_server['port'];
283 * This method is used to retrieve the URI of the CAS server.
285 * @return string a URI.
287 private function _getServerURI()
289 return $this->_server['uri'];
293 * This method is used to retrieve the base URL of the CAS server.
295 * @return string a URL.
297 private function _getServerBaseURL()
299 // the URL is build only when needed
300 if ( empty($this->_server['base_url']) ) {
301 $this->_server['base_url'] = 'https://' . $this->_getServerHostname();
302 if ($this->_getServerPort()!=443) {
303 $this->_server['base_url'] .= ':'
304 .$this->_getServerPort();
306 $this->_server['base_url'] .= $this->_getServerURI();
308 return $this->_server['base_url'];
312 * This method is used to retrieve the login URL of the CAS server.
314 * @param bool $gateway true to check authentication, false to force it
315 * @param bool $renew true to force the authentication with the CAS server
318 * @note It is recommended that CAS implementations ignore the "gateway"
319 * parameter if "renew" is set
321 public function getServerLoginURL($gateway=false,$renew=false)
323 phpCAS::traceBegin();
324 // the URL is build only when needed
325 if ( empty($this->_server['login_url']) ) {
326 $this->_server['login_url'] = $this->_getServerBaseURL();
327 $this->_server['login_url'] .= 'login?service=';
328 $this->_server['login_url'] .= urlencode($this->getURL());
330 $url = $this->_server['login_url'];
332 // It is recommended that when the "renew" parameter is set, its
334 $url = $this->_buildQueryUrl($url, 'renew=true');
335 } elseif ($gateway) {
336 // It is recommended that when the "gateway" parameter is set, its
338 $url = $this->_buildQueryUrl($url, 'gateway=true');
340 phpCAS::traceEnd($url);
345 * This method sets the login URL of the CAS server.
347 * @param string $url the login URL
349 * @return string login url
351 public function setServerLoginURL($url)
353 return $this->_server['login_url'] = $url;
358 * This method sets the serviceValidate URL of the CAS server.
360 * @param string $url the serviceValidate URL
362 * @return string serviceValidate URL
364 public function setServerServiceValidateURL($url)
366 return $this->_server['service_validate_url'] = $url;
371 * This method sets the proxyValidate URL of the CAS server.
373 * @param string $url the proxyValidate URL
375 * @return string proxyValidate URL
377 public function setServerProxyValidateURL($url)
379 return $this->_server['proxy_validate_url'] = $url;
384 * This method sets the samlValidate URL of the CAS server.
386 * @param string $url the samlValidate URL
388 * @return string samlValidate URL
390 public function setServerSamlValidateURL($url)
392 return $this->_server['saml_validate_url'] = $url;
397 * This method is used to retrieve the service validating URL of the CAS server.
399 * @return string serviceValidate URL.
401 public function getServerServiceValidateURL()
403 phpCAS::traceBegin();
404 // the URL is build only when needed
405 if ( empty($this->_server['service_validate_url']) ) {
406 switch ($this->getServerVersion()) {
407 case CAS_VERSION_1_0:
408 $this->_server['service_validate_url'] = $this->_getServerBaseURL()
411 case CAS_VERSION_2_0:
412 $this->_server['service_validate_url'] = $this->_getServerBaseURL()
417 $url = $this->_buildQueryUrl($this->_server['service_validate_url'], 'service='.urlencode($this->getURL()));
418 phpCAS::traceEnd($url);
422 * This method is used to retrieve the SAML validating URL of the CAS server.
424 * @return string samlValidate URL.
426 public function getServerSamlValidateURL()
428 phpCAS::traceBegin();
429 // the URL is build only when needed
430 if ( empty($this->_server['saml_validate_url']) ) {
431 switch ($this->getServerVersion()) {
432 case SAML_VERSION_1_1:
433 $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate';
438 $url = $this->_buildQueryUrl($this->_server['saml_validate_url'], 'TARGET='.urlencode($this->getURL()));
439 phpCAS::traceEnd($url);
444 * This method is used to retrieve the proxy validating URL of the CAS server.
446 * @return string proxyValidate URL.
448 public function getServerProxyValidateURL()
450 phpCAS::traceBegin();
451 // the URL is build only when needed
452 if ( empty($this->_server['proxy_validate_url']) ) {
453 switch ($this->getServerVersion()) {
454 case CAS_VERSION_1_0:
455 $this->_server['proxy_validate_url'] = '';
457 case CAS_VERSION_2_0:
458 $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate';
462 $url = $this->_buildQueryUrl($this->_server['proxy_validate_url'], 'service='.urlencode($this->getURL()));
463 phpCAS::traceEnd($url);
469 * This method is used to retrieve the proxy URL of the CAS server.
471 * @return string proxy URL.
473 public function getServerProxyURL()
475 // the URL is build only when needed
476 if ( empty($this->_server['proxy_url']) ) {
477 switch ($this->getServerVersion()) {
478 case CAS_VERSION_1_0:
479 $this->_server['proxy_url'] = '';
481 case CAS_VERSION_2_0:
482 $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy';
486 return $this->_server['proxy_url'];
490 * This method is used to retrieve the logout URL of the CAS server.
492 * @return string logout URL.
494 public function getServerLogoutURL()
496 // the URL is build only when needed
497 if ( empty($this->_server['logout_url']) ) {
498 $this->_server['logout_url'] = $this->_getServerBaseURL().'logout';
500 return $this->_server['logout_url'];
504 * This method sets the logout URL of the CAS server.
506 * @param string $url the logout URL
508 * @return string logout url
510 public function setServerLogoutURL($url)
512 return $this->_server['logout_url'] = $url;
516 * An array to store extra curl options.
518 private $_curl_options = array();
521 * This method is used to set additional user curl options.
523 * @param string $key name of the curl option
524 * @param string $value value of the curl option
528 public function setExtraCurlOption($key, $value)
530 $this->_curl_options[$key] = $value;
535 // ########################################################################
536 // Change the internal behaviour of phpcas
537 // ########################################################################
540 * @addtogroup internalBehave
545 * The class to instantiate for making web requests in readUrl().
546 * The class specified must implement the CAS_Request_RequestInterface.
547 * By default CAS_Request_CurlRequest is used, but this may be overridden to
548 * supply alternate request mechanisms for testing.
550 private $_requestImplementation = 'CAS_Request_CurlRequest';
553 * Override the default implementation used to make web requests in readUrl().
554 * This class must implement the CAS_Request_RequestInterface.
556 * @param string $className name of the RequestImplementation class
560 public function setRequestImplementation ($className)
562 $obj = new $className;
563 if (!($obj instanceof CAS_Request_RequestInterface)) {
564 throw new CAS_InvalidArgumentException('$className must implement the CAS_Request_RequestInterface');
566 $this->_requestImplementation = $className;
570 * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session
571 * tickets from the URL after a successful authentication.
573 private $_clearTicketsFromUrl = true;
576 * Configure the client to not send redirect headers and call exit() on
577 * authentication success. The normal redirect is used to remove the service
578 * ticket from the client's URL, but for running unit tests we need to
579 * continue without exiting.
581 * Needed for testing authentication
585 public function setNoClearTicketsFromUrl ()
587 $this->_clearTicketsFromUrl = false;
591 * @var callback $_postAuthenticateCallbackFunction;
593 private $_postAuthenticateCallbackFunction = null;
596 * @var array $_postAuthenticateCallbackArgs;
598 private $_postAuthenticateCallbackArgs = array();
601 * Set a callback function to be run when a user authenticates.
603 * The callback function will be passed a $logoutTicket as its first parameter,
604 * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
605 * opaque string that can be used to map a session-id to the logout request
606 * in order to support single-signout in applications that manage their own
607 * sessions (rather than letting phpCAS start the session).
609 * phpCAS::forceAuthentication() will always exit and forward client unless
610 * they are already authenticated. To perform an action at the moment the user
611 * logs in (such as registering an account, performing logging, etc), register
612 * a callback function here.
614 * @param string $function callback function to call
615 * @param array $additionalArgs optional array of arguments
619 public function setPostAuthenticateCallback ($function, array $additionalArgs = array())
621 $this->_postAuthenticateCallbackFunction = $function;
622 $this->_postAuthenticateCallbackArgs = $additionalArgs;
626 * @var callback $_signoutCallbackFunction;
628 private $_signoutCallbackFunction = null;
631 * @var array $_signoutCallbackArgs;
633 private $_signoutCallbackArgs = array();
636 * Set a callback function to be run when a single-signout request is received.
638 * The callback function will be passed a $logoutTicket as its first parameter,
639 * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
640 * opaque string that can be used to map a session-id to the logout request in
641 * order to support single-signout in applications that manage their own sessions
642 * (rather than letting phpCAS start and destroy the session).
644 * @param string $function callback function to call
645 * @param array $additionalArgs optional array of arguments
649 public function setSingleSignoutCallback ($function, array $additionalArgs = array())
651 $this->_signoutCallbackFunction = $function;
652 $this->_signoutCallbackArgs = $additionalArgs;
655 // ########################################################################
656 // Methods for supplying code-flow feedback to integrators.
657 // ########################################################################
660 * Mark the caller of authentication. This will help client integraters determine
661 * problems with their code flow if they call a function such as getUser() before
662 * authentication has occurred.
664 * @param bool $auth True if authentication was successful, false otherwise.
668 public function markAuthenticationCall ($auth)
670 // store where the authentication has been checked and the result
671 $dbg = debug_backtrace();
672 $this->_authentication_caller = array (
673 'file' => $dbg[1]['file'],
674 'line' => $dbg[1]['line'],
675 'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
676 'result' => (boolean)$auth
679 private $_authentication_caller;
682 * Answer true if authentication has been checked.
686 public function wasAuthenticationCalled ()
688 return !empty($this->_authentication_caller);
692 * Answer the result of the authentication call.
694 * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
695 * and markAuthenticationCall() didn't happen.
699 public function wasAuthenticationCallSuccessful ()
701 if (empty($this->_authentication_caller)) {
702 throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
704 return $this->_authentication_caller['result'];
708 * Answer information about the authentication caller.
710 * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
711 * and markAuthenticationCall() didn't happen.
713 * @return array Keys are 'file', 'line', and 'method'
715 public function getAuthenticationCallerFile ()
717 if (empty($this->_authentication_caller)) {
718 throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
720 return $this->_authentication_caller['file'];
724 * Answer information about the authentication caller.
726 * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
727 * and markAuthenticationCall() didn't happen.
729 * @return array Keys are 'file', 'line', and 'method'
731 public function getAuthenticationCallerLine ()
733 if (empty($this->_authentication_caller)) {
734 throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
736 return $this->_authentication_caller['line'];
740 * Answer information about the authentication caller.
742 * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
743 * and markAuthenticationCall() didn't happen.
745 * @return array Keys are 'file', 'line', and 'method'
747 public function getAuthenticationCallerMethod ()
749 if (empty($this->_authentication_caller)) {
750 throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
752 return $this->_authentication_caller['method'];
757 // ########################################################################
759 // ########################################################################
761 * @addtogroup internalConfig
766 * CAS_Client constructor.
768 * @param string $server_version the version of the CAS server
769 * @param bool $proxy true if the CAS client is a CAS proxy
770 * @param string $server_hostname the hostname of the CAS server
771 * @param int $server_port the port the CAS server is running on
772 * @param string $server_uri the URI the CAS server is responding on
773 * @param bool $changeSessionID Allow phpCAS to change the session_id (Single Sign Out/handleLogoutRequests is based on that change)
775 * @return a newly created CAS_Client object
777 public function __construct(
783 $changeSessionID = true
786 phpCAS::traceBegin();
788 $this->_setChangeSessionID($changeSessionID); // true : allow to change the session_id(), false session_id won't be change and logout won't be handle because of that
790 // skip Session Handling for logout requests and if don't want it'
791 if (session_id()=="" && !$this->_isLogoutRequest()) {
792 phpCAS :: trace("Starting a new session");
796 // are we in proxy mode ?
797 $this->_proxy = $proxy;
799 // Make cookie handling available.
800 if ($this->isProxy()) {
801 if (!isset($_SESSION['phpCAS'])) {
802 $_SESSION['phpCAS'] = array();
804 if (!isset($_SESSION['phpCAS']['service_cookies'])) {
805 $_SESSION['phpCAS']['service_cookies'] = array();
807 $this->_serviceCookieJar = new CAS_CookieJar($_SESSION['phpCAS']['service_cookies']);
811 switch ($server_version) {
812 case CAS_VERSION_1_0:
813 if ( $this->isProxy() ) {
815 'CAS proxies are not supported in CAS '.$server_version
819 case CAS_VERSION_2_0:
821 case SAML_VERSION_1_1:
825 'this version of CAS (`'.$server_version
826 .'\') is not supported by phpCAS '.phpCAS::getVersion()
829 $this->_server['version'] = $server_version;
832 if ( empty($server_hostname)
833 || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/', $server_hostname)
835 phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
837 $this->_server['hostname'] = $server_hostname;
840 if ( $server_port == 0
841 || !is_int($server_port)
843 phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
845 $this->_server['port'] = $server_port;
848 if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/', $server_uri) ) {
849 phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
851 // add leading and trailing `/' and remove doubles
852 $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri.'/');
853 $this->_server['uri'] = $server_uri;
855 // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
856 if ( $this->isProxy() ) {
857 $this->_setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
860 if ( $this->_isCallbackMode() ) {
861 //callback mode: check that phpCAS is secured
862 if ( !$this->_isHttps() ) {
863 phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
866 //normal mode: get ticket and remove it from CGI parameters for
868 $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
869 if (preg_match('/^[SP]T-/', $ticket) ) {
870 phpCAS::trace('Ticket \''.$ticket.'\' found');
871 $this->setTicket($ticket);
872 unset($_GET['ticket']);
873 } else if ( !empty($ticket) ) {
874 //ill-formed ticket, halt
875 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
884 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
886 // XX Session Handling XX
888 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
891 * @addtogroup internalConfig
897 * A variable to whether phpcas will use its own session handling. Default = true
900 private $_change_session_id = true;
903 * Set a parameter whether to allow phpCas to change session_id
905 * @param bool $allowed allow phpCas to change session_id
909 private function _setChangeSessionID($allowed)
911 $this->_change_session_id = $allowed;
915 * Get whether phpCas is allowed to change session_id
919 public function getChangeSessionID()
921 return $this->_change_session_id;
926 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
928 // XX AUTHENTICATION XX
930 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
933 * @addtogroup internalAuthentication
938 * The Authenticated user. Written by CAS_Client::_setUser(), read by
939 * CAS_Client::getUser().
946 * This method sets the CAS user's login name.
948 * @param string $user the login name of the authenticated user.
952 private function _setUser($user)
954 $this->_user = $user;
958 * This method returns the CAS user's login name.
960 * @return string the login name of the authenticated user
962 * @warning should be called only after CAS_Client::forceAuthentication() or
963 * CAS_Client::isAuthenticated(), otherwise halt with an error.
965 public function getUser()
967 if ( empty($this->_user) ) {
969 'this method should be used only after '.__CLASS__
970 .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
977 * The Authenticated users attributes. Written by
978 * CAS_Client::setAttributes(), read by CAS_Client::getAttributes().
979 * @attention client applications should use phpCAS::getAttributes().
983 private $_attributes = array();
986 * Set an array of attributes
988 * @param array $attributes a key value array of attributes
992 public function setAttributes($attributes)
994 $this->_attributes = $attributes;
998 * Get an key values arry of attributes
1000 * @return arry of attributes
1002 public function getAttributes()
1004 if ( empty($this->_user) ) {
1005 // if no user is set, there shouldn't be any attributes also...
1007 'this method should be used only after '.__CLASS__
1008 .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
1011 return $this->_attributes;
1015 * Check whether attributes are available
1017 * @return bool attributes available
1019 public function hasAttributes()
1021 return !empty($this->_attributes);
1024 * Check whether a specific attribute with a name is available
1026 * @param string $key name of attribute
1028 * @return bool is attribute available
1030 public function hasAttribute($key)
1032 return (is_array($this->_attributes)
1033 && array_key_exists($key, $this->_attributes));
1037 * Get a specific attribute by name
1039 * @param string $key name of attribute
1041 * @return string attribute values
1043 public function getAttribute($key)
1045 if ($this->hasAttribute($key)) {
1046 return $this->_attributes[$key];
1051 * This method is called to renew the authentication of the user
1052 * If the user is authenticated, renew the connection
1053 * If not, redirect to CAS
1057 public function renewAuthentication()
1059 phpCAS::traceBegin();
1060 // Either way, the user is authenticated by CAS
1061 if (isset( $_SESSION['phpCAS']['auth_checked'])) {
1062 unset($_SESSION['phpCAS']['auth_checked']);
1064 if ( $this->isAuthenticated() ) {
1065 phpCAS::trace('user already authenticated; renew');
1066 $this->redirectToCas(false, true);
1068 $this->redirectToCas();
1074 * This method is called to be sure that the user is authenticated. When not
1075 * authenticated, halt by redirecting to the CAS server; otherwise return true.
1077 * @return true when the user is authenticated; otherwise halt.
1079 public function forceAuthentication()
1081 phpCAS::traceBegin();
1083 if ( $this->isAuthenticated() ) {
1084 // the user is authenticated, nothing to be done.
1085 phpCAS::trace('no need to authenticate');
1088 // the user is not authenticated, redirect to the CAS server
1089 if (isset($_SESSION['phpCAS']['auth_checked'])) {
1090 unset($_SESSION['phpCAS']['auth_checked']);
1092 $this->redirectToCas(false/* no gateway */);
1096 phpCAS::traceEnd($res);
1101 * An integer that gives the number of times authentication will be cached
1106 private $_cache_times_for_auth_recheck = 0;
1109 * Set the number of times authentication will be cached before rechecked.
1111 * @param int $n number of times to wait for a recheck
1115 public function setCacheTimesForAuthRecheck($n)
1117 $this->_cache_times_for_auth_recheck = $n;
1121 * This method is called to check whether the user is authenticated or not.
1123 * @return true when the user is authenticated, false when a previous
1124 * gateway login failed or the function will not return if the user is
1125 * redirected to the cas server for a gateway login attempt
1127 public function checkAuthentication()
1129 phpCAS::traceBegin();
1131 if ( $this->isAuthenticated() ) {
1132 phpCAS::trace('user is authenticated');
1133 /* The 'auth_checked' variable is removed just in case it's set. */
1134 unset($_SESSION['phpCAS']['auth_checked']);
1136 } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
1137 // the previous request has redirected the client to the CAS server
1138 // with gateway=true
1139 unset($_SESSION['phpCAS']['auth_checked']);
1142 // avoid a check against CAS on every request
1143 if (!isset($_SESSION['phpCAS']['unauth_count'])) {
1144 $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
1147 if (($_SESSION['phpCAS']['unauth_count'] != -2
1148 && $this->_cache_times_for_auth_recheck == -1)
1149 || ($_SESSION['phpCAS']['unauth_count'] >= 0
1150 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)
1154 if ($this->_cache_times_for_auth_recheck != -1) {
1155 $_SESSION['phpCAS']['unauth_count']++;
1157 'user is not authenticated (cached for '
1158 .$_SESSION['phpCAS']['unauth_count'].' times of '
1159 .$this->_cache_times_for_auth_recheck.')'
1162 phpCAS::trace('user is not authenticated (cached for until login pressed)');
1165 $_SESSION['phpCAS']['unauth_count'] = 0;
1166 $_SESSION['phpCAS']['auth_checked'] = true;
1167 phpCAS::trace('user is not authenticated (cache reset)');
1168 $this->redirectToCas(true/* gateway */);
1173 phpCAS::traceEnd($res);
1178 * This method is called to check if the user is authenticated (previously or by
1179 * tickets given in the URL).
1181 * @return true when the user is authenticated. Also may redirect to the
1182 * same URL without the ticket.
1184 public function isAuthenticated()
1186 phpCAS::traceBegin();
1189 if ( $this->_wasPreviouslyAuthenticated() ) {
1190 if ($this->hasTicket()) {
1191 // User has a additional ticket but was already authenticated
1192 phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
1193 if ($this->_clearTicketsFromUrl) {
1194 phpCAS::trace("Prepare redirect to : ".$this->getURL());
1195 header('Location: '.$this->getURL());
1197 phpCAS::traceExit();
1198 throw new CAS_GracefullTerminationException();
1200 phpCAS::trace('Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.');
1204 // the user has already (previously during the session) been
1205 // authenticated, nothing to be done.
1206 phpCAS::trace('user was already authenticated, no need to look for tickets');
1210 if ($this->hasTicket()) {
1211 switch ($this->getServerVersion()) {
1212 case CAS_VERSION_1_0:
1213 // if a Service Ticket was given, validate it
1214 phpCAS::trace('CAS 1.0 ticket `'.$this->getTicket().'\' is present');
1215 $this->validateCAS10($validate_url, $text_response, $tree_response); // if it fails, it halts
1216 phpCAS::trace('CAS 1.0 ticket `'.$this->getTicket().'\' was validated');
1217 $_SESSION['phpCAS']['user'] = $this->getUser();
1219 $logoutTicket = $this->getTicket();
1221 case CAS_VERSION_2_0:
1222 // if a Proxy Ticket was given, validate it
1223 phpCAS::trace('CAS 2.0 ticket `'.$this->getTicket().'\' is present');
1224 $this->validateCAS20($validate_url, $text_response, $tree_response); // note: if it fails, it halts
1225 phpCAS::trace('CAS 2.0 ticket `'.$this->getTicket().'\' was validated');
1226 if ( $this->isProxy() ) {
1227 $this->_validatePGT($validate_url, $text_response, $tree_response); // idem
1228 phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated');
1229 $_SESSION['phpCAS']['pgt'] = $this->_getPGT();
1231 $_SESSION['phpCAS']['user'] = $this->getUser();
1232 if ($this->hasAttributes()) {
1233 $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1235 $proxies = $this->getProxies();
1236 if (!empty($proxies)) {
1237 $_SESSION['phpCAS']['proxies'] = $this->getProxies();
1240 $logoutTicket = $this->getTicket();
1242 case SAML_VERSION_1_1:
1243 // if we have a SAML ticket, validate it.
1244 phpCAS::trace('SAML 1.1 ticket `'.$this->getTicket().'\' is present');
1245 $this->validateSA($validate_url, $text_response, $tree_response); // if it fails, it halts
1246 phpCAS::trace('SAML 1.1 ticket `'.$this->getTicket().'\' was validated');
1247 $_SESSION['phpCAS']['user'] = $this->getUser();
1248 $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1250 $logoutTicket = $this->getTicket();
1253 phpCAS::trace('Protocoll error');
1257 // no ticket given, not authenticated
1258 phpCAS::trace('no ticket found');
1261 // Mark the auth-check as complete to allow post-authentication
1262 // callbacks to make use of phpCAS::getUser() and similar methods
1263 $this->markAuthenticationCall($res);
1265 // call the post-authenticate callback if registered.
1266 if ($this->_postAuthenticateCallbackFunction) {
1267 $args = $this->_postAuthenticateCallbackArgs;
1268 array_unshift($args, $logoutTicket);
1269 call_user_func_array($this->_postAuthenticateCallbackFunction, $args);
1272 // if called with a ticket parameter, we need to redirect to the
1273 // app without the ticket so that CAS-ification is transparent
1274 // to the browser (for later POSTS) most of the checks and
1275 // errors should have been made now, so we're safe for redirect
1276 // without masking error messages. remove the ticket as a
1277 // security precaution to prevent a ticket in the HTTP_REFERRER
1278 if ($this->_clearTicketsFromUrl) {
1279 phpCAS::trace("Prepare redirect to : ".$this->getURL());
1280 header('Location: '.$this->getURL());
1282 phpCAS::traceExit();
1283 throw new CAS_GracefullTerminationException();
1288 phpCAS::traceEnd($res);
1293 * This method tells if the current session is authenticated.
1295 * @return true if authenticated based soley on $_SESSION variable
1297 public function isSessionAuthenticated ()
1299 return !empty($_SESSION['phpCAS']['user']);
1303 * This method tells if the user has already been (previously) authenticated
1304 * by looking into the session variables.
1306 * @note This function switches to callback mode when needed.
1308 * @return true when the user has already been authenticated; false otherwise.
1310 private function _wasPreviouslyAuthenticated()
1312 phpCAS::traceBegin();
1314 if ( $this->_isCallbackMode() ) {
1315 // Rebroadcast the pgtIou and pgtId to all nodes
1316 if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) {
1317 $this->_rebroadcast(self::PGTIOU);
1324 if ( $this->isProxy() ) {
1325 // CAS proxy: username and PGT must be present
1326 if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1327 // authentication already done
1328 $this->_setUser($_SESSION['phpCAS']['user']);
1329 if (isset($_SESSION['phpCAS']['attributes'])) {
1330 $this->setAttributes($_SESSION['phpCAS']['attributes']);
1332 $this->_setPGT($_SESSION['phpCAS']['pgt']);
1333 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
1335 // Include the list of proxies
1336 if (isset($_SESSION['phpCAS']['proxies'])) {
1337 $this->_setProxies($_SESSION['phpCAS']['proxies']);
1338 phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"');
1342 } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
1343 // these two variables should be empty or not empty at the same time
1344 phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
1345 // unset all tickets to enforce authentication
1346 unset($_SESSION['phpCAS']);
1347 $this->setTicket('');
1348 } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1349 // these two variables should be empty or not empty at the same time
1350 phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
1351 // unset all tickets to enforce authentication
1352 unset($_SESSION['phpCAS']);
1353 $this->setTicket('');
1355 phpCAS::trace('neither user nor PGT found');
1358 // `simple' CAS client (not a proxy): username must be present
1359 if ( $this->isSessionAuthenticated() ) {
1360 // authentication already done
1361 $this->_setUser($_SESSION['phpCAS']['user']);
1362 if (isset($_SESSION['phpCAS']['attributes'])) {
1363 $this->setAttributes($_SESSION['phpCAS']['attributes']);
1365 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
1367 // Include the list of proxies
1368 if (isset($_SESSION['phpCAS']['proxies'])) {
1369 $this->_setProxies($_SESSION['phpCAS']['proxies']);
1370 phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"');
1375 phpCAS::trace('no user found');
1379 phpCAS::traceEnd($auth);
1384 * This method is used to redirect the client to the CAS server.
1385 * It is used by CAS_Client::forceAuthentication() and
1386 * CAS_Client::checkAuthentication().
1388 * @param bool $gateway true to check authentication, false to force it
1389 * @param bool $renew true to force the authentication with the CAS server
1393 public function redirectToCas($gateway=false,$renew=false)
1395 phpCAS::traceBegin();
1396 $cas_url = $this->getServerLoginURL($gateway, $renew);
1397 if (php_sapi_name() === 'cli') {
1398 @header('Location: '.$cas_url);
1400 header('Location: '.$cas_url);
1402 phpCAS::trace("Redirect to : ".$cas_url);
1403 $lang = $this->getLangObj();
1404 $this->printHTMLHeader($lang->getAuthenticationWanted());
1405 printf('<p>'. $lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
1406 $this->printHTMLFooter();
1407 phpCAS::traceExit();
1408 throw new CAS_GracefullTerminationException();
1413 * This method is used to logout from CAS.
1415 * @param array $params an array that contains the optional url and service
1416 * parameters that will be passed to the CAS server
1420 public function logout($params)
1422 phpCAS::traceBegin();
1423 $cas_url = $this->getServerLogoutURL();
1424 $paramSeparator = '?';
1425 if (isset($params['url'])) {
1426 $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']);
1427 $paramSeparator = '&';
1429 if (isset($params['service'])) {
1430 $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
1432 header('Location: '.$cas_url);
1433 phpCAS::trace("Prepare redirect to : ".$cas_url);
1437 $lang = $this->getLangObj();
1438 $this->printHTMLHeader($lang->getLogout());
1439 printf('<p>'.$lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
1440 $this->printHTMLFooter();
1441 phpCAS::traceExit();
1442 throw new CAS_GracefullTerminationException();
1446 * Check of the current request is a logout request
1448 * @return bool is logout request.
1450 private function _isLogoutRequest()
1452 return !empty($_POST['logoutRequest']);
1456 * This method handles logout requests.
1458 * @param bool $check_client true to check the client bofore handling
1459 * the request, false not to perform any access control. True by default.
1460 * @param bool $allowed_clients an array of host names allowed to send
1465 public function handleLogoutRequests($check_client=true, $allowed_clients=false)
1467 phpCAS::traceBegin();
1468 if (!$this->_isLogoutRequest()) {
1469 phpCAS::trace("Not a logout request");
1473 if (!$this->getChangeSessionID() && is_null($this->_signoutCallbackFunction)) {
1474 phpCAS::trace("phpCAS can't handle logout requests if it is not allowed to change session_id.");
1476 phpCAS::trace("Logout requested");
1477 $decoded_logout_rq = urldecode($_POST['logoutRequest']);
1478 phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq);
1480 if ($check_client) {
1481 if (!$allowed_clients) {
1482 $allowed_clients = array( $this->_getServerHostname() );
1484 $client_ip = $_SERVER['REMOTE_ADDR'];
1485 $client = gethostbyaddr($client_ip);
1486 phpCAS::trace("Client: ".$client."/".$client_ip);
1487 foreach ($allowed_clients as $allowed_client) {
1488 if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
1489 phpCAS::trace("Allowed client '".$allowed_client."' matches, logout request is allowed");
1493 phpCAS::trace("Allowed client '".$allowed_client."' does not match");
1497 phpCAS::trace("No access control set");
1500 // If Logout command is permitted proceed with the logout
1502 phpCAS::trace("Logout command allowed");
1503 // Rebroadcast the logout request
1504 if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) {
1505 $this->_rebroadcast(self::LOGOUT);
1507 // Extract the ticket from the SAML Request
1508 preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3);
1509 $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|', '', $tick[0][0]);
1510 $ticket2logout = preg_replace('|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex);
1511 phpCAS::trace("Ticket to logout: ".$ticket2logout);
1513 // call the post-authenticate callback if registered.
1514 if ($this->_signoutCallbackFunction) {
1515 $args = $this->_signoutCallbackArgs;
1516 array_unshift($args, $ticket2logout);
1517 call_user_func_array($this->_signoutCallbackFunction, $args);
1520 // If phpCAS is managing the session_id, destroy session thanks to session_id.
1521 if ($this->getChangeSessionID()) {
1522 $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket2logout);
1523 phpCAS::trace("Session id: ".$session_id);
1525 // destroy a possible application session created before phpcas
1526 if (session_id() !== "") {
1531 session_id($session_id);
1532 $_COOKIE[session_name()]=$session_id;
1533 $_GET[session_name()]=$session_id;
1535 // Overwrite session
1539 phpCAS::trace("Session ". $session_id . " destroyed");
1542 phpCAS::error("Unauthorized logout request from client '".$client."'");
1543 phpCAS::trace("Unauthorized logout request from client '".$client."'");
1546 phpCAS::traceExit();
1547 throw new CAS_GracefullTerminationException();
1553 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1555 // XX BASIC CLIENT FEATURES (CAS 1.0) XX
1557 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1559 // ########################################################################
1561 // ########################################################################
1563 * @addtogroup internalBasic
1568 * The Ticket provided in the URL of the request if present
1569 * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
1570 * CAS_Client::getTicket() and CAS_Client::_hasPGT().
1574 private $_ticket = '';
1577 * This method returns the Service Ticket provided in the URL of the request.
1579 * @return string service ticket.
1581 public function getTicket()
1583 return $this->_ticket;
1587 * This method stores the Service Ticket.
1589 * @param string $st The Service Ticket.
1593 public function setTicket($st)
1595 $this->_ticket = $st;
1599 * This method tells if a Service Ticket was stored.
1601 * @return bool if a Service Ticket has been stored.
1603 public function hasTicket()
1605 return !empty($this->_ticket);
1610 // ########################################################################
1612 // ########################################################################
1614 * @addtogroup internalBasic
1619 * the certificate of the CAS server CA.
1623 private $_cas_server_ca_cert = null;
1627 * validate CN of the CAS server certificate
\r
1629 * @hideinitializer
\r
1631 private $_cas_server_cn_validate = true;
1634 * Set to true not to validate the CAS server.
1638 private $_no_cas_server_validation = false;
1642 * Set the CA certificate of the CAS server.
1644 * @param string $cert the PEM certificate file name of the CA that emited
1645 * the cert of the server
1646 * @param bool $validate_cn valiate CN of the CAS server certificate
1650 public function setCasServerCACert($cert, $validate_cn)
1652 $this->_cas_server_ca_cert = $cert;
1653 $this->_cas_server_cn_validate = $validate_cn;
1657 * Set no SSL validation for the CAS server.
1661 public function setNoCasServerValidation()
1663 $this->_no_cas_server_validation = true;
1667 * This method is used to validate a CAS 1,0 ticket; halt on failure, and
1668 * sets $validate_url, $text_reponse and $tree_response on success.
1670 * @param string &$validate_url reference to the the URL of the request to
1672 * @param string &$text_response reference to the response of the CAS
1673 * server, as is (XML text).
1674 * @param string &$tree_response reference to the response of the CAS
1675 * server, as a DOM XML tree.
1677 * @return bool true when successfull and issue a CAS_AuthenticationException
1678 * and false on an error
1680 public function validateCAS10(&$validate_url,&$text_response,&$tree_response)
1682 phpCAS::traceBegin();
1684 // build the URL to validate the ticket
1685 $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getTicket();
1687 // open and read the URL
1688 if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
1689 phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1690 throw new CAS_AuthenticationException(
1691 $this, 'CAS 1.0 ticket not validated', $validate_url,
1692 true/*$no_response*/
1697 if (preg_match('/^no\n/', $text_response)) {
1698 phpCAS::trace('Ticket has not been validated');
1699 throw new CAS_AuthenticationException(
1700 $this, 'ST not validated', $validate_url, false/*$no_response*/,
1701 false/*$bad_response*/, $text_response
1704 } else if (!preg_match('/^yes\n/', $text_response)) {
1705 phpCAS::trace('ill-formed response');
1706 throw new CAS_AuthenticationException(
1707 $this, 'Ticket not validated', $validate_url,
1708 false/*$no_response*/, true/*$bad_response*/, $text_response
1712 // ticket has been validated, extract the user name
1713 $arr = preg_split('/\n/', $text_response);
1714 $this->_setUser(trim($arr[1]));
1718 $this->_renameSession($this->getTicket());
1720 // at this step, ticket has been validated and $this->_user has been set,
1721 phpCAS::traceEnd(true);
1728 // ########################################################################
1730 // ########################################################################
1732 * @addtogroup internalSAML
1737 * This method is used to validate a SAML TICKET; halt on failure, and sets
1738 * $validate_url, $text_reponse and $tree_response on success. These
1739 * parameters are used later by CAS_Client::_validatePGT() for CAS proxies.
1741 * @param string &$validate_url reference to the the URL of the request to
1743 * @param string &$text_response reference to the response of the CAS
1744 * server, as is (XML text).
1745 * @param string &$tree_response reference to the response of the CAS
1746 * server, as a DOM XML tree.
1748 * @return bool true when successfull and issue a CAS_AuthenticationException
1749 * and false on an error
1751 public function validateSA(&$validate_url,&$text_response,&$tree_response)
1753 phpCAS::traceBegin();
1755 // build the URL to validate the ticket
1756 $validate_url = $this->getServerSamlValidateURL();
1758 // open and read the URL
1759 if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
1760 phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1761 throw new CAS_AuthenticationException($this, 'SA not validated', $validate_url, true/*$no_response*/);
1764 phpCAS::trace('server version: '.$this->getServerVersion());
1766 // analyze the result depending on the version
1767 switch ($this->getServerVersion()) {
1768 case SAML_VERSION_1_1:
1769 // create new DOMDocument Object
1770 $dom = new DOMDocument();
1771 // Fix possible whitspace problems
1772 $dom->preserveWhiteSpace = false;
1773 // read the response of the CAS server into a DOM object
1774 if (!($dom->loadXML($text_response))) {
1775 phpCAS::trace('dom->loadXML() failed');
1776 throw new CAS_AuthenticationException(
1777 $this, 'SA not validated', $validate_url,
1778 false/*$no_response*/, true/*$bad_response*/,
1783 // read the root node of the XML tree
1784 if (!($tree_response = $dom->documentElement)) {
1785 phpCAS::trace('documentElement() failed');
1786 throw new CAS_AuthenticationException(
1787 $this, 'SA not validated', $validate_url,
1788 false/*$no_response*/, true/*$bad_response*/,
1792 } else if ( $tree_response->localName != 'Envelope' ) {
1793 // insure that tag name is 'Envelope'
1794 phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->localName.'\'');
1795 throw new CAS_AuthenticationException(
1796 $this, 'SA not validated', $validate_url,
1797 false/*$no_response*/, true/*$bad_response*/,
1801 } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
1802 // check for the NameIdentifier tag in the SAML response
1803 $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
1804 phpCAS::trace('NameIdentifier found');
1805 $user = trim($success_elements->item(0)->nodeValue);
1806 phpCAS::trace('user = `'.$user.'`');
1807 $this->_setUser($user);
1808 $this->_setSessionAttributes($text_response);
1811 phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
1812 throw new CAS_AuthenticationException(
1813 $this, 'SA not validated', $validate_url,
1814 false/*$no_response*/, true/*$bad_response*/,
1821 $this->_renameSession($this->getTicket());
1823 // at this step, ST has been validated and $this->_user has been set,
1824 phpCAS::traceEnd($result);
1829 * This method will parse the DOM and pull out the attributes from the SAML
1830 * payload and put them into an array, then put the array into the session.
1832 * @param string $text_response the SAML payload.
1834 * @return bool true when successfull and false if no attributes a found
1836 private function _setSessionAttributes($text_response)
1838 phpCAS::traceBegin();
1842 $attr_array = array();
1844 // create new DOMDocument Object
1845 $dom = new DOMDocument();
1846 // Fix possible whitspace problems
1847 $dom->preserveWhiteSpace = false;
1848 if (($dom->loadXML($text_response))) {
1849 $xPath = new DOMXpath($dom);
1850 $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
1851 $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
1852 $nodelist = $xPath->query("//saml:Attribute");
1855 foreach ($nodelist as $node) {
1856 $xres = $xPath->query("saml:AttributeValue", $node);
1857 $name = $node->getAttribute("AttributeName");
1858 $value_array = array();
1859 foreach ($xres as $node2) {
1860 $value_array[] = $node2->nodeValue;
1862 $attr_array[$name] = $value_array;
1864 // UGent addition...
1865 foreach ($attr_array as $attr_key => $attr_value) {
1866 if (count($attr_value) > 1) {
1867 $this->_attributes[$attr_key] = $attr_value;
1868 phpCAS::trace("* " . $attr_key . "=" . $attr_value);
1870 $this->_attributes[$attr_key] = $attr_value[0];
1871 phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
1876 phpCAS::trace("SAML Attributes are empty");
1880 phpCAS::traceEnd($result);
1886 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1888 // XX PROXY FEATURES (CAS 2.0) XX
1890 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1892 // ########################################################################
1894 // ########################################################################
1896 * @addtogroup internalProxy
1901 * A boolean telling if the client is a CAS proxy or not. Written by
1902 * CAS_Client::CAS_Client(), read by CAS_Client::isProxy().
1907 * Handler for managing service cookies.
1909 private $_serviceCookieJar;
1912 * Tells if a CAS client is a CAS proxy or not
1914 * @return true when the CAS client is a CAs proxy, false otherwise
1916 public function isProxy()
1918 return $this->_proxy;
1922 // ########################################################################
1924 // ########################################################################
1926 * @addtogroup internalProxy
1931 * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
1932 * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and
1933 * CAS_Client::_hasPGT().
1940 * This method returns the Proxy Granting Ticket given by the CAS server.
1942 * @return string the Proxy Granting Ticket.
1944 private function _getPGT()
1950 * This method stores the Proxy Granting Ticket.
1952 * @param string $pgt The Proxy Granting Ticket.
1956 private function _setPGT($pgt)
1962 * This method tells if a Proxy Granting Ticket was stored.
1964 * @return true if a Proxy Granting Ticket has been stored.
1966 private function _hasPGT()
1968 return !empty($this->_pgt);
1973 // ########################################################################
1975 // ########################################################################
1977 * @addtogroup internalCallback
1981 * each PHP script using phpCAS in proxy mode is its own callback to get the
1982 * PGT back from the CAS server. callback_mode is detected by the constructor
1983 * thanks to the GET parameters.
1987 * a boolean to know if the CAS client is running in callback mode. Written by
1988 * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode().
1992 private $_callback_mode = false;
1995 * This method sets/unsets callback mode.
1997 * @param bool $callback_mode true to set callback mode, false otherwise.
2001 private function _setCallbackMode($callback_mode)
2003 $this->_callback_mode = $callback_mode;
2007 * This method returns true when the CAs client is running i callback mode,
2010 * @return A boolean.
2012 private function _isCallbackMode()
2014 return $this->_callback_mode;
2018 * the URL that should be used for the PGT callback (in fact the URL of the
2019 * current request without any CGI parameter). Written and read by
2020 * CAS_Client::_getCallbackURL().
2024 private $_callback_url = '';
2027 * This method returns the URL that should be used for the PGT callback (in
2028 * fact the URL of the current request without any CGI parameter, except if
2029 * phpCAS::setFixedCallbackURL() was used).
2031 * @return The callback URL
2033 private function _getCallbackURL()
2035 // the URL is built when needed only
2036 if ( empty($this->_callback_url) ) {
2038 // remove the ticket if present in the URL
2039 $final_uri = 'https://';
2040 $final_uri .= $this->_getServerUrl();
2041 $request_uri = $_SERVER['REQUEST_URI'];
2042 $request_uri = preg_replace('/\?.*$/', '', $request_uri);
2043 $final_uri .= $request_uri;
2044 $this->setCallbackURL($final_uri);
2046 return $this->_callback_url;
2050 * This method sets the callback url.
2052 * @param string $url url to set callback
2056 public function setCallbackURL($url)
2058 return $this->_callback_url = $url;
2062 * This method is called by CAS_Client::CAS_Client() when running in callback
2063 * mode. It stores the PGT and its PGT Iou, prints its output and halts.
2067 private function _callback()
2069 phpCAS::traceBegin();
2070 if (preg_match('/PGTIOU-[\.\-\w]/', $_GET['pgtIou'])) {
2071 if (preg_match('/[PT]GT-[\.\-\w]/', $_GET['pgtId'])) {
2072 $this->printHTMLHeader('phpCAS callback');
2073 $pgt_iou = $_GET['pgtIou'];
2074 $pgt = $_GET['pgtId'];
2075 phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
2076 echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
2077 $this->_storePGT($pgt, $pgt_iou);
2078 $this->printHTMLFooter();
2079 phpCAS::traceExit("Successfull Callback");
2081 phpCAS::error('PGT format invalid' . $_GET['pgtId']);
2082 phpCAS::traceExit('PGT format invalid' . $_GET['pgtId']);
2085 phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
2086 phpCAS::traceExit('PGTiou format invalid' . $_GET['pgtIou']);
2089 // Flush the buffer to prevent from sending anything other then a 200
2090 // Success Status back to the CAS Server. The Exception would normally
2091 // report as a 500 error.
2093 throw new CAS_GracefullTerminationException();
2099 // ########################################################################
2101 // ########################################################################
2103 * @addtogroup internalPGTStorage
2108 * an instance of a class inheriting of PGTStorage, used to deal with PGT
2109 * storage. Created by CAS_Client::setPGTStorageFile(), used
2110 * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage().
2114 private $_pgt_storage = null;
2117 * This method is used to initialize the storage of PGT's.
2122 private function _initPGTStorage()
2124 // if no SetPGTStorageXxx() has been used, default to file
2125 if ( !is_object($this->_pgt_storage) ) {
2126 $this->setPGTStorageFile();
2129 // initializes the storage
2130 $this->_pgt_storage->init();
2134 * This method stores a PGT. Halts on error.
2136 * @param string $pgt the PGT to store
2137 * @param string $pgt_iou its corresponding Iou
2141 private function _storePGT($pgt,$pgt_iou)
2143 // ensure that storage is initialized
2144 $this->_initPGTStorage();
2146 $this->_pgt_storage->write($pgt, $pgt_iou);
2150 * This method reads a PGT from its Iou and deletes the corresponding
2153 * @param string $pgt_iou the PGT Iou
2155 * @return mul The PGT corresponding to the Iou, false when not found.
2157 private function _loadPGT($pgt_iou)
2159 // ensure that storage is initialized
2160 $this->_initPGTStorage();
2162 return $this->_pgt_storage->read($pgt_iou);
2166 * This method can be used to set a custom PGT storage object.
2168 * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that
2169 * inherits from the CAS_PGTStorage_AbstractStorage class
2173 public function setPGTStorage($storage)
2175 // check that the storage has not already been set
2176 if ( is_object($this->_pgt_storage) ) {
2177 phpCAS::error('PGT storage already defined');
2180 // check to make sure a valid storage object was specified
2181 if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) {
2182 phpCAS::error('Invalid PGT storage object');
2185 // store the PGTStorage object
2186 $this->_pgt_storage = $storage;
2190 * This method is used to tell phpCAS to store the response of the
2191 * CAS server to PGT requests in a database.
2193 * @param string $dsn_or_pdo a dsn string to use for creating a PDO
2194 * object or a PDO object
2195 * @param string $username the username to use when connecting to the
2197 * @param string $password the password to use when connecting to the
2199 * @param string $table the table to use for storing and retrieving
2201 * @param string $driver_options any driver options to use when connecting
2206 public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null)
2208 // create the storage object
2209 $this->setPGTStorage(new CAS_PGTStorage_Db($this, $dsn_or_pdo, $username, $password, $table, $driver_options));
2213 * This method is used to tell phpCAS to store the response of the
2214 * CAS server to PGT requests onto the filesystem.
2216 * @param string $path the path where the PGT's should be stored
2220 public function setPGTStorageFile($path='')
2222 // create the storage object
2223 $this->setPGTStorage(new CAS_PGTStorage_File($this, $path));
2227 // ########################################################################
2229 // ########################################################################
2231 * This method is used to validate a PGT; halt on failure.
2233 * @param string &$validate_url the URL of the request to the CAS server.
2234 * @param string $text_response the response of the CAS server, as is
2235 * (XML text); result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
2236 * @param string $tree_response the response of the CAS server, as a DOM XML
2237 * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
2239 * @return bool true when successfull and issue a CAS_AuthenticationException
2240 * and false on an error
2242 private function _validatePGT(&$validate_url,$text_response,$tree_response)
2244 phpCAS::traceBegin();
2245 if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
2246 phpCAS::trace('<proxyGrantingTicket> not found');
2247 // authentication succeded, but no PGT Iou was transmitted
2248 throw new CAS_AuthenticationException(
2249 $this, 'Ticket validated but no PGT Iou transmitted',
2250 $validate_url, false/*$no_response*/, false/*$bad_response*/,
2254 // PGT Iou transmitted, extract it
2255 $pgt_iou = trim($tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue);
2256 if (preg_match('/PGTIOU-[\.\-\w]/', $pgt_iou)) {
2257 $pgt = $this->_loadPGT($pgt_iou);
2258 if ( $pgt == false ) {
2259 phpCAS::trace('could not load PGT');
2260 throw new CAS_AuthenticationException(
2261 $this, 'PGT Iou was transmitted but PGT could not be retrieved',
2262 $validate_url, false/*$no_response*/,
2263 false/*$bad_response*/, $text_response
2266 $this->_setPGT($pgt);
2268 phpCAS::trace('PGTiou format error');
2269 throw new CAS_AuthenticationException(
2270 $this, 'PGT Iou was transmitted but has wrong format',
2271 $validate_url, false/*$no_response*/, false/*$bad_response*/,
2276 phpCAS::traceEnd(true);
2280 // ########################################################################
2282 // ########################################################################
2285 * This method is used to retrieve PT's from the CAS server thanks to a PGT.
2287 * @param string $target_service the service to ask for with the PT.
2288 * @param string &$err_code an error code (PHPCAS_SERVICE_OK on success).
2289 * @param string &$err_msg an error message (empty on success).
2291 * @return a Proxy Ticket, or false on error.
2293 public function retrievePT($target_service,&$err_code,&$err_msg)
2295 phpCAS::traceBegin();
2297 // by default, $err_msg is set empty and $pt to true. On error, $pt is
2298 // set to false and $err_msg to an error message. At the end, if $pt is false
2299 // and $error_msg is still empty, it is set to 'invalid response' (the most
2300 // commonly encountered error).
2303 // build the URL to retrieve the PT
2304 $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->_getPGT();
2306 // open and read the URL
2307 if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) {
2308 phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
2309 $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2310 $err_msg = 'could not retrieve PT (no response from the CAS server)';
2311 phpCAS::traceEnd(false);
2315 $bad_response = false;
2317 if ( !$bad_response ) {
2318 // create new DOMDocument object
2319 $dom = new DOMDocument();
2320 // Fix possible whitspace problems
2321 $dom->preserveWhiteSpace = false;
2322 // read the response of the CAS server into a DOM object
2323 if ( !($dom->loadXML($cas_response))) {
2324 phpCAS::trace('dom->loadXML() failed');
2326 $bad_response = true;
2330 if ( !$bad_response ) {
2331 // read the root node of the XML tree
2332 if ( !($root = $dom->documentElement) ) {
2333 phpCAS::trace('documentElement failed');
2335 $bad_response = true;
2339 if ( !$bad_response ) {
2340 // insure that tag name is 'serviceResponse'
2341 if ( $root->localName != 'serviceResponse' ) {
2342 phpCAS::trace('localName failed');
2344 $bad_response = true;
2348 if ( !$bad_response ) {
2349 // look for a proxySuccess tag
2350 if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
2351 $proxy_success_list = $root->getElementsByTagName("proxySuccess");
2353 // authentication succeded, look for a proxyTicket tag
2354 if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
2355 $err_code = PHPCAS_SERVICE_OK;
2357 $pt = trim($proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue);
2358 phpCAS::trace('original PT: '.trim($pt));
2359 phpCAS::traceEnd($pt);
2362 phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
2364 } else if ($root->getElementsByTagName("proxyFailure")->length != 0) {
2365 // look for a proxyFailure tag
2366 $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
2368 // authentication failed, extract the error
2369 $err_code = PHPCAS_SERVICE_PT_FAILURE;
2370 $err_msg = 'PT retrieving failed (code=`'
2371 .$proxy_failure_list->item(0)->getAttribute('code')
2373 .trim($proxy_failure_list->item(0)->nodeValue)
2375 phpCAS::traceEnd(false);
2378 phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
2382 // at this step, we are sure that the response of the CAS server was
2384 $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
2385 $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
2387 phpCAS::traceEnd(false);
2393 // ########################################################################
2394 // READ CAS SERVER ANSWERS
2395 // ########################################################################
2398 * @addtogroup internalMisc
2403 * This method is used to acces a remote URL.
2405 * @param string $url the URL to access.
2406 * @param string &$headers an array containing the HTTP header lines of the
2407 * response (an empty array on failure).
2408 * @param string &$body the body of the response, as a string (empty on
2410 * @param string &$err_msg an error message, filled on failure.
2412 * @return true on success, false otherwise (in this later case, $err_msg
2413 * contains an error message).
2415 private function _readURL($url, &$headers, &$body, &$err_msg)
2417 phpCAS::traceBegin();
2418 $className = $this->_requestImplementation;
2419 $request = new $className();
2421 if (count($this->_curl_options)) {
2422 $request->setCurlOptions($this->_curl_options);
2425 $request->setUrl($url);
2427 if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
2428 phpCAS::error('one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
2430 if ($this->_cas_server_ca_cert != '') {
2431 $request->setSslCaCert($this->_cas_server_ca_cert, $this->_cas_server_cn_validate);
2434 // add extra stuff if SAML
2435 if ($this->getServerVersion() == SAML_VERSION_1_1) {
2436 $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
2437 $request->addHeader("cache-control: no-cache");
2438 $request->addHeader("pragma: no-cache");
2439 $request->addHeader("accept: text/xml");
2440 $request->addHeader("connection: keep-alive");
2441 $request->addHeader("content-type: text/xml");
2442 $request->makePost();
2443 $request->setPostBody($this->_buildSAMLPayload());
2446 if ($request->send()) {
2447 $headers = $request->getResponseHeaders();
2448 $body = $request->getResponseBody();
2450 phpCAS::traceEnd(true);
2455 $err_msg = $request->getErrorMessage();
2456 phpCAS::traceEnd(false);
2462 * This method is used to build the SAML POST body sent to /samlValidate URL.
2464 * @return the SOAP-encased SAMLP artifact (the ticket).
2466 private function _buildSAMLPayload()
2468 phpCAS::traceBegin();
2471 $sa = $this->getTicket();
2473 $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;
2475 phpCAS::traceEnd($body);
2481 // ########################################################################
2482 // ACCESS TO EXTERNAL SERVICES
2483 // ########################################################################
2486 * @addtogroup internalProxyServices
2492 * Answer a proxy-authenticated service handler.
2494 * @param string $type The service type. One of:
2495 * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST,
2496 * PHPCAS_PROXIED_SERVICE_IMAP
2498 * @return CAS_ProxiedService
2499 * @throws InvalidArgumentException If the service type is unknown.
2501 public function getProxiedService ($type)
2504 case PHPCAS_PROXIED_SERVICE_HTTP_GET:
2505 case PHPCAS_PROXIED_SERVICE_HTTP_POST:
2506 $requestClass = $this->_requestImplementation;
2507 $request = new $requestClass();
2508 if (count($this->_curl_options)) {
2509 $request->setCurlOptions($this->_curl_options);
2511 $proxiedService = new $type($request, $this->_serviceCookieJar);
2512 if ($proxiedService instanceof CAS_ProxiedService_Testable) {
2513 $proxiedService->setCasClient($this);
2515 return $proxiedService;
2516 case PHPCAS_PROXIED_SERVICE_IMAP;
2517 $proxiedService = new CAS_ProxiedService_Imap($this->getUser());
2518 if ($proxiedService instanceof CAS_ProxiedService_Testable) {
2519 $proxiedService->setCasClient($this);
2521 return $proxiedService;
2523 throw new CAS_InvalidArgumentException("Unknown proxied-service type, $type.");
2528 * Initialize a proxied-service handler with the proxy-ticket it should use.
2530 * @param CAS_ProxiedService $proxiedService service handler
2534 * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
2535 * The code of the Exception will be one of:
2536 * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
2537 * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
2538 * PHPCAS_SERVICE_PT_FAILURE
2539 * @throws CAS_ProxiedService_Exception If there is a failure getting the
2540 * url from the proxied service.
2542 public function initializeProxiedService (CAS_ProxiedService $proxiedService)
2544 $url = $proxiedService->getServiceUrl();
2545 if (!is_string($url)) {
2546 throw new CAS_ProxiedService_Exception("Proxied Service ".get_class($proxiedService)."->getServiceUrl() should have returned a string, returned a ".gettype($url)." instead.");
2548 $pt = $this->retrievePT($url, $err_code, $err_msg);
2550 throw new CAS_ProxyTicketException($err_msg, $err_code);
2552 $proxiedService->setProxyTicket($pt);
2556 * This method is used to access an HTTP[S] service.
2558 * @param string $url the service to access.
2559 * @param int &$err_code an error code Possible values are
2560 * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
2561 * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
2562 * PHPCAS_SERVICE_NOT_AVAILABLE.
2563 * @param string &$output the output of the service (also used to give an error
2564 * message on failure).
2566 * @return true on success, false otherwise (in this later case, $err_code
2567 * gives the reason why it failed and $output contains an error message).
2569 public function serviceWeb($url,&$err_code,&$output)
2572 $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
2573 $service->setUrl($url);
2575 $output = $service->getResponseBody();
2576 $err_code = PHPCAS_SERVICE_OK;
2578 } catch (CAS_ProxyTicketException $e) {
2579 $err_code = $e->getCode();
2580 $output = $e->getMessage();
2582 } catch (CAS_ProxiedService_Exception $e) {
2583 $lang = $this->getLangObj();
2584 $output = sprintf($lang->getServiceUnavailable(), $url, $e->getMessage());
2585 $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2591 * This method is used to access an IMAP/POP3/NNTP service.
2593 * @param string $url a string giving the URL of the service, including
2594 * the mailing box for IMAP URLs, as accepted by imap_open().
2595 * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket
2596 * @param string $flags options given to imap_open().
2597 * @param int &$err_code an error code Possible values are
2598 * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
2599 * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
2600 * PHPCAS_SERVICE_NOT_AVAILABLE.
2601 * @param string &$err_msg an error message on failure
2602 * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS
2603 * server to access the URL on success, false on error).
2605 * @return object an IMAP stream on success, false otherwise (in this later
2606 * case, $err_code gives the reason why it failed and $err_msg contains an
2609 public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
2612 $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
2613 $service->setServiceUrl($serviceUrl);
2614 $service->setMailbox($url);
2615 $service->setOptions($flags);
2617 $stream = $service->open();
2618 $err_code = PHPCAS_SERVICE_OK;
2619 $pt = $service->getImapProxyTicket();
2621 } catch (CAS_ProxyTicketException $e) {
2622 $err_msg = $e->getMessage();
2623 $err_code = $e->getCode();
2626 } catch (CAS_ProxiedService_Exception $e) {
2627 $lang = $this->getLangObj();
2629 $lang->getServiceUnavailable(),
2633 $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2641 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2643 // XX PROXIED CLIENT FEATURES (CAS 2.0) XX
2645 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2647 // ########################################################################
2649 // ########################################################################
2651 * @addtogroup internalService
2656 * This array will store a list of proxies in front of this application. This
2657 * property will only be populated if this script is being proxied rather than
2658 * accessed directly.
2660 * It is set in CAS_Client::validateCAS20() and can be read by
2661 * CAS_Client::getProxies()
2665 private $_proxies = array();
2668 * Answer an array of proxies that are sitting in front of this application.
2670 * This method will only return a non-empty array if we have received and
2671 * validated a Proxy Ticket.
2676 public function getProxies()
2678 return $this->_proxies;
2682 * Set the Proxy array, probably from persistant storage.
2684 * @param array $proxies An array of proxies
2689 private function _setProxies($proxies)
2691 $this->_proxies = $proxies;
2692 if (!empty($proxies)) {
2693 // For proxy-authenticated requests people are not viewing the URL
2694 // directly since the client is another application making a
2695 // web-service call.
2696 // Because of this, stripping the ticket from the URL is unnecessary
2697 // and causes another web-service request to be performed. Additionally,
2698 // if session handling on either the client or the server malfunctions
2699 // then the subsequent request will not complete successfully.
2700 $this->setNoClearTicketsFromUrl();
2705 * A container of patterns to be allowed as proxies in front of the cas client.
2707 * @var CAS_ProxyChain_AllowedList
2709 private $_allowed_proxy_chains;
2712 * Answer the CAS_ProxyChain_AllowedList object for this client.
2714 * @return CAS_ProxyChain_AllowedList
2716 public function getAllowedProxyChains ()
2718 if (empty($this->_allowed_proxy_chains)) {
2719 $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList();
2721 return $this->_allowed_proxy_chains;
2725 // ########################################################################
2727 // ########################################################################
2729 * @addtogroup internalProxied
2734 * This method is used to validate a cas 2.0 ST or PT; halt on failure
2735 * Used for all CAS 2.0 validations
2737 * @param string &$validate_url the url of the reponse
2738 * @param string &$text_response the text of the repsones
2739 * @param string &$tree_response the domxml tree of the respones
2741 * @return bool true when successfull and issue a CAS_AuthenticationException
2742 * and false on an error
2744 public function validateCAS20(&$validate_url,&$text_response,&$tree_response)
2746 phpCAS::traceBegin();
2747 phpCAS::trace($text_response);
2749 // build the URL to validate the ticket
2750 if ($this->getAllowedProxyChains()->isProxyingAllowed()) {
2751 $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getTicket();
2753 $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getTicket();
2756 if ( $this->isProxy() ) {
2757 // pass the callback url for CAS proxies
2758 $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL());
2761 // open and read the URL
2762 if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
2763 phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
2764 throw new CAS_AuthenticationException(
2765 $this, 'Ticket not validated', $validate_url,
2766 true/*$no_response*/
2771 // create new DOMDocument object
2772 $dom = new DOMDocument();
2773 // Fix possible whitspace problems
2774 $dom->preserveWhiteSpace = false;
2775 // CAS servers should only return data in utf-8
2776 $dom->encoding = "utf-8";
2777 // read the response of the CAS server into a DOMDocument object
2778 if ( !($dom->loadXML($text_response))) {
2780 throw new CAS_AuthenticationException(
2781 $this, 'Ticket not validated', $validate_url,
2782 false/*$no_response*/, true/*$bad_response*/, $text_response
2785 } else if ( !($tree_response = $dom->documentElement) ) {
2786 // read the root node of the XML tree
2788 throw new CAS_AuthenticationException(
2789 $this, 'Ticket not validated', $validate_url,
2790 false/*$no_response*/, true/*$bad_response*/, $text_response
2793 } else if ($tree_response->localName != 'serviceResponse') {
2794 // insure that tag name is 'serviceResponse'
2796 throw new CAS_AuthenticationException(
2797 $this, 'Ticket not validated', $validate_url,
2798 false/*$no_response*/, true/*$bad_response*/, $text_response
2801 } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
2802 // authentication succeded, extract the user name
2803 $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
2804 if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
2805 // no user specified => error
2806 throw new CAS_AuthenticationException(
2807 $this, 'Ticket not validated', $validate_url,
2808 false/*$no_response*/, true/*$bad_response*/, $text_response
2812 $this->_setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue));
2813 $this->_readExtraAttributesCas20($success_elements);
2814 // Store the proxies we are sitting behind for authorization checking
2815 $proxyList = array();
2816 if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
2817 foreach ($arr as $proxyElem) {
2818 phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue);
2819 $proxyList[] = trim($proxyElem->nodeValue);
2821 $this->_setProxies($proxyList);
2822 phpCAS::trace("Storing Proxy List");
2824 // Check if the proxies in front of us are allowed
2825 if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) {
2826 throw new CAS_AuthenticationException(
2827 $this, 'Proxy not allowed', $validate_url,
2828 false/*$no_response*/, true/*$bad_response*/,
2836 } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
2837 // authentication succeded, extract the error code and message
2838 $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
2839 throw new CAS_AuthenticationException(
2840 $this, 'Ticket not validated', $validate_url,
2841 false/*$no_response*/, false/*$bad_response*/,
2843 $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
2844 trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
2848 throw new CAS_AuthenticationException(
2849 $this, 'Ticket not validated', $validate_url,
2850 false/*$no_response*/, true/*$bad_response*/,
2856 $this->_renameSession($this->getTicket());
2858 // at this step, Ticket has been validated and $this->_user has been set,
2860 phpCAS::traceEnd($result);
2866 * This method will parse the DOM and pull out the attributes from the XML
2867 * payload and put them into an array, then put the array into the session.
2869 * @param string $success_elements payload of the response
2871 * @return bool true when successfull, halt otherwise by calling
2872 * CAS_Client::_authError().
2874 private function _readExtraAttributesCas20($success_elements)
2876 phpCAS::traceBegin();
2878 $extra_attributes = array();
2880 // "Jasig Style" Attributes:
2882 // <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2883 // <cas:authenticationSuccess>
2884 // <cas:user>jsmith</cas:user>
2886 // <cas:attraStyle>RubyCAS</cas:attraStyle>
2887 // <cas:surname>Smith</cas:surname>
2888 // <cas:givenName>John</cas:givenName>
2889 // <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
2890 // <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
2891 // </cas:attributes>
2892 // <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
2893 // </cas:authenticationSuccess>
2894 // </cas:serviceResponse>
2896 if ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
2897 $attr_nodes = $success_elements->item(0)->getElementsByTagName("attributes");
2898 phpCas :: trace("Found nested jasig style attributes");
2899 if ($attr_nodes->item(0)->hasChildNodes()) {
2900 // Nested Attributes
2901 foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
2902 phpCas :: trace("Attribute [".$attr_child->localName."] = ".$attr_child->nodeValue);
2903 $this->_addAttributeToArray($extra_attributes, $attr_child->localName, $attr_child->nodeValue);
2907 // "RubyCAS Style" attributes
2909 // <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2910 // <cas:authenticationSuccess>
2911 // <cas:user>jsmith</cas:user>
2913 // <cas:attraStyle>RubyCAS</cas:attraStyle>
2914 // <cas:surname>Smith</cas:surname>
2915 // <cas:givenName>John</cas:givenName>
2916 // <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
2917 // <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
2919 // <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
2920 // </cas:authenticationSuccess>
2921 // </cas:serviceResponse>
2923 phpCas :: trace("Testing for rubycas style attributes");
2924 $childnodes = $success_elements->item(0)->childNodes;
2925 foreach ($childnodes as $attr_node) {
2926 switch ($attr_node->localName) {
2929 case 'proxyGrantingTicket':
2932 if (strlen(trim($attr_node->nodeValue))) {
2933 phpCas :: trace("Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue);
2934 $this->_addAttributeToArray($extra_attributes, $attr_node->localName, $attr_node->nodeValue);
2940 // "Name-Value" attributes.
2942 // Attribute format from these mailing list thread:
2943 // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
2944 // Note: This is a less widely used format, but in use by at least two institutions.
2946 // <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2947 // <cas:authenticationSuccess>
2948 // <cas:user>jsmith</cas:user>
2950 // <cas:attribute name='attraStyle' value='Name-Value' />
2951 // <cas:attribute name='surname' value='Smith' />
2952 // <cas:attribute name='givenName' value='John' />
2953 // <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
2954 // <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
2956 // <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
2957 // </cas:authenticationSuccess>
2958 // </cas:serviceResponse>
2960 if (!count($extra_attributes) && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0) {
2961 $attr_nodes = $success_elements->item(0)->getElementsByTagName("attribute");
2962 $firstAttr = $attr_nodes->item(0);
2963 if (!$firstAttr->hasChildNodes() && $firstAttr->hasAttribute('name') && $firstAttr->hasAttribute('value')) {
2964 phpCas :: trace("Found Name-Value style attributes");
2965 // Nested Attributes
2966 foreach ($attr_nodes as $attr_node) {
2967 if ($attr_node->hasAttribute('name') && $attr_node->hasAttribute('value')) {
2968 phpCas :: trace("Attribute [".$attr_node->getAttribute('name')."] = ".$attr_node->getAttribute('value'));
2969 $this->_addAttributeToArray($extra_attributes, $attr_node->getAttribute('name'), $attr_node->getAttribute('value'));
2975 $this->setAttributes($extra_attributes);
2981 * Add an attribute value to an array of attributes.
2983 * @param array &$attributeArray reference to array
2984 * @param string $name name of attribute
2985 * @param string $value value of attribute
2989 private function _addAttributeToArray(array &$attributeArray, $name, $value)
2991 // If multiple attributes exist, add as an array value
2992 if (isset($attributeArray[$name])) {
2993 // Initialize the array with the existing value
2994 if (!is_array($attributeArray[$name])) {
2995 $existingValue = $attributeArray[$name];
2996 $attributeArray[$name] = array($existingValue);
2999 $attributeArray[$name][] = trim($value);
3001 $attributeArray[$name] = trim($value);
3007 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3011 // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3014 * @addtogroup internalMisc
3018 // ########################################################################
3020 // ########################################################################
3022 * the URL of the current request (without any ticket CGI parameter). Written
3023 * and read by CAS_Client::getURL().
3031 * This method sets the URL of the current request
3033 * @param string $url url to set for service
3037 public function setURL($url)
3043 * This method returns the URL of the current request (without any ticket
3048 public function getURL()
3050 phpCAS::traceBegin();
3051 // the URL is built when needed only
3052 if ( empty($this->_url) ) {
3054 // remove the ticket if present in the URL
3055 $final_uri = ($this->_isHttps()) ? 'https' : 'http';
3056 $final_uri .= '://';
3058 $final_uri .= $this->_getServerUrl();
3059 $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
3060 $final_uri .= $request_uri[0];
3062 if (isset($request_uri[1]) && $request_uri[1]) {
3063 $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]);
3065 // If the query string still has anything left, append it to the final URI
3066 if ($query_string !== '') {
3067 $final_uri .= "?$query_string";
3071 phpCAS::trace("Final URI: $final_uri");
3072 $this->setURL($final_uri);
3074 phpCAS::traceEnd($this->_url);
3080 * Try to figure out the server URL with possible Proxys / Ports etc.
3082 * @return string Server URL with domain:port
3084 private function _getServerUrl()
3087 if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
3088 // explode the host list separated by comma and use the first host
3089 $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
3090 $server_url = $hosts[0];
3091 } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
3092 $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
3094 if (empty($_SERVER['SERVER_NAME'])) {
3095 $server_url = $_SERVER['HTTP_HOST'];
3097 $server_url = $_SERVER['SERVER_NAME'];
3100 if (!strpos($server_url, ':')) {
3101 if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
3102 $server_port = $_SERVER['SERVER_PORT'];
3104 $server_port = $_SERVER['HTTP_X_FORWARDED_PORT'];
3107 if ( ($this->_isHttps() && $server_port!=443)
3108 || (!$this->_isHttps() && $server_port!=80)
3111 $server_url .= $server_port;
3118 * This method checks to see if the request is secured via HTTPS
3120 * @return bool true if https, false otherwise
3122 private function _isHttps()
3124 if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
3132 * Removes a parameter from a query string
3134 * @param string $parameterName name of parameter
3135 * @param string $queryString query string
3137 * @return string new query string
3139 * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
3141 private function _removeParameterFromQueryString($parameterName, $queryString)
3143 $parameterName = preg_quote($parameterName);
3144 return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
3148 * This method is used to append query parameters to an url. Since the url
3149 * might already contain parameter it has to be detected and to build a proper
3152 * @param string $url base url to add the query params to
3153 * @param string $query params in query form with & separated
3155 * @return url with query params
3157 private function _buildQueryUrl($url, $query)
3159 $url .= (strstr($url, '?') === false) ? '?' : '&';
3165 * Renaming the session
3167 * @param string $ticket name of the ticket
3171 private function _renameSession($ticket)
3173 phpCAS::traceBegin();
3174 if ($this->getChangeSessionID()) {
3175 if (!empty($this->_user)) {
3176 $old_session = $_SESSION;
3178 // set up a new session, of name based on the ticket
3179 $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket);
3180 phpCAS :: trace("Session ID: ".$session_id);
3181 session_id($session_id);
3183 phpCAS :: trace("Restoring old session vars");
3184 $_SESSION = $old_session;
3186 phpCAS :: error('Session should only be renamed after successfull authentication');
3189 phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
3195 // ########################################################################
3196 // AUTHENTICATION ERROR HANDLING
3197 // ########################################################################
3199 * This method is used to print the HTML output when the user was not
3202 * @param string $failure the failure that occured
3203 * @param string $cas_url the URL the CAS server was asked for
3204 * @param bool $no_response the response from the CAS server (other
3205 * parameters are ignored if true)
3206 * @param bool $bad_response bad response from the CAS server ($err_code
3207 * and $err_msg ignored if true)
3208 * @param string $cas_response the response of the CAS server
3209 * @param int $err_code the error code given by the CAS server
3210 * @param string $err_msg the error message given by the CAS server
3214 private function _authError(
3223 phpCAS::traceBegin();
3224 $lang = $this->getLangObj();
3225 $this->printHTMLHeader($lang->getAuthenticationFailed());
3226 printf($lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), $_SERVER['SERVER_ADMIN']);
3227 phpCAS::trace('CAS URL: '.$cas_url);
3228 phpCAS::trace('Authentication failure: '.$failure);
3229 if ( $no_response ) {
3230 phpCAS::trace('Reason: no response from the CAS server');
3232 if ( $bad_response ) {
3233 phpCAS::trace('Reason: bad response from the CAS server');
3235 switch ($this->getServerVersion()) {
3236 case CAS_VERSION_1_0:
3237 phpCAS::trace('Reason: CAS error');
3239 case CAS_VERSION_2_0:
3240 if ( empty($err_code) ) {
3241 phpCAS::trace('Reason: no CAS error');
3243 phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
3248 phpCAS::trace('CAS response: '.$cas_response);
3250 $this->printHTMLFooter();
3251 phpCAS::traceExit();
3252 throw new CAS_GracefullTerminationException();
3255 // ########################################################################
3256 // PGTIOU/PGTID and logoutRequest rebroadcasting
3257 // ########################################################################
3260 * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and
3261 * array of the nodes.
3263 private $_rebroadcast = false;
3264 private $_rebroadcast_nodes = array();
3267 * Constants used for determining rebroadcast node type.
3273 * Determine the node type from the URL.
3275 * @param String $nodeURL The node URL.
3277 * @return string hostname
3280 private function _getNodeType($nodeURL)
3282 phpCAS::traceBegin();
3283 if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) {
3284 phpCAS::traceEnd(self::IP);
3287 phpCAS::traceEnd(self::HOSTNAME);
3288 return self::HOSTNAME;
3293 * Store the rebroadcast node for pgtIou/pgtId and logout requests.
3295 * @param string $rebroadcastNodeUrl The rebroadcast node URL.
3299 public function addRebroadcastNode($rebroadcastNodeUrl)
3301 // Store the rebroadcast node and set flag
3302 $this->_rebroadcast = true;
3303 $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl;
3307 * An array to store extra rebroadcast curl options.
3309 private $_rebroadcast_headers = array();
3312 * This method is used to add header parameters when rebroadcasting
3313 * pgtIou/pgtId or logoutRequest.
3315 * @param string $header Header to send when rebroadcasting.
3319 public function addRebroadcastHeader($header)
3321 $this->_rebroadcast_headers[] = $header;
3325 * Constants used for determining rebroadcast type (logout or pgtIou/pgtId).
3331 * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU
3333 * @param int $type type of rebroadcasting.
3337 private function _rebroadcast($type)
3339 phpCAS::traceBegin();
3341 $rebroadcast_curl_options = array(
3342 CURLOPT_FAILONERROR => 1,
3343 CURLOPT_FOLLOWLOCATION => 1,
3344 CURLOPT_RETURNTRANSFER => 1,
3345 CURLOPT_CONNECTTIMEOUT => 1,
3346 CURLOPT_TIMEOUT => 4);
3348 // Try to determine the IP address of the server
3349 if (!empty($_SERVER['SERVER_ADDR'])) {
3350 $ip = $_SERVER['SERVER_ADDR'];
3351 } else if (!empty($_SERVER['LOCAL_ADDR'])) {
3353 $ip = $_SERVER['LOCAL_ADDR'];
3355 // Try to determine the DNS name of the server
3357 $dns = gethostbyaddr($ip);
3359 $multiClassName = 'CAS_Request_CurlMultiRequest';
3360 $multiRequest = new $multiClassName();
3362 for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) {
3363 if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))) {
3364 phpCAS::trace('Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI']);
3365 $className = $this->_requestImplementation;
3366 $request = new $className();
3368 $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI'];
3369 $request->setUrl($url);
3371 if (count($this->_rebroadcast_headers)) {
3372 $request->addHeaders($this->_rebroadcast_headers);
3375 $request->makePost();
3376 if ($type == self::LOGOUT) {
3378 $request->setPostBody('rebroadcast=false&logoutRequest='.$_POST['logoutRequest']);
3379 } else if ($type == self::PGTIOU) {
3380 // pgtIou/pgtId rebroadcast
3381 $request->setPostBody('rebroadcast=false');
3384 $request->setCurlOptions($rebroadcast_curl_options);
3386 $multiRequest->addRequest($request);
3388 phpCAS::trace('Rebroadcast not sent to self: '.$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'').'/'.(!empty($dns)?$dns:''));
3391 // We need at least 1 request
3392 if ($multiRequest->getNumRequests() > 0) {
3393 $multiRequest->send();