PageRenderTime 58ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/auth/cas/CAS/CAS/client.php

https://bitbucket.org/ceu/moodle_demo
PHP | 2776 lines | 1684 code | 196 blank | 896 comment | 207 complexity | 570a4a876336b914765dec972b205abe MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1
  1. <?php
  2. /*
  3. * Copyright Š 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. *
  9. * * Redistributions of source code must retain the above copyright notice,
  10. * this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. * * Neither the name of the ESUP-Portail consortium & the JA-SIG
  15. * Collaborative nor the names of its contributors may be used to endorse or
  16. * promote products derived from this software without specific prior
  17. * written permission.
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  22. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  25. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. */
  29. /**
  30. * @file CAS/client.php
  31. * Main class of the phpCAS library
  32. */
  33. // include internationalization stuff
  34. include_once(dirname(__FILE__).'/languages/languages.php');
  35. // include PGT storage classes
  36. include_once(dirname(__FILE__).'/PGTStorage/pgt-main.php');
  37. /**
  38. * @class CASClient
  39. * The CASClient class is a client interface that provides CAS authentication
  40. * to PHP applications.
  41. *
  42. * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
  43. */
  44. class CASClient
  45. {
  46. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  47. // XX XX
  48. // XX CONFIGURATION XX
  49. // XX XX
  50. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  51. // ########################################################################
  52. // HTML OUTPUT
  53. // ########################################################################
  54. /**
  55. * @addtogroup internalOutput
  56. * @{
  57. */
  58. /**
  59. * This method filters a string by replacing special tokens by appropriate values
  60. * and prints it. The corresponding tokens are taken into account:
  61. * - __CAS_VERSION__
  62. * - __PHPCAS_VERSION__
  63. * - __SERVER_BASE_URL__
  64. *
  65. * Used by CASClient::PrintHTMLHeader() and CASClient::printHTMLFooter().
  66. *
  67. * @param $str the string to filter and output
  68. *
  69. * @private
  70. */
  71. function HTMLFilterOutput($str)
  72. {
  73. $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
  74. $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
  75. $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
  76. echo $str;
  77. }
  78. /**
  79. * A string used to print the header of HTML pages. Written by CASClient::setHTMLHeader(),
  80. * read by CASClient::printHTMLHeader().
  81. *
  82. * @hideinitializer
  83. * @private
  84. * @see CASClient::setHTMLHeader, CASClient::printHTMLHeader()
  85. */
  86. var $_output_header = '';
  87. /**
  88. * This method prints the header of the HTML output (after filtering). If
  89. * CASClient::setHTMLHeader() was not used, a default header is output.
  90. *
  91. * @param $title the title of the page
  92. *
  93. * @see HTMLFilterOutput()
  94. * @private
  95. */
  96. function printHTMLHeader($title)
  97. {
  98. $this->HTMLFilterOutput(str_replace('__TITLE__',
  99. $title,
  100. (empty($this->_output_header)
  101. ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
  102. : $this->_output_header)
  103. )
  104. );
  105. }
  106. /**
  107. * A string used to print the footer of HTML pages. Written by CASClient::setHTMLFooter(),
  108. * read by printHTMLFooter().
  109. *
  110. * @hideinitializer
  111. * @private
  112. * @see CASClient::setHTMLFooter, CASClient::printHTMLFooter()
  113. */
  114. var $_output_footer = '';
  115. /**
  116. * This method prints the footer of the HTML output (after filtering). If
  117. * CASClient::setHTMLFooter() was not used, a default footer is output.
  118. *
  119. * @see HTMLFilterOutput()
  120. * @private
  121. */
  122. function printHTMLFooter()
  123. {
  124. $this->HTMLFilterOutput(empty($this->_output_footer)
  125. ?('<hr><address>phpCAS __PHPCAS_VERSION__ '.$this->getString(CAS_STR_USING_SERVER).' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>')
  126. :$this->_output_footer);
  127. }
  128. /**
  129. * This method set the HTML header used for all outputs.
  130. *
  131. * @param $header the HTML header.
  132. *
  133. * @public
  134. */
  135. function setHTMLHeader($header)
  136. {
  137. $this->_output_header = $header;
  138. }
  139. /**
  140. * This method set the HTML footer used for all outputs.
  141. *
  142. * @param $footer the HTML footer.
  143. *
  144. * @public
  145. */
  146. function setHTMLFooter($footer)
  147. {
  148. $this->_output_footer = $footer;
  149. }
  150. /** @} */
  151. // ########################################################################
  152. // INTERNATIONALIZATION
  153. // ########################################################################
  154. /**
  155. * @addtogroup internalLang
  156. * @{
  157. */
  158. /**
  159. * A string corresponding to the language used by phpCAS. Written by
  160. * CASClient::setLang(), read by CASClient::getLang().
  161. * @note debugging information is always in english (debug purposes only).
  162. *
  163. * @hideinitializer
  164. * @private
  165. * @sa CASClient::_strings, CASClient::getString()
  166. */
  167. var $_lang = '';
  168. /**
  169. * This method returns the language used by phpCAS.
  170. *
  171. * @return a string representing the language
  172. *
  173. * @private
  174. */
  175. function getLang()
  176. {
  177. if ( empty($this->_lang) )
  178. $this->setLang(PHPCAS_LANG_DEFAULT);
  179. return $this->_lang;
  180. }
  181. /**
  182. * array containing the strings used by phpCAS. Written by CASClient::setLang(), read by
  183. * CASClient::getString() and used by CASClient::setLang().
  184. *
  185. * @note This array is filled by instructions in CAS/languages/<$this->_lang>.php
  186. *
  187. * @private
  188. * @see CASClient::_lang, CASClient::getString(), CASClient::setLang(), CASClient::getLang()
  189. */
  190. var $_strings;
  191. /**
  192. * This method returns a string depending on the language.
  193. *
  194. * @param $str the index of the string in $_string.
  195. *
  196. * @return the string corresponding to $index in $string.
  197. *
  198. * @private
  199. */
  200. function getString($str)
  201. {
  202. // call CASclient::getLang() to be sure the language is initialized
  203. $this->getLang();
  204. if ( !isset($this->_strings[$str]) ) {
  205. trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
  206. }
  207. return $this->_strings[$str];
  208. }
  209. /**
  210. * This method is used to set the language used by phpCAS.
  211. * @note Can be called only once.
  212. *
  213. * @param $lang a string representing the language.
  214. *
  215. * @public
  216. * @sa CAS_LANG_FRENCH, CAS_LANG_ENGLISH
  217. */
  218. function setLang($lang)
  219. {
  220. // include the corresponding language file
  221. include_once(dirname(__FILE__).'/languages/'.$lang.'.php');
  222. if ( !is_array($this->_strings) ) {
  223. trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
  224. }
  225. $this->_lang = $lang;
  226. }
  227. /** @} */
  228. // ########################################################################
  229. // CAS SERVER CONFIG
  230. // ########################################################################
  231. /**
  232. * @addtogroup internalConfig
  233. * @{
  234. */
  235. /**
  236. * a record to store information about the CAS server.
  237. * - $_server["version"]: the version of the CAS server
  238. * - $_server["hostname"]: the hostname of the CAS server
  239. * - $_server["port"]: the port the CAS server is running on
  240. * - $_server["uri"]: the base URI the CAS server is responding on
  241. * - $_server["base_url"]: the base URL of the CAS server
  242. * - $_server["login_url"]: the login URL of the CAS server
  243. * - $_server["service_validate_url"]: the service validating URL of the CAS server
  244. * - $_server["proxy_url"]: the proxy URL of the CAS server
  245. * - $_server["proxy_validate_url"]: the proxy validating URL of the CAS server
  246. * - $_server["logout_url"]: the logout URL of the CAS server
  247. *
  248. * $_server["version"], $_server["hostname"], $_server["port"] and $_server["uri"]
  249. * are written by CASClient::CASClient(), read by CASClient::getServerVersion(),
  250. * CASClient::getServerHostname(), CASClient::getServerPort() and CASClient::getServerURI().
  251. *
  252. * The other fields are written and read by CASClient::getServerBaseURL(),
  253. * CASClient::getServerLoginURL(), CASClient::getServerServiceValidateURL(),
  254. * CASClient::getServerProxyValidateURL() and CASClient::getServerLogoutURL().
  255. *
  256. * @hideinitializer
  257. * @private
  258. */
  259. var $_server = array(
  260. 'version' => -1,
  261. 'hostname' => 'none',
  262. 'port' => -1,
  263. 'uri' => 'none'
  264. );
  265. /**
  266. * This method is used to retrieve the version of the CAS server.
  267. * @return the version of the CAS server.
  268. * @private
  269. */
  270. function getServerVersion()
  271. {
  272. return $this->_server['version'];
  273. }
  274. /**
  275. * This method is used to retrieve the hostname of the CAS server.
  276. * @return the hostname of the CAS server.
  277. * @private
  278. */
  279. function getServerHostname()
  280. { return $this->_server['hostname']; }
  281. /**
  282. * This method is used to retrieve the port of the CAS server.
  283. * @return the port of the CAS server.
  284. * @private
  285. */
  286. function getServerPort()
  287. { return $this->_server['port']; }
  288. /**
  289. * This method is used to retrieve the URI of the CAS server.
  290. * @return a URI.
  291. * @private
  292. */
  293. function getServerURI()
  294. { return $this->_server['uri']; }
  295. /**
  296. * This method is used to retrieve the base URL of the CAS server.
  297. * @return a URL.
  298. * @private
  299. */
  300. function getServerBaseURL()
  301. {
  302. // the URL is build only when needed
  303. if ( empty($this->_server['base_url']) ) {
  304. $this->_server['base_url'] = 'https://' . $this->getServerHostname();
  305. if ($this->getServerPort()!=443) {
  306. $this->_server['base_url'] .= ':'
  307. .$this->getServerPort();
  308. }
  309. $this->_server['base_url'] .= $this->getServerURI();
  310. }
  311. return $this->_server['base_url'];
  312. }
  313. /**
  314. * This method is used to retrieve the login URL of the CAS server.
  315. * @param $gateway true to check authentication, false to force it
  316. * @param $renew true to force the authentication with the CAS server
  317. * NOTE : It is recommended that CAS implementations ignore the
  318. "gateway" parameter if "renew" is set
  319. * @return a URL.
  320. * @private
  321. */
  322. 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'] .= preg_replace('/&/','%26',$this->getURL());
  329. $this->_server['login_url'] .= urlencode($this->getURL());
  330. if($renew) {
  331. // It is recommended that when the "renew" parameter is set, its value be "true"
  332. $this->_server['login_url'] .= '&renew=true';
  333. } elseif ($gateway) {
  334. // It is recommended that when the "gateway" parameter is set, its value be "true"
  335. $this->_server['login_url'] .= '&gateway=true';
  336. }
  337. }
  338. phpCAS::traceEnd($this->_server['login_url']);
  339. return $this->_server['login_url'];
  340. }
  341. /**
  342. * This method sets the login URL of the CAS server.
  343. * @param $url the login URL
  344. * @private
  345. * @since 0.4.21 by Wyman Chan
  346. */
  347. function setServerLoginURL($url)
  348. {
  349. return $this->_server['login_url'] = $url;
  350. }
  351. /**
  352. * This method sets the serviceValidate URL of the CAS server.
  353. * @param $url the serviceValidate URL
  354. * @private
  355. * @since 1.1.0 by Joachim Fritschi
  356. */
  357. function setServerServiceValidateURL($url)
  358. {
  359. return $this->_server['service_validate_url'] = $url;
  360. }
  361. /**
  362. * This method sets the proxyValidate URL of the CAS server.
  363. * @param $url the proxyValidate URL
  364. * @private
  365. * @since 1.1.0 by Joachim Fritschi
  366. */
  367. function setServerProxyValidateURL($url)
  368. {
  369. return $this->_server['proxy_validate_url'] = $url;
  370. }
  371. /**
  372. * This method sets the samlValidate URL of the CAS server.
  373. * @param $url the samlValidate URL
  374. * @private
  375. * @since 1.1.0 by Joachim Fritschi
  376. */
  377. function setServerSamlValidateURL($url)
  378. {
  379. return $this->_server['saml_validate_url'] = $url;
  380. }
  381. /**
  382. * This method is used to retrieve the service validating URL of the CAS server.
  383. * @return a URL.
  384. * @private
  385. */
  386. function getServerServiceValidateURL()
  387. {
  388. // the URL is build only when needed
  389. if ( empty($this->_server['service_validate_url']) ) {
  390. switch ($this->getServerVersion()) {
  391. case CAS_VERSION_1_0:
  392. $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
  393. break;
  394. case CAS_VERSION_2_0:
  395. $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
  396. break;
  397. }
  398. }
  399. // return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
  400. return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL());
  401. }
  402. /**
  403. * This method is used to retrieve the SAML validating URL of the CAS server.
  404. * @return a URL.
  405. * @private
  406. */
  407. function getServerSamlValidateURL()
  408. {
  409. phpCAS::traceBegin();
  410. // the URL is build only when needed
  411. if ( empty($this->_server['saml_validate_url']) ) {
  412. switch ($this->getServerVersion()) {
  413. case SAML_VERSION_1_1:
  414. $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
  415. break;
  416. }
  417. }
  418. phpCAS::traceEnd($this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL()));
  419. return $this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL());
  420. }
  421. /**
  422. * This method is used to retrieve the proxy validating URL of the CAS server.
  423. * @return a URL.
  424. * @private
  425. */
  426. function getServerProxyValidateURL()
  427. {
  428. // the URL is build only when needed
  429. if ( empty($this->_server['proxy_validate_url']) ) {
  430. switch ($this->getServerVersion()) {
  431. case CAS_VERSION_1_0:
  432. $this->_server['proxy_validate_url'] = '';
  433. break;
  434. case CAS_VERSION_2_0:
  435. $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
  436. break;
  437. }
  438. }
  439. // return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
  440. return $this->_server['proxy_validate_url'].'?service='.urlencode($this->getURL());
  441. }
  442. /**
  443. * This method is used to retrieve the proxy URL of the CAS server.
  444. * @return a URL.
  445. * @private
  446. */
  447. function getServerProxyURL()
  448. {
  449. // the URL is build only when needed
  450. if ( empty($this->_server['proxy_url']) ) {
  451. switch ($this->getServerVersion()) {
  452. case CAS_VERSION_1_0:
  453. $this->_server['proxy_url'] = '';
  454. break;
  455. case CAS_VERSION_2_0:
  456. $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
  457. break;
  458. }
  459. }
  460. return $this->_server['proxy_url'];
  461. }
  462. /**
  463. * This method is used to retrieve the logout URL of the CAS server.
  464. * @return a URL.
  465. * @private
  466. */
  467. function getServerLogoutURL()
  468. {
  469. // the URL is build only when needed
  470. if ( empty($this->_server['logout_url']) ) {
  471. $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
  472. }
  473. return $this->_server['logout_url'];
  474. }
  475. /**
  476. * This method sets the logout URL of the CAS server.
  477. * @param $url the logout URL
  478. * @private
  479. * @since 0.4.21 by Wyman Chan
  480. */
  481. function setServerLogoutURL($url)
  482. {
  483. return $this->_server['logout_url'] = $url;
  484. }
  485. /**
  486. * An array to store extra curl options.
  487. */
  488. var $_curl_options = array();
  489. /**
  490. * This method is used to set additional user curl options.
  491. */
  492. function setExtraCurlOption($key, $value)
  493. {
  494. $this->_curl_options[$key] = $value;
  495. }
  496. /**
  497. * This method checks to see if the request is secured via HTTPS
  498. * @return true if https, false otherwise
  499. * @private
  500. */
  501. function isHttps() {
  502. //if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ) {
  503. //0.4.24 by Hinnack
  504. if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
  505. return true;
  506. } else {
  507. return false;
  508. }
  509. }
  510. // ########################################################################
  511. // CONSTRUCTOR
  512. // ########################################################################
  513. /**
  514. * CASClient constructor.
  515. *
  516. * @param $server_version the version of the CAS server
  517. * @param $proxy TRUE if the CAS client is a CAS proxy, FALSE otherwise
  518. * @param $server_hostname the hostname of the CAS server
  519. * @param $server_port the port the CAS server is running on
  520. * @param $server_uri the URI the CAS server is responding on
  521. * @param $start_session Have phpCAS start PHP sessions (default true)
  522. *
  523. * @return a newly created CASClient object
  524. *
  525. * @public
  526. */
  527. function CASClient(
  528. $server_version,
  529. $proxy,
  530. $server_hostname,
  531. $server_port,
  532. $server_uri,
  533. $start_session = true) {
  534. phpCAS::traceBegin();
  535. // the redirect header() call and DOM parsing code from domxml-php4-php5.php won't work in PHP4 compatibility mode
  536. if (version_compare(PHP_VERSION,'5','>=') && ini_get('zend.ze1_compatibility_mode')) {
  537. phpCAS::error('phpCAS cannot support zend.ze1_compatibility_mode. Sorry.');
  538. }
  539. $this->_start_session = $start_session;
  540. if ($this->_start_session && session_id() !== "")
  541. {
  542. phpCAS :: error("Another session was started before phpcas. Either disable the session" .
  543. " handling for phpcas in the client() call or modify your application to leave" .
  544. " session handling to phpcas");
  545. }
  546. // skip Session Handling for logout requests and if don't want it'
  547. if ($start_session && !$this->isLogoutRequest())
  548. {
  549. phpCAS :: trace("Starting a new session");
  550. session_start();
  551. }
  552. // are we in proxy mode ?
  553. $this->_proxy = $proxy;
  554. //check version
  555. switch ($server_version) {
  556. case CAS_VERSION_1_0:
  557. if ( $this->isProxy() )
  558. phpCAS::error('CAS proxies are not supported in CAS '
  559. .$server_version);
  560. break;
  561. case CAS_VERSION_2_0:
  562. break;
  563. case SAML_VERSION_1_1:
  564. break;
  565. default:
  566. phpCAS::error('this version of CAS (`'
  567. .$server_version
  568. .'\') is not supported by phpCAS '
  569. .phpCAS::getVersion());
  570. }
  571. $this->_server['version'] = $server_version;
  572. // check hostname
  573. if ( empty($server_hostname)
  574. || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
  575. phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
  576. }
  577. $this->_server['hostname'] = $server_hostname;
  578. // check port
  579. if ( $server_port == 0
  580. || !is_int($server_port) ) {
  581. phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
  582. }
  583. $this->_server['port'] = $server_port;
  584. // check URI
  585. if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
  586. phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
  587. }
  588. // add leading and trailing `/' and remove doubles
  589. $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
  590. $this->_server['uri'] = $server_uri;
  591. // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
  592. if ( $this->isProxy() ) {
  593. $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
  594. }
  595. if ( $this->isCallbackMode() ) {
  596. //callback mode: check that phpCAS is secured
  597. if ( !$this->isHttps() ) {
  598. phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
  599. }
  600. } else {
  601. //normal mode: get ticket and remove it from CGI parameters for developpers
  602. $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
  603. switch ($this->getServerVersion()) {
  604. case CAS_VERSION_1_0: // check for a Service Ticket
  605. if( preg_match('/^ST-/',$ticket) ) {
  606. phpCAS::trace('ST \''.$ticket.'\' found');
  607. //ST present
  608. $this->setST($ticket);
  609. //ticket has been taken into account, unset it to hide it to applications
  610. unset($_GET['ticket']);
  611. } else if ( !empty($ticket) ) {
  612. //ill-formed ticket, halt
  613. phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
  614. }
  615. break;
  616. case CAS_VERSION_2_0: // check for a Service or Proxy Ticket
  617. if( preg_match('/^[SP]T-/',$ticket) ) {
  618. phpCAS::trace('ST or PT \''.$ticket.'\' found');
  619. $this->setPT($ticket);
  620. unset($_GET['ticket']);
  621. } else if ( !empty($ticket) ) {
  622. //ill-formed ticket, halt
  623. phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
  624. }
  625. break;
  626. case SAML_VERSION_1_1: // SAML just does Service Tickets
  627. if( preg_match('/^[SP]T-/',$ticket) ) {
  628. phpCAS::trace('SA \''.$ticket.'\' found');
  629. $this->setSA($ticket);
  630. unset($_GET['ticket']);
  631. } else if ( !empty($ticket) ) {
  632. //ill-formed ticket, halt
  633. phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
  634. }
  635. break;
  636. }
  637. }
  638. phpCAS::traceEnd();
  639. }
  640. /** @} */
  641. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  642. // XX XX
  643. // XX Session Handling XX
  644. // XX XX
  645. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  646. /**
  647. * A variable to whether phpcas will use its own session handling. Default = true
  648. * @hideinitializer
  649. * @private
  650. */
  651. var $_start_session = true;
  652. function setStartSession($session)
  653. {
  654. $this->_start_session = session;
  655. }
  656. function getStartSession($session)
  657. {
  658. $this->_start_session = session;
  659. }
  660. /**
  661. * Renaming the session
  662. */
  663. function renameSession($ticket)
  664. {
  665. phpCAS::traceBegin();
  666. if($this->_start_session){
  667. if (!empty ($this->_user))
  668. {
  669. $old_session = $_SESSION;
  670. session_destroy();
  671. // set up a new session, of name based on the ticket
  672. $session_id = preg_replace('/[^\w]/', '', $ticket);
  673. phpCAS :: trace("Session ID: ".$session_id);
  674. session_id($session_id);
  675. session_start();
  676. phpCAS :: trace("Restoring old session vars");
  677. $_SESSION = $old_session;
  678. } else
  679. {
  680. phpCAS :: error('Session should only be renamed after successfull authentication');
  681. }
  682. }else{
  683. phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
  684. }
  685. phpCAS::traceEnd();
  686. }
  687. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  688. // XX XX
  689. // XX AUTHENTICATION XX
  690. // XX XX
  691. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  692. /**
  693. * @addtogroup internalAuthentication
  694. * @{
  695. */
  696. /**
  697. * The Authenticated user. Written by CASClient::setUser(), read by CASClient::getUser().
  698. * @attention client applications should use phpCAS::getUser().
  699. *
  700. * @hideinitializer
  701. * @private
  702. */
  703. var $_user = '';
  704. /**
  705. * This method sets the CAS user's login name.
  706. *
  707. * @param $user the login name of the authenticated user.
  708. *
  709. * @private
  710. */
  711. function setUser($user)
  712. {
  713. $this->_user = $user;
  714. }
  715. /**
  716. * This method returns the CAS user's login name.
  717. * @warning should be called only after CASClient::forceAuthentication() or
  718. * CASClient::isAuthenticated(), otherwise halt with an error.
  719. *
  720. * @return the login name of the authenticated user
  721. */
  722. function getUser()
  723. {
  724. if ( empty($this->_user) ) {
  725. phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
  726. }
  727. return $this->_user;
  728. }
  729. /***********************************************************************************************************************
  730. * Atrributes section
  731. *
  732. * @author Matthias Crauwels <matthias.crauwels@ugent.be>, Ghent University, Belgium
  733. *
  734. ***********************************************************************************************************************/
  735. /**
  736. * The Authenticated users attributes. Written by CASClient::setAttributes(), read by CASClient::getAttributes().
  737. * @attention client applications should use phpCAS::getAttributes().
  738. *
  739. * @hideinitializer
  740. * @private
  741. */
  742. var $_attributes = array();
  743. function setAttributes($attributes)
  744. { $this->_attributes = $attributes; }
  745. function getAttributes() {
  746. if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also...
  747. phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
  748. }
  749. return $this->_attributes;
  750. }
  751. function hasAttributes()
  752. { return !empty($this->_attributes); }
  753. function hasAttribute($key)
  754. { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
  755. function getAttribute($key) {
  756. if($this->hasAttribute($key)) {
  757. return $this->_attributes[$key];
  758. }
  759. }
  760. /**
  761. * This method is called to renew the authentication of the user
  762. * If the user is authenticated, renew the connection
  763. * If not, redirect to CAS
  764. * @public
  765. */
  766. function renewAuthentication(){
  767. phpCAS::traceBegin();
  768. // Either way, the user is authenticated by CAS
  769. if( isset( $_SESSION['phpCAS']['auth_checked'] ) )
  770. unset($_SESSION['phpCAS']['auth_checked']);
  771. if ( $this->isAuthenticated() ) {
  772. phpCAS::trace('user already authenticated; renew');
  773. $this->redirectToCas(false,true);
  774. } else {
  775. $this->redirectToCas();
  776. }
  777. phpCAS::traceEnd();
  778. }
  779. /**
  780. * This method is called to be sure that the user is authenticated. When not
  781. * authenticated, halt by redirecting to the CAS server; otherwise return TRUE.
  782. * @return TRUE when the user is authenticated; otherwise halt.
  783. * @public
  784. */
  785. function forceAuthentication()
  786. {
  787. phpCAS::traceBegin();
  788. if ( $this->isAuthenticated() ) {
  789. // the user is authenticated, nothing to be done.
  790. phpCAS::trace('no need to authenticate');
  791. $res = TRUE;
  792. } else {
  793. // the user is not authenticated, redirect to the CAS server
  794. if (isset($_SESSION['phpCAS']['auth_checked'])) {
  795. unset($_SESSION['phpCAS']['auth_checked']);
  796. }
  797. $this->redirectToCas(FALSE/* no gateway */);
  798. // never reached
  799. $res = FALSE;
  800. }
  801. phpCAS::traceEnd($res);
  802. return $res;
  803. }
  804. /**
  805. * An integer that gives the number of times authentication will be cached before rechecked.
  806. *
  807. * @hideinitializer
  808. * @private
  809. */
  810. var $_cache_times_for_auth_recheck = 0;
  811. /**
  812. * Set the number of times authentication will be cached before rechecked.
  813. *
  814. * @param $n an integer.
  815. *
  816. * @public
  817. */
  818. function setCacheTimesForAuthRecheck($n)
  819. {
  820. $this->_cache_times_for_auth_recheck = $n;
  821. }
  822. /**
  823. * This method is called to check whether the user is authenticated or not.
  824. * @return TRUE when the user is authenticated, FALSE otherwise.
  825. * @public
  826. */
  827. function checkAuthentication()
  828. {
  829. phpCAS::traceBegin();
  830. if ( $this->isAuthenticated() ) {
  831. phpCAS::trace('user is authenticated');
  832. $res = TRUE;
  833. } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
  834. // the previous request has redirected the client to the CAS server with gateway=true
  835. unset($_SESSION['phpCAS']['auth_checked']);
  836. $res = FALSE;
  837. } else {
  838. // $_SESSION['phpCAS']['auth_checked'] = true;
  839. // $this->redirectToCas(TRUE/* gateway */);
  840. // // never reached
  841. // $res = FALSE;
  842. // avoid a check against CAS on every request
  843. if (! isset($_SESSION['phpCAS']['unauth_count']) )
  844. $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
  845. if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1)
  846. || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck))
  847. {
  848. $res = FALSE;
  849. if ($this->_cache_times_for_auth_recheck != -1)
  850. {
  851. $_SESSION['phpCAS']['unauth_count']++;
  852. phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')');
  853. }
  854. else
  855. {
  856. phpCAS::trace('user is not authenticated (cached for until login pressed)');
  857. }
  858. }
  859. else
  860. {
  861. $_SESSION['phpCAS']['unauth_count'] = 0;
  862. $_SESSION['phpCAS']['auth_checked'] = true;
  863. phpCAS::trace('user is not authenticated (cache reset)');
  864. $this->redirectToCas(TRUE/* gateway */);
  865. // never reached
  866. $res = FALSE;
  867. }
  868. }
  869. phpCAS::traceEnd($res);
  870. return $res;
  871. }
  872. /**
  873. * This method is called to check if the user is authenticated (previously or by
  874. * tickets given in the URL).
  875. *
  876. * @return TRUE when the user is authenticated. Also may redirect to the same URL without the ticket.
  877. *
  878. * @public
  879. */
  880. function isAuthenticated()
  881. {
  882. phpCAS::traceBegin();
  883. $res = FALSE;
  884. $validate_url = '';
  885. if ( $this->wasPreviouslyAuthenticated() ) {
  886. if($this->hasST() || $this->hasPT() || $this->hasSA()){
  887. // User has a additional ticket but was already authenticated
  888. phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
  889. header('Location: '.$this->getURL());
  890. phpCAS::log( "Prepare redirect to remove ticket: ".$this->getURL() );
  891. phpCAS::traceExit();
  892. exit();
  893. }else{
  894. // the user has already (previously during the session) been
  895. // authenticated, nothing to be done.
  896. phpCAS::trace('user was already authenticated, no need to look for tickets');
  897. $res = TRUE;
  898. }
  899. }
  900. else {
  901. if ( $this->hasST() ) {
  902. // if a Service Ticket was given, validate it
  903. phpCAS::trace('ST `'.$this->getST().'\' is present');
  904. $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
  905. phpCAS::trace('ST `'.$this->getST().'\' was validated');
  906. if ( $this->isProxy() ) {
  907. $this->validatePGT($validate_url,$text_response,$tree_response); // idem
  908. phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
  909. $_SESSION['phpCAS']['pgt'] = $this->getPGT();
  910. }
  911. $_SESSION['phpCAS']['user'] = $this->getUser();
  912. $res = TRUE;
  913. }
  914. elseif ( $this->hasPT() ) {
  915. // if a Proxy Ticket was given, validate it
  916. phpCAS::trace('PT `'.$this->getPT().'\' is present');
  917. $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
  918. phpCAS::trace('PT `'.$this->getPT().'\' was validated');
  919. if ( $this->isProxy() ) {
  920. $this->validatePGT($validate_url,$text_response,$tree_response); // idem
  921. phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
  922. $_SESSION['phpCAS']['pgt'] = $this->getPGT();
  923. }
  924. $_SESSION['phpCAS']['user'] = $this->getUser();
  925. $res = TRUE;
  926. }
  927. elseif ( $this->hasSA() ) {
  928. // if we have a SAML ticket, validate it.
  929. phpCAS::trace('SA `'.$this->getSA().'\' is present');
  930. $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
  931. phpCAS::trace('SA `'.$this->getSA().'\' was validated');
  932. $_SESSION['phpCAS']['user'] = $this->getUser();
  933. $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
  934. $res = TRUE;
  935. }
  936. else {
  937. // no ticket given, not authenticated
  938. phpCAS::trace('no ticket found');
  939. }
  940. if ($res) {
  941. // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
  942. // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
  943. // remove the ticket as a security precaution to prevent a ticket in the HTTP_REFERRER
  944. header('Location: '.$this->getURL());
  945. phpCAS::log( "Prepare redirect to : ".$this->getURL() );
  946. phpCAS::traceExit();
  947. exit();
  948. }
  949. }
  950. phpCAS::traceEnd($res);
  951. return $res;
  952. }
  953. /**
  954. * This method tells if the current session is authenticated.
  955. * @return true if authenticated based soley on $_SESSION variable
  956. * @since 0.4.22 by Brendan Arnold
  957. */
  958. function isSessionAuthenticated ()
  959. {
  960. return !empty($_SESSION['phpCAS']['user']);
  961. }
  962. /**
  963. * This method tells if the user has already been (previously) authenticated
  964. * by looking into the session variables.
  965. *
  966. * @note This function switches to callback mode when needed.
  967. *
  968. * @return TRUE when the user has already been authenticated; FALSE otherwise.
  969. *
  970. * @private
  971. */
  972. function wasPreviouslyAuthenticated()
  973. {
  974. phpCAS::traceBegin();
  975. if ( $this->isCallbackMode() ) {
  976. $this->callback();
  977. }
  978. $auth = FALSE;
  979. if ( $this->isProxy() ) {
  980. // CAS proxy: username and PGT must be present
  981. if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
  982. // authentication already done
  983. $this->setUser($_SESSION['phpCAS']['user']);
  984. $this->setPGT($_SESSION['phpCAS']['pgt']);
  985. phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
  986. $auth = TRUE;
  987. } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
  988. // these two variables should be empty or not empty at the same time
  989. phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
  990. // unset all tickets to enforce authentication
  991. unset($_SESSION['phpCAS']);
  992. $this->setST('');
  993. $this->setPT('');
  994. } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
  995. // these two variables should be empty or not empty at the same time
  996. phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
  997. // unset all tickets to enforce authentication
  998. unset($_SESSION['phpCAS']);
  999. $this->setST('');
  1000. $this->setPT('');
  1001. } else {
  1002. phpCAS::trace('neither user not PGT found');
  1003. }
  1004. } else {
  1005. // `simple' CAS client (not a proxy): username must be present
  1006. if ( $this->isSessionAuthenticated() ) {
  1007. // authentication already done
  1008. $this->setUser($_SESSION['phpCAS']['user']);
  1009. if(isset($_SESSION['phpCAS']['attributes'])){
  1010. $this->setAttributes($_SESSION['phpCAS']['attributes']);
  1011. }
  1012. phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
  1013. $auth = TRUE;
  1014. } else {
  1015. phpCAS::trace('no user found');
  1016. }
  1017. }
  1018. phpCAS::traceEnd($auth);
  1019. return $auth;
  1020. }
  1021. /**
  1022. * This method is used to redirect the client to the CAS server.
  1023. * It is used by CASClient::forceAuthentication() and CASClient::checkAuthentication().
  1024. * @param $gateway true to check authentication, false to force it
  1025. * @param $renew true to force the authentication with the CAS server
  1026. * @public
  1027. */
  1028. function redirectToCas($gateway=false,$renew=false){
  1029. phpCAS::traceBegin();
  1030. $cas_url = $this->getServerLoginURL($gateway,$renew);
  1031. header('Location: '.$cas_url);
  1032. phpCAS::log( "Redirect to : ".$cas_url );
  1033. $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED));
  1034. printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
  1035. $this->printHTMLFooter();
  1036. phpCAS::traceExit();
  1037. exit();
  1038. }
  1039. /**
  1040. * This method is used to logout from CAS.
  1041. * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server
  1042. * @public
  1043. */
  1044. function logout($params) {
  1045. phpCAS::traceBegin();
  1046. $cas_url = $this->getServerLogoutURL();
  1047. $paramSeparator = '?';
  1048. if (isset($params['url'])) {
  1049. $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']);
  1050. $paramSeparator = '&';
  1051. }
  1052. if (isset($params['service'])) {
  1053. $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
  1054. }
  1055. header('Location: '.$cas_url);
  1056. phpCAS::log( "Prepare redirect to : ".$cas_url );
  1057. session_unset();
  1058. session_destroy();
  1059. $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
  1060. printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
  1061. $this->printHTMLFooter();
  1062. phpCAS::traceExit();
  1063. exit();
  1064. }
  1065. /**
  1066. * @return true if the current request is a logout request.
  1067. * @private
  1068. */
  1069. function isLogoutRequest() {
  1070. return !empty($_POST['logoutRequest']);
  1071. }
  1072. /**
  1073. * @return true if a logout request is allowed.
  1074. * @private
  1075. */
  1076. function isLogoutRequestAllowed() {
  1077. }
  1078. /**
  1079. * This method handles logout requests.
  1080. * @param $check_client true to check the client bofore handling the request,
  1081. * false not to perform any access control. True by default.
  1082. * @param $allowed_clients an array of host names allowed to send logout requests.
  1083. * By default, only the CAs server (declared in the constructor) will be allowed.
  1084. * @public
  1085. */
  1086. function handleLogoutRequests($check_client=true, $allowed_clients=false) {
  1087. phpCAS::traceBegin();
  1088. if (!$this->isLogoutRequest()) {
  1089. phpCAS::log("Not a logout request");
  1090. phpCAS::traceEnd();
  1091. return;
  1092. }
  1093. if(!$this->_start_session){
  1094. phpCAS::log("phpCAS can't handle logout requests if it does not manage the session.");
  1095. }
  1096. phpCAS::log("Logout requested");
  1097. phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']);
  1098. if ($check_client) {
  1099. if (!$allowed_clients) {
  1100. $allowed_clients = array( $this->getServerHostname() );
  1101. }
  1102. $client_ip = $_SERVER['REMOTE_ADDR'];
  1103. $client = gethostbyaddr($client_ip);
  1104. phpCAS::log("Client: ".$client."/".$client_ip);
  1105. $allowed = false;
  1106. foreach ($allowed_clients as $allowed_client) {
  1107. if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
  1108. phpCAS::log("Allowed client '".$allowed_client."' matches, logout request is allowed");
  1109. $allowed = true;
  1110. break;
  1111. } else {
  1112. phpCAS::log("Allowed client '".$allowed_client."' does not match");
  1113. }
  1114. }
  1115. if (!$allowed) {
  1116. phpCAS::error("Unauthorized logout request from client '".$client."'");
  1117. printf("Unauthorized!");
  1118. phpCAS::traceExit();
  1119. exit();
  1120. }
  1121. } else {
  1122. phpCAS::log("No access control set");
  1123. }
  1124. // Extract the ticket from the SAML Request
  1125. preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3);
  1126. $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|','',$tick[0][0]);
  1127. $ticket2logout = preg_replace('|</samlp:SessionIndex>|','',$wrappedSamlSessionIndex);
  1128. phpCAS::log("Ticket to logout: ".$ticket2logout);
  1129. $session_id = preg_replace('/[^\w]/','',$ticket2logout);
  1130. phpCAS::log("Session id: ".$session_id);
  1131. // destroy a possible application session created before phpcas
  1132. if(session_id() !== ""){
  1133. session_unset();
  1134. session_destroy();
  1135. }
  1136. // fix session ID
  1137. session_id($session_id);
  1138. $_COOKIE[session_name()]=$session_id;
  1139. $_GET[session_name()]=$session_id;
  1140. // Overwrite session
  1141. session_start();
  1142. session_unset();
  1143. session_destroy();
  1144. printf("Disconnected!");
  1145. phpCAS::traceExit();
  1146. exit();
  1147. }
  1148. /** @} */
  1149. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1150. // XX XX
  1151. // XX BASIC CLIENT FEATURES (CAS 1.0) XX
  1152. // XX XX
  1153. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1154. // ########################################################################
  1155. // ST
  1156. // ########################################################################
  1157. /**
  1158. * @addtogroup internalBasic
  1159. * @{
  1160. */
  1161. /**
  1162. * the Service Ticket provided in the URL of the request if present
  1163. * (empty otherwise). Written by CASClient::CASClient(), read by
  1164. * CASClient::getST() and CASClient::hasPGT().
  1165. *
  1166. * @hideinitializer
  1167. * @private
  1168. */
  1169. var $_st = '';
  1170. /**
  1171. * This method returns the Service Ticket provided in the URL of the request.
  1172. * @return The service ticket.
  1173. * @private
  1174. */
  1175. function getST()
  1176. { return $this->_st; }
  1177. /**
  1178. * This method stores the Service Ticket.
  1179. * @param $st The Service Ticket.
  1180. * @private
  1181. */
  1182. function setST($st)
  1183. { $this->_st = $st; }
  1184. /**
  1185. * This method tells if a Service Ticket was stored.
  1186. * @return TRUE if a Service Ticket has been stored.
  1187. * @private
  1188. */
  1189. function hasST()
  1190. { return !empty($this->_st); }
  1191. /** @} */
  1192. // ########################################################################
  1193. // ST VALIDATION
  1194. // ########################################################################
  1195. /**
  1196. * @addtogroup internalBasic
  1197. * @{
  1198. */
  1199. /**
  1200. * the certificate of the CAS server.
  1201. *
  1202. * @hideinitializer
  1203. * @private
  1204. */
  1205. var $_cas_server_cert = '';
  1206. /**
  1207. * the certificate of the CAS server CA.
  1208. *
  1209. * @hideinitializer
  1210. * @private
  1211. */
  1212. var $_cas_server_ca_cert = '';
  1213. /**
  1214. * Set to true not to validate the CAS server.
  1215. *
  1216. * @hideinitializer
  1217. * @private
  1218. */
  1219. var $_no_cas_server_validation = false;
  1220. /**
  1221. * Set the certificate of the CAS server.
  1222. *
  1223. * @param $cert the PEM certificate
  1224. */
  1225. function setCasServerCert($cert)
  1226. {
  1227. $this->_cas_server_cert = $cert;
  1228. }
  1229. /**
  1230. * Set the CA certificate of the CAS server.
  1231. *
  1232. * @param $cert the PEM certificate of the CA that emited the cert of the server
  1233. */
  1234. function setCasServerCACert($cert)
  1235. {
  1236. $this->_cas_server_ca_cert = $cert;
  1237. }
  1238. /**
  1239. * Set no SSL validation for the CAS server.
  1240. */
  1241. function setNoCasServerValidation()
  1242. {
  1243. $this->_no_cas_server_validation = true;
  1244. }
  1245. /**
  1246. * This method is used to validate a ST; halt on failure, and sets $validate_url,
  1247. * $text_reponse and $tree_response on success. These parameters are used later
  1248. * by CASClient::validatePGT() for CAS proxies.
  1249. * Used for all CAS 1.0 validations
  1250. * @param $validate_url the URL of the request to the CAS server.
  1251. * @param $text_response the response of the CAS server, as is (XML text).
  1252. * @param $tree_response the response of the CAS server, as a DOM XML tree.
  1253. *
  1254. * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
  1255. *
  1256. * @private
  1257. */
  1258. function validateST($validate_url,&$text_response,&$tree_response)
  1259. {
  1260. phpCAS::traceBegin();
  1261. // build the URL to validate the ticket
  1262. $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
  1263. if ( $this->isProxy() ) {
  1264. // pass the callback url for CAS proxies
  1265. $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
  1266. }
  1267. // open and read the URL
  1268. if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
  1269. phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
  1270. $this->authError('ST not validated',
  1271. $validate_url,
  1272. TRUE/*$no_response*/);
  1273. }
  1274. // analyze the result depending on the version
  1275. switch ($this->getServerVersion()) {
  1276. case CAS_VERSION_1_0:
  1277. if (preg_match('/^no\n/',$text_response)) {
  1278. phpCAS::trace('ST has not been validated');
  1279. $this->authError('ST not validated',
  1280. $validate_url,
  1281. FALSE/*$no_response*/,
  1282. FALSE/*$bad_response*/,
  1283. $text_response);
  1284. }
  1285. if (!preg_match('/^yes\n/',$text_response)) {
  1286. phpCAS::trace('ill-formed response');
  1287. $this->authError('ST not validated',
  1288. $validate_url,
  1289. FALSE/*$no_response*/,
  1290. TRUE/*$bad_response*/,
  1291. $text_response);
  1292. }
  1293. // ST has been validated, extract the user name
  1294. $arr = preg_split('/\n/',$text_response);
  1295. $this->setUser(trim($arr[1]));
  1296. break;
  1297. case CAS_VERSION_2_0:
  1298. // read the response of the CAS server into a DOM object
  1299. if ( !($dom = domxml_open_mem($text_response))) {
  1300. phpCAS::trace('domxml_open_mem() failed');
  1301. $this->authError('ST not validated',
  1302. $validate_url,
  1303. FALSE/*$no_response*/,
  1304. TRUE/*$bad_response*/,
  1305. $text_response);
  1306. }
  1307. // read the root node of the XML tree
  1308. if ( !($tree_response = $dom->document_element()) ) {
  1309. phpCAS::trace('document_element() failed');
  1310. $this->authError('ST not validated',
  1311. $validate_url,
  1312. FALSE/*$no_response*/,
  1313. TRUE/*$bad_response*/,
  1314. $text_response);
  1315. }
  1316. // insure that tag name is 'serviceResponse'
  1317. if ( $tree_response->node_name() != 'serviceResponse' ) {
  1318. phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->node_name().'\'');
  1319. $this->authError('ST not validated',
  1320. $validate_url,
  1321. FALSE/*$no_response*/,
  1322. TRUE/*$bad_response*/,
  1323. $text_response);
  1324. }
  1325. if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
  1326. // authentication succeded, extract the user name
  1327. if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) {
  1328. phpCAS::trace('<authenticationSuccess> found, but no <user>');
  1329. $this->authError('ST not validated',
  1330. $validate_url,
  1331. FALSE/*$no_response*/,
  1332. TRUE/*$bad_response*/,
  1333. $text_response);
  1334. }
  1335. $user = trim($user_elements[0]->get_content());
  1336. phpCAS::trace('user = `'.$user);
  1337. $this->setUser($user);
  1338. } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
  1339. phpCAS::trace('<authenticationFailure> found');
  1340. // authentication failed, extract the error code and message
  1341. $this->authError('ST not validated',
  1342. $validate_url,
  1343. FALSE/*$no_response*/,
  1344. FALSE/*$bad_response*/,
  1345. $text_response,
  1346. $failure_elements[0]->get_attribute('code')/*$err_code*/,
  1347. trim($failure_elements[0]->get_content())/*$err_msg*/);
  1348. } else {
  1349. phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
  1350. $this->authError('ST not validated',
  1351. $validate_url,
  1352. FALSE/*$no_response*/,
  1353. TRUE/*$bad_response*/,
  1354. $text_response);
  1355. }
  1356. break;
  1357. }
  1358. $this->renameSession($this->getST());
  1359. // at this step, ST has been validated and $this->_user has been set,
  1360. phpCAS::traceEnd(TRUE);
  1361. return TRUE;
  1362. }
  1363. // ########################################################################
  1364. // SAML VALIDATION
  1365. // ########################################################################
  1366. /**
  1367. * @addtogroup internalBasic
  1368. * @{
  1369. */
  1370. /**
  1371. * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
  1372. * $text_reponse and $tree_response on success. These parameters are used later
  1373. * by CASClient::validatePGT() for CAS proxies.
  1374. *
  1375. * @param $validate_url the URL of the request to the CAS server.
  1376. * @param $text_response the response of the CAS server, as is (XML text).
  1377. * @param $tree_response the response of the CAS server, as a DOM XML tree.
  1378. *
  1379. * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
  1380. *
  1381. * @private
  1382. */
  1383. function validateSA($validate_url,&$text_response,&$tree_response)
  1384. {
  1385. phpCAS::traceBegin();
  1386. // build the URL to validate the ticket
  1387. $validate_url = $this->getServerSamlValidateURL();
  1388. // open and read the URL
  1389. if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
  1390. phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
  1391. $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
  1392. }
  1393. phpCAS::trace('server version: '.$this->getServerVersion());
  1394. // analyze the result depending on the version
  1395. switch ($this->getServerVersion()) {
  1396. case SAML_VERSION_1_1:
  1397. // read the response of the CAS server into a DOM object
  1398. if ( !($dom = domxml_open_mem($text_response))) {
  1399. phpCAS::trace('domxml_open_mem() failed');
  1400. $this->authError('SA not validated',
  1401. $validate_url,
  1402. FALSE/*$no_response*/,
  1403. TRUE/*$bad_response*/,
  1404. $text_response);
  1405. }
  1406. // read the root node of the XML tree
  1407. if ( !($tree_response = $dom->document_element()) ) {
  1408. phpCAS::trace('document_element() failed');
  1409. $this->authError('SA not validated',
  1410. $validate_url,
  1411. FALSE/*$no_response*/,
  1412. TRUE/*$bad_response*/,
  1413. $text_response);
  1414. }
  1415. // insure that tag name is 'Envelope'
  1416. if ( $tree_response->node_name() != 'Envelope' ) {
  1417. phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->node_name().'\'');
  1418. $this->authError('SA not validated',
  1419. $validate_url,
  1420. FALSE/*$no_response*/,
  1421. TRUE/*$bad_response*/,
  1422. $text_response);
  1423. }
  1424. // check for the NameIdentifier tag in the SAML response
  1425. if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
  1426. phpCAS::trace('NameIdentifier found');
  1427. $user = trim($success_elements[0]->get_content());
  1428. phpCAS::trace('user = `'.$user.'`');
  1429. $this->setUser($user);
  1430. $this->setSessionAttributes($text_response);
  1431. } else {
  1432. phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
  1433. $this->authError('SA not validated',
  1434. $validate_url,
  1435. FALSE/*$no_response*/,
  1436. TRUE/*$bad_response*/,
  1437. $text_response);
  1438. }
  1439. break;
  1440. }
  1441. $this->renameSession($this->getSA());
  1442. // at this step, ST has been validated and $this->_user has been set,
  1443. phpCAS::traceEnd(TRUE);
  1444. return TRUE;
  1445. }
  1446. /**
  1447. * This method will parse the DOM and pull out the attributes from the SAML
  1448. * payload and put them into an array, then put the array into the session.
  1449. *
  1450. * @param $text_response the SAML payload.
  1451. * @return bool TRUE when successfull and FALSE if no attributes a found
  1452. *
  1453. * @private
  1454. */
  1455. function setSessionAttributes($text_response)
  1456. {
  1457. phpCAS::traceBegin();
  1458. $result = FALSE;
  1459. if (isset($_SESSION[SAML_ATTRIBUTES])) {
  1460. phpCAS::trace("session attrs already set."); //testbml - do we care?
  1461. }
  1462. $attr_array = array();
  1463. if (($dom = domxml_open_mem($text_response))) {
  1464. $xPath = $dom->xpath_new_context();
  1465. $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
  1466. $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
  1467. $nodelist = $xPath->xpath_eval("//saml:Attribute");
  1468. if($nodelist){
  1469. $attrs = $nodelist->nodeset;
  1470. foreach($attrs as $attr){
  1471. $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
  1472. $name = $attr->get_attribute("AttributeName");
  1473. $value_array = array();
  1474. foreach($xres->nodeset as $node){
  1475. $value_array[] = $node->get_content();
  1476. }
  1477. $attr_array[$name] = $value_array;
  1478. }
  1479. $_SESSION[SAML_ATTRIBUTES] = $attr_array;
  1480. // UGent addition...
  1481. foreach($attr_array as $attr_key => $attr_value) {
  1482. if(count($attr_value) > 1) {
  1483. $this->_attributes[$attr_key] = $attr_value;
  1484. phpCAS::trace("* " . $attr_key . "=" . $attr_value);
  1485. }
  1486. else {
  1487. $this->_attributes[$attr_key] = $attr_value[0];
  1488. phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
  1489. }
  1490. }
  1491. $result = TRUE;
  1492. }else{
  1493. phpCAS::trace("SAML Attributes are empty");
  1494. $result = FALSE;
  1495. }
  1496. }
  1497. phpCAS::traceEnd($result);
  1498. return $result;
  1499. }
  1500. /** @} */
  1501. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1502. // XX XX
  1503. // XX PROXY FEATURES (CAS 2.0) XX
  1504. // XX XX
  1505. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1506. // ########################################################################
  1507. // PROXYING
  1508. // ########################################################################
  1509. /**
  1510. * @addtogroup internalProxy
  1511. * @{
  1512. */
  1513. /**
  1514. * A boolean telling if the client is a CAS proxy or not. Written by CASClient::CASClient(),
  1515. * read by CASClient::isProxy().
  1516. *
  1517. * @private
  1518. */
  1519. var $_proxy;
  1520. /**
  1521. * Tells if a CAS client is a CAS proxy or not
  1522. *
  1523. * @return TRUE when the CAS client is a CAs proxy, FALSE otherwise
  1524. *
  1525. * @private
  1526. */
  1527. function isProxy()
  1528. {
  1529. return $this->_proxy;
  1530. }
  1531. /** @} */
  1532. // ########################################################################
  1533. // PGT
  1534. // ########################################################################
  1535. /**
  1536. * @addtogroup internalProxy
  1537. * @{
  1538. */
  1539. /**
  1540. * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
  1541. * Written by CASClient::setPGT(), read by CASClient::getPGT() and CASClient::hasPGT().
  1542. *
  1543. * @hideinitializer
  1544. * @private
  1545. */
  1546. var $_pgt = '';
  1547. /**
  1548. * This method returns the Proxy Granting Ticket given by the CAS server.
  1549. * @return The Proxy Granting Ticket.
  1550. * @private
  1551. */
  1552. function getPGT()
  1553. { return $this->_pgt; }
  1554. /**
  1555. * This method stores the Proxy Granting Ticket.
  1556. * @param $pgt The Proxy Granting Ticket.
  1557. * @private
  1558. */
  1559. function setPGT($pgt)
  1560. { $this->_pgt = $pgt; }
  1561. /**
  1562. * This method tells if a Proxy Granting Ticket was stored.
  1563. * @return TRUE if a Proxy Granting Ticket has been stored.
  1564. * @private
  1565. */
  1566. function hasPGT()
  1567. { return !empty($this->_pgt); }
  1568. /** @} */
  1569. // ########################################################################
  1570. // CALLBACK MODE
  1571. // ########################################################################
  1572. /**
  1573. * @addtogroup internalCallback
  1574. * @{
  1575. */
  1576. /**
  1577. * each PHP script using phpCAS in proxy mode is its own callback to get the
  1578. * PGT back from the CAS server. callback_mode is detected by the constructor
  1579. * thanks to the GET parameters.
  1580. */
  1581. /**
  1582. * a boolean to know if the CAS client is running in callback mode. Written by
  1583. * CASClient::setCallBackMode(), read by CASClient::isCallbackMode().
  1584. *
  1585. * @hideinitializer
  1586. * @private
  1587. */
  1588. var $_callback_mode = FALSE;
  1589. /**
  1590. * This method sets/unsets callback mode.
  1591. *
  1592. * @param $callback_mode TRUE to set callback mode, FALSE otherwise.
  1593. *
  1594. * @private
  1595. */
  1596. function setCallbackMode($callback_mode)
  1597. {
  1598. $this->_callback_mode = $callback_mode;
  1599. }
  1600. /**
  1601. * This method returns TRUE when the CAs client is running i callback mode,
  1602. * FALSE otherwise.
  1603. *
  1604. * @return A boolean.
  1605. *
  1606. * @private
  1607. */
  1608. function isCallbackMode()
  1609. {
  1610. return $this->_callback_mode;
  1611. }
  1612. /**
  1613. * the URL that should be used for the PGT callback (in fact the URL of the
  1614. * current request without any CGI parameter). Written and read by
  1615. * CASClient::getCallbackURL().
  1616. *
  1617. * @hideinitializer
  1618. * @private
  1619. */
  1620. var $_callback_url = '';
  1621. /**
  1622. * This method returns the URL that should be used for the PGT callback (in
  1623. * fact the URL of the current request without any CGI parameter, except if
  1624. * phpCAS::setFixedCallbackURL() was used).
  1625. *
  1626. * @return The callback URL
  1627. *
  1628. * @private
  1629. */
  1630. function getCallbackURL()
  1631. {
  1632. // the URL is built when needed only
  1633. if ( empty($this->_callback_url) ) {
  1634. $final_uri = '';
  1635. // remove the ticket if present in the URL
  1636. $final_uri = 'https://';
  1637. /* replaced by Julien Marchal - v0.4.6
  1638. * $this->uri .= $_SERVER['SERVER_NAME'];
  1639. */
  1640. if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
  1641. /* replaced by teedog - v0.4.12
  1642. * $final_uri .= $_SERVER['SERVER_NAME'];
  1643. */
  1644. if (empty($_SERVER['SERVER_NAME'])) {
  1645. $final_uri .= $_SERVER['HTTP_HOST'];
  1646. } else {
  1647. $final_uri .= $_SERVER['SERVER_NAME'];
  1648. }
  1649. } else {
  1650. $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
  1651. }
  1652. if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
  1653. || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
  1654. $final_uri .= ':';
  1655. $final_uri .= $_SERVER['SERVER_PORT'];
  1656. }
  1657. $request_uri = $_SERVER['REQUEST_URI'];
  1658. $request_uri = preg_replace('/\?.*$/','',$request_uri);
  1659. $final_uri .= $request_uri;
  1660. $this->setCallbackURL($final_uri);
  1661. }
  1662. return $this->_callback_url;
  1663. }
  1664. /**
  1665. * This method sets the callback url.
  1666. *
  1667. * @param $callback_url url to set callback
  1668. *
  1669. * @private
  1670. */
  1671. function setCallbackURL($url)
  1672. {
  1673. return $this->_callback_url = $url;
  1674. }
  1675. /**
  1676. * This method is called by CASClient::CASClient() when running in callback
  1677. * mode. It stores the PGT and its PGT Iou, prints its output and halts.
  1678. *
  1679. * @private
  1680. */
  1681. function callback()
  1682. {
  1683. phpCAS::traceBegin();
  1684. if (preg_match('/PGTIOU-[\.\-\w]/', $_GET['pgtIou'])){
  1685. if(preg_match('/[PT]GT-[\.\-\w]/', $_GET['pgtId'])){
  1686. $this->printHTMLHeader('phpCAS callback');
  1687. $pgt_iou = $_GET['pgtIou'];
  1688. $pgt = $_GET['pgtId'];
  1689. phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
  1690. echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
  1691. $this->storePGT($pgt,$pgt_iou);
  1692. $this->printHTMLFooter();
  1693. }else{
  1694. phpCAS::error('PGT format invalid' . $_GET['pgtId']);
  1695. }
  1696. }else{
  1697. phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
  1698. }
  1699. phpCAS::traceExit();
  1700. exit();
  1701. }
  1702. /** @} */
  1703. // ########################################################################
  1704. // PGT STORAGE
  1705. // ########################################################################
  1706. /**
  1707. * @addtogroup internalPGTStorage
  1708. * @{
  1709. */
  1710. /**
  1711. * an instance of a class inheriting of PGTStorage, used to deal with PGT
  1712. * storage. Created by CASClient::setPGTStorageFile() or CASClient::setPGTStorageDB(), used
  1713. * by CASClient::setPGTStorageFile(), CASClient::setPGTStorageDB() and CASClient::initPGTStorage().
  1714. *
  1715. * @hideinitializer
  1716. * @private
  1717. */
  1718. var $_pgt_storage = null;
  1719. /**
  1720. * This method is used to initialize the storage of PGT's.
  1721. * Halts on error.
  1722. *
  1723. * @private
  1724. */
  1725. function initPGTStorage()
  1726. {
  1727. // if no SetPGTStorageXxx() has been used, default to file
  1728. if ( !is_object($this->_pgt_storage) ) {
  1729. $this->setPGTStorageFile();
  1730. }
  1731. // initializes the storage
  1732. $this->_pgt_storage->init();
  1733. }
  1734. /**
  1735. * This method stores a PGT. Halts on error.
  1736. *
  1737. * @param $pgt the PGT to store
  1738. * @param $pgt_iou its corresponding Iou
  1739. *
  1740. * @private
  1741. */
  1742. function storePGT($pgt,$pgt_iou)
  1743. {
  1744. // ensure that storage is initialized
  1745. $this->initPGTStorage();
  1746. // writes the PGT
  1747. $this->_pgt_storage->write($pgt,$pgt_iou);
  1748. }
  1749. /**
  1750. * This method reads a PGT from its Iou and deletes the corresponding storage entry.
  1751. *
  1752. * @param $pgt_iou the PGT Iou
  1753. *
  1754. * @return The PGT corresponding to the Iou, FALSE when not found.
  1755. *
  1756. * @private
  1757. */
  1758. function loadPGT($pgt_iou)
  1759. {
  1760. // ensure that storage is initialized
  1761. $this->initPGTStorage();
  1762. // read the PGT
  1763. return $this->_pgt_storage->read($pgt_iou);
  1764. }
  1765. /**
  1766. * This method is used to tell phpCAS to store the response of the
  1767. * CAS server to PGT requests onto the filesystem.
  1768. *
  1769. * @param $format the format used to store the PGT's (`plain' and `xml' allowed)
  1770. * @param $path the path where the PGT's should be stored
  1771. *
  1772. * @public
  1773. */
  1774. function setPGTStorageFile($format='',
  1775. $path='')
  1776. {
  1777. // check that the storage has not already been set
  1778. if ( is_object($this->_pgt_storage) ) {
  1779. phpCAS::error('PGT storage already defined');
  1780. }
  1781. // create the storage object
  1782. $this->_pgt_storage = new PGTStorageFile($this,$format,$path);
  1783. }
  1784. // ########################################################################
  1785. // PGT VALIDATION
  1786. // ########################################################################
  1787. /**
  1788. * This method is used to validate a PGT; halt on failure.
  1789. *
  1790. * @param $validate_url the URL of the request to the CAS server.
  1791. * @param $text_response the response of the CAS server, as is (XML text); result
  1792. * of CASClient::validateST() or CASClient::validatePT().
  1793. * @param $tree_response the response of the CAS server, as a DOM XML tree; result
  1794. * of CASClient::validateST() or CASClient::validatePT().
  1795. *
  1796. * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
  1797. *
  1798. * @private
  1799. */
  1800. function validatePGT(&$validate_url,$text_response,$tree_response)
  1801. {
  1802. // here cannot use phpCAS::traceBegin(); alongside domxml-php4-to-php5.php
  1803. phpCAS::log('start validatePGT()');
  1804. if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
  1805. phpCAS::trace('<proxyGrantingTicket> not found');
  1806. // authentication succeded, but no PGT Iou was transmitted
  1807. $this->authError('Ticket validated but no PGT Iou transmitted',
  1808. $validate_url,
  1809. FALSE/*$no_response*/,
  1810. FALSE/*$bad_response*/,
  1811. $text_response);
  1812. } else {
  1813. // PGT Iou transmitted, extract it
  1814. $pgt_iou = trim($arr[0]->get_content());
  1815. if(preg_match('/PGTIOU-[\.\-\w]/',$pgt_iou)){
  1816. $pgt = $this->loadPGT($pgt_iou);
  1817. if ( $pgt == FALSE ) {
  1818. phpCAS::trace('could not load PGT');
  1819. $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
  1820. $validate_url,
  1821. FALSE/*$no_response*/,
  1822. FALSE/*$bad_response*/,
  1823. $text_response);
  1824. }
  1825. $this->setPGT($pgt);
  1826. }else{
  1827. phpCAS::trace('PGTiou format error');
  1828. $this->authError('PGT Iou was transmitted but has wrong fromat',
  1829. $validate_url,
  1830. FALSE/*$no_response*/,
  1831. FALSE/*$bad_response*/,
  1832. $text_response);
  1833. }
  1834. }
  1835. // here, cannot use phpCAS::traceEnd(TRUE); alongside domxml-php4-to-php5.php
  1836. phpCAS::log('end validatePGT()');
  1837. return TRUE;
  1838. }
  1839. // ########################################################################
  1840. // PGT VALIDATION
  1841. // ########################################################################
  1842. /**
  1843. * This method is used to retrieve PT's from the CAS server thanks to a PGT.
  1844. *
  1845. * @param $target_service the service to ask for with the PT.
  1846. * @param $err_code an error code (PHPCAS_SERVICE_OK on success).
  1847. * @param $err_msg an error message (empty on success).
  1848. *
  1849. * @return a Proxy Ticket, or FALSE on error.
  1850. *
  1851. * @private
  1852. */
  1853. function retrievePT($target_service,&$err_code,&$err_msg)
  1854. {
  1855. phpCAS::traceBegin();
  1856. // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
  1857. // set to false and $err_msg to an error message. At the end, if $pt is FALSE
  1858. // and $error_msg is still empty, it is set to 'invalid response' (the most
  1859. // commonly encountered error).
  1860. $err_msg = '';
  1861. // build the URL to retrieve the PT
  1862. // $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT();
  1863. $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT();
  1864. // open and read the URL
  1865. if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) {
  1866. phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
  1867. $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
  1868. $err_msg = 'could not retrieve PT (no response from the CAS server)';
  1869. phpCAS::traceEnd(FALSE);
  1870. return FALSE;
  1871. }
  1872. $bad_response = FALSE;
  1873. if ( !$bad_response ) {
  1874. // read the response of the CAS server into a DOM object
  1875. if ( !($dom = @domxml_open_mem($cas_response))) {
  1876. phpCAS::trace('domxml_open_mem() failed');
  1877. // read failed
  1878. $bad_response = TRUE;
  1879. }
  1880. }
  1881. if ( !$bad_response ) {
  1882. // read the root node of the XML tree
  1883. if ( !($root = $dom->document_element()) ) {
  1884. phpCAS::trace('document_element() failed');
  1885. // read failed
  1886. $bad_response = TRUE;
  1887. }
  1888. }
  1889. if ( !$bad_response ) {
  1890. // insure that tag name is 'serviceResponse'
  1891. if ( $root->node_name() != 'serviceResponse' ) {
  1892. phpCAS::trace('node_name() failed');
  1893. // bad root node
  1894. $bad_response = TRUE;
  1895. }
  1896. }
  1897. if ( !$bad_response ) {
  1898. // look for a proxySuccess tag
  1899. if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) {
  1900. // authentication succeded, look for a proxyTicket tag
  1901. if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) {
  1902. $err_code = PHPCAS_SERVICE_OK;
  1903. $err_msg = '';
  1904. phpCAS::trace('original PT: '.trim($arr[0]->get_content()));
  1905. $pt = trim($arr[0]->get_content());
  1906. phpCAS::traceEnd($pt);
  1907. return $pt;
  1908. } else {
  1909. phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
  1910. }
  1911. }
  1912. // look for a proxyFailure tag
  1913. else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) {
  1914. // authentication failed, extract the error
  1915. $err_code = PHPCAS_SERVICE_PT_FAILURE;
  1916. $err_msg = 'PT retrieving failed (code=`'
  1917. .$arr[0]->get_attribute('code')
  1918. .'\', message=`'
  1919. .trim($arr[0]->get_content())
  1920. .'\')';
  1921. phpCAS::traceEnd(FALSE);
  1922. return FALSE;
  1923. } else {
  1924. phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
  1925. }
  1926. }
  1927. // at this step, we are sure that the response of the CAS server was ill-formed
  1928. $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
  1929. $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
  1930. phpCAS::traceEnd(FALSE);
  1931. return FALSE;
  1932. }
  1933. // ########################################################################
  1934. // ACCESS TO EXTERNAL SERVICES
  1935. // ########################################################################
  1936. /**
  1937. * This method is used to acces a remote URL.
  1938. *
  1939. * @param $url the URL to access.
  1940. * @param $cookies an array containing cookies strings such as 'name=val'
  1941. * @param $headers an array containing the HTTP header lines of the response
  1942. * (an empty array on failure).
  1943. * @param $body the body of the response, as a string (empty on failure).
  1944. * @param $err_msg an error message, filled on failure.
  1945. *
  1946. * @return TRUE on success, FALSE otherwise (in this later case, $err_msg
  1947. * contains an error message).
  1948. *
  1949. * @private
  1950. */
  1951. function readURL($url,$cookies,&$headers,&$body,&$err_msg)
  1952. {
  1953. phpCAS::traceBegin();
  1954. $headers = '';
  1955. $body = '';
  1956. $err_msg = '';
  1957. $res = TRUE;
  1958. // initialize the CURL session
  1959. $ch = curl_init($url);
  1960. if (version_compare(PHP_VERSION,'5.1.3','>=')) {
  1961. //only avaible in php5
  1962. curl_setopt_array($ch, $this->_curl_options);
  1963. } else {
  1964. foreach ($this->_curl_options as $key => $value) {
  1965. curl_setopt($ch, $key, $value);
  1966. }
  1967. }
  1968. if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) {
  1969. phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
  1970. }
  1971. if ($this->_cas_server_cert != '' && $this->_cas_server_ca_cert != '') {
  1972. // This branch added by IDMS. Seems phpCAS implementor got a bit confused about the curl options CURLOPT_SSLCERT and CURLOPT_CAINFO
  1973. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  1974. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
  1975. curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
  1976. curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
  1977. curl_setopt($ch, CURLOPT_VERBOSE, '1');
  1978. phpCAS::trace('CURL: Set all required opts for mutual authentication ------');
  1979. } else if ($this->_cas_server_cert != '' ) {
  1980. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  1981. curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
  1982. } else if ($this->_cas_server_ca_cert != '') {
  1983. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  1984. curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
  1985. } else {
  1986. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
  1987. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  1988. }
  1989. // return the CURL output into a variable
  1990. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  1991. // get the HTTP header with a callback
  1992. $this->_curl_headers = array(); // empty the headers array
  1993. curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curl_read_headers'));
  1994. // add cookies headers
  1995. if ( is_array($cookies) ) {
  1996. curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
  1997. }
  1998. // add extra stuff if SAML
  1999. if ($this->hasSA()) {
  2000. $more_headers = array ("soapaction: http://www.oasis-open.org/committees/security",
  2001. "cache-control: no-cache",
  2002. "pragma: no-cache",
  2003. "accept: text/xml",
  2004. "connection: keep-alive",
  2005. "content-type: text/xml");
  2006. curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
  2007. curl_setopt($ch, CURLOPT_POST, 1);
  2008. $data = $this->buildSAMLPayload();
  2009. //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
  2010. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  2011. }
  2012. // perform the query
  2013. $buf = curl_exec ($ch);
  2014. //phpCAS::trace('CURL: Call completed. Response body is: \''.$buf.'\'');
  2015. if ( $buf === FALSE ) {
  2016. phpCAS::trace('curl_exec() failed');
  2017. $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch);
  2018. //phpCAS::trace('curl error: '.$err_msg);
  2019. // close the CURL session
  2020. curl_close ($ch);
  2021. $res = FALSE;
  2022. } else {
  2023. // close the CURL session
  2024. curl_close ($ch);
  2025. $headers = $this->_curl_headers;
  2026. $body = $buf;
  2027. }
  2028. phpCAS::traceEnd($res);
  2029. return $res;
  2030. }
  2031. /**
  2032. * This method is used to build the SAML POST body sent to /samlValidate URL.
  2033. *
  2034. * @return the SOAP-encased SAMLP artifact (the ticket).
  2035. *
  2036. * @private
  2037. */
  2038. function buildSAMLPayload()
  2039. {
  2040. phpCAS::traceBegin();
  2041. //get the ticket
  2042. $sa = $this->getSA();
  2043. //phpCAS::trace("SA: ".$sa);
  2044. $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;
  2045. phpCAS::traceEnd($body);
  2046. return ($body);
  2047. }
  2048. /**
  2049. * This method is the callback used by readURL method to request HTTP headers.
  2050. */
  2051. var $_curl_headers = array();
  2052. function _curl_read_headers($ch, $header)
  2053. {
  2054. $this->_curl_headers[] = $header;
  2055. return strlen($header);
  2056. }
  2057. /**
  2058. * This method is used to access an HTTP[S] service.
  2059. *
  2060. * @param $url the service to access.
  2061. * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
  2062. * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
  2063. * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE.
  2064. * @param $output the output of the service (also used to give an error
  2065. * message on failure).
  2066. *
  2067. * @return TRUE on success, FALSE otherwise (in this later case, $err_code
  2068. * gives the reason why it failed and $output contains an error message).
  2069. *
  2070. * @public
  2071. */
  2072. function serviceWeb($url,&$err_code,&$output)
  2073. {
  2074. phpCAS::traceBegin();
  2075. // at first retrieve a PT
  2076. $pt = $this->retrievePT($url,$err_code,$output);
  2077. $res = TRUE;
  2078. // test if PT was retrieved correctly
  2079. if ( !$pt ) {
  2080. // note: $err_code and $err_msg are filled by CASClient::retrievePT()
  2081. phpCAS::trace('PT was not retrieved correctly');
  2082. $res = FALSE;
  2083. } else {
  2084. // add cookies if necessary
  2085. $cookies = $this->getCookies($url);
  2086. // build the URL including the PT
  2087. if ( strstr($url,'?') === FALSE ) {
  2088. $service_url = $url.'?ticket='.$pt;
  2089. } else {
  2090. $service_url = $url.'&ticket='.$pt;
  2091. }
  2092. phpCAS::trace('reading URL`'.$service_url.'\'');
  2093. if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) {
  2094. phpCAS::trace('could not read URL`'.$service_url.'\'');
  2095. $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
  2096. // give an error message
  2097. $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
  2098. $service_url,
  2099. $err_msg);
  2100. $res = FALSE;
  2101. } else {
  2102. // URL has been fetched, extract the cookies
  2103. phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:');
  2104. $this->setCookies($headers,$url);
  2105. // Check for a possible redirect (phpCAS authenticiation redirect after ticket removal)
  2106. foreach($headers as $header){
  2107. if (preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches))
  2108. {
  2109. $redirect_url = trim(array_pop($matches));
  2110. phpCAS :: trace('Found redirect:'.$redirect_url);
  2111. $cookies = $this->getCookies($redirect_url);
  2112. phpCAS::trace('reading URL`'.$redirect_url.'\'');
  2113. if ( !$this->readURL($redirect_url,$cookies,$headers,$output,$err_msg) ) {
  2114. phpCAS::trace('could not read URL`'.$redirect_url.'\'');
  2115. $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
  2116. // give an error message
  2117. $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
  2118. $service_url,
  2119. $err_msg);
  2120. $res = FALSE;
  2121. } else {
  2122. // URL has been fetched, extract the cookies
  2123. phpCAS::trace('URL`'.$redirect_url.'\' has been read, storing cookies:');
  2124. $this->setCookies($headers,$redirect_url);
  2125. }
  2126. break;
  2127. }
  2128. }
  2129. }
  2130. }
  2131. phpCAS::traceEnd($res);
  2132. return $res;
  2133. }
  2134. /**
  2135. * This method stores cookies from a HTTP Header in the session
  2136. * @param $header HTTP Header
  2137. * @param $url the url the Header is from
  2138. */
  2139. function setCookies($headers,$url){
  2140. phpCAS::traceBegin();
  2141. foreach ( $headers as $header ) {
  2142. // test if the header is a cookie
  2143. if ( preg_match('/^Set-Cookie:/',$header) ) {
  2144. // the header is a cookie, remove the beginning
  2145. $header_val = preg_replace('/^Set-Cookie: */','',$header);
  2146. // extract interesting information
  2147. $name_val = strtok($header_val,'; ');
  2148. // extract the name and the value of the cookie
  2149. $cookie_name = strtok($name_val,'=');
  2150. $cookie_val = strtok('=');
  2151. // store the cookie
  2152. $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val;
  2153. phpCAS::trace($cookie_name.' -> '.$cookie_val);
  2154. }
  2155. }
  2156. phpCAS::traceEnd();
  2157. }
  2158. /**
  2159. * This method get the cookies from the session
  2160. */
  2161. function getCookies($url){
  2162. $cookies = array();
  2163. if ( isset($_SESSION['phpCAS']['services'][$url]['cookies']) &&
  2164. is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
  2165. foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) {
  2166. $cookies[] = $name.'='.$val;
  2167. }
  2168. }
  2169. return $cookies;
  2170. }
  2171. /**
  2172. * This method is used to access an IMAP/POP3/NNTP service.
  2173. *
  2174. * @param $url a string giving the URL of the service, including the mailing box
  2175. * for IMAP URLs, as accepted by imap_open().
  2176. * @param $service a string giving for CAS retrieve Proxy ticket
  2177. * @param $flags options given to imap_open().
  2178. * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
  2179. * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
  2180. * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE.
  2181. * @param $err_msg an error message on failure
  2182. * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL
  2183. * on success, FALSE on error).
  2184. *
  2185. * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code
  2186. * gives the reason why it failed and $err_msg contains an error message).
  2187. *
  2188. * @public
  2189. */
  2190. function serviceMail($url,$service,$flags,&$err_code,&$err_msg,&$pt)
  2191. {
  2192. phpCAS::traceBegin();
  2193. // at first retrieve a PT
  2194. $pt = $this->retrievePT($service,$err_code,$output);
  2195. $stream = FALSE;
  2196. // test if PT was retrieved correctly
  2197. if ( !$pt ) {
  2198. // note: $err_code and $err_msg are filled by CASClient::retrievePT()
  2199. phpCAS::trace('PT was not retrieved correctly');
  2200. } else {
  2201. phpCAS::trace('opening IMAP URL `'.$url.'\'...');
  2202. $stream = @imap_open($url,$this->getUser(),$pt,$flags);
  2203. if ( !$stream ) {
  2204. phpCAS::trace('could not open URL');
  2205. $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
  2206. // give an error message
  2207. $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
  2208. $service_url,
  2209. var_export(imap_errors(),TRUE));
  2210. $pt = FALSE;
  2211. $stream = FALSE;
  2212. } else {
  2213. phpCAS::trace('ok');
  2214. }
  2215. }
  2216. phpCAS::traceEnd($stream);
  2217. return $stream;
  2218. }
  2219. /** @} */
  2220. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2221. // XX XX
  2222. // XX PROXIED CLIENT FEATURES (CAS 2.0) XX
  2223. // XX XX
  2224. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2225. // ########################################################################
  2226. // PT
  2227. // ########################################################################
  2228. /**
  2229. * @addtogroup internalProxied
  2230. * @{
  2231. */
  2232. /**
  2233. * the Proxy Ticket provided in the URL of the request if present
  2234. * (empty otherwise). Written by CASClient::CASClient(), read by
  2235. * CASClient::getPT() and CASClient::hasPGT().
  2236. *
  2237. * @hideinitializer
  2238. * @private
  2239. */
  2240. var $_pt = '';
  2241. /**
  2242. * This method returns the Proxy Ticket provided in the URL of the request.
  2243. * @return The proxy ticket.
  2244. * @private
  2245. */
  2246. function getPT()
  2247. {
  2248. // return 'ST'.substr($this->_pt, 2);
  2249. return $this->_pt;
  2250. }
  2251. /**
  2252. * This method stores the Proxy Ticket.
  2253. * @param $pt The Proxy Ticket.
  2254. * @private
  2255. */
  2256. function setPT($pt)
  2257. { $this->_pt = $pt; }
  2258. /**
  2259. * This method tells if a Proxy Ticket was stored.
  2260. * @return TRUE if a Proxy Ticket has been stored.
  2261. * @private
  2262. */
  2263. function hasPT()
  2264. { return !empty($this->_pt); }
  2265. /**
  2266. * This method returns the SAML Ticket provided in the URL of the request.
  2267. * @return The SAML ticket.
  2268. * @private
  2269. */
  2270. function getSA()
  2271. { return 'ST'.substr($this->_sa, 2); }
  2272. /**
  2273. * This method stores the SAML Ticket.
  2274. * @param $sa The SAML Ticket.
  2275. * @private
  2276. */
  2277. function setSA($sa)
  2278. { $this->_sa = $sa; }
  2279. /**
  2280. * This method tells if a SAML Ticket was stored.
  2281. * @return TRUE if a SAML Ticket has been stored.
  2282. * @private
  2283. */
  2284. function hasSA()
  2285. { return !empty($this->_sa); }
  2286. /** @} */
  2287. // ########################################################################
  2288. // PT VALIDATION
  2289. // ########################################################################
  2290. /**
  2291. * @addtogroup internalProxied
  2292. * @{
  2293. */
  2294. /**
  2295. * This method is used to validate a ST or PT; halt on failure
  2296. * Used for all CAS 2.0 validations
  2297. * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
  2298. *
  2299. * @private
  2300. */
  2301. function validatePT(&$validate_url,&$text_response,&$tree_response)
  2302. {
  2303. phpCAS::traceBegin();
  2304. // build the URL to validate the ticket
  2305. $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
  2306. if ( $this->isProxy() ) {
  2307. // pass the callback url for CAS proxies
  2308. $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
  2309. }
  2310. // open and read the URL
  2311. if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
  2312. phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
  2313. $this->authError('PT not validated',
  2314. $validate_url,
  2315. TRUE/*$no_response*/);
  2316. }
  2317. // read the response of the CAS server into a DOM object
  2318. if ( !($dom = domxml_open_mem($text_response))) {
  2319. // read failed
  2320. $this->authError('PT not validated',
  2321. $validate_url,
  2322. FALSE/*$no_response*/,
  2323. TRUE/*$bad_response*/,
  2324. $text_response);
  2325. }
  2326. // read the root node of the XML tree
  2327. if ( !($tree_response = $dom->document_element()) ) {
  2328. // read failed
  2329. $this->authError('PT not validated',
  2330. $validate_url,
  2331. FALSE/*$no_response*/,
  2332. TRUE/*$bad_response*/,
  2333. $text_response);
  2334. }
  2335. // insure that tag name is 'serviceResponse'
  2336. if ( $tree_response->node_name() != 'serviceResponse' ) {
  2337. // bad root node
  2338. $this->authError('PT not validated',
  2339. $validate_url,
  2340. FALSE/*$no_response*/,
  2341. TRUE/*$bad_response*/,
  2342. $text_response);
  2343. }
  2344. if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
  2345. // authentication succeded, extract the user name
  2346. if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) {
  2347. // no user specified => error
  2348. $this->authError('PT not validated',
  2349. $validate_url,
  2350. FALSE/*$no_response*/,
  2351. TRUE/*$bad_response*/,
  2352. $text_response);
  2353. }
  2354. $this->setUser(trim($arr[0]->get_content()));
  2355. } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
  2356. // authentication succeded, extract the error code and message
  2357. $this->authError('PT not validated',
  2358. $validate_url,
  2359. FALSE/*$no_response*/,
  2360. FALSE/*$bad_response*/,
  2361. $text_response,
  2362. $arr[0]->get_attribute('code')/*$err_code*/,
  2363. trim($arr[0]->get_content())/*$err_msg*/);
  2364. } else {
  2365. $this->authError('PT not validated',
  2366. $validate_url,
  2367. FALSE/*$no_response*/,
  2368. TRUE/*$bad_response*/,
  2369. $text_response);
  2370. }
  2371. $this->renameSession($this->getPT());
  2372. // at this step, PT has been validated and $this->_user has been set,
  2373. phpCAS::traceEnd(TRUE);
  2374. return TRUE;
  2375. }
  2376. /** @} */
  2377. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2378. // XX XX
  2379. // XX MISC XX
  2380. // XX XX
  2381. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2382. /**
  2383. * @addtogroup internalMisc
  2384. * @{
  2385. */
  2386. // ########################################################################
  2387. // URL
  2388. // ########################################################################
  2389. /**
  2390. * the URL of the current request (without any ticket CGI parameter). Written
  2391. * and read by CASClient::getURL().
  2392. *
  2393. * @hideinitializer
  2394. * @private
  2395. */
  2396. var $_url = '';
  2397. /**
  2398. * This method returns the URL of the current request (without any ticket
  2399. * CGI parameter).
  2400. *
  2401. * @return The URL
  2402. *
  2403. * @private
  2404. */
  2405. function getURL()
  2406. {
  2407. phpCAS::traceBegin();
  2408. // the URL is built when needed only
  2409. if ( empty($this->_url) ) {
  2410. $final_uri = '';
  2411. // remove the ticket if present in the URL
  2412. $final_uri = ($this->isHttps()) ? 'https' : 'http';
  2413. $final_uri .= '://';
  2414. /* replaced by Julien Marchal - v0.4.6
  2415. * $this->_url .= $_SERVER['SERVER_NAME'];
  2416. */
  2417. if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
  2418. /* replaced by teedog - v0.4.12
  2419. * $this->_url .= $_SERVER['SERVER_NAME'];
  2420. */
  2421. if (empty($_SERVER['SERVER_NAME'])) {
  2422. $server_name = $_SERVER['HTTP_HOST'];
  2423. } else {
  2424. $server_name = $_SERVER['SERVER_NAME'];
  2425. }
  2426. } else {
  2427. $server_name = $_SERVER['HTTP_X_FORWARDED_SERVER'];
  2428. }
  2429. $final_uri .= $server_name;
  2430. if (!strpos($server_name, ':')) {
  2431. if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
  2432. || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
  2433. $final_uri .= ':';
  2434. $final_uri .= $_SERVER['SERVER_PORT'];
  2435. }
  2436. }
  2437. $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
  2438. $final_uri .= $request_uri[0];
  2439. if (isset($request_uri[1]) && $request_uri[1])
  2440. {
  2441. $query_string = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
  2442. // If the query string still has anything left, append it to the final URI
  2443. if ($query_string !== '')
  2444. $final_uri .= "?$query_string";
  2445. }
  2446. phpCAS::trace("Final URI: $final_uri");
  2447. $this->setURL($final_uri);
  2448. }
  2449. phpCAS::traceEnd($this->_url);
  2450. return $this->_url;
  2451. }
  2452. /**
  2453. * Removes a parameter from a query string
  2454. *
  2455. * @param string $parameterName
  2456. * @param string $queryString
  2457. * @return string
  2458. *
  2459. * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
  2460. */
  2461. function removeParameterFromQueryString($parameterName, $queryString)
  2462. {
  2463. $parameterName = preg_quote($parameterName);
  2464. return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
  2465. }
  2466. /**
  2467. * This method sets the URL of the current request
  2468. *
  2469. * @param $url url to set for service
  2470. *
  2471. * @private
  2472. */
  2473. function setURL($url)
  2474. {
  2475. $this->_url = $url;
  2476. }
  2477. // ########################################################################
  2478. // AUTHENTICATION ERROR HANDLING
  2479. // ########################################################################
  2480. /**
  2481. * This method is used to print the HTML output when the user was not authenticated.
  2482. *
  2483. * @param $failure the failure that occured
  2484. * @param $cas_url the URL the CAS server was asked for
  2485. * @param $no_response the response from the CAS server (other
  2486. * parameters are ignored if TRUE)
  2487. * @param $bad_response bad response from the CAS server ($err_code
  2488. * and $err_msg ignored if TRUE)
  2489. * @param $cas_response the response of the CAS server
  2490. * @param $err_code the error code given by the CAS server
  2491. * @param $err_msg the error message given by the CAS server
  2492. *
  2493. * @private
  2494. */
  2495. function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
  2496. {
  2497. phpCAS::traceBegin();
  2498. $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
  2499. printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
  2500. phpCAS::trace('CAS URL: '.$cas_url);
  2501. phpCAS::trace('Authentication failure: '.$failure);
  2502. if ( $no_response ) {
  2503. phpCAS::trace('Reason: no response from the CAS server');
  2504. } else {
  2505. if ( $bad_response ) {
  2506. phpCAS::trace('Reason: bad response from the CAS server');
  2507. } else {
  2508. switch ($this->getServerVersion()) {
  2509. case CAS_VERSION_1_0:
  2510. phpCAS::trace('Reason: CAS error');
  2511. break;
  2512. case CAS_VERSION_2_0:
  2513. if ( empty($err_code) )
  2514. phpCAS::trace('Reason: no CAS error');
  2515. else
  2516. phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
  2517. break;
  2518. }
  2519. }
  2520. phpCAS::trace('CAS response: '.$cas_response);
  2521. }
  2522. $this->printHTMLFooter();
  2523. phpCAS::traceExit();
  2524. exit();
  2525. }
  2526. /** @} */
  2527. }
  2528. ?>