Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/auth/cas/ -> auth.php (source)

Differences Between: [Versions 310 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Authentication Plugin: CAS Authentication
  19   *
  20   * Authentication using CAS (Central Authentication Server).
  21   *
  22   * @author Martin Dougiamas
  23   * @author Jerome GUTIERREZ
  24   * @author IƱaki Arenaza
  25   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  26   * @package auth_cas
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  require_once($CFG->dirroot.'/auth/ldap/auth.php');
  32  require_once($CFG->dirroot.'/auth/cas/CAS/vendor/autoload.php');
  33  require_once($CFG->dirroot.'/auth/cas/CAS/vendor/apereo/phpcas/source/CAS.php');
  34  
  35  /**
  36   * CAS authentication plugin.
  37   */
  38  class auth_plugin_cas extends auth_plugin_ldap {
  39  
  40      /**
  41       * Constructor.
  42       */
  43      public function __construct() {
  44          $this->authtype = 'cas';
  45          $this->roleauth = 'auth_cas';
  46          $this->errorlogtag = '[AUTH CAS] ';
  47          $this->init_plugin($this->authtype);
  48      }
  49  
  50      /**
  51       * Old syntax of class constructor. Deprecated in PHP7.
  52       *
  53       * @deprecated since Moodle 3.1
  54       */
  55      public function auth_plugin_cas() {
  56          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
  57          self::__construct();
  58      }
  59  
  60      function prevent_local_passwords() {
  61          return true;
  62      }
  63  
  64      /**
  65       * Authenticates user against CAS
  66       * Returns true if the username and password work and false if they are
  67       * wrong or don't exist.
  68       *
  69       * @param string $username The username (with system magic quotes)
  70       * @param string $password The password (with system magic quotes)
  71       * @return bool Authentication success or failure.
  72       */
  73      function user_login ($username, $password) {
  74          $this->connectCAS();
  75          return phpCAS::isAuthenticated() && (trim(core_text::strtolower(phpCAS::getUser())) == $username);
  76      }
  77  
  78      /**
  79       * Returns true if this authentication plugin is 'internal'.
  80       *
  81       * @return bool
  82       */
  83      function is_internal() {
  84          return false;
  85      }
  86  
  87      /**
  88       * Returns true if this authentication plugin can change the user's
  89       * password.
  90       *
  91       * @return bool
  92       */
  93      function can_change_password() {
  94          return false;
  95      }
  96  
  97      /**
  98       * Authentication choice (CAS or other)
  99       * Redirection to the CAS form or to login/index.php
 100       * for other authentication
 101       */
 102      function loginpage_hook() {
 103          global $frm;
 104          global $CFG;
 105          global $SESSION, $OUTPUT, $PAGE;
 106  
 107          $site = get_site();
 108          $CASform = get_string('CASform', 'auth_cas');
 109          $username = optional_param('username', '', PARAM_RAW);
 110          $courseid = optional_param('courseid', 0, PARAM_INT);
 111  
 112          if (!empty($username)) {
 113              if (isset($SESSION->wantsurl) && (strstr($SESSION->wantsurl, 'ticket') ||
 114                                                strstr($SESSION->wantsurl, 'NOCAS'))) {
 115                  unset($SESSION->wantsurl);
 116              }
 117              return;
 118          }
 119  
 120          // Return if CAS enabled and settings not specified yet
 121          if (empty($this->config->hostname)) {
 122              return;
 123          }
 124  
 125          // If the multi-authentication setting is used, check for the param before connecting to CAS.
 126          if ($this->config->multiauth) {
 127  
 128              // If there is an authentication error, stay on the default authentication page.
 129              if (!empty($SESSION->loginerrormsg)) {
 130                  return;
 131              }
 132  
 133              $authCAS = optional_param('authCAS', '', PARAM_RAW);
 134              if ($authCAS != 'CAS') {
 135                  return;
 136              }
 137  
 138          }
 139  
 140          // Connection to CAS server
 141          $this->connectCAS();
 142  
 143          if (phpCAS::checkAuthentication()) {
 144              $frm = new stdClass();
 145              $frm->username = phpCAS::getUser();
 146              $frm->password = 'passwdCas';
 147              $frm->logintoken = \core\session\manager::get_login_token();
 148  
 149              // Redirect to a course if multi-auth is activated, authCAS is set to CAS and the courseid is specified.
 150              if ($this->config->multiauth && !empty($courseid)) {
 151                  redirect(new moodle_url('/course/view.php', array('id'=>$courseid)));
 152              }
 153  
 154              return;
 155          }
 156  
 157          if (isset($_GET['loginguest']) && ($_GET['loginguest'] == true)) {
 158              $frm = new stdClass();
 159              $frm->username = 'guest';
 160              $frm->password = 'guest';
 161              $frm->logintoken = \core\session\manager::get_login_token();
 162              return;
 163          }
 164  
 165          // Force CAS authentication (if needed).
 166          if (!phpCAS::isAuthenticated()) {
 167              phpCAS::setLang($this->config->language);
 168              phpCAS::forceAuthentication();
 169          }
 170      }
 171  
 172  
 173      /**
 174       * Connect to the CAS (clientcas connection or proxycas connection)
 175       *
 176       */
 177      function connectCAS() {
 178          global $CFG;
 179          static $connected = false;
 180  
 181          if (!$connected) {
 182              // Form the base URL of the server with just the protocol and hostname.
 183              $serverurl = new moodle_url("/");
 184              $servicebaseurl = $serverurl->get_scheme() ? $serverurl->get_scheme() . "://" : '';
 185              $servicebaseurl .= $serverurl->get_host();
 186              // Add the port if set.
 187              $servicebaseurl .= $serverurl->get_port() ? ':' . $serverurl->get_port() : '';
 188  
 189              // Make sure phpCAS doesn't try to start a new PHP session when connecting to the CAS server.
 190              if ($this->config->proxycas) {
 191                  phpCAS::proxy($this->config->casversion, $this->config->hostname, (int) $this->config->port, $this->config->baseuri,
 192                      $servicebaseurl, false);
 193              } else {
 194                  phpCAS::client($this->config->casversion, $this->config->hostname, (int) $this->config->port,
 195                      $this->config->baseuri, $servicebaseurl, false);
 196              }
 197              // Some CAS installs require SSLv3 that should be explicitly set.
 198              if (!empty($this->config->curl_ssl_version)) {
 199                  phpCAS::setExtraCurlOption(CURLOPT_SSLVERSION, $this->config->curl_ssl_version);
 200              }
 201  
 202              $connected = true;
 203          }
 204  
 205          // If Moodle is configured to use a proxy, phpCAS needs some curl options set.
 206          if (!empty($CFG->proxyhost) && !is_proxybypass(phpCAS::getServerLoginURL())) {
 207              phpCAS::setExtraCurlOption(CURLOPT_PROXY, $CFG->proxyhost);
 208              if (!empty($CFG->proxyport)) {
 209                  phpCAS::setExtraCurlOption(CURLOPT_PROXYPORT, $CFG->proxyport);
 210              }
 211              if (!empty($CFG->proxytype)) {
 212                  // Only set CURLOPT_PROXYTYPE if it's something other than the curl-default http
 213                  if ($CFG->proxytype == 'SOCKS5') {
 214                      phpCAS::setExtraCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
 215                  }
 216              }
 217              if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
 218                  phpCAS::setExtraCurlOption(CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
 219                  if (defined('CURLOPT_PROXYAUTH')) {
 220                      // any proxy authentication if PHP 5.1
 221                      phpCAS::setExtraCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
 222                  }
 223              }
 224          }
 225  
 226          if ($this->config->certificate_check && $this->config->certificate_path){
 227              phpCAS::setCasServerCACert($this->config->certificate_path);
 228          } else {
 229              // Don't try to validate the server SSL credentials
 230              phpCAS::setNoCasServerValidation();
 231          }
 232      }
 233  
 234      /**
 235       * Returns the URL for changing the user's pw, or empty if the default can
 236       * be used.
 237       *
 238       * @return moodle_url
 239       */
 240      function change_password_url() {
 241          return null;
 242      }
 243  
 244      /**
 245       * Returns true if user should be coursecreator.
 246       *
 247       * @param mixed $username    username (without system magic quotes)
 248       * @return boolean result
 249       */
 250      function iscreator($username) {
 251          if (empty($this->config->host_url) or (empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) {
 252              return false;
 253          }
 254  
 255          $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 256  
 257          // Test for group creator
 258          if (!empty($this->config->groupecreators)) {
 259              $ldapconnection = $this->ldap_connect();
 260              if ($this->config->memberattribute_isdn) {
 261                  if(!($userid = $this->ldap_find_userdn($ldapconnection, $extusername))) {
 262                      return false;
 263                  }
 264              } else {
 265                  $userid = $extusername;
 266              }
 267  
 268              $group_dns = explode(';', $this->config->groupecreators);
 269              if (ldap_isgroupmember($ldapconnection, $userid, $group_dns, $this->config->memberattribute)) {
 270                  return true;
 271              }
 272          }
 273  
 274          // Build filter for attrcreator
 275          if (!empty($this->config->attrcreators)) {
 276              $attrs = explode(';', $this->config->attrcreators);
 277              $filter = '(& ('.$this->config->user_attribute."=$username)(|";
 278              foreach ($attrs as $attr){
 279                  if(strpos($attr, '=')) {
 280                      $filter .= "($attr)";
 281                  } else {
 282                      $filter .= '('.$this->config->memberattribute."=$attr)";
 283                  }
 284              }
 285              $filter .= '))';
 286  
 287              // Search
 288              $result = $this->ldap_get_userlist($filter);
 289              if (count($result) != 0) {
 290                  return true;
 291              }
 292          }
 293  
 294          return false;
 295      }
 296  
 297      /**
 298       * Reads user information from LDAP and returns it as array()
 299       *
 300       * If no LDAP servers are configured, user information has to be
 301       * provided via other methods (CSV file, manually, etc.). Return
 302       * an empty array so existing user info is not lost. Otherwise,
 303       * calls parent class method to get user info.
 304       *
 305       * @param string $username username
 306       * @return mixed array with no magic quotes or false on error
 307       */
 308      function get_userinfo($username) {
 309          if (empty($this->config->host_url)) {
 310              return array();
 311          }
 312          return parent::get_userinfo($username);
 313      }
 314  
 315      /**
 316       * Syncronizes users from LDAP server to moodle user table.
 317       *
 318       * If no LDAP servers are configured, simply return. Otherwise,
 319       * call parent class method to do the work.
 320       *
 321       * @param bool $do_updates will do pull in data updates from LDAP if relevant
 322       * @return nothing
 323       */
 324      function sync_users($do_updates=true) {
 325          if (empty($this->config->host_url)) {
 326              error_log('[AUTH CAS] '.get_string('noldapserver', 'auth_cas'));
 327              return;
 328          }
 329          parent::sync_users($do_updates);
 330      }
 331  
 332      /**
 333      * Hook for logout page
 334      */
 335      function logoutpage_hook() {
 336          global $USER, $redirect;
 337  
 338          // Only do this if the user is actually logged in via CAS
 339          if ($USER->auth === $this->authtype) {
 340              // Check if there is an alternative logout return url defined
 341              if (isset($this->config->logout_return_url) && !empty($this->config->logout_return_url)) {
 342                  // Set redirect to alternative return url
 343                  $redirect = $this->config->logout_return_url;
 344              }
 345          }
 346      }
 347  
 348      /**
 349       * Post logout hook.
 350       *
 351       * Note: this method replace the prelogout_hook method to avoid redirect to CAS logout
 352       * before the event userlogout being triggered.
 353       *
 354       * @param stdClass $user clone of USER object object before the user session was terminated
 355       */
 356      public function postlogout_hook($user) {
 357          global $CFG;
 358          // Only redirect to CAS logout if the user is logged as a CAS user.
 359          if (!empty($this->config->logoutcas) && $user->auth == $this->authtype) {
 360              $backurl = !empty($this->config->logout_return_url) ? $this->config->logout_return_url : $CFG->wwwroot;
 361              $this->connectCAS();
 362              phpCAS::logoutWithRedirectService($backurl);
 363          }
 364      }
 365  
 366      /**
 367       * Return a list of identity providers to display on the login page.
 368       *
 369       * @param string|moodle_url $wantsurl The requested URL.
 370       * @return array List of arrays with keys url, iconurl and name.
 371       */
 372      public function loginpage_idp_list($wantsurl) {
 373          if (empty($this->config->hostname)) {
 374              // CAS is not configured.
 375              return [];
 376          }
 377  
 378          if ($this->config->auth_logo) {
 379              $iconurl = moodle_url::make_pluginfile_url(
 380                  context_system::instance()->id,
 381                  'auth_cas',
 382                  'logo',
 383                  null,
 384                  null,
 385                  $this->config->auth_logo);
 386          } else {
 387              $iconurl = null;
 388          }
 389  
 390          return [
 391              [
 392                  'url' => new moodle_url(get_login_url(), [
 393                          'authCAS' => 'CAS',
 394                      ]),
 395                  'iconurl' => $iconurl,
 396                  'name' => format_string($this->config->auth_name),
 397              ],
 398          ];
 399      }
 400  }