Commit a84e8a36 authored by Richard Mansfield's avatar Richard Mansfield

Remove institution field from usr table and add new usr_institution table

parent 877fc79e
......@@ -48,7 +48,7 @@ if ($institution || $add) {
if ($delete) {
function delete_validate(Pieform $form, $values) {
if (get_field('usr', 'COUNT(*)', 'institution', $values['i'])) {
if (get_field('usr_institution', 'COUNT(*)', 'institution', $values['i'])) {
// TODO: exception is of the wrong type
throw new Exception('Attempt to delete an institution that has members');
}
......@@ -63,6 +63,7 @@ if ($institution || $add) {
db_begin();
delete_records('institution_locked_profile_field', 'name', $values['i']);
delete_records('usr_institution_request', 'institution', $values['i']);
delete_records('institution', 'name', $values['i']);
db_commit();
......@@ -110,9 +111,12 @@ if ($institution || $add) {
$instancestring = implode(',',$instancearray);
$inuserecords = array();
$records = get_records_sql_assoc('select authinstance, count(id) from {usr} where authinstance in ('.$instancestring.') group by authinstance', array());
foreach ($records as $record) {
$inuserecords[] = $record->authinstance;
if ($records = get_records_sql_assoc('select authinstance, count(id) from {usr} where authinstance in ('.$instancestring.') group by authinstance', array())) {
foreach ($records as $record) {
if (!in_array($record->authinstance, $inuserecords)) {
$inuserecords[] = $record->authinstance;
}
}
}
$inuse = implode(',',$inuserecords);
}
......
......@@ -50,7 +50,7 @@ if ($action == 'search') {
$params = new StdClass;
$params->query = trim(param_variable('query', ''));
$params->institution = param_alpha('institution', null);
$params->institution = param_alphanum('institution', null);
$params->f = param_alpha('f', null);
$params->l = param_alpha('l', null);
......
......@@ -36,7 +36,7 @@ require('searchlib.php');
$search = (object) array(
'query' => trim(param_variable('query', '')),
'institution' => param_alpha('institution', 'all'),
'institution' => param_alphanum('institution', 'all'),
'f' => param_alpha('f', null), // first initial
'l' => param_alpha('l', null), // last initial
);
......
......@@ -296,7 +296,6 @@ function uploadcsv_submit(Pieform $form, $values) {
foreach ($CSVDATA as $record) {
log_debug('adding user ' . $record[$formatkeylookup['username']]);
$user = new StdClass;
$user->institution = $institution;
$user->authinstance = $authinstance;
$user->username = $record[$formatkeylookup['username']];
$user->password = $record[$formatkeylookup['password']];
......@@ -311,6 +310,15 @@ function uploadcsv_submit(Pieform $form, $values) {
$user->passwordchange = 1;
$id = insert_record('usr', $user, 'id', true);
$user->id = $id;
if ($institution != 'mahara') {
$userinstitution = new StdClass;
$userinstitution->usr = $user->id;
$userinstitution->institution = $institution;
if (isset($formatkeylookup['studentid'])) {
$userinstitution->studentid = $record[$formatkeylookup['studentid']];
}
insert_record('usr_institution', $userinstitution);
}
foreach ($FORMAT as $field) {
if ($field == 'username' || $field == 'password') {
......
......@@ -202,7 +202,7 @@ abstract class Auth {
*/
public function user_exists($username) {
$this->must_be_ready();
if (record_exists('usr', 'LOWER(username)', strtolower($username), 'authinstance', $this->instanceid)) {
if (record_exists('usr', 'LOWER(username)', strtolower($username))) {
return true;
}
throw new AuthUnknownUserException("\"$username\" is not known to Auth");
......@@ -243,7 +243,7 @@ abstract class Auth {
*/
public function get_user_info_cached($username) {
$this->must_be_ready();
if (!$result = get_record('usr', 'LOWER(username)', strtolower($username), 'authinstance', $this->instanceid,
if (!$result = get_record('usr', 'LOWER(username)', strtolower($username), null, null, null, null,
'*, ' . db_format_tsfield('expiry') . ', ' . db_format_tsfield('lastlogin'))) {
throw new AuthUnknownUserException("\"$username\" is not known to AuthInternal");
}
......@@ -751,16 +751,6 @@ function auth_get_login_form() {
'required' => true
)
),
'login_institution' => array(
'type' => 'select',
'title' => get_string('institution'). ':',
'defaultvalue' => $defaultinstitution,
'options' => $institutions,
'rules' => array(
'required' => true
),
'ignore' => count($institutions) == 1
),
'submit' => array(
'type' => 'submit',
'value' => get_string('login')
......@@ -888,13 +878,12 @@ function login_submit(Pieform $form, $values) {
$username = $values['login_username'];
$password = $values['login_password'];
$institution = (isset($values['login_institution'])) ? $values['login_institution'] : 'mahara';
$authenticated = false;
$oldlastlogin = 0;
try {
//var_dump(array($username, $password, $institution, $_SESSION));
$authenticated = $USER->login($username, $password, $institution);
$authenticated = $USER->login($username, $password);
if (empty($authenticated)) {
$SESSION->add_error_msg(get_string('loginfailed'));
......@@ -903,36 +892,33 @@ function login_submit(Pieform $form, $values) {
}
catch (AuthUnknownUserException $e) {
try {
// The user doesn't exist, but maybe the institution wants us to
// create users that don't exist
$institution = get_record('institution', 'name', $institution);
if ($institution != false) {
if ($institution->updateuserinfoonlogin == 0) {
// Institution does not want us to create unknown users
throw new AuthUnknownUserException("\"$username\" at \"$institution\" is not known");
}
// If the user doesn't exist, check for institutions that
// want to create users automatically.
$authinstances = get_records_sql_array('
SELECT a.id, a.instancename, a.priority, a.authname, a.institution
FROM {institution} i JOIN {auth_instance} a ON a.institution = i.name
WHERE i.updateuserinfoonlogin = 1
ORDER BY a.institution, a.priority, a.instancename', null);
if ($authinstances == false) {
throw new AuthUnknownUserException("\"$username\" is not known");
}
// Institution says we should create unknown users
// Get all the auth options for the institution
$authinstances = get_records_array('auth_instance', 'institution', $institution, 'priority, instancename', 'id, instancename, priority, authname');
$USER->username = $username;
$USER->institution = $institution->name;
while (list(, $authinstance) = each($authinstances) && false == $authenticated) {
// TODO: Test this code with an auth plugin that provides a
// get_user_info method
$auth = AuthFactory::create($authinstance->id);
if ($auth->authenticate_user_account($USER, $password)) {
$authenticated = true;
} else {
continue;
}
$USER->username = $username;
$USER->authinstance = $authinstance->id;
foreach ($authinstances as $authinstance) {
if ($auth->authenticate_user_account($username, $password, $institution)) {
while (list(, $authinstance) = each($authinstances) && false == $authenticated) {
// TODO: Test this code with an auth plugin that provides a
// get_user_info method
$auth = AuthFactory::create($authinstance->id);
if ($auth->authenticate_user_account($USER, $password)) {
$authenticated = true;
} else {
continue;
}
$USER->authinstance = $authinstance->id;
$userdata = $auth->get_user_info();
if (
empty($userdata) ||
......@@ -940,7 +926,7 @@ function login_submit(Pieform $form, $values) {
empty($userdata->lastname) ||
empty($userdata->email)
) {
throw new AuthUnknownUserException("\"$username\" at \"$institution\" is not known");
throw new AuthUnknownUserException("\"$username\" is not known");
} else {
// We have the data - create the user
$USER->expiry = db_format_timestamp(time() + 86400);
......@@ -952,6 +938,10 @@ function login_submit(Pieform $form, $values) {
try {
db_begin();
$USER->commit();
if ($authinstance->institution !== 'mahara') {
$USER->join_institution($authinstance->institution);
}
handle_event('createuser', $USER);
db_commit();
......@@ -987,7 +977,8 @@ function login_submit(Pieform $form, $values) {
}
// Check if the user's account has become inactive
$inactivetime = get_field('institution', 'defaultaccountinactiveexpire', 'name', $USER->institution);
// To become a config setting:
$inactivetime = get_field('institution', 'defaultaccountinactiveexpire', 'name', 'mahara');
if ($inactivetime && $oldlastlogin > 0
&& $oldlastlogin + $inactivetime < time()) {
die_info(get_string('accountinactive'));
......@@ -1093,11 +1084,6 @@ function auth_generate_login_form() {
return;
}
require_once('pieforms/pieform.php');
$institutions = get_records_menu('institution', '', '', 'name, displayname');
$defaultinstitution = get_cookie('institution');
if (!$defaultinstitution) {
$defaultinstitution = 'mahara';
}
$loginform = get_login_form_js(pieform(array(
'name' => 'login',
'renderer' => 'div',
......@@ -1122,17 +1108,6 @@ function auth_generate_login_form() {
'required' => true
)
),
'login_institution' => array(
'type' => 'select',
'title' => get_string('institution') . ':',
'defaultvalue' => $defaultinstitution,
'options' => $institutions,
'rules' => array(
'required' => true
),
'ignore' => count($institutions) == 1,
'help' => true,
),
'submit' => array(
'type' => 'submit',
'value' => get_string('login')
......@@ -1235,7 +1210,8 @@ class PluginAuth extends Plugin {
// ensure we have everything we need
$user = get_user($user['id']);
$inactivetime = get_field('institution', 'defaultaccountinactiveexpire', 'name', $user->institution);
// To become a config setting:
$inactivetime = get_field('institution', 'defaultaccountinactiveexpire', 'name', 'mahara');
if ($user->suspendedcusr) {
$active = false;
}
......
......@@ -82,6 +82,7 @@ class User {
'sessionid' => '', /* The real session ID that PHP knows about */
'accountprefs' => array(),
'activityprefs' => array(),
'institutions' => array(),
'sesskey' => ''
);
$this->attributes = array();
......@@ -343,6 +344,20 @@ class User {
return true;
}
public function join_institution($institution) {
$institutions = $this->get('institutions');
if (!isset($institutions[$institution])) {
// @todo: set expiry, studentid, ctime
insert_record('usr_institution', (object) array(
'usr' => $this->get('id'),
'institution' => $institution
));
}
}
}
......@@ -364,28 +379,25 @@ class LiveUser extends User {
}
/**
* Take a username, password and institution and try to authenticate the
* Take a username and password and try to authenticate the
* user
*
* @param string $username
* @param string $password
* @param string $institution
* @return bool
*/
public function login($username, $password, $institution) {
$users = get_records_select_array('usr', 'LOWER(username) = ? AND institution = ?', array(strtolower($username), $institution), 'authinstance', '*');
public function login($username, $password) {
$user = get_record_select('usr', 'LOWER(username) = ?', array(strtolower($username)), '*');
if ($users == false) {
throw new AuthUnknownUserException("\"$username\" at \"$institution\" is not known");
if ($user == false) {
throw new AuthUnknownUserException("\"$username\" is not known");
}
foreach($users as $user) {
$auth = AuthFactory::create($user->authinstance);
if ($auth->authenticate_user_account($user, $password)) {
$user->lastauthinstance = $auth->instanceid;
$this->authenticate($user);
return true;
}
$auth = AuthFactory::create($user->authinstance);
if ($auth->authenticate_user_account($user, $password)) {
$user->lastauthinstance = $auth->instanceid;
$this->authenticate($user);
return true;
}
return false;
......@@ -441,6 +453,7 @@ class LiveUser extends User {
if (empty($user->id)) $this->commit();
$this->activityprefs = load_activity_preferences($user->id);
$this->accountprefs = load_account_preferences($user->id);
$this->institutions = load_user_institutions($user->id);
$this->commit();
}
......
......@@ -162,7 +162,6 @@ class AuthXmlrpc extends Auth {
if ($create) {
$user->username = $remoteuser->username;
$user->institution = $peer->institution;
$user->passwordchange = 1;
$user->active = 1;
$user->deleted = 0;
......@@ -183,6 +182,7 @@ class AuthXmlrpc extends Auth {
$user->authinstance = empty($this->config['parent']) ? $this->instanceid : $this->parent;
$user->commit();
$user->join_institution($peer->institution);
set_profile_field($user->id, 'firstname', $user->firstname);
set_profile_field($user->id, 'lastname', $user->lastname);
......
......@@ -191,7 +191,7 @@ function forgotpasschange_submit(Pieform $form, $values) {
exit;
}
throw new Exception('User "' . $user->username . '@' . $user->institution
throw new Exception('User "' . $user->username
. ' tried to change their password, but the attempt failed');
}
......
......@@ -99,6 +99,7 @@ function UserSearch() {
self.params = {};
self.resetInitials();
self.params.query = $('query').value;
self.params.institution = $('institution').value;
self.doSearch();
e.stop();
}
......
......@@ -102,6 +102,37 @@
<INDEX NAME="usernameuk" UNIQUE="true" FIELDS="username,authinstance"/>
</INDEXES>
</TABLE>
<TABLE NAME="usr_institution">
<FIELDS>
<FIELD NAME="usr" TYPE="int" LENGTH="10" NOTNULL="true"/>
<FIELD NAME="institution" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="ctime" TYPE="datetime" NOTNULL="false"/>
<FIELD NAME="expiry" TYPE="datetime" NOTNULL="false"/>
<FIELD NAME="studentid" TYPE="text" NOTNULL="false"/>
<FIELD NAME="staff" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0"/>
<FIELD NAME="admin" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="usr,institution" />
<KEY NAME="usr" TYPE="foreign" FIELDS="usr" REFTABLE="usr" REFFIELDS="id"/>
<KEY NAME="institution" TYPE="foreign" FIELDS="institution" REFTABLE="institution" REFFIELDS="name"/>
</KEYS>
</TABLE>
<TABLE NAME="usr_institution_request">
<FIELDS>
<FIELD NAME="usr" TYPE="int" LENGTH="10" NOTNULL="true"/>
<FIELD NAME="institution" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="confirmedusr" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
<FIELD NAME="confirmedinstitution" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
<FIELD NAME="ctime" TYPE="datetime" NOTNULL="false"/>
<FIELD NAME="studentid" TYPE="text" NOTNULL="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="usr,institution" />
<KEY NAME="usr" TYPE="foreign" FIELDS="usr" REFTABLE="usr" REFFIELDS="id"/>
<KEY NAME="institution" TYPE="foreign" FIELDS="institution" REFTABLE="institution" REFFIELDS="name"/>
</KEYS>
</TABLE>
<TABLE NAME="sso_session">
<FIELDS>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" />
......
......@@ -377,6 +377,116 @@ function xmldb_core_upgrade($oldversion=0) {
set_config('imagemaxheight', 1024);
}
if ($oldversion < 2007112300) {
$table = new XMLDBTable('usr_institution');
$table->addFieldInfo('usr', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL);
$table->addFieldInfo('institution', XMLDB_TYPE_CHAR, 255, XMLDB_UNSIGNED, XMLDB_NOTNULL);
$table->addFieldInfo('ctime', XMLDB_TYPE_DATETIME, null, null);
$table->addFieldInfo('expiry', XMLDB_TYPE_DATETIME, null, null);
$table->addFieldInfo('studentid', XMLDB_TYPE_TEXT, null);
$table->addFieldInfo('staff', XMLDB_TYPE_INTEGER, 1, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('admin', XMLDB_TYPE_INTEGER, 1, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, 0);
$table->addKeyInfo('usrfk', XMLDB_KEY_FOREIGN, array('usr'), 'usr', array('id'));
$table->addKeyInfo('institutionfk', XMLDB_KEY_FOREIGN, array('institution'), 'institution', array('name'));
$table->addKeyInfo('usrinstitutionuk', XMLDB_KEY_UNIQUE, array('usr', 'institution'));
create_table($table);
$table = new XMLDBTable('usr_institution_request');
$table->addFieldInfo('usr', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL);
$table->addFieldInfo('institution', XMLDB_TYPE_CHAR, 255, XMLDB_UNSIGNED, XMLDB_NOTNULL);
$table->addFieldInfo('confirmedusr', XMLDB_TYPE_INTEGER, 1, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('confirmedinstitution', XMLDB_TYPE_INTEGER, 1, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('studentid', XMLDB_TYPE_TEXT, null);
$table->addFieldInfo('ctime', XMLDB_TYPE_DATETIME, null, null);
$table->addKeyInfo('usrfk', XMLDB_KEY_FOREIGN, array('usr'), 'usr', array('id'));
$table->addKeyInfo('institutionfk', XMLDB_KEY_FOREIGN, array('institution'), 'institution', array('name'));
$table->addKeyInfo('usrinstitutionuk', XMLDB_KEY_UNIQUE, array('usr', 'institution'));
create_table($table);
$users = get_records_array('usr', '', '', '', 'id, username, institution, lastlogin, expiry, studentid, staff, admin, email, firstname, lastname');
if ($users) {
$usernames = get_column_sql("SELECT DISTINCT username FROM {usr}");
$newusernames = array();
$renamed = array();
// Create new usr_institution records for every non-default institution
// Make usernames unique
foreach ($users as $u) {
if ($u->institution != 'mahara') {
$ui = (object) array(
'usr' => $u->id,
'institution' => $u->institution,
'expiry' => $u->expiry,
'studentid' => $u->studentid,
'staff' => $u->staff,
'admin' => $u->admin
);
insert_record('usr_institution', $ui);
}
if (!isset($newusernames[$u->username])) {
$newname = $u->username;
} else { // Rename the user
$newname = false;
if ($u->institution != 'mahara') { // First try prepending the institution
$try = $u->institution . $u->username;
if (strlen($try) <= 30 && !isset($newusernames[$try]) && !isset($usernames[$try])) {
$newname = $try;
}
}
if ($newname == false) { // Append digits keeping total length <= 30
$i = 1;
$newname = substr($u->username, 0, 29) . $i;
while (isset($newusernames[$newname]) || isset($usernames[$newname])) {
$i++;
$newname = substr($u->username, 0, 30 - floor(log10($i)+1)) . $i;
}
}
set_field('usr', 'username', $newname, 'id', $u->id);
$renamed[$newname] = $u;
}
$newusernames[$newname] = true;
}
}
execute_sql('DROP INDEX {usr_useaut_uix};');
execute_sql('CREATE UNIQUE INDEX {usr_use_uix} ON {usr} (username);');
$table = new XMLDBField('usr');
$field = new XMLDBField('institution');
drop_field($table, $field);
if (!empty($renamed)) {
// Notify changed usernames to administrator
$report = '# Each line in this file is in the form "institution old_username new_username"'."\n";
$message = "Mahara now requires usernames to be unique.\n";
$message .= "Some usernames on your site were changed during the upgrade:\n\n";
foreach ($renamed as $newname => $olduser) {
$report .= "$olduser->institution $olduser->username $newname\n";
$message .= "Institution: $olduser->institution\n"
. "Old username: $olduser->username\n"
. "New username: $newname\n\n";
}
$sitename = get_config('sitename');
$file = get_config('dataroot') . 'user_migration_report.txt';
if (file_put_contents($file, $report)) {
$message .= "\n" . 'A copy of this list has been saved to the file ' . $file;
}
global $USER;
email_user($USER, null, $sitename . ': User migration', $message);
// Notify changed usernames to users
$usermessagestart = "Your username at $sitename has been changed:\n\n";
$usermessageend = "\n\nNext time you visit the site, please login using your new username.";
foreach ($renamed as $newname => $olduser) {
if (empty($olduser->email)) {
continue;
}
email_user($olduser, null, $sitename, $usermessagestart
. "Old username: $olduser->username\nNew username: $newname"
. $usermessageend);
}
}
}
return $status;
}
......
......@@ -173,13 +173,19 @@ function build_admin_user_search_results($search, $offset, $limit, $sortby, $sor
'username' => array('name' => get_string('username'),
'template' => file_get_contents($templatedir . 'username.tpl')),
'email' => array('name' => get_string('email')),
'institution' => array('name' => get_string('institution'),
'template' => file_get_contents($templatedir . 'institution.tpl')),
);
$institutions = get_records_assoc('institution', '', '', '', 'name,displayname');
if (count($institutions) > 1) {
$cols['institution'] = array('name' => get_string('institution'),
'template' => file_get_contents($templatedir . 'institution.tpl'));
}
$cols['suspended'] = array('name' => get_string('suspended', 'admin'),
'template' => file_get_contents($templatedir . 'suspendlink.tpl'));
$smarty = smarty_core();
$smarty->assign_by_ref('results', $results);
$smarty->assign_by_ref('institutions', get_records_assoc('institution', '', '', '', 'name,displayname'));
$smarty->assign_by_ref('institutions', $institutions);
$smarty->assign('searchurl', $searchurl);
$smarty->assign('sortby', $sortby);
$smarty->assign('sortdir', $sortdir);
......
......@@ -543,7 +543,6 @@ function core_install_lastcoredata_defaults() {
$user->username = 'root';
$user->password = '*';
$user->salt = '*';
$user->institution = 'mahara';
$user->firstname = 'System';
$user->lastname = 'User';
$user->email = 'root@example.org';
......@@ -559,7 +558,6 @@ function core_install_lastcoredata_defaults() {
$user = new StdClass;
$user->username = 'admin';
$user->password = 'mahara';
$user->institution = 'mahara';
$user->authinstance = $auth_instance->id;
$user->passwordchange = 1;
$user->admin = 1;
......
......@@ -769,4 +769,16 @@ function send_user_message($to, $message, $from=null) {
}
}
function load_user_institutions($userid) {
if (empty($userid)) {
throw new InvalidArgumentException("couldn't load institutions, no user id specified");
}
if ($institutions = get_records_assoc('usr_institution', 'usr', $userid, '',
'institution,ctime,expiry,studentid,staff,admin')) {
return $institutions;
}
return array();
}
?>
......@@ -27,7 +27,7 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2007082204;
$config->version = 2007112300;
$config->release = '0.9.0alpha3dev';
$config->minupgradefrom = 2007080700;
$config->minupgraderelease = '0.8.0 (release tag 0.8.0_RELEASE)';
......
......@@ -97,6 +97,8 @@ if (isset($key)) {
$user->lastname = $registration->lastname;
$user->commit();
$user->join_institution($registration->institution);
$registration->id = $user->id;
// Insert standard stuff as artefacts
......
......@@ -272,28 +272,47 @@ class PluginSearchInternal extends PluginSearch {
$where .= join(' OR ', $str) . ') ';
}
$institutionsearch = '';
if (!empty($constraints)) {
foreach ($constraints as $f) {
$where .= ' AND u.' . $f['field'] . $matchtypes[$f['type']];
$values[] = $f['string'];
if ($f['field'] == 'institution') {
$institutionsearch .= ' LEFT OUTER JOIN {usr_institution} i ON i.usr = u.id ';
if ($f['string'] == 'mahara') {
$where .= ' AND i.institution IS NULL';
} else {
$where .= ' AND i.institution' . $matchtypes[$f['type']];
$values[] = $f['string'];
}
} else {
$where .= ' AND u.' . $f['field'] . $matchtypes[$f['type']];
$values[] = $f['string'];
}
}
}
$count = get_field_sql('SELECT COUNT(*) FROM {usr} u ' . $where, $values);