client.php 98 KB

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