5fc773baa7de99a3b2c4f6b1b12c735afb9d1045
[moodle.git] / auth / cas / auth.php
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/>.
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  */
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot.'/auth/ldap/auth.php');
32 require_once($CFG->dirroot.'/auth/cas/CAS/CAS.php');
34 /**
35  * CAS authentication plugin.
36  */
37 class auth_plugin_cas extends auth_plugin_ldap {
39     /**
40      * Constructor.
41      */
42     function auth_plugin_cas() {
43         $this->authtype = 'cas';
44         $this->roleauth = 'auth_cas';
45         $this->errorlogtag = '[AUTH CAS] ';
46         $this->init_plugin($this->authtype);
47     }
49     function prevent_local_passwords() {
50         return true;
51     }
53     /**
54      * Authenticates user against CAS
55      * Returns true if the username and password work and false if they are
56      * wrong or don't exist.
57      *
58      * @param string $username The username (with system magic quotes)
59      * @param string $password The password (with system magic quotes)
60      * @return bool Authentication success or failure.
61      */
62     function user_login ($username, $password) {
63         $this->connectCAS();
64         return phpCAS::isAuthenticated() && (trim(core_text::strtolower(phpCAS::getUser())) == $username);
65     }
67     /**
68      * Returns true if this authentication plugin is 'internal'.
69      *
70      * @return bool
71      */
72     function is_internal() {
73         return false;
74     }
76     /**
77      * Returns true if this authentication plugin can change the user's
78      * password.
79      *
80      * @return bool
81      */
82     function can_change_password() {
83         return false;
84     }
86     /**
87      * Authentication choice (CAS or other)
88      * Redirection to the CAS form or to login/index.php
89      * for other authentication
90      */
91     function loginpage_hook() {
92         global $frm;
93         global $CFG;
94         global $SESSION, $OUTPUT, $PAGE;
96         $site = get_site();
97         $CASform = get_string('CASform', 'auth_cas');
98         $username = optional_param('username', '', PARAM_RAW);
99         $courseid = optional_param('courseid', 0, PARAM_INT);
101         if (!empty($username)) {
102             if (isset($SESSION->wantsurl) && (strstr($SESSION->wantsurl, 'ticket') ||
103                                               strstr($SESSION->wantsurl, 'NOCAS'))) {
104                 unset($SESSION->wantsurl);
105             }
106             return;
107         }
109         // Return if CAS enabled and settings not specified yet
110         if (empty($this->config->hostname)) {
111             return;
112         }
114         // If the multi-authentication setting is used, check for the param before connecting to CAS.
115         if ($this->config->multiauth) {
117             // If there is an authentication error, stay on the default authentication page.
118             if (!empty($SESSION->loginerrormsg)) {
119                 return;
120             }
122             $authCAS = optional_param('authCAS', '', PARAM_RAW);
123             if ($authCAS == 'NOCAS') {
124                 return;
125             }
126             // Show authentication form for multi-authentication.
127             // Test pgtIou parameter for proxy mode (https connection in background from CAS server to the php server).
128             if ($authCAS != 'CAS' && !isset($_GET['pgtIou'])) {
129                 $PAGE->set_url('/login/index.php');
130                 $PAGE->navbar->add($CASform);
131                 $PAGE->set_title("$site->fullname: $CASform");
132                 $PAGE->set_heading($site->fullname);
133                 echo $OUTPUT->header();
134                 include($CFG->dirroot.'/auth/cas/cas_form.html');
135                 echo $OUTPUT->footer();
136                 exit();
137             }
138         }
140         // Connection to CAS server
141         $this->connectCAS();
143         if (phpCAS::checkAuthentication()) {
144             $frm = new stdClass();
145             $frm->username = phpCAS::getUser();
146             $frm->password = 'passwdCas';
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             }
153             return;
154         }
156         if (isset($_GET['loginguest']) && ($_GET['loginguest'] == true)) {
157             $frm = new stdClass();
158             $frm->username = 'guest';
159             $frm->password = 'guest';
160             return;
161         }
163         // Force CAS authentication (if needed).
164         if (!phpCAS::isAuthenticated()) {
165             phpCAS::setLang($this->config->language);
166             phpCAS::forceAuthentication();
167         }
168     }
171     /**
172      * Connect to the CAS (clientcas connection or proxycas connection)
173      *
174      */
175     function connectCAS() {
176         global $CFG;
177         static $connected = false;
179         if (!$connected) {
180             // Make sure phpCAS doesn't try to start a new PHP session when connecting to the CAS server.
181             if ($this->config->proxycas) {
182                 phpCAS::proxy($this->config->casversion, $this->config->hostname, (int) $this->config->port, $this->config->baseuri, false);
183             } else {
184                 phpCAS::client($this->config->casversion, $this->config->hostname, (int) $this->config->port, $this->config->baseuri, false);
185             }
186             //Some CAS installs require SSLv3, etc be explicitly set.
187             if (!empty($this->config->curl_ssl_version)) {
188                 phpCAS::setExtraCurlOption(CURLOPT_SSLVERSION,$this->config->curl_ssl_version);
189             }
191             $connected = true;
192         }
194         // If Moodle is configured to use a proxy, phpCAS needs some curl options set.
195         if (!empty($CFG->proxyhost) && !is_proxybypass(phpCAS::getServerLoginURL())) {
196             phpCAS::setExtraCurlOption(CURLOPT_PROXY, $CFG->proxyhost);
197             if (!empty($CFG->proxyport)) {
198                 phpCAS::setExtraCurlOption(CURLOPT_PROXYPORT, $CFG->proxyport);
199             }
200             if (!empty($CFG->proxytype)) {
201                 // Only set CURLOPT_PROXYTYPE if it's something other than the curl-default http
202                 if ($CFG->proxytype == 'SOCKS5') {
203                     phpCAS::setExtraCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
204                 }
205             }
206             if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
207                 phpCAS::setExtraCurlOption(CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
208                 if (defined('CURLOPT_PROXYAUTH')) {
209                     // any proxy authentication if PHP 5.1
210                     phpCAS::setExtraCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
211                 }
212             }
213         }
215         if ($this->config->certificate_check && $this->config->certificate_path){
216             phpCAS::setCasServerCACert($this->config->certificate_path);
217         } else {
218             // Don't try to validate the server SSL credentials
219             phpCAS::setNoCasServerValidation();
220         }
221     }
223     /**
224      * Prints a form for configuring this authentication plugin.
225      *
226      * This function is called from admin/auth.php, and outputs a full page with
227      * a form for configuring this plugin.
228      *
229      * @param array $page An object containing all the data for this page.
230      */
231     function config_form($config, $err, $user_fields) {
232         global $CFG, $OUTPUT;
234         if (!function_exists('ldap_connect')) { // Is php-ldap really there?
235             echo $OUTPUT->notification(get_string('auth_ldap_noextension', 'auth_ldap'));
237             // Don't return here, like we do in auth/ldap. We cas use CAS without LDAP.
238             // So just warn the user (done above) and define the LDAP constants we use
239             // in config.html, to silence the warnings.
240             if (!defined('LDAP_DEREF_NEVER')) {
241                 define ('LDAP_DEREF_NEVER', 0);
242             }
243             if (!defined('LDAP_DEREF_ALWAYS')) {
244                 define ('LDAP_DEREF_ALWAYS', 3);
245             }
246         }
248         include($CFG->dirroot.'/auth/cas/config.html');
249     }
251     /**
252      * A chance to validate form data, and last chance to
253      * do stuff before it is inserted in config_plugin
254      * @param object object with submitted configuration settings (without system magic quotes)
255      * @param array $err array of error messages
256      */
257     function validate_form($form, &$err) {
258         $certificate_path = trim($form->certificate_path);
259         if ($form->certificate_check && empty($certificate_path)) {
260             $err['certificate_path'] = get_string('auth_cas_certificate_path_empty', 'auth_cas');
261         }
262     }
264     /**
265      * Returns the URL for changing the user's pw, or empty if the default can
266      * be used.
267      *
268      * @return moodle_url
269      */
270     function change_password_url() {
271         return null;
272     }
274     /**
275      * Processes and stores configuration data for this authentication plugin.
276      */
277     function process_config($config) {
279         // CAS settings
280         if (!isset($config->hostname)) {
281             $config->hostname = '';
282         }
283         if (!isset($config->port)) {
284             $config->port = '';
285         }
286         if (!isset($config->casversion)) {
287             $config->casversion = '';
288         }
289         if (!isset($config->baseuri)) {
290             $config->baseuri = '';
291         }
292         if (!isset($config->language)) {
293             $config->language = '';
294         }
295         if (!isset($config->proxycas)) {
296             $config->proxycas = '';
297         }
298         if (!isset($config->logoutcas)) {
299             $config->logoutcas = '';
300         }
301         if (!isset($config->multiauth)) {
302             $config->multiauth = '';
303         }
304         if (!isset($config->certificate_check)) {
305             $config->certificate_check = '';
306         }
307         if (!isset($config->certificate_path)) {
308             $config->certificate_path = '';
309         }
310         if (!isset($config->curl_ssl_version)) {
311             $config->curl_ssl_version = '';
312         }
313         if (!isset($config->logout_return_url)) {
314             $config->logout_return_url = '';
315         }
317         // LDAP settings
318         if (!isset($config->host_url)) {
319             $config->host_url = '';
320         }
321         if (!isset($config->start_tls)) {
322              $config->start_tls = false;
323         }
324         if (empty($config->ldapencoding)) {
325             $config->ldapencoding = 'utf-8';
326         }
327         if (!isset($config->pagesize)) {
328             $config->pagesize = LDAP_DEFAULT_PAGESIZE;
329         }
330         if (!isset($config->contexts)) {
331             $config->contexts = '';
332         }
333         if (!isset($config->user_type)) {
334             $config->user_type = 'default';
335         }
336         if (!isset($config->user_attribute)) {
337             $config->user_attribute = '';
338         }
339         if (!isset($config->search_sub)) {
340             $config->search_sub = '';
341         }
342         if (!isset($config->opt_deref)) {
343             $config->opt_deref = LDAP_DEREF_NEVER;
344         }
345         if (!isset($config->bind_dn)) {
346             $config->bind_dn = '';
347         }
348         if (!isset($config->bind_pw)) {
349             $config->bind_pw = '';
350         }
351         if (!isset($config->ldap_version)) {
352             $config->ldap_version = '3';
353         }
354         if (!isset($config->objectclass)) {
355             $config->objectclass = '';
356         }
357         if (!isset($config->memberattribute)) {
358             $config->memberattribute = '';
359         }
361         if (!isset($config->memberattribute_isdn)) {
362             $config->memberattribute_isdn = '';
363         }
364         if (!isset($config->attrcreators)) {
365             $config->attrcreators = '';
366         }
367         if (!isset($config->groupecreators)) {
368             $config->groupecreators = '';
369         }
370         if (!isset($config->removeuser)) {
371             $config->removeuser = AUTH_REMOVEUSER_KEEP;
372         }
374         // save CAS settings
375         set_config('hostname', trim($config->hostname), $this->pluginconfig);
376         set_config('port', trim($config->port), $this->pluginconfig);
377         set_config('casversion', $config->casversion, $this->pluginconfig);
378         set_config('baseuri', trim($config->baseuri), $this->pluginconfig);
379         set_config('language', $config->language, $this->pluginconfig);
380         set_config('proxycas', $config->proxycas, $this->pluginconfig);
381         set_config('logoutcas', $config->logoutcas, $this->pluginconfig);
382         set_config('multiauth', $config->multiauth, $this->pluginconfig);
383         set_config('certificate_check', $config->certificate_check, $this->pluginconfig);
384         set_config('certificate_path', $config->certificate_path, $this->pluginconfig);
385         set_config('curl_ssl_version', $config->curl_ssl_version, $this->pluginconfig);
386         set_config('logout_return_url', $config->logout_return_url, $this->pluginconfig);
388         // save LDAP settings
389         set_config('host_url', trim($config->host_url), $this->pluginconfig);
390         set_config('start_tls', $config->start_tls, $this->pluginconfig);
391         set_config('ldapencoding', trim($config->ldapencoding), $this->pluginconfig);
392         set_config('pagesize', (int)trim($config->pagesize), $this->pluginconfig);
393         set_config('contexts', trim($config->contexts), $this->pluginconfig);
394         set_config('user_type', core_text::strtolower(trim($config->user_type)), $this->pluginconfig);
395         set_config('user_attribute', core_text::strtolower(trim($config->user_attribute)), $this->pluginconfig);
396         set_config('search_sub', $config->search_sub, $this->pluginconfig);
397         set_config('opt_deref', $config->opt_deref, $this->pluginconfig);
398         set_config('bind_dn', trim($config->bind_dn), $this->pluginconfig);
399         set_config('bind_pw', $config->bind_pw, $this->pluginconfig);
400         set_config('ldap_version', $config->ldap_version, $this->pluginconfig);
401         set_config('objectclass', trim($config->objectclass), $this->pluginconfig);
402         set_config('memberattribute', core_text::strtolower(trim($config->memberattribute)), $this->pluginconfig);
403         set_config('memberattribute_isdn', $config->memberattribute_isdn, $this->pluginconfig);
404         set_config('attrcreators', trim($config->attrcreators), $this->pluginconfig);
405         set_config('groupecreators', trim($config->groupecreators), $this->pluginconfig);
406         set_config('removeuser', $config->removeuser, $this->pluginconfig);
408         return true;
409     }
411     /**
412      * Returns true if user should be coursecreator.
413      *
414      * @param mixed $username    username (without system magic quotes)
415      * @return boolean result
416      */
417     function iscreator($username) {
418         if (empty($this->config->host_url) or (empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) {
419             return false;
420         }
422         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
424         // Test for group creator
425         if (!empty($this->config->groupecreators)) {
426             $ldapconnection = $this->ldap_connect();
427             if ($this->config->memberattribute_isdn) {
428                 if(!($userid = $this->ldap_find_userdn($ldapconnection, $extusername))) {
429                     return false;
430                 }
431             } else {
432                 $userid = $extusername;
433             }
435             $group_dns = explode(';', $this->config->groupecreators);
436             if (ldap_isgroupmember($ldapconnection, $userid, $group_dns, $this->config->memberattribute)) {
437                 return true;
438             }
439         }
441         // Build filter for attrcreator
442         if (!empty($this->config->attrcreators)) {
443             $attrs = explode(';', $this->config->attrcreators);
444             $filter = '(& ('.$this->config->user_attribute."=$username)(|";
445             foreach ($attrs as $attr){
446                 if(strpos($attr, '=')) {
447                     $filter .= "($attr)";
448                 } else {
449                     $filter .= '('.$this->config->memberattribute."=$attr)";
450                 }
451             }
452             $filter .= '))';
454             // Search
455             $result = $this->ldap_get_userlist($filter);
456             if (count($result) != 0) {
457                 return true;
458             }
459         }
461         return false;
462     }
464     /**
465      * Reads user information from LDAP and returns it as array()
466      *
467      * If no LDAP servers are configured, user information has to be
468      * provided via other methods (CSV file, manually, etc.). Return
469      * an empty array so existing user info is not lost. Otherwise,
470      * calls parent class method to get user info.
471      *
472      * @param string $username username
473      * @return mixed array with no magic quotes or false on error
474      */
475     function get_userinfo($username) {
476         if (empty($this->config->host_url)) {
477             return array();
478         }
479         return parent::get_userinfo($username);
480     }
482     /**
483      * Syncronizes users from LDAP server to moodle user table.
484      *
485      * If no LDAP servers are configured, simply return. Otherwise,
486      * call parent class method to do the work.
487      *
488      * @param bool $do_updates will do pull in data updates from LDAP if relevant
489      * @return nothing
490      */
491     function sync_users($do_updates=true) {
492         if (empty($this->config->host_url)) {
493             error_log('[AUTH CAS] '.get_string('noldapserver', 'auth_cas'));
494             return;
495         }
496         parent::sync_users($do_updates);
497     }
499     /**
500     * Hook for logout page
501     */
502     function logoutpage_hook() {
503         global $USER, $redirect;
505         // Only do this if the user is actually logged in via CAS
506         if ($USER->auth === $this->authtype) {
507             // Check if there is an alternative logout return url defined
508             if (isset($this->config->logout_return_url) && !empty($this->config->logout_return_url)) {
509                 // Set redirect to alternative return url
510                 $redirect = $this->config->logout_return_url;
511             }
512         }
513     }
515     /**
516      * Post logout hook.
517      *
518      * Note: this method replace the prelogout_hook method to avoid redirect to CAS logout
519      * before the event userlogout being triggered.
520      *
521      * @param stdClass $user clone of USER object object before the user session was terminated
522      */
523     public function postlogout_hook($user) {
524         global $CFG;
525         // Only redirect to CAS logout if the user is logged as a CAS user.
526         if (!empty($this->config->logoutcas) && $user->auth == $this->authtype) {
527             $backurl = !empty($this->config->logout_return_url) ? $this->config->logout_return_url : $CFG->wwwroot;
528             $this->connectCAS();
529             phpCAS::logoutWithRedirectService($backurl);
530         }
531     }