From 6ab7e0d283991bd03f0cc8a8d94e66271e998085 Mon Sep 17 00:00:00 2001 From: Cameron Ball Date: Mon, 11 Jan 2016 14:08:58 +0800 Subject: [PATCH] MDL-21912 restore: Add admin setting to restore conflicting admin user --- admin/settings/courses.php | 3 ++ backup/util/dbops/restore_dbops.class.php | 39 ++++++++++++++++++++--- lang/en/backup.php | 2 ++ lang/en/moodle.php | 1 + 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/admin/settings/courses.php b/admin/settings/courses.php index bd88630580e..21ecab77915 100644 --- a/admin/settings/courses.php +++ b/admin/settings/courses.php @@ -205,6 +205,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // Create a page for general import configuration and defaults. $temp = new admin_settingpage('importgeneralsettings', new lang_string('importgeneralsettings', 'backup'), 'moodle/backup:backupcourse'); $temp->add(new admin_setting_configtext('backup/import_general_maxresults', new lang_string('importgeneralmaxresults', 'backup'), new lang_string('importgeneralmaxresults_desc', 'backup'), 10)); + $temp->add(new admin_setting_configcheckbox('backup/import_general_duplicate_admin_allowed', + new lang_string('importgeneralduplicateadminallowed', 'backup'), + new lang_string('importgeneralduplicateadminallowed_desc', 'backup'), 0)); $ADMIN->add('backups', $temp); // Create a page for automated backups configuration and defaults. diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php index 880e75dfbfc..430e8bb0e46 100644 --- a/backup/util/dbops/restore_dbops.class.php +++ b/backup/util/dbops/restore_dbops.class.php @@ -1287,7 +1287,11 @@ abstract class restore_dbops { * 1F - None of the above, return true => User needs to be created * * if restoring from another site backup (cannot match by id here, replace it by email/firstaccess combination): - * 2A - Normal check: If match by username and mnethost and (email or non-zero firstaccess) => ok, return target user + * 2A - Normal check: + * 2A1 - If match by username and mnethost and (email or non-zero firstaccess) => ok, return target user + * 2A2 - Exceptional handling (MDL-21912): Match "admin" username. Then, if import_general_duplicate_admin_allowed is + * enabled, attempt to map the admin user to the user 'admin_[oldsiteid]' if it exists. If not, + * the user 'admin_[oldsiteid]' will be created in precheck_included users * 2B - Handle users deleted in DB and "alive" in backup file: * 2B1 - If match by mnethost and user is deleted in DB and not empty email = md5(username) and * (username LIKE 'backup_email.%' or non-zero firstaccess) => ok, return target user @@ -1305,7 +1309,7 @@ abstract class restore_dbops { * Note: for DB deleted users md5(username) is stored *sometimes* in the email field, * hence we are looking there for usernames if not empty. See delete_user() */ - protected static function precheck_user($user, $samesite) { + protected static function precheck_user($user, $samesite, $siteid = null) { global $CFG, $DB; // Handle checks from same site backups @@ -1376,7 +1380,7 @@ abstract class restore_dbops { // Handle checks from different site backups } else { - // 2A - If match by username and mnethost and + // 2A1 - If match by username and mnethost and // (email or non-zero firstaccess) => ok, return target user if ($rec = $DB->get_record_sql("SELECT * FROM {user} u @@ -1393,6 +1397,14 @@ abstract class restore_dbops { return $rec; // Matching user found, return it } + // 2A2 - If we're allowing conflicting admins, attempt to map user to admin_[oldsiteid]. + if (get_config('backup', 'import_general_duplicate_admin_allowed') && $user->username === 'admin' && $siteid + && $user->mnethostid == $CFG->mnet_localhost_id) { + if ($rec = $DB->get_record('user', array('username' => 'admin_' . $siteid))) { + return $rec; + } + } + // 2B - Handle users deleted in DB and "alive" in backup file // Note: for DB deleted users email is stored in username field, hence we // are looking there for emails. See delete_user() @@ -1500,6 +1512,9 @@ abstract class restore_dbops { // Calculate the context we are going to use for capability checking $context = context_course::instance($courseid); + // When conflicting users are detected we may need original site info. + $restoreinfo = restore_controller_dbops::load_controller($restoreid)->get_info(); + // Calculate if we have perms to create users, by checking: // to 'moodle/restore:createuser' and 'moodle/restore:userinfo' // and also observe $CFG->disableusercreationonrestore @@ -1535,14 +1550,28 @@ abstract class restore_dbops { } // Now, precheck that user and, based on returned results, annotate action/problem - $usercheck = self::precheck_user($user, $samesite); + $usercheck = self::precheck_user($user, $samesite, $restoreinfo->original_site_identifier_hash); if (is_object($usercheck)) { // No problem, we have found one user in DB to be mapped to // Annotate it, for later process. Set newitemid to mapping user->id self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, $usercheck->id); } else if ($usercheck === false) { // Found conflict, report it as problem - $problems[] = get_string('restoreuserconflict', '', $user->username); + if (!get_config('backup', 'import_general_duplicate_admin_allowed')) { + $problems[] = get_string('restoreuserconflict', '', $user->username); + } else if ($user->username == 'admin') { + if (!$cancreateuser) { + $problems[] = get_string('restorecannotcreateuser', '', $user->username); + } + if ($user->mnethostid != $CFG->mnet_localhost_id) { + $problems[] = get_string('restoremnethostidmismatch', '', $user->username); + } + if (!$problems) { + // Duplicate admin allowed, append original site idenfitier to username. + $user->username .= '_' . $restoreinfo->original_site_identifier_hash; + self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, 0, null, (array)$user); + } + } } else if ($usercheck === true) { // User needs to be created, check if we are able if ($cancreateuser) { // Can create user, set newitemid to 0 so will be created later diff --git a/lang/en/backup.php b/lang/en/backup.php index c0c00783cca..dc12e736873 100644 --- a/lang/en/backup.php +++ b/lang/en/backup.php @@ -153,6 +153,8 @@ $string['hidetypes'] = 'Hide type options'; $string['importgeneralsettings'] = 'General import defaults'; $string['importgeneralmaxresults'] = 'Maximum number of courses listed for import'; $string['importgeneralmaxresults_desc'] = 'This controls the number of courses that are listed during the first step of the import process'; +$string['importgeneralduplicateadminallowed'] = 'Allow admin conflict resolution'; +$string['importgeneralduplicateadminallowed_desc'] = 'If the site has an account with username \'admin\', then attempting to restore a backup file containing an account with username \'admin\' can cause a conflict. If this setting is enabled, the conflict will be resolved by changing the username in the backup file to \'admin_xyz\'.'; $string['importfile'] = 'Import a backup file'; $string['importbackupstage1action'] = 'Next'; $string['importbackupstage2action'] = 'Next'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 9b92f32e8d6..bdd8259b400 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1554,6 +1554,7 @@ $string['restorecancelled'] = 'Restore cancelled'; $string['restorecannotassignroles'] = 'Restore needs to assign roles and you do not have permission to do so'; $string['restorecannotcreateorassignroles'] = 'Restore needs to create or assign roles and you do not have permission to do so'; $string['restorecannotcreateuser'] = 'Restore needs to create user \'{$a}\' from backup file and you do not have permission to do so'; +$string['restoremnethostidmismatch'] = 'MNet host id of user \'{$a}\' does not match local MNet host ID.'; $string['restorecannotoverrideperms'] = 'Restore needs to override permissions and you do not have permission to do so'; $string['restorecoursenow'] = 'Restore this course now!'; $string['restoredaccount'] = 'Restored account';