/auth/ldap/auth.php
PHP | 2301 lines | 1460 code | 260 blank | 581 comment | 298 complexity | e430a2f4f283fbf57ba16176ca6fa223 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- <?php
-
- /**
- * @author Martin Dougiamas
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package moodle multiauth
- *
- * Authentication Plugin: LDAP Authentication
- *
- * Authentication using LDAP (Lightweight Directory Access Protocol).
- *
- * 2006-08-28 File created.
- */
-
- if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
- }
-
- // See http://support.microsoft.com/kb/305144 to interprete these values.
- if (!defined('AUTH_AD_ACCOUNTDISABLE')) {
- define('AUTH_AD_ACCOUNTDISABLE', 0x0002);
- }
- if (!defined('AUTH_AD_NORMAL_ACCOUNT')) {
- define('AUTH_AD_NORMAL_ACCOUNT', 0x0200);
- }
- if (!defined('AUTH_NTLMTIMEOUT')) { // timewindow for the NTLM SSO process, in secs...
- define('AUTH_NTLMTIMEOUT', 10);
- }
-
-
- require_once($CFG->libdir.'/authlib.php');
-
- /**
- * LDAP authentication plugin.
- */
- class auth_plugin_ldap extends auth_plugin_base {
-
- /**
- * Constructor with initialisation.
- */
- function auth_plugin_ldap() {
- $this->authtype = 'ldap';
- $this->config = get_config('auth/ldap');
- 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 (stripos($this->config->objectclass, 'objectClass=') === 0) {
- // Value is 'objectClass=some-string-here', so just add ()
- // around the value (filter _must_ have them).
- $this->config->objectclass = '('.$this->config->objectclass.')';
- } else if (stripos($this->config->objectclass, '(') !== 0) {
- // Value is 'some-string-not-starting-with-left-parentheses',
- // which is assumed to be the objectClass matching value.
- // So build a valid filter with it.
- $this->config->objectclass = '(objectClass='.$this->config->objectclass.')';
- } else {
- // There is an additional possible value
- // '(some-string-here)', that can be used to specify any
- // valid filter string, to select subsets of users based
- // on any criteria. For example, we could select the users
- // whose objectClass is 'user' and have the
- // 'enabledMoodleUser' attribute, with something like:
- //
- // (&(objectClass=user)(enabledMoodleUser=1))
- //
- // This is only used in the functions that deal with the
- // whole potential set of users (currently sync_users()
- // and get_user_list() only).
- //
- // In this particular case we don't need to do anything,
- // so leave $this->config->objectclass as is.
- }
-
- }
-
- /**
- * Returns true if the username and password work and false if they are
- * wrong or don't exist.
- *
- * @param string $username The username (with system magic quotes)
- * @param string $password The password (with system magic quotes)
- *
- * @return bool Authentication success or failure.
- */
- function user_login($username, $password) {
- if (! function_exists('ldap_bind')) {
- print_error('auth_ldapnotinstalled','auth');
- return false;
- }
-
- if (!$username or !$password) { // Don't allow blank usernames or passwords
- return false;
- }
-
- $textlib = textlib_get_instance();
- $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
- $extpassword = $textlib->convert(stripslashes($password), 'utf-8', $this->config->ldapencoding);
-
- //
- // Before we connect to LDAP, check if this is an AD SSO login
- // if we succeed in this block, we'll return success early.
- //
- $key = sesskey();
- if (!empty($this->config->ntlmsso_enabled) && $key === $password) {
- $cf = get_cache_flags('auth/ldap/ntlmsess');
- // We only get the cache flag if we retrieve it before
- // it expires (AUTH_NTLMTIMEOUT seconds).
- if (!isset($cf[$key]) || $cf[$key] === '') {
- return false;
- }
-
- $sessusername = $cf[$key];
- if ($username === $sessusername) {
- unset($sessusername);
- unset($cf);
-
- // Check that the user is inside one of the configured LDAP contexts
- $validuser = false;
- $ldapconnection = $this->ldap_connect();
- if ($ldapconnection) {
- // if the user is not inside the configured contexts,
- // ldap_find_userdn returns false.
- if ($this->ldap_find_userdn($ldapconnection, $extusername)) {
- $validuser = true;
- }
- $this->ldap_close();
- }
-
- // Shortcut here - SSO confirmed
- return $validuser;
- }
- } // End SSO processing
- unset($key);
-
- $ldapconnection = $this->ldap_connect();
- if ($ldapconnection) {
- $ldap_user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
-
- //if ldap_user_dn is empty, user does not exist
- if (!$ldap_user_dn) {
- $this->ldap_close();
- return false;
- }
-
- // Try to bind with current username and password
- $ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, $extpassword);
- $this->ldap_close();
- if ($ldap_login) {
- return true;
- }
- }
- else {
- $this->ldap_close();
- print_error('auth_ldap_noconnect','auth','',$this->config->host_url);
- }
- return false;
- }
-
- /**
- * 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 ((moodle_strtolower($value) == 'dn') || (moodle_strtolower($value) == 'distinguishedname')) {
- $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;
- }
- }
-
- $this->ldap_close();
- 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;
- }
-
- /**
- * 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);
- }
-
- /**
- * Creates a new user on external database.
- * By using information in userobject
- * Use user_exists to prevent dublicate usernames
- *
- * @param mixed $userobject Moodle userobject (with system magic quotes)
- * @param mixed $plainpass Plaintext password (with system magic quotes)
- */
- function user_create($userobject, $plainpass) {
- $textlib = textlib_get_instance();
- $extusername = $textlib->convert(stripslashes($userobject->username), 'utf-8', $this->config->ldapencoding);
- $extpassword = $textlib->convert(stripslashes($plainpass), 'utf-8', $this->config->ldapencoding);
-
- switch ($this->config->passtype) {
- case 'md5':
- $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword)));
- break;
- case 'sha1':
- $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword)));
- break;
- case 'plaintext':
- default:
- break; // plaintext
- }
-
- $ldapconnection = $this->ldap_connect();
- $attrmap = $this->ldap_attributes();
-
- $newuser = array();
-
- foreach ($attrmap as $key => $values) {
- if (!is_array($values)) {
- $values = array($values);
- }
- foreach ($values as $value) {
- if (!empty($userobject->$key) ) {
- $newuser[$value] = $textlib->convert(stripslashes($userobject->$key), 'utf-8', $this->config->ldapencoding);
- }
- }
- }
-
- //Following sets all mandatory and other forced attribute values
- //User should be creted as login disabled untill email confirmation is processed
- //Feel free to add your user type and send patches to paca@sci.fi to add them
- //Moodle distribution
-
- switch ($this->config->user_type) {
- case 'edir':
- $newuser['objectClass'] = array("inetOrgPerson","organizationalPerson","person","top");
- $newuser['uniqueId'] = $extusername;
- $newuser['logindisabled'] = "TRUE";
- $newuser['userpassword'] = $extpassword;
- $uadd = ldap_add($ldapconnection, $this->config->user_attribute.'="'.$this->ldap_addslashes($userobject->username).','.$this->config->create_context.'"', $newuser);
- break;
- case 'ad':
- // User account creation is a two step process with AD. First you
- // create the user object, then you set the password. If you try
- // to set the password while creating the user, the operation
- // fails.
-
- // Passwords in Active Directory must be encoded as Unicode
- // strings (UCS-2 Little Endian format) and surrounded with
- // double quotes. See http://support.microsoft.com/?kbid=269190
- if (!function_exists('mb_convert_encoding')) {
- print_error ('auth_ldap_no_mbstring', 'auth');
- }
-
- // First create the user account, and mark it as disabled.
- $newuser['objectClass'] = array('top','person','user','organizationalPerson');
- $newuser['sAMAccountName'] = $extusername;
- $newuser['userAccountControl'] = AUTH_AD_NORMAL_ACCOUNT |
- AUTH_AD_ACCOUNTDISABLE;
- $userdn = 'cn=' . $this->ldap_addslashes($extusername) .
- ',' . $this->config->create_context;
- if (!ldap_add($ldapconnection, $userdn, $newuser)) {
- print_error ('auth_ldap_ad_create_req', 'auth');
- }
-
- // Now set the password
- unset($newuser);
- $newuser['unicodePwd'] = mb_convert_encoding('"' . $extpassword . '"',
- "UCS-2LE", "UTF-8");
- if(!ldap_modify($ldapconnection, $userdn, $newuser)) {
- // Something went wrong: delete the user account and error out
- ldap_delete ($ldapconnection, $userdn);
- print_error ('auth_ldap_ad_create_req', 'auth');
- }
- $uadd = true;
- break;
- default:
- print_error('auth_ldap_unsupportedusertype','auth','',$this->config->user_type);
- }
- $this->ldap_close();
- return $uadd;
-
- }
-
- function can_reset_password() {
- return !empty($this->config->stdchangepassword);
- }
-
- function can_signup() {
- return (!empty($this->config->auth_user_create) and !empty($this->config->create_context));
- }
-
- /**
- * Sign up a new user ready for confirmation.
- * Password is passed in plaintext.
- *
- * @param object $user new user object (with system magic quotes)
- * @param boolean $notify print notice with link and terminate
- */
- function user_signup($user, $notify=true) {
- global $CFG;
- require_once($CFG->dirroot.'/user/profile/lib.php');
-
- if ($this->user_exists($user->username)) {
- print_error('auth_ldap_user_exists', 'auth');
- }
-
- $plainslashedpassword = $user->password;
- unset($user->password);
-
- if (! $this->user_create($user, $plainslashedpassword)) {
- print_error('auth_ldap_create_error', 'auth');
- }
-
- if (! ($user->id = insert_record('user', $user)) ) {
- print_error('auth_emailnoinsert', 'auth');
- }
-
- /// Save any custom profile field information
- profile_save_data($user);
-
- $this->update_user_record($user->username);
- update_internal_user_password($user, $plainslashedpassword);
-
- $user = get_record('user', 'id', $user->id);
- events_trigger('user_created', $user);
-
- if (! send_confirmation_email($user)) {
- print_error('auth_emailnoemail', 'auth');
- }
-
- if ($notify) {
- global $CFG;
- $emailconfirm = get_string('emailconfirm');
- $navlinks = array();
- $navlinks[] = array('name' => $emailconfirm, 'link' => null, 'type' => 'misc');
- $navigation = build_navigation($navlinks);
-
- print_header($emailconfirm, $emailconfirm, $navigation);
- notice(get_string('emailconfirmsent', '', $user->email), "$CFG->wwwroot/index.php");
- } else {
- return true;
- }
- }
-
- /**
- * Returns true if plugin allows confirming of new users.
- *
- * @return bool
- */
- function can_confirm() {
- return $this->can_signup();
- }
-
- /**
- * Confirm the new user as registered.
- *
- * @param string $username (with system magic quotes)
- * @param string $confirmsecret (with system magic quotes)
- */
- function user_confirm($username, $confirmsecret) {
- $user = get_complete_user_data('username', $username);
-
- if (!empty($user)) {
- if ($user->confirmed) {
- return AUTH_CONFIRM_ALREADY;
-
- } else if ($user->auth != 'ldap') {
- return AUTH_CONFIRM_ERROR;
-
- } else if ($user->secret == stripslashes($confirmsecret)) { // They have provided the secret key to get in
- if (!$this->user_activate($username)) {
- return AUTH_CONFIRM_FAIL;
- }
- if (!set_field("user", "confirmed", 1, "id", $user->id)) {
- return AUTH_CONFIRM_FAIL;
- }
- if (!set_field("user", "firstaccess", time(), "id", $user->id)) {
- return AUTH_CONFIRM_FAIL;
- }
- return AUTH_CONFIRM_OK;
- }
- } else {
- return AUTH_CONFIRM_ERROR;
- }
- }
-
- /**
- * return number of days to user password expires
- *
- * If userpassword does not expire it should return 0. If password is already expired
- * it should return negative value.
- *
- * @param mixed $username username (with system magic quotes)
- * @return integer
- */
- function password_expire($username) {
- $result = 0;
-
- $textlib = textlib_get_instance();
- $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
-
- $ldapconnection = $this->ldap_connect();
- $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
- $search_attribs = array($this->config->expireattr);
- $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
- if ($sr) {
- $info = $this->ldap_get_entries($ldapconnection, $sr);
- if (!empty ($info) and !empty($info[0][$this->config->expireattr][0])) {
- $expiretime = $this->ldap_expirationtime2unix($info[0][$this->config->expireattr][0], $ldapconnection, $user_dn);
- if ($expiretime != 0) {
- $now = time();
- if ($expiretime > $now) {
- $result = ceil(($expiretime - $now) / DAYSECS);
- }
- else {
- $result = floor(($expiretime - $now) / DAYSECS);
- }
- }
- }
- } else {
- error_log("ldap: password_expire did't find expiration time.");
- }
-
- //error_log("ldap: password_expire user $user_dn expires in $result days!");
- return $result;
- }
-
- /**
- * 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
- $temptable = $CFG->prefix . 'extuser';
- $createtemptablesql = '';
-
- // configure a temp table
- print "Configuring temp table\n";
- switch (strtolower($CFG->dbfamily)) {
- case 'mysql':
- $droptablesql[] = 'DROP TEMPORARY TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
- $createtemptablesql = 'CREATE TEMPORARY TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username)) ENGINE=MyISAM';
- break;
- case 'postgres':
- $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
- $bulk_insert_records = 1; // no support for multiple sets of values
- $createtemptablesql = 'CREATE TEMPORARY TABLE '. $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))';
- break;
- case 'mssql':
- $temptable = '#'. $temptable; /// MSSQL temp tables begin with #
- $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
- $bulk_insert_records = 1; // no support for multiple sets of values
- $createtemptablesql = 'CREATE TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))';
- break;
- case 'oracle':
- $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)
- $bulk_insert_records = 1; // no support for multiple sets of values
- $createtemptablesql = 'CREATE GLOBAL TEMPORARY TABLE '.$temptable.' (username VARCHAR(64), PRIMARY KEY (username)) ON COMMIT PRESERVE ROWS';
- break;
- }
-
-
- execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
- echo "Creating temp table $temptable\n";
- if(! execute_sql($createtemptablesql, false) ){
- print "Failed to create temporary users table - aborting\n";
- exit;
- }
-
- print "Connecting to ldap...\n";
- $ldapconnection = $this->ldap_connect();
-
- if (!$ldapconnection) {
- $this->ldap_close();
- 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');
- // usernames are __always__ lowercase.
- array_push($fresult, moodle_strtolower($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, u.auth
- FROM {$CFG->prefix}user u
- LEFT JOIN $temptable e ON u.username = e.username
- WHERE u.auth='ldap'
- 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";
-
- foreach ($remove_users as $user) {
- if ($this->config->removeuser == 2) {
- if (delete_user($user)) {
- 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";
- }
- }
- }
- } 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 = 'ldap';
- if (update_record('user', $updateuser)) {
- echo "\t"; print_string('auth_dbreviveduser', 'auth', array($user->username, $user->id)); echo "\n";
- } else {
- echo "\t"; print_string('auth_dbrevivedusererror', '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='ldap'");
- 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, 'ldap');
- } else {
- role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'ldap');
- }
- }
-
- 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 = 'ldap';
- $user->mnethostid = $CFG->mnet_localhost_id;
- // get_userinfo_asobj() might have replaced $user->username with the value
- // from the LDAP server (which can be mixed-case). Make sure it's lowercase
- $user->username = trim(moodle_strtolower($user->username));
- 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);
- }
-
- // add course creators if needed
- if ($creatorrole !== false and $this->iscreator(stripslashes($user->username))) {
- role_assign($creatorrole->id, $id, 0, $sitecontext->id, 0, 0, 0, 'ldap');
- }
- } else {
- echo "\t"; print_string('auth_dbinsertusererror', 'auth', $user->username); echo "\n";
- }
-
- }
- commit_sql();
- unset($add_users); // free mem
- } else {
- print "No users to be added\n";
- }
- $this->ldap_close();
- 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);
- }
-
-
- /**
- * Activates (enables) user in external db so user can login to external db
- *
- * @param mixed $username username (with system magic quotes)
- * @return boolen result
- */
- function user_activate($username) {
- $textlib = textlib_get_instance();
- $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
-
- $ldapconnection = $this->ldap_connect();
-
- $userdn = $this->ldap_find_userdn($ldapconnection, $extusername);
- switch ($this->config->user_type) {
- case 'edir':
- $newinfo['loginDisabled']="FALSE";
- break;
- case 'ad':
- // We need to unset the ACCOUNTDISABLE bit in the
- // userAccountControl attribute ( see
- // http://support.microsoft.com/kb/305144 )
- $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)',
- array('userAccountControl'));
- $info = ldap_get_entries($ldapconnection, $sr);
- $newinfo['userAccountControl'] = $info[0]['userAccountControl'][0]
- & (~AUTH_AD_ACCOUNTDISABLE);
- break;
- default:
- error ('auth: ldap user_activate() does not support selected usertype:"'.$this->config->user_type.'" (..yet)');
- }
- $result = ldap_modify($ldapconnection, $userdn, $newinfo);
- $this->ldap_close();
- return $result;
- }
-
- /**
- * Disables user in external db so user can't login to external db
- *
- * @param mixed $username username
- * @return boolean result
- */
- /* function user_disable($username) {
- $textlib = textlib_get_instance();
- $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
-
- $ldapconnection = $this->ldap_connect();
-
- $userdn = $this->ldap_find_userdn($ldapconnection, $extusername);
- switch ($this->config->user_type) {
- case 'edir':
- $newinfo['loginDisabled']="TRUE";
- break;
- case 'ad':
- // We need to set the ACCOUNTDISABLE bit in the
- // userAccountControl attribute ( see
- // http://support.microsoft.com/kb/305144 )
- $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)',
- array('userAccountControl'));
- $info = auth_ldap_get_entries($ldapconnection, $sr);
- $newinfo['userAccountControl'] = $info[0]['userAccountControl'][0]
- | AUTH_AD_ACCOUNTDISABLE;
- break;
- default:
- error ('auth: ldap user_disable() does not support selected usertype (..yet)');
- }
- $result = ldap_modify($ldapconnection, $userdn, $newinfo);
- $this->ldap_close();
- return $result;
- }*/
-
- /**
- * 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->creators) or empty($this->config->memberattribute)) {
- return null;
- }
-
- $textlib = textlib_get_instance();
- $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
-
- return (boolean)$this->ldap_isgroupmember($extusername, $this->config->creators);
- }
-
- /**
- * Called when the user record is updated.
- * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
- * conpares information saved modified information to external db.
- *
- * @param mixed $olduser Userobject before modifications (without system magic quotes)
- * @param mixed $newuser Userobject new modified userobject (without system magic quotes)
- * @return boolean result
- *
- */
- function user_update($olduser, $newuser) {
-
- global $USER;
-
- if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
- error_log("ERROR:User renaming not allowed in LDAP");
- return false;
- }
-
- if (isset($olduser->auth) and $olduser->auth != 'ldap') {
- return true; // just change auth and skip update
- }
-
- $attrmap = $this->ldap_attributes();
-
- // Before doing anything else, make sure really need to update anything
- // in the external LDAP server.
- $update_external = false;
- foreach ($attrmap as $key => $ldapkeys) {
- if (!empty($this->config->{'field_updateremote_'.$key})) {
- $update_external = true;
- break;
- }
- }
- if (!$update_external) {
- return true;
- }
-
- $textlib = textlib_get_instance();
- $extoldusername = $textlib->convert($olduser->username, 'utf-8', $this->config->ldapencoding);
-
- $ldapconnection = $this->ldap_connect();
-
- $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, $extoldusername);
-
- $user_info_result = ldap_read($ldapconnection, $user_dn,
- $this->config->objectclass, $search_attribs);
-
- if ($user_info_result) {
-
- $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
- if (empty($user_entry)) {
- $error = 'ldap: Could not find user while updating externally. '.
- 'Details follow: search base: \''.$user_dn.'\'; search filter: \''.
- $this->config->objectclass.'\'; search attributes: ';
- foreach ($search_attribs as $attrib) {
- $error .= $attrib.' ';
- }
- error_log($error);
- return false; // old user not found!
- } else if (count($user_entry) > 1) {
- error_log('ldap: Strange! More than one user record found in ldap. Only using the first one.');
- return false;
- }
- $user_entry = $user_entry[0];
-
- //error_log(var_export($user_entry) . 'fpp' );
-
- foreach ($attrmap as $key => $ldapkeys) {
- // only process if the moodle field ($key) has changed and we
- // are set to update LDAP with it
- if (isset($olduser->$key) and isset($newuser->$key)
- and $olduser->$key !== $newuser->$key
- and !empty($this->config->{'field_updateremote_'. $key})) {
- // for ldap values that could be in more than one
- // ldap key, we will do our best to match
- // where they came from
- $ambiguous = true;
- $changed = false;
- if (!is_array($ldapkeys)) {
- $ldapkeys = array($ldapkeys);
- }
- if (count($ldapkeys) < 2) {
- $ambiguous = false;
- }
-
- $nuvalue = $textlib->convert($newuser->$key, 'utf-8', $this->config->ldapencoding);
- empty($nuvalue) ? $nuvalue = array() : $nuvalue;
- $ouvalue = $textlib->convert($olduser->$key, 'utf-8', $this->config->ldapencoding);
-
- foreach ($ldapkeys as $ldapkey) {
- $ldapkey = $ldapkey;
- $ldapvalue = $user_entry[$ldapkey][0];
- if (!$ambiguous) {
- // skip update if the values already match
- if ($nuvalue !== $ldapvalue) {
- //this might fail due to schema validation
- if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
- continue;
- } else {
- error_log('Error updating LDAP record. Error code: '
- . ldap_errno($ldapconnection) . '; Error string : '
- . ldap_err2str(ldap_errno($ldapconnection))
- . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'");
- continue;
- }
- }
- } else {
- // ambiguous
- // value empty before in Moodle (and LDAP) - use 1st ldap candidate field
- // no need to guess
- if ($ouvalue === '') { // value empty before - use 1st ldap candidate
- //this might fail due to schema validation
- if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
- $changed = true;
- continue;
- } else {
- error_log('Error updating LDAP record. Error code: '
- . ldap_errno($ldapconnection) . '; Error string : '
- . ldap_err2str(ldap_errno($ldapconnection))
- . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'");
- continue;
- }
- }
-
- // we found which ldap key to update!
- if ($ouvalue !== '' and $ouvalue === $ldapvalue ) {
- //this might fail due to schema validation
- if (@ldap_modify($ldapconnection, …
Large files files are truncated, but you can click here to view the full file