Unverified Commit 6002e2dd authored by alexpott's avatar alexpott

Issue #2854252 by AdamPS, agoradesign, alexpott, Berdir, borisson_, xjm,...

Issue #2854252 by AdamPS, agoradesign, alexpott, Berdir, borisson_, xjm, jhedstrom, joachim: User forms broken for admin without 'administer users'
parent 29734423
......@@ -68,8 +68,19 @@ public function form(array $form, FormStateInterface $form_state) {
$form['#cache']['tags'] = $config->getCacheTags();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Check for new account.
$register = $account->isAnonymous();
$admin = $user->hasPermission('administer users');
// For a new account, there are 2 sub-cases:
// $self_register: A user creates their own, new, account
// (path '/user/register')
// $admin_create: An administrator creates a new account for another user
// (path '/admin/people/create')
// If the current user is logged in and has permission to create users
// then it must be the second case.
$admin_create = $register && $account->access('create');
$self_register = $register && !$admin_create;
// Account information.
$form['account'] = [
......@@ -85,7 +96,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#type' => 'email',
'#title' => $this->t('Email address'),
'#description' => $this->t('A valid email address. All emails from the system will be sent to this address. The email address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by email.'),
'#required' => !(!$account->getEmail() && $admin),
'#required' => !(!$account->getEmail() && $user->hasPermission('administer users')),
'#default_value' => (!$register ? $account->getEmail() : ''),
];
......@@ -103,7 +114,7 @@ public function form(array $form, FormStateInterface $form_state) {
'spellcheck' => 'false',
],
'#default_value' => (!$register ? $account->getAccountName() : ''),
'#access' => ($register || ($user->id() == $account->id() && $user->hasPermission('change own username')) || $admin),
'#access' => $account->name->access('edit'),
];
// Display password field only for existing users or when user is allowed to
......@@ -150,7 +161,7 @@ public function form(array $form, FormStateInterface $form_state) {
}
}
}
elseif (!$config->get('verify_mail') || $admin) {
elseif (!$config->get('verify_mail') || $admin_create) {
$form['account']['pass'] = [
'#type' => 'password_confirm',
'#size' => 25,
......@@ -169,7 +180,7 @@ public function form(array $form, FormStateInterface $form_state) {
}
}
if ($admin || !$register) {
if (!$self_register) {
$status = $account->get('status')->value;
}
else {
......@@ -181,7 +192,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#title' => $this->t('Status'),
'#default_value' => $status,
'#options' => [$this->t('Blocked'), $this->t('Active')],
'#access' => $admin,
'#access' => $account->status->access('edit'),
];
$roles = array_map(['\Drupal\Component\Utility\Html', 'escape'], user_role_names(TRUE));
......@@ -203,7 +214,7 @@ public function form(array $form, FormStateInterface $form_state) {
$form['account']['notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user of new account'),
'#access' => $register && $admin,
'#access' => $admin_create,
];
$user_preferred_langcode = $register ? $language_interface->getId() : $account->getPreferredLangcode();
......@@ -222,7 +233,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#open' => TRUE,
// Display language selector when either creating a user on the admin
// interface or editing a user account.
'#access' => !$register || $admin,
'#access' => !$self_register,
];
$form['language']['preferred_langcode'] = [
......
......@@ -19,6 +19,13 @@ class UserCancelForm extends ContentEntityConfirmFormBase {
*/
protected $cancelMethods;
/**
* Whether allowed to select cancellation method.
*
* @var bool
*/
protected $selectCancel;
/**
* The user being cancelled.
*
......@@ -49,14 +56,19 @@ public function getCancelUrl() {
public function getDescription() {
$description = '';
$default_method = $this->config('user.settings')->get('cancel_method');
if ($this->currentUser()->hasPermission('administer users') || $this->currentUser()->hasPermission('select account cancellation method')) {
$own_account = $this->entity->id() == $this->currentUser()->id();
if ($this->selectCancel) {
$description = $this->t('Select the method to cancel the account above.');
}
// Options supplied via user_cancel_methods() can have a custom
// #confirm_description property for the confirmation form description.
elseif (isset($this->cancelMethods[$default_method]['#confirm_description'])) {
// This text refers to "Your account" so only user it if cancelling own account.
elseif ($own_account && isset($this->cancelMethods[$default_method]['#confirm_description'])) {
$description = $this->cancelMethods[$default_method]['#confirm_description'];
}
else {
$description = $this->cancelMethods['#options'][$default_method];
}
return $description . ' ' . $this->t('This action cannot be undone.');
}
......@@ -75,18 +87,19 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$this->cancelMethods = user_cancel_methods();
// Display account cancellation method selection, if allowed.
$admin_access = $user->hasPermission('administer users');
$own_account = $this->entity->id() == $user->id();
$this->selectCancel = $user->hasPermission('administer users') || $user->hasPermission('select account cancellation method');
$form['user_cancel_method'] = [
'#type' => 'radios',
'#title' => ($this->entity->id() == $user->id() ? $this->t('When cancelling your account') : $this->t('When cancelling the account')),
'#access' => $admin_access || $user->hasPermission('select account cancellation method'),
'#title' => $own_account ? $this->t('When cancelling your account') : $this->t('When cancelling the account'),
'#access' => $this->selectCancel,
];
$form['user_cancel_method'] += $this->cancelMethods;
// Allow user administrators to skip the account cancellation confirmation
// mail (by default), as long as they do not attempt to cancel their own
// account.
$override_access = $admin_access && ($this->entity->id() != $user->id());
// When managing another user, can skip the account cancellation
// confirmation mail (by default).
$override_access = !$own_account;
$form['user_cancel_confirm'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require email confirmation to cancel account'),
......@@ -111,7 +124,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
// if desired.
$form['access'] = [
'#type' => 'value',
'#value' => $user->hasPermission('administer users'),
'#value' => !$own_account,
];
$form = parent::buildForm($form, $form_state);
......
......@@ -142,13 +142,28 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$form['operation'] = ['#type' => 'hidden', '#value' => 'cancel'];
// Display account cancellation method selection, if allowed.
$user = $this->currentUser();
$selectCancel = $user->hasPermission('administer users') || $user->hasPermission('select account cancellation method');
$form['user_cancel_method'] = [
'#type' => 'radios',
'#title' => $this->t('When cancelling these accounts'),
'#access' => $selectCancel,
];
$form['user_cancel_method'] += user_cancel_methods();
if (!$selectCancel) {
// Display an item to inform the user of the setting.
$default_method = $form['user_cancel_method']['#default_value'];
$form['user_cancel_method_show'] = [
'#type' => 'item',
'#title' => $this->t('When cancelling these accounts'),
'#plain_text' => $form['user_cancel_method']['#options'][$default_method],
];
}
// Allow to send the account cancellation confirmation mail.
$form['user_cancel_confirm'] = [
'#type' => 'checkbox',
......
......@@ -20,12 +20,10 @@ protected function actions(array $form, FormStateInterface $form_state) {
// The user account being edited.
$account = $this->entity;
// The user doing the editing.
$user = $this->currentUser();
$element['delete']['#type'] = 'submit';
$element['delete']['#value'] = $this->t('Cancel account');
$element['delete']['#submit'] = ['::editCancelSubmit'];
$element['delete']['#access'] = $account->id() > 1 && (($account->id() == $user->id() && $user->hasPermission('cancel account')) || $user->hasPermission('administer users'));
$element['delete']['#access'] = $account->id() > 1 && $account->access('delete');
return $element;
}
......
......@@ -18,7 +18,14 @@ public function form(array $form, FormStateInterface $form_state) {
$user = $this->currentUser();
/** @var \Drupal\user\UserInterface $account */
$account = $this->entity;
$admin = $user->hasPermission('administer users');
// This form is used for two cases:
// - Self-register (route = 'user.register').
// - Admin-create (route = 'user.admin_create').
// If the current user has permission to create users then it must be the
// second case.
$admin = $account->access('create');
// Pass access information to the submit handler. Running an access check
// inside the submit function interferes with form processing and breaks
// hook_form_alter().
......
<?php
namespace Drupal\user\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Test 'sub-admin' account with permission to edit some users but without 'administer users' permission.
*
* @group user
*/
class UserSubAdminTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user_access_test'];
/**
* Test create and cancel forms as 'sub-admin'.
*/
public function testSubAdmin() {
$user = $this->drupalCreateUser(['sub-admin']);
$this->drupalLogin($user);
// Test that the create user page has admin fields.
$this->drupalGet('admin/people/create');
$this->assertField("edit-name", "Name field exists.");
$this->assertField("edit-notify", "Notify field exists.");
// Not 'status' or 'roles' as they require extra permission.
$this->assertNoField("edit-status-0", "Status field missing.");
$this->assertNoField("edit-role", "Role field missing.");
// Test that create user gives an admin style message.
$edit = [
'name' => $this->randomMachineName(),
'mail' => $this->randomMachineName() . '@example.com',
'pass[pass1]' => $pass = $this->randomString(),
'pass[pass2]' => $pass,
'notify' => FALSE,
];
$this->drupalPostForm('admin/people/create', $edit, t('Create new account'));
$this->assertText(t('Created a new user account for @name. No email has been sent.', ['@name' => $edit['name']]), 'User created');
// Test that the cancel user page has admin fields.
$cancel_user = $this->createUser();
$this->drupalGet('user/' . $cancel_user->id() . '/cancel');
$this->assertRaw(t('Are you sure you want to cancel the account %name?', ['%name' => $cancel_user->getUsername()]), 'Confirmation form to cancel account displayed.');
$this->assertRaw(t('Disable the account and keep its content.') . ' ' . t('This action cannot be undone.'), 'Cannot select account cancellation method.');
// Test that cancel confirmation gives an admin style message.
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertRaw(t('%name has been disabled.', ['%name' => $cancel_user->getUsername()]), "Confirmation message displayed to user.");
// Repeat with permission to select account cancellation method.
$user->addRole($this->drupalCreateRole(['select account cancellation method']));
$user->save();
$cancel_user = $this->createUser();
$this->drupalGet('user/' . $cancel_user->id() . '/cancel');
$this->assertText(t('Select the method to cancel the account above.'), 'Allows to select account cancellation method.');
}
}
......@@ -93,11 +93,9 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
$is_own_account = $items ? $items->getEntity()->id() == $account->id() : FALSE;
switch ($field_definition->getName()) {
case 'name':
// Allow view access to anyone with access to the entity. Anonymous
// users should be able to access the username field during the
// registration process, otherwise the username and email constraints
// are not checked.
if ($operation == 'view' || ($items && $account->isAnonymous() && $items->getEntity()->isAnonymous())) {
// Allow view access to anyone with access to the entity.
// The username field is editable during the registration process.
if ($operation == 'view' || ($items && $items->getEntity()->isAnonymous())) {
return AccessResult::allowed()->cachePerPermissions();
}
// Allow edit access for the own user name if the permission is
......
......@@ -23,18 +23,38 @@ function user_access_test_user_access(User $entity, $operation, $account) {
// Deny delete access.
return AccessResult::forbidden();
}
// Account with role sub-admin can manage users with no roles.
if (count($entity->getRoles()) == 1) {
return AccessResult::allowedIfHasPermission($account, 'sub-admin');
}
return AccessResult::neutral();
}
/**
* Implements hook_entity_create_access().
*/
function user_access_test_entity_create_access(AccountInterface $account, array $context, $entity_bundle) {
if ($context['entity_type_id'] != 'user') {
return AccessResult::neutral();
}
// Account with role sub-admin can create users.
return AccessResult::allowedIfHasPermission($account, 'sub-admin');
}
/**
* Implements hook_entity_field_access().
*/
function user_access_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
// Account with role sub-admin can view the status, init and mail fields for user with no roles.
if ($operation === 'view' && in_array($field_definition->getName(), ['status', 'init', 'mail'])) {
// Account with role sub-admin can view the status, init and mail fields for
// user with no roles.
if ($field_definition->getTargetEntityTypeId() == 'user' && $operation === 'view' && in_array($field_definition->getName(), ['status', 'init', 'mail'])) {
if (($items == NULL) || (count($items->getEntity()->getRoles()) == 1)) {
return AccessResult::allowedIfHasPermission($account, 'sub-admin');
}
}
return AccessResult::neutral();
}
......@@ -14,7 +14,7 @@ access user profiles:
change own username:
title: 'Change own username'
select account cancellation method:
title: 'Select method for cancelling own account'
title: 'Select method for cancelling account'
restrict access: true
cancel account:
title: 'Cancel own user account'
......
......@@ -43,7 +43,7 @@ user.admin_create:
_entity_form: 'user.register'
_title: 'Add user'
requirements:
_permission: 'administer users'
_entity_create_access: 'user'
user.admin_permissions:
path: '/admin/people/permissions'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment