Client.php 136 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948
  1. <?php
  2. /**
  3. * Licensed to Jasig under one or more contributor license
  4. * agreements. See the NOTICE file distributed with this work for
  5. * additional information regarding copyright ownership.
  6. *
  7. * Jasig licenses this file to you under the Apache License,
  8. * Version 2.0 (the "License"); you may not use this file except in
  9. * compliance with the License. You may obtain a copy of the License at:
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * PHP Version 5
  20. *
  21. * @file CAS/Client.php
  22. * @category Authentication
  23. * @package PhpCAS
  24. * @author Pascal Aubry <pascal.aubry@univ-rennes1.fr>
  25. * @author Olivier Berger <olivier.berger@it-sudparis.eu>
  26. * @author Brett Bieber <brett.bieber@gmail.com>
  27. * @author Joachim Fritschi <jfritschi@freenet.de>
  28. * @author Adam Franco <afranco@middlebury.edu>
  29. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
  30. * @link https://wiki.jasig.org/display/CASC/phpCAS
  31. */
  32. /**
  33. * The CAS_Client class is a client interface that provides CAS authentication
  34. * to PHP applications.
  35. *
  36. * @class CAS_Client
  37. * @category Authentication
  38. * @package PhpCAS
  39. * @author Pascal Aubry <pascal.aubry@univ-rennes1.fr>
  40. * @author Olivier Berger <olivier.berger@it-sudparis.eu>
  41. * @author Brett Bieber <brett.bieber@gmail.com>
  42. * @author Joachim Fritschi <jfritschi@freenet.de>
  43. * @author Adam Franco <afranco@middlebury.edu>
  44. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
  45. * @link https://wiki.jasig.org/display/CASC/phpCAS
  46. *
  47. */
  48. class CAS_Client
  49. {
  50. // ########################################################################
  51. // HTML OUTPUT
  52. // ########################################################################
  53. /**
  54. * @addtogroup internalOutput
  55. * @{
  56. */
  57. /**
  58. * This method filters a string by replacing special tokens by appropriate values
  59. * and prints it. The corresponding tokens are taken into account:
  60. * - __CAS_VERSION__
  61. * - __PHPCAS_VERSION__
  62. * - __SERVER_BASE_URL__
  63. *
  64. * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter().
  65. *
  66. * @param string $str the string to filter and output
  67. *
  68. * @return void
  69. */
  70. private function _htmlFilterOutput($str)
  71. {
  72. $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str);
  73. $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str);
  74. $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str);
  75. echo $str;
  76. }
  77. /**
  78. * A string used to print the header of HTML pages. Written by
  79. * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader().
  80. *
  81. * @hideinitializer
  82. * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader()
  83. */
  84. private $_output_header = '';
  85. /**
  86. * This method prints the header of the HTML output (after filtering). If
  87. * CAS_Client::setHTMLHeader() was not used, a default header is output.
  88. *
  89. * @param string $title the title of the page
  90. *
  91. * @return void
  92. * @see _htmlFilterOutput()
  93. */
  94. public function printHTMLHeader($title)
  95. {
  96. $this->_htmlFilterOutput(
  97. str_replace(
  98. '__TITLE__', $title,
  99. (empty($this->_output_header)
  100. ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
  101. : $this->_output_header)
  102. )
  103. );
  104. }
  105. /**
  106. * A string used to print the footer of HTML pages. Written by
  107. * CAS_Client::setHTMLFooter(), read by printHTMLFooter().
  108. *
  109. * @hideinitializer
  110. * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter()
  111. */
  112. private $_output_footer = '';
  113. /**
  114. * This method prints the footer of the HTML output (after filtering). If
  115. * CAS_Client::setHTMLFooter() was not used, a default footer is output.
  116. *
  117. * @return void
  118. * @see _htmlFilterOutput()
  119. */
  120. public function printHTMLFooter()
  121. {
  122. $lang = $this->getLangObj();
  123. $this->_htmlFilterOutput(
  124. empty($this->_output_footer)?
  125. (phpCAS::getVerbose())?
  126. '<hr><address>phpCAS __PHPCAS_VERSION__ '
  127. .$lang->getUsingServer()
  128. .' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>'
  129. :'</body></html>'
  130. :$this->_output_footer
  131. );
  132. }
  133. /**
  134. * This method set the HTML header used for all outputs.
  135. *
  136. * @param string $header the HTML header.
  137. *
  138. * @return void
  139. */
  140. public function setHTMLHeader($header)
  141. {
  142. // Argument Validation
  143. if (gettype($header) != 'string')
  144. throw new CAS_TypeMismatchException($header, '$header', 'string');
  145. $this->_output_header = $header;
  146. }
  147. /**
  148. * This method set the HTML footer used for all outputs.
  149. *
  150. * @param string $footer the HTML footer.
  151. *
  152. * @return void
  153. */
  154. public function setHTMLFooter($footer)
  155. {
  156. // Argument Validation
  157. if (gettype($footer) != 'string')
  158. throw new CAS_TypeMismatchException($footer, '$footer', 'string');
  159. $this->_output_footer = $footer;
  160. }
  161. /** @} */
  162. // ########################################################################
  163. // INTERNATIONALIZATION
  164. // ########################################################################
  165. /**
  166. * @addtogroup internalLang
  167. * @{
  168. */
  169. /**
  170. * A string corresponding to the language used by phpCAS. Written by
  171. * CAS_Client::setLang(), read by CAS_Client::getLang().
  172. * @note debugging information is always in english (debug purposes only).
  173. */
  174. private $_lang = PHPCAS_LANG_DEFAULT;
  175. /**
  176. * This method is used to set the language used by phpCAS.
  177. *
  178. * @param string $lang representing the language.
  179. *
  180. * @return void
  181. */
  182. public function setLang($lang)
  183. {
  184. // Argument Validation
  185. if (gettype($lang) != 'string')
  186. throw new CAS_TypeMismatchException($lang, '$lang', 'string');
  187. phpCAS::traceBegin();
  188. $obj = new $lang();
  189. if (!($obj instanceof CAS_Languages_LanguageInterface)) {
  190. throw new CAS_InvalidArgumentException(
  191. '$className must implement the CAS_Languages_LanguageInterface'
  192. );
  193. }
  194. $this->_lang = $lang;
  195. phpCAS::traceEnd();
  196. }
  197. /**
  198. * Create the language
  199. *
  200. * @return CAS_Languages_LanguageInterface object implementing the class
  201. */
  202. public function getLangObj()
  203. {
  204. $classname = $this->_lang;
  205. return new $classname();
  206. }
  207. /** @} */
  208. // ########################################################################
  209. // CAS SERVER CONFIG
  210. // ########################################################################
  211. /**
  212. * @addtogroup internalConfig
  213. * @{
  214. */
  215. /**
  216. * a record to store information about the CAS server.
  217. * - $_server['version']: the version of the CAS server
  218. * - $_server['hostname']: the hostname of the CAS server
  219. * - $_server['port']: the port the CAS server is running on
  220. * - $_server['uri']: the base URI the CAS server is responding on
  221. * - $_server['base_url']: the base URL of the CAS server
  222. * - $_server['login_url']: the login URL of the CAS server
  223. * - $_server['service_validate_url']: the service validating URL of the
  224. * CAS server
  225. * - $_server['proxy_url']: the proxy URL of the CAS server
  226. * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server
  227. * - $_server['logout_url']: the logout URL of the CAS server
  228. *
  229. * $_server['version'], $_server['hostname'], $_server['port'] and
  230. * $_server['uri'] are written by CAS_Client::CAS_Client(), read by
  231. * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(),
  232. * CAS_Client::_getServerPort() and CAS_Client::_getServerURI().
  233. *
  234. * The other fields are written and read by CAS_Client::_getServerBaseURL(),
  235. * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(),
  236. * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL().
  237. *
  238. * @hideinitializer
  239. */
  240. private $_server = array(
  241. 'version' => '',
  242. 'hostname' => 'none',
  243. 'port' => -1,
  244. 'uri' => 'none');
  245. /**
  246. * This method is used to retrieve the version of the CAS server.
  247. *
  248. * @return string the version of the CAS server.
  249. */
  250. public function getServerVersion()
  251. {
  252. return $this->_server['version'];
  253. }
  254. /**
  255. * This method is used to retrieve the hostname of the CAS server.
  256. *
  257. * @return string the hostname of the CAS server.
  258. */
  259. private function _getServerHostname()
  260. {
  261. return $this->_server['hostname'];
  262. }
  263. /**
  264. * This method is used to retrieve the port of the CAS server.
  265. *
  266. * @return int the port of the CAS server.
  267. */
  268. private function _getServerPort()
  269. {
  270. return $this->_server['port'];
  271. }
  272. /**
  273. * This method is used to retrieve the URI of the CAS server.
  274. *
  275. * @return string a URI.
  276. */
  277. private function _getServerURI()
  278. {
  279. return $this->_server['uri'];
  280. }
  281. /**
  282. * This method is used to retrieve the base URL of the CAS server.
  283. *
  284. * @return string a URL.
  285. */
  286. private function _getServerBaseURL()
  287. {
  288. // the URL is build only when needed
  289. if ( empty($this->_server['base_url']) ) {
  290. $this->_server['base_url'] = 'https://' . $this->_getServerHostname();
  291. if ($this->_getServerPort()!=443) {
  292. $this->_server['base_url'] .= ':'
  293. .$this->_getServerPort();
  294. }
  295. $this->_server['base_url'] .= $this->_getServerURI();
  296. }
  297. return $this->_server['base_url'];
  298. }
  299. /**
  300. * This method is used to retrieve the login URL of the CAS server.
  301. *
  302. * @param bool $gateway true to check authentication, false to force it
  303. * @param bool $renew true to force the authentication with the CAS server
  304. *
  305. * @return string a URL.
  306. * @note It is recommended that CAS implementations ignore the "gateway"
  307. * parameter if "renew" is set
  308. */
  309. public function getServerLoginURL($gateway=false,$renew=false)
  310. {
  311. phpCAS::traceBegin();
  312. // the URL is build only when needed
  313. if ( empty($this->_server['login_url']) ) {
  314. $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL()));
  315. }
  316. $url = $this->_server['login_url'];
  317. if ($renew) {
  318. // It is recommended that when the "renew" parameter is set, its
  319. // value be "true"
  320. $url = $this->_buildQueryUrl($url, 'renew=true');
  321. } elseif ($gateway) {
  322. // It is recommended that when the "gateway" parameter is set, its
  323. // value be "true"
  324. $url = $this->_buildQueryUrl($url, 'gateway=true');
  325. }
  326. phpCAS::traceEnd($url);
  327. return $url;
  328. }
  329. /**
  330. * This method sets the login URL of the CAS server.
  331. *
  332. * @param string $url the login URL
  333. *
  334. * @return string login url
  335. */
  336. public function setServerLoginURL($url)
  337. {
  338. // Argument Validation
  339. if (gettype($url) != 'string')
  340. throw new CAS_TypeMismatchException($url, '$url', 'string');
  341. return $this->_server['login_url'] = $url;
  342. }
  343. /**
  344. * This method sets the serviceValidate URL of the CAS server.
  345. *
  346. * @param string $url the serviceValidate URL
  347. *
  348. * @return string serviceValidate URL
  349. */
  350. public function setServerServiceValidateURL($url)
  351. {
  352. // Argument Validation
  353. if (gettype($url) != 'string')
  354. throw new CAS_TypeMismatchException($url, '$url', 'string');
  355. return $this->_server['service_validate_url'] = $url;
  356. }
  357. /**
  358. * This method sets the proxyValidate URL of the CAS server.
  359. *
  360. * @param string $url the proxyValidate URL
  361. *
  362. * @return string proxyValidate URL
  363. */
  364. public function setServerProxyValidateURL($url)
  365. {
  366. // Argument Validation
  367. if (gettype($url) != 'string')
  368. throw new CAS_TypeMismatchException($url, '$url', 'string');
  369. return $this->_server['proxy_validate_url'] = $url;
  370. }
  371. /**
  372. * This method sets the samlValidate URL of the CAS server.
  373. *
  374. * @param string $url the samlValidate URL
  375. *
  376. * @return string samlValidate URL
  377. */
  378. public function setServerSamlValidateURL($url)
  379. {
  380. // Argument Validation
  381. if (gettype($url) != 'string')
  382. throw new CAS_TypeMismatchException($url, '$url', 'string');
  383. return $this->_server['saml_validate_url'] = $url;
  384. }
  385. /**
  386. * This method is used to retrieve the service validating URL of the CAS server.
  387. *
  388. * @return string serviceValidate URL.
  389. */
  390. public function getServerServiceValidateURL()
  391. {
  392. phpCAS::traceBegin();
  393. // the URL is build only when needed
  394. if ( empty($this->_server['service_validate_url']) ) {
  395. switch ($this->getServerVersion()) {
  396. case CAS_VERSION_1_0:
  397. $this->_server['service_validate_url'] = $this->_getServerBaseURL()
  398. .'validate';
  399. break;
  400. case CAS_VERSION_2_0:
  401. $this->_server['service_validate_url'] = $this->_getServerBaseURL()
  402. .'serviceValidate';
  403. break;
  404. case CAS_VERSION_3_0:
  405. $this->_server['service_validate_url'] = $this->_getServerBaseURL()
  406. .'p3/serviceValidate';
  407. break;
  408. }
  409. }
  410. $url = $this->_buildQueryUrl(
  411. $this->_server['service_validate_url'],
  412. 'service='.urlencode($this->getURL())
  413. );
  414. phpCAS::traceEnd($url);
  415. return $url;
  416. }
  417. /**
  418. * This method is used to retrieve the SAML validating URL of the CAS server.
  419. *
  420. * @return string samlValidate URL.
  421. */
  422. public function getServerSamlValidateURL()
  423. {
  424. phpCAS::traceBegin();
  425. // the URL is build only when needed
  426. if ( empty($this->_server['saml_validate_url']) ) {
  427. switch ($this->getServerVersion()) {
  428. case SAML_VERSION_1_1:
  429. $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate';
  430. break;
  431. }
  432. }
  433. $url = $this->_buildQueryUrl(
  434. $this->_server['saml_validate_url'],
  435. 'TARGET='.urlencode($this->getURL())
  436. );
  437. phpCAS::traceEnd($url);
  438. return $url;
  439. }
  440. /**
  441. * This method is used to retrieve the proxy validating URL of the CAS server.
  442. *
  443. * @return string proxyValidate URL.
  444. */
  445. public function getServerProxyValidateURL()
  446. {
  447. phpCAS::traceBegin();
  448. // the URL is build only when needed
  449. if ( empty($this->_server['proxy_validate_url']) ) {
  450. switch ($this->getServerVersion()) {
  451. case CAS_VERSION_1_0:
  452. $this->_server['proxy_validate_url'] = '';
  453. break;
  454. case CAS_VERSION_2_0:
  455. $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate';
  456. break;
  457. case CAS_VERSION_3_0:
  458. $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate';
  459. break;
  460. }
  461. }
  462. $url = $this->_buildQueryUrl(
  463. $this->_server['proxy_validate_url'],
  464. 'service='.urlencode($this->getURL())
  465. );
  466. phpCAS::traceEnd($url);
  467. return $url;
  468. }
  469. /**
  470. * This method is used to retrieve the proxy URL of the CAS server.
  471. *
  472. * @return string proxy URL.
  473. */
  474. public function getServerProxyURL()
  475. {
  476. // the URL is build only when needed
  477. if ( empty($this->_server['proxy_url']) ) {
  478. switch ($this->getServerVersion()) {
  479. case CAS_VERSION_1_0:
  480. $this->_server['proxy_url'] = '';
  481. break;
  482. case CAS_VERSION_2_0:
  483. case CAS_VERSION_3_0:
  484. $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy';
  485. break;
  486. }
  487. }
  488. return $this->_server['proxy_url'];
  489. }
  490. /**
  491. * This method is used to retrieve the logout URL of the CAS server.
  492. *
  493. * @return string logout URL.
  494. */
  495. public function getServerLogoutURL()
  496. {
  497. // the URL is build only when needed
  498. if ( empty($this->_server['logout_url']) ) {
  499. $this->_server['logout_url'] = $this->_getServerBaseURL().'logout';
  500. }
  501. return $this->_server['logout_url'];
  502. }
  503. /**
  504. * This method sets the logout URL of the CAS server.
  505. *
  506. * @param string $url the logout URL
  507. *
  508. * @return string logout url
  509. */
  510. public function setServerLogoutURL($url)
  511. {
  512. // Argument Validation
  513. if (gettype($url) != 'string')
  514. throw new CAS_TypeMismatchException($url, '$url', 'string');
  515. return $this->_server['logout_url'] = $url;
  516. }
  517. /**
  518. * An array to store extra curl options.
  519. */
  520. private $_curl_options = array();
  521. /**
  522. * This method is used to set additional user curl options.
  523. *
  524. * @param string $key name of the curl option
  525. * @param string $value value of the curl option
  526. *
  527. * @return void
  528. */
  529. public function setExtraCurlOption($key, $value)
  530. {
  531. $this->_curl_options[$key] = $value;
  532. }
  533. /** @} */
  534. // ########################################################################
  535. // Change the internal behaviour of phpcas
  536. // ########################################################################
  537. /**
  538. * @addtogroup internalBehave
  539. * @{
  540. */
  541. /**
  542. * The class to instantiate for making web requests in readUrl().
  543. * The class specified must implement the CAS_Request_RequestInterface.
  544. * By default CAS_Request_CurlRequest is used, but this may be overridden to
  545. * supply alternate request mechanisms for testing.
  546. */
  547. private $_requestImplementation = 'CAS_Request_CurlRequest';
  548. /**
  549. * Override the default implementation used to make web requests in readUrl().
  550. * This class must implement the CAS_Request_RequestInterface.
  551. *
  552. * @param string $className name of the RequestImplementation class
  553. *
  554. * @return void
  555. */
  556. public function setRequestImplementation ($className)
  557. {
  558. $obj = new $className;
  559. if (!($obj instanceof CAS_Request_RequestInterface)) {
  560. throw new CAS_InvalidArgumentException(
  561. '$className must implement the CAS_Request_RequestInterface'
  562. );
  563. }
  564. $this->_requestImplementation = $className;
  565. }
  566. /**
  567. * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session
  568. * tickets from the URL after a successful authentication.
  569. */
  570. private $_clearTicketsFromUrl = true;
  571. /**
  572. * Configure the client to not send redirect headers and call exit() on
  573. * authentication success. The normal redirect is used to remove the service
  574. * ticket from the client's URL, but for running unit tests we need to
  575. * continue without exiting.
  576. *
  577. * Needed for testing authentication
  578. *
  579. * @return void
  580. */
  581. public function setNoClearTicketsFromUrl ()
  582. {
  583. $this->_clearTicketsFromUrl = false;
  584. }
  585. /**
  586. * @var callback $_attributeParserCallbackFunction;
  587. */
  588. private $_casAttributeParserCallbackFunction = null;
  589. /**
  590. * @var array $_attributeParserCallbackArgs;
  591. */
  592. private $_casAttributeParserCallbackArgs = array();
  593. /**
  594. * Set a callback function to be run when parsing CAS attributes
  595. *
  596. * The callback function will be passed a XMLNode as its first parameter,
  597. * followed by any $additionalArgs you pass.
  598. *
  599. * @param string $function callback function to call
  600. * @param array $additionalArgs optional array of arguments
  601. *
  602. * @return void
  603. */
  604. public function setCasAttributeParserCallback($function, array $additionalArgs = array())
  605. {
  606. $this->_casAttributeParserCallbackFunction = $function;
  607. $this->_casAttributeParserCallbackArgs = $additionalArgs;
  608. }
  609. /** @var callable $_postAuthenticateCallbackFunction;
  610. */
  611. private $_postAuthenticateCallbackFunction = null;
  612. /**
  613. * @var array $_postAuthenticateCallbackArgs;
  614. */
  615. private $_postAuthenticateCallbackArgs = array();
  616. /**
  617. * Set a callback function to be run when a user authenticates.
  618. *
  619. * The callback function will be passed a $logoutTicket as its first parameter,
  620. * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
  621. * opaque string that can be used to map a session-id to the logout request
  622. * in order to support single-signout in applications that manage their own
  623. * sessions (rather than letting phpCAS start the session).
  624. *
  625. * phpCAS::forceAuthentication() will always exit and forward client unless
  626. * they are already authenticated. To perform an action at the moment the user
  627. * logs in (such as registering an account, performing logging, etc), register
  628. * a callback function here.
  629. *
  630. * @param callable $function callback function to call
  631. * @param array $additionalArgs optional array of arguments
  632. *
  633. * @return void
  634. */
  635. public function setPostAuthenticateCallback ($function, array $additionalArgs = array())
  636. {
  637. $this->_postAuthenticateCallbackFunction = $function;
  638. $this->_postAuthenticateCallbackArgs = $additionalArgs;
  639. }
  640. /**
  641. * @var callable $_signoutCallbackFunction;
  642. */
  643. private $_signoutCallbackFunction = null;
  644. /**
  645. * @var array $_signoutCallbackArgs;
  646. */
  647. private $_signoutCallbackArgs = array();
  648. /**
  649. * Set a callback function to be run when a single-signout request is received.
  650. *
  651. * The callback function will be passed a $logoutTicket as its first parameter,
  652. * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
  653. * opaque string that can be used to map a session-id to the logout request in
  654. * order to support single-signout in applications that manage their own sessions
  655. * (rather than letting phpCAS start and destroy the session).
  656. *
  657. * @param callable $function callback function to call
  658. * @param array $additionalArgs optional array of arguments
  659. *
  660. * @return void
  661. */
  662. public function setSingleSignoutCallback ($function, array $additionalArgs = array())
  663. {
  664. $this->_signoutCallbackFunction = $function;
  665. $this->_signoutCallbackArgs = $additionalArgs;
  666. }
  667. // ########################################################################
  668. // Methods for supplying code-flow feedback to integrators.
  669. // ########################################################################
  670. /**
  671. * Ensure that this is actually a proxy object or fail with an exception
  672. *
  673. * @throws CAS_OutOfSequenceBeforeProxyException
  674. *
  675. * @return void
  676. */
  677. public function ensureIsProxy()
  678. {
  679. if (!$this->isProxy()) {
  680. throw new CAS_OutOfSequenceBeforeProxyException();
  681. }
  682. }
  683. /**
  684. * Mark the caller of authentication. This will help client integraters determine
  685. * problems with their code flow if they call a function such as getUser() before
  686. * authentication has occurred.
  687. *
  688. * @param bool $auth True if authentication was successful, false otherwise.
  689. *
  690. * @return null
  691. */
  692. public function markAuthenticationCall ($auth)
  693. {
  694. // store where the authentication has been checked and the result
  695. $dbg = debug_backtrace();
  696. $this->_authentication_caller = array (
  697. 'file' => $dbg[1]['file'],
  698. 'line' => $dbg[1]['line'],
  699. 'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
  700. 'result' => (boolean)$auth
  701. );
  702. }
  703. private $_authentication_caller;
  704. /**
  705. * Answer true if authentication has been checked.
  706. *
  707. * @return bool
  708. */
  709. public function wasAuthenticationCalled ()
  710. {
  711. return !empty($this->_authentication_caller);
  712. }
  713. /**
  714. * Ensure that authentication was checked. Terminate with exception if no
  715. * authentication was performed
  716. *
  717. * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
  718. *
  719. * @return void
  720. */
  721. private function _ensureAuthenticationCalled()
  722. {
  723. if (!$this->wasAuthenticationCalled()) {
  724. throw new CAS_OutOfSequenceBeforeAuthenticationCallException();
  725. }
  726. }
  727. /**
  728. * Answer the result of the authentication call.
  729. *
  730. * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
  731. * and markAuthenticationCall() didn't happen.
  732. *
  733. * @return bool
  734. */
  735. public function wasAuthenticationCallSuccessful ()
  736. {
  737. $this->_ensureAuthenticationCalled();
  738. return $this->_authentication_caller['result'];
  739. }
  740. /**
  741. * Ensure that authentication was checked. Terminate with exception if no
  742. * authentication was performed
  743. *
  744. * @throws CAS_OutOfSequenceException
  745. *
  746. * @return void
  747. */
  748. public function ensureAuthenticationCallSuccessful()
  749. {
  750. $this->_ensureAuthenticationCalled();
  751. if (!$this->_authentication_caller['result']) {
  752. throw new CAS_OutOfSequenceException(
  753. 'authentication was checked (by '
  754. . $this->getAuthenticationCallerMethod()
  755. . '() at ' . $this->getAuthenticationCallerFile()
  756. . ':' . $this->getAuthenticationCallerLine()
  757. . ') but the method returned false'
  758. );
  759. }
  760. }
  761. /**
  762. * Answer information about the authentication caller.
  763. *
  764. * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
  765. * and markAuthenticationCall() didn't happen.
  766. *
  767. * @return string the file that called authentication
  768. */
  769. public function getAuthenticationCallerFile ()
  770. {
  771. $this->_ensureAuthenticationCalled();
  772. return $this->_authentication_caller['file'];
  773. }
  774. /**
  775. * Answer information about the authentication caller.
  776. *
  777. * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
  778. * and markAuthenticationCall() didn't happen.
  779. *
  780. * @return int the line that called authentication
  781. */
  782. public function getAuthenticationCallerLine ()
  783. {
  784. $this->_ensureAuthenticationCalled();
  785. return $this->_authentication_caller['line'];
  786. }
  787. /**
  788. * Answer information about the authentication caller.
  789. *
  790. * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
  791. * and markAuthenticationCall() didn't happen.
  792. *
  793. * @return string the method that called authentication
  794. */
  795. public function getAuthenticationCallerMethod ()
  796. {
  797. $this->_ensureAuthenticationCalled();
  798. return $this->_authentication_caller['method'];
  799. }
  800. /** @} */
  801. // ########################################################################
  802. // CONSTRUCTOR
  803. // ########################################################################
  804. /**
  805. * @addtogroup internalConfig
  806. * @{
  807. */
  808. /**
  809. * CAS_Client constructor.
  810. *
  811. * @param string $server_version the version of the CAS server
  812. * @param bool $proxy true if the CAS client is a CAS proxy
  813. * @param string $server_hostname the hostname of the CAS server
  814. * @param int $server_port the port the CAS server is running on
  815. * @param string $server_uri the URI the CAS server is responding on
  816. * @param bool $changeSessionID Allow phpCAS to change the session_id
  817. * (Single Sign Out/handleLogoutRequests
  818. * is based on that change)
  819. *
  820. * @return self a newly created CAS_Client object
  821. */
  822. public function __construct(
  823. $server_version,
  824. $proxy,
  825. $server_hostname,
  826. $server_port,
  827. $server_uri,
  828. $changeSessionID = true
  829. ) {
  830. // Argument validation
  831. if (gettype($server_version) != 'string')
  832. throw new CAS_TypeMismatchException($server_version, '$server_version', 'string');
  833. if (gettype($proxy) != 'boolean')
  834. throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean');
  835. if (gettype($server_hostname) != 'string')
  836. throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string');
  837. if (gettype($server_port) != 'integer')
  838. throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer');
  839. if (gettype($server_uri) != 'string')
  840. throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string');
  841. if (gettype($changeSessionID) != 'boolean')
  842. throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean');
  843. phpCAS::traceBegin();
  844. // true : allow to change the session_id(), false session_id won't be
  845. // change and logout won't be handle because of that
  846. $this->_setChangeSessionID($changeSessionID);
  847. // skip Session Handling for logout requests and if don't want it'
  848. if (session_id()=="" && !$this->_isLogoutRequest()) {
  849. session_start();
  850. phpCAS :: trace("Starting a new session " . session_id());
  851. }
  852. // Only for debug purposes
  853. if ($this->isSessionAuthenticated()){
  854. phpCAS :: trace("Session is authenticated as: " . $_SESSION['phpCAS']['user']);
  855. } else {
  856. phpCAS :: trace("Session is not authenticated");
  857. }
  858. // are we in proxy mode ?
  859. $this->_proxy = $proxy;
  860. // Make cookie handling available.
  861. if ($this->isProxy()) {
  862. if (!isset($_SESSION['phpCAS'])) {
  863. $_SESSION['phpCAS'] = array();
  864. }
  865. if (!isset($_SESSION['phpCAS']['service_cookies'])) {
  866. $_SESSION['phpCAS']['service_cookies'] = array();
  867. }
  868. $this->_serviceCookieJar = new CAS_CookieJar(
  869. $_SESSION['phpCAS']['service_cookies']
  870. );
  871. }
  872. // check version
  873. $supportedProtocols = phpCAS::getSupportedProtocols();
  874. if (isset($supportedProtocols[$server_version]) === false) {
  875. phpCAS::error(
  876. 'this version of CAS (`'.$server_version
  877. .'\') is not supported by phpCAS '.phpCAS::getVersion()
  878. );
  879. }
  880. if ($server_version === CAS_VERSION_1_0 && $this->isProxy()) {
  881. phpCAS::error(
  882. 'CAS proxies are not supported in CAS '.$server_version
  883. );
  884. }
  885. $this->_server['version'] = $server_version;
  886. // check hostname
  887. if ( empty($server_hostname)
  888. || !preg_match('/[\.\d\-a-z]*/', $server_hostname)
  889. ) {
  890. phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
  891. }
  892. $this->_server['hostname'] = $server_hostname;
  893. // check port
  894. if ( $server_port == 0
  895. || !is_int($server_port)
  896. ) {
  897. phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
  898. }
  899. $this->_server['port'] = $server_port;
  900. // check URI
  901. if ( !preg_match('/[\.\d\-_a-z\/]*/', $server_uri) ) {
  902. phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
  903. }
  904. // add leading and trailing `/' and remove doubles
  905. if(strstr($server_uri, '?') === false) $server_uri .= '/';
  906. $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri);
  907. $this->_server['uri'] = $server_uri;
  908. // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
  909. if ( $this->isProxy() ) {
  910. $this->_setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
  911. }
  912. if ( $this->_isCallbackMode() ) {
  913. //callback mode: check that phpCAS is secured
  914. if ( !$this->_isHttps() ) {
  915. phpCAS::error(
  916. 'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'
  917. );
  918. }
  919. } else {
  920. //normal mode: get ticket and remove it from CGI parameters for
  921. // developers
  922. $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
  923. if (preg_match('/^[SP]T-/', $ticket) ) {
  924. phpCAS::trace('Ticket \''.$ticket.'\' found');
  925. $this->setTicket($ticket);
  926. unset($_GET['ticket']);
  927. } else if ( !empty($ticket) ) {
  928. //ill-formed ticket, halt
  929. phpCAS::error(
  930. 'ill-formed ticket found in the URL (ticket=`'
  931. .htmlentities($ticket).'\')'
  932. );
  933. }
  934. }
  935. phpCAS::traceEnd();
  936. }
  937. /** @} */
  938. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  939. // XX XX
  940. // XX Session Handling XX
  941. // XX XX
  942. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  943. /**
  944. * @addtogroup internalConfig
  945. * @{
  946. */
  947. /**
  948. * @var bool A variable to whether phpcas will use its own session handling. Default = true
  949. * @hideinitializer
  950. */
  951. private $_change_session_id = true;
  952. /**
  953. * Set a parameter whether to allow phpCAS to change session_id
  954. *
  955. * @param bool $allowed allow phpCAS to change session_id
  956. *
  957. * @return void
  958. */
  959. private function _setChangeSessionID($allowed)
  960. {
  961. $this->_change_session_id = $allowed;
  962. }
  963. /**
  964. * Get whether phpCAS is allowed to change session_id
  965. *
  966. * @return bool
  967. */
  968. public function getChangeSessionID()
  969. {
  970. return $this->_change_session_id;
  971. }
  972. /** @} */
  973. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  974. // XX XX
  975. // XX AUTHENTICATION XX
  976. // XX XX
  977. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  978. /**
  979. * @addtogroup internalAuthentication
  980. * @{
  981. */
  982. /**
  983. * The Authenticated user. Written by CAS_Client::_setUser(), read by
  984. * CAS_Client::getUser().
  985. *
  986. * @hideinitializer
  987. */
  988. private $_user = '';
  989. /**
  990. * This method sets the CAS user's login name.
  991. *
  992. * @param string $user the login name of the authenticated user.
  993. *
  994. * @return void
  995. */
  996. private function _setUser($user)
  997. {
  998. $this->_user = $user;
  999. }
  1000. /**
  1001. * This method returns the CAS user's login name.
  1002. *
  1003. * @return string the login name of the authenticated user
  1004. *
  1005. * @warning should be called only after CAS_Client::forceAuthentication() or
  1006. * CAS_Client::isAuthenticated(), otherwise halt with an error.
  1007. */
  1008. public function getUser()
  1009. {
  1010. // Sequence validation
  1011. $this->ensureAuthenticationCallSuccessful();
  1012. return $this->_getUser();
  1013. }
  1014. /**
  1015. * This method returns the CAS user's login name.
  1016. *
  1017. * @return string the login name of the authenticated user
  1018. *
  1019. * @warning should be called only after CAS_Client::forceAuthentication() or
  1020. * CAS_Client::isAuthenticated(), otherwise halt with an error.
  1021. */
  1022. private function _getUser()
  1023. {
  1024. // This is likely a duplicate check that could be removed....
  1025. if ( empty($this->_user) ) {
  1026. phpCAS::error(
  1027. 'this method should be used only after '.__CLASS__
  1028. .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
  1029. );
  1030. }
  1031. return $this->_user;
  1032. }
  1033. /**
  1034. * The Authenticated users attributes. Written by
  1035. * CAS_Client::setAttributes(), read by CAS_Client::getAttributes().
  1036. * @attention client applications should use phpCAS::getAttributes().
  1037. *
  1038. * @hideinitializer
  1039. */
  1040. private $_attributes = array();
  1041. /**
  1042. * Set an array of attributes
  1043. *
  1044. * @param array $attributes a key value array of attributes
  1045. *
  1046. * @return void
  1047. */
  1048. public function setAttributes($attributes)
  1049. {
  1050. $this->_attributes = $attributes;
  1051. }
  1052. /**
  1053. * Get an key values arry of attributes
  1054. *
  1055. * @return array of attributes
  1056. */
  1057. public function getAttributes()
  1058. {
  1059. // Sequence validation
  1060. $this->ensureAuthenticationCallSuccessful();
  1061. // This is likely a duplicate check that could be removed....
  1062. if ( empty($this->_user) ) {
  1063. // if no user is set, there shouldn't be any attributes also...
  1064. phpCAS::error(
  1065. 'this method should be used only after '.__CLASS__
  1066. .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
  1067. );
  1068. }
  1069. return $this->_attributes;
  1070. }
  1071. /**
  1072. * Check whether attributes are available
  1073. *
  1074. * @return bool attributes available
  1075. */
  1076. public function hasAttributes()
  1077. {
  1078. // Sequence validation
  1079. $this->ensureAuthenticationCallSuccessful();
  1080. return !empty($this->_attributes);
  1081. }
  1082. /**
  1083. * Check whether a specific attribute with a name is available
  1084. *
  1085. * @param string $key name of attribute
  1086. *
  1087. * @return bool is attribute available
  1088. */
  1089. public function hasAttribute($key)
  1090. {
  1091. // Sequence validation
  1092. $this->ensureAuthenticationCallSuccessful();
  1093. return $this->_hasAttribute($key);
  1094. }
  1095. /**
  1096. * Check whether a specific attribute with a name is available
  1097. *
  1098. * @param string $key name of attribute
  1099. *
  1100. * @return bool is attribute available
  1101. */
  1102. private function _hasAttribute($key)
  1103. {
  1104. return (is_array($this->_attributes)
  1105. && array_key_exists($key, $this->_attributes));
  1106. }
  1107. /**
  1108. * Get a specific attribute by name
  1109. *
  1110. * @param string $key name of attribute
  1111. *
  1112. * @return string attribute values
  1113. */
  1114. public function getAttribute($key)
  1115. {
  1116. // Sequence validation
  1117. $this->ensureAuthenticationCallSuccessful();
  1118. if ($this->_hasAttribute($key)) {
  1119. return $this->_attributes[$key];
  1120. }
  1121. }
  1122. /**
  1123. * This method is called to renew the authentication of the user
  1124. * If the user is authenticated, renew the connection
  1125. * If not, redirect to CAS
  1126. *
  1127. * @return bool true when the user is authenticated; otherwise halt.
  1128. */
  1129. public function renewAuthentication()
  1130. {
  1131. phpCAS::traceBegin();
  1132. // Either way, the user is authenticated by CAS
  1133. if (isset( $_SESSION['phpCAS']['auth_checked'])) {
  1134. unset($_SESSION['phpCAS']['auth_checked']);
  1135. }
  1136. if ( $this->isAuthenticated(true) ) {
  1137. phpCAS::trace('user already authenticated');
  1138. $res = true;
  1139. } else {
  1140. $this->redirectToCas(false, true);
  1141. // never reached
  1142. $res = false;
  1143. }
  1144. phpCAS::traceEnd();
  1145. return $res;
  1146. }
  1147. /**
  1148. * This method is called to be sure that the user is authenticated. When not
  1149. * authenticated, halt by redirecting to the CAS server; otherwise return true.
  1150. *
  1151. * @return bool true when the user is authenticated; otherwise halt.
  1152. */
  1153. public function forceAuthentication()
  1154. {
  1155. phpCAS::traceBegin();
  1156. if ( $this->isAuthenticated() ) {
  1157. // the user is authenticated, nothing to be done.
  1158. phpCAS::trace('no need to authenticate');
  1159. $res = true;
  1160. } else {
  1161. // the user is not authenticated, redirect to the CAS server
  1162. if (isset($_SESSION['phpCAS']['auth_checked'])) {
  1163. unset($_SESSION['phpCAS']['auth_checked']);
  1164. }
  1165. $this->redirectToCas(false/* no gateway */);
  1166. // never reached
  1167. $res = false;
  1168. }
  1169. phpCAS::traceEnd($res);
  1170. return $res;
  1171. }
  1172. /**
  1173. * An integer that gives the number of times authentication will be cached
  1174. * before rechecked.
  1175. *
  1176. * @hideinitializer
  1177. */
  1178. private $_cache_times_for_auth_recheck = 0;
  1179. /**
  1180. * Set the number of times authentication will be cached before rechecked.
  1181. *
  1182. * @param int $n number of times to wait for a recheck
  1183. *
  1184. * @return void
  1185. */
  1186. public function setCacheTimesForAuthRecheck($n)
  1187. {
  1188. if (gettype($n) != 'integer')
  1189. throw new CAS_TypeMismatchException($n, '$n', 'string');
  1190. $this->_cache_times_for_auth_recheck = $n;
  1191. }
  1192. /**
  1193. * This method is called to check whether the user is authenticated or not.
  1194. *
  1195. * @return bool true when the user is authenticated, false when a previous
  1196. * gateway login failed or the function will not return if the user is
  1197. * redirected to the cas server for a gateway login attempt
  1198. */
  1199. public function checkAuthentication()
  1200. {
  1201. phpCAS::traceBegin();
  1202. if ( $this->isAuthenticated() ) {
  1203. phpCAS::trace('user is authenticated');
  1204. /* The 'auth_checked' variable is removed just in case it's set. */
  1205. unset($_SESSION['phpCAS']['auth_checked']);
  1206. $res = true;
  1207. } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
  1208. // the previous request has redirected the client to the CAS server
  1209. // with gateway=true
  1210. unset($_SESSION['phpCAS']['auth_checked']);
  1211. $res = false;
  1212. } else {
  1213. // avoid a check against CAS on every request
  1214. if (!isset($_SESSION['phpCAS']['unauth_count'])) {
  1215. $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
  1216. }
  1217. if (($_SESSION['phpCAS']['unauth_count'] != -2
  1218. && $this->_cache_times_for_auth_recheck == -1)
  1219. || ($_SESSION['phpCAS']['unauth_count'] >= 0
  1220. && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)
  1221. ) {
  1222. $res = false;
  1223. if ($this->_cache_times_for_auth_recheck != -1) {
  1224. $_SESSION['phpCAS']['unauth_count']++;
  1225. phpCAS::trace(
  1226. 'user is not authenticated (cached for '
  1227. .$_SESSION['phpCAS']['unauth_count'].' times of '
  1228. .$this->_cache_times_for_auth_recheck.')'
  1229. );
  1230. } else {
  1231. phpCAS::trace(
  1232. 'user is not authenticated (cached for until login pressed)'
  1233. );
  1234. }
  1235. } else {
  1236. $_SESSION['phpCAS']['unauth_count'] = 0;
  1237. $_SESSION['phpCAS']['auth_checked'] = true;
  1238. phpCAS::trace('user is not authenticated (cache reset)');
  1239. $this->redirectToCas(true/* gateway */);
  1240. // never reached
  1241. $res = false;
  1242. }
  1243. }
  1244. phpCAS::traceEnd($res);
  1245. return $res;
  1246. }
  1247. /**
  1248. * This method is called to check if the user is authenticated (previously or by
  1249. * tickets given in the URL).
  1250. *
  1251. * @param bool $renew true to force the authentication with the CAS server
  1252. *
  1253. * @return bool true when the user is authenticated. Also may redirect to the
  1254. * same URL without the ticket.
  1255. */
  1256. public function isAuthenticated($renew=false)
  1257. {
  1258. phpCAS::traceBegin();
  1259. $res = false;
  1260. $validate_url = '';
  1261. if ( $this->_wasPreviouslyAuthenticated() ) {
  1262. if ($this->hasTicket()) {
  1263. // User has a additional ticket but was already authenticated
  1264. phpCAS::trace(
  1265. 'ticket was present and will be discarded, use renewAuthenticate()'
  1266. );
  1267. if ($this->_clearTicketsFromUrl) {
  1268. phpCAS::trace("Prepare redirect to : ".$this->getURL());
  1269. session_write_close();
  1270. header('Location: '.$this->getURL());
  1271. flush();
  1272. phpCAS::traceExit();
  1273. throw new CAS_GracefullTerminationException();
  1274. } else {
  1275. phpCAS::trace(
  1276. 'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.'
  1277. );
  1278. $res = true;
  1279. }
  1280. } else {
  1281. // the user has already (previously during the session) been
  1282. // authenticated, nothing to be done.
  1283. phpCAS::trace(
  1284. 'user was already authenticated, no need to look for tickets'
  1285. );
  1286. $res = true;
  1287. }
  1288. // Mark the auth-check as complete to allow post-authentication
  1289. // callbacks to make use of phpCAS::getUser() and similar methods
  1290. $this->markAuthenticationCall($res);
  1291. } else {
  1292. if ($this->hasTicket()) {
  1293. switch ($this->getServerVersion()) {
  1294. case CAS_VERSION_1_0:
  1295. // if a Service Ticket was given, validate it
  1296. phpCAS::trace(
  1297. 'CAS 1.0 ticket `'.$this->getTicket().'\' is present'
  1298. );
  1299. $this->validateCAS10(
  1300. $validate_url, $text_response, $tree_response, $renew
  1301. ); // if it fails, it halts
  1302. phpCAS::trace(
  1303. 'CAS 1.0 ticket `'.$this->getTicket().'\' was validated'
  1304. );
  1305. $_SESSION['phpCAS']['user'] = $this->_getUser();
  1306. $res = true;
  1307. $logoutTicket = $this->getTicket();
  1308. break;
  1309. case CAS_VERSION_2_0:
  1310. case CAS_VERSION_3_0:
  1311. // if a Proxy Ticket was given, validate it
  1312. phpCAS::trace(
  1313. 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present'
  1314. );
  1315. $this->validateCAS20(
  1316. $validate_url, $text_response, $tree_response, $renew
  1317. ); // note: if it fails, it halts
  1318. phpCAS::trace(
  1319. 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated'
  1320. );
  1321. if ( $this->isProxy() ) {
  1322. $this->_validatePGT(
  1323. $validate_url, $text_response, $tree_response
  1324. ); // idem
  1325. phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated');
  1326. $_SESSION['phpCAS']['pgt'] = $this->_getPGT();
  1327. }
  1328. $_SESSION['phpCAS']['user'] = $this->_getUser();
  1329. if (!empty($this->_attributes)) {
  1330. $_SESSION['phpCAS']['attributes'] = $this->_attributes;
  1331. }
  1332. $proxies = $this->getProxies();
  1333. if (!empty($proxies)) {
  1334. $_SESSION['phpCAS']['proxies'] = $this->getProxies();
  1335. }
  1336. $res = true;
  1337. $logoutTicket = $this->getTicket();
  1338. break;
  1339. case SAML_VERSION_1_1:
  1340. // if we have a SAML ticket, validate it.
  1341. phpCAS::trace(
  1342. 'SAML 1.1 ticket `'.$this->getTicket().'\' is present'
  1343. );
  1344. $this->validateSA(
  1345. $validate_url, $text_response, $tree_response, $renew
  1346. ); // if it fails, it halts
  1347. phpCAS::trace(
  1348. 'SAML 1.1 ticket `'.$this->getTicket().'\' was validated'
  1349. );
  1350. $_SESSION['phpCAS']['user'] = $this->_getUser();
  1351. $_SESSION['phpCAS']['attributes'] = $this->_attributes;
  1352. $res = true;
  1353. $logoutTicket = $this->getTicket();
  1354. break;
  1355. default:
  1356. phpCAS::trace('Protocoll error');
  1357. break;
  1358. }
  1359. } else {
  1360. // no ticket given, not authenticated
  1361. phpCAS::trace('no ticket found');
  1362. }
  1363. // Mark the auth-check as complete to allow post-authentication
  1364. // callbacks to make use of phpCAS::getUser() and similar methods
  1365. $this->markAuthenticationCall($res);
  1366. if ($res) {
  1367. // call the post-authenticate callback if registered.
  1368. if ($this->_postAuthenticateCallbackFunction) {
  1369. $args = $this->_postAuthenticateCallbackArgs;
  1370. array_unshift($args, $logoutTicket);
  1371. call_user_func_array(
  1372. $this->_postAuthenticateCallbackFunction, $args
  1373. );
  1374. }
  1375. // if called with a ticket parameter, we need to redirect to the
  1376. // app without the ticket so that CAS-ification is transparent
  1377. // to the browser (for later POSTS) most of the checks and
  1378. // errors should have been made now, so we're safe for redirect
  1379. // without masking error messages. remove the ticket as a
  1380. // security precaution to prevent a ticket in the HTTP_REFERRER
  1381. if ($this->_clearTicketsFromUrl) {
  1382. phpCAS::trace("Prepare redirect to : ".$this->getURL());
  1383. session_write_close();
  1384. header('Location: '.$this->getURL());
  1385. flush();
  1386. phpCAS::traceExit();
  1387. throw new CAS_GracefullTerminationException();
  1388. }
  1389. }
  1390. }
  1391. phpCAS::traceEnd($res);
  1392. return $res;
  1393. }
  1394. /**
  1395. * This method tells if the current session is authenticated.
  1396. *
  1397. * @return bool true if authenticated based soley on $_SESSION variable
  1398. */
  1399. public function isSessionAuthenticated ()
  1400. {
  1401. return !empty($_SESSION['phpCAS']['user']);
  1402. }
  1403. /**
  1404. * This method tells if the user has already been (previously) authenticated
  1405. * by looking into the session variables.
  1406. *
  1407. * @note This function switches to callback mode when needed.
  1408. *
  1409. * @return bool true when the user has already been authenticated; false otherwise.
  1410. */
  1411. private function _wasPreviouslyAuthenticated()
  1412. {
  1413. phpCAS::traceBegin();
  1414. if ( $this->_isCallbackMode() ) {
  1415. // Rebroadcast the pgtIou and pgtId to all nodes
  1416. if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) {
  1417. $this->_rebroadcast(self::PGTIOU);
  1418. }
  1419. $this->_callback();
  1420. }
  1421. $auth = false;
  1422. if ( $this->isProxy() ) {
  1423. // CAS proxy: username and PGT must be present
  1424. if ( $this->isSessionAuthenticated()
  1425. && !empty($_SESSION['phpCAS']['pgt'])
  1426. ) {
  1427. // authentication already done
  1428. $this->_setUser($_SESSION['phpCAS']['user']);
  1429. if (isset($_SESSION['phpCAS']['attributes'])) {
  1430. $this->setAttributes($_SESSION['phpCAS']['attributes']);
  1431. }
  1432. $this->_setPGT($_SESSION['phpCAS']['pgt']);
  1433. phpCAS::trace(
  1434. 'user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'
  1435. .$_SESSION['phpCAS']['pgt'].'\''
  1436. );
  1437. // Include the list of proxies
  1438. if (isset($_SESSION['phpCAS']['proxies'])) {
  1439. $this->_setProxies($_SESSION['phpCAS']['proxies']);
  1440. phpCAS::trace(
  1441. 'proxies = "'
  1442. .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
  1443. );
  1444. }
  1445. $auth = true;
  1446. } elseif ( $this->isSessionAuthenticated()
  1447. && empty($_SESSION['phpCAS']['pgt'])
  1448. ) {
  1449. // these two variables should be empty or not empty at the same time
  1450. phpCAS::trace(
  1451. 'username found (`'.$_SESSION['phpCAS']['user']
  1452. .'\') but PGT is empty'
  1453. );
  1454. // unset all tickets to enforce authentication
  1455. unset($_SESSION['phpCAS']);
  1456. $this->setTicket('');
  1457. } elseif ( !$this->isSessionAuthenticated()
  1458. && !empty($_SESSION['phpCAS']['pgt'])
  1459. ) {
  1460. // these two variables should be empty or not empty at the same time
  1461. phpCAS::trace(
  1462. 'PGT found (`'.$_SESSION['phpCAS']['pgt']
  1463. .'\') but username is empty'
  1464. );
  1465. // unset all tickets to enforce authentication
  1466. unset($_SESSION['phpCAS']);
  1467. $this->setTicket('');
  1468. } else {
  1469. phpCAS::trace('neither user nor PGT found');
  1470. }
  1471. } else {
  1472. // `simple' CAS client (not a proxy): username must be present
  1473. if ( $this->isSessionAuthenticated() ) {
  1474. // authentication already done
  1475. $this->_setUser($_SESSION['phpCAS']['user']);
  1476. if (isset($_SESSION['phpCAS']['attributes'])) {
  1477. $this->setAttributes($_SESSION['phpCAS']['attributes']);
  1478. }
  1479. phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
  1480. // Include the list of proxies
  1481. if (isset($_SESSION['phpCAS']['proxies'])) {
  1482. $this->_setProxies($_SESSION['phpCAS']['proxies']);
  1483. phpCAS::trace(
  1484. 'proxies = "'
  1485. .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
  1486. );
  1487. }
  1488. $auth = true;
  1489. } else {
  1490. phpCAS::trace('no user found');
  1491. }
  1492. }
  1493. phpCAS::traceEnd($auth);
  1494. return $auth;
  1495. }
  1496. /**
  1497. * This method is used to redirect the client to the CAS server.
  1498. * It is used by CAS_Client::forceAuthentication() and
  1499. * CAS_Client::checkAuthentication().
  1500. *
  1501. * @param bool $gateway true to check authentication, false to force it
  1502. * @param bool $renew true to force the authentication with the CAS server
  1503. *
  1504. * @return void
  1505. */
  1506. public function redirectToCas($gateway=false,$renew=false)
  1507. {
  1508. phpCAS::traceBegin();
  1509. $cas_url = $this->getServerLoginURL($gateway, $renew);
  1510. session_write_close();
  1511. if (php_sapi_name() === 'cli') {
  1512. @header('Location: '.$cas_url);
  1513. } else {
  1514. header('Location: '.$cas_url);
  1515. }
  1516. phpCAS::trace("Redirect to : ".$cas_url);
  1517. $lang = $this->getLangObj();
  1518. $this->printHTMLHeader($lang->getAuthenticationWanted());
  1519. printf('<p>'. $lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
  1520. $this->printHTMLFooter();
  1521. phpCAS::traceExit();
  1522. throw new CAS_GracefullTerminationException();
  1523. }
  1524. /**
  1525. * This method is used to logout from CAS.
  1526. *
  1527. * @param array $params an array that contains the optional url and service
  1528. * parameters that will be passed to the CAS server
  1529. *
  1530. * @return void
  1531. */
  1532. public function logout($params)
  1533. {
  1534. phpCAS::traceBegin();
  1535. $cas_url = $this->getServerLogoutURL();
  1536. $paramSeparator = '?';
  1537. if (isset($params['url'])) {
  1538. $cas_url = $cas_url . $paramSeparator . "url="
  1539. . urlencode($params['url']);
  1540. $paramSeparator = '&';
  1541. }
  1542. if (isset($params['service'])) {
  1543. $cas_url = $cas_url . $paramSeparator . "service="
  1544. . urlencode($params['service']);
  1545. }
  1546. header('Location: '.$cas_url);
  1547. phpCAS::trace("Prepare redirect to : ".$cas_url);
  1548. phpCAS::trace("Destroying session : ".session_id());
  1549. session_unset();
  1550. session_destroy();
  1551. if (session_status() === PHP_SESSION_NONE) {
  1552. phpCAS::trace("Session terminated");
  1553. } else {
  1554. phpCAS::error("Session was not terminated");
  1555. phpCAS::trace("Session was not terminated");
  1556. }
  1557. $lang = $this->getLangObj();
  1558. $this->printHTMLHeader($lang->getLogout());
  1559. printf('<p>'.$lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
  1560. $this->printHTMLFooter();
  1561. phpCAS::traceExit();
  1562. throw new CAS_GracefullTerminationException();
  1563. }
  1564. /**
  1565. * Check of the current request is a logout request
  1566. *
  1567. * @return bool is logout request.
  1568. */
  1569. private function _isLogoutRequest()
  1570. {
  1571. return !empty($_POST['logoutRequest']);
  1572. }
  1573. /**
  1574. * This method handles logout requests.
  1575. *
  1576. * @param bool $check_client true to check the client bofore handling
  1577. * the request, false not to perform any access control. True by default.
  1578. * @param array $allowed_clients an array of host names allowed to send
  1579. * logout requests.
  1580. *
  1581. * @return void
  1582. */
  1583. public function handleLogoutRequests($check_client=true, $allowed_clients=array())
  1584. {
  1585. phpCAS::traceBegin();
  1586. if (!$this->_isLogoutRequest()) {
  1587. phpCAS::trace("Not a logout request");
  1588. phpCAS::traceEnd();
  1589. return;
  1590. }
  1591. if (!$this->getChangeSessionID()
  1592. && is_null($this->_signoutCallbackFunction)
  1593. ) {
  1594. phpCAS::trace(
  1595. "phpCAS can't handle logout requests if it is not allowed to change session_id."
  1596. );
  1597. }
  1598. phpCAS::trace("Logout requested");
  1599. $decoded_logout_rq = urldecode($_POST['logoutRequest']);
  1600. phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq);
  1601. $allowed = false;
  1602. if ($check_client) {
  1603. if ($allowed_clients === array()) {
  1604. $allowed_clients = array( $this->_getServerHostname() );
  1605. }
  1606. $client_ip = $_SERVER['REMOTE_ADDR'];
  1607. $client = gethostbyaddr($client_ip);
  1608. phpCAS::trace("Client: ".$client."/".$client_ip);
  1609. foreach ($allowed_clients as $allowed_client) {
  1610. if (($client == $allowed_client)
  1611. || ($client_ip == $allowed_client)
  1612. ) {
  1613. phpCAS::trace(
  1614. "Allowed client '".$allowed_client
  1615. ."' matches, logout request is allowed"
  1616. );
  1617. $allowed = true;
  1618. break;
  1619. } else {
  1620. phpCAS::trace(
  1621. "Allowed client '".$allowed_client."' does not match"
  1622. );
  1623. }
  1624. }
  1625. } else {
  1626. phpCAS::trace("No access control set");
  1627. $allowed = true;
  1628. }
  1629. // If Logout command is permitted proceed with the logout
  1630. if ($allowed) {
  1631. phpCAS::trace("Logout command allowed");
  1632. // Rebroadcast the logout request
  1633. if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) {
  1634. $this->_rebroadcast(self::LOGOUT);
  1635. }
  1636. // Extract the ticket from the SAML Request
  1637. preg_match(
  1638. "|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|",
  1639. $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3
  1640. );
  1641. $wrappedSamlSessionIndex = preg_replace(
  1642. '|<samlp:SessionIndex>|', '', $tick[0][0]
  1643. );
  1644. $ticket2logout = preg_replace(
  1645. '|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex
  1646. );
  1647. phpCAS::trace("Ticket to logout: ".$ticket2logout);
  1648. // call the post-authenticate callback if registered.
  1649. if ($this->_signoutCallbackFunction) {
  1650. $args = $this->_signoutCallbackArgs;
  1651. array_unshift($args, $ticket2logout);
  1652. call_user_func_array($this->_signoutCallbackFunction, $args);
  1653. }
  1654. // If phpCAS is managing the session_id, destroy session thanks to
  1655. // session_id.
  1656. if ($this->getChangeSessionID()) {
  1657. $session_id = $this->_sessionIdForTicket($ticket2logout);
  1658. phpCAS::trace("Session id: ".$session_id);
  1659. // destroy a possible application session created before phpcas
  1660. if (session_id() !== "") {
  1661. session_unset();
  1662. session_destroy();
  1663. }
  1664. // fix session ID
  1665. session_id($session_id);
  1666. $_COOKIE[session_name()]=$session_id;
  1667. $_GET[session_name()]=$session_id;
  1668. // Overwrite session
  1669. session_start();
  1670. session_unset();
  1671. session_destroy();
  1672. phpCAS::trace("Session ". $session_id . " destroyed");
  1673. }
  1674. } else {
  1675. phpCAS::error("Unauthorized logout request from client '".$client."'");
  1676. phpCAS::trace("Unauthorized logout request from client '".$client."'");
  1677. }
  1678. flush();
  1679. phpCAS::traceExit();
  1680. throw new CAS_GracefullTerminationException();
  1681. }
  1682. /** @} */
  1683. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1684. // XX XX
  1685. // XX BASIC CLIENT FEATURES (CAS 1.0) XX
  1686. // XX XX
  1687. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  1688. // ########################################################################
  1689. // ST
  1690. // ########################################################################
  1691. /**
  1692. * @addtogroup internalBasic
  1693. * @{
  1694. */
  1695. /**
  1696. * The Ticket provided in the URL of the request if present
  1697. * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
  1698. * CAS_Client::getTicket() and CAS_Client::_hasPGT().
  1699. *
  1700. * @hideinitializer
  1701. */
  1702. private $_ticket = '';
  1703. /**
  1704. * This method returns the Service Ticket provided in the URL of the request.
  1705. *
  1706. * @return string service ticket.
  1707. */
  1708. public function getTicket()
  1709. {
  1710. return $this->_ticket;
  1711. }
  1712. /**
  1713. * This method stores the Service Ticket.
  1714. *
  1715. * @param string $st The Service Ticket.
  1716. *
  1717. * @return void
  1718. */
  1719. public function setTicket($st)
  1720. {
  1721. $this->_ticket = $st;
  1722. }
  1723. /**
  1724. * This method tells if a Service Ticket was stored.
  1725. *
  1726. * @return bool if a Service Ticket has been stored.
  1727. */
  1728. public function hasTicket()
  1729. {
  1730. return !empty($this->_ticket);
  1731. }
  1732. /** @} */
  1733. // ########################################################################
  1734. // ST VALIDATION
  1735. // ########################################################################
  1736. /**
  1737. * @addtogroup internalBasic
  1738. * @{
  1739. */
  1740. /**
  1741. * @var string the certificate of the CAS server CA.
  1742. *
  1743. * @hideinitializer
  1744. */
  1745. private $_cas_server_ca_cert = null;
  1746. /**
  1747. * validate CN of the CAS server certificate
  1748. *
  1749. * @hideinitializer
  1750. */
  1751. private $_cas_server_cn_validate = true;
  1752. /**
  1753. * Set to true not to validate the CAS server.
  1754. *
  1755. * @hideinitializer
  1756. */
  1757. private $_no_cas_server_validation = false;
  1758. /**
  1759. * Set the CA certificate of the CAS server.
  1760. *
  1761. * @param string $cert the PEM certificate file name of the CA that emited
  1762. * the cert of the server
  1763. * @param bool $validate_cn valiate CN of the CAS server certificate
  1764. *
  1765. * @return void
  1766. */
  1767. public function setCasServerCACert($cert, $validate_cn)
  1768. {
  1769. // Argument validation
  1770. if (gettype($cert) != 'string') {
  1771. throw new CAS_TypeMismatchException($cert, '$cert', 'string');
  1772. }
  1773. if (gettype($validate_cn) != 'boolean') {
  1774. throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean');
  1775. }
  1776. if ( !file_exists($cert) && $this->_requestImplementation !== 'CAS_TestHarness_DummyRequest'){
  1777. throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation);
  1778. }
  1779. $this->_cas_server_ca_cert = $cert;
  1780. $this->_cas_server_cn_validate = $validate_cn;
  1781. }
  1782. /**
  1783. * Set no SSL validation for the CAS server.
  1784. *
  1785. * @return void
  1786. */
  1787. public function setNoCasServerValidation()
  1788. {
  1789. $this->_no_cas_server_validation = true;
  1790. }
  1791. /**
  1792. * This method is used to validate a CAS 1,0 ticket; halt on failure, and
  1793. * sets $validate_url, $text_reponse and $tree_response on success.
  1794. *
  1795. * @param string &$validate_url reference to the the URL of the request to
  1796. * the CAS server.
  1797. * @param string &$text_response reference to the response of the CAS
  1798. * server, as is (XML text).
  1799. * @param string &$tree_response reference to the response of the CAS
  1800. * server, as a DOM XML tree.
  1801. * @param bool $renew true to force the authentication with the CAS server
  1802. *
  1803. * @return bool true when successfull and issue a CAS_AuthenticationException
  1804. * and false on an error
  1805. * @throws CAS_AuthenticationException
  1806. */
  1807. public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false)
  1808. {
  1809. phpCAS::traceBegin();
  1810. // build the URL to validate the ticket
  1811. $validate_url = $this->getServerServiceValidateURL()
  1812. .'&ticket='.urlencode($this->getTicket());
  1813. if ( $renew ) {
  1814. // pass the renew
  1815. $validate_url .= '&renew=true';
  1816. }
  1817. // open and read the URL
  1818. if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
  1819. phpCAS::trace(
  1820. 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
  1821. );
  1822. throw new CAS_AuthenticationException(
  1823. $this, 'CAS 1.0 ticket not validated', $validate_url,
  1824. true/*$no_response*/
  1825. );
  1826. }
  1827. if (preg_match('/^no\n/', $text_response)) {
  1828. phpCAS::trace('Ticket has not been validated');
  1829. throw new CAS_AuthenticationException(
  1830. $this, 'ST not validated', $validate_url, false/*$no_response*/,
  1831. false/*$bad_response*/, $text_response
  1832. );
  1833. } else if (!preg_match('/^yes\n/', $text_response)) {
  1834. phpCAS::trace('ill-formed response');
  1835. throw new CAS_AuthenticationException(
  1836. $this, 'Ticket not validated', $validate_url,
  1837. false/*$no_response*/, true/*$bad_response*/, $text_response
  1838. );
  1839. }
  1840. // ticket has been validated, extract the user name
  1841. $arr = preg_split('/\n/', $text_response);
  1842. $this->_setUser(trim($arr[1]));
  1843. $this->_renameSession($this->getTicket());
  1844. // at this step, ticket has been validated and $this->_user has been set,
  1845. phpCAS::traceEnd(true);
  1846. return true;
  1847. }
  1848. /** @} */
  1849. // ########################################################################
  1850. // SAML VALIDATION
  1851. // ########################################################################
  1852. /**
  1853. * @addtogroup internalSAML
  1854. * @{
  1855. */
  1856. /**
  1857. * This method is used to validate a SAML TICKET; halt on failure, and sets
  1858. * $validate_url, $text_reponse and $tree_response on success. These
  1859. * parameters are used later by CAS_Client::_validatePGT() for CAS proxies.
  1860. *
  1861. * @param string &$validate_url reference to the the URL of the request to
  1862. * the CAS server.
  1863. * @param string &$text_response reference to the response of the CAS
  1864. * server, as is (XML text).
  1865. * @param string &$tree_response reference to the response of the CAS
  1866. * server, as a DOM XML tree.
  1867. * @param bool $renew true to force the authentication with the CAS server
  1868. *
  1869. * @return bool true when successfull and issue a CAS_AuthenticationException
  1870. * and false on an error
  1871. *
  1872. * @throws CAS_AuthenticationException
  1873. */
  1874. public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false)
  1875. {
  1876. phpCAS::traceBegin();
  1877. $result = false;
  1878. // build the URL to validate the ticket
  1879. $validate_url = $this->getServerSamlValidateURL();
  1880. if ( $renew ) {
  1881. // pass the renew
  1882. $validate_url .= '&renew=true';
  1883. }
  1884. // open and read the URL
  1885. if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
  1886. phpCAS::trace(
  1887. 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
  1888. );
  1889. throw new CAS_AuthenticationException(
  1890. $this, 'SA not validated', $validate_url, true/*$no_response*/
  1891. );
  1892. }
  1893. phpCAS::trace('server version: '.$this->getServerVersion());
  1894. // analyze the result depending on the version
  1895. switch ($this->getServerVersion()) {
  1896. case SAML_VERSION_1_1:
  1897. // create new DOMDocument Object
  1898. $dom = new DOMDocument();
  1899. // Fix possible whitspace problems
  1900. $dom->preserveWhiteSpace = false;
  1901. // read the response of the CAS server into a DOM object
  1902. if (!($dom->loadXML($text_response))) {
  1903. phpCAS::trace('dom->loadXML() failed');
  1904. throw new CAS_AuthenticationException(
  1905. $this, 'SA not validated', $validate_url,
  1906. false/*$no_response*/, true/*$bad_response*/,
  1907. $text_response
  1908. );
  1909. }
  1910. // read the root node of the XML tree
  1911. if (!($tree_response = $dom->documentElement)) {
  1912. phpCAS::trace('documentElement() failed');
  1913. throw new CAS_AuthenticationException(
  1914. $this, 'SA not validated', $validate_url,
  1915. false/*$no_response*/, true/*$bad_response*/,
  1916. $text_response
  1917. );
  1918. } else if ( $tree_response->localName != 'Envelope' ) {
  1919. // insure that tag name is 'Envelope'
  1920. phpCAS::trace(
  1921. 'bad XML root node (should be `Envelope\' instead of `'
  1922. .$tree_response->localName.'\''
  1923. );
  1924. throw new CAS_AuthenticationException(
  1925. $this, 'SA not validated', $validate_url,
  1926. false/*$no_response*/, true/*$bad_response*/,
  1927. $text_response
  1928. );
  1929. } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
  1930. // check for the NameIdentifier tag in the SAML response
  1931. $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
  1932. phpCAS::trace('NameIdentifier found');
  1933. $user = trim($success_elements->item(0)->nodeValue);
  1934. phpCAS::trace('user = `'.$user.'`');
  1935. $this->_setUser($user);
  1936. $this->_setSessionAttributes($text_response);
  1937. $result = true;
  1938. } else {
  1939. phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
  1940. throw new CAS_AuthenticationException(
  1941. $this, 'SA not validated', $validate_url,
  1942. false/*$no_response*/, true/*$bad_response*/,
  1943. $text_response
  1944. );
  1945. }
  1946. }
  1947. if ($result) {
  1948. $this->_renameSession($this->getTicket());
  1949. }
  1950. // at this step, ST has been validated and $this->_user has been set,
  1951. phpCAS::traceEnd($result);
  1952. return $result;
  1953. }
  1954. /**
  1955. * This method will parse the DOM and pull out the attributes from the SAML
  1956. * payload and put them into an array, then put the array into the session.
  1957. *
  1958. * @param string $text_response the SAML payload.
  1959. *
  1960. * @return bool true when successfull and false if no attributes a found
  1961. */
  1962. private function _setSessionAttributes($text_response)
  1963. {
  1964. phpCAS::traceBegin();
  1965. $result = false;
  1966. $attr_array = array();
  1967. // create new DOMDocument Object
  1968. $dom = new DOMDocument();
  1969. // Fix possible whitspace problems
  1970. $dom->preserveWhiteSpace = false;
  1971. if (($dom->loadXML($text_response))) {
  1972. $xPath = new DOMXPath($dom);
  1973. $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
  1974. $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
  1975. $nodelist = $xPath->query("//saml:Attribute");
  1976. if ($nodelist) {
  1977. foreach ($nodelist as $node) {
  1978. $xres = $xPath->query("saml:AttributeValue", $node);
  1979. $name = $node->getAttribute("AttributeName");
  1980. $value_array = array();
  1981. foreach ($xres as $node2) {
  1982. $value_array[] = $node2->nodeValue;
  1983. }
  1984. $attr_array[$name] = $value_array;
  1985. }
  1986. // UGent addition...
  1987. foreach ($attr_array as $attr_key => $attr_value) {
  1988. if (count($attr_value) > 1) {
  1989. $this->_attributes[$attr_key] = $attr_value;
  1990. phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true));
  1991. } else {
  1992. $this->_attributes[$attr_key] = $attr_value[0];
  1993. phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
  1994. }
  1995. }
  1996. $result = true;
  1997. } else {
  1998. phpCAS::trace("SAML Attributes are empty");
  1999. $result = false;
  2000. }
  2001. }
  2002. phpCAS::traceEnd($result);
  2003. return $result;
  2004. }
  2005. /** @} */
  2006. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2007. // XX XX
  2008. // XX PROXY FEATURES (CAS 2.0) XX
  2009. // XX XX
  2010. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2011. // ########################################################################
  2012. // PROXYING
  2013. // ########################################################################
  2014. /**
  2015. * @addtogroup internalProxy
  2016. * @{
  2017. */
  2018. /**
  2019. * @var bool is the client a proxy
  2020. * A boolean telling if the client is a CAS proxy or not. Written by
  2021. * CAS_Client::CAS_Client(), read by CAS_Client::isProxy().
  2022. */
  2023. private $_proxy;
  2024. /**
  2025. * @var CAS_CookieJar Handler for managing service cookies.
  2026. */
  2027. private $_serviceCookieJar;
  2028. /**
  2029. * Tells if a CAS client is a CAS proxy or not
  2030. *
  2031. * @return bool true when the CAS client is a CAS proxy, false otherwise
  2032. */
  2033. public function isProxy()
  2034. {
  2035. return $this->_proxy;
  2036. }
  2037. /** @} */
  2038. // ########################################################################
  2039. // PGT
  2040. // ########################################################################
  2041. /**
  2042. * @addtogroup internalProxy
  2043. * @{
  2044. */
  2045. /**
  2046. * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
  2047. * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and
  2048. * CAS_Client::_hasPGT().
  2049. *
  2050. * @hideinitializer
  2051. */
  2052. private $_pgt = '';
  2053. /**
  2054. * This method returns the Proxy Granting Ticket given by the CAS server.
  2055. *
  2056. * @return string the Proxy Granting Ticket.
  2057. */
  2058. private function _getPGT()
  2059. {
  2060. return $this->_pgt;
  2061. }
  2062. /**
  2063. * This method stores the Proxy Granting Ticket.
  2064. *
  2065. * @param string $pgt The Proxy Granting Ticket.
  2066. *
  2067. * @return void
  2068. */
  2069. private function _setPGT($pgt)
  2070. {
  2071. $this->_pgt = $pgt;
  2072. }
  2073. /**
  2074. * This method tells if a Proxy Granting Ticket was stored.
  2075. *
  2076. * @return bool true if a Proxy Granting Ticket has been stored.
  2077. */
  2078. private function _hasPGT()
  2079. {
  2080. return !empty($this->_pgt);
  2081. }
  2082. /** @} */
  2083. // ########################################################################
  2084. // CALLBACK MODE
  2085. // ########################################################################
  2086. /**
  2087. * @addtogroup internalCallback
  2088. * @{
  2089. */
  2090. /**
  2091. * each PHP script using phpCAS in proxy mode is its own callback to get the
  2092. * PGT back from the CAS server. callback_mode is detected by the constructor
  2093. * thanks to the GET parameters.
  2094. */
  2095. /**
  2096. * @var bool a boolean to know if the CAS client is running in callback mode. Written by
  2097. * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode().
  2098. *
  2099. * @hideinitializer
  2100. */
  2101. private $_callback_mode = false;
  2102. /**
  2103. * This method sets/unsets callback mode.
  2104. *
  2105. * @param bool $callback_mode true to set callback mode, false otherwise.
  2106. *
  2107. * @return void
  2108. */
  2109. private function _setCallbackMode($callback_mode)
  2110. {
  2111. $this->_callback_mode = $callback_mode;
  2112. }
  2113. /**
  2114. * This method returns true when the CAS client is running in callback mode,
  2115. * false otherwise.
  2116. *
  2117. * @return bool A boolean.
  2118. */
  2119. private function _isCallbackMode()
  2120. {
  2121. return $this->_callback_mode;
  2122. }
  2123. /**
  2124. * the URL that should be used for the PGT callback (in fact the URL of the
  2125. * current request without any CGI parameter). Written and read by
  2126. * CAS_Client::_getCallbackURL().
  2127. *
  2128. * @hideinitializer
  2129. */
  2130. private $_callback_url = '';
  2131. /**
  2132. * This method returns the URL that should be used for the PGT callback (in
  2133. * fact the URL of the current request without any CGI parameter, except if
  2134. * phpCAS::setFixedCallbackURL() was used).
  2135. *
  2136. * @return string The callback URL
  2137. */
  2138. private function _getCallbackURL()
  2139. {
  2140. // the URL is built when needed only
  2141. if ( empty($this->_callback_url) ) {
  2142. // remove the ticket if present in the URL
  2143. $final_uri = 'https://';
  2144. $final_uri .= $this->_getClientUrl();
  2145. $request_uri = $_SERVER['REQUEST_URI'];
  2146. $request_uri = preg_replace('/\?.*$/', '', $request_uri);
  2147. $final_uri .= $request_uri;
  2148. $this->_callback_url = $final_uri;
  2149. }
  2150. return $this->_callback_url;
  2151. }
  2152. /**
  2153. * This method sets the callback url.
  2154. *
  2155. * @param string $url url to set callback
  2156. *
  2157. * @return string the callback url
  2158. */
  2159. public function setCallbackURL($url)
  2160. {
  2161. // Sequence validation
  2162. $this->ensureIsProxy();
  2163. // Argument Validation
  2164. if (gettype($url) != 'string')
  2165. throw new CAS_TypeMismatchException($url, '$url', 'string');
  2166. return $this->_callback_url = $url;
  2167. }
  2168. /**
  2169. * This method is called by CAS_Client::CAS_Client() when running in callback
  2170. * mode. It stores the PGT and its PGT Iou, prints its output and halts.
  2171. *
  2172. * @return void
  2173. */
  2174. private function _callback()
  2175. {
  2176. phpCAS::traceBegin();
  2177. if (preg_match('/^PGTIOU-[\.\-\w]+$/', $_GET['pgtIou'])) {
  2178. if (preg_match('/^[PT]GT-[\.\-\w]+$/', $_GET['pgtId'])) {
  2179. $this->printHTMLHeader('phpCAS callback');
  2180. $pgt_iou = $_GET['pgtIou'];
  2181. $pgt = $_GET['pgtId'];
  2182. phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
  2183. echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
  2184. $this->_storePGT($pgt, $pgt_iou);
  2185. $this->printHTMLFooter();
  2186. phpCAS::traceExit("Successfull Callback");
  2187. } else {
  2188. phpCAS::error('PGT format invalid' . $_GET['pgtId']);
  2189. phpCAS::traceExit('PGT format invalid' . $_GET['pgtId']);
  2190. }
  2191. } else {
  2192. phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
  2193. phpCAS::traceExit('PGTiou format invalid' . $_GET['pgtIou']);
  2194. }
  2195. // Flush the buffer to prevent from sending anything other then a 200
  2196. // Success Status back to the CAS Server. The Exception would normally
  2197. // report as a 500 error.
  2198. flush();
  2199. throw new CAS_GracefullTerminationException();
  2200. }
  2201. /** @} */
  2202. // ########################################################################
  2203. // PGT STORAGE
  2204. // ########################################################################
  2205. /**
  2206. * @addtogroup internalPGTStorage
  2207. * @{
  2208. */
  2209. /**
  2210. * @var CAS_PGTStorage_AbstractStorage
  2211. * an instance of a class inheriting of PGTStorage, used to deal with PGT
  2212. * storage. Created by CAS_Client::setPGTStorageFile(), used
  2213. * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage().
  2214. *
  2215. * @hideinitializer
  2216. */
  2217. private $_pgt_storage = null;
  2218. /**
  2219. * This method is used to initialize the storage of PGT's.
  2220. * Halts on error.
  2221. *
  2222. * @return void
  2223. */
  2224. private function _initPGTStorage()
  2225. {
  2226. // if no SetPGTStorageXxx() has been used, default to file
  2227. if ( !is_object($this->_pgt_storage) ) {
  2228. $this->setPGTStorageFile();
  2229. }
  2230. // initializes the storage
  2231. $this->_pgt_storage->init();
  2232. }
  2233. /**
  2234. * This method stores a PGT. Halts on error.
  2235. *
  2236. * @param string $pgt the PGT to store
  2237. * @param string $pgt_iou its corresponding Iou
  2238. *
  2239. * @return void
  2240. */
  2241. private function _storePGT($pgt,$pgt_iou)
  2242. {
  2243. // ensure that storage is initialized
  2244. $this->_initPGTStorage();
  2245. // writes the PGT
  2246. $this->_pgt_storage->write($pgt, $pgt_iou);
  2247. }
  2248. /**
  2249. * This method reads a PGT from its Iou and deletes the corresponding
  2250. * storage entry.
  2251. *
  2252. * @param string $pgt_iou the PGT Iou
  2253. *
  2254. * @return string mul The PGT corresponding to the Iou, false when not found.
  2255. */
  2256. private function _loadPGT($pgt_iou)
  2257. {
  2258. // ensure that storage is initialized
  2259. $this->_initPGTStorage();
  2260. // read the PGT
  2261. return $this->_pgt_storage->read($pgt_iou);
  2262. }
  2263. /**
  2264. * This method can be used to set a custom PGT storage object.
  2265. *
  2266. * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that
  2267. * inherits from the CAS_PGTStorage_AbstractStorage class
  2268. *
  2269. * @return void
  2270. */
  2271. public function setPGTStorage($storage)
  2272. {
  2273. // Sequence validation
  2274. $this->ensureIsProxy();
  2275. // check that the storage has not already been set
  2276. if ( is_object($this->_pgt_storage) ) {
  2277. phpCAS::error('PGT storage already defined');
  2278. }
  2279. // check to make sure a valid storage object was specified
  2280. if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) )
  2281. throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object');
  2282. // store the PGTStorage object
  2283. $this->_pgt_storage = $storage;
  2284. }
  2285. /**
  2286. * This method is used to tell phpCAS to store the response of the
  2287. * CAS server to PGT requests in a database.
  2288. *
  2289. * @param string|PDO $dsn_or_pdo a dsn string to use for creating a PDO
  2290. * object or a PDO object
  2291. * @param string $username the username to use when connecting to the
  2292. * database
  2293. * @param string $password the password to use when connecting to the
  2294. * database
  2295. * @param string $table the table to use for storing and retrieving
  2296. * PGTs
  2297. * @param string $driver_options any driver options to use when connecting
  2298. * to the database
  2299. *
  2300. * @return void
  2301. */
  2302. public function setPGTStorageDb(
  2303. $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null
  2304. ) {
  2305. // Sequence validation
  2306. $this->ensureIsProxy();
  2307. // Argument validation
  2308. if (!(is_object($dsn_or_pdo) && $dsn_or_pdo instanceof PDO) && !is_string($dsn_or_pdo))
  2309. throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object');
  2310. if (gettype($username) != 'string')
  2311. throw new CAS_TypeMismatchException($username, '$username', 'string');
  2312. if (gettype($password) != 'string')
  2313. throw new CAS_TypeMismatchException($password, '$password', 'string');
  2314. if (gettype($table) != 'string')
  2315. throw new CAS_TypeMismatchException($table, '$password', 'string');
  2316. // create the storage object
  2317. $this->setPGTStorage(
  2318. new CAS_PGTStorage_Db(
  2319. $this, $dsn_or_pdo, $username, $password, $table, $driver_options
  2320. )
  2321. );
  2322. }
  2323. /**
  2324. * This method is used to tell phpCAS to store the response of the
  2325. * CAS server to PGT requests onto the filesystem.
  2326. *
  2327. * @param string $path the path where the PGT's should be stored
  2328. *
  2329. * @return void
  2330. */
  2331. public function setPGTStorageFile($path='')
  2332. {
  2333. // Sequence validation
  2334. $this->ensureIsProxy();
  2335. // Argument validation
  2336. if (gettype($path) != 'string')
  2337. throw new CAS_TypeMismatchException($path, '$path', 'string');
  2338. // create the storage object
  2339. $this->setPGTStorage(new CAS_PGTStorage_File($this, $path));
  2340. }
  2341. // ########################################################################
  2342. // PGT VALIDATION
  2343. // ########################################################################
  2344. /**
  2345. * This method is used to validate a PGT; halt on failure.
  2346. *
  2347. * @param string &$validate_url the URL of the request to the CAS server.
  2348. * @param string $text_response the response of the CAS server, as is
  2349. * (XML text); result of
  2350. * CAS_Client::validateCAS10() or
  2351. * CAS_Client::validateCAS20().
  2352. * @param DOMElement $tree_response the response of the CAS server, as a DOM XML
  2353. * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
  2354. *
  2355. * @return bool true when successfull and issue a CAS_AuthenticationException
  2356. * and false on an error
  2357. *
  2358. * @throws CAS_AuthenticationException
  2359. */
  2360. private function _validatePGT(&$validate_url,$text_response,$tree_response)
  2361. {
  2362. phpCAS::traceBegin();
  2363. if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
  2364. phpCAS::trace('<proxyGrantingTicket> not found');
  2365. // authentication succeded, but no PGT Iou was transmitted
  2366. throw new CAS_AuthenticationException(
  2367. $this, 'Ticket validated but no PGT Iou transmitted',
  2368. $validate_url, false/*$no_response*/, false/*$bad_response*/,
  2369. $text_response
  2370. );
  2371. } else {
  2372. // PGT Iou transmitted, extract it
  2373. $pgt_iou = trim(
  2374. $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue
  2375. );
  2376. if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgt_iou)) {
  2377. $pgt = $this->_loadPGT($pgt_iou);
  2378. if ( $pgt == false ) {
  2379. phpCAS::trace('could not load PGT');
  2380. throw new CAS_AuthenticationException(
  2381. $this,
  2382. 'PGT Iou was transmitted but PGT could not be retrieved',
  2383. $validate_url, false/*$no_response*/,
  2384. false/*$bad_response*/, $text_response
  2385. );
  2386. }
  2387. $this->_setPGT($pgt);
  2388. } else {
  2389. phpCAS::trace('PGTiou format error');
  2390. throw new CAS_AuthenticationException(
  2391. $this, 'PGT Iou was transmitted but has wrong format',
  2392. $validate_url, false/*$no_response*/, false/*$bad_response*/,
  2393. $text_response
  2394. );
  2395. }
  2396. }
  2397. phpCAS::traceEnd(true);
  2398. return true;
  2399. }
  2400. // ########################################################################
  2401. // PGT VALIDATION
  2402. // ########################################################################
  2403. /**
  2404. * This method is used to retrieve PT's from the CAS server thanks to a PGT.
  2405. *
  2406. * @param string $target_service the service to ask for with the PT.
  2407. * @param int &$err_code an error code (PHPCAS_SERVICE_OK on success).
  2408. * @param string &$err_msg an error message (empty on success).
  2409. *
  2410. * @return string|false a Proxy Ticket, or false on error.
  2411. */
  2412. public function retrievePT($target_service,&$err_code,&$err_msg)
  2413. {
  2414. // Argument validation
  2415. if (gettype($target_service) != 'string')
  2416. throw new CAS_TypeMismatchException($target_service, '$target_service', 'string');
  2417. phpCAS::traceBegin();
  2418. // by default, $err_msg is set empty and $pt to true. On error, $pt is
  2419. // set to false and $err_msg to an error message. At the end, if $pt is false
  2420. // and $error_msg is still empty, it is set to 'invalid response' (the most
  2421. // commonly encountered error).
  2422. $err_msg = '';
  2423. // build the URL to retrieve the PT
  2424. $cas_url = $this->getServerProxyURL().'?targetService='
  2425. .urlencode($target_service).'&pgt='.$this->_getPGT();
  2426. // open and read the URL
  2427. if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) {
  2428. phpCAS::trace(
  2429. 'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'
  2430. );
  2431. $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
  2432. $err_msg = 'could not retrieve PT (no response from the CAS server)';
  2433. phpCAS::traceEnd(false);
  2434. return false;
  2435. }
  2436. $bad_response = false;
  2437. // create new DOMDocument object
  2438. $dom = new DOMDocument();
  2439. // Fix possible whitspace problems
  2440. $dom->preserveWhiteSpace = false;
  2441. // read the response of the CAS server into a DOM object
  2442. if ( !($dom->loadXML($cas_response))) {
  2443. phpCAS::trace('dom->loadXML() failed');
  2444. // read failed
  2445. $bad_response = true;
  2446. }
  2447. if ( !$bad_response ) {
  2448. // read the root node of the XML tree
  2449. if ( !($root = $dom->documentElement) ) {
  2450. phpCAS::trace('documentElement failed');
  2451. // read failed
  2452. $bad_response = true;
  2453. }
  2454. }
  2455. if ( !$bad_response ) {
  2456. // insure that tag name is 'serviceResponse'
  2457. if ( $root->localName != 'serviceResponse' ) {
  2458. phpCAS::trace('localName failed');
  2459. // bad root node
  2460. $bad_response = true;
  2461. }
  2462. }
  2463. if ( !$bad_response ) {
  2464. // look for a proxySuccess tag
  2465. if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
  2466. $proxy_success_list = $root->getElementsByTagName("proxySuccess");
  2467. // authentication succeded, look for a proxyTicket tag
  2468. if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
  2469. $err_code = PHPCAS_SERVICE_OK;
  2470. $err_msg = '';
  2471. $pt = trim(
  2472. $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue
  2473. );
  2474. phpCAS::trace('original PT: '.trim($pt));
  2475. phpCAS::traceEnd($pt);
  2476. return $pt;
  2477. } else {
  2478. phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
  2479. }
  2480. } else if ($root->getElementsByTagName("proxyFailure")->length != 0) {
  2481. // look for a proxyFailure tag
  2482. $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
  2483. // authentication failed, extract the error
  2484. $err_code = PHPCAS_SERVICE_PT_FAILURE;
  2485. $err_msg = 'PT retrieving failed (code=`'
  2486. .$proxy_failure_list->item(0)->getAttribute('code')
  2487. .'\', message=`'
  2488. .trim($proxy_failure_list->item(0)->nodeValue)
  2489. .'\')';
  2490. phpCAS::traceEnd(false);
  2491. return false;
  2492. } else {
  2493. phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
  2494. }
  2495. }
  2496. // at this step, we are sure that the response of the CAS server was
  2497. // illformed
  2498. $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
  2499. $err_msg = 'Invalid response from the CAS server (response=`'
  2500. .$cas_response.'\')';
  2501. phpCAS::traceEnd(false);
  2502. return false;
  2503. }
  2504. /** @} */
  2505. // ########################################################################
  2506. // READ CAS SERVER ANSWERS
  2507. // ########################################################################
  2508. /**
  2509. * @addtogroup internalMisc
  2510. * @{
  2511. */
  2512. /**
  2513. * This method is used to acces a remote URL.
  2514. *
  2515. * @param string $url the URL to access.
  2516. * @param string &$headers an array containing the HTTP header lines of the
  2517. * response (an empty array on failure).
  2518. * @param string &$body the body of the response, as a string (empty on
  2519. * failure).
  2520. * @param string &$err_msg an error message, filled on failure.
  2521. *
  2522. * @return bool true on success, false otherwise (in this later case, $err_msg
  2523. * contains an error message).
  2524. */
  2525. private function _readURL($url, &$headers, &$body, &$err_msg)
  2526. {
  2527. phpCAS::traceBegin();
  2528. $className = $this->_requestImplementation;
  2529. $request = new $className();
  2530. if (count($this->_curl_options)) {
  2531. $request->setCurlOptions($this->_curl_options);
  2532. }
  2533. $request->setUrl($url);
  2534. if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
  2535. phpCAS::error(
  2536. 'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'
  2537. );
  2538. }
  2539. if ($this->_cas_server_ca_cert != '') {
  2540. $request->setSslCaCert(
  2541. $this->_cas_server_ca_cert, $this->_cas_server_cn_validate
  2542. );
  2543. }
  2544. // add extra stuff if SAML
  2545. if ($this->getServerVersion() == SAML_VERSION_1_1) {
  2546. $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
  2547. $request->addHeader("cache-control: no-cache");
  2548. $request->addHeader("pragma: no-cache");
  2549. $request->addHeader("accept: text/xml");
  2550. $request->addHeader("connection: keep-alive");
  2551. $request->addHeader("content-type: text/xml");
  2552. $request->makePost();
  2553. $request->setPostBody($this->_buildSAMLPayload());
  2554. }
  2555. if ($request->send()) {
  2556. $headers = $request->getResponseHeaders();
  2557. $body = $request->getResponseBody();
  2558. $err_msg = '';
  2559. phpCAS::traceEnd(true);
  2560. return true;
  2561. } else {
  2562. $headers = '';
  2563. $body = '';
  2564. $err_msg = $request->getErrorMessage();
  2565. phpCAS::traceEnd(false);
  2566. return false;
  2567. }
  2568. }
  2569. /**
  2570. * This method is used to build the SAML POST body sent to /samlValidate URL.
  2571. *
  2572. * @return string the SOAP-encased SAMLP artifact (the ticket).
  2573. */
  2574. private function _buildSAMLPayload()
  2575. {
  2576. phpCAS::traceBegin();
  2577. //get the ticket
  2578. $sa = urlencode($this->getTicket());
  2579. $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST
  2580. .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE
  2581. .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
  2582. phpCAS::traceEnd($body);
  2583. return ($body);
  2584. }
  2585. /** @} **/
  2586. // ########################################################################
  2587. // ACCESS TO EXTERNAL SERVICES
  2588. // ########################################################################
  2589. /**
  2590. * @addtogroup internalProxyServices
  2591. * @{
  2592. */
  2593. /**
  2594. * Answer a proxy-authenticated service handler.
  2595. *
  2596. * @param string $type The service type. One of:
  2597. * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST,
  2598. * PHPCAS_PROXIED_SERVICE_IMAP
  2599. *
  2600. * @return CAS_ProxiedService
  2601. * @throws InvalidArgumentException If the service type is unknown.
  2602. */
  2603. public function getProxiedService ($type)
  2604. {
  2605. // Sequence validation
  2606. $this->ensureIsProxy();
  2607. $this->ensureAuthenticationCallSuccessful();
  2608. // Argument validation
  2609. if (gettype($type) != 'string')
  2610. throw new CAS_TypeMismatchException($type, '$type', 'string');
  2611. switch ($type) {
  2612. case PHPCAS_PROXIED_SERVICE_HTTP_GET:
  2613. case PHPCAS_PROXIED_SERVICE_HTTP_POST:
  2614. $requestClass = $this->_requestImplementation;
  2615. $request = new $requestClass();
  2616. if (count($this->_curl_options)) {
  2617. $request->setCurlOptions($this->_curl_options);
  2618. }
  2619. $proxiedService = new $type($request, $this->_serviceCookieJar);
  2620. if ($proxiedService instanceof CAS_ProxiedService_Testable) {
  2621. $proxiedService->setCasClient($this);
  2622. }
  2623. return $proxiedService;
  2624. case PHPCAS_PROXIED_SERVICE_IMAP;
  2625. $proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
  2626. if ($proxiedService instanceof CAS_ProxiedService_Testable) {
  2627. $proxiedService->setCasClient($this);
  2628. }
  2629. return $proxiedService;
  2630. default:
  2631. throw new CAS_InvalidArgumentException(
  2632. "Unknown proxied-service type, $type."
  2633. );
  2634. }
  2635. }
  2636. /**
  2637. * Initialize a proxied-service handler with the proxy-ticket it should use.
  2638. *
  2639. * @param CAS_ProxiedService $proxiedService service handler
  2640. *
  2641. * @return void
  2642. *
  2643. * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
  2644. * The code of the Exception will be one of:
  2645. * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
  2646. * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
  2647. * PHPCAS_SERVICE_PT_FAILURE
  2648. * @throws CAS_ProxiedService_Exception If there is a failure getting the
  2649. * url from the proxied service.
  2650. */
  2651. public function initializeProxiedService (CAS_ProxiedService $proxiedService)
  2652. {
  2653. // Sequence validation
  2654. $this->ensureIsProxy();
  2655. $this->ensureAuthenticationCallSuccessful();
  2656. $url = $proxiedService->getServiceUrl();
  2657. if (!is_string($url)) {
  2658. throw new CAS_ProxiedService_Exception(
  2659. "Proxied Service ".get_class($proxiedService)
  2660. ."->getServiceUrl() should have returned a string, returned a "
  2661. .gettype($url)." instead."
  2662. );
  2663. }
  2664. $pt = $this->retrievePT($url, $err_code, $err_msg);
  2665. if (!$pt) {
  2666. throw new CAS_ProxyTicketException($err_msg, $err_code);
  2667. }
  2668. $proxiedService->setProxyTicket($pt);
  2669. }
  2670. /**
  2671. * This method is used to access an HTTP[S] service.
  2672. *
  2673. * @param string $url the service to access.
  2674. * @param int &$err_code an error code Possible values are
  2675. * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
  2676. * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
  2677. * PHPCAS_SERVICE_NOT_AVAILABLE.
  2678. * @param string &$output the output of the service (also used to give an error
  2679. * message on failure).
  2680. *
  2681. * @return bool true on success, false otherwise (in this later case, $err_code
  2682. * gives the reason why it failed and $output contains an error message).
  2683. */
  2684. public function serviceWeb($url,&$err_code,&$output)
  2685. {
  2686. // Sequence validation
  2687. $this->ensureIsProxy();
  2688. $this->ensureAuthenticationCallSuccessful();
  2689. // Argument validation
  2690. if (gettype($url) != 'string')
  2691. throw new CAS_TypeMismatchException($url, '$url', 'string');
  2692. try {
  2693. $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
  2694. $service->setUrl($url);
  2695. $service->send();
  2696. $output = $service->getResponseBody();
  2697. $err_code = PHPCAS_SERVICE_OK;
  2698. return true;
  2699. } catch (CAS_ProxyTicketException $e) {
  2700. $err_code = $e->getCode();
  2701. $output = $e->getMessage();
  2702. return false;
  2703. } catch (CAS_ProxiedService_Exception $e) {
  2704. $lang = $this->getLangObj();
  2705. $output = sprintf(
  2706. $lang->getServiceUnavailable(), $url, $e->getMessage()
  2707. );
  2708. $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
  2709. return false;
  2710. }
  2711. }
  2712. /**
  2713. * This method is used to access an IMAP/POP3/NNTP service.
  2714. *
  2715. * @param string $url a string giving the URL of the service, including
  2716. * the mailing box for IMAP URLs, as accepted by imap_open().
  2717. * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket
  2718. * @param string $flags options given to imap_open().
  2719. * @param int &$err_code an error code Possible values are
  2720. * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
  2721. * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
  2722. * PHPCAS_SERVICE_NOT_AVAILABLE.
  2723. * @param string &$err_msg an error message on failure
  2724. * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS
  2725. * server to access the URL on success, false on error).
  2726. *
  2727. * @return object|false an IMAP stream on success, false otherwise (in this later
  2728. * case, $err_code gives the reason why it failed and $err_msg contains an
  2729. * error message).
  2730. */
  2731. public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
  2732. {
  2733. // Sequence validation
  2734. $this->ensureIsProxy();
  2735. $this->ensureAuthenticationCallSuccessful();
  2736. // Argument validation
  2737. if (gettype($url) != 'string')
  2738. throw new CAS_TypeMismatchException($url, '$url', 'string');
  2739. if (gettype($serviceUrl) != 'string')
  2740. throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string');
  2741. if (gettype($flags) != 'integer')
  2742. throw new CAS_TypeMismatchException($flags, '$flags', 'string');
  2743. try {
  2744. $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
  2745. $service->setServiceUrl($serviceUrl);
  2746. $service->setMailbox($url);
  2747. $service->setOptions($flags);
  2748. $stream = $service->open();
  2749. $err_code = PHPCAS_SERVICE_OK;
  2750. $pt = $service->getImapProxyTicket();
  2751. return $stream;
  2752. } catch (CAS_ProxyTicketException $e) {
  2753. $err_msg = $e->getMessage();
  2754. $err_code = $e->getCode();
  2755. $pt = false;
  2756. return false;
  2757. } catch (CAS_ProxiedService_Exception $e) {
  2758. $lang = $this->getLangObj();
  2759. $err_msg = sprintf(
  2760. $lang->getServiceUnavailable(),
  2761. $url,
  2762. $e->getMessage()
  2763. );
  2764. $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
  2765. $pt = false;
  2766. return false;
  2767. }
  2768. }
  2769. /** @} **/
  2770. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2771. // XX XX
  2772. // XX PROXIED CLIENT FEATURES (CAS 2.0) XX
  2773. // XX XX
  2774. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2775. // ########################################################################
  2776. // PT
  2777. // ########################################################################
  2778. /**
  2779. * @addtogroup internalService
  2780. * @{
  2781. */
  2782. /**
  2783. * This array will store a list of proxies in front of this application. This
  2784. * property will only be populated if this script is being proxied rather than
  2785. * accessed directly.
  2786. *
  2787. * It is set in CAS_Client::validateCAS20() and can be read by
  2788. * CAS_Client::getProxies()
  2789. *
  2790. * @access private
  2791. */
  2792. private $_proxies = array();
  2793. /**
  2794. * Answer an array of proxies that are sitting in front of this application.
  2795. *
  2796. * This method will only return a non-empty array if we have received and
  2797. * validated a Proxy Ticket.
  2798. *
  2799. * @return array
  2800. * @access public
  2801. */
  2802. public function getProxies()
  2803. {
  2804. return $this->_proxies;
  2805. }
  2806. /**
  2807. * Set the Proxy array, probably from persistant storage.
  2808. *
  2809. * @param array $proxies An array of proxies
  2810. *
  2811. * @return void
  2812. * @access private
  2813. */
  2814. private function _setProxies($proxies)
  2815. {
  2816. $this->_proxies = $proxies;
  2817. if (!empty($proxies)) {
  2818. // For proxy-authenticated requests people are not viewing the URL
  2819. // directly since the client is another application making a
  2820. // web-service call.
  2821. // Because of this, stripping the ticket from the URL is unnecessary
  2822. // and causes another web-service request to be performed. Additionally,
  2823. // if session handling on either the client or the server malfunctions
  2824. // then the subsequent request will not complete successfully.
  2825. $this->setNoClearTicketsFromUrl();
  2826. }
  2827. }
  2828. /**
  2829. * A container of patterns to be allowed as proxies in front of the cas client.
  2830. *
  2831. * @var CAS_ProxyChain_AllowedList
  2832. */
  2833. private $_allowed_proxy_chains;
  2834. /**
  2835. * Answer the CAS_ProxyChain_AllowedList object for this client.
  2836. *
  2837. * @return CAS_ProxyChain_AllowedList
  2838. */
  2839. public function getAllowedProxyChains ()
  2840. {
  2841. if (empty($this->_allowed_proxy_chains)) {
  2842. $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList();
  2843. }
  2844. return $this->_allowed_proxy_chains;
  2845. }
  2846. /** @} */
  2847. // ########################################################################
  2848. // PT VALIDATION
  2849. // ########################################################################
  2850. /**
  2851. * @addtogroup internalProxied
  2852. * @{
  2853. */
  2854. /**
  2855. * This method is used to validate a cas 2.0 ST or PT; halt on failure
  2856. * Used for all CAS 2.0 validations
  2857. *
  2858. * @param string &$validate_url the url of the reponse
  2859. * @param string &$text_response the text of the repsones
  2860. * @param DOMElement &$tree_response the domxml tree of the respones
  2861. * @param bool $renew true to force the authentication with the CAS server
  2862. *
  2863. * @return bool true when successfull and issue a CAS_AuthenticationException
  2864. * and false on an error
  2865. *
  2866. * @throws CAS_AuthenticationException
  2867. */
  2868. public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false)
  2869. {
  2870. phpCAS::traceBegin();
  2871. phpCAS::trace($text_response);
  2872. // build the URL to validate the ticket
  2873. if ($this->getAllowedProxyChains()->isProxyingAllowed()) {
  2874. $validate_url = $this->getServerProxyValidateURL().'&ticket='
  2875. .urlencode($this->getTicket());
  2876. } else {
  2877. $validate_url = $this->getServerServiceValidateURL().'&ticket='
  2878. .urlencode($this->getTicket());
  2879. }
  2880. if ( $this->isProxy() ) {
  2881. // pass the callback url for CAS proxies
  2882. $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL());
  2883. }
  2884. if ( $renew ) {
  2885. // pass the renew
  2886. $validate_url .= '&renew=true';
  2887. }
  2888. // open and read the URL
  2889. if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
  2890. phpCAS::trace(
  2891. 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
  2892. );
  2893. throw new CAS_AuthenticationException(
  2894. $this, 'Ticket not validated', $validate_url,
  2895. true/*$no_response*/
  2896. );
  2897. }
  2898. // create new DOMDocument object
  2899. $dom = new DOMDocument();
  2900. // Fix possible whitspace problems
  2901. $dom->preserveWhiteSpace = false;
  2902. // CAS servers should only return data in utf-8
  2903. $dom->encoding = "utf-8";
  2904. // read the response of the CAS server into a DOMDocument object
  2905. if ( !($dom->loadXML($text_response))) {
  2906. // read failed
  2907. throw new CAS_AuthenticationException(
  2908. $this, 'Ticket not validated', $validate_url,
  2909. false/*$no_response*/, true/*$bad_response*/, $text_response
  2910. );
  2911. } else if ( !($tree_response = $dom->documentElement) ) {
  2912. // read the root node of the XML tree
  2913. // read failed
  2914. throw new CAS_AuthenticationException(
  2915. $this, 'Ticket not validated', $validate_url,
  2916. false/*$no_response*/, true/*$bad_response*/, $text_response
  2917. );
  2918. } else if ($tree_response->localName != 'serviceResponse') {
  2919. // insure that tag name is 'serviceResponse'
  2920. // bad root node
  2921. throw new CAS_AuthenticationException(
  2922. $this, 'Ticket not validated', $validate_url,
  2923. false/*$no_response*/, true/*$bad_response*/, $text_response
  2924. );
  2925. } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
  2926. // authentication failed, extract the error code and message and throw exception
  2927. $auth_fail_list = $tree_response
  2928. ->getElementsByTagName("authenticationFailure");
  2929. throw new CAS_AuthenticationException(
  2930. $this, 'Ticket not validated', $validate_url,
  2931. false/*$no_response*/, false/*$bad_response*/,
  2932. $text_response,
  2933. $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
  2934. trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
  2935. );
  2936. } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
  2937. // authentication succeded, extract the user name
  2938. $success_elements = $tree_response
  2939. ->getElementsByTagName("authenticationSuccess");
  2940. if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
  2941. // no user specified => error
  2942. throw new CAS_AuthenticationException(
  2943. $this, 'Ticket not validated', $validate_url,
  2944. false/*$no_response*/, true/*$bad_response*/, $text_response
  2945. );
  2946. } else {
  2947. $this->_setUser(
  2948. trim(
  2949. $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue
  2950. )
  2951. );
  2952. $this->_readExtraAttributesCas20($success_elements);
  2953. // Store the proxies we are sitting behind for authorization checking
  2954. $proxyList = array();
  2955. if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
  2956. foreach ($arr as $proxyElem) {
  2957. phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue);
  2958. $proxyList[] = trim($proxyElem->nodeValue);
  2959. }
  2960. $this->_setProxies($proxyList);
  2961. phpCAS::trace("Storing Proxy List");
  2962. }
  2963. // Check if the proxies in front of us are allowed
  2964. if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) {
  2965. throw new CAS_AuthenticationException(
  2966. $this, 'Proxy not allowed', $validate_url,
  2967. false/*$no_response*/, true/*$bad_response*/,
  2968. $text_response
  2969. );
  2970. } else {
  2971. $result = true;
  2972. }
  2973. }
  2974. } else {
  2975. throw new CAS_AuthenticationException(
  2976. $this, 'Ticket not validated', $validate_url,
  2977. false/*$no_response*/, true/*$bad_response*/,
  2978. $text_response
  2979. );
  2980. }
  2981. $this->_renameSession($this->getTicket());
  2982. // at this step, Ticket has been validated and $this->_user has been set,
  2983. phpCAS::traceEnd($result);
  2984. return $result;
  2985. }
  2986. /**
  2987. * This method will parse the DOM and pull out the attributes from the XML
  2988. * payload and put them into an array, then put the array into the session.
  2989. *
  2990. * @param DOMNodeList $success_elements payload of the response
  2991. *
  2992. * @return bool true when successfull, halt otherwise by calling
  2993. * CAS_Client::_authError().
  2994. */
  2995. private function _readExtraAttributesCas20($success_elements)
  2996. {
  2997. phpCAS::traceBegin();
  2998. $extra_attributes = array();
  2999. // "Jasig Style" Attributes:
  3000. //
  3001. // <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
  3002. // <cas:authenticationSuccess>
  3003. // <cas:user>jsmith</cas:user>
  3004. // <cas:attributes>
  3005. // <cas:attraStyle>RubyCAS</cas:attraStyle>
  3006. // <cas:surname>Smith</cas:surname>
  3007. // <cas:givenName>John</cas:givenName>
  3008. // <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
  3009. // <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
  3010. // </cas:attributes>
  3011. // <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
  3012. // </cas:authenticationSuccess>
  3013. // </cas:serviceResponse>
  3014. //
  3015. if ($this->_casAttributeParserCallbackFunction !== null
  3016. && is_callable($this->_casAttributeParserCallbackFunction)
  3017. ) {
  3018. array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0));
  3019. phpCAS :: trace("Calling attritubeParser callback");
  3020. $extra_attributes = call_user_func_array(
  3021. $this->_casAttributeParserCallbackFunction,
  3022. $this->_casAttributeParserCallbackArgs
  3023. );
  3024. } elseif ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
  3025. $attr_nodes = $success_elements->item(0)
  3026. ->getElementsByTagName("attributes");
  3027. phpCAS :: trace("Found nested jasig style attributes");
  3028. if ($attr_nodes->item(0)->hasChildNodes()) {
  3029. // Nested Attributes
  3030. foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
  3031. phpCAS :: trace(
  3032. "Attribute [".$attr_child->localName."] = "
  3033. .$attr_child->nodeValue
  3034. );
  3035. $this->_addAttributeToArray(
  3036. $extra_attributes, $attr_child->localName,
  3037. $attr_child->nodeValue
  3038. );
  3039. }
  3040. }
  3041. } else {
  3042. // "RubyCAS Style" attributes
  3043. //
  3044. // <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
  3045. // <cas:authenticationSuccess>
  3046. // <cas:user>jsmith</cas:user>
  3047. //
  3048. // <cas:attraStyle>RubyCAS</cas:attraStyle>
  3049. // <cas:surname>Smith</cas:surname>
  3050. // <cas:givenName>John</cas:givenName>
  3051. // <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
  3052. // <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
  3053. //
  3054. // <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
  3055. // </cas:authenticationSuccess>
  3056. // </cas:serviceResponse>
  3057. //
  3058. phpCAS :: trace("Testing for rubycas style attributes");
  3059. $childnodes = $success_elements->item(0)->childNodes;
  3060. foreach ($childnodes as $attr_node) {
  3061. switch ($attr_node->localName) {
  3062. case 'user':
  3063. case 'proxies':
  3064. case 'proxyGrantingTicket':
  3065. break;
  3066. default:
  3067. if (strlen(trim($attr_node->nodeValue))) {
  3068. phpCAS :: trace(
  3069. "Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue
  3070. );
  3071. $this->_addAttributeToArray(
  3072. $extra_attributes, $attr_node->localName,
  3073. $attr_node->nodeValue
  3074. );
  3075. }
  3076. }
  3077. }
  3078. }
  3079. // "Name-Value" attributes.
  3080. //
  3081. // Attribute format from these mailing list thread:
  3082. // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
  3083. // Note: This is a less widely used format, but in use by at least two institutions.
  3084. //
  3085. // <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
  3086. // <cas:authenticationSuccess>
  3087. // <cas:user>jsmith</cas:user>
  3088. //
  3089. // <cas:attribute name='attraStyle' value='Name-Value' />
  3090. // <cas:attribute name='surname' value='Smith' />
  3091. // <cas:attribute name='givenName' value='John' />
  3092. // <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
  3093. // <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
  3094. //
  3095. // <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
  3096. // </cas:authenticationSuccess>
  3097. // </cas:serviceResponse>
  3098. //
  3099. if (!count($extra_attributes)
  3100. && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0
  3101. ) {
  3102. $attr_nodes = $success_elements->item(0)
  3103. ->getElementsByTagName("attribute");
  3104. $firstAttr = $attr_nodes->item(0);
  3105. if (!$firstAttr->hasChildNodes()
  3106. && $firstAttr->hasAttribute('name')
  3107. && $firstAttr->hasAttribute('value')
  3108. ) {
  3109. phpCAS :: trace("Found Name-Value style attributes");
  3110. // Nested Attributes
  3111. foreach ($attr_nodes as $attr_node) {
  3112. if ($attr_node->hasAttribute('name')
  3113. && $attr_node->hasAttribute('value')
  3114. ) {
  3115. phpCAS :: trace(
  3116. "Attribute [".$attr_node->getAttribute('name')
  3117. ."] = ".$attr_node->getAttribute('value')
  3118. );
  3119. $this->_addAttributeToArray(
  3120. $extra_attributes, $attr_node->getAttribute('name'),
  3121. $attr_node->getAttribute('value')
  3122. );
  3123. }
  3124. }
  3125. }
  3126. }
  3127. $this->setAttributes($extra_attributes);
  3128. phpCAS::traceEnd();
  3129. return true;
  3130. }
  3131. /**
  3132. * Add an attribute value to an array of attributes.
  3133. *
  3134. * @param array &$attributeArray reference to array
  3135. * @param string $name name of attribute
  3136. * @param string $value value of attribute
  3137. *
  3138. * @return void
  3139. */
  3140. private function _addAttributeToArray(array &$attributeArray, $name, $value)
  3141. {
  3142. // If multiple attributes exist, add as an array value
  3143. if (isset($attributeArray[$name])) {
  3144. // Initialize the array with the existing value
  3145. if (!is_array($attributeArray[$name])) {
  3146. $existingValue = $attributeArray[$name];
  3147. $attributeArray[$name] = array($existingValue);
  3148. }
  3149. $attributeArray[$name][] = trim($value);
  3150. } else {
  3151. $attributeArray[$name] = trim($value);
  3152. }
  3153. }
  3154. /** @} */
  3155. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  3156. // XX XX
  3157. // XX MISC XX
  3158. // XX XX
  3159. // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  3160. /**
  3161. * @addtogroup internalMisc
  3162. * @{
  3163. */
  3164. // ########################################################################
  3165. // URL
  3166. // ########################################################################
  3167. /**
  3168. * the URL of the current request (without any ticket CGI parameter). Written
  3169. * and read by CAS_Client::getURL().
  3170. *
  3171. * @hideinitializer
  3172. */
  3173. private $_url = '';
  3174. /**
  3175. * This method sets the URL of the current request
  3176. *
  3177. * @param string $url url to set for service
  3178. *
  3179. * @return void
  3180. */
  3181. public function setURL($url)
  3182. {
  3183. // Argument Validation
  3184. if (gettype($url) != 'string')
  3185. throw new CAS_TypeMismatchException($url, '$url', 'string');
  3186. $this->_url = $url;
  3187. }
  3188. /**
  3189. * This method returns the URL of the current request (without any ticket
  3190. * CGI parameter).
  3191. *
  3192. * @return string The URL
  3193. */
  3194. public function getURL()
  3195. {
  3196. phpCAS::traceBegin();
  3197. // the URL is built when needed only
  3198. if ( empty($this->_url) ) {
  3199. // remove the ticket if present in the URL
  3200. $final_uri = ($this->_isHttps()) ? 'https' : 'http';
  3201. $final_uri .= '://';
  3202. $final_uri .= $this->_getClientUrl();
  3203. $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
  3204. $final_uri .= $request_uri[0];
  3205. if (isset($request_uri[1]) && $request_uri[1]) {
  3206. $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]);
  3207. // If the query string still has anything left,
  3208. // append it to the final URI
  3209. if ($query_string !== '') {
  3210. $final_uri .= "?$query_string";
  3211. }
  3212. }
  3213. phpCAS::trace("Final URI: $final_uri");
  3214. $this->setURL($final_uri);
  3215. }
  3216. phpCAS::traceEnd($this->_url);
  3217. return $this->_url;
  3218. }
  3219. /**
  3220. * This method sets the base URL of the CAS server.
  3221. *
  3222. * @param string $url the base URL
  3223. *
  3224. * @return string base url
  3225. */
  3226. public function setBaseURL($url)
  3227. {
  3228. // Argument Validation
  3229. if (gettype($url) != 'string')
  3230. throw new CAS_TypeMismatchException($url, '$url', 'string');
  3231. return $this->_server['base_url'] = $url;
  3232. }
  3233. /**
  3234. * Try to figure out the phpCAS client URL with possible Proxys / Ports etc.
  3235. *
  3236. * @return string Server URL with domain:port
  3237. */
  3238. private function _getClientUrl()
  3239. {
  3240. if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
  3241. // explode the host list separated by comma and use the first host
  3242. $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
  3243. // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default
  3244. return $hosts[0];
  3245. } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
  3246. $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
  3247. } else {
  3248. if (empty($_SERVER['SERVER_NAME'])) {
  3249. $server_url = $_SERVER['HTTP_HOST'];
  3250. } else {
  3251. $server_url = $_SERVER['SERVER_NAME'];
  3252. }
  3253. }
  3254. if (!strpos($server_url, ':')) {
  3255. if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
  3256. $server_port = $_SERVER['SERVER_PORT'];
  3257. } else {
  3258. $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
  3259. $server_port = $ports[0];
  3260. }
  3261. if ( ($this->_isHttps() && $server_port!=443)
  3262. || (!$this->_isHttps() && $server_port!=80)
  3263. ) {
  3264. $server_url .= ':';
  3265. $server_url .= $server_port;
  3266. }
  3267. }
  3268. return $server_url;
  3269. }
  3270. /**
  3271. * This method checks to see if the request is secured via HTTPS
  3272. *
  3273. * @return bool true if https, false otherwise
  3274. */
  3275. private function _isHttps()
  3276. {
  3277. if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
  3278. return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
  3279. } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) {
  3280. return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https');
  3281. } elseif ( isset($_SERVER['HTTPS'])
  3282. && !empty($_SERVER['HTTPS'])
  3283. && strcasecmp($_SERVER['HTTPS'], 'off') !== 0
  3284. ) {
  3285. return true;
  3286. }
  3287. return false;
  3288. }
  3289. /**
  3290. * Removes a parameter from a query string
  3291. *
  3292. * @param string $parameterName name of parameter
  3293. * @param string $queryString query string
  3294. *
  3295. * @return string new query string
  3296. *
  3297. * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
  3298. */
  3299. private function _removeParameterFromQueryString($parameterName, $queryString)
  3300. {
  3301. $parameterName = preg_quote($parameterName);
  3302. return preg_replace(
  3303. "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/",
  3304. '', $queryString
  3305. );
  3306. }
  3307. /**
  3308. * This method is used to append query parameters to an url. Since the url
  3309. * might already contain parameter it has to be detected and to build a proper
  3310. * URL
  3311. *
  3312. * @param string $url base url to add the query params to
  3313. * @param string $query params in query form with & separated
  3314. *
  3315. * @return string url with query params
  3316. */
  3317. private function _buildQueryUrl($url, $query)
  3318. {
  3319. $url .= (strstr($url, '?') === false) ? '?' : '&';
  3320. $url .= $query;
  3321. return $url;
  3322. }
  3323. /**
  3324. * Renaming the session
  3325. *
  3326. * @param string $ticket name of the ticket
  3327. *
  3328. * @return void
  3329. */
  3330. private function _renameSession($ticket)
  3331. {
  3332. phpCAS::traceBegin();
  3333. if ($this->getChangeSessionID()) {
  3334. if (!empty($this->_user)) {
  3335. $old_session = $_SESSION;
  3336. phpCAS :: trace("Killing session: ". session_id());
  3337. session_destroy();
  3338. // set up a new session, of name based on the ticket
  3339. $session_id = $this->_sessionIdForTicket($ticket);
  3340. phpCAS :: trace("Starting session: ". $session_id);
  3341. session_id($session_id);
  3342. session_start();
  3343. phpCAS :: trace("Restoring old session vars");
  3344. $_SESSION = $old_session;
  3345. } else {
  3346. phpCAS :: trace (
  3347. 'Session should only be renamed after successfull authentication'
  3348. );
  3349. }
  3350. } else {
  3351. phpCAS :: trace(
  3352. "Skipping session rename since phpCAS is not handling the session."
  3353. );
  3354. }
  3355. phpCAS::traceEnd();
  3356. }
  3357. /**
  3358. * Answer a valid session-id given a CAS ticket.
  3359. *
  3360. * The output must be deterministic to allow single-log-out when presented with
  3361. * the ticket to log-out.
  3362. *
  3363. *
  3364. * @param string $ticket name of the ticket
  3365. *
  3366. * @return string
  3367. */
  3368. private function _sessionIdForTicket($ticket)
  3369. {
  3370. // Hash the ticket to ensure that the value meets the PHP 7.1 requirement
  3371. // that session-ids have a length between 22 and 256 characters.
  3372. return hash('sha256', $this->_sessionIdSalt . $ticket);
  3373. }
  3374. /**
  3375. * Set a salt/seed for the session-id hash to make it harder to guess.
  3376. *
  3377. * @var string $_sessionIdSalt
  3378. */
  3379. private $_sessionIdSalt = '';
  3380. /**
  3381. * Set a salt/seed for the session-id hash to make it harder to guess.
  3382. *
  3383. * @param string $salt
  3384. *
  3385. * @return void
  3386. */
  3387. public function setSessionIdSalt($salt) {
  3388. $this->_sessionIdSalt = (string)$salt;
  3389. }
  3390. // ########################################################################
  3391. // AUTHENTICATION ERROR HANDLING
  3392. // ########################################################################
  3393. /**
  3394. * This method is used to print the HTML output when the user was not
  3395. * authenticated.
  3396. *
  3397. * @param string $failure the failure that occured
  3398. * @param string $cas_url the URL the CAS server was asked for
  3399. * @param bool $no_response the response from the CAS server (other
  3400. * parameters are ignored if true)
  3401. * @param bool $bad_response bad response from the CAS server ($err_code
  3402. * and $err_msg ignored if true)
  3403. * @param string $cas_response the response of the CAS server
  3404. * @param int $err_code the error code given by the CAS server
  3405. * @param string $err_msg the error message given by the CAS server
  3406. *
  3407. * @return void
  3408. */
  3409. private function _authError(
  3410. $failure,
  3411. $cas_url,
  3412. $no_response=false,
  3413. $bad_response=false,
  3414. $cas_response='',
  3415. $err_code=-1,
  3416. $err_msg=''
  3417. ) {
  3418. phpCAS::traceBegin();
  3419. $lang = $this->getLangObj();
  3420. $this->printHTMLHeader($lang->getAuthenticationFailed());
  3421. printf(
  3422. $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()),
  3423. isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:''
  3424. );
  3425. phpCAS::trace('CAS URL: '.$cas_url);
  3426. phpCAS::trace('Authentication failure: '.$failure);
  3427. if ( $no_response ) {
  3428. phpCAS::trace('Reason: no response from the CAS server');
  3429. } else {
  3430. if ( $bad_response ) {
  3431. phpCAS::trace('Reason: bad response from the CAS server');
  3432. } else {
  3433. switch ($this->getServerVersion()) {
  3434. case CAS_VERSION_1_0:
  3435. phpCAS::trace('Reason: CAS error');
  3436. break;
  3437. case CAS_VERSION_2_0:
  3438. case CAS_VERSION_3_0:
  3439. if ( $err_code === -1 ) {
  3440. phpCAS::trace('Reason: no CAS error');
  3441. } else {
  3442. phpCAS::trace(
  3443. 'Reason: ['.$err_code.'] CAS error: '.$err_msg
  3444. );
  3445. }
  3446. break;
  3447. }
  3448. }
  3449. phpCAS::trace('CAS response: '.$cas_response);
  3450. }
  3451. $this->printHTMLFooter();
  3452. phpCAS::traceExit();
  3453. throw new CAS_GracefullTerminationException();
  3454. }
  3455. // ########################################################################
  3456. // PGTIOU/PGTID and logoutRequest rebroadcasting
  3457. // ########################################################################
  3458. /**
  3459. * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and
  3460. * array of the nodes.
  3461. */
  3462. private $_rebroadcast = false;
  3463. private $_rebroadcast_nodes = array();
  3464. /**
  3465. * Constants used for determining rebroadcast node type.
  3466. */
  3467. const HOSTNAME = 0;
  3468. const IP = 1;
  3469. /**
  3470. * Determine the node type from the URL.
  3471. *
  3472. * @param String $nodeURL The node URL.
  3473. *
  3474. * @return int hostname
  3475. *
  3476. */
  3477. private function _getNodeType($nodeURL)
  3478. {
  3479. phpCAS::traceBegin();
  3480. if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) {
  3481. phpCAS::traceEnd(self::IP);
  3482. return self::IP;
  3483. } else {
  3484. phpCAS::traceEnd(self::HOSTNAME);
  3485. return self::HOSTNAME;
  3486. }
  3487. }
  3488. /**
  3489. * Store the rebroadcast node for pgtIou/pgtId and logout requests.
  3490. *
  3491. * @param string $rebroadcastNodeUrl The rebroadcast node URL.
  3492. *
  3493. * @return void
  3494. */
  3495. public function addRebroadcastNode($rebroadcastNodeUrl)
  3496. {
  3497. // Argument validation
  3498. if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl))
  3499. throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url');
  3500. // Store the rebroadcast node and set flag
  3501. $this->_rebroadcast = true;
  3502. $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl;
  3503. }
  3504. /**
  3505. * An array to store extra rebroadcast curl options.
  3506. */
  3507. private $_rebroadcast_headers = array();
  3508. /**
  3509. * This method is used to add header parameters when rebroadcasting
  3510. * pgtIou/pgtId or logoutRequest.
  3511. *
  3512. * @param string $header Header to send when rebroadcasting.
  3513. *
  3514. * @return void
  3515. */
  3516. public function addRebroadcastHeader($header)
  3517. {
  3518. if (gettype($header) != 'string')
  3519. throw new CAS_TypeMismatchException($header, '$header', 'string');
  3520. $this->_rebroadcast_headers[] = $header;
  3521. }
  3522. /**
  3523. * Constants used for determining rebroadcast type (logout or pgtIou/pgtId).
  3524. */
  3525. const LOGOUT = 0;
  3526. const PGTIOU = 1;
  3527. /**
  3528. * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU
  3529. *
  3530. * @param int $type type of rebroadcasting.
  3531. *
  3532. * @return void
  3533. */
  3534. private function _rebroadcast($type)
  3535. {
  3536. phpCAS::traceBegin();
  3537. $rebroadcast_curl_options = array(
  3538. CURLOPT_FAILONERROR => 1,
  3539. CURLOPT_FOLLOWLOCATION => 1,
  3540. CURLOPT_RETURNTRANSFER => 1,
  3541. CURLOPT_CONNECTTIMEOUT => 1,
  3542. CURLOPT_TIMEOUT => 4);
  3543. // Try to determine the IP address of the server
  3544. if (!empty($_SERVER['SERVER_ADDR'])) {
  3545. $ip = $_SERVER['SERVER_ADDR'];
  3546. } else if (!empty($_SERVER['LOCAL_ADDR'])) {
  3547. // IIS 7
  3548. $ip = $_SERVER['LOCAL_ADDR'];
  3549. }
  3550. // Try to determine the DNS name of the server
  3551. if (!empty($ip)) {
  3552. $dns = gethostbyaddr($ip);
  3553. }
  3554. $multiClassName = 'CAS_Request_CurlMultiRequest';
  3555. $multiRequest = new $multiClassName();
  3556. for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) {
  3557. if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false))
  3558. || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))
  3559. ) {
  3560. phpCAS::trace(
  3561. 'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i]
  3562. .$_SERVER['REQUEST_URI']
  3563. );
  3564. $className = $this->_requestImplementation;
  3565. $request = new $className();
  3566. $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI'];
  3567. $request->setUrl($url);
  3568. if (count($this->_rebroadcast_headers)) {
  3569. $request->addHeaders($this->_rebroadcast_headers);
  3570. }
  3571. $request->makePost();
  3572. if ($type == self::LOGOUT) {
  3573. // Logout request
  3574. $request->setPostBody(
  3575. 'rebroadcast=false&logoutRequest='.$_POST['logoutRequest']
  3576. );
  3577. } else if ($type == self::PGTIOU) {
  3578. // pgtIou/pgtId rebroadcast
  3579. $request->setPostBody('rebroadcast=false');
  3580. }
  3581. $request->setCurlOptions($rebroadcast_curl_options);
  3582. $multiRequest->addRequest($request);
  3583. } else {
  3584. phpCAS::trace(
  3585. 'Rebroadcast not sent to self: '
  3586. .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'')
  3587. .'/'.(!empty($dns)?$dns:'')
  3588. );
  3589. }
  3590. }
  3591. // We need at least 1 request
  3592. if ($multiRequest->getNumRequests() > 0) {
  3593. $multiRequest->send();
  3594. }
  3595. phpCAS::traceEnd();
  3596. }
  3597. /** @} */
  3598. }