Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/auth/cas/ -> auth.php (source)

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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