client.php 83 KB

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