Client.php 150 KB

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