ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
client.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * * Redistributions of source code must retain the above copyright notice,
11  * this list of conditions and the following disclaimer.
12  * * Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  * * Neither the name of the ESUP-Portail consortium & the JA-SIG
16  * Collaborative nor the names of its contributors may be used to endorse or
17  * promote products derived from this software without specific prior
18  * written permission.
19 
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
37 // include internationalization stuff
38 include_once(dirname(__FILE__).'/languages/languages.php');
39 
40 // include PGT storage classes
41 include_once(dirname(__FILE__).'/PGTStorage/pgt-main.php');
42 
51 class CASClient
52 {
53 
54  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
55  // XX XX
56  // XX CONFIGURATION XX
57  // XX XX
58  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
59 
60  // ########################################################################
61  // HTML OUTPUT
62  // ########################################################################
81  function HTMLFilterOutput($str)
82  {
83  $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
84  $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
85  $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
86  echo $str;
87  }
88 
97  var $_output_header = '';
98 
109  {
110  $this->HTMLFilterOutput(str_replace('__TITLE__',
111  $title,
112  (empty($this->_output_header)
113  ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
114  : $this->_output_header)
115  )
116  );
117  }
118 
127  var $_output_footer = '';
128 
136  function printHTMLFooter()
137  {
138  $this->HTMLFilterOutput(empty($this->_output_footer)
139  ?('<hr><address>phpCAS __PHPCAS_VERSION__ '.$this->getString(CAS_STR_USING_SERVER).' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>')
140  :$this->_output_footer);
141  }
142 
150  function setHTMLHeader($header)
151  {
152  $this->_output_header = $header;
153  }
154 
162  function setHTMLFooter($footer)
163  {
164  $this->_output_footer = $footer;
165  }
166 
168  // ########################################################################
169  // INTERNATIONALIZATION
170  // ########################################################################
185  var $_lang = '';
186 
194  function getLang()
195  {
196  if ( empty($this->_lang) )
198  return $this->_lang;
199  }
200 
211 
221  function getString($str)
222  {
223  // call CASclient::getLang() to be sure the language is initialized
224  $this->getLang();
225 
226  if ( !isset($this->_strings[$str]) ) {
227  trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
228  }
229  return $this->_strings[$str];
230  }
231 
241  function setLang($lang)
242  {
243  // include the corresponding language file
244  include_once(dirname(__FILE__).'/languages/'.$lang.'.php');
245 
246  if ( !is_array($this->_strings) ) {
247  trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
248  }
249  $this->_lang = $lang;
250  }
251 
253  // ########################################################################
254  // CAS SERVER CONFIG
255  // ########################################################################
285  var $_server = array(
286  'version' => -1,
287  'hostname' => 'none',
288  'port' => -1,
289  'uri' => 'none'
290  );
291 
297  function getServerVersion()
298  {
299  return $this->_server['version'];
300  }
301 
307  function getServerHostname()
308  { return $this->_server['hostname']; }
309 
315  function getServerPort()
316  { return $this->_server['port']; }
317 
323  function getServerURI()
324  { return $this->_server['uri']; }
325 
331  function getServerBaseURL()
332  {
333  // the URL is build only when needed
334  if ( empty($this->_server['base_url']) ) {
335  $this->_server['base_url'] = 'https://'
336  .$this->getServerHostname()
337  .':'
338  .$this->getServerPort()
339  .$this->getServerURI();
340  }
341  return $this->_server['base_url'];
342  }
343 
353  function getServerLoginURL($gateway=false,$renew=false) {
355  // the URL is build only when needed
356  if ( empty($this->_server['login_url']) ) {
357  $this->_server['login_url'] = $this->getServerBaseURL();
358  $this->_server['login_url'] .= 'login?service=';
359  // $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL());
360  $this->_server['login_url'] .= urlencode($this->getURL());
361  if($renew) {
362  // It is recommended that when the "renew" parameter is set, its value be "true"
363  $this->_server['login_url'] .= '&renew=true';
364  } elseif ($gateway) {
365  // It is recommended that when the "gateway" parameter is set, its value be "true"
366  $this->_server['login_url'] .= '&gateway=true';
367  }
368  }
369  phpCAS::traceEnd($this->_server['login_url']);
370  return $this->_server['login_url'];
371  }
372 
379  function setServerLoginURL($url)
380  {
381  return $this->_server['login_url'] = $url;
382  }
383 
384 
392  {
393  return $this->_server['service_validate_url'] = $url;
394  }
395 
396 
404  {
405  return $this->_server['proxy_validate_url'] = $url;
406  }
407 
408 
416  {
417  return $this->_server['saml_validate_url'] = $url;
418  }
419 
420 
427  {
428  // the URL is build only when needed
429  if ( empty($this->_server['service_validate_url']) ) {
430  switch ($this->getServerVersion()) {
431  case CAS_VERSION_1_0:
432  $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
433  break;
434  case CAS_VERSION_2_0:
435  $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
436  break;
437  }
438  }
439  // return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
440  return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL());
441  }
448  {
450  // the URL is build only when needed
451  if ( empty($this->_server['saml_validate_url']) ) {
452  switch ($this->getServerVersion()) {
453  case SAML_VERSION_1_1:
454  $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
455  break;
456  }
457  }
458  phpCAS::traceEnd($this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL()));
459  return $this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL());
460  }
467  {
468  // the URL is build only when needed
469  if ( empty($this->_server['proxy_validate_url']) ) {
470  switch ($this->getServerVersion()) {
471  case CAS_VERSION_1_0:
472  $this->_server['proxy_validate_url'] = '';
473  break;
474  case CAS_VERSION_2_0:
475  $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
476  break;
477  }
478  }
479  // return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
480  return $this->_server['proxy_validate_url'].'?service='.urlencode($this->getURL());
481  }
482 
488  function getServerProxyURL()
489  {
490  // the URL is build only when needed
491  if ( empty($this->_server['proxy_url']) ) {
492  switch ($this->getServerVersion()) {
493  case CAS_VERSION_1_0:
494  $this->_server['proxy_url'] = '';
495  break;
496  case CAS_VERSION_2_0:
497  $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
498  break;
499  }
500  }
501  return $this->_server['proxy_url'];
502  }
503 
510  {
511  // the URL is build only when needed
512  if ( empty($this->_server['logout_url']) ) {
513  $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
514  }
515  return $this->_server['logout_url'];
516  }
517 
524  function setServerLogoutURL($url)
525  {
526  return $this->_server['logout_url'] = $url;
527  }
528 
532  var $_curl_options = array();
533 
537  function setExtraCurlOption($key, $value)
538  {
539  $this->_curl_options[$key] = $value;
540  }
541 
547  function isHttps() {
548  //if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ) {
549  //0.4.24 by Hinnack
550  if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
551  return false;
552  } else {
553  return false;
554  }
555  }
556 
557  // ########################################################################
558  // CONSTRUCTOR
559  // ########################################################################
574  function CASClient(
575  $server_version,
576  $proxy,
577  $server_hostname,
578  $server_port,
579  $server_uri,
580  $start_session = true) {
581 
583 
584  // the redirect header() call and DOM parsing code from domxml-php4-php5.php won't work in PHP4 compatibility mode
585  if (version_compare(PHP_VERSION,'5','>=') && ini_get('zend.ze1_compatibility_mode')) {
586  phpCAS::error('phpCAS cannot support zend.ze1_compatibility_mode. Sorry.');
587  }
588  $this->_start_session = $start_session;
589 
590  if ($this->_start_session && session_id())
591  {
592  phpCAS :: error("Another session was started before phpcas. Either disable the session" .
593  " handling for phpcas in the client() call or modify your application to leave" .
594  " session handling to phpcas");
595  }
596  // skip Session Handling for logout requests and if don't want it'
597  if ($start_session && !$this->isLogoutRequest())
598  {
599  phpCAS :: trace("Starting a new session");
600  session_start();
601  }
602 
603 
604  // are we in proxy mode ?
605  $this->_proxy = $proxy;
606 
607  //check version
608  switch ($server_version) {
609  case CAS_VERSION_1_0:
610  if ( $this->isProxy() )
611  phpCAS::error('CAS proxies are not supported in CAS '
612  .$server_version);
613  break;
614  case CAS_VERSION_2_0:
615  break;
616  case SAML_VERSION_1_1:
617  break;
618  default:
619  phpCAS::error('this version of CAS (`'
620  .$server_version
621  .'\') is not supported by phpCAS '
622  .phpCAS::getVersion());
623  }
624  $this->_server['version'] = $server_version;
625 
626  // check hostname
627  if ( empty($server_hostname)
628  || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
629  phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
630  }
631  $this->_server['hostname'] = $server_hostname;
632 
633  // check port
634  if ( $server_port == 0
635  || !is_int($server_port) ) {
636  phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
637  }
638  $this->_server['port'] = $server_port;
639 
640  // check URI
641  if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
642  phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
643  }
644  // add leading and trailing `/' and remove doubles
645  $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
646  $this->_server['uri'] = $server_uri;
647 
648  // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
649  if ( $this->isProxy() ) {
650  $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
651  }
652 
653  if ( $this->isCallbackMode() ) {
654  //callback mode: check that phpCAS is secured
655  if ( !$this->isHttps() ) {
656  phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
657  }
658  } else {
659  //normal mode: get ticket and remove it from CGI parameters for developpers
660  $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
661  switch ($this->getServerVersion()) {
662  case CAS_VERSION_1_0: // check for a Service Ticket
663  if( preg_match('/^ST-/',$ticket) ) {
664  phpCAS::trace('ST \''.$ticket.'\' found');
665  //ST present
666  $this->setST($ticket);
667  //ticket has been taken into account, unset it to hide it to applications
668  unset($_GET['ticket']);
669  } else if ( !empty($ticket) ) {
670  //ill-formed ticket, halt
671  phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
672  }
673  break;
674  case CAS_VERSION_2_0: // check for a Service or Proxy Ticket
675  if( preg_match('/^ST-/',$ticket) ) {
676  phpCAS::trace('ST \''.$ticket.'\' found');
677  $this->setST($ticket);
678  unset($_GET['ticket']);
679  }
680  elseif(preg_match('/PT-', $ticket)) {
681  phpCAS::trace('PT \''.$ticket.'\' found');
682  $this->setPT($ticket);
683  unset($_GET['ticket']);
684  } else if ( !empty($ticket) ) {
685  //ill-formed ticket, halt
686  phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
687  }
688  break;
689  case SAML_VERSION_1_1: // SAML just does Service Tickets
690  if( preg_match('/^[SP]T-/',$ticket) ) {
691  phpCAS::trace('SA \''.$ticket.'\' found');
692  $this->setSA($ticket);
693  unset($_GET['ticket']);
694  } else if ( !empty($ticket) ) {
695  //ill-formed ticket, halt
696  phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
697  }
698  break;
699  }
700  }
702  }
703 
706  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
707  // XX XX
708  // XX Session Handling XX
709  // XX XX
710  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
711 
717  var $_start_session = true;
718 
719  function setStartSession($session)
720  {
721  $this->_start_session = session;
722  }
723 
724  function getStartSession($session)
725  {
726  $this->_start_session = session;
727  }
728 
732  function renameSession($ticket)
733  {
735  if($this->_start_session){
736  if (!empty ($this->_user))
737  {
738  $old_session = $_SESSION;
739  session_destroy();
740 
741  // Fix for http://bugs.php.net/bug.php?id=32330
742  if(version_compare(PHP_VERSION, '5.3.0', '<'))
743  {
744  include_once './Services/Init/classes/class.ilInitialisation.php';
745  $init = new ilInitialisation();
746  $init->setSessionHandler();
747  }
748 
749  // set up a new session, of name based on the ticket
750  $session_id = preg_replace('/[^\w]/', '', $ticket);
751  phpCAS :: trace("Session ID: ".$session_id);
752  session_id($session_id);
753  session_start();
754  phpCAS :: trace("Restoring old session vars");
755  $_SESSION = $old_session;
756  } else
757  {
758  phpCAS :: error('Session should only be renamed after successfull authentication');
759  }
760  }else{
761  phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
762  }
763  phpCAS::traceEnd();
764  }
765 
766  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
767  // XX XX
768  // XX AUTHENTICATION XX
769  // XX XX
770  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
771 
784  var $_user = '';
785 
793  function setUser($user)
794  {
795  $this->_user = $user;
796  }
797 
805  function getUser()
806  {
807  if ( empty($this->_user) ) {
808  phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
809  }
810  return $this->_user;
811  }
812 
813 
814 
815  /***********************************************************************************************************************
816  * Atrributes section
817  *
818  * @author Matthias Crauwels <matthias.crauwels@ugent.be>, Ghent University, Belgium
819  *
820  ***********************************************************************************************************************/
828  var $_attributes = array();
829 
830  function setAttributes($attributes)
831  { $this->_attributes = $attributes; }
832 
833  function getAttributes() {
834  if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also...
835  phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
836  }
837  return $this->_attributes;
838  }
839 
840  function hasAttributes()
841  { return !empty($this->_attributes); }
842 
843  function hasAttribute($key)
844  { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
845 
846  function getAttribute($key) {
847  if($this->hasAttribute($key)) {
848  return $this->_attributes[$key];
849  }
850  }
851 
860  // Either way, the user is authenticated by CAS
861  if( isset( $_SESSION['phpCAS']['auth_checked'] ) )
862  unset($_SESSION['phpCAS']['auth_checked']);
863  if ( $this->isAuthenticated() ) {
864  phpCAS::trace('user already authenticated; renew');
865  $this->redirectToCas(false,true);
866  } else {
867  $this->redirectToCas();
868  }
870  }
871 
879  {
881 
882  if ( $this->isAuthenticated() ) {
883  // the user is authenticated, nothing to be done.
884  phpCAS::trace('no need to authenticate');
885  $res = TRUE;
886  } else {
887  // the user is not authenticated, redirect to the CAS server
888  if (isset($_SESSION['phpCAS']['auth_checked'])) {
889  unset($_SESSION['phpCAS']['auth_checked']);
890  }
891  $this->redirectToCas(FALSE/* no gateway */);
892  // never reached
893  $res = FALSE;
894  }
896  return $res;
897  }
898 
906 
915  {
916  $this->_cache_times_for_auth_recheck = $n;
917  }
918 
925  {
927 
928  if ( $this->isAuthenticated() ) {
929  phpCAS::trace('user is authenticated');
930  $res = TRUE;
931  } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
932  // the previous request has redirected the client to the CAS server with gateway=true
933  unset($_SESSION['phpCAS']['auth_checked']);
934  $res = FALSE;
935  } else {
936  // $_SESSION['phpCAS']['auth_checked'] = true;
937  // $this->redirectToCas(TRUE/* gateway */);
938  // // never reached
939  // $res = FALSE;
940  // avoid a check against CAS on every request
941  if (! isset($_SESSION['phpCAS']['unauth_count']) )
942  $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
943 
944  if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1)
945  || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck))
946  {
947  $res = FALSE;
948 
949  if ($this->_cache_times_for_auth_recheck != -1)
950  {
951  $_SESSION['phpCAS']['unauth_count']++;
952  phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')');
953  }
954  else
955  {
956  phpCAS::trace('user is not authenticated (cached for until login pressed)');
957  }
958  }
959  else
960  {
961  $_SESSION['phpCAS']['unauth_count'] = 0;
962  $_SESSION['phpCAS']['auth_checked'] = true;
963  phpCAS::trace('user is not authenticated (cache reset)');
964  $this->redirectToCas(TRUE/* gateway */);
965  // never reached
966  $res = FALSE;
967  }
968  }
970  return $res;
971  }
972 
981  function isAuthenticated()
982  {
984  $res = FALSE;
985  $validate_url = '';
986 
987  if ( $this->wasPreviouslyAuthenticated() ) {
988  if($this->hasST() || $this->hasPT() || $this->hasSA()){
989  // User has a additional ticket but was already authenticated
990  phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
991  header('Location: '.$this->getURL());
992  phpCAS::log( "Prepare redirect to remove ticket: ".$this->getURL() );
993  }else{
994  // the user has already (previously during the session) been
995  // authenticated, nothing to be done.
996  phpCAS::trace('user was already authenticated, no need to look for tickets');
997  }
998  $res = TRUE;
999  }
1000  else {
1001  if ( $this->hasST() ) {
1002  // if a Service Ticket was given, validate it
1003  phpCAS::trace('ST `'.$this->getST().'\' is present');
1004  $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
1005  phpCAS::trace('ST `'.$this->getST().'\' was validated');
1006  if ( $this->isProxy() ) {
1007  $this->validatePGT($validate_url,$text_response,$tree_response); // idem
1008  phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
1009  $_SESSION['phpCAS']['pgt'] = $this->getPGT();
1010  }
1011  $_SESSION['phpCAS']['user'] = $this->getUser();
1012  $res = TRUE;
1013  }
1014  elseif ( $this->hasPT() ) {
1015  // if a Proxy Ticket was given, validate it
1016  phpCAS::trace('PT `'.$this->getPT().'\' is present');
1017  $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
1018  phpCAS::trace('PT `'.$this->getPT().'\' was validated');
1019  if ( $this->isProxy() ) {
1020  $this->validatePGT($validate_url,$text_response,$tree_response); // idem
1021  phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
1022  $_SESSION['phpCAS']['pgt'] = $this->getPGT();
1023  }
1024  $_SESSION['phpCAS']['user'] = $this->getUser();
1025  $res = TRUE;
1026  }
1027  elseif ( $this->hasSA() ) {
1028  // if we have a SAML ticket, validate it.
1029  phpCAS::trace('SA `'.$this->getSA().'\' is present');
1030  $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
1031  phpCAS::trace('SA `'.$this->getSA().'\' was validated');
1032  $_SESSION['phpCAS']['user'] = $this->getUser();
1033  $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1034  $res = TRUE;
1035  }
1036  else {
1037  // no ticket given, not authenticated
1038  phpCAS::trace('no ticket found');
1039  }
1040  if ($res) {
1041  // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
1042  // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
1043  header('Location: '.$this->getURL());
1044  phpCAS::log( "Prepare redirect to : ".$this->getURL() );
1045  }
1046  }
1047 
1049  return $res;
1050  }
1051 
1058  {
1059  return !empty($_SESSION['phpCAS']['user']);
1060  }
1061 
1073  {
1075 
1076  if ( $this->isCallbackMode() ) {
1077  $this->callback();
1078  }
1079 
1080  $auth = FALSE;
1081 
1082  if ( $this->isProxy() ) {
1083  // CAS proxy: username and PGT must be present
1084  if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1085  // authentication already done
1086  $this->setUser($_SESSION['phpCAS']['user']);
1087  $this->setPGT($_SESSION['phpCAS']['pgt']);
1088  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
1089  $auth = TRUE;
1090  } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
1091  // these two variables should be empty or not empty at the same time
1092  phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
1093  // unset all tickets to enforce authentication
1094  unset($_SESSION['phpCAS']);
1095  $this->setST('');
1096  $this->setPT('');
1097  } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1098  // these two variables should be empty or not empty at the same time
1099  phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
1100  // unset all tickets to enforce authentication
1101  unset($_SESSION['phpCAS']);
1102  $this->setST('');
1103  $this->setPT('');
1104  } else {
1105  phpCAS::trace('neither user not PGT found');
1106  }
1107  } else {
1108  // `simple' CAS client (not a proxy): username must be present
1109  if ( $this->isSessionAuthenticated() ) {
1110  // authentication already done
1111  $this->setUser($_SESSION['phpCAS']['user']);
1112  if(isset($_SESSION['phpCAS']['attributes'])){
1113  $this->setAttributes($_SESSION['phpCAS']['attributes']);
1114  }
1115  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
1116  $auth = TRUE;
1117  } else {
1118  phpCAS::trace('no user found');
1119  }
1120  }
1121 
1123  return $auth;
1124  }
1125 
1133  function redirectToCas($gateway=false,$renew=false){
1135  $cas_url = $this->getServerLoginURL($gateway,$renew);
1136  header('Location: '.$cas_url);
1137  phpCAS::log( "Redirect to : ".$cas_url );
1138 
1140 
1141  printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
1142  $this->printHTMLFooter();
1143 
1145  exit();
1146  }
1147 
1148 
1154  function logout($params) {
1156  $cas_url = $this->getServerLogoutURL();
1157  $paramSeparator = '?';
1158  if (isset($params['url'])) {
1159  $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']);
1160  $paramSeparator = '&';
1161  }
1162  if (isset($params['service'])) {
1163  $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
1164  }
1165  header('Location: '.$cas_url);
1166  phpCAS::log( "Prepare redirect to : ".$cas_url );
1167 
1168  session_unset();
1169  session_destroy();
1170 
1171  $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
1172  printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
1173  $this->printHTMLFooter();
1174 
1176  exit();
1177  }
1178 
1183  function isLogoutRequest() {
1184  return !empty($_POST['logoutRequest']);
1185  }
1186 
1192  }
1193 
1202  function handleLogoutRequests($check_client=true, $allowed_clients=false) {
1204  if (!$this->isLogoutRequest()) {
1205  phpCAS::log("Not a logout request");
1206  phpCAS::traceEnd();
1207  return;
1208  }
1209  if(!$this->_start_session){
1210  phpCAS::log("phpCAS can't handle logout requests if it does not manage the session.");
1211  }
1212  phpCAS::log("Logout requested");
1213  phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']);
1214  if ($check_client) {
1215  if (!$allowed_clients) {
1216  $allowed_clients = array( $this->getServerHostname() );
1217  }
1218  $client_ip = $_SERVER['REMOTE_ADDR'];
1219  $client = gethostbyaddr($client_ip);
1220  phpCAS::log("Client: ".$client."/".$client_ip);
1221  $allowed = false;
1222  foreach ($allowed_clients as $allowed_client) {
1223  if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
1224  phpCAS::log("Allowed client '".$allowed_client."' matches, logout request is allowed");
1225  $allowed = true;
1226  break;
1227  } else {
1228  phpCAS::log("Allowed client '".$allowed_client."' does not match");
1229  }
1230  }
1231  if (!$allowed) {
1232  phpCAS::error("Unauthorized logout request from client '".$client."'");
1233  printf("Unauthorized!");
1235  exit();
1236  }
1237  } else {
1238  phpCAS::log("No access control set");
1239  }
1240  // Extract the ticket from the SAML Request
1241  preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3);
1242  $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|','',$tick[0][0]);
1243  $ticket2logout = preg_replace('|</samlp:SessionIndex>|','',$wrappedSamlSessionIndex);
1244  phpCAS::log("Ticket to logout: ".$ticket2logout);
1245  $session_id = preg_replace('/[^\w]/','',$ticket2logout);
1246  phpCAS::log("Session id: ".$session_id);
1247 
1248  // destroy a possible application session created before phpcas
1249  if(session_id()){
1250  session_unset();
1251  session_destroy();
1252  }
1253  // fix session ID
1254  session_id($session_id);
1255  $_COOKIE[session_name()]=$session_id;
1256  $_GET[session_name()]=$session_id;
1257 
1258  // Overwrite session
1259  session_start();
1260  session_unset();
1261  session_destroy();
1262  printf("Disconnected!");
1264  exit();
1265  }
1266 
1269  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1270  // XX XX
1271  // XX BASIC CLIENT FEATURES (CAS 1.0) XX
1272  // XX XX
1273  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1274 
1275  // ########################################################################
1276  // ST
1277  // ########################################################################
1291  var $_st = '';
1292 
1298  function getST()
1299  { return $this->_st; }
1300 
1306  function setST($st)
1307  { $this->_st = $st; }
1308 
1314  function hasST()
1315  { return !empty($this->_st); }
1316 
1319  // ########################################################################
1320  // ST VALIDATION
1321  // ########################################################################
1334 
1342 
1350 
1356  function setCasServerCert($cert)
1357  {
1358  $this->_cas_server_cert = $cert;
1359  }
1360 
1366  function setCasServerCACert($cert)
1367  {
1368  $this->_cas_server_ca_cert = $cert;
1369  }
1370 
1375  {
1376  $this->_no_cas_server_validation = true;
1377  }
1378 
1392  function validateST($validate_url,&$text_response,&$tree_response)
1393  {
1395  // build the URL to validate the ticket
1396  $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
1397  if ( $this->isProxy() ) {
1398  // pass the callback url for CAS proxies
1399  $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
1400  }
1401 
1402  // open and read the URL
1403  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
1404  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1405  $this->authError('ST not validated',
1406  $validate_url,
1407  TRUE/*$no_response*/);
1408  }
1409 
1410  // analyze the result depending on the version
1411  switch ($this->getServerVersion()) {
1412  case CAS_VERSION_1_0:
1413  if (preg_match('/^no\n/',$text_response)) {
1414  phpCAS::trace('ST has not been validated');
1415  $this->authError('ST not validated',
1416  $validate_url,
1417  FALSE/*$no_response*/,
1418  FALSE/*$bad_response*/,
1419  $text_response);
1420  }
1421  if (!preg_match('/^yes\n/',$text_response)) {
1422  phpCAS::trace('ill-formed response');
1423  $this->authError('ST not validated',
1424  $validate_url,
1425  FALSE/*$no_response*/,
1426  TRUE/*$bad_response*/,
1427  $text_response);
1428  }
1429  // ST has been validated, extract the user name
1430  $arr = preg_split('/\n/',$text_response);
1431  $this->setUser(trim($arr[1]));
1432  break;
1433  case CAS_VERSION_2_0:
1434  // read the response of the CAS server into a DOM object
1435  if ( !($dom = domxml_open_mem($text_response))) {
1436  phpCAS::trace('domxml_open_mem() failed');
1437  $this->authError('ST not validated',
1438  $validate_url,
1439  FALSE/*$no_response*/,
1440  TRUE/*$bad_response*/,
1441  $text_response);
1442  }
1443  // read the root node of the XML tree
1444  if ( !($tree_response = $dom->document_element()) ) {
1445  phpCAS::trace('document_element() failed');
1446  $this->authError('ST not validated',
1447  $validate_url,
1448  FALSE/*$no_response*/,
1449  TRUE/*$bad_response*/,
1450  $text_response);
1451  }
1452  // insure that tag name is 'serviceResponse'
1453  if ( $tree_response->node_name() != 'serviceResponse' ) {
1454  phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->node_name().'\'');
1455  $this->authError('ST not validated',
1456  $validate_url,
1457  FALSE/*$no_response*/,
1458  TRUE/*$bad_response*/,
1459  $text_response);
1460  }
1461  if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
1462  // authentication succeded, extract the user name
1463  if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) {
1464  phpCAS::trace('<authenticationSuccess> found, but no <user>');
1465  $this->authError('ST not validated',
1466  $validate_url,
1467  FALSE/*$no_response*/,
1468  TRUE/*$bad_response*/,
1469  $text_response);
1470  }
1471  $user = trim($user_elements[0]->get_content());
1472  phpCAS::trace('user = `'.$user);
1473  $this->setUser($user);
1474 
1475  } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
1476  phpCAS::trace('<authenticationFailure> found');
1477  // authentication failed, extract the error code and message
1478  $this->authError('ST not validated',
1479  $validate_url,
1480  FALSE/*$no_response*/,
1481  FALSE/*$bad_response*/,
1482  $text_response,
1483  $failure_elements[0]->get_attribute('code')/*$err_code*/,
1484  trim($failure_elements[0]->get_content())/*$err_msg*/);
1485  } else {
1486  phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
1487  $this->authError('ST not validated',
1488  $validate_url,
1489  FALSE/*$no_response*/,
1490  TRUE/*$bad_response*/,
1491  $text_response);
1492  }
1493  break;
1494  }
1495  $this->renameSession($this->getST());
1496  // at this step, ST has been validated and $this->_user has been set,
1497  phpCAS::traceEnd(TRUE);
1498  return TRUE;
1499  }
1500 
1501  // ########################################################################
1502  // SAML VALIDATION
1503  // ########################################################################
1522  function validateSA($validate_url,&$text_response,&$tree_response)
1523  {
1525 
1526  // build the URL to validate the ticket
1527  $validate_url = $this->getServerSamlValidateURL();
1528 
1529  // open and read the URL
1530  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
1531  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1532  $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
1533  }
1534 
1535  phpCAS::trace('server version: '.$this->getServerVersion());
1536 
1537  // analyze the result depending on the version
1538  switch ($this->getServerVersion()) {
1539  case SAML_VERSION_1_1:
1540 
1541  // read the response of the CAS server into a DOM object
1542  if ( !($dom = domxml_open_mem($text_response))) {
1543  phpCAS::trace('domxml_open_mem() failed');
1544  $this->authError('SA not validated',
1545  $validate_url,
1546  FALSE/*$no_response*/,
1547  TRUE/*$bad_response*/,
1548  $text_response);
1549  }
1550  // read the root node of the XML tree
1551  if ( !($tree_response = $dom->document_element()) ) {
1552  phpCAS::trace('document_element() failed');
1553  $this->authError('SA not validated',
1554  $validate_url,
1555  FALSE/*$no_response*/,
1556  TRUE/*$bad_response*/,
1557  $text_response);
1558  }
1559  // insure that tag name is 'Envelope'
1560  if ( $tree_response->node_name() != 'Envelope' ) {
1561  phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->node_name().'\'');
1562  $this->authError('SA not validated',
1563  $validate_url,
1564  FALSE/*$no_response*/,
1565  TRUE/*$bad_response*/,
1566  $text_response);
1567  }
1568  // check for the NameIdentifier tag in the SAML response
1569  if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
1570  phpCAS::trace('NameIdentifier found');
1571  $user = trim($success_elements[0]->get_content());
1572  phpCAS::trace('user = `'.$user.'`');
1573  $this->setUser($user);
1574  $this->setSessionAttributes($text_response);
1575  } else {
1576  phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
1577  $this->authError('SA not validated',
1578  $validate_url,
1579  FALSE/*$no_response*/,
1580  TRUE/*$bad_response*/,
1581  $text_response);
1582  }
1583  break;
1584  }
1585  $this->renameSession($this->getSA());
1586  // at this step, ST has been validated and $this->_user has been set,
1587  phpCAS::traceEnd(TRUE);
1588  return TRUE;
1589  }
1590 
1600  function setSessionAttributes($text_response)
1601  {
1603 
1604  $result = FALSE;
1605 
1606  if (isset($_SESSION[SAML_ATTRIBUTES])) {
1607  phpCAS::trace("session attrs already set."); //testbml - do we care?
1608  }
1609 
1610  $attr_array = array();
1611 
1612  if (($dom = domxml_open_mem($text_response))) {
1613  $xPath = $dom->xpath_new_context();
1614  $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
1615  $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
1616  $nodelist = $xPath->xpath_eval("//saml:Attribute");
1617  if($nodelist){
1618  $attrs = $nodelist->nodeset;
1619  foreach($attrs as $attr){
1620  $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
1621  $name = $attr->get_attribute("AttributeName");
1622  $value_array = array();
1623  foreach($xres->nodeset as $node){
1624  $value_array[] = $node->get_content();
1625  }
1626  $attr_array[$name] = $value_array;
1627  }
1628  $_SESSION[SAML_ATTRIBUTES] = $attr_array;
1629  // UGent addition...
1630  foreach($attr_array as $attr_key => $attr_value) {
1631  if(count($attr_value) > 1) {
1632  $this->_attributes[$attr_key] = $attr_value;
1633  phpCAS::trace("* " . $attr_key . "=" . $attr_value);
1634  }
1635  else {
1636  $this->_attributes[$attr_key] = $attr_value[0];
1637  phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
1638  }
1639  }
1640  $result = TRUE;
1641  }else{
1642  phpCAS::trace("SAML Attributes are empty");
1643  $result = FALSE;
1644  }
1645  }
1646  phpCAS::traceEnd($result);
1647  return $result;
1648  }
1649 
1652  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1653  // XX XX
1654  // XX PROXY FEATURES (CAS 2.0) XX
1655  // XX XX
1656  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1657 
1658  // ########################################################################
1659  // PROXYING
1660  // ########################################################################
1672  var $_proxy;
1673 
1681  function isProxy()
1682  {
1683  return $this->_proxy;
1684  }
1685 
1687  // ########################################################################
1688  // PGT
1689  // ########################################################################
1702  var $_pgt = '';
1703 
1709  function getPGT()
1710  { return $this->_pgt; }
1711 
1717  function setPGT($pgt)
1718  { $this->_pgt = $pgt; }
1719 
1725  function hasPGT()
1726  { return !empty($this->_pgt); }
1727 
1730  // ########################################################################
1731  // CALLBACK MODE
1732  // ########################################################################
1750  var $_callback_mode = FALSE;
1751 
1759  function setCallbackMode($callback_mode)
1760  {
1761  $this->_callback_mode = $callback_mode;
1762  }
1763 
1772  function isCallbackMode()
1773  {
1774  return $this->_callback_mode;
1775  }
1776 
1785  var $_callback_url = '';
1786 
1796  function getCallbackURL()
1797  {
1798  // the URL is built when needed only
1799  if ( empty($this->_callback_url) ) {
1800  $final_uri = '';
1801  // remove the ticket if present in the URL
1802  $final_uri = 'https://';
1803  /* replaced by Julien Marchal - v0.4.6
1804  * $this->uri .= $_SERVER['SERVER_NAME'];
1805  */
1806  if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
1807  /* replaced by teedog - v0.4.12
1808  * $final_uri .= $_SERVER['SERVER_NAME'];
1809  */
1810  if (empty($_SERVER['SERVER_NAME'])) {
1811  $final_uri .= $_SERVER['HTTP_HOST'];
1812  } else {
1813  $final_uri .= $_SERVER['SERVER_NAME'];
1814  }
1815  } else {
1816  $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1817  }
1818  if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
1819  || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
1820  $final_uri .= ':';
1821  $final_uri .= $_SERVER['SERVER_PORT'];
1822  }
1823  $request_uri = $_SERVER['REQUEST_URI'];
1824  $request_uri = preg_replace('/\?.*$/','',$request_uri);
1825  $final_uri .= $request_uri;
1826  $this->setCallbackURL($final_uri);
1827  }
1828  return $this->_callback_url;
1829  }
1830 
1838  function setCallbackURL($url)
1839  {
1840  return $this->_callback_url = $url;
1841  }
1842 
1849  function callback()
1850  {
1852  $this->printHTMLHeader('phpCAS callback');
1853  $pgt_iou = $_GET['pgtIou'];
1854  $pgt = $_GET['pgtId'];
1855  phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
1856  echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
1857  $this->storePGT($pgt,$pgt_iou);
1858  $this->printHTMLFooter();
1860  exit();
1861  }
1862 
1865  // ########################################################################
1866  // PGT STORAGE
1867  // ########################################################################
1881  var $_pgt_storage = null;
1882 
1889  function initPGTStorage()
1890  {
1891  // if no SetPGTStorageXxx() has been used, default to file
1892  if ( !is_object($this->_pgt_storage) ) {
1893  $this->setPGTStorageFile();
1894  }
1895 
1896  // initializes the storage
1897  $this->_pgt_storage->init();
1898  }
1899 
1908  function storePGT($pgt,$pgt_iou)
1909  {
1910  // ensure that storage is initialized
1911  $this->initPGTStorage();
1912  // writes the PGT
1913  $this->_pgt_storage->write($pgt,$pgt_iou);
1914  }
1915 
1925  function loadPGT($pgt_iou)
1926  {
1927  // ensure that storage is initialized
1928  $this->initPGTStorage();
1929  // read the PGT
1930  return $this->_pgt_storage->read($pgt_iou);
1931  }
1932 
1942  function setPGTStorageFile($format='',
1943  $path='')
1944  {
1945  // check that the storage has not already been set
1946  if ( is_object($this->_pgt_storage) ) {
1947  phpCAS::error('PGT storage already defined');
1948  }
1949 
1950  // create the storage object
1951  $this->_pgt_storage = new PGTStorageFile($this,$format,$path);
1952  }
1953 
1971  function setPGTStorageDB($user,
1972  $password,
1973  $database_type,
1974  $hostname,
1975  $port,
1976  $database,
1977  $table)
1978  {
1979  // check that the storage has not already been set
1980  if ( is_object($this->_pgt_storage) ) {
1981  phpCAS::error('PGT storage already defined');
1982  }
1983 
1984  // warn the user that he should use file storage...
1985  trigger_error('PGT storage into database is an experimental feature, use at your own risk',E_USER_WARNING);
1986 
1987  // create the storage object
1988  $this->_pgt_storage = new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table);
1989  }
1990 
1991  // ########################################################################
1992  // PGT VALIDATION
1993  // ########################################################################
2007  function validatePGT(&$validate_url,$text_response,$tree_response)
2008  {
2009  // here cannot use phpCAS::traceBegin(); alongside domxml-php4-to-php5.php
2010  phpCAS::log('start validatePGT()');
2011  if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
2012  phpCAS::trace('<proxyGrantingTicket> not found');
2013  // authentication succeded, but no PGT Iou was transmitted
2014  $this->authError('Ticket validated but no PGT Iou transmitted',
2015  $validate_url,
2016  FALSE/*$no_response*/,
2017  FALSE/*$bad_response*/,
2018  $text_response);
2019  } else {
2020  // PGT Iou transmitted, extract it
2021  $pgt_iou = trim($arr[0]->get_content());
2022  $pgt = $this->loadPGT($pgt_iou);
2023  if ( $pgt == FALSE ) {
2024  phpCAS::trace('could not load PGT');
2025  $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
2026  $validate_url,
2027  FALSE/*$no_response*/,
2028  FALSE/*$bad_response*/,
2029  $text_response);
2030  }
2031  $this->setPGT($pgt);
2032  }
2033  // here, cannot use phpCAS::traceEnd(TRUE); alongside domxml-php4-to-php5.php
2034  phpCAS::log('end validatePGT()');
2035  return TRUE;
2036  }
2037 
2038  // ########################################################################
2039  // PGT VALIDATION
2040  // ########################################################################
2041 
2053  function retrievePT($target_service,&$err_code,&$err_msg)
2054  {
2056 
2057  // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
2058  // set to false and $err_msg to an error message. At the end, if $pt is FALSE
2059  // and $error_msg is still empty, it is set to 'invalid response' (the most
2060  // commonly encountered error).
2061  $err_msg = '';
2062 
2063  // build the URL to retrieve the PT
2064  // $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT();
2065  $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT();
2066 
2067  // open and read the URL
2068  if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) {
2069  phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
2070  $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2071  $err_msg = 'could not retrieve PT (no response from the CAS server)';
2072  phpCAS::traceEnd(FALSE);
2073  return FALSE;
2074  }
2075 
2076  $bad_response = FALSE;
2077 
2078  if ( !$bad_response ) {
2079  // read the response of the CAS server into a DOM object
2080  if ( !($dom = @domxml_open_mem($cas_response))) {
2081  phpCAS::trace('domxml_open_mem() failed');
2082  // read failed
2083  $bad_response = TRUE;
2084  }
2085  }
2086 
2087  if ( !$bad_response ) {
2088  // read the root node of the XML tree
2089  if ( !($root = $dom->document_element()) ) {
2090  phpCAS::trace('document_element() failed');
2091  // read failed
2092  $bad_response = TRUE;
2093  }
2094  }
2095 
2096  if ( !$bad_response ) {
2097  // insure that tag name is 'serviceResponse'
2098  if ( $root->node_name() != 'serviceResponse' ) {
2099  phpCAS::trace('node_name() failed');
2100  // bad root node
2101  $bad_response = TRUE;
2102  }
2103  }
2104 
2105  if ( !$bad_response ) {
2106  // look for a proxySuccess tag
2107  if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) {
2108  // authentication succeded, look for a proxyTicket tag
2109  if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) {
2110  $err_code = PHPCAS_SERVICE_OK;
2111  $err_msg = '';
2112  phpCAS::trace('original PT: '.trim($arr[0]->get_content()));
2113  $pt = trim($arr[0]->get_content());
2114  phpCAS::traceEnd($pt);
2115  return $pt;
2116  } else {
2117  phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
2118  }
2119  }
2120  // look for a proxyFailure tag
2121  else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) {
2122  // authentication failed, extract the error
2123  $err_code = PHPCAS_SERVICE_PT_FAILURE;
2124  $err_msg = 'PT retrieving failed (code=`'
2125  .$arr[0]->get_attribute('code')
2126  .'\', message=`'
2127  .trim($arr[0]->get_content())
2128  .'\')';
2129  phpCAS::traceEnd(FALSE);
2130  return FALSE;
2131  } else {
2132  phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
2133  }
2134  }
2135 
2136  // at this step, we are sure that the response of the CAS server was ill-formed
2137  $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
2138  $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
2139 
2140  phpCAS::traceEnd(FALSE);
2141  return FALSE;
2142  }
2143 
2144  // ########################################################################
2145  // ACCESS TO EXTERNAL SERVICES
2146  // ########################################################################
2147 
2163  function readURL($url,$cookies,&$headers,&$body,&$err_msg)
2164  {
2166  $headers = '';
2167  $body = '';
2168  $err_msg = '';
2169 
2170  $res = TRUE;
2171 
2172  // initialize the CURL session
2173  $ch = curl_init($url);
2174 
2175  if (version_compare(PHP_VERSION,'5.1.3','>=')) {
2176  //only avaible in php5
2177  curl_setopt_array($ch, $this->_curl_options);
2178  } else {
2179  foreach ($this->_curl_options as $key => $value) {
2180  curl_setopt($ch, $key, $value);
2181  }
2182  }
2183 
2184  if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) {
2185  phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
2186  }
2187  if ($this->_cas_server_cert != '' && $this->_cas_server_ca_cert != '') {
2188  // This branch added by IDMS. Seems phpCAS implementor got a bit confused about the curl options CURLOPT_SSLCERT and CURLOPT_CAINFO
2189  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2190  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
2191  curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
2192  curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
2193  curl_setopt($ch, CURLOPT_VERBOSE, '1');
2194  phpCAS::trace('CURL: Set all required opts for mutual authentication ------');
2195  } else if ($this->_cas_server_cert != '' ) {
2196  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2197  curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
2198  } else if ($this->_cas_server_ca_cert != '') {
2199  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2200  curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
2201  } else {
2202  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
2203  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
2204  }
2205 
2206  // return the CURL output into a variable
2207  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
2208  // get the HTTP header with a callback
2209  $this->_curl_headers = array(); // empty the headers array
2210  curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curl_read_headers'));
2211  // add cookies headers
2212  if ( is_array($cookies) ) {
2213  curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
2214  }
2215  // add extra stuff if SAML
2216  if ($this->hasSA()) {
2217  $more_headers = array ("soapaction: http://www.oasis-open.org/committees/security",
2218  "cache-control: no-cache",
2219  "pragma: no-cache",
2220  "accept: text/xml",
2221  "connection: keep-alive",
2222  "content-type: text/xml");
2223 
2224  curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
2225  curl_setopt($ch, CURLOPT_POST, 1);
2226  $data = $this->buildSAMLPayload();
2227  //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
2228  curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
2229  }
2230  // perform the query
2231  $buf = curl_exec ($ch);
2232  //phpCAS::trace('CURL: Call completed. Response body is: \''.$buf.'\'');
2233  if ( $buf === FALSE ) {
2234  phpCAS::trace('curl_exec() failed');
2235  $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch);
2236  //phpCAS::trace('curl error: '.$err_msg);
2237  // close the CURL session
2238  curl_close ($ch);
2239  $res = FALSE;
2240  } else {
2241  // close the CURL session
2242  curl_close ($ch);
2243 
2244  $headers = $this->_curl_headers;
2245  $body = $buf;
2246  }
2247 
2249  return $res;
2250  }
2251 
2259  function buildSAMLPayload()
2260  {
2262 
2263  //get the ticket
2264  $sa = $this->getSA();
2265  //phpCAS::trace("SA: ".$sa);
2266 
2267  $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
2268 
2269  phpCAS::traceEnd($body);
2270  return ($body);
2271  }
2272 
2276  var $_curl_headers = array();
2277  function _curl_read_headers($ch, $header)
2278  {
2279  $this->_curl_headers[] = $header;
2280  return strlen($header);
2281  }
2282 
2298  function serviceWeb($url,&$err_code,&$output)
2299  {
2301  $cookies = array();
2302  // at first retrieve a PT
2303  $pt = $this->retrievePT($url,$err_code,$output);
2304 
2305  $res = TRUE;
2306 
2307  // test if PT was retrieved correctly
2308  if ( !$pt ) {
2309  // note: $err_code and $err_msg are filled by CASClient::retrievePT()
2310  phpCAS::trace('PT was not retrieved correctly');
2311  $res = FALSE;
2312  } else {
2313  // add cookies if necessary
2314  if ( isset($_SESSION['phpCAS']['services'][$url]['cookies']) &&
2315  is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
2316  foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) {
2317  $cookies[] = $name.'='.$val;
2318  }
2319  }
2320 
2321  // build the URL including the PT
2322  if ( strstr($url,'?') === FALSE ) {
2323  $service_url = $url.'?ticket='.$pt;
2324  } else {
2325  $service_url = $url.'&ticket='.$pt;
2326  }
2327 
2328  phpCAS::trace('reading URL`'.$service_url.'\'');
2329  if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) {
2330  phpCAS::trace('could not read URL`'.$service_url.'\'');
2331  $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2332  // give an error message
2333  $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2334  $service_url,
2335  $err_msg);
2336  $res = FALSE;
2337  } else {
2338  // URL has been fetched, extract the cookies
2339  phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:');
2340  foreach ( $headers as $header ) {
2341  // test if the header is a cookie
2342  if ( preg_match('/^Set-Cookie:/',$header) ) {
2343  // the header is a cookie, remove the beginning
2344  $header_val = preg_replace('/^Set-Cookie: */','',$header);
2345  // extract interesting information
2346  $name_val = strtok($header_val,'; ');
2347  // extract the name and the value of the cookie
2348  $cookie_name = strtok($name_val,'=');
2349  $cookie_val = strtok('=');
2350  // store the cookie
2351  $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val;
2352  phpCAS::trace($cookie_name.' -> '.$cookie_val);
2353  }
2354  }
2355  }
2356  }
2357 
2358  phpCAS::traceEnd($res);
2359  return $res;
2360  }
2361 
2381  function serviceMail($url,$service,$flags,&$err_code,&$err_msg,&$pt)
2382  {
2383  phpCAS::traceBegin();
2384  // at first retrieve a PT
2385  $pt = $this->retrievePT($service,$err_code,$output);
2386 
2387  $stream = FALSE;
2388 
2389  // test if PT was retrieved correctly
2390  if ( !$pt ) {
2391  // note: $err_code and $err_msg are filled by CASClient::retrievePT()
2392  phpCAS::trace('PT was not retrieved correctly');
2393  } else {
2394  phpCAS::trace('opening IMAP URL `'.$url.'\'...');
2395  $stream = @imap_open($url,$this->getUser(),$pt,$flags);
2396  if ( !$stream ) {
2397  phpCAS::trace('could not open URL');
2398  $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2399  // give an error message
2400  $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2401  $service_url,
2402  var_export(imap_errors(),TRUE));
2403  $pt = FALSE;
2404  $stream = FALSE;
2405  } else {
2406  phpCAS::trace('ok');
2407  }
2408  }
2409 
2410  phpCAS::traceEnd($stream);
2411  return $stream;
2412  }
2413 
2416  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2417  // XX XX
2418  // XX PROXIED CLIENT FEATURES (CAS 2.0) XX
2419  // XX XX
2420  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2421 
2422  // ########################################################################
2423  // PT
2424  // ########################################################################
2438  var $_pt = '';
2439 
2445  function getPT()
2446  {
2447  // return 'ST'.substr($this->_pt, 2);
2448  return $this->_pt;
2449  }
2450 
2456  function setPT($pt)
2457  { $this->_pt = $pt; }
2458 
2464  function hasPT()
2465  { return !empty($this->_pt); }
2471  function getSA()
2472  { return 'ST'.substr($this->_sa, 2); }
2473 
2479  function setSA($sa)
2480  { $this->_sa = $sa; }
2481 
2487  function hasSA()
2488  { return !empty($this->_sa); }
2489 
2491  // ########################################################################
2492  // PT VALIDATION
2493  // ########################################################################
2506  function validatePT(&$validate_url,&$text_response,&$tree_response)
2507  {
2509  // build the URL to validate the ticket
2510  $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
2511 
2512  if ( $this->isProxy() ) {
2513  // pass the callback url for CAS proxies
2514  $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
2515  }
2516 
2517  // open and read the URL
2518  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
2519  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
2520  $this->authError('PT not validated',
2521  $validate_url,
2522  TRUE/*$no_response*/);
2523  }
2524 
2525  // read the response of the CAS server into a DOM object
2526  if ( !($dom = domxml_open_mem($text_response))) {
2527  // read failed
2528  $this->authError('PT not validated',
2529  $validate_url,
2530  FALSE/*$no_response*/,
2531  TRUE/*$bad_response*/,
2532  $text_response);
2533  }
2534  // read the root node of the XML tree
2535  if ( !($tree_response = $dom->document_element()) ) {
2536  // read failed
2537  $this->authError('PT not validated',
2538  $validate_url,
2539  FALSE/*$no_response*/,
2540  TRUE/*$bad_response*/,
2541  $text_response);
2542  }
2543  // insure that tag name is 'serviceResponse'
2544  if ( $tree_response->node_name() != 'serviceResponse' ) {
2545  // bad root node
2546  $this->authError('PT not validated',
2547  $validate_url,
2548  FALSE/*$no_response*/,
2549  TRUE/*$bad_response*/,
2550  $text_response);
2551  }
2552  if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
2553  // authentication succeded, extract the user name
2554  if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) {
2555  // no user specified => error
2556  $this->authError('PT not validated',
2557  $validate_url,
2558  FALSE/*$no_response*/,
2559  TRUE/*$bad_response*/,
2560  $text_response);
2561  }
2562  $this->setUser(trim($arr[0]->get_content()));
2563 
2564  } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
2565  // authentication succeded, extract the error code and message
2566  $this->authError('PT not validated',
2567  $validate_url,
2568  FALSE/*$no_response*/,
2569  FALSE/*$bad_response*/,
2570  $text_response,
2571  $arr[0]->get_attribute('code')/*$err_code*/,
2572  trim($arr[0]->get_content())/*$err_msg*/);
2573  } else {
2574  $this->authError('PT not validated',
2575  $validate_url,
2576  FALSE/*$no_response*/,
2577  TRUE/*$bad_response*/,
2578  $text_response);
2579  }
2580 
2581  $this->renameSession($this->getPT());
2582  // at this step, PT has been validated and $this->_user has been set,
2583 
2584  phpCAS::traceEnd(TRUE);
2585  return TRUE;
2586  }
2587 
2590  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2591  // XX XX
2592  // XX MISC XX
2593  // XX XX
2594  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2595 
2601  // ########################################################################
2602  // URL
2603  // ########################################################################
2611  var $_url = '';
2612 
2621  function getURL()
2622  {
2623  phpCAS::traceBegin();
2624  // the URL is built when needed only
2625  if ( empty($this->_url) ) {
2626  $final_uri = '';
2627  // remove the ticket if present in the URL
2628  $final_uri = ($this->isHttps()) ? 'https' : 'http';
2629  $final_uri .= '://';
2630  /* replaced by Julien Marchal - v0.4.6
2631  * $this->_url .= $_SERVER['SERVER_NAME'];
2632  */
2633  if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
2634  /* replaced by teedog - v0.4.12
2635  * $this->_url .= $_SERVER['SERVER_NAME'];
2636  */
2637  if (empty($_SERVER['SERVER_NAME'])) {
2638  $server_name = $_SERVER['HTTP_HOST'];
2639  } else {
2640  $server_name = $_SERVER['SERVER_NAME'];
2641  }
2642  } else {
2643  $server_name = $_SERVER['HTTP_X_FORWARDED_SERVER'];
2644  }
2645  $final_uri .= $server_name;
2646  if (!strpos($server_name, ':')) {
2647  if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
2648  || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
2649  $final_uri .= ':';
2650  $final_uri .= $_SERVER['SERVER_PORT'];
2651  }
2652  }
2653 
2654  $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
2655  $final_uri .= $request_uri[0];
2656 
2657  if (isset($request_uri[1]) && $request_uri[1])
2658  {
2659  $query_string = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
2660 
2661  // If the query string still has anything left, append it to the final URI
2662  if ($query_string !== '')
2663  $final_uri .= "?$query_string";
2664 
2665  }
2666 
2667  phpCAS::trace("Final URI: $final_uri");
2668  $this->setURL($final_uri);
2669  }
2670  phpCAS::traceEnd($this->_url);
2671  return $this->_url;
2672  }
2673 
2674 
2675 
2685  function removeParameterFromQueryString($parameterName, $queryString)
2686  {
2687  $parameterName = preg_quote($parameterName);
2688  return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
2689  }
2690 
2691 
2699  function setURL($url)
2700  {
2701  $this->_url = $url;
2702  }
2703 
2704  // ########################################################################
2705  // AUTHENTICATION ERROR HANDLING
2706  // ########################################################################
2722  function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
2723  {
2725 
2726  $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
2727  printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
2728  phpCAS::trace('CAS URL: '.$cas_url);
2729  phpCAS::trace('Authentication failure: '.$failure);
2730  if ( $no_response ) {
2731  phpCAS::trace('Reason: no response from the CAS server');
2732  } else {
2733  if ( $bad_response ) {
2734  phpCAS::trace('Reason: bad response from the CAS server');
2735  } else {
2736  switch ($this->getServerVersion()) {
2737  case CAS_VERSION_1_0:
2738  phpCAS::trace('Reason: CAS error');
2739  break;
2740  case CAS_VERSION_2_0:
2741  if ( empty($err_code) )
2742  phpCAS::trace('Reason: no CAS error');
2743  else
2744  phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
2745  break;
2746  }
2747  }
2748  phpCAS::trace('CAS response: '.$cas_response);
2749  }
2750  $this->printHTMLFooter();
2752  exit();
2753  }
2754 
2756 }
2757 
2758 ?>