Index: modules/user/user.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v retrieving revision 1.63 diff -u -p -r1.63 user.admin.inc --- modules/user/user.admin.inc 6 Jul 2009 19:07:21 -0000 1.63 +++ modules/user/user.admin.inc 7 Jul 2009 05:54:11 -0000 @@ -126,7 +126,6 @@ function user_filter_form_submit($form, * @see user_admin_account_submit() */ function user_admin_account() { - $header = array( array(), array('data' => t('Username'), 'field' => 'u.name'), @@ -195,7 +194,7 @@ function user_admin_account() { } $form['accounts'] = array( '#type' => 'checkboxes', - '#options' => $accounts + '#options' => $accounts, ); $form['pager'] = array('#markup' => theme('pager', NULL)); Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.1008 diff -u -p -r1.1008 user.module --- modules/user/user.module 5 Jul 2009 18:07:04 -0000 1.1008 +++ modules/user/user.module 7 Jul 2009 05:54:12 -0000 @@ -2263,46 +2263,67 @@ function user_multiple_role_edit($accoun function user_multiple_cancel_confirm(&$form_state) { $edit = $form_state['input']; - $form['accounts'] = array('#prefix' => '', '#tree' => TRUE); - // array_filter() returns only elements with TRUE values. - foreach (array_filter($edit['accounts']) as $uid => $value) { - $user = db_query('SELECT name FROM {users} WHERE uid = :uid', array(':uid' => $uid))->fetchField(); - $form['accounts'][$uid] = array('#type' => 'hidden', '#value' => $uid, '#prefix' => '
  • ', '#suffix' => check_plain($user) . "
  • \n"); - } - - $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel'); - - module_load_include('inc', 'user', 'user.pages'); - $form['user_cancel_method'] = array( - '#type' => 'item', - '#title' => t('When cancelling these accounts'), - ); - $form['user_cancel_method'] += user_cancel_methods(); - // Remove method descriptions. - foreach (element_children($form['user_cancel_method']) as $element) { - unset($form['user_cancel_method'][$element]['#description']); - } + // Remove uid1 from the $accounts array so it can't be accidentally deleted. + // This should have been prevented by JS, so this is just a failsafe check. + if ($edit['accounts'] && array_key_exists(1, $edit['accounts'])) { + $user1 = user_load(1); + unset($edit['accounts'][1]); + drupal_set_message(t('You attempted to cancel the %user1 account which might have broken your site; that part of the request has been disallowed.', array('%user1' => check_plain($user1->name))), 'warning'); + } + + if (count($edit['accounts']) > 0) { + $form['accounts'] = array('#prefix' => '', '#tree' => TRUE); + // array_filter() returns only elements with TRUE values. + foreach (array_filter($edit['accounts']) as $uid => $value) { + $user = db_query('SELECT name FROM {users} WHERE uid = :uid', array(':uid' => $uid))->fetchField(); + $form['accounts'][$uid] = array('#type' => 'hidden', '#value' => $uid, '#prefix' => '
  • ', '#suffix' => check_plain($user) . "
  • \n"); + } + + $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel'); + + module_load_include('inc', 'user', 'user.pages'); + $form['user_cancel_method'] = array( + '#type' => 'item', + '#title' => t('When cancelling these accounts'), + ); + $form['user_cancel_method'] += user_cancel_methods(); + // Remove method descriptions. + foreach (element_children($form['user_cancel_method']) as $element) { + unset($form['user_cancel_method'][$element]['#description']); + } - // Allow to send the account cancellation confirmation mail. - $form['user_cancel_confirm'] = array( - '#type' => 'checkbox', - '#title' => t('Require e-mail confirmation to cancel account.'), - '#default_value' => FALSE, - '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), - ); - // Also allow to send account canceled notification mail, if enabled. - $form['user_cancel_notify'] = array( - '#type' => 'checkbox', - '#title' => t('Notify user when account is canceled.'), - '#default_value' => FALSE, - '#access' => variable_get('user_mail_status_canceled_notify', FALSE), - '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), - ); + // Allow to send the account cancellation confirmation mail. + $form['user_cancel_confirm'] = array( + '#type' => 'checkbox', + '#title' => t('Require e-mail confirmation to cancel account.'), + '#default_value' => FALSE, + '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), + ); + // Also allow to send account canceled notification mail, if enabled. + $form['user_cancel_notify'] = array( + '#type' => 'checkbox', + '#title' => t('Notify user when account is canceled.'), + '#default_value' => FALSE, + '#access' => variable_get('user_mail_status_canceled_notify', FALSE), + '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), + ); - return confirm_form($form, - t('Are you sure you want to cancel these user accounts?'), - 'admin/user/user', t('This action cannot be undone.'), - t('Cancel accounts'), t('Cancel')); + return confirm_form($form, + t('Are you sure you want to cancel these user accounts?'), + 'admin/user/user', t('This action cannot be undone.'), + t('Cancel accounts'), t('Cancel')); + } + else { + $form = ''; + return confirm_form($form, + t('No valid user accounts to cancel'), + 'admin/user/user', + t('You have not selected any valid user accounts to cancel. Go back and try again.'), + t('Cancel'), + NULL, + t('Cancel') + ); + } } /** @@ -2316,6 +2337,10 @@ function user_multiple_cancel_confirm_su if ($form_state['values']['confirm']) { foreach ($form_state['values']['accounts'] as $uid => $value) { + // Prevent uid 1 from being deleted. + if ($uid <= 1) { + continue; + } // Prevent user administrators from deleting themselves without confirmation. if ($uid == $user->uid) { $admin_form_state = $form_state; Index: modules/user/user.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v retrieving revision 1.45 diff -u -p -r1.45 user.pages.inc --- modules/user/user.pages.inc 1 Jul 2009 12:47:30 -0000 1.45 +++ modules/user/user.pages.inc 7 Jul 2009 05:54:12 -0000 @@ -257,7 +257,7 @@ function user_profile_form($form_state, $form['_category'] = array('#type' => 'value', '#value' => $category); $form['_account'] = array('#type' => 'value', '#value' => $account); $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 30); - if (($account->uid == $user->uid && user_access('cancel account')) || user_access('administer users')) { + if ((($account->uid == $user->uid && user_access('cancel account')) || user_access('administer users')) && $account->uid > 1) { $form['cancel'] = array( '#type' => 'submit', '#value' => t('Cancel account'), Index: modules/user/user.test =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.test,v retrieving revision 1.44 diff -u -p -r1.44 user.test --- modules/user/user.test 1 Jul 2009 12:06:22 -0000 1.44 +++ modules/user/user.test 7 Jul 2009 05:54:12 -0000 @@ -162,7 +162,7 @@ class UserValidationTestCase extends Dru class UserCancelTestCase extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => t('Cancel account'), + 'name' => t('Cancel an account'), 'description' => t('Ensure that account cancellation methods work as expected.'), 'group' => t('User'), ); @@ -185,12 +185,12 @@ class UserCancelTestCase extends DrupalW // Attempt to cancel account. $this->drupalGet('user/' . $account->uid . '/edit'); - $this->assertNoRaw(t('Cancel account'), t('No cancel account button displayed.')); + $this->assertNoRaw(t('Cancel account'), t('Cancel account button is hidden if user does not have sufficient permission.')); // Attempt bogus account cancellation request confirmation. $timestamp = $account->login; $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); - $this->assertResponse(403, t('Bogus cancelling request rejected.')); + $this->assertResponse(403, t('Bogus account cancellation request was rejected.')); $account = user_load($account->uid); $this->assertTrue($account->status == 1, t('User account was not canceled.')); @@ -241,6 +241,24 @@ class UserCancelTestCase extends DrupalW } /** + * Test for protection of user account #1 while logged as uid1. + * This should never be possible, for obvious reasons. + */ + function testUserCancelUser1() { + variable_set('user_cancel_method', 'user_cancel_uid1_prevent'); + // Fetch the uid1 account and and log them in. + $user1 = $this->drupalCreateUser(); + $this->drupalLogin($user1); + $user1 = user_load(1, TRUE); + // Make sure the Cancel Account button does not show up on user/1/edit form. + $this->drupalGet('user/' . $user1->uid . '/edit'); + $this->assertNoRaw(t('Cancel account'), t('Cancel account button is always hidden for user #1.')); + // Make sure the Edit checkbox does not show up on the admin/user page. + $this->drupalGet('admin/user/user'); + $this->assertNoFieldByID('edit-accounts-1', 'unchecked', t('The Update checkbox for uid1 on the admin/user page does not appear.')); + } + + /** * Disable account and keep all content. */ function testUserBlock() { @@ -450,7 +468,7 @@ class UserCancelTestCase extends DrupalW } /** - * Create an administrative user and mass-delete other users. + * Create an administrative user and try to bulk-delete other users. */ function testMassUserCancelByAdmin() { variable_set('user_cancel_method', 'user_cancel_reassign'); @@ -461,25 +479,24 @@ class UserCancelTestCase extends DrupalW $admin_user = $this->drupalCreateUser(array('administer users')); $this->drupalLogin($admin_user); - // Create some users. + // Create three regular users. $users = array(); for ($i = 0; $i < 3; $i++) { $account = $this->drupalCreateUser(array()); $users[$account->uid] = $account; } - - // Cancel user accounts, including own one. + // Attempt to cancel newly created user accounts. $edit = array(); $edit['operation'] = 'cancel'; foreach ($users as $uid => $account) { - $edit['accounts[' . $uid . ']'] = TRUE; + if ($uid > 1) { + $edit['accounts[' . $uid . ']'] = TRUE; + } } - $edit['accounts[' . $admin_user->uid . ']'] = TRUE; $this->drupalPost('admin/user/user', $edit, t('Update')); $this->assertText(t('Are you sure you want to cancel these user accounts?'), t('Confirmation form to cancel accounts displayed.')); $this->assertText(t('When cancelling these accounts'), t('Allows to select account cancellation method.')); $this->assertText(t('Require e-mail confirmation to cancel account.'), t('Allows to send confirmation mail.')); - $this->assertText(t('Notify user when account is canceled.'), t('Allows to send notification mail.')); // Confirm deletion. $this->drupalPost(NULL, NULL, t('Cancel accounts')); @@ -489,11 +506,13 @@ class UserCancelTestCase extends DrupalW $status = $status && !user_load($account->uid, TRUE); } $this->assertTrue($status, t('Users deleted and not found in the database.')); + // $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - // Ensure that admin account was not cancelled. - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + // Ensure that neither the administrative account nor uid1 was cancelled. $admin_user = user_load($admin_user->uid); - $this->assertTrue($admin_user->status == 1, t('Administrative user is found in the database and enabled.')); + $this->assertTrue($admin_user->status == 1, t('The administrative user still exists and is enabled.')); + $uid1 = user_load(1); + $this->assertTrue($uid1->status == 1, t('User #1 still exists and is enabled.')); } }