From: jgutierr25 Date: Fri, 4 May 2007 08:06:40 +0000 (+0000) Subject: patch for bug when you connect from authentication block on main page X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=0c121d998adc9a462671d5a5e6edf69af0728d89;p=moodle.git patch for bug when you connect from authentication block on main page --- diff --git a/auth/cas/auth.php b/auth/cas/auth.php index c49b83cc0f..1b6b15af07 100644 --- a/auth/cas/auth.php +++ b/auth/cas/auth.php @@ -1,6 +1,7 @@ libdir.'/authlib.php'); - +require_once('CAS/CAS.php'); /** * CAS authentication plugin. */ class auth_plugin_cas extends auth_plugin_base { - /** * Constructor. */ function auth_plugin_cas() { $this->authtype = 'cas'; $this->config = get_config('auth/cas'); + if (empty($this->config->ldapencoding)) { + $this->config->ldapencoding = 'utf-8'; + } + if (empty($this->config->user_type)) { + $this->config->user_type = 'default'; + } + $default = $this->ldap_getdefaults(); + //use defaults if values not given + foreach ($default as $key => $value) { + // watch out - 0, false are correct values too + if (!isset($this->config->{$key}) or $this->config->{$key} == '') { + $this->config->{$key} = $value[$this->config->user_type]; + } + } + //hack prefix to objectclass + if (empty($this->config->objectclass)) { // Can't send empty filter + $this->config->objectclass='objectClass=*'; + } else if (strpos($this->config->objectclass, 'objectClass=') !== 0) { + $this->config->objectclass = 'objectClass='.$this->config->objectclass; + } } - /** - * Authenticates user againt CAS with LDAP. + * Authenticates user againt CAS * Returns true if the username and password work and false if they are * wrong or don't exist. * @@ -40,167 +57,9 @@ class auth_plugin_cas extends auth_plugin_base { * @return bool Authentication success or failure. */ function user_login ($username, $password) { - if (! function_exists('ldap_connect')) { - print_error('auth_casnotinstalled','mnet'); - return false; - } - - global $CFG; - - // don't allow blank usernames or passwords - if (!$username or !$password) { - return false; - } - - // CAS specific - if ($CFG->auth == "cas" and !empty($this->config->enabled)) { - if ($this->config->create_user == '0') { - if (record_exists('user', 'username', $username)) { - return true; - } - else { - return false; - } - } - else { - return true; - } - } - - $ldap_connection = ldap_connect(); - - if ($ldap_connection) { - $ldap_user_dn = auth_ldap_find_userdn($ldap_connection, $username); - - // if ldap_user_dn is empty, user does not exist - if (!$ldap_user_dn) { - ldap_close($ldap_connection); - return false; - } - - // Try to bind with current username and password - $ldap_login = ldap_bind($ldap_connection, $ldap_user_dn, $password); - ldap_close($ldap_connection); - if ($ldap_login) { - if ($this->config->create_user=='0') { //cas specific - if (record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id)) { - return true; - }else{ - return false; - } - }else{ - return true; - } - } - } else { - ldap_close($ldap_connection); - print_error('auth_cas_cantconnect', 'auth', $CFG->ldap_host_url); - } - return false; - } - - /** - * Authenticates user against CAS from screen login - * the user doesn't have a CAS Ticket yet. - * - * Returns an object user if the username and password work - * and nothing if they don't - * - * @param string $username - * @param string $password - * - */ - function authenticate_user_login ($username, $password) { - - // TODO: fix SOMEOTHER:: - - global $CFG; - // FIX ME: $cas_validate is not global - $cas_validate = true; - phpCAS::client($this->config->casversion, $this->config->hostname, (int) $this->config->port, $this->config->baseuri); - phpCAS::setLang($this->config->language); - phpCAS::forceAuthentication(); - if ($this->config->create_user == '0') { - if (record_exists('user', 'username', phpCAS::getUser(), 'mnethostid', $CFG->mnet_localhost_id)) { - // TODO::SOMEOTHER:: - $user = authenticate_user_login(phpCAS::getUser(), 'cas'); - } - else { - // login as guest if CAS but not Moodle and not automatic creation - if ($CFG->guestloginbutton) { - // TODO::SOMEOTHER:: - $user = authenticate_user_login('guest', 'guest'); - } - else { - // TODO::SOMEOTHER:: - $user = authenticate_user_login(phpCAS::getUser(), 'cas'); - } - } - } - else { - // TODO::SOMEOTHER:: - $user = authenticate_user_login(phpCAS::getUser(), 'cas'); - } - return $user; + $this->connectCAS(); + return phpCAS::isAuthenticated(); } - - /** - * Authenticates user against CAS when first call of Moodle - * if already in CAS (cookie with the CAS ticket), don't have to log again (SSO) - * - * Returns an object user if the username and password work - * and nothing if they don't - * - * @param object $user - * - */ - function automatic_authenticate ($user='') { - - // TODO: fix SOMEOTHER:: - - global $CFG; - // FIX ME: $cas_validate is not global, but it works anyway ;-) - if (!$cas_validate) { - $cas_validate = true; - phpCAS::client($this->config->casversion, $this->config->hostname, (int) $this->config->port, $this->config->baseuri); - phpCAS::setLang($this->config->language); - $cas_user_exist = phpCAS::checkAuthentication(); - if (!$cas_user_exist and !$CFG->guestloginbutton) { - $cas_user_exist=phpCAS::forceAuthentication(); - } - if ($cas_user_exist) { - if ($this->config->create_user == '0') { - if (record_exists('user', 'username', phpCAS::getUser(), 'mnethostid', $CFG->mnet_localhost_id)) { - // TODO::SOMEOTHER:: - $user = authenticate_user_login(phpCAS::getUser(), 'cas'); - } - else { - // login as guest if CAS but not Moodle and not automatic creation - if ($CFG->guestloginbutton) { - // TODO::SOMEOTHER:: - $user = authenticate_user_login('guest', 'guest'); - } - else { - // TODO::SOMEOTHER:: - $user = authenticate_user_login(phpCAS::getUser(), 'cas'); - } - } - } - else { - // TODO::SOMEOTHER:: - $user = authenticate_user_login(phpCAS::getUser(), 'cas'); - } - return $user; - } - else { - return; - } - } - else { - return $user; - } - } - /** * Returns true if this authentication plugin is 'internal'. * @@ -209,7 +68,6 @@ class auth_plugin_cas extends auth_plugin_base { function is_internal() { return false; } - /** * Returns true if this authentication plugin can change the user's * password. @@ -217,25 +75,82 @@ class auth_plugin_cas extends auth_plugin_base { * @return bool */ function can_change_password() { - return !empty($this->config->changepasswordurl); + return false; } - + /** + * authentication choice (CAS or other) + * redirection to the CAS form or to login/index.php + * for other authentication + */ function loginpage_hook() { - // Load alternative login screens if necessary - // TODO: fix the cas login screen - return; - - if(!empty($CFG->cas_enabled)) { - require($CFG->dirroot.'/auth/cas/login.php'); + global $frm; + global $test; + global $CFG; + $site = get_site(); + $CASform = get_string("CASform","auth"); + $username = optional_param("username"); + if (!empty($username)) { + return; } - } - +// Connection to CAS server + $this->connectCAS(); + if ($this->config->multiauth) { + $authCAS = optional_param("authCAS"); + if ($authCAS=="NOCAS") + return; + +// choice authentication form for multi-authentication +// test pgtIou parameter for proxy mode (https connection +// in background from CAS server to the php server) + if ($authCAS!="CAS" && !isset($_GET["pgtIou"])) + { + print_header("$site->fullname: $CASform", $site->fullname, $CASform); + include($CFG->dirroot."/auth/cas/cas_form.html"); + print_footer(); + exit(); + } + } +// CAS authentication + if (!phpCAS::isAuthenticated()) + {phpCAS::forceAuthentication();} + $frm->username=phpCAS::getUser(); + $frm->password="cas"; +} + /** + * logout from the cas + * + * This function is called from admin/auth.php + * + */ function prelogout_hook() { global $CFG; - - require($CFG->dirroot.'/auth/cas/logout.php'); + if ($this->config->logoutcas ) { + $backurl = $CFG->wwwroot; + $this->connectCAS(); + phpCAS::logout($backurl); + } } - + /** + * Connect to the cas (clientcas connection or proxycas connection + * + * This function is called from admin/auth.php + * + */ + function connectCAS() { + + global $PHPCAS_CLIENT; +// mode proxy CAS +if ( !is_object($PHPCAS_CLIENT) ) { + if ($this->config->proxycas) { + phpCAS::proxy($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri); + } +// mode client CAS + else { + phpCAS::client($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri); + } + } + + } /** * Prints a form for configuring this authentication plugin. * @@ -247,7 +162,6 @@ class auth_plugin_cas extends auth_plugin_base { function config_form($config, $err, $user_fields) { include 'config.html'; } - /** * Returns the URL for changing the user's pw, or empty if the default can * be used. @@ -255,42 +169,80 @@ class auth_plugin_cas extends auth_plugin_base { * @return string */ function change_password_url() { - return $this->config->changepasswordurl; + return ""; + } + /** + * returns predefined usertypes + * + * @return array of predefined usertypes + */ + function ldap_suppported_usertypes() { + $types = array(); + $types['edir']='Novell Edirectory'; + $types['rfc2307']='posixAccount (rfc2307)'; + $types['rfc2307bis']='posixAccount (rfc2307bis)'; + $types['samba']='sambaSamAccount (v.3.0.7)'; + $types['ad']='MS ActiveDirectory'; + $types['default']=get_string('default'); + return $types; } - /** * Processes and stores configuration data for this authentication plugin. */ function process_config($config) { // set to defaults if undefined - if (!isset ($config->hostname)) { + // CAS settings + if (!isset ($config->hostname)) $config->hostname = ''; - } - if (!isset ($config->port)) { + if (!isset ($config->port)) $config->port = ''; - } - if (!isset ($config->casversion)) { + if (!isset ($config->casversion)) $config->casversion = ''; - } - if (!isset ($config->baseuri)) { + if (!isset ($config->baseuri)) $config->baseuri = ''; - } - if (!isset ($config->language)) { + if (!isset ($config->language)) $config->language = ''; - } - if (!isset ($config->use_cas)) { + if (!isset ($config->use_cas)) $config->use_cas = ''; - } - if (!isset ($config->auth_user_create)) { - $config->auth_user_create = ''; - } - if (!isset ($config->create_user)) { - $config->create_user = '0'; - } - if (!isset($config->changepasswordurl)) { - $config->changepasswordurl = ''; - } - + if (!isset ($config->proxycas)) + $config->proxycas = ''; + if (!isset ($config->logoutcas)) + $config->logoutcas = ''; + if (!isset ($config->multiauth)) + $config->multiauth = ''; + // LDAP settings + if (!isset($config->host_url)) + { $config->host_url = ''; } + if (empty($config->ldapencoding)) + { $config->ldapencoding = 'utf-8'; } + if (!isset($config->contexts)) + { $config->contexts = ''; } + if (!isset($config->user_type)) + { $config->user_type = 'default'; } + if (!isset($config->user_attribute)) + { $config->user_attribute = ''; } + if (!isset($config->search_sub)) + { $config->search_sub = ''; } + if (!isset($config->opt_deref)) + { $config->opt_deref = ''; } + if (!isset($config->bind_dn)) + {$config->bind_dn = ''; } + if (!isset($config->bind_pw)) + {$config->bind_pw = ''; } + if (!isset($config->version)) + {$config->version = '2'; } + if (!isset($config->objectclass)) + {$config->objectclass = ''; } + if (!isset($config->memberattribute)) + {$config->memberattribute = ''; } + if (!isset($config->memberattribute_isdn)) + {$config->memberattribute_isdn = ''; } + if (!isset($config->attrcreators)) + {$config->attrcreators = ''; } + if (!isset($config->groupecreators)) + {$config->groupecreators = ''; } + if (!isset($config->removeuser)) + {$config->removeuser = 0; } // save CAS settings set_config('hostname', $config->hostname, 'auth/cas'); set_config('port', $config->port, 'auth/cas'); @@ -298,18 +250,851 @@ class auth_plugin_cas extends auth_plugin_base { set_config('baseuri', $config->baseuri, 'auth/cas'); set_config('language', $config->language, 'auth/cas'); set_config('use_cas', $config->use_cas, 'auth/cas'); - set_config('auth_user_create', $config->auth_user_create, 'auth/cas'); - set_config('create_user', $config->create_user, 'auth/cas'); - set_config('changepasswordurl', $config->changepasswordurl, 'auth/cas'); - + set_config('proxycas', $config->proxycas, 'auth/cas'); + set_config('logoutcas', $config->logoutcas, 'auth/cas'); + set_config('multiauth', $config->multiauth, 'auth/cas'); // save LDAP settings - // TODO: settings must be separated now that we have multiauth! - $ldapauth = get_auth_plugin('ldap'); - $ldapauth->process_config($config); - + set_config('host_url', $config->host_url, 'auth/cas'); + set_config('ldapencoding', $config->ldapencoding, 'auth/cas'); + set_config('host_url', $config->host_url, 'auth/cas'); + set_config('contexts', $config->contexts, 'auth/cas'); + set_config('user_type', $config->user_type, 'auth/cas'); + set_config('user_attribute', $config->user_attribute, 'auth/cas'); + set_config('search_sub', $config->search_sub, 'auth/cas'); + set_config('opt_deref', $config->opt_deref, 'auth/cas'); + set_config('bind_dn', $config->bind_dn, 'auth/cas'); + set_config('bind_pw', $config->bind_pw, 'auth/cas'); + set_config('version', $config->version, 'auth/cas'); + set_config('objectclass', $config->objectclass, 'auth/cas'); + set_config('memberattribute', $config->memberattribute, 'auth/cas'); + set_config('memberattribute_isdn', $config->memberattribute_isdn, 'auth/cas'); + set_config('attrcreators', $config->attrcreators, 'auth/cas'); + set_config('groupecreators', $config->groupecreators, 'auth/cas'); + set_config('removeuser', $config->removeuser, 'auth/cas'); return true; } - + /** + * Initializes needed ldap variables for cas-module + * + * Uses names defined in ldap_supported_usertypes. + * $default is first defined as: + * $default['pseudoname'] = array( + * 'typename1' => 'value', + * 'typename2' => 'value' + * .... + * ); + * + * @return array of default values + */ + function ldap_getdefaults() { + $default['objectclass'] = array( + 'edir' => 'User', + 'rfc2307' => 'posixAccount', + 'rfc2307bis' => 'posixAccount', + 'samba' => 'sambaSamAccount', + 'ad' => 'user', + 'default' => '*' + ); + $default['user_attribute'] = array( + 'edir' => 'cn', + 'rfc2307' => 'uid', + 'rfc2307bis' => 'uid', + 'samba' => 'uid', + 'ad' => 'cn', + 'default' => 'cn' + ); + $default['memberattribute'] = array( + 'edir' => 'member', + 'rfc2307' => 'member', + 'rfc2307bis' => 'member', + 'samba' => 'member', + 'ad' => 'member', + 'default' => 'member' + ); + $default['memberattribute_isdn'] = array( + 'edir' => '1', + 'rfc2307' => '0', + 'rfc2307bis' => '1', + 'samba' => '0', //is this right? + 'ad' => '1', + 'default' => '0' + ); + return $default; + } + /** + * reads userinformation from ldap and return it in array() + * + * Read user information from external database and returns it as array(). + * Function should return all information available. If you are saving + * this information to moodle user-table you should honor syncronization flags + * + * @param string $username username (with system magic quotes) + * + * @return mixed array with no magic quotes or false on error + */ + function get_userinfo($username) { + $textlib = textlib_get_instance(); + $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding); + $ldapconnection = $this->ldap_connect(); + $attrmap = $this->ldap_attributes(); + $result = array(); + $search_attribs = array(); + foreach ($attrmap as $key=>$values) { + if (!is_array($values)) { + $values = array($values); + } + foreach ($values as $value) { + if (!in_array($value, $search_attribs)) { + array_push($search_attribs, $value); + } + } + } + $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername); + if (!$user_info_result = ldap_read($ldapconnection, $user_dn, $this->config->objectclass, $search_attribs)) { + return false; // error! + } + $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result); + if (empty($user_entry)) { + return false; // entry not found + } + foreach ($attrmap as $key=>$values) { + if (!is_array($values)) { + $values = array($values); + } + $ldapval = NULL; + foreach ($values as $value) { + if ($value == 'dn') { + $result[$key] = $user_dn; + } + if (!array_key_exists($value, $user_entry[0])) { + continue; // wrong data mapping! + } + if (is_array($user_entry[0][$value])) { + $newval = $textlib->convert($user_entry[0][$value][0], $this->config->ldapencoding, 'utf-8'); + } else { + $newval = $textlib->convert($user_entry[0][$value], $this->config->ldapencoding, 'utf-8'); + } + if (!empty($newval)) { // favour ldap entries that are set + $ldapval = $newval; + } + } + if (!is_null($ldapval)) { + $result[$key] = $ldapval; + } + } + @ldap_close($ldapconnection); + return $result; + } + /** + * reads userinformation from ldap and return it in an object + * + * @param string $username username (with system magic quotes) + * @return mixed object or false on error + */ + function get_userinfo_asobj($username) { + $user_array = $this->get_userinfo($username); + if ($user_array == false) { + return false; //error or not found + } + $user_array = truncate_userinfo($user_array); + $user = new object(); + foreach ($user_array as $key=>$value) { + $user->{$key} = $value; + } + return $user; + } + /** + * connects to ldap server + * + * Tries connect to specified ldap servers. + * Returns connection result or error. + * + * @return connection result + */ + function ldap_connect($binddn='',$bindpwd='') { + //Select bind password, With empty values use + //ldap_bind_* variables or anonymous bind if ldap_bind_* are empty + if ($binddn == '' and $bindpwd == '') { + if (!empty($this->config->bind_dn)) { + $binddn = $this->config->bind_dn; + } + if (!empty($this->config->bind_pw)) { + $bindpwd = $this->config->bind_pw; + } + } + $urls = explode(";",$this->config->host_url); + foreach ($urls as $server) { + $server = trim($server); + if (empty($server)) { + continue; + } + $connresult = ldap_connect($server); + //ldap_connect returns ALWAYS true + if (!empty($this->config->version)) { + ldap_set_option($connresult, LDAP_OPT_PROTOCOL_VERSION, $this->config->version); + } + if (!empty($binddn)) { + //bind with search-user + //$debuginfo .= 'Using bind user'.$binddn.'and password:'.$bindpwd; + $bindresult=ldap_bind($connresult, $binddn,$bindpwd); + } + else { + //bind anonymously + $bindresult=@ldap_bind($connresult); + } + if (!empty($this->config->opt_deref)) { + ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref); + } + if ($bindresult) { + return $connresult; + } + $debuginfo .= "
Server: '$server'
Connection: '$connresult'
Bind result: '$bindresult'
"; + } + //If any of servers are alive we have already returned connection + print_error('auth_ldap_noconnect_all','auth',$this->config->user_type); + return false; + } + /** + * retuns user attribute mappings between moodle and ldap + * + * @return array + */ + function ldap_attributes () { + $fields = array("firstname", "lastname", "email", "phone1", "phone2", + "department", "address", "city", "country", "description", + "idnumber", "lang" ); + $moodleattributes = array(); + foreach ($fields as $field) { + if (!empty($this->config->{"field_map_$field"})) { + $moodleattributes[$field] = $this->config->{"field_map_$field"}; + if (preg_match('/,/',$moodleattributes[$field])) { + $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ? + } + } + } + $moodleattributes['username'] = $this->config->user_attribute; + return $moodleattributes; + } + /** + * retuns dn of username + * + * Search specified contexts for username and return user dn + * like: cn=username,ou=suborg,o=org + * + * @param mixed $ldapconnection $ldapconnection result + * @param mixed $username username (external encoding no slashes) + * + */ + function ldap_find_userdn ($ldapconnection, $extusername) { + //default return value + $ldap_user_dn = FALSE; + //get all contexts and look for first matching user + $ldap_contexts = explode(";",$this->config->contexts); + if (!empty($this->config->create_context)) { + array_push($ldap_contexts, $this->config->create_context); + } + foreach ($ldap_contexts as $context) { + $context = trim($context); + if (empty($context)) { + continue; + } + if ($this->config->search_sub) { + //use ldap_search to find first user from subtree + $ldap_result = ldap_search($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute)); + } + else { + //search only in this context + $ldap_result = ldap_list($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute)); + } + $entry = ldap_first_entry($ldapconnection,$ldap_result); + if ($entry) { + $ldap_user_dn = ldap_get_dn($ldapconnection, $entry); + break ; + } + } + return $ldap_user_dn; + } + /** + * Quote control characters in quoted "texts" used in ldap + * + * @param string + */ + function ldap_addslashes($text) { + $text = str_replace('\\', '\\\\', $text); + $text = str_replace(array('"', "\0"), + array('\\"', '\\00'), $text); + return $text; + } + /** + * returns all usernames from external database + * + * get_userlist returns all usernames from external database + * + * @return array + */ + function get_userlist() { + return $this->ldap_get_userlist("({$this->config->user_attribute}=*)"); + } + /** + * checks if user exists on external db + * + * @param string $username (with system magic quotes) + */ + function user_exists($username) { + $textlib = textlib_get_instance(); + $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding); + //returns true if given username exist on ldap + $users = $this->ldap_get_userlist("({$this->config->user_attribute}=".$this->filter_addslashes($extusername).")"); + return count($users); + } + /** + * syncronizes user fron external db to moodle user table + * + * Sync is now using username attribute. + * + * Syncing users removes or suspends users that dont exists anymore in external db. + * Creates new users and updates coursecreator status of users. + * + * @param int $bulk_insert_records will insert $bulkinsert_records per insert statement + * valid only with $unsafe. increase to a couple thousand for + * blinding fast inserts -- but test it: you may hit mysqld's + * max_allowed_packet limit. + * @param bool $do_updates will do pull in data updates from ldap if relevant + */ + function sync_users ($bulk_insert_records = 1000, $do_updates = true) { + global $CFG; + $textlib = textlib_get_instance(); + $droptablesql = array(); /// sql commands to drop the table (because session scope could be a problem for + /// some persistent drivers like ODBTP (mssql) or if this function is invoked + /// from within a PHP application using persistent connections + // configure a temp table + print "Configuring temp table\n"; + switch (strtolower($CFG->dbfamily)) { + case 'mysql': + $temptable = $CFG->prefix . 'extuser'; + $droptablesql[] = 'DROP TEMPORARY TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem) + execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later + echo "Creating temp table $temptable\n"; + execute_sql('CREATE TEMPORARY TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username)) TYPE=MyISAM', false); + break; + case 'postgres': + $temptable = $CFG->prefix . 'extuser'; + $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem) + execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later + echo "Creating temp table $temptable\n"; + $bulk_insert_records = 1; // no support for multiple sets of values + execute_sql('CREATE TEMPORARY TABLE '. $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))', false); + break; + case 'mssql': + $temptable = '#'.$CFG->prefix . 'extuser'; /// MSSQL temp tables begin with # + $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem) + execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later + echo "Creating temp table $temptable\n"; + $bulk_insert_records = 1; // no support for multiple sets of values + execute_sql('CREATE TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))', false); + break; + case 'oracle': + $temptable = $CFG->prefix . 'extuser'; + $droptablesql[] = 'TRUNCATE TABLE ' . $temptable; // oracle requires truncate before being able to drop a temp table + $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem) + execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later + echo "Creating temp table $temptable\n"; + $bulk_insert_records = 1; // no support for multiple sets of values + execute_sql('CREATE GLOBAL TEMPORARY TABLE '.$temptable.' (username VARCHAR(64), PRIMARY KEY (username)) ON COMMIT PRESERVE ROWS', false); + break; + } + print "Connecting to ldap...\n"; + $ldapconnection = $this->ldap_connect(); + if (!$ldapconnection) { + @ldap_close($ldapconnection); + print get_string('auth_ldap_noconnect','auth',$this->config->host_url); + exit; + } + //// + //// get user's list from ldap to sql in a scalable fashion + //// + // prepare some data we'll need + $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))"; + $contexts = explode(";",$this->config->contexts); + if (!empty($this->config->create_context)) { + array_push($contexts, $this->config->create_context); + } + $fresult = array(); + foreach ($contexts as $context) { + $context = trim($context); + if (empty($context)) { + continue; + } + begin_sql(); + if ($this->config->search_sub) { + //use ldap_search to find first user from subtree + $ldap_result = ldap_search($ldapconnection, $context, + $filter, + array($this->config->user_attribute)); + } else { + //search only in this context + $ldap_result = ldap_list($ldapconnection, $context, + $filter, + array($this->config->user_attribute)); + } + if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) { + do { + $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute); + $value = $textlib->convert($value[0], $this->config->ldapencoding, 'utf-8'); + array_push($fresult, $value); + if (count($fresult) >= $bulk_insert_records) { + $this->ldap_bulk_insert($fresult, $temptable); + $fresult = array(); + } + } while ($entry = ldap_next_entry($ldapconnection, $entry)); + } + unset($ldap_result); // free mem + // insert any remaining users and release mem + if (count($fresult)) { + $this->ldap_bulk_insert($fresult, $temptable); + $fresult = array(); + } + commit_sql(); + } + /// preserve our user database + /// if the temp table is empty, it probably means that something went wrong, exit + /// so as to avoid mass deletion of users; which is hard to undo + $count = get_record_sql('SELECT COUNT(username) AS count, 1 FROM ' . $temptable); + $count = $count->{'count'}; + if ($count < 1) { + print "Did not get any users from LDAP -- error? -- exiting\n"; + exit; + } else { + print "Got $count records from LDAP\n\n"; + } +/// User removal + // find users in DB that aren't in ldap -- to be removed! + // this is still not as scalable (but how often do we mass delete?) + if (!empty($this->config->removeuser)) { + $sql = "SELECT u.id, u.username, u.email + FROM {$CFG->prefix}user u + LEFT JOIN $temptable e ON u.username = e.username + WHERE u.auth='cas' + AND u.deleted=0 + AND e.username IS NULL"; + $remove_users = get_records_sql($sql); + if (!empty($remove_users)) { + print "User entries to remove: ". count($remove_users) . "\n"; + begin_sql(); + foreach ($remove_users as $user) { + if ($this->config->removeuser == 2) { + //following is copy pasted from admin/user.php + //maybe this should moved to function in lib/datalib.php + $updateuser = new object(); + $updateuser->id = $user->id; + $updateuser->deleted = 1; + $updateuser->username = addslashes("$user->email.".time()); // Remember it just in case + $updateuser->email = ''; // Clear this field to free it up + $updateuser->idnumber = ''; // Clear this field to free it up + $updateuser->timemodified = time(); + if (update_record('user', $updateuser)) { + delete_records('role_assignments', 'userid', $user->id); // unassign all roles + //copy pasted part ends + echo "\t"; print_string('auth_dbdeleteuser', 'auth', array($user->username, $user->id)); echo "\n"; + } else { + echo "\t"; print_string('auth_dbdeleteusererror', 'auth', $user->username); echo "\n"; + } + } else if ($this->config->removeuser == 1) { + $updateuser = new object(); + $updateuser->id = $user->id; + $updateuser->auth = 'nologin'; + if (update_record('user', $updateuser)) { + echo "\t"; print_string('auth_dbsuspenduser', 'auth', array($user->username, $user->id)); echo "\n"; + } else { + echo "\t"; print_string('auth_dbsuspendusererror', 'auth', $user->username); echo "\n"; + } + } + } + commit_sql(); + } else { + print "No user entries to be removed\n"; + } + unset($remove_users); // free mem! + } +/// Revive suspended users + if (!empty($this->config->removeuser) and $this->config->removeuser == 1) { + $sql = "SELECT u.id, u.username + FROM $temptable e, {$CFG->prefix}user u + WHERE e.username=u.username + AND u.auth='nologin'"; + $revive_users = get_records_sql($sql); + if (!empty($revive_users)) { + print "User entries to be revived: ". count($revive_users) . "\n"; + begin_sql(); + foreach ($revive_users as $user) { + $updateuser = new object(); + $updateuser->id = $user->id; + $updateuser->auth = 'cas'; + if (update_record('user', $updateuser)) { + echo "\t"; print_string('auth_dbreviveser', 'auth', array($user->username, $user->id)); echo "\n"; + } else { + echo "\t"; print_string('auth_dbreviveusererror', 'auth', $user->username); echo "\n"; + } + } + commit_sql(); + } else { + print "No user entries to be revived\n"; + } + unset($revive_users); + } +/// User Updates - time-consuming (optional) + if ($do_updates) { + // narrow down what fields we need to update + $all_keys = array_keys(get_object_vars($this->config)); + $updatekeys = array(); + foreach ($all_keys as $key) { + if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) { + // if we have a field to update it from + // and it must be updated 'onlogin' we + // update it on cron + if ( !empty($this->config->{'field_map_'.$match[1]}) + and $this->config->{$match[0]} === 'onlogin') { + array_push($updatekeys, $match[1]); // the actual key name + } + } + } + // print_r($all_keys); print_r($updatekeys); + unset($all_keys); unset($key); + } else { + print "No updates to be done\n"; + } + if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant + $users = get_records_sql("SELECT u.username, u.id + FROM {$CFG->prefix}user u + WHERE u.deleted=0 AND u.auth='cas'"); + if (!empty($users)) { + print "User entries to update: ". count($users). "\n"; + $sitecontext = get_context_instance(CONTEXT_SYSTEM); + if (!empty($this->config->creators) and !empty($this->config->memberattribute) + and $roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) { + $creatorrole = array_shift($roles); // We can only use one, let's use the first one + } else { + $creatorrole = false; + } + begin_sql(); + $xcount = 0; + $maxxcount = 100; + foreach ($users as $user) { + echo "\t"; print_string('auth_dbupdatinguser', 'auth', array($user->username, $user->id)); + if (!$this->update_user_record(addslashes($user->username), $updatekeys)) { + echo " - ".get_string('skipped'); + } + echo "\n"; + $xcount++; + // update course creators if needed + if ($creatorrole !== false) { + if ($this->iscreator($user->username)) { + role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas'); + } else { + role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'cas'); + } + } + if ($xcount++ > $maxxcount) { + commit_sql(); + begin_sql(); + $xcount = 0; + } + } + commit_sql(); + unset($users); // free mem + } + } else { // end do updates + print "No updates to be done\n"; + } +/// User Additions + // find users missing in DB that are in LDAP + // note that get_records_sql wants at least 2 fields returned, + // and gives me a nifty object I don't want. + // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin + $sql = "SELECT e.username, e.username + FROM $temptable e LEFT JOIN {$CFG->prefix}user u ON e.username = u.username + WHERE u.id IS NULL"; + $add_users = get_records_sql($sql); // get rid of the fat + if (!empty($add_users)) { + print "User entries to add: ". count($add_users). "\n"; + $sitecontext = get_context_instance(CONTEXT_SYSTEM); + if (!empty($this->config->creators) and !empty($this->config->memberattribute) + and $roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) { + $creatorrole = array_shift($roles); // We can only use one, let's use the first one + } else { + $creatorrole = false; + } + begin_sql(); + foreach ($add_users as $user) { + $user = $this->get_userinfo_asobj(addslashes($user->username)); + // prep a few params + $user->modified = time(); + $user->confirmed = 1; + $user->auth = 'cas'; + $user->mnethostid = $CFG->mnet_localhost_id; + if (empty($user->lang)) { + $user->lang = $CFG->lang; + } + $user = addslashes_recursive($user); + if ($id = insert_record('user',$user)) { + echo "\t"; print_string('auth_dbinsertuser', 'auth', array(stripslashes($user->username), $id)); echo "\n"; + $userobj = $this->update_user_record($user->username); + if (!empty($this->config->forcechangepassword)) { + set_user_preference('auth_forcepasswordchange', 1, $userobj->id); + } + } else { + echo "\t"; print_string('auth_dbinsertusererror', 'auth', $user->username); echo "\n"; + } + // add course creators if needed + if ($creatorrole !== false and $this->iscreator(stripslashes($user->username))) { + role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas'); + } + } + commit_sql(); + unset($add_users); // free mem + } else { + print "No users to be added\n"; + } + return true; + } + /** + * Update a local user record from an external source. + * This is a lighter version of the one in moodlelib -- won't do + * expensive ops such as enrolment. + * + * If you don't pass $updatekeys, there is a performance hit and + * values removed from LDAP won't be removed from moodle. + * + * @param string $username username (with system magic quotes) + */ + function update_user_record($username, $updatekeys = false) { + global $CFG; + //just in case check text case + $username = trim(moodle_strtolower($username)); + // get the current user record + $user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id); + if (empty($user)) { // trouble + error_log("Cannot update non-existent user: ".stripslashes($username)); + print_error('auth_dbusernotexist','auth',$username); + die; + } + // Protect the userid from being overwritten + $userid = $user->id; + if ($newinfo = $this->get_userinfo($username)) { + $newinfo = truncate_userinfo($newinfo); + if (empty($updatekeys)) { // all keys? this does not support removing values + $updatekeys = array_keys($newinfo); + } + foreach ($updatekeys as $key) { + if (isset($newinfo[$key])) { + $value = $newinfo[$key]; + } else { + $value = ''; + } + if (!empty($this->config->{'field_updatelocal_' . $key})) { + if ($user->{$key} != $value) { // only update if it's changed + set_field('user', $key, addslashes($value), 'id', $userid); + } + } + } + } else { + return false; + } + return get_record_select('user', "id = $userid AND deleted = 0"); + } + /** + * Bulk insert in SQL's temp table + * @param array $users is an array of usernames + */ + function ldap_bulk_insert($users, $temptable) { + // bulk insert -- superfast with $bulk_insert_records + $sql = 'INSERT INTO ' . $temptable . ' (username) VALUES '; + // make those values safe + $users = addslashes_recursive($users); + // join and quote the whole lot + $sql = $sql . "('" . implode("'),('", $users) . "')"; + print "\t+ " . count($users) . " users\n"; + execute_sql($sql, false); + } + /** + * Returns true if user should be coursecreator. + * + * @param mixed $username username (without system magic quotes) + * @return boolean result + */ + function iscreator($username) { + if ((empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) { + return null; + } + $textlib = textlib_get_instance(); + $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); +//test for groupe creator +if (!empty($this->config->groupecreators)) + if ((boolean)$this->ldap_isgroupmember($extusername, $this->config->groupecreators)) + return true; +//build filter for attrcreator +if (!empty($this->config->attrcreators)) { + $attrs = explode(";",$this->config->attrcreators); + $filter = "(& (".$this->config->user_attribute."=$username)(|"; + foreach ($attrs as $attr){ + if(strpos($attr, "=")) + $filter .= "($attr)"; + else + $filter .= "(".$this->config->memberattribute."=$attr)"; + } + $filter .= "))"; + //search + $result = $this->ldap_get_userlist($filter); + if (count($result)!=0) + return true; + } + return false; + } + /** + * checks if user belong to specific group(s) + * + * Returns true if user belongs group in grupdns string. + * + * @param mixed $username username + * @param mixed $groupdns string of group dn separated by ; + * + */ + function ldap_isgroupmember($extusername='', $groupdns='') { + // Takes username and groupdn(s) , separated by ; + // Returns true if user is member of any given groups + $ldapconnection = $this->ldap_connect(); + if (empty($extusername) or empty($groupdns)) { + return false; + } + if ($this->config->memberattribute_isdn) { + $memberuser = $this->ldap_find_userdn($ldapconnection, $extusername); + } else { + $memberuser = $extusername; + } + if (empty($memberuser)) { + return false; + } + $groups = explode(";",$groupdns); + $result = false; + foreach ($groups as $group) { + $group = trim($group); + if (empty($group)) { + continue; + } + //echo "Checking group $group for member $username\n"; + $search = ldap_read($ldapconnection, $group, '('.$this->config->memberattribute.'='.$this->filter_addslashes($memberuser).')', array($this->config->memberattribute)); + if (!empty($search) and ldap_count_entries($ldapconnection, $search)) { + $info = $this->ldap_get_entries($ldapconnection, $search); + if (count($info) > 0 ) { + // user is member of group + $result = true; + break; + } + } + } + return $result; + } + /** + * return all usernames from ldap + * + * @return array + */ + function ldap_get_userlist($filter="*") { + /// returns all users from ldap servers + $fresult = array(); + $ldapconnection = $this->ldap_connect(); + if ($filter=="*") { + $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))"; + } + $contexts = explode(";",$this->config->contexts); + if (!empty($this->config->create_context)) { + array_push($contexts, $this->config->create_context); + } + foreach ($contexts as $context) { + $context = trim($context); + if (empty($context)) { + continue; + } + if ($this->config->search_sub) { + //use ldap_search to find first user from subtree + $ldap_result = ldap_search($ldapconnection, $context,$filter,array($this->config->user_attribute)); + } + else { + //search only in this context + $ldap_result = ldap_list($ldapconnection, $context, + $filter, + array($this->config->user_attribute)); + } + $users = $this->ldap_get_entries($ldapconnection, $ldap_result); + //add found users to list + for ($i=0;$iconfig->user_attribute][0]) ); + } + } + return $fresult; + } + /** + * return entries from ldap + * + * Returns values like ldap_get_entries but is + * binary compatible and return all attributes as array + * + * @return array ldap-entries + */ + function ldap_get_entries($conn, $searchresult) { + //Returns values like ldap_get_entries but is + //binary compatible + $i=0; + $fresult=array(); + $entry = ldap_first_entry($conn, $searchresult); + do { + $attributes = @ldap_get_attributes($conn, $entry); + for ($j=0; $j<$attributes['count']; $j++) { + $values = ldap_get_values_len($conn, $entry,$attributes[$j]); + if (is_array($values)) { + $fresult[$i][$attributes[$j]] = $values; + } + else { + $fresult[$i][$attributes[$j]] = array($values); + } + } + $i++; + } + while ($entry = @ldap_next_entry($conn, $entry)); + //were done + return ($fresult); + } + /** + * Sync roles for this user + * + * @param $user object user object (without system magic quotes) + */ + function sync_roles($user) { + $iscreator = $this->iscreator($user->username); + if ($iscreator === null) { + return; //nothing to sync - creators not configured + } + if ($roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) { + $creatorrole = array_shift($roles); // We can only use one, let's use the first one + $systemcontext = get_context_instance(CONTEXT_SYSTEM); + if ($iscreator) { // Following calls will not create duplicates + role_assign($creatorrole->id, $user->id, 0, $systemcontext->id, 0, 0, 0, 'cas'); + } else { + //unassign only if previously assigned by this plugin! + role_unassign($creatorrole->id, $user->id, 0, $systemcontext->id, 'cas'); + } + } + } + /** + * Quote control characters in texts used in ldap filters - see rfc2254.txt + * + * @param string + */ + function filter_addslashes($text) { + $text = str_replace('\\', '\\5c', $text); + $text = str_replace(array('*', '(', ')', "\0"), + array('\\2a', '\\28', '\\29', '\\00'), $text); + return $text; + } } - -?> +?> \ No newline at end of file