Files
moodle/auth/cas/auth.php
T
iarenaza eee34307b9 Cache LDAP connections: MDL-18130 Properly handle open LDAP connections.
Both CAS and LDAP auth plugins open new connections to the LDAP server
to get the user account details. While this is the desired behaviour
for regular logins (we probably don't have an already open connection
to the LDAP server), this is a ressource hog when we are doing user
synchronization, as the closed connections remain in the TCP_WAIT
state for a while before the server can reuse them. If we are syncing
a lot of users, we can make the server run out of available TCP
ressources.

So we cache the connection the first time we establish it and return
the same connection handle everytime, unless we've closed all the
'open' connections, or the auth object is destroyed.

In addition to that, there were a few missing calls to ldap_close().
2009-02-15 15:03:33 +00:00

1144 lines
43 KiB
PHP

<?php
/**
* @author Martin Dougiamas
* @authro Jerome GUTIERREZ
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package moodle multiauth
*
* Authentication Plugin: CAS Authentication
*
* Authentication using CAS (Central Authentication Server).
*
* 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
}
require_once($CFG->libdir.'/authlib.php');
require_once($CFG->dirroot.'/auth/cas/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 (stripos($this->config->objectclass, 'objectClass=') !== 0) {
$this->config->objectclass = 'objectClass='.$this->config->objectclass;
}
}
/**
* Authenticates user againt CAS
* Returns true if the username and password work and false if they are
* wrong or don't exist.
*
* @param string $username The username
* @param string $password The password
* @return bool Authentication success or failure.
*/
function user_login ($username, $password) {
$this->connectCAS();
return phpCAS::isAuthenticated();
}
/**
* Returns true if this authentication plugin is 'internal'.
*
* @return bool
*/
function is_internal() {
return false;
}
/**
* Returns true if this authentication plugin can change the user's
* password.
*
* @return bool
*/
function can_change_password() {
return false;
}
/**
* authentication choice (CAS or other)
* redirection to the CAS form or to login/index.php
* for other authentication
*/
function loginpage_hook() {
global $frm;
global $CFG;
global $SESSION;
$site = get_site();
$CASform = get_string("CASform","auth");
$username = optional_param("username");
if (!empty($username)) {
if (strstr($SESSION->wantsurl,'ticket') || strstr($SESSION->wantsurl,'NOCAS'))
unset($SESSION->wantsurl);
return;
}
// Test si cas activ et paramtres non remplis
if (empty($this->config->hostname)) {
return;
}
// Connection to CAS server
$this->connectCAS();
// Gestion de la connection CAS si accs direct d'un ent ou autre
if (phpCAS::checkAuthentication()) {
$frm->username=phpCAS::getUser();
// if (phpCAS::getUser()=='esup9992')
// $frm->username='erhar0062';
$frm->password="passwdCas";
return;
}
if ($_GET["loginguest"]== true) {
$frm->username="guest";
$frm->password="guest";
return;
}
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"])) {
$navlinks = array();
$navlinks[] = array('name' => $CASform, 'link' => null, 'type' => 'misc');
$navigation = build_navigation($navlinks);
print_header("$site->fullname: $CASform", $site->fullname, $navigation);
include($CFG->dirroot."/auth/cas/cas_form.html");
print_footer();
exit();
}
}
// CAS authentication
if (!phpCAS::isAuthenticated())
{phpCAS::forceAuthentication();}
}
/**
* logout from the cas
*
* This function is called from admin/auth.php
*
*/
function prelogout_hook() {
global $CFG;
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.
*
* This function is called from admin/auth.php, and outputs a full page with
* a form for configuring this plugin.
*
* @param array $page An object containing all the data for this page.
*/
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.
*
* @return string
*/
function change_password_url() {
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
// CAS settings
if (!isset ($config->hostname))
$config->hostname = '';
if (!isset ($config->port))
$config->port = '';
if (!isset ($config->casversion))
$config->casversion = '';
if (!isset ($config->baseuri))
$config->baseuri = '';
if (!isset ($config->language))
$config->language = '';
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 = AUTH_REMOVEUSER_KEEP; }
// save CAS settings
set_config('hostname', $config->hostname, 'auth/cas');
set_config('port', $config->port, 'auth/cas');
set_config('casversion', $config->casversion, 'auth/cas');
set_config('baseuri', $config->baseuri, 'auth/cas');
set_config('language', $config->language, '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
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
*
* @return mixed array with no magic quotes or false on error
*/
function get_userinfo($username) {
$textlib = textlib_get_instance();
$extusername = $textlib->convert($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(strtolower($value), $user_entry[0])) {
continue; // wrong data mapping!
}
if (is_array($user_entry[0][strtolower($value)])) {
$newval = $textlib->convert($user_entry[0][strtolower($value)][0], $this->config->ldapencoding, 'utf-8');
} else {
$newval = $textlib->convert($user_entry[0][strtolower($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($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='') {
// Cache ldap connections (they are expensive to set up
// and can drain the TCP/IP ressources on the server if we
// are syncing a lot of users (as we try to open a new connection
// to get the user details). This is the least invasive way
// to reuse existing connections without greater code surgery.
if(!empty($this->ldapconnection)) {
$this->ldapconns++;
return $this->ldapconnection;
}
//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) {
// Set the connection counter so we can call PHP's ldap_close()
// when we call $this->ldap_close() for the last 'open' connection.
$this->ldapconns = 1;
$this->ldapconnection = $connresult;
return $connresult;
}
$debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>";
}
//If any of servers are alive we have already returned connection
print_error('auth_ldap_noconnect_all','auth',$this->config->user_type);
return false;
}
/**
* disconnects from a ldap server
*
*/
function ldap_close() {
$this->ldapconns--;
if($this->ldapconns == 0) {
@ldap_close($this->ldapconnection);
unset($this->ldapconnection);
}
}
/**
* retuns user attribute mappings between moodle and ldap
*
* @return array
*/
function ldap_attributes () {
$moodleattributes = array();
foreach ($this->userfields 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
*/
function user_exists($username) {
$textlib = textlib_get_instance();
$extusername = $textlib->convert($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 bool $do_updates will do pull in data updates from ldap if relevant
*/
function sync_users ($do_updates = true) {
global $CFG, $DB;
$textlib = textlib_get_instance();
$dbman = $DB->get_manager();
/// Define table user to be created
$table = new xmldb_table('tmp_extuser');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
$table->add_field('username', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, null, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username'));
echo "Creating temp table $temptable\n";
if (!$dbman->create_temp_table($table)) {
print "Failed to create temporary users table - aborting\n";
exit;
}
print "Connecting to ldap...\n";
$ldapconnection = $this->ldap_connect();
if (!$ldapconnection) {
$this->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;
}
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');
$this->ldap_bulk_insert($value);
} while ($entry = ldap_next_entry($ldapconnection, $entry));
}
unset($ldap_result); // free mem
}
/// 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 = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}');
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 {user} u
LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?)
WHERE u.auth='cas'
AND u.deleted=0
AND e.username IS NULL";
$remove_users = $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));
if (!empty($remove_users)) {
print "User entries to remove: ". count($remove_users) . "\n";
foreach ($remove_users as $user) {
if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
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 == AUTH_REMOVEUSER_SUSPEND) {
$updateuser = new object();
$updateuser->id = $user->id;
$updateuser->auth = 'nologin';
if ($DB->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 == AUTH_REMOVEUSER_SUSPEND) {
$sql = "SELECT u.id, u.username
FROM {user} u
JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?)
WHERE u.auth='nologin' AND u.deleted=0";
$revive_users = $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));
if (!empty($revive_users)) {
print "User entries to be revived: ". count($revive_users) . "\n";
foreach ($revive_users as $user) {
$updateuser = new object();
$updateuser->id = $user->id;
$updateuser->auth = 'ldap';
if ($DB->pdate_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";
}
}
} 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 = $DB->get_records_sql("SELECT u.username, u.id
FROM {user} u
WHERE u.deleted=0 AND u.auth='cas' AND u.mnethostid = ?", array($CFG->mnet_localhost_id));
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;
}
$DB->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($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) {
$DB->commit_sql();
$DB->begin_sql();
$xcount = 0;
}
}
$DB->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
// 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
// mnetid not used here in join because we can not add users with the same username
$sql = "SELECT e.id, e.username
FROM {user} u
LEFT JOIN {tmp_extuser} e ON u.username = e.username
WHERE u.id IS NULL";
$add_users = $DB->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;
}
$DB->begin_sql();
foreach ($add_users as $user) {
$user = $this->get_userinfo_asobj($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;
}
if ($id = $DB->insert_record('user', $user)) {
echo "\t"; print_string('auth_dbinsertuser', 'auth', array($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($user->username)) {
role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
}
}
$DB->commit_sql();
unset($add_users); // free mem
} else {
print "No users to be added\n";
}
$dbman->drop_temp_table($table);
$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
*/
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 = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
if (empty($user)) { // trouble
error_log("Cannot update non-existent user: ".$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
$DB->set_field('user', $key, $value, array('id'=>$userid));
}
}
}
} else {
return false;
}
return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
}
/**
* Bulk insert in SQL's temp table
*/
function ldap_bulk_insert($username) {
global $DB;
$username = moodle_strtolower($username); // usernames are __always__ lowercase.
$DB->insert_record_raw('tmp_extuser', array('username'=>$username), false, true);
print ".";
}
/**
* 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;
}
}
}
$this->ldap_close();
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;$i<count($users);$i++) {
array_push($fresult, ($users[$i][$this->config->user_attribute][0]) );
}
}
$this->ldap_close();
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][strtolower($attributes[$j])] = $values;
}
else {
$fresult[$i][strtolower($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;
}
}
?>