PageRenderTime 61ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/common/libraries/plugin/pear/CAS/client.php

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