Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_access_test\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for user_access_test.
|
||||
*/
|
||||
class UserAccessTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access() for entity type "user".
|
||||
*/
|
||||
#[Hook('user_access')]
|
||||
public function userAccess(User $entity, $operation, $account): AccessResultInterface {
|
||||
if ($entity->getAccountName() == "no_edit" && $operation == "update") {
|
||||
// Deny edit access.
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
if ($entity->getAccountName() == "no_delete" && $operation == "delete") {
|
||||
// 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().
|
||||
*/
|
||||
#[Hook('entity_create_access')]
|
||||
public function entityCreateAccess(AccountInterface $account, array $context, $entity_bundle): AccessResultInterface {
|
||||
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().
|
||||
*/
|
||||
#[Hook('entity_field_access')]
|
||||
public function entityFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL): AccessResultInterface {
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
if (\Drupal::state()->get('user_access_test_forbid_mail_edit', FALSE)) {
|
||||
if ($operation === 'edit' && $items && $items->getEntity()->getEntityTypeId() === 'user' && $field_definition->getName() === 'mail') {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User access tests'
|
||||
type: module
|
||||
description: 'Support module for user access testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,2 @@
|
||||
sub-admin:
|
||||
title: 'Administer users with no roles'
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_auth_decorator_test;
|
||||
|
||||
use Drupal\user\UserAuthInterface;
|
||||
|
||||
/**
|
||||
* Helper to validate UserAuthInterface BC layers are functional.
|
||||
*/
|
||||
class UserAuthDecorator implements UserAuthInterface {
|
||||
|
||||
/**
|
||||
* Constructs a UserAuthDecorator object.
|
||||
*
|
||||
* @param \Drupal\user\UserAuthInterface $inner
|
||||
* The inner User.Auth service.
|
||||
*/
|
||||
public function __construct(protected UserAuthInterface $inner) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate($username, #[\SensitiveParameter] $password) {
|
||||
return $this->inner->authenticate($username, $password);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User Auth Service decorated only with UserAuthInterface'
|
||||
type: module
|
||||
description: 'Support module for user authentication testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,6 @@
|
||||
services:
|
||||
user_auth_decorator.user.auth:
|
||||
class: \Drupal\user_auth_decorator_test\UserAuthDecorator
|
||||
decorates: user.auth
|
||||
arguments:
|
||||
- '@user_auth_decorator.user.auth.inner'
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_config_override_test;
|
||||
|
||||
use Drupal\Core\Config\StorableConfigBase;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Tests overridden permissions.
|
||||
*/
|
||||
class ConfigOverrider implements ConfigFactoryOverrideInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOverrides($names): array {
|
||||
return [
|
||||
'user.role.anonymous' => [
|
||||
'permissions' => [9999 => 'access content'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheSuffix(): string {
|
||||
return 'user_config_override_test';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheableMetadata($name): CacheableMetadata {
|
||||
return new CacheableMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION): StorableConfigBase|null {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
name: 'Permission config overrider'
|
||||
type: module
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,5 @@
|
||||
services:
|
||||
user_config_override_test.overrider:
|
||||
class: Drupal\user_config_override_test\ConfigOverrider
|
||||
tags:
|
||||
- { name: config.factory.override }
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User custom password hash params test'
|
||||
type: module
|
||||
description: 'Support module for testing custom hashing password algorithm parameters.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,19 @@
|
||||
services:
|
||||
# The first argument of the hashing service (constructor of PhpPassword) is
|
||||
# the hashing algorithm. This should be set to PASSWORD_DEFAULT for hash
|
||||
# params test (In PHP 8, PASSWORD_DEFAULT equals PASSWORD_BCRYPT).
|
||||
#
|
||||
# The second argument of the hashing service (constructor of PhpPassword)
|
||||
# specifies the options passed to password_hash(). In PHP 8 the default 'cost'
|
||||
# value is 10. For the hash parameter test, the value must be higher than the
|
||||
# default value.
|
||||
#
|
||||
# Future versions of PHP may increase this value in order to counteract
|
||||
# increases in the speed and power of computers available to crack the hashes.
|
||||
# It is necessary to track changes of the default options when new versions
|
||||
# of PHP are released and increment the cost parameter accordingly.
|
||||
password:
|
||||
class: Drupal\Core\Password\PhpPassword
|
||||
arguments:
|
||||
- "2y"
|
||||
- { cost: 11 }
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_form_test\Hook;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for user_form_test.
|
||||
*/
|
||||
class UserFormTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter() for user_cancel_form().
|
||||
*/
|
||||
#[Hook('form_user_cancel_form_alter')]
|
||||
public function formUserCancelFormAlter(&$form, &$form_state) : void {
|
||||
$form['user_cancel_confirm']['#default_value'] = FALSE;
|
||||
$form['access']['#value'] = \Drupal::currentUser()->hasPermission('cancel other accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_base_field_info_alter().
|
||||
*/
|
||||
#[Hook('entity_base_field_info_alter')]
|
||||
public function entityBaseFieldInfoAlter(&$fields, EntityTypeInterface $entity_type): void {
|
||||
if ($entity_type->id() === 'user' && \Drupal::keyvalue('user_form_test')->get('user_form_test_constraint_roles_edit')) {
|
||||
$fields['roles']->addConstraint('FieldWidgetConstraint');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User module form tests'
|
||||
type: module
|
||||
description: 'Support module for user form testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,2 @@
|
||||
cancel other accounts:
|
||||
title: 'Cancel other user accounts'
|
||||
@ -0,0 +1,6 @@
|
||||
user_form_test.cancel:
|
||||
path: '/user_form_test_cancel/{user}'
|
||||
defaults:
|
||||
_entity_form: 'user.cancel'
|
||||
requirements:
|
||||
_permission: 'cancel other accounts'
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_hooks_test\Hook;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Contains hook implementations.
|
||||
*/
|
||||
class UserHooksTest {
|
||||
|
||||
/**
|
||||
* Alters the username.
|
||||
*
|
||||
* @param string|\Drupal\Component\Render\MarkupInterface $name
|
||||
* The username that is displayed for a user.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The object on which the operation is being performed.
|
||||
*/
|
||||
#[Hook('user_format_name_alter')]
|
||||
public function userFormatNameAlter(&$name, AccountInterface $account): void {
|
||||
if (\Drupal::keyValue('user_hooks_test')->get('user_format_name_alter', FALSE)) {
|
||||
if (\Drupal::keyValue('user_hooks_test')->get('user_format_name_alter_safe', FALSE)) {
|
||||
$name = new FormattableMarkup('<em>@uid</em>', ['@uid' => $account->id()]);
|
||||
}
|
||||
else {
|
||||
$name = '<em>' . $account->id() . '</em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User module hooks tests'
|
||||
type: module
|
||||
description: 'Support module for user hooks testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_language_test\Controller;
|
||||
|
||||
/**
|
||||
* Returns responses for User Language Test routes.
|
||||
*/
|
||||
class UserLanguageTestController {
|
||||
|
||||
/**
|
||||
* Builds the response.
|
||||
*/
|
||||
public function buildPostResponse() {
|
||||
return ['#markup' => 'It works!'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user_language_test\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a User Language Test form.
|
||||
*/
|
||||
class UserLanguageTestForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_language_test';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$form['#action'] = Url::fromRoute('user_language_test.post_response')->toString();
|
||||
|
||||
$form['actions'] = [
|
||||
'#type' => 'actions',
|
||||
'submit' => [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Send'),
|
||||
],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User language tests'
|
||||
type: module
|
||||
description: 'Support module for user language testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,16 @@
|
||||
user_language_test.post_response:
|
||||
path: '/user-language-test/post'
|
||||
defaults:
|
||||
_controller: Drupal\user_language_test\Controller\UserLanguageTestController::buildPostResponse
|
||||
methods: [post]
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
user_language_test.form:
|
||||
path: '/user-language-test/form'
|
||||
defaults:
|
||||
_form: 'Drupal\user_language_test\Form\UserLanguageTestForm'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'User permission tests'
|
||||
type: module
|
||||
description: 'Support module for user permission testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,8 @@
|
||||
c:
|
||||
title: 'Test permission'
|
||||
a:
|
||||
title: 'Test permission'
|
||||
b:
|
||||
title: 'Test permission'
|
||||
user_permissions_test:
|
||||
title: 'Permission with the same name as the module'
|
||||
@ -0,0 +1,33 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_access_perm
|
||||
label: test_access_perm
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'views_test_data test permission'
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,44 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_access_role
|
||||
label: test_access_role
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: views_test_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
fields:
|
||||
id:
|
||||
id: id
|
||||
field: id
|
||||
table: views_test_data
|
||||
plugin_id: numeric
|
||||
access:
|
||||
type: role
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
page_1:
|
||||
display_options:
|
||||
path: test-role
|
||||
display_plugin: page
|
||||
display_title: Page
|
||||
id: page_1
|
||||
position: 1
|
||||
@ -0,0 +1,138 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_field_permission
|
||||
label: test_node_revision_vid
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Uid
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: field
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
permission:
|
||||
id: permission
|
||||
table: user__roles
|
||||
field: permission
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Permission
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: separator
|
||||
separator: ', '
|
||||
plugin_id: user_permissions
|
||||
filters: { }
|
||||
sorts: { }
|
||||
@ -0,0 +1,132 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_filter_current_user
|
||||
label: Users
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Filter
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
uid:
|
||||
id: uid
|
||||
table: users
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
filters:
|
||||
uid_current:
|
||||
id: uid_current
|
||||
table: users
|
||||
field: uid_current
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: user
|
||||
plugin_id: user_current
|
||||
sorts:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user
|
||||
tags: { }
|
||||
@ -0,0 +1,141 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_filter_permission
|
||||
label: test_filter_permission
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
uid:
|
||||
id: uid
|
||||
table: users
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
link_to_user: false
|
||||
plugin_id: user
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
filters:
|
||||
permission:
|
||||
id: permission
|
||||
table: user__roles
|
||||
field: permission
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: or
|
||||
value:
|
||||
'access user profiles': 'access user profiles'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: '0'
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
reduce_duplicates: true
|
||||
plugin_id: user_permissions
|
||||
sorts:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
plugin_id: standard
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
@ -0,0 +1,92 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_groupwise_user
|
||||
label: test_groupwise_user
|
||||
module: views
|
||||
description: ''
|
||||
tag: default
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
options:
|
||||
perm: 'access user profiles'
|
||||
type: perm
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
fields:
|
||||
name:
|
||||
field: name
|
||||
id: name
|
||||
table: users_field_data
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
nid:
|
||||
field: nid
|
||||
id: nid
|
||||
relationship: uid_representative
|
||||
table: node_field_data
|
||||
plugin_id: node
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
filters:
|
||||
status:
|
||||
expose:
|
||||
operator: '0'
|
||||
field: status
|
||||
group: 1
|
||||
id: status
|
||||
table: users_field_data
|
||||
value: '1'
|
||||
plugin_id: boolean
|
||||
entity_type: user
|
||||
entity_field: status
|
||||
pager:
|
||||
options:
|
||||
items_per_page: 10
|
||||
type: full
|
||||
query:
|
||||
type: views_query
|
||||
relationships:
|
||||
uid_representative:
|
||||
admin_label: 'Representative node'
|
||||
field: uid_representative
|
||||
group_type: group
|
||||
id: uid_representative
|
||||
relationship: none
|
||||
required: false
|
||||
subquery_namespace: ''
|
||||
subquery_order: DESC
|
||||
subquery_regenerate: true
|
||||
subquery_sort: node.nid
|
||||
subquery_view: ''
|
||||
table: users_field_data
|
||||
plugin_id: groupwise_max
|
||||
row:
|
||||
type: fields
|
||||
sorts:
|
||||
created:
|
||||
field: uid
|
||||
id: uid
|
||||
order: ASC
|
||||
table: users_field_data
|
||||
plugin_id: field
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
style:
|
||||
type: default
|
||||
title: test_groupwise_user
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,62 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: test_plugin_argument_default_current_user
|
||||
label: test_plugin_argument_default_current_user
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
arguments:
|
||||
'null':
|
||||
default_action: default
|
||||
default_argument_type: current_user
|
||||
field: 'null'
|
||||
id: 'null'
|
||||
must_not_be: false
|
||||
table: views
|
||||
plugin_id: 'null'
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
fields:
|
||||
title:
|
||||
alter:
|
||||
alter_text: false
|
||||
ellipsis: true
|
||||
html: false
|
||||
make_link: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
word_boundary: true
|
||||
empty_zero: false
|
||||
field: title
|
||||
hide_empty: false
|
||||
id: title
|
||||
table: node_field_data
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
pager:
|
||||
options:
|
||||
id: 0
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,66 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_bulk_form
|
||||
label: test_user_bulk_form
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
class: ''
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
user_bulk_form:
|
||||
id: user_bulk_form
|
||||
table: users
|
||||
field: user_bulk_form
|
||||
plugin_id: user_bulk_form
|
||||
entity_type: user
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
sorts:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
order: ASC
|
||||
plugin_id: user
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
filters:
|
||||
status:
|
||||
id: status
|
||||
table: users_field_data
|
||||
field: status
|
||||
operator: '='
|
||||
value: '1'
|
||||
plugin_id: boolean
|
||||
entity_type: user
|
||||
entity_field: status
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: null
|
||||
display_options:
|
||||
path: test-user-bulk-form
|
||||
@ -0,0 +1,242 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_bulk_form_combine_filter
|
||||
label: test_user_bulk_form_combine_filter
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
user_bulk_form:
|
||||
id: user_bulk_form
|
||||
table: users
|
||||
field: user_bulk_form
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
action_title: 'With selection'
|
||||
include_exclude: exclude
|
||||
selected_actions: { }
|
||||
entity_type: user
|
||||
plugin_id: user_bulk_form
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: user_name
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
filters:
|
||||
combine:
|
||||
id: combine
|
||||
table: views
|
||||
field: combine
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: dummy
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
fields:
|
||||
user_bulk_form: user_bulk_form
|
||||
name: name
|
||||
plugin_id: combine
|
||||
sorts: { }
|
||||
title: test_user_bulk_form_combine_filter
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: test-user-bulk-form-combine-filter
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
@ -0,0 +1,60 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_changed
|
||||
label: test_user_changed
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
row:
|
||||
type: fields
|
||||
style:
|
||||
type: default
|
||||
fields:
|
||||
name:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
changed:
|
||||
id: changed
|
||||
table: users_field_data
|
||||
field: changed
|
||||
label: 'Updated date'
|
||||
element_label_colon: true
|
||||
plugin_id: field
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: html_date
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
entity_type: user
|
||||
entity_field: changed
|
||||
filters: { }
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
page_1:
|
||||
display_options:
|
||||
path: test_user_changed
|
||||
display_plugin: page
|
||||
display_title: Page
|
||||
id: page_1
|
||||
position: 0
|
||||
@ -0,0 +1,130 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_data
|
||||
label: test_user_data
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access user profiles'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
data:
|
||||
id: data
|
||||
table: users
|
||||
field: data
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Data
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
data_module: views_test_config
|
||||
data_name: test_value_name
|
||||
plugin_id: user_data
|
||||
entity_type: user
|
||||
filters:
|
||||
uid:
|
||||
value:
|
||||
value: '2'
|
||||
table: users_field_data
|
||||
field: uid
|
||||
id: uid
|
||||
expose:
|
||||
operator: '0'
|
||||
group: 1
|
||||
plugin_id: numeric
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: users_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
plugin_id: date
|
||||
entity_type: user
|
||||
entity_field: created
|
||||
@ -0,0 +1,478 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_fields_access
|
||||
label: test_user_fields_access
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
class: ''
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
name: name
|
||||
status: status
|
||||
mail: mail
|
||||
init: init
|
||||
created: created
|
||||
info:
|
||||
name:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
status:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
mail:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
init:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
created:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: '-1'
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Name
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: user_name
|
||||
settings:
|
||||
link_to_entity: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
plugin_id: field
|
||||
status:
|
||||
id: status
|
||||
table: users_field_data
|
||||
field: status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Status
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: boolean
|
||||
settings:
|
||||
format: default
|
||||
format_custom_true: ''
|
||||
format_custom_false: ''
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: status
|
||||
plugin_id: field
|
||||
mail:
|
||||
id: mail
|
||||
table: users_field_data
|
||||
field: mail
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Email
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: basic_string
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: mail
|
||||
plugin_id: field
|
||||
init:
|
||||
id: init
|
||||
table: users_field_data
|
||||
field: init
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Init
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: basic_string
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: init
|
||||
plugin_id: field
|
||||
created:
|
||||
id: created
|
||||
table: users_field_data
|
||||
field: created
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Created
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: medium
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: created
|
||||
plugin_id: field
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: ''
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: test_user_fields_access
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
@ -0,0 +1,60 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_name
|
||||
label: test_user_name
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
row:
|
||||
type: fields
|
||||
style:
|
||||
type: default
|
||||
fields:
|
||||
name:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
filters:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: uid_op
|
||||
label: Name
|
||||
operator: uid_op
|
||||
identifier: uid
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
page_1:
|
||||
display_options:
|
||||
path: test_user_name
|
||||
display_plugin: page
|
||||
display_title: Page
|
||||
id: page_1
|
||||
position: 0
|
||||
@ -0,0 +1,114 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_user_relationship
|
||||
label: test_user_relationship
|
||||
module: views
|
||||
description: ''
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
fields:
|
||||
name:
|
||||
alter:
|
||||
absolute: false
|
||||
alter_text: false
|
||||
ellipsis: true
|
||||
external: false
|
||||
html: false
|
||||
make_link: false
|
||||
nl2br: false
|
||||
replace_spaces: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
trim_whitespace: false
|
||||
word_boundary: true
|
||||
element_default_classes: true
|
||||
element_label_colon: true
|
||||
empty_zero: false
|
||||
field: name
|
||||
hide_alter_empty: false
|
||||
hide_empty: false
|
||||
id: name
|
||||
table: users_field_data
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
title:
|
||||
alter:
|
||||
absolute: false
|
||||
alter_text: false
|
||||
ellipsis: false
|
||||
html: false
|
||||
make_link: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
empty_zero: false
|
||||
field: title
|
||||
hide_empty: false
|
||||
id: title
|
||||
label: ''
|
||||
table: node_field_data
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
uid:
|
||||
alter:
|
||||
absolute: false
|
||||
alter_text: false
|
||||
ellipsis: true
|
||||
external: false
|
||||
html: false
|
||||
make_link: false
|
||||
nl2br: false
|
||||
replace_spaces: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
trim_whitespace: false
|
||||
word_boundary: true
|
||||
element_default_classes: true
|
||||
element_label_colon: true
|
||||
empty_zero: false
|
||||
field: uid
|
||||
hide_alter_empty: false
|
||||
hide_empty: false
|
||||
id: uid
|
||||
table: users_field_data
|
||||
plugin_id: field
|
||||
type: user
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
pager:
|
||||
options:
|
||||
items_per_page: 10
|
||||
type: full
|
||||
query:
|
||||
options:
|
||||
query_comment: ''
|
||||
type: views_query
|
||||
title: test_user_relationship
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
default_field_elements: true
|
||||
hide_empty: false
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,218 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_roles_rid
|
||||
label: test_user_roles_rid
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: user_name
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: users_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: user
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
sorts:
|
||||
uid:
|
||||
id: uid
|
||||
table: users
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments:
|
||||
roles_target_id:
|
||||
id: roles_target_id
|
||||
table: user__roles
|
||||
field: roles_target_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: empty
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: true
|
||||
title: '{{ arguments.roles_target_id }}'
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
summary_options:
|
||||
base_path: ''
|
||||
count: true
|
||||
items_per_page: 25
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
break_phrase: false
|
||||
add_table: false
|
||||
require_value: false
|
||||
reduce_duplicates: false
|
||||
plugin_id: user__roles_rid
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: user_roles_rid_test
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
@ -0,0 +1,37 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_user_uid_argument
|
||||
label: test_user_uid_argument
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
fields:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
plugin_id: user
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
arguments:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
title_enable: true
|
||||
title: '{{ arguments.uid }}'
|
||||
plugin_id: user_uid
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,39 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: test_view_argument_validate_user
|
||||
label: test_view_argument_validate_user
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
arguments:
|
||||
'null':
|
||||
default_argument_type: fixed
|
||||
field: 'null'
|
||||
id: 'null'
|
||||
must_not_be: false
|
||||
table: views
|
||||
validate:
|
||||
type: 'entity:user'
|
||||
plugin_id: 'null'
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,39 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: test_view_argument_validate_username
|
||||
label: test_view_argument_validate_username
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
arguments:
|
||||
'null':
|
||||
default_argument_type: fixed
|
||||
field: 'null'
|
||||
id: 'null'
|
||||
must_not_be: false
|
||||
table: views
|
||||
validate:
|
||||
type: user_name
|
||||
plugin_id: 'null'
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,176 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_views_handler_field_role
|
||||
label: test_views_handler_field_role
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access user profiles'
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
items_per_page: null
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Name
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
roles_target_id:
|
||||
id: roles_target_id
|
||||
table: user__roles
|
||||
field: roles_target_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Roles
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: separator
|
||||
separator: ''
|
||||
plugin_id: user_roles
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: users_field_data
|
||||
field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: '0'
|
||||
group: 1
|
||||
plugin_id: boolean
|
||||
entity_type: user
|
||||
entity_field: status
|
||||
sorts:
|
||||
uid:
|
||||
id: uid
|
||||
table: users_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: user
|
||||
entity_field: uid
|
||||
plugin_id: standard
|
||||
title: test_user_role
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: null
|
||||
display_options:
|
||||
path: test-views-handler-field-role
|
||||
@ -0,0 +1,62 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
id: test_views_handler_field_user_name
|
||||
label: test_views_handler_field_user_name
|
||||
module: views
|
||||
description: ''
|
||||
tag: default
|
||||
base_table: users_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
fields:
|
||||
name:
|
||||
alter:
|
||||
absolute: false
|
||||
alter_text: false
|
||||
ellipsis: false
|
||||
html: false
|
||||
make_link: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
empty_zero: false
|
||||
field: name
|
||||
hide_empty: false
|
||||
id: name
|
||||
label: ''
|
||||
table: users_field_data
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
pager:
|
||||
type: full
|
||||
query:
|
||||
options:
|
||||
query_comment: ''
|
||||
type: views_query
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
sorts:
|
||||
uid:
|
||||
id: uid
|
||||
table: users
|
||||
field: uid
|
||||
plugin_id: standard
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'User test views'
|
||||
type: module
|
||||
description: 'Provides default views for views user tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:user
|
||||
- drupal:views
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\views_ui\Functional\UITestBase;
|
||||
|
||||
/**
|
||||
* Tests views role access plugin UI.
|
||||
*
|
||||
* @group user
|
||||
* @see \Drupal\user\Plugin\views\access\Role
|
||||
*/
|
||||
class AccessRoleUITest extends UITestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_access_role'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'user_test_views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['user_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the role access plugin UI.
|
||||
*/
|
||||
public function testAccessRoleUI(): void {
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$entity_type_manager->getStorage('user_role')->create(['id' => 'custom_role', 'label' => 'Custom role'])->save();
|
||||
$access_url = "admin/structure/views/nojs/display/test_access_role/default/access_options";
|
||||
$this->drupalGet($access_url);
|
||||
$this->submitForm(['access_options[role][custom_role]' => 1], 'Apply');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$view = $entity_type_manager->getStorage('view')->load('test_access_role');
|
||||
|
||||
$display = $view->getDisplay('default');
|
||||
$this->assertEquals(['custom_role' => 'custom_role'], $display['display_options']['access']['options']['role']);
|
||||
|
||||
// Test changing access plugin from role to none.
|
||||
$this->drupalGet('admin/structure/views/nojs/display/test_access_role/default/access');
|
||||
$this->submitForm(['access[type]' => 'none'], 'Apply');
|
||||
$this->submitForm([], 'Save');
|
||||
// Verify that role option is not set.
|
||||
$view = $entity_type_manager->getStorage('view')->load('test_access_role');
|
||||
$display = $view->getDisplay('default');
|
||||
$this->assertFalse(isset($display['display_options']['access']['options']['role']));
|
||||
}
|
||||
|
||||
}
|
||||
14
web/core/modules/user/tests/src/Functional/GenericTest.php
Normal file
14
web/core/modules/user/tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for user.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonAnonTest extends RoleResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonBasicAuthTest extends RoleResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonCookieTest extends RoleResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Resource test base for the UserRole entity.
|
||||
*/
|
||||
abstract class RoleResourceTestBase extends ConfigEntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'user_role';
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\RoleInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$role = Role::create([
|
||||
'id' => 'llama',
|
||||
'label' => 'Llama',
|
||||
]);
|
||||
$role->save();
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'weight' => 2,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'id' => 'llama',
|
||||
'label' => 'Llama',
|
||||
'is_admin' => FALSE,
|
||||
'permissions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleXmlAnonTest extends RoleResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleXmlBasicAuthTest extends RoleResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleXmlCookieTest extends RoleResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonAnonTest extends UserResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\user_auth_decorator_test\UserAuthDecorator;
|
||||
|
||||
/**
|
||||
* Run UserJsonBasicAuthTest with a user.auth decorator.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonBasicAuthDecoratedTest extends UserJsonBasicAuthTest {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user_auth_decorator_test'];
|
||||
|
||||
/**
|
||||
* Test that the UserAuthDecorator is providing user.auth.
|
||||
*/
|
||||
public function testServiceDecorated(): void {
|
||||
$service = \Drupal::service('user.auth');
|
||||
$this->assertInstanceOf(UserAuthDecorator::class, $service);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonBasicAuthTest extends UserResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonCookieTest extends UserResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use PHPUnit\Framework\Attributes\Before;
|
||||
|
||||
/**
|
||||
* Resource test base for the user entity.
|
||||
*/
|
||||
abstract class UserResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'user';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'changed' => NULL,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $labelFieldName = 'name';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $firstCreatedEntityId = 4;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $secondCreatedEntityId = 5;
|
||||
|
||||
/**
|
||||
* Marks some tests as skipped because XML cannot be deserialized.
|
||||
*/
|
||||
#[Before]
|
||||
public function userResourceTestBaseSkipTests(): void {
|
||||
if (in_array($this->name(), ['testPatchDxForSecuritySensitiveBaseFields', 'testPatchSecurityOtherUser'], TRUE)) {
|
||||
if (static::$format === 'xml') {
|
||||
$this->markTestSkipped('Deserialization of the XML format is not supported.');
|
||||
}
|
||||
|
||||
if (static::$auth === FALSE) {
|
||||
$this->markTestSkipped('The anonymous user is never allowed to modify itself.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access user profiles']);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['administer users']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Llama" user.
|
||||
$user = User::create(['created' => 123456789]);
|
||||
$user->setUsername('Llama')
|
||||
->setChangedTime(123456789)
|
||||
->activate()
|
||||
->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createAnotherEntity() {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->entity->createDuplicate();
|
||||
$user->setUsername($user->label() . '_dupe');
|
||||
$user->save();
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'uid' => [
|
||||
['value' => 3],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp(123456789)->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Drama llama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests PATCHing security-sensitive base fields of the logged in account.
|
||||
*/
|
||||
public function testPatchDxForSecuritySensitiveBaseFields(): void {
|
||||
$this->initAuthentication();
|
||||
$this->provisionEntityResource();
|
||||
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = static::$auth ? $this->account : User::load(0);
|
||||
// @todo Remove the array_diff_key() call in https://www.drupal.org/node/2821077.
|
||||
$original_normalization = array_diff_key($this->serializer->normalize($user, static::$format), ['created' => TRUE, 'changed' => TRUE, 'name' => TRUE]);
|
||||
|
||||
// Since this test must be performed by the user that is being modified,
|
||||
// we cannot use $this->getUrl().
|
||||
$url = $user->toUrl()->setOption('query', ['_format' => static::$format]);
|
||||
$request_options = [
|
||||
RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
|
||||
];
|
||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
|
||||
|
||||
// Test case 1: changing email.
|
||||
$normalization = $original_normalization;
|
||||
$normalization['mail'] = [['value' => 'new-email@example.com']];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// DX: 422 when changing email without providing the password.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response, FALSE, FALSE, FALSE, FALSE);
|
||||
|
||||
$normalization['pass'] = [['existing' => 'wrong']];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// DX: 422 when changing email while providing a wrong password.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response, FALSE, FALSE, FALSE, FALSE);
|
||||
|
||||
$normalization['pass'] = [['existing' => $this->account->passRaw]];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// 200 for well-formed request.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceResponse(200, FALSE, $response);
|
||||
|
||||
// Test case 2: changing password.
|
||||
$normalization = $original_normalization;
|
||||
$new_password = $this->randomString();
|
||||
$normalization['pass'] = [['value' => $new_password]];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// DX: 422 when changing password without providing the current password.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the Password.\n", $response, FALSE, FALSE, FALSE, FALSE);
|
||||
|
||||
$normalization['pass'][0]['existing'] = $this->account->pass_raw;
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// 200 for well-formed request.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceResponse(200, FALSE, $response);
|
||||
|
||||
// Verify that we can log in with the new password.
|
||||
$this->assertRpcLogin($user->getAccountName(), $new_password);
|
||||
|
||||
// Update password in $this->account, prepare for future requests.
|
||||
$this->account->passRaw = $new_password;
|
||||
$this->initAuthentication();
|
||||
$request_options = [
|
||||
RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
|
||||
];
|
||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
|
||||
|
||||
// Test case 3: changing name.
|
||||
$normalization = $original_normalization;
|
||||
$normalization['name'] = [['value' => 'Cooler Llama']];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// DX: 403 when modifying username without required permission.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceErrorResponse(403, "Access denied on updating field 'name'.", $response);
|
||||
|
||||
$this->grantPermissionsToTestedRole(['change own username']);
|
||||
|
||||
// 200 for well-formed request.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceResponse(200, FALSE, $response);
|
||||
|
||||
// Verify that we can log in with the new username.
|
||||
$this->assertRpcLogin('Cooler Llama', $new_password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that logging in with the given username and password works.
|
||||
*
|
||||
* @param string $username
|
||||
* The username to log in with.
|
||||
* @param string $password
|
||||
* The password to log in with.
|
||||
*/
|
||||
protected function assertRpcLogin($username, $password) {
|
||||
$request_body = [
|
||||
'name' => $username,
|
||||
'pass' => $password,
|
||||
];
|
||||
$request_options = [
|
||||
RequestOptions::HEADERS => [],
|
||||
RequestOptions::BODY => $this->serializer->encode($request_body, 'json'),
|
||||
];
|
||||
$response = $this->request('POST', Url::fromRoute('user.login.http')->setRouteParameter('_format', 'json'), $request_options);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests PATCHing security-sensitive base fields to change other users.
|
||||
*/
|
||||
public function testPatchSecurityOtherUser(): void {
|
||||
$this->initAuthentication();
|
||||
$this->provisionEntityResource();
|
||||
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->account;
|
||||
$original_normalization = array_diff_key($this->serializer->normalize($user, static::$format), ['changed' => TRUE]);
|
||||
|
||||
// Since this test must be performed by the user that is being modified,
|
||||
// we cannot use $this->getUrl().
|
||||
$url = $user->toUrl()->setOption('query', ['_format' => static::$format]);
|
||||
$request_options = [
|
||||
RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
|
||||
];
|
||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
|
||||
|
||||
$normalization = $original_normalization;
|
||||
$normalization['mail'] = [['value' => 'new-email@example.com']];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// Try changing user 1's email.
|
||||
$user1 = [
|
||||
'mail' => [['value' => 'another_email_address@example.com']],
|
||||
'uid' => [['value' => 1]],
|
||||
'name' => [['value' => 'another_user_name']],
|
||||
'pass' => [['existing' => $this->account->passRaw]],
|
||||
'uuid' => [['value' => '2e9403a4-d8af-4096-a116-624710140be0']],
|
||||
] + $original_normalization;
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($user1, static::$format);
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
// Ensure the email address has not changed.
|
||||
$this->assertEquals('admin@example.com', $this->entityStorage->loadUnchanged(1)->getEmail());
|
||||
$this->assertResourceErrorResponse(403, "Access denied on updating field 'uid'. The entity ID cannot be changed.", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
return "The 'access user profiles' permission is required.";
|
||||
|
||||
case 'PATCH':
|
||||
return "Users can only update their own account, unless they have the 'administer users' permission.";
|
||||
|
||||
case 'DELETE':
|
||||
return "The 'cancel account' permission is required.";
|
||||
|
||||
default:
|
||||
return parent::getExpectedUnauthorizedAccessMessage($method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
|
||||
// @see \Drupal\user\UserAccessControlHandler::checkAccess()
|
||||
$result = parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated);
|
||||
|
||||
if (!\Drupal::currentUser()->hasPermission('access user profiles')) {
|
||||
$result->addCacheContexts(['user']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return [
|
||||
'url.site',
|
||||
// Due to the 'mail' field's access varying by user.
|
||||
'user',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserXmlAnonTest extends UserResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserXmlBasicAuthTest extends UserResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserXmlCookieTest extends UserResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests user-account links.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserAccountLinksTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['menu_ui', 'block', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_menu_block:account', ['id' => 'user_account_links_test_system_menu_block_account']);
|
||||
// Make test-page default.
|
||||
$this->config('system.site')->set('page.front', '/test-page')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the secondary menu.
|
||||
*/
|
||||
public function testSecondaryMenu(): void {
|
||||
// Create a regular user.
|
||||
$user = $this->drupalCreateUser([]);
|
||||
|
||||
// Log in and get the homepage.
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
// For a logged-in user, expect the secondary menu to have links for "My
|
||||
// account" and "Log out".
|
||||
$this->assertSession()->elementsCount('xpath', '//nav[@id="block-user-account-links-test-system-menu-block-account"]/ul/li/a[contains(@href, "user") and text()="My account"]', 1);
|
||||
$this->assertSession()->elementsCount('xpath', '//nav[@id="block-user-account-links-test-system-menu-block-account"]/ul/li/a[contains(@href, "user/logout") and text()="Log out"]', 1);
|
||||
|
||||
// Log out and get the homepage.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
// For a logged-out user, expect the secondary menu to have a "Log in" link.
|
||||
$this->assertSession()->elementsCount('xpath', '//nav[@id="block-user-account-links-test-system-menu-block-account"]/ul/li/a[contains(@href, "user/login") and text()="Log in"]', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests disabling the 'My account' link.
|
||||
*/
|
||||
public function testDisabledAccountLink(): void {
|
||||
// Create an admin user and log in.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer menu',
|
||||
]));
|
||||
|
||||
// Verify that the 'My account' link exists before we check for its
|
||||
// disappearance.
|
||||
$this->assertSession()->elementsCount('xpath', '//nav[@id="block-user-account-links-test-system-menu-block-account"]/ul/li/a[contains(@href, "user") and text()="My account"]', 1);
|
||||
|
||||
// Verify that the 'My account' link is enabled. Do not assume the value of
|
||||
// auto-increment is 1. Use XPath to obtain input element id and name using
|
||||
// the consistent label text.
|
||||
$this->drupalGet('admin/structure/menu/manage/account');
|
||||
$label = $this->xpath('//label[contains(.,:text)]/@for', [':text' => 'Enable My account menu link']);
|
||||
$this->assertSession()->checkboxChecked($label[0]->getText());
|
||||
|
||||
// Disable the 'My account' link.
|
||||
$edit['links[menu_plugin_id:user.page][enabled]'] = FALSE;
|
||||
$this->drupalGet('admin/structure/menu/manage/account');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Get the homepage.
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
// Verify that the 'My account' link does not appear when disabled.
|
||||
$this->assertSession()->elementNotExists('xpath', '//nav[@id="block-user-account-links-test-system-menu-block-account"]/ul/li/a[contains(@href, "user") and text()="My account"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests page title is set correctly on user account tabs.
|
||||
*/
|
||||
public function testAccountPageTitles(): void {
|
||||
// Default page titles are suffixed with the site name - Drupal.
|
||||
$title_suffix = ' | Drupal';
|
||||
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->titleEquals('Log in' . $title_suffix);
|
||||
|
||||
$this->drupalGet('user/login');
|
||||
$this->assertSession()->titleEquals('Log in' . $title_suffix);
|
||||
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertSession()->titleEquals('Create new account' . $title_suffix);
|
||||
|
||||
$this->drupalGet('user/password');
|
||||
$this->assertSession()->titleEquals('Reset your password' . $title_suffix);
|
||||
|
||||
// Check the page title for registered users is "My Account" in menus.
|
||||
$this->drupalLogin($this->drupalCreateUser());
|
||||
// After login, the client is redirected to /user.
|
||||
$this->assertSession()->linkExists('My account', 0, "Page title of /user is 'My Account' in menus for registered users");
|
||||
$this->assertSession()->linkByHrefExists(\Drupal::urlGenerator()->generate('user.page'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that logout URL redirects an anonymous user to the front page.
|
||||
*/
|
||||
public function testAnonymousLogout(): void {
|
||||
$this->drupalGet('user/logout');
|
||||
$this->assertSession()->addressEquals('/');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// The redirection shouldn't affect other pages.
|
||||
$this->drupalGet('admin');
|
||||
$this->assertSession()->addressEquals('/admin');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests users' ability to change their own administration language.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserAdminLanguageTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to access admin pages and administer languages.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A non-administrator user for this test.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $regularUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'language', 'language_test', 'user_language_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// User to add and remove language.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
// User to check non-admin access.
|
||||
$this->regularUser = $this->drupalCreateUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that admin language is not configurable in single language sites.
|
||||
*/
|
||||
public function testUserAdminLanguageConfigurationNotAvailableWithOnlyOneLanguage(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->setLanguageNegotiation();
|
||||
$path = 'user/' . $this->adminUser->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
// Ensure administration pages language settings widget is not available.
|
||||
$this->assertSession()->fieldNotExists('edit-preferred-admin-langcode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that admin language negotiation is configurable only if enabled.
|
||||
*/
|
||||
public function testUserAdminLanguageConfigurationAvailableWithAdminLanguageNegotiation(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->addCustomLanguage();
|
||||
$path = 'user/' . $this->adminUser->id() . '/edit';
|
||||
|
||||
// Checks with user administration pages language negotiation disabled.
|
||||
$this->drupalGet($path);
|
||||
// Ensure administration pages language settings widget is not available.
|
||||
$this->assertSession()->fieldNotExists('edit-preferred-admin-langcode');
|
||||
|
||||
// Checks with user administration pages language negotiation enabled.
|
||||
$this->setLanguageNegotiation();
|
||||
$this->drupalGet($path);
|
||||
// Ensure administration pages language settings widget is available.
|
||||
$this->assertSession()->fieldExists('edit-preferred-admin-langcode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the admin language is configurable only for administrators.
|
||||
*
|
||||
* If a user has the permission "access administration pages" or
|
||||
* "view the administration theme", they should be able to see the setting to
|
||||
* pick the language they want those pages in.
|
||||
*
|
||||
* If a user does not have that permission, it would confusing for them to
|
||||
* have a setting for pages they cannot access, so they should not be able to
|
||||
* set a language for those pages.
|
||||
*/
|
||||
public function testUserAdminLanguageConfigurationAvailableIfAdminLanguageNegotiationIsEnabled(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Adds a new language, because with only one language, setting won't show.
|
||||
$this->addCustomLanguage();
|
||||
$this->setLanguageNegotiation();
|
||||
$path = 'user/' . $this->adminUser->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
// Ensure administration pages language setting is visible for admin.
|
||||
$this->assertSession()->fieldExists('edit-preferred-admin-langcode');
|
||||
|
||||
// Ensure administration pages language setting is visible for editors.
|
||||
$editor = $this->drupalCreateUser(['view the administration theme']);
|
||||
$this->drupalLogin($editor);
|
||||
$path = 'user/' . $editor->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->fieldExists('edit-preferred-admin-langcode');
|
||||
|
||||
// Ensure administration pages language setting is hidden for non-admins.
|
||||
$this->drupalLogin($this->regularUser);
|
||||
$path = 'user/' . $this->regularUser->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->fieldNotExists('edit-preferred-admin-langcode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the actual language negotiation.
|
||||
*/
|
||||
public function testActualNegotiation(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->addCustomLanguage();
|
||||
$this->setLanguageNegotiation();
|
||||
|
||||
// Even though we have admin language negotiation, so long as the user has
|
||||
// no preference set, negotiation will fall back further.
|
||||
$path = 'user/' . $this->adminUser->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-default');
|
||||
$this->drupalGet('xx/' . $path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-url');
|
||||
|
||||
// Set a preferred language code for the user.
|
||||
$edit = [];
|
||||
$edit['preferred_admin_langcode'] = 'xx';
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Test negotiation with the URL method first. The admin method will only
|
||||
// be used if the URL method did not match.
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-user-admin');
|
||||
$this->drupalGet('xx/' . $path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-url');
|
||||
|
||||
// Test negotiation with the admin language method first. The admin method
|
||||
// will be used at all times.
|
||||
$this->setLanguageNegotiation(TRUE);
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-user-admin');
|
||||
$this->drupalGet('xx/' . $path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-user-admin');
|
||||
|
||||
// Make sure 'language-user-admin' plugin does not fail when a route is
|
||||
// restricted to POST requests and language negotiation with the admin
|
||||
// language method is used.
|
||||
$this->drupalGet('/user-language-test/form');
|
||||
$this->submitForm([], 'Send');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Unset the preferred language code for the user.
|
||||
$edit = [];
|
||||
$edit['preferred_admin_langcode'] = '';
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-default');
|
||||
$this->drupalGet('xx/' . $path);
|
||||
$this->assertSession()->pageTextContains('Language negotiation method: language-url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the User interface negotiation detection method.
|
||||
*
|
||||
* Enables the "Account preference for administration pages" language
|
||||
* detection method for the User interface language negotiation type.
|
||||
*
|
||||
* @param bool $admin_first
|
||||
* Whether the admin negotiation should be first.
|
||||
*/
|
||||
public function setLanguageNegotiation($admin_first = FALSE): void {
|
||||
$edit = [
|
||||
'language_interface[enabled][language-user-admin]' => TRUE,
|
||||
'language_interface[enabled][language-url]' => TRUE,
|
||||
'language_interface[weight][language-user-admin]' => ($admin_first ? -12 : -8),
|
||||
'language_interface[weight][language-url]' => -10,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/detection');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for adding a custom language.
|
||||
*/
|
||||
public function addCustomLanguage(): void {
|
||||
$langcode = 'xx';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the user admin listing if views is not enabled.
|
||||
*
|
||||
* @group user
|
||||
* @see user_admin_account()
|
||||
*/
|
||||
class UserAdminListingTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the listing.
|
||||
*/
|
||||
public function testUserListing(): void {
|
||||
// Ensure the anonymous user cannot access the admin listing.
|
||||
$this->drupalGet('admin/people');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Create a bunch of users.
|
||||
$accounts = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$account = $this->drupalCreateUser();
|
||||
$accounts[$account->label()] = $account;
|
||||
}
|
||||
// Create a blocked user.
|
||||
$account = $this->drupalCreateUser();
|
||||
$account->block();
|
||||
$account->save();
|
||||
$accounts[$account->label()] = $account;
|
||||
|
||||
// Create a user at a certain timestamp.
|
||||
$account = $this->drupalCreateUser();
|
||||
$account->created = 1363219200;
|
||||
$account->save();
|
||||
$accounts[$account->label()] = $account;
|
||||
$timestamp_user = $account->label();
|
||||
|
||||
$rid_1 = $this->drupalCreateRole([], 'custom_role_1', 'custom_role_1');
|
||||
$rid_2 = $this->drupalCreateRole([], 'custom_role_2', 'custom_role_2');
|
||||
|
||||
$account = $this->drupalCreateUser();
|
||||
$account->addRole($rid_1)->addRole($rid_2)->save();
|
||||
$accounts[$account->label()] = $account;
|
||||
$role_account_name = $account->label();
|
||||
|
||||
// Create an admin user and look at the listing.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$accounts[$admin_user->label()] = $admin_user;
|
||||
|
||||
$accounts['admin'] = User::load(1);
|
||||
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Ensure the admin user can access the admin listing.
|
||||
$this->drupalGet('admin/people');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$result = $this->xpath('//table[contains(@class, "responsive-enabled")]/tbody/tr');
|
||||
$result_accounts = [];
|
||||
foreach ($result as $account) {
|
||||
$account_columns = $account->findAll('css', 'td');
|
||||
$name = $account_columns[0]->find('css', 'a')->getText();
|
||||
$roles = [];
|
||||
$account_roles = $account_columns[2]->findAll('css', 'td ul li');
|
||||
if (!empty($account_roles)) {
|
||||
foreach ($account_roles as $element) {
|
||||
$roles[] = $element->getText();
|
||||
}
|
||||
}
|
||||
|
||||
$result_accounts[$name] = [
|
||||
'name' => $name,
|
||||
'status' => $account_columns[1]->getText(),
|
||||
'roles' => $roles,
|
||||
'member_for' => $account_columns[3]->getText(),
|
||||
'last_access' => $account_columns[4]->getText(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->assertEmpty(array_keys(array_diff_key($result_accounts, $accounts)), 'Ensure all accounts are listed.');
|
||||
foreach ($result_accounts as $name => $values) {
|
||||
$this->assertEquals($accounts[$name]->status->value, $values['status'] == 'active');
|
||||
}
|
||||
|
||||
$expected_roles = ['custom_role_1', 'custom_role_2'];
|
||||
$this->assertEquals($expected_roles, $result_accounts[$role_account_name]['roles'], 'Ensure roles are listed properly.');
|
||||
|
||||
$this->assertEquals(\Drupal::service('date.formatter')->formatTimeDiffSince($accounts[$timestamp_user]->created->value), $result_accounts[$timestamp_user]['member_for'], 'Ensure the right member time is displayed.');
|
||||
|
||||
$this->assertEquals('never', $result_accounts[$timestamp_user]['last_access'], 'Ensure the last access time is "never".');
|
||||
}
|
||||
|
||||
}
|
||||
232
web/core/modules/user/tests/src/Functional/UserAdminTest.php
Normal file
232
web/core/modules/user/tests/src/Functional/UserAdminTest.php
Normal file
@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests user administration page functionality.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserAdminTest extends BrowserTestBase {
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['taxonomy', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Gets the xpath selector for a user account.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user to get the link for.
|
||||
*
|
||||
* @return string
|
||||
* The xpath selector for the user link.
|
||||
*/
|
||||
private static function getLinkSelectorForUser(UserInterface $user): string {
|
||||
return '//td[contains(@class, "views-field-name")]/a[text()="' . $user->getAccountName() . '"]';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a user and deletes it.
|
||||
*/
|
||||
public function testUserAdmin(): void {
|
||||
$config = $this->config('user.settings');
|
||||
$user_a = $this->drupalCreateUser();
|
||||
$user_a->name = 'User A';
|
||||
$user_a->mail = $this->randomMachineName() . '@example.com';
|
||||
$user_a->save();
|
||||
$user_b = $this->drupalCreateUser(['administer taxonomy']);
|
||||
$user_b->name = 'User B';
|
||||
$user_b->save();
|
||||
$user_c = $this->drupalCreateUser(['administer taxonomy']);
|
||||
$user_c->name = 'User C';
|
||||
$user_c->save();
|
||||
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create admin user to delete registered user.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
// Use a predictable name so that we can reliably order the user admin page
|
||||
// by name.
|
||||
$admin_user->name = 'Admin user';
|
||||
$admin_user->save();
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/people');
|
||||
$this->assertSession()->pageTextContains($user_a->getAccountName());
|
||||
$this->assertSession()->pageTextContains($user_b->getAccountName());
|
||||
$this->assertSession()->pageTextContains($user_c->getAccountName());
|
||||
$this->assertSession()->pageTextContains($admin_user->getAccountName());
|
||||
|
||||
// Test for existence of edit link in table.
|
||||
$link = $user_a->toLink('Edit', 'edit-form', [
|
||||
'query' => ['destination' => $user_a->toUrl('collection')->toString()],
|
||||
'attributes' => ['aria-label' => 'Edit ' . $user_a->label()],
|
||||
])->toString();
|
||||
$this->assertSession()->responseContains($link);
|
||||
|
||||
// Test exposed filter elements.
|
||||
foreach (['user', 'role', 'permission', 'status'] as $field) {
|
||||
$this->assertSession()->fieldExists("edit-$field");
|
||||
}
|
||||
// Make sure the reduce duplicates element from the ManyToOneHelper is not
|
||||
// displayed.
|
||||
$this->assertSession()->fieldNotExists('edit-reduce-duplicates');
|
||||
|
||||
// Filter the users by name/email.
|
||||
$this->drupalGet('admin/people', ['query' => ['user' => $user_a->getAccountName()]]);
|
||||
$result = $this->xpath('//table/tbody/tr');
|
||||
$this->assertCount(1, $result, 'Filter by username returned the right amount.');
|
||||
$this->assertEquals($user_a->getAccountName(), $result[0]->find('xpath', '/td[2]/a')->getText(), 'Filter by username returned the right user.');
|
||||
|
||||
$this->drupalGet('admin/people', ['query' => ['user' => $user_a->getEmail()]]);
|
||||
$result = $this->xpath('//table/tbody/tr');
|
||||
$this->assertCount(1, $result, 'Filter by username returned the right amount.');
|
||||
$this->assertEquals($user_a->getAccountName(), $result[0]->find('xpath', '/td[2]/a')->getText(), 'Filter by username returned the right user.');
|
||||
|
||||
// Filter the users by permission 'administer taxonomy'.
|
||||
$this->drupalGet('admin/people', ['query' => ['permission' => 'administer taxonomy']]);
|
||||
|
||||
// Check if the correct users show up.
|
||||
$this->assertSession()->elementNotExists('xpath', static::getLinkSelectorForUser($user_a));
|
||||
$this->assertSession()->elementExists('xpath', static::getLinkSelectorForUser($user_b));
|
||||
$this->assertSession()->elementExists('xpath', static::getLinkSelectorForUser($user_c));
|
||||
|
||||
// Filter the users by role. Grab the system-generated role name for User C.
|
||||
$roles = $user_c->getRoles();
|
||||
unset($roles[array_search(RoleInterface::AUTHENTICATED_ID, $roles)]);
|
||||
$this->drupalGet('admin/people', ['query' => ['role' => reset($roles)]]);
|
||||
|
||||
// Check if the correct users show up when filtered by role.
|
||||
$this->assertSession()->elementNotExists('xpath', static::getLinkSelectorForUser($user_a));
|
||||
$this->assertSession()->elementNotExists('xpath', static::getLinkSelectorForUser($user_b));
|
||||
$this->assertSession()->elementExists('xpath', static::getLinkSelectorForUser($user_c));
|
||||
|
||||
// Test blocking of a user.
|
||||
$account = $user_storage->load($user_c->id());
|
||||
$this->assertTrue($account->isActive(), 'User C not blocked');
|
||||
$edit = [];
|
||||
$edit['action'] = 'user_block_user_action';
|
||||
$edit['user_bulk_form[4]'] = TRUE;
|
||||
$config
|
||||
->set('notify.status_blocked', TRUE)
|
||||
->save();
|
||||
$this->drupalGet('admin/people', [
|
||||
// Sort the table by username so that we know reliably which user will be
|
||||
// targeted with the blocking action.
|
||||
'query' => ['order' => 'name', 'sort' => 'asc'],
|
||||
]);
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
$site_name = $this->config('system.site')->get('name');
|
||||
$this->assertMailString('body', 'Your account on ' . $site_name . ' has been blocked.', 1, 'Blocked message found in the mail sent to user C.');
|
||||
$account = $user_storage->load($user_c->id());
|
||||
$this->assertTrue($account->isBlocked(), 'User C blocked');
|
||||
|
||||
// Test filtering on admin page for blocked users
|
||||
$this->drupalGet('admin/people', ['query' => ['status' => 2]]);
|
||||
$this->assertSession()->elementNotExists('xpath', static::getLinkSelectorForUser($user_a));
|
||||
$this->assertSession()->elementNotExists('xpath', static::getLinkSelectorForUser($user_b));
|
||||
$this->assertSession()->elementExists('xpath', static::getLinkSelectorForUser($user_c));
|
||||
|
||||
// Test unblocking of a user from /admin/people page and sending of
|
||||
// activation mail
|
||||
$edit_unblock = [];
|
||||
$edit_unblock['action'] = 'user_unblock_user_action';
|
||||
$edit_unblock['user_bulk_form[4]'] = TRUE;
|
||||
$this->drupalGet('admin/people', [
|
||||
// Sort the table by username so that we know reliably which user will be
|
||||
// targeted with the blocking action.
|
||||
'query' => ['order' => 'name', 'sort' => 'asc'],
|
||||
]);
|
||||
$this->submitForm($edit_unblock, 'Apply to selected items');
|
||||
$account = $user_storage->load($user_c->id());
|
||||
$this->assertTrue($account->isActive(), 'User C unblocked');
|
||||
$this->assertMail("to", $account->getEmail(), "Activation mail sent to user C");
|
||||
|
||||
// Test blocking and unblocking another user from /user/[uid]/edit form and
|
||||
// sending of activation mail.
|
||||
$user_d = $this->drupalCreateUser([]);
|
||||
$account1 = $user_storage->load($user_d->id());
|
||||
$this->drupalGet('user/' . $account1->id() . '/edit');
|
||||
$this->submitForm(['status' => 0], 'Save');
|
||||
$account1 = $user_storage->load($user_d->id());
|
||||
$this->assertTrue($account1->isBlocked(), 'User D blocked');
|
||||
$this->drupalGet('user/' . $account1->id() . '/edit');
|
||||
$this->submitForm(['status' => TRUE], 'Save');
|
||||
$account1 = $user_storage->load($user_d->id());
|
||||
$this->assertTrue($account1->isActive(), 'User D unblocked');
|
||||
$this->assertMail("to", $account1->getEmail(), "Activation mail sent to user D");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the alternate notification email address for user mails.
|
||||
*/
|
||||
public function testNotificationEmailAddress(): void {
|
||||
// Test that the Notification Email address field is on the config page.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer users',
|
||||
'administer account settings',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config/people/accounts');
|
||||
$this->assertSession()->responseContains('id="edit-mail-notification-address"');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Test custom user registration approval email address(es).
|
||||
$config = $this->config('user.settings');
|
||||
// Allow users to register with admin approval.
|
||||
$config
|
||||
->set('verify_mail', TRUE)
|
||||
->set('register', UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)
|
||||
->save();
|
||||
// Set the site and notification email addresses.
|
||||
$system = $this->config('system.site');
|
||||
$server_address = $this->randomMachineName() . '@example.com';
|
||||
$notify_address = $this->randomMachineName() . '@example.com';
|
||||
$system
|
||||
->set('mail', $server_address)
|
||||
->set('mail_notification', $notify_address)
|
||||
->save();
|
||||
// Register a new user account.
|
||||
$edit = [];
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$edit['mail'] = $edit['name'] . '@example.com';
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$subject = 'Account details for ' . $edit['name'] . ' at ' . $system->get('name') . ' (pending admin approval)';
|
||||
// Ensure that admin notification mail is sent to the configured
|
||||
// Notification Email address.
|
||||
$admin_mail = $this->drupalGetMails([
|
||||
'to' => $notify_address,
|
||||
'from' => $server_address,
|
||||
'subject' => $subject,
|
||||
]);
|
||||
$this->assertCount(1, $admin_mail, 'New user mail to admin is sent to configured Notification Email address');
|
||||
// Ensure that user notification mail is sent from the configured
|
||||
// Notification Email address.
|
||||
$user_mail = $this->drupalGetMails([
|
||||
'to' => $edit['mail'],
|
||||
'from' => $server_address,
|
||||
'reply-to' => $notify_address,
|
||||
'subject' => $subject,
|
||||
]);
|
||||
$this->assertCount(1, $user_mail, 'New user mail to user is sent from configured Notification Email address');
|
||||
}
|
||||
|
||||
}
|
||||
143
web/core/modules/user/tests/src/Functional/UserBlocksTest.php
Normal file
143
web/core/modules/user/tests/src/Functional/UserBlocksTest.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests user blocks.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserBlocksTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with the 'administer blocks' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['administer blocks']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPlaceBlock('user_login_block', ['id' => 'user_blocks_test_user_login_block']);
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user login block is hidden from user/login.
|
||||
*/
|
||||
public function testUserLoginBlockVisibility(): void {
|
||||
// Array keyed list where key being the URL address and value being expected
|
||||
// visibility as boolean type.
|
||||
$paths = [
|
||||
'node' => TRUE,
|
||||
'user/login' => FALSE,
|
||||
'user/register' => TRUE,
|
||||
'user/password' => TRUE,
|
||||
];
|
||||
foreach ($paths as $path => $expected_visibility) {
|
||||
$this->drupalGet($path);
|
||||
if ($expected_visibility) {
|
||||
$this->assertSession()->elementExists('xpath', '//div[@id="block-user-blocks-test-user-login-block" and @role="form"]');
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->elementNotExists('xpath', '//div[@id="block-user-blocks-test-user-login-block" and @role="form"]');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the user login block.
|
||||
*/
|
||||
public function testUserLoginBlock(): void {
|
||||
// Create a user with some permission that anonymous users lack.
|
||||
$user = $this->drupalCreateUser(['administer permissions']);
|
||||
|
||||
// Log in using the block.
|
||||
$edit = [];
|
||||
$edit['name'] = $user->getAccountName();
|
||||
$edit['pass'] = $user->passRaw;
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextNotContains('User login');
|
||||
|
||||
// Check that we are still on the same page.
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('user.admin_permissions'));
|
||||
|
||||
// Now, log out and repeat with a non-403 page.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('filter/tips');
|
||||
$this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'MISS');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextNotContains('User login');
|
||||
// Verify that we are still on the same page after login for allowed page.
|
||||
$this->assertSession()->responseMatches('!<title.*?Compose tips.*?</title>!');
|
||||
|
||||
// Log out again and repeat with a non-403 page including query arguments.
|
||||
$this->drupalLogout();
|
||||
// @todo This test should not check for cache hits. Because it does and the
|
||||
// cache has some clever redirect logic internally, we need to request the
|
||||
// page twice to see the cache HIT in the headers.
|
||||
// @see https://www.drupal.org/project/drupal/issues/2551419 #154
|
||||
$this->drupalGet('filter/tips', ['query' => ['cat' => 'dog']]);
|
||||
$this->drupalGet('filter/tips', ['query' => ['foo' => 'bar']]);
|
||||
$this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'HIT');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextNotContains('User login');
|
||||
// Verify that we are still on the same page after login for allowed page.
|
||||
$this->assertSession()->responseMatches('!<title.*?Compose tips.*?</title>!');
|
||||
$this->assertStringContainsString('/filter/tips?foo=bar', $this->getUrl(), 'Correct query arguments are displayed after login');
|
||||
|
||||
// Repeat with different query arguments.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('filter/tips', ['query' => ['foo' => 'baz']]);
|
||||
$this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'HIT');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextNotContains('User login');
|
||||
// Verify that we are still on the same page after login for allowed page.
|
||||
$this->assertSession()->responseMatches('!<title.*?Compose tips.*?</title>!');
|
||||
$this->assertStringContainsString('/filter/tips?foo=baz', $this->getUrl(), 'Correct query arguments are displayed after login');
|
||||
|
||||
// Check that the user login block is not vulnerable to information
|
||||
// disclosure to third party sites.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('http://example.com/', ['external' => FALSE]);
|
||||
$this->submitForm($edit, 'Log in');
|
||||
// Check that we remain on the site after login.
|
||||
$this->assertSession()->addressEquals($user->toUrl('canonical'));
|
||||
|
||||
// Verify that form validation errors are displayed immediately for forms
|
||||
// in blocks and not on subsequent page requests.
|
||||
$this->drupalLogout();
|
||||
$edit = [];
|
||||
$edit['name'] = 'foo';
|
||||
$edit['pass'] = 'invalid password';
|
||||
$this->drupalGet('filter/tips');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextContains('Unrecognized username or password. Forgot your password?');
|
||||
$this->drupalGet('filter/tips');
|
||||
$this->assertSession()->pageTextNotContains('Unrecognized username or password. Forgot your password?');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the User entity's cache tags.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserCacheTagsTest extends EntityWithUriCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Give anonymous users permission to view user profiles, so that we can
|
||||
// verify the cache tags of cached versions of user profile pages.
|
||||
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
$user_role->grantPermission('access user profiles');
|
||||
$user_role->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Llama" user.
|
||||
$user = User::create([
|
||||
'name' => 'Llama',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
$user->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAdditionalCacheTagsForEntityListing(): array {
|
||||
return ['user:0', 'user:1'];
|
||||
}
|
||||
|
||||
}
|
||||
724
web/core/modules/user/tests/src/Functional/UserCancelTest.php
Normal file
724
web/core/modules/user/tests/src/Functional/UserCancelTest.php
Normal file
@ -0,0 +1,724 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\comment\CommentInterface;
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\node\Traits\NodeAccessTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Ensure that account cancellation methods work as expected.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserCancelTest extends BrowserTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
use NodeAccessTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'comment'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to cancel account without permission.
|
||||
*/
|
||||
public function testUserCancelWithoutPermission(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser([]);
|
||||
$this->drupalLogin($account);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create a node.
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/edit');
|
||||
$this->assertSession()->pageTextNotContains("Cancel account");
|
||||
|
||||
// Attempt bogus account cancellation request confirmation.
|
||||
$timestamp = $account->getLastLoginTime();
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$user_storage->resetCache([$account->id()]);
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->isActive(), 'User account was not canceled.');
|
||||
|
||||
// Confirm user's content has not been altered.
|
||||
$node_storage->resetCache([$node->id()]);
|
||||
$test_node = $node_storage->load($node->id());
|
||||
$this->assertEquals($account->id(), $test_node->getOwnerId(), 'Node of the user has not been altered.');
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ability to change the permission for canceling users.
|
||||
*/
|
||||
public function testUserCancelChangePermission(): void {
|
||||
\Drupal::service('module_installer')->install(['user_form_test']);
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
|
||||
// Create a regular user.
|
||||
$account = $this->drupalCreateUser([]);
|
||||
|
||||
$admin_user = $this->drupalCreateUser(['cancel other accounts']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Delete regular user.
|
||||
$this->drupalGet('user_form_test_cancel/' . $account->id());
|
||||
$this->submitForm([], 'Confirm');
|
||||
|
||||
// Confirm deletion.
|
||||
$this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been deleted.");
|
||||
$this->assertNull(User::load($account->id()), 'User is not found in the database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user account for uid 1 cannot be cancelled.
|
||||
*
|
||||
* This should never be possible, or the site owner would become unable to
|
||||
* administer the site.
|
||||
*/
|
||||
public function testUserCancelUid1(): void {
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
\Drupal::service('module_installer')->install(['views']);
|
||||
|
||||
// Try to cancel uid 1's account with a different user.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = [
|
||||
'action' => 'user_cancel_user_action',
|
||||
'user_bulk_form[0]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/people');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
|
||||
// Verify that uid 1's account was not cancelled.
|
||||
$user1 = $user_storage->load(1);
|
||||
$this->assertTrue($user1->isActive(), 'User #1 still exists and is not blocked.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt invalid account cancellations.
|
||||
*/
|
||||
public function testUserCancelInvalid(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser(['cancel account']);
|
||||
$this->drupalLogin($account);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create a node.
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$timestamp = time();
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Attempt bogus account cancellation request confirmation.
|
||||
$bogus_timestamp = $timestamp + 60;
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account, $bogus_timestamp));
|
||||
$this->assertSession()->pageTextContains('You have tried to use an account cancellation link that has expired. Request a new one using the form below.');
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->isActive(), 'User account was not canceled.');
|
||||
|
||||
// Attempt expired account cancellation request confirmation.
|
||||
$bogus_timestamp = $timestamp - 86400 - 60;
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account, $bogus_timestamp));
|
||||
$this->assertSession()->pageTextContains('You have tried to use an account cancellation link that has expired. Request a new one using the form below.');
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->isActive(), 'User account was not canceled.');
|
||||
|
||||
// Confirm user's content has not been altered.
|
||||
$test_node = $node_storage->load($node->id());
|
||||
$this->assertEquals($account->id(), $test_node->getOwnerId(), 'Node of the user has not been altered.');
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable account and keep all content.
|
||||
*/
|
||||
public function testUserBlock(): void {
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_block')->save();
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$web_user = $this->drupalCreateUser(['cancel account']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($web_user->id());
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel your account?');
|
||||
$this->assertSession()->pageTextContains('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your username.');
|
||||
$this->assertSession()->pageTextNotContains('Cancellation method');
|
||||
|
||||
// Confirm account cancellation.
|
||||
$timestamp = time();
|
||||
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Confirm account cancellation request.
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->isBlocked(), 'User has been blocked.');
|
||||
|
||||
// Confirm that the confirmation message made it through to the end user.
|
||||
$this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been disabled.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable account and unpublish all content.
|
||||
*/
|
||||
public function testUserBlockUnpublish(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_block_unpublish')->save();
|
||||
// Create comment field on page.
|
||||
$this->addDefaultCommentField('node', 'page');
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser(['cancel account']);
|
||||
$this->drupalLogin($account);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create a node with two revisions.
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
$settings = get_object_vars($node);
|
||||
$settings['revision'] = 1;
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Add a comment to the page.
|
||||
$comment_subject = $this->randomMachineName(8);
|
||||
$comment_body = $this->randomMachineName(8);
|
||||
$comment = Comment::create([
|
||||
'subject' => $comment_subject,
|
||||
'comment_body' => $comment_body,
|
||||
'entity_id' => $node->id(),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'comment',
|
||||
'status' => CommentInterface::PUBLISHED,
|
||||
'uid' => $account->id(),
|
||||
]);
|
||||
$comment->save();
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel your account?');
|
||||
$this->assertSession()->pageTextContains('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators.');
|
||||
|
||||
// Confirm account cancellation.
|
||||
$timestamp = time();
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Confirm account cancellation request.
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
// Confirm that the user was redirected to the front page.
|
||||
$this->assertSession()->addressEquals('');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Confirm that the confirmation message made it through to the end user.
|
||||
$this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been disabled.");
|
||||
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->isBlocked(), 'User has been blocked.');
|
||||
|
||||
// Confirm user's content has been unpublished.
|
||||
$test_node = $node_storage->load($node->id());
|
||||
$this->assertFalse($test_node->isPublished(), 'Node of the user has been unpublished.');
|
||||
$test_node = $node_storage->loadRevision($node->getRevisionId());
|
||||
$this->assertFalse($test_node->isPublished(), 'Node revision of the user has been unpublished.');
|
||||
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('comment');
|
||||
$comment = $storage->load($comment->id());
|
||||
$this->assertFalse($comment->isPublished(), 'Comment of the user has been unpublished.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests nodes are unpublished even if inaccessible to cancelling user.
|
||||
*/
|
||||
public function testUserBlockUnpublishNodeAccess(): void {
|
||||
\Drupal::service('module_installer')->install(['node_access_test', 'user_form_test']);
|
||||
|
||||
// Setup node access
|
||||
node_access_rebuild();
|
||||
$this->addPrivateField(NodeType::load('page'));
|
||||
\Drupal::state()->set('node_access_test.private', TRUE);
|
||||
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_block_unpublish')->save();
|
||||
|
||||
// Create a user.
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
$account = $this->drupalCreateUser(['cancel account']);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create a published private node.
|
||||
$node = $this->drupalCreateNode([
|
||||
'uid' => $account->id(),
|
||||
'type' => 'page',
|
||||
'status' => 1,
|
||||
'private' => TRUE,
|
||||
]);
|
||||
|
||||
// Cancel node author.
|
||||
$admin_user = $this->drupalCreateUser(['cancel other accounts']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('user_form_test_cancel/' . $account->id());
|
||||
$this->submitForm([], 'Confirm');
|
||||
|
||||
// Confirm node has been unpublished, even though the admin user
|
||||
// does not have permission to access it.
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$test_node = $node_storage->load($node->id());
|
||||
$this->assertFalse($test_node->isPublished(), 'Node of the user has been unpublished.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account and anonymize all content.
|
||||
*/
|
||||
public function testUserAnonymize(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
// Create comment field on page.
|
||||
$this->addDefaultCommentField('node', 'page');
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser(['cancel account']);
|
||||
$this->drupalLogin($account);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create a simple node.
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
|
||||
// Add a comment to the page.
|
||||
$comment_subject = $this->randomMachineName(8);
|
||||
$comment_body = $this->randomMachineName(8);
|
||||
$comment = Comment::create([
|
||||
'subject' => $comment_subject,
|
||||
'comment_body' => $comment_body,
|
||||
'entity_id' => $node->id(),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'comment',
|
||||
'status' => CommentInterface::PUBLISHED,
|
||||
'uid' => $account->id(),
|
||||
]);
|
||||
$comment->save();
|
||||
|
||||
// Create a node with two revisions, the initial one belonging to the
|
||||
// cancelling user.
|
||||
$revision_node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
$revision = $revision_node->getRevisionId();
|
||||
$settings = get_object_vars($revision_node);
|
||||
$settings['revision'] = 1;
|
||||
// Set new/current revision to someone else.
|
||||
$settings['uid'] = 1;
|
||||
$revision_node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel your account?');
|
||||
$this->assertSession()->pageTextContains("Your account will be removed and all account information deleted. All of your content will be assigned to the {$this->config('user.settings')->get('anonymous')} user.");
|
||||
|
||||
// Confirm account cancellation.
|
||||
$timestamp = time();
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Confirm account cancellation request.
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
$this->assertNull($user_storage->load($account->id()), 'User is not found in the database.');
|
||||
|
||||
// Confirm that user's content has been attributed to anonymous user.
|
||||
$anonymous_user = User::getAnonymousUser();
|
||||
$test_node = $node_storage->load($node->id());
|
||||
$this->assertEquals(0, $test_node->getOwnerId(), 'Node of the user has been attributed to anonymous user.');
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
$test_node = $node_storage->loadRevision($revision);
|
||||
$this->assertEquals(0, $test_node->getRevisionUser()->id(), 'Node revision of the user has been attributed to anonymous user.');
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
$test_node = $node_storage->load($revision_node->id());
|
||||
$this->assertNotEquals(0, $test_node->getOwnerId(), "Current revision of the user's node was not attributed to anonymous user.");
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('comment');
|
||||
$test_comment = $storage->load($comment->id());
|
||||
$this->assertEquals(0, $test_comment->getOwnerId(), 'Comment of the user has been attributed to anonymous user.');
|
||||
$this->assertTrue($test_comment->isPublished());
|
||||
$this->assertEquals($anonymous_user->getDisplayName(), $test_comment->getAuthorName(), 'Comment of the user has been attributed to anonymous user name.');
|
||||
|
||||
// Confirm that the confirmation message made it through to the end user.
|
||||
$this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been deleted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account and anonymize all content using a batch process.
|
||||
*/
|
||||
public function testUserAnonymizeBatch(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser(['cancel account']);
|
||||
$this->drupalLogin($account);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create 11 nodes in order to trigger batch processing in
|
||||
// node_mass_update().
|
||||
$nodes = [];
|
||||
for ($i = 0; $i < 11; $i++) {
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
$nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel your account?');
|
||||
$this->assertSession()->pageTextContains("Your account will be removed and all account information deleted. All of your content will be assigned to the {$this->config('user.settings')->get('anonymous')} user.");
|
||||
|
||||
// Confirm account cancellation.
|
||||
$timestamp = time();
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Confirm account cancellation request.
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
$this->assertNull($user_storage->load($account->id()), 'User is not found in the database.');
|
||||
|
||||
// Confirm that user's content has been attributed to anonymous user.
|
||||
$node_storage->resetCache(array_keys($nodes));
|
||||
$test_nodes = $node_storage->loadMultiple(array_keys($nodes));
|
||||
foreach ($test_nodes as $test_node) {
|
||||
$this->assertEquals(0, $test_node->getOwnerId(), 'Node ' . $test_node->id() . ' of the user has been attributed to anonymous user.');
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account and remove all content.
|
||||
*/
|
||||
public function testUserDelete(): void {
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_delete')->save();
|
||||
\Drupal::service('module_installer')->install(['comment']);
|
||||
$this->resetAll();
|
||||
$this->addDefaultCommentField('node', 'page');
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser([
|
||||
'cancel account',
|
||||
'post comments',
|
||||
'skip comment approval',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
// Load a real user object.
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
// Create a simple node.
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
|
||||
// Create comment.
|
||||
$edit = [];
|
||||
$edit['subject[0][value]'] = $this->randomMachineName(8);
|
||||
$edit['comment_body[0][value]'] = $this->randomMachineName(16);
|
||||
|
||||
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment');
|
||||
$this->submitForm($edit, 'Preview');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Your comment has been posted.');
|
||||
$comments = \Drupal::entityTypeManager()->getStorage('comment')->loadByProperties(['subject' => $edit['subject[0][value]']]);
|
||||
$comment = reset($comments);
|
||||
$this->assertNotEmpty($comment->id(), 'Comment found.');
|
||||
|
||||
// Create a node with two revisions, the initial one belonging to the
|
||||
// cancelling user.
|
||||
$revision_node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
$revision = $revision_node->getRevisionId();
|
||||
$settings = get_object_vars($revision_node);
|
||||
$settings['revision'] = 1;
|
||||
// Set new/current revision to someone else.
|
||||
$settings['uid'] = 1;
|
||||
$revision_node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel your account?');
|
||||
$this->assertSession()->pageTextContains('Your account will be removed and all account information deleted. All of your content will also be deleted.');
|
||||
|
||||
// Confirm account cancellation.
|
||||
$timestamp = time();
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Confirm account cancellation request.
|
||||
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
$this->assertNull($user_storage->load($account->id()), 'User is not found in the database.');
|
||||
|
||||
// Confirm there's only one session in the database. The user will be logged
|
||||
// out and their session migrated.
|
||||
// @see _user_cancel_session_regenerate()
|
||||
$this->assertSame(1, (int) \Drupal::database()->select('sessions', 's')->countQuery()->execute()->fetchField());
|
||||
|
||||
// Confirm that user's content has been deleted.
|
||||
$this->assertNull($node_storage->load($node->id()), 'Node of the user has been deleted.');
|
||||
$this->assertNull($node_storage->loadRevision($revision), 'Node revision of the user has been deleted.');
|
||||
$this->assertInstanceOf(Node::class, $node_storage->load($revision_node->id()));
|
||||
$this->assertNull(Comment::load($comment->id()), 'Comment of the user has been deleted.');
|
||||
|
||||
// Confirm that the confirmation message made it through to the end user.
|
||||
$this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been deleted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an administrative user and delete other users.
|
||||
*/
|
||||
public function testUserCancelByAdmin(): void {
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
|
||||
// Create administrative user.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Test deletion of a user account without email confirmation.
|
||||
$account1 = $this->drupalCreateUser([]);
|
||||
|
||||
// Delete regular user.
|
||||
$this->drupalGet('user/' . $account1->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains("Are you sure you want to cancel the account {$account1->getAccountName()}?");
|
||||
$this->assertSession()->pageTextContains('Cancellation method');
|
||||
|
||||
// Confirm deletion.
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains("Account {$account1->getAccountName()} has been deleted.");
|
||||
$this->assertNull(User::load($account1->id()), 'User is not found in the database.');
|
||||
|
||||
// Test deletion of a user account with email confirmation.
|
||||
$account2 = $this->drupalCreateUser([]);
|
||||
|
||||
// Delete regular user.
|
||||
$this->drupalGet('user/' . $account2->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains("Are you sure you want to cancel the account {$account2->getAccountName()}?");
|
||||
$this->assertSession()->pageTextContains('Cancellation method');
|
||||
|
||||
// Confirm deletion.
|
||||
$this->submitForm(['edit-user-cancel-confirm' => 1], 'Confirm');
|
||||
$this->assertSession()->pageTextContains("A confirmation request to cancel the account {$account2->getAccountName()} has been sent to the user's email address.");
|
||||
$this->assertNotNull(User::load($account2->id()), 'User is found in the database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deletion of a user account without an email address.
|
||||
*/
|
||||
public function testUserWithoutEmailCancelByAdmin(): void {
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
|
||||
// Create a regular user.
|
||||
$account = $this->drupalCreateUser([]);
|
||||
// This user has no email address.
|
||||
$account->mail = '';
|
||||
$account->save();
|
||||
|
||||
// Create administrative user.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Delete regular user without email address.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains("Are you sure you want to cancel the account {$account->getAccountName()}?");
|
||||
$this->assertSession()->pageTextContains('Cancellation method');
|
||||
|
||||
// Confirm deletion.
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been deleted.");
|
||||
$this->assertNull(User::load($account->id()), 'User is not found in the database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an administrative user and mass-delete other users.
|
||||
*/
|
||||
public function testMassUserCancelByAdmin(): void {
|
||||
\Drupal::service('module_installer')->install(['views']);
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
// Enable account cancellation notification.
|
||||
$this->config('user.settings')->set('notify.status_canceled', TRUE)->save();
|
||||
|
||||
// Create administrative user.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create some users.
|
||||
$users = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$account = $this->drupalCreateUser([]);
|
||||
$users[$account->id()] = $account;
|
||||
}
|
||||
|
||||
// Cancel user accounts, including own one.
|
||||
$edit = [];
|
||||
$edit['action'] = 'user_cancel_user_action';
|
||||
for ($i = 0; $i <= 4; $i++) {
|
||||
$edit['user_bulk_form[' . $i . ']'] = TRUE;
|
||||
}
|
||||
$this->drupalGet('admin/people');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel these user accounts?');
|
||||
$this->assertSession()->pageTextContains('Cancellation method');
|
||||
$this->assertSession()->pageTextContains('Require email confirmation');
|
||||
$this->assertSession()->pageTextContains('Notify user when account is canceled');
|
||||
|
||||
// Confirm deletion.
|
||||
$this->submitForm([], 'Confirm');
|
||||
$status = TRUE;
|
||||
foreach ($users as $account) {
|
||||
$status = $status && (str_contains($this->getTextContent(), "Account {$account->getAccountName()} has been deleted."));
|
||||
$status = $status && !$user_storage->load($account->id());
|
||||
}
|
||||
$this->assertTrue($status, 'Users deleted and not found in the database.');
|
||||
|
||||
// Ensure that admin account was not cancelled.
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
$admin_user = $user_storage->load($admin_user->id());
|
||||
$this->assertTrue($admin_user->isActive(), 'Administrative user is found in the database and enabled.');
|
||||
|
||||
// Verify that uid 1's account was not cancelled.
|
||||
$user1 = $user_storage->load(1);
|
||||
$this->assertTrue($user1->isActive(), 'User #1 still exists and is not blocked.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user cancel with node access.
|
||||
*/
|
||||
public function testUserDeleteWithContentAndNodeAccess(): void {
|
||||
|
||||
\Drupal::service('module_installer')->install(['node_access_test']);
|
||||
// Rebuild node access.
|
||||
node_access_rebuild();
|
||||
|
||||
$account = $this->drupalCreateUser(['access content']);
|
||||
$node = $this->drupalCreateNode(['type' => 'page', 'uid' => $account->id()]);
|
||||
$account->delete();
|
||||
$load2 = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
|
||||
$this->assertEmpty($load2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account and anonymize all content and it's translations.
|
||||
*/
|
||||
public function testUserAnonymizeTranslations(): void {
|
||||
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
|
||||
// Create comment field on page.
|
||||
$this->addDefaultCommentField('node', 'page');
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
\Drupal::service('module_installer')->install([
|
||||
'language',
|
||||
'locale',
|
||||
]);
|
||||
\Drupal::service('router.builder')->rebuildIfNeeded();
|
||||
ConfigurableLanguage::createFromLangcode('ur')->save();
|
||||
// Rebuild the container to update the default language container variable.
|
||||
$this->rebuildContainer();
|
||||
|
||||
$account = $this->drupalCreateUser(['cancel account']);
|
||||
$this->drupalLogin($account);
|
||||
$account = $user_storage->load($account->id());
|
||||
|
||||
$node = $this->drupalCreateNode(['uid' => $account->id()]);
|
||||
|
||||
// Add a comment to the page.
|
||||
$comment_subject = $this->randomMachineName(8);
|
||||
$comment_body = $this->randomMachineName(8);
|
||||
$comment = Comment::create([
|
||||
'subject' => $comment_subject,
|
||||
'comment_body' => $comment_body,
|
||||
'entity_id' => $node->id(),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'comment',
|
||||
'status' => CommentInterface::PUBLISHED,
|
||||
'uid' => $account->id(),
|
||||
]);
|
||||
$comment->save();
|
||||
$comment->addTranslation('ur', [
|
||||
'subject' => 'ur ' . $comment->label(),
|
||||
'status' => CommentInterface::PUBLISHED,
|
||||
])->save();
|
||||
|
||||
// Attempt to cancel account.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to cancel your account?');
|
||||
$this->assertSession()->pageTextContains('Your account will be removed and all account information deleted. All of your content will be assigned to the ' . $this->config('user.settings')->get('anonymous') . ' user.');
|
||||
|
||||
// Confirm account cancellation.
|
||||
$timestamp = time();
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.');
|
||||
|
||||
// Confirm account cancellation request.
|
||||
$this->drupalGet('user/' . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
|
||||
$user_storage->resetCache([$account->id()]);
|
||||
$this->assertNull($user_storage->load($account->id()), 'User is not found in the database.');
|
||||
|
||||
// Confirm that user's content has been attributed to anonymous user.
|
||||
$anonymous_user = User::getAnonymousUser();
|
||||
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('comment');
|
||||
$test_comment = $storage->load($comment->id());
|
||||
$this->assertEquals(0, $test_comment->getOwnerId());
|
||||
$this->assertTrue($test_comment->isPublished(), 'Comment of the user has been attributed to anonymous user.');
|
||||
$this->assertEquals($anonymous_user->getDisplayName(), $test_comment->getAuthorName());
|
||||
|
||||
$comment_translation = $test_comment->getTranslation('ur');
|
||||
$this->assertEquals(0, $comment_translation->getOwnerId());
|
||||
$this->assertTrue($comment_translation->isPublished(), 'Comment translation of the user has been attributed to anonymous user.');
|
||||
$this->assertEquals($anonymous_user->getDisplayName(), $comment_translation->getAuthorName());
|
||||
|
||||
// Confirm that the confirmation message made it through to the end user.
|
||||
$this->assertSession()->pageTextContains($account->getAccountName() . ' has been deleted.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the create user administration page.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserCreateFailMailTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['system_mail_failure_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the create user administration page.
|
||||
*/
|
||||
public function testUserAdd(): void {
|
||||
$user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Replace the mail functionality with a fake, malfunctioning service.
|
||||
$this->config('system.mail')->set('interface.default', 'test_php_mail_failure')->save();
|
||||
// Create a user, but fail to send an email.
|
||||
$name = $this->randomMachineName();
|
||||
$edit = [
|
||||
'name' => $name,
|
||||
'mail' => $this->randomMachineName() . '@example.com',
|
||||
'pass[pass1]' => $pass = $this->randomString(),
|
||||
'pass[pass2]' => $pass,
|
||||
'notify' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
|
||||
$this->assertSession()->pageTextContains('Unable to send email. Contact the site administrator if the problem persists.');
|
||||
$this->assertSession()->pageTextNotContains('A welcome message with further instructions has been emailed to the new user ' . $edit['name'] . '.');
|
||||
}
|
||||
|
||||
}
|
||||
141
web/core/modules/user/tests/src/Functional/UserCreateTest.php
Normal file
141
web/core/modules/user/tests/src/Functional/UserCreateTest.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the create user administration page.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserCreateTest extends BrowserTestBase {
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['image'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests user creation and display from the administration interface.
|
||||
*/
|
||||
public function testUserAdd(): void {
|
||||
$user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$this->assertEquals(\Drupal::time()->getRequestTime(), $user->getCreatedTime(), 'Creating a user sets default "created" timestamp.');
|
||||
$this->assertEquals(\Drupal::time()->getRequestTime(), $user->getChangedTime(), 'Creating a user sets default "changed" timestamp.');
|
||||
|
||||
// Create a field.
|
||||
$field_name = 'test_field';
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'user',
|
||||
'module' => 'image',
|
||||
'type' => 'image',
|
||||
'cardinality' => 1,
|
||||
'locked' => FALSE,
|
||||
'indexes' => ['target_id' => ['target_id']],
|
||||
'settings' => [
|
||||
'uri_scheme' => 'public',
|
||||
],
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'user',
|
||||
'label' => 'Picture',
|
||||
'bundle' => 'user',
|
||||
'description' => 'Your virtual face or picture.',
|
||||
'required' => FALSE,
|
||||
'settings' => [
|
||||
'file_extensions' => 'png gif jpg jpeg webp',
|
||||
'file_directory' => 'pictures',
|
||||
'max_filesize' => '30 KB',
|
||||
'alt_field' => 0,
|
||||
'title_field' => 0,
|
||||
'max_resolution' => '85x85',
|
||||
'min_resolution' => '',
|
||||
],
|
||||
])->save();
|
||||
|
||||
// Test user creation page for valid fields.
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->assertSession()->fieldValueEquals('edit-status-0', '1');
|
||||
$this->assertSession()->fieldValueEquals('edit-status-1', '1');
|
||||
$this->assertSession()->checkboxChecked('edit-status-1');
|
||||
|
||||
// Test that browser autocomplete behavior does not occur.
|
||||
$this->assertSession()->responseNotContains('data-user-info-from-browser');
|
||||
|
||||
// Test that the password strength indicator displays.
|
||||
$config = $this->config('user.settings');
|
||||
|
||||
$config->set('password_strength', TRUE)->save();
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->assertSession()->responseContains("Password strength:");
|
||||
|
||||
$config->set('password_strength', FALSE)->save();
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->assertSession()->responseNotContains("Password strength:");
|
||||
|
||||
// We create two users, notifying one and not notifying the other, to
|
||||
// ensure that the tests work in both cases.
|
||||
foreach ([FALSE, TRUE] as $notify) {
|
||||
$name = $this->randomMachineName();
|
||||
$edit = [
|
||||
'name' => $name,
|
||||
'mail' => $this->randomMachineName() . '@example.com',
|
||||
'pass[pass1]' => $pass = $this->randomString(),
|
||||
'pass[pass2]' => $pass,
|
||||
'notify' => $notify,
|
||||
];
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
|
||||
if ($notify) {
|
||||
$this->assertSession()->pageTextContains('A welcome message with further instructions has been emailed to the new user ' . $edit['name'] . '.');
|
||||
$this->assertCount(1, $this->drupalGetMails(), 'Notification email sent');
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->pageTextContains('Created a new user account for ' . $edit['name'] . '. No email has been sent.');
|
||||
$this->assertCount(0, $this->drupalGetMails(), 'Notification email not sent');
|
||||
}
|
||||
|
||||
$this->drupalGet('admin/people');
|
||||
$this->assertSession()->pageTextContains($edit['name']);
|
||||
$user = user_load_by_name($name);
|
||||
$this->assertTrue($user->isActive(), 'User is not blocked');
|
||||
}
|
||||
|
||||
// Test that the password '0' is considered a password.
|
||||
// @see https://www.drupal.org/node/2563751.
|
||||
$name = $this->randomMachineName();
|
||||
$edit = [
|
||||
'name' => $name,
|
||||
'mail' => $this->randomMachineName() . '@example.com',
|
||||
'pass[pass1]' => 0,
|
||||
'pass[pass2]' => 0,
|
||||
'notify' => FALSE,
|
||||
];
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains("Created a new user account for $name. No email has been sent");
|
||||
$this->assertSession()->pageTextNotContains('Password field is required');
|
||||
}
|
||||
|
||||
}
|
||||
303
web/core/modules/user/tests/src/Functional/UserEditTest.php
Normal file
303
web/core/modules/user/tests/src/Functional/UserEditTest.php
Normal file
@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests user edit page.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserEditTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests user edit page.
|
||||
*/
|
||||
public function testUserEdit(): void {
|
||||
// Test user edit functionality.
|
||||
$user1 = $this->drupalCreateUser(['change own username']);
|
||||
$user2 = $this->drupalCreateUser([]);
|
||||
$this->drupalLogin($user1);
|
||||
|
||||
// Test that error message appears when attempting to use a non-unique user
|
||||
// name.
|
||||
$edit['name'] = $user2->getAccountName();
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The username {$edit['name']} is already taken.");
|
||||
|
||||
// Check that the default value in user name field
|
||||
// is the raw value and not a formatted one.
|
||||
\Drupal::keyValue('user_hooks_test')->set('user_format_name_alter', TRUE);
|
||||
\Drupal::service('module_installer')->install(['user_hooks_test']);
|
||||
Cache::invalidateTags(['rendered']);
|
||||
$this->drupalGet('user/' . $user1->id() . '/edit');
|
||||
$this->assertSession()->fieldValueEquals('name', $user1->getAccountName());
|
||||
|
||||
// Ensure the formatted name is displayed when expected.
|
||||
$this->drupalGet('user/' . $user1->id());
|
||||
$this->assertSession()->responseContains($user1->getDisplayName());
|
||||
$this->assertSession()->titleEquals(strip_tags($user1->getDisplayName()) . ' | Drupal');
|
||||
|
||||
// Check that filling out a single password field does not validate.
|
||||
$edit = [];
|
||||
$edit['pass[pass1]'] = '';
|
||||
$edit['pass[pass2]'] = $this->randomMachineName();
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The specified passwords do not match.");
|
||||
|
||||
$edit['pass[pass1]'] = $this->randomMachineName();
|
||||
$edit['pass[pass2]'] = '';
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The specified passwords do not match.");
|
||||
|
||||
// Test that the error message appears when attempting to change the mail or
|
||||
// pass without the current password.
|
||||
$edit = [];
|
||||
$edit['mail'] = $this->randomMachineName() . '@new.example.com';
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Your current password is missing or incorrect; it's required to change the Email.");
|
||||
|
||||
$edit['current_pass'] = $user1->passRaw;
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The changes have been saved.");
|
||||
|
||||
// Test that the user must enter current password before changing passwords.
|
||||
$edit = [];
|
||||
$edit['pass[pass1]'] = $new_pass = $this->randomMachineName();
|
||||
$edit['pass[pass2]'] = $new_pass;
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Your current password is missing or incorrect; it's required to change the Password.");
|
||||
|
||||
// Try again with the current password.
|
||||
$edit['current_pass'] = $user1->passRaw;
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The changes have been saved.");
|
||||
|
||||
// Confirm there's only one session in the database as the existing session
|
||||
// has been migrated when the password is changed.
|
||||
// @see \Drupal\user\Entity\User::postSave()
|
||||
$this->assertSame(1, (int) \Drupal::database()->select('sessions', 's')->countQuery()->execute()->fetchField());
|
||||
|
||||
// Make sure the changed timestamp is updated.
|
||||
$this->assertEquals(\Drupal::time()->getRequestTime(), $user1->getChangedTime(), 'Changing a user sets "changed" timestamp.');
|
||||
|
||||
// Make sure the user can log in with their new password.
|
||||
$this->drupalLogout();
|
||||
$user1->passRaw = $new_pass;
|
||||
$this->drupalLogin($user1);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Test that the password strength indicator displays.
|
||||
$config = $this->config('user.settings');
|
||||
$this->drupalLogin($user1);
|
||||
|
||||
$config->set('password_strength', TRUE)->save();
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->responseContains("Password strength:");
|
||||
|
||||
$config->set('password_strength', FALSE)->save();
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->responseNotContains("Password strength:");
|
||||
|
||||
// Check that the user status field has the correct value and that it is
|
||||
// properly displayed.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalGet('user/' . $user1->id() . '/edit');
|
||||
$this->assertSession()->checkboxNotChecked('edit-status-0');
|
||||
$this->assertSession()->checkboxChecked('edit-status-1');
|
||||
|
||||
$edit = ['status' => 0];
|
||||
$this->drupalGet('user/' . $user1->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->assertSession()->checkboxChecked('edit-status-0');
|
||||
$this->assertSession()->checkboxNotChecked('edit-status-1');
|
||||
|
||||
$edit = ['status' => 1];
|
||||
$this->drupalGet('user/' . $user1->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->assertSession()->checkboxNotChecked('edit-status-0');
|
||||
$this->assertSession()->checkboxChecked('edit-status-1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting the password to "0".
|
||||
*
|
||||
* We discovered in https://www.drupal.org/node/2563751 that logging in with a
|
||||
* password that is literally "0" was not possible. This test ensures that
|
||||
* this regression can't happen again.
|
||||
*/
|
||||
public function testUserWith0Password(): void {
|
||||
$admin = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin);
|
||||
// Create a regular user.
|
||||
$user1 = $this->drupalCreateUser([]);
|
||||
|
||||
$edit = ['pass[pass1]' => '0', 'pass[pass2]' => '0'];
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The changes have been saved.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing of a user account without an email address.
|
||||
*/
|
||||
public function testUserWithoutEmailEdit(): void {
|
||||
// Test that an admin can edit users without an email address.
|
||||
$admin = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin);
|
||||
// Create a regular user.
|
||||
$user1 = $this->drupalCreateUser([]);
|
||||
// This user has no email address.
|
||||
$user1->mail = '';
|
||||
$user1->save();
|
||||
$this->drupalGet("user/" . $user1->id() . "/edit");
|
||||
$this->submitForm(['mail' => ''], 'Save');
|
||||
$this->assertSession()->pageTextContains("The changes have been saved.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests well known change password route redirects to user edit form.
|
||||
*/
|
||||
public function testUserWellKnownChangePasswordAuth(): void {
|
||||
$account = $this->drupalCreateUser([]);
|
||||
$this->drupalLogin($account);
|
||||
$this->drupalGet('.well-known/change-password');
|
||||
$this->assertSession()->addressEquals("user/" . $account->id() . "/edit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests well known change password route returns 403 to anonymous user.
|
||||
*/
|
||||
public function testUserWellKnownChangePasswordAnon(): void {
|
||||
$this->drupalGet('.well-known/change-password');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a user is able to change site language.
|
||||
*/
|
||||
public function testUserChangeSiteLanguage(): void {
|
||||
// Install these modules here as these aren't needed for other test methods.
|
||||
\Drupal::service('module_installer')->install([
|
||||
'content_translation',
|
||||
'language',
|
||||
]);
|
||||
// Create and login as an admin user to add a new language and enable
|
||||
// translation for user accounts.
|
||||
$adminUser = $this->drupalCreateUser([
|
||||
'administer account settings',
|
||||
'administer languages',
|
||||
'administer content translation',
|
||||
'administer users',
|
||||
'translate any entity',
|
||||
]);
|
||||
$this->drupalLogin($adminUser);
|
||||
|
||||
// Add a new language into the system.
|
||||
$edit = [
|
||||
'predefined_langcode' => 'fr',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
$this->assertSession()->pageTextContains('French');
|
||||
|
||||
// Enable translation for user accounts.
|
||||
$edit = [
|
||||
'language[content_translation]' => 1,
|
||||
];
|
||||
$this->drupalGet('admin/config/people/accounts');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
$this->assertSession()->pageTextContains('The configuration options have been saved.');
|
||||
|
||||
// Create a regular user for whom translation will be enabled.
|
||||
$webUser = $this->drupalCreateUser();
|
||||
|
||||
// Create a translation for a regular user account.
|
||||
$this->drupalGet('user/' . $webUser->id() . '/translations/add/en/fr');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
|
||||
// Update the site language of the user account.
|
||||
$edit = [
|
||||
'preferred_langcode' => 'fr',
|
||||
];
|
||||
$this->drupalGet('user/' . $webUser->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the account form implements entity field access for mail.
|
||||
*/
|
||||
public function testUserMailFieldAccess(): void {
|
||||
\Drupal::state()->set('user_access_test_forbid_mail_edit', TRUE);
|
||||
\Drupal::service('module_installer')->install(['user_access_test']);
|
||||
$user = $this->drupalCreateUser();
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet("user/" . $user->id() . "/edit");
|
||||
$this->assertFalse($this->getSession()->getPage()->hasField('mail'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an admin cannot edit their own account status.
|
||||
*/
|
||||
public function testAdminSelfBlocking(): void {
|
||||
// Create an admin user with permission to manage other users.
|
||||
$admin = $this->drupalCreateUser(['administer users']);
|
||||
$user = $this->drupalCreateUser();
|
||||
|
||||
// Log in as the admin and attempt to edit their own profile.
|
||||
$this->drupalLogin($admin);
|
||||
$this->drupalGet("user/" . $admin->id() . "/edit");
|
||||
|
||||
// Ensure the status field is not rendered.
|
||||
$this->assertSession()->fieldNotExists('edit-status-0');
|
||||
$this->assertSession()->fieldNotExists('edit-status-1');
|
||||
|
||||
// Test editing another user to ensure the status field is rendered.
|
||||
$this->drupalGet("user/" . $user->id() . "/edit");
|
||||
$this->assertSession()->fieldExists('edit-status-0');
|
||||
$this->assertSession()->fieldEnabled('edit-status-0');
|
||||
$this->assertSession()->fieldEnabled('edit-status-1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests constraint violations are triggered on the user account form.
|
||||
*/
|
||||
public function testRolesValidation(): void {
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet("user/" . $admin_user->id() . "/edit");
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
\Drupal::keyvalue('user_form_test')->set('user_form_test_constraint_roles_edit', TRUE);
|
||||
\Drupal::service('module_installer')->install(['entity_test', 'user_form_test']);
|
||||
$this->drupalGet("user/" . $admin_user->id() . "/edit");
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Widget constraint has failed.');
|
||||
$this->assertSession()->pageTextNotContains('The changes have been saved.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests user edited own account can still log in.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserEditedOwnAccountTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that a user who edits their own account can still log in.
|
||||
*/
|
||||
public function testUserEditedOwnAccount(): void {
|
||||
// Change account setting 'Who can register accounts?' to Administrators
|
||||
// only.
|
||||
$this->config('user.settings')->set('register', UserInterface::REGISTER_ADMINISTRATORS_ONLY)->save();
|
||||
|
||||
// Create a new user account and log in.
|
||||
$account = $this->drupalCreateUser(['change own username']);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Change own username.
|
||||
$edit = [];
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$this->drupalGet('user/' . $account->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Log out.
|
||||
$this->drupalLogout();
|
||||
|
||||
// Set the new name on the user account and attempt to log back in.
|
||||
$account->name = $edit['name'];
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests preferred language configuration and language selector access.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLanguageCreationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'language'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Functional test for language handling during user creation.
|
||||
*/
|
||||
public function testLocalUserCreation(): void {
|
||||
// User to add and remove language and create new users.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer users',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Add predefined language.
|
||||
$langcode = 'fr';
|
||||
ConfigurableLanguage::createFromLangcode($langcode)->save();
|
||||
|
||||
// Set language negotiation.
|
||||
$edit = [
|
||||
'language_interface[enabled][language-url]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/detection');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains('Language detection configuration saved.');
|
||||
|
||||
// Check if the language selector is available on admin/people/create and
|
||||
// set to the currently active language.
|
||||
$this->drupalGet($langcode . '/admin/people/create');
|
||||
$this->assertTrue($this->assertSession()->optionExists("edit-preferred-langcode", $langcode)->isSelected());
|
||||
|
||||
// Create a user with the admin/people/create form and check if the correct
|
||||
// language is set.
|
||||
$username = $this->randomMachineName(10);
|
||||
$edit = [
|
||||
'name' => $username,
|
||||
'mail' => $this->randomMachineName(4) . '@example.com',
|
||||
'pass[pass1]' => $username,
|
||||
'pass[pass2]' => $username,
|
||||
];
|
||||
|
||||
$this->drupalGet($langcode . '/admin/people/create');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
|
||||
$user = user_load_by_name($username);
|
||||
$this->assertEquals($langcode, $user->getPreferredLangcode(), 'New user has correct preferred language set.');
|
||||
$this->assertEquals($langcode, $user->language()->getId(), 'New user has correct profile language set.');
|
||||
|
||||
// Register a new user and check if the language selector is hidden.
|
||||
$this->drupalLogout();
|
||||
|
||||
$this->drupalGet($langcode . '/user/register');
|
||||
$this->assertSession()->fieldNotExists('language[fr]');
|
||||
|
||||
$username = $this->randomMachineName(10);
|
||||
$edit = [
|
||||
'name' => $username,
|
||||
'mail' => $this->randomMachineName(4) . '@example.com',
|
||||
];
|
||||
|
||||
$this->drupalGet($langcode . '/user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
|
||||
$user = user_load_by_name($username);
|
||||
$this->assertEquals($langcode, $user->getPreferredLangcode(), 'New user has correct preferred language set.');
|
||||
$this->assertEquals($langcode, $user->language()->getId(), 'New user has correct profile language set.');
|
||||
|
||||
// Test that the admin can use the language selector and if the correct
|
||||
// language is saved.
|
||||
$user_edit = $langcode . '/user/' . $user->id() . '/edit';
|
||||
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet($user_edit);
|
||||
$this->assertTrue($this->assertSession()->optionExists("edit-preferred-langcode", $langcode)->isSelected());
|
||||
|
||||
// Set passRaw so we can log in the new user.
|
||||
$user->passRaw = $this->randomMachineName(10);
|
||||
$edit = [
|
||||
'pass[pass1]' => $user->passRaw,
|
||||
'pass[pass2]' => $user->passRaw,
|
||||
];
|
||||
|
||||
$this->drupalGet($user_edit);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet($user_edit);
|
||||
$this->assertTrue($this->assertSession()->optionExists("edit-preferred-langcode", $langcode)->isSelected());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Functional tests for a user's ability to change their default language.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLanguageTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'language'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests if user can change their default language.
|
||||
*/
|
||||
public function testUserLanguageConfiguration(): void {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
// User to change their default language.
|
||||
$web_user = $this->drupalCreateUser();
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Log in as normal user and edit account settings.
|
||||
$this->drupalLogin($web_user);
|
||||
$path = 'user/' . $web_user->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
// Ensure language settings widget is available.
|
||||
$this->assertSession()->pageTextContains('Language');
|
||||
// Ensure custom language is present.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
// Switch to our custom language.
|
||||
$edit = [
|
||||
'preferred_langcode' => $langcode,
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
// Ensure form was submitted successfully.
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
// Check if language was changed.
|
||||
$this->assertTrue($this->assertSession()->optionExists('edit-preferred-langcode', $langcode)->isSelected());
|
||||
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\user_auth_decorator_test\UserAuthDecorator;
|
||||
|
||||
/**
|
||||
* Ensure that login works as expected with a decorator.
|
||||
*
|
||||
* The decorator does not implement UserAuthenticationInterface.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLoginDecoratedTest extends UserLoginTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user_auth_decorator_test'];
|
||||
|
||||
/**
|
||||
* Test that the UserAuthDecorator is providing user.auth.
|
||||
*/
|
||||
public function testServiceDecorated(): void {
|
||||
$service = \Drupal::service('user.auth');
|
||||
$this->assertInstanceOf(UserAuthDecorator::class, $service);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\user_auth_decorator_test\UserAuthDecorator;
|
||||
|
||||
/**
|
||||
* Tests login and password reset via direct HTTP with a user.auth decorator.
|
||||
*
|
||||
* The decorator does not implement UserAuthenticationInterface.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLoginHttpDecoratedTest extends UserLoginHttpTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user_auth_decorator_test'];
|
||||
|
||||
/**
|
||||
* Test that the UserAuthDecorator is providing user.auth.
|
||||
*/
|
||||
public function testServiceDecorated(): void {
|
||||
$service = \Drupal::service('user.auth');
|
||||
$this->assertInstanceOf(UserAuthDecorator::class, $service);
|
||||
}
|
||||
|
||||
}
|
||||
611
web/core/modules/user/tests/src/Functional/UserLoginHttpTest.php
Normal file
611
web/core/modules/user/tests/src/Functional/UserLoginHttpTest.php
Normal file
@ -0,0 +1,611 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Flood\DatabaseBackend;
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Controller\UserAuthenticationController;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* Tests login and password reset via direct HTTP.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLoginHttpTest extends BrowserTestBase {
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['dblog'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The cookie jar.
|
||||
*
|
||||
* @var \GuzzleHttp\Cookie\CookieJar
|
||||
*/
|
||||
protected $cookies;
|
||||
|
||||
/**
|
||||
* The serializer.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->cookies = new CookieJar();
|
||||
$encoders = [new JsonEncoder(), new XmlEncoder()];
|
||||
$this->serializer = new Serializer([], $encoders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a login HTTP request for a given serialization format.
|
||||
*
|
||||
* @param string $name
|
||||
* The username.
|
||||
* @param string $pass
|
||||
* The user password.
|
||||
* @param string $format
|
||||
* The format to use to make the request.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The HTTP response.
|
||||
*/
|
||||
protected function loginRequest($name, $pass, $format = 'json'): ResponseInterface {
|
||||
$user_login_url = Url::fromRoute('user.login.http')
|
||||
->setRouteParameter('_format', $format)
|
||||
->setAbsolute();
|
||||
|
||||
$request_body = [];
|
||||
if (isset($name)) {
|
||||
$request_body['name'] = $name;
|
||||
}
|
||||
if (isset($pass)) {
|
||||
$request_body['pass'] = $pass;
|
||||
}
|
||||
|
||||
$result = \Drupal::httpClient()->post($user_login_url->toString(), [
|
||||
'body' => $this->serializer->encode($request_body, $format),
|
||||
'headers' => [
|
||||
'Accept' => "application/$format",
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
'cookies' => $this->cookies,
|
||||
]);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user session life cycle.
|
||||
*/
|
||||
public function testLogin(): void {
|
||||
// Without the serialization module only JSON is supported.
|
||||
$this->doTestLogin('json');
|
||||
|
||||
// Enable serialization so we have access to additional formats.
|
||||
$this->container->get('module_installer')->install(['serialization']);
|
||||
$this->rebuildAll();
|
||||
|
||||
$this->doTestLogin('json');
|
||||
$this->doTestLogin('xml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do login testing for a given serialization format.
|
||||
*
|
||||
* @param string $format
|
||||
* Serialization format.
|
||||
*/
|
||||
protected function doTestLogin($format): void {
|
||||
$client = \Drupal::httpClient();
|
||||
// Create new user for each iteration to reset flood.
|
||||
// Grant the user administer users permissions to they can see the
|
||||
// 'roles' field.
|
||||
$account = $this->drupalCreateUser(['administer users']);
|
||||
$name = $account->getAccountName();
|
||||
$pass = $account->passRaw;
|
||||
|
||||
$login_status_url = $this->getLoginStatusUrlString($format);
|
||||
$response = $client->get($login_status_url);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
|
||||
|
||||
// Flooded.
|
||||
$this->config('user.flood')
|
||||
->set('user_limit', 3)
|
||||
->save();
|
||||
|
||||
$response = $this->loginRequest($name, 'wrong-pass', $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
|
||||
$response = $this->loginRequest($name, 'wrong-pass', $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
|
||||
$response = $this->loginRequest($name, 'wrong-pass', $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
|
||||
$response = $this->loginRequest($name, 'wrong-pass', $format);
|
||||
$this->assertHttpResponseWithMessage($response, 403, 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.', $format);
|
||||
|
||||
// After testing the flood control we can increase the limit.
|
||||
$this->config('user.flood')
|
||||
->set('user_limit', 100)
|
||||
->save();
|
||||
|
||||
$response = $this->loginRequest(NULL, NULL, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.', $format);
|
||||
|
||||
$response = $this->loginRequest(NULL, $pass, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name.', $format);
|
||||
|
||||
$response = $this->loginRequest($name, NULL, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.pass.', $format);
|
||||
|
||||
// Blocked.
|
||||
$account
|
||||
->block()
|
||||
->save();
|
||||
|
||||
$response = $this->loginRequest($name, $pass, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
|
||||
|
||||
$account
|
||||
->activate()
|
||||
->save();
|
||||
|
||||
$response = $this->loginRequest($name, 'garbage', $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
|
||||
$response = $this->loginRequest('garbage', $pass, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
|
||||
$response = $this->loginRequest($name, $pass, $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$result_data = $this->serializer->decode((string) $response->getBody(), $format);
|
||||
$this->assertEquals($name, $result_data['current_user']['name']);
|
||||
$this->assertEquals($account->id(), $result_data['current_user']['uid']);
|
||||
$this->assertEquals($account->getRoles(), $result_data['current_user']['roles']);
|
||||
$logout_token = $result_data['logout_token'];
|
||||
|
||||
// Logging in while already logged in results in a 403 with helpful message.
|
||||
$response = $this->loginRequest($name, $pass, $format);
|
||||
$this->assertSame(403, $response->getStatusCode());
|
||||
$this->assertSame(['message' => 'This route can only be accessed by anonymous users.'], $this->serializer->decode((string) $response->getBody(), $format));
|
||||
|
||||
$response = $client->get($login_status_url, ['cookies' => $this->cookies]);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_IN);
|
||||
|
||||
$response = $this->logoutRequest($format, $logout_token);
|
||||
$this->assertEquals(204, $response->getStatusCode());
|
||||
|
||||
$response = $client->get($login_status_url, ['cookies' => $this->cookies]);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
|
||||
|
||||
$this->resetFlood();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a password HTTP request for a given serialization format.
|
||||
*
|
||||
* @param array $request_body
|
||||
* The request body.
|
||||
* @param string $format
|
||||
* The format to use to make the request.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The HTTP response.
|
||||
*/
|
||||
protected function passwordRequest(array $request_body, $format = 'json'): ResponseInterface {
|
||||
$password_reset_url = Url::fromRoute('user.pass.http')
|
||||
->setRouteParameter('_format', $format)
|
||||
->setAbsolute();
|
||||
|
||||
$result = \Drupal::httpClient()->post($password_reset_url->toString(), [
|
||||
'body' => $this->serializer->encode($request_body, $format),
|
||||
'headers' => [
|
||||
'Accept' => "application/$format",
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
'cookies' => $this->cookies,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user password reset.
|
||||
*/
|
||||
public function testPasswordReset(): void {
|
||||
// Create a user account.
|
||||
$account = $this->drupalCreateUser();
|
||||
|
||||
// Without the serialization module only JSON is supported.
|
||||
$this->doTestPasswordReset('json', $account);
|
||||
|
||||
// Enable serialization so we have access to additional formats.
|
||||
$this->container->get('module_installer')->install(['serialization']);
|
||||
$this->rebuildAll();
|
||||
|
||||
$this->doTestPasswordReset('json', $account);
|
||||
$this->doTestPasswordReset('xml', $account);
|
||||
|
||||
$this->doTestGlobalLoginFloodControl('json');
|
||||
$this->doTestPerUserLoginFloodControl('json');
|
||||
$this->doTestLogoutCsrfProtection('json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value for a given key from the response.
|
||||
*
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* The response object.
|
||||
* @param string $key
|
||||
* The key for the value.
|
||||
* @param string $format
|
||||
* The encoded format.
|
||||
*
|
||||
* @return mixed
|
||||
* The value for the key.
|
||||
*/
|
||||
protected function getResultValue(ResponseInterface $response, $key, $format) {
|
||||
$decoded = $this->serializer->decode((string) $response->getBody(), $format);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded[$key];
|
||||
}
|
||||
else {
|
||||
return $decoded->{$key};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all flood entries.
|
||||
*/
|
||||
protected function resetFlood(): void {
|
||||
$this->container->get('database')->delete(DatabaseBackend::TABLE_NAME)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the global login flood control for a given serialization format.
|
||||
*
|
||||
* @param string $format
|
||||
* The encoded format.
|
||||
*
|
||||
* @see \Drupal\basic_auth\Authentication\Provider\BasicAuthTest::testGlobalLoginFloodControl
|
||||
* @see \Drupal\Tests\user\Functional\UserLoginTest::testGlobalLoginFloodControl
|
||||
*/
|
||||
public function doTestGlobalLoginFloodControl(string $format): void {
|
||||
$database = \Drupal::database();
|
||||
$this->config('user.flood')
|
||||
->set('ip_limit', 2)
|
||||
// Set a high per-user limit out so that it is not relevant in the test.
|
||||
->set('user_limit', 4000)
|
||||
->save();
|
||||
|
||||
$user = $this->drupalCreateUser([]);
|
||||
$incorrect_user = clone $user;
|
||||
$incorrect_user->passRaw .= 'incorrect';
|
||||
|
||||
// Try 2 failed logins.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$response = $this->loginRequest($incorrect_user->getAccountName(), $incorrect_user->passRaw, $format);
|
||||
$this->assertEquals('400', $response->getStatusCode());
|
||||
}
|
||||
|
||||
// IP limit has reached to its limit. Even valid user credentials will fail.
|
||||
$response = $this->loginRequest($user->getAccountName(), $user->passRaw, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 403, 'Access is blocked because of IP based flood prevention.', $format);
|
||||
$last_log = $database->select('watchdog', 'w')
|
||||
->fields('w', ['message'])
|
||||
->condition('type', 'user')
|
||||
->orderBy('wid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals('Flood control blocked login attempt from %ip', $last_log, 'A watchdog message was logged for the login attempt blocked by flood control per IP.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a response for status code and body.
|
||||
*
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* The response object.
|
||||
* @param int $expected_code
|
||||
* The expected status code.
|
||||
* @param string $expected_body
|
||||
* The expected response body.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertHttpResponse(ResponseInterface $response, int $expected_code, string $expected_body): void {
|
||||
$this->assertEquals($expected_code, $response->getStatusCode());
|
||||
$this->assertEquals($expected_body, $response->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a response for status code and message.
|
||||
*
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* The response object.
|
||||
* @param int $expected_code
|
||||
* The expected status code.
|
||||
* @param string $expected_message
|
||||
* The expected message encoded in response.
|
||||
* @param string $format
|
||||
* The format that the response is encoded in.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertHttpResponseWithMessage(ResponseInterface $response, int $expected_code, string $expected_message, string $format = 'json'): void {
|
||||
$this->assertEquals($expected_code, $response->getStatusCode());
|
||||
$this->assertEquals($expected_message, $this->getResultValue($response, 'message', $format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the per-user login flood control for a given serialization format.
|
||||
*
|
||||
* @see \Drupal\basic_auth\Authentication\Provider\BasicAuthTest::testPerUserLoginFloodControl
|
||||
* @see \Drupal\Tests\user\Functional\UserLoginTest::testPerUserLoginFloodControl
|
||||
*/
|
||||
public function doTestPerUserLoginFloodControl($format): void {
|
||||
$database = \Drupal::database();
|
||||
foreach ([TRUE, FALSE] as $uid_only_setting) {
|
||||
$this->config('user.flood')
|
||||
// Set a high global limit out so that it is not relevant in the test.
|
||||
->set('ip_limit', 4000)
|
||||
->set('user_limit', 3)
|
||||
->set('uid_only', $uid_only_setting)
|
||||
->save();
|
||||
|
||||
$user1 = $this->drupalCreateUser([]);
|
||||
$incorrect_user1 = clone $user1;
|
||||
$incorrect_user1->passRaw .= 'incorrect';
|
||||
|
||||
$user2 = $this->drupalCreateUser([]);
|
||||
|
||||
// Try 2 failed logins.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$response = $this->loginRequest($incorrect_user1->getAccountName(), $incorrect_user1->passRaw, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
}
|
||||
|
||||
// A successful login will reset the per-user flood control count.
|
||||
$response = $this->loginRequest($user1->getAccountName(), $user1->passRaw, $format);
|
||||
$result_data = $this->serializer->decode((string) $response->getBody(), $format);
|
||||
$this->logoutRequest($format, $result_data['logout_token']);
|
||||
|
||||
// Try 3 failed logins for user 1, they will not trigger flood control.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$response = $this->loginRequest($incorrect_user1->getAccountName(), $incorrect_user1->passRaw, $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
|
||||
}
|
||||
|
||||
// Try one successful attempt for user 2, it should not trigger any
|
||||
// flood control.
|
||||
$this->drupalLogin($user2);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Try one more attempt for user 1, it should be rejected, even if the
|
||||
// correct password has been used.
|
||||
$response = $this->loginRequest($user1->getAccountName(), $user1->passRaw, $format);
|
||||
// Depending on the uid_only setting the error message will be different.
|
||||
if ($uid_only_setting) {
|
||||
$expected_message = 'There have been more than 3 failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.';
|
||||
$expected_log = 'Flood control blocked login attempt for uid %uid';
|
||||
}
|
||||
else {
|
||||
$expected_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.';
|
||||
$expected_log = 'Flood control blocked login attempt for uid %uid from %ip';
|
||||
}
|
||||
$this->assertHttpResponseWithMessage($response, 403, $expected_message, $format);
|
||||
$last_log = $database->select('watchdog', 'w')
|
||||
->fields('w', ['message'])
|
||||
->condition('type', 'user')
|
||||
->orderBy('wid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals($expected_log, $last_log, 'A watchdog message was logged for the login attempt blocked by flood control per user.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a logout HTTP request for a given serialization format.
|
||||
*
|
||||
* @param string $format
|
||||
* The format to use to make the request.
|
||||
* @param string $logout_token
|
||||
* The csrf token for user logout.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The HTTP response.
|
||||
*/
|
||||
protected function logoutRequest($format = 'json', $logout_token = ''): ResponseInterface {
|
||||
/** @var \GuzzleHttp\Client $client */
|
||||
$client = $this->container->get('http_client');
|
||||
$user_logout_url = Url::fromRoute('user.logout.http')
|
||||
->setRouteParameter('_format', $format)
|
||||
->setAbsolute();
|
||||
if ($logout_token) {
|
||||
$user_logout_url->setOption('query', ['token' => $logout_token]);
|
||||
}
|
||||
$post_options = [
|
||||
'headers' => [
|
||||
'Accept' => "application/$format",
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
'cookies' => $this->cookies,
|
||||
];
|
||||
|
||||
$response = $client->post($user_logout_url->toString(), $post_options);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests csrf protection of User Logout route for given serialization format.
|
||||
*/
|
||||
public function doTestLogoutCsrfProtection(string $format): void {
|
||||
$client = \Drupal::httpClient();
|
||||
$login_status_url = $this->getLoginStatusUrlString();
|
||||
$account = $this->drupalCreateUser();
|
||||
$name = $account->getAccountName();
|
||||
$pass = $account->passRaw;
|
||||
|
||||
$response = $this->loginRequest($name, $pass, $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$result_data = $this->serializer->decode((string) $response->getBody(), $format);
|
||||
|
||||
$logout_token = $result_data['logout_token'];
|
||||
|
||||
// Test third party site posting to current site with logout request.
|
||||
// This should not logout the current user because it lacks the CSRF
|
||||
// token.
|
||||
$response = $this->logoutRequest($format);
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
|
||||
// Ensure still logged in.
|
||||
$response = $client->get($login_status_url, ['cookies' => $this->cookies]);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_IN);
|
||||
|
||||
// Try with an incorrect token.
|
||||
$response = $this->logoutRequest($format, 'not-the-correct-token');
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
|
||||
// Ensure still logged in.
|
||||
$response = $client->get($login_status_url, ['cookies' => $this->cookies]);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_IN);
|
||||
|
||||
// Try a logout request with correct token.
|
||||
$response = $this->logoutRequest($format, $logout_token);
|
||||
$this->assertEquals(204, $response->getStatusCode());
|
||||
|
||||
// Ensure actually logged out.
|
||||
$response = $client->get($login_status_url, ['cookies' => $this->cookies]);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL string for checking login for a given serialization format.
|
||||
*
|
||||
* @param string $format
|
||||
* The format to use to make the request.
|
||||
*
|
||||
* @return string
|
||||
* The URL string.
|
||||
*/
|
||||
protected function getLoginStatusUrlString($format = 'json') {
|
||||
$user_login_status_url = Url::fromRoute('user.login_status.http');
|
||||
$user_login_status_url->setRouteParameter('_format', $format);
|
||||
$user_login_status_url->setAbsolute();
|
||||
return $user_login_status_url->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do password reset testing for given format and account.
|
||||
*
|
||||
* @param string $format
|
||||
* Serialization format.
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* Test account.
|
||||
*/
|
||||
protected function doTestPasswordReset($format, $account): void {
|
||||
$response = $this->passwordRequest([], $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name or credentials.mail', $format);
|
||||
|
||||
$response = $this->passwordRequest(['name' => 'drama llama'], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = $this->passwordRequest(['mail' => 'llama@drupal.org'], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$account
|
||||
->block()
|
||||
->save();
|
||||
|
||||
$response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
// Check that the proper warning has been logged.
|
||||
$arguments = [
|
||||
'%identifier' => $account->getAccountName(),
|
||||
];
|
||||
$logged = Database::getConnection()->select('watchdog')
|
||||
->fields('watchdog', ['variables'])
|
||||
->condition('type', 'user')
|
||||
->condition('message', 'Unable to send password reset email for blocked or not yet activated user %identifier.')
|
||||
->orderBy('wid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals(serialize($arguments), $logged);
|
||||
|
||||
$response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
// Check that the proper warning has been logged.
|
||||
$arguments = [
|
||||
'%identifier' => $account->getEmail(),
|
||||
];
|
||||
|
||||
$logged = Database::getConnection()->select('watchdog')
|
||||
->fields('watchdog', ['variables'])
|
||||
->condition('type', 'user')
|
||||
->condition('message', 'Unable to send password reset email for blocked or not yet activated user %identifier.')
|
||||
->orderBy('wid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals(serialize($arguments), $logged);
|
||||
|
||||
$account
|
||||
->activate()
|
||||
->save();
|
||||
|
||||
$response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->loginFromResetEmail();
|
||||
$this->drupalLogout();
|
||||
|
||||
$response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->loginFromResetEmail();
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Login from reset password email.
|
||||
*/
|
||||
protected function loginFromResetEmail(): void {
|
||||
$_emails = $this->drupalGetMails();
|
||||
$email = end($_emails);
|
||||
$urls = [];
|
||||
preg_match('#.+user/reset/.+#', $email['body'], $urls);
|
||||
$resetURL = $urls[0];
|
||||
$this->drupalGet($resetURL);
|
||||
$this->submitForm([], 'Log in');
|
||||
$this->assertSession()->pageTextContains('You have used a one-time login link. You can set your new password now.');
|
||||
}
|
||||
|
||||
}
|
||||
363
web/core/modules/user/tests/src/Functional/UserLoginTest.php
Normal file
363
web/core/modules/user/tests/src/Functional/UserLoginTest.php
Normal file
@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Ensure that login works as expected.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLoginTest extends BrowserTestBase {
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['dblog'];
|
||||
|
||||
/**
|
||||
* Tests login with destination.
|
||||
*/
|
||||
public function testLoginCacheTagsAndDestination(): void {
|
||||
$this->drupalGet('user/login');
|
||||
// The user login form says "Enter your <site name> username.", hence it
|
||||
// depends on config:system.site, and its cache tags should be present.
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:system.site');
|
||||
|
||||
$user = $this->drupalCreateUser([]);
|
||||
$this->drupalGet('user/login', ['query' => ['destination' => 'foo']]);
|
||||
$edit = ['name' => $user->getAccountName(), 'pass' => $user->passRaw];
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->addressEquals('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the global login flood control.
|
||||
*/
|
||||
public function testGlobalLoginFloodControl(): void {
|
||||
$this->config('user.flood')
|
||||
->set('ip_limit', 10)
|
||||
// Set a high per-user limit out so that it is not relevant in the test.
|
||||
->set('user_limit', 4000)
|
||||
->save();
|
||||
|
||||
$user1 = $this->drupalCreateUser([]);
|
||||
$incorrect_user1 = clone $user1;
|
||||
$incorrect_user1->passRaw .= 'incorrect';
|
||||
|
||||
// Try 2 failed logins.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$this->assertFailedLogin($incorrect_user1);
|
||||
}
|
||||
|
||||
// A successful login will not reset the IP-based flood control count.
|
||||
$this->drupalLogin($user1);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Try 8 more failed logins, they should not trigger the flood control
|
||||
// mechanism.
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$this->assertFailedLogin($incorrect_user1);
|
||||
}
|
||||
|
||||
// The next login trial should result in an IP-based flood error message.
|
||||
$this->assertFailedLogin($incorrect_user1, 'ip');
|
||||
|
||||
// A login with the correct password should also result in a flood error
|
||||
// message.
|
||||
$this->assertFailedLogin($user1, 'ip');
|
||||
|
||||
// A login attempt after resetting the password should still fail, since the
|
||||
// IP-based flood control count is not cleared after a password reset.
|
||||
$this->resetUserPassword($user1);
|
||||
$this->drupalLogout();
|
||||
$this->assertFailedLogin($user1, 'ip');
|
||||
$this->assertSession()->responseContains('Too many failed login attempts from your IP address.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the per-user login flood control.
|
||||
*/
|
||||
public function testPerUserLoginFloodControl(): void {
|
||||
$this->config('user.flood')
|
||||
// Set a high global limit out so that it is not relevant in the test.
|
||||
->set('ip_limit', 4000)
|
||||
->set('user_limit', 3)
|
||||
->save();
|
||||
|
||||
$user1 = $this->drupalCreateUser([]);
|
||||
$incorrect_user1 = clone $user1;
|
||||
$incorrect_user1->passRaw .= 'incorrect';
|
||||
|
||||
$user2 = $this->drupalCreateUser([]);
|
||||
|
||||
// Try 2 failed logins.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$this->assertFailedLogin($incorrect_user1);
|
||||
}
|
||||
|
||||
// We're not going to test resetting the password which should clear the
|
||||
// flood table and allow the user to log in again.
|
||||
$this->drupalLogin($user1);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Try 3 failed logins for user 1, they will not trigger flood control.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->assertFailedLogin($incorrect_user1);
|
||||
}
|
||||
|
||||
// Try one successful attempt for user 2, it should not trigger any
|
||||
// flood control.
|
||||
$this->drupalLogin($user2);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Try one more attempt for user 1, it should be rejected, even if the
|
||||
// correct password has been used.
|
||||
$this->assertFailedLogin($user1, 'user');
|
||||
$this->resetUserPassword($user1);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Try to log in as user 1, it should be successful.
|
||||
$this->drupalLogin($user1);
|
||||
$this->assertSession()->responseContains('Member for');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user password is re-hashed upon login after changing $count_log2.
|
||||
*/
|
||||
public function testPasswordRehashOnLogin(): void {
|
||||
// Retrieve instance of password hashing algorithm.
|
||||
$password_hasher = $this->container->get('password');
|
||||
|
||||
// Create a new user and authenticate.
|
||||
$account = $this->drupalCreateUser([]);
|
||||
$password = $account->passRaw;
|
||||
$this->drupalLogin($account);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Load the stored user. The password hash shouldn't need a rehash.
|
||||
$account = User::load($account->id());
|
||||
|
||||
// Check that the stored password doesn't need rehash.
|
||||
$this->assertFalse($password_hasher->needsRehash($account->getPassword()));
|
||||
|
||||
// The current hashing cost is set to 10 in the container. Increase cost by
|
||||
// one, by enabling a module containing the necessary container changes.
|
||||
\Drupal::service('module_installer')->install(['user_custom_pass_hash_params_test']);
|
||||
$this->resetAll();
|
||||
// Reload the hashing service after container changes.
|
||||
$password_hasher = $this->container->get('password');
|
||||
|
||||
// Check that the stored password does need rehash.
|
||||
$this->assertTrue($password_hasher->needsRehash($account->getPassword()));
|
||||
|
||||
$account->passRaw = $password;
|
||||
$this->drupalGet('user/login');
|
||||
$edit = [
|
||||
'name' => $account->getAccountName(),
|
||||
'pass' => $account->passRaw,
|
||||
];
|
||||
$this->submitForm($edit, 'Log in');
|
||||
|
||||
// Load the stored user, which should have a different password hash now.
|
||||
$account = User::load($account->id());
|
||||
|
||||
// Check that the stored password doesn't need rehash.
|
||||
$this->assertFalse($password_hasher->needsRehash($account->getPassword()));
|
||||
$this->assertTrue($password_hasher->check($password, $account->getPassword()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests log in with a maximum length and a too long password.
|
||||
*/
|
||||
public function testPasswordLengthLogin(): void {
|
||||
// Create a new user and authenticate.
|
||||
$account = $this->drupalCreateUser([]);
|
||||
$current_password = $account->passRaw;
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Use the length specified in
|
||||
// \Drupal\Core\Render\Element\Password::getInfo().
|
||||
$length = 128;
|
||||
|
||||
$current_password = $this->doPasswordLengthLogin($account, $current_password, $length);
|
||||
$this->assertSession()->pageTextNotContains('Password cannot be longer than');
|
||||
$this->assertSession()->pageTextContains('Member for');
|
||||
|
||||
$this->doPasswordLengthLogin($account, $current_password, $length + 1);
|
||||
$this->assertSession()->pageTextContains('Password cannot be longer than ' . $length . ' characters but is currently ' . ($length + 1) . ' characters long.');
|
||||
$this->assertSession()->pageTextNotContains('Member for');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to test log in with a maximum length password.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* An object containing the user account.
|
||||
* @param string $current_password
|
||||
* The current password associated with the user.
|
||||
* @param int $length
|
||||
* The length of the password.
|
||||
*
|
||||
* @return string
|
||||
* The new password associated with the user.
|
||||
*/
|
||||
public function doPasswordLengthLogin(UserInterface $account, string $current_password, int $length) {
|
||||
$new_password = \Drupal::service('password_generator')->generate($length);
|
||||
$uid = $account->id();
|
||||
$edit = [
|
||||
'current_pass' => $current_password,
|
||||
'mail' => $account->getEmail(),
|
||||
'pass[pass1]' => $new_password,
|
||||
'pass[pass2]' => $new_password,
|
||||
];
|
||||
|
||||
// Change the password.
|
||||
$this->drupalGet("user/$uid/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Login with new password.
|
||||
$this->drupalGet('user/login');
|
||||
$edit = [
|
||||
'name' => $account->getAccountName(),
|
||||
'pass' => $new_password,
|
||||
];
|
||||
$this->submitForm($edit, 'Log in');
|
||||
return $new_password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a browser that denies cookies.
|
||||
*/
|
||||
public function testCookiesNotAccepted(): void {
|
||||
$this->drupalGet('user/login');
|
||||
$form_build_id = $this->getSession()->getPage()->findField('form_build_id');
|
||||
|
||||
$account = $this->drupalCreateUser([]);
|
||||
$post = [
|
||||
'form_id' => 'user_login_form',
|
||||
'form_build_id' => $form_build_id,
|
||||
'name' => $account->getAccountName(),
|
||||
'pass' => $account->passRaw,
|
||||
'op' => 'Log in',
|
||||
];
|
||||
$url = $this->buildUrl(Url::fromRoute('user.login'));
|
||||
|
||||
/** @var \Psr\Http\Message\ResponseInterface $response */
|
||||
$response = $this->getHttpClient()->post($url, [
|
||||
'form_params' => $post,
|
||||
'http_errors' => FALSE,
|
||||
'cookies' => FALSE,
|
||||
'allow_redirects' => FALSE,
|
||||
]);
|
||||
|
||||
// Follow the location header.
|
||||
$this->drupalGet($response->getHeader('location')[0]);
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertSession()->pageTextContains('To log in to this site, your browser must accept cookies from the domain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an unsuccessful login attempt.
|
||||
*
|
||||
* @param \Drupal\user\Entity\User $account
|
||||
* A user object with name and passRaw attributes for the login attempt.
|
||||
* @param string $flood_trigger
|
||||
* (optional) Whether or not to expect that the flood control mechanism
|
||||
* will be triggered. Defaults to NULL.
|
||||
* - Set to 'user' to expect a 'too many failed logins error.
|
||||
* - Set to any value to expect an error for too many failed logins per IP.
|
||||
* - Set to NULL to expect a failed login.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertFailedLogin(User $account, ?string $flood_trigger = NULL): void {
|
||||
$database = \Drupal::database();
|
||||
$edit = [
|
||||
'name' => $account->getAccountName(),
|
||||
'pass' => $account->passRaw,
|
||||
];
|
||||
$this->drupalGet('user/login');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
if (isset($flood_trigger)) {
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertSession()->fieldNotExists('pass');
|
||||
$last_log = $database->select('watchdog', 'w')
|
||||
->fields('w', ['message'])
|
||||
->condition('type', 'user')
|
||||
->orderBy('wid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
if ($flood_trigger == 'user') {
|
||||
$this->assertSession()->pageTextMatches("/There (has|have) been more than \w+ failed login attempt.* for this account. It is temporarily blocked. Try again later or request a new password./");
|
||||
$this->assertSession()->elementExists('css', 'body.maintenance-page--flood');
|
||||
$this->assertSession()->linkExists("request a new password");
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('user.pass')->toString());
|
||||
$this->assertEquals('Flood control blocked login attempt for uid %uid from %ip', $last_log, 'A watchdog message was logged for the login attempt blocked by flood control per user.');
|
||||
}
|
||||
else {
|
||||
// No uid, so the limit is IP-based.
|
||||
$this->assertSession()->pageTextContains("Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.");
|
||||
$this->assertSession()->elementExists('css', 'body.maintenance-page--flood');
|
||||
$this->assertSession()->linkExists("request a new password");
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('user.pass')->toString());
|
||||
$this->assertEquals('Flood control blocked login attempt from %ip', $last_log, 'A watchdog message was logged for the login attempt blocked by flood control per IP.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->fieldValueEquals('pass', '');
|
||||
$this->assertSession()->pageTextContains('Unrecognized username or password. Forgot your password?');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset user password.
|
||||
*
|
||||
* @param object $user
|
||||
* A user object.
|
||||
*/
|
||||
public function resetUserPassword($user): void {
|
||||
$this->drupalGet('user/password');
|
||||
$edit['name'] = $user->getDisplayName();
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$_emails = $this->drupalGetMails();
|
||||
$email = end($_emails);
|
||||
$urls = [];
|
||||
preg_match('#.+user/reset/.+#', $email['body'], $urls);
|
||||
$resetURL = $urls[0];
|
||||
$this->drupalGet($resetURL);
|
||||
$this->submitForm([], 'Log in');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user login form has the autocomplete attributes.
|
||||
*/
|
||||
public function testAutocompleteHtmlAttributes(): void {
|
||||
$this->drupalGet('user/login');
|
||||
$name_field = $this->getSession()->getPage()->findField('name');
|
||||
$pass_field = $this->getSession()->getPage()->findField('pass');
|
||||
$this->assertEquals('username', $name_field->getAttribute('autocomplete'));
|
||||
$this->assertEquals('current-password', $pass_field->getAttribute('autocomplete'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests user logout.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLogoutTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() : void {
|
||||
parent::setUp();
|
||||
$this->placeBlock('system_menu_block:account');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user logout functionality.
|
||||
*/
|
||||
public function testLogout(): void {
|
||||
$account = $this->createUser();
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Test missing csrf token does not log the user out.
|
||||
$logoutUrl = Url::fromRoute('user.logout');
|
||||
$confirmUrl = Url::fromRoute('user.logout.confirm');
|
||||
$this->drupalGet($logoutUrl);
|
||||
$this->assertTrue($this->drupalUserIsLoggedIn($account));
|
||||
$this->assertSession()->addressEquals($confirmUrl);
|
||||
|
||||
// Test invalid csrf token does not log the user out.
|
||||
$this->drupalGet($logoutUrl, ['query' => ['token' => '123']]);
|
||||
$this->assertTrue($this->drupalUserIsLoggedIn($account));
|
||||
$this->assertSession()->addressEquals($confirmUrl);
|
||||
// Test to ensure the text 'This action cannot be undone.' is not
|
||||
// present on the page.
|
||||
$this->assertSession()->pageTextNotContains('This action cannot be undone.');
|
||||
// Submitting the confirmation form correctly logs the user out.
|
||||
$this->submitForm([], 'Log out');
|
||||
$this->assertFalse($this->drupalUserIsLoggedIn($account));
|
||||
|
||||
$this->drupalResetSession();
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Test with valid logout link.
|
||||
$this->drupalGet('user');
|
||||
$this->getSession()->getPage()->clickLink('Log out');
|
||||
$this->assertFalse($this->drupalUserIsLoggedIn($account));
|
||||
|
||||
// Test hitting the confirm form while logged out redirects to the
|
||||
// frontpage.
|
||||
$this->drupalGet($confirmUrl);
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('<front>'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,682 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Ensure that password reset methods work as expected.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserPasswordResetTest extends BrowserTestBase {
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user object to test password resetting.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Language manager object.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'language'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Enable page caching.
|
||||
$config = $this->config('system.performance');
|
||||
$config->set('cache.page.max_age', 3600);
|
||||
$config->save();
|
||||
$this->drupalPlaceBlock('system_menu_block:account');
|
||||
|
||||
// Create a user.
|
||||
$account = $this->drupalCreateUser();
|
||||
|
||||
// Activate user by logging in.
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$this->account = User::load($account->id());
|
||||
$this->account->passRaw = $account->passRaw;
|
||||
$this->drupalLogout();
|
||||
|
||||
// Set the last login time that is used to generate the one-time link so
|
||||
// that it is definitely over a second ago.
|
||||
$account->login = \Drupal::time()->getRequestTime() - mt_rand(10, 100000);
|
||||
Database::getConnection()->update('users_field_data')
|
||||
->fields(['login' => $account->getLastLoginTime()])
|
||||
->condition('uid', $account->id())
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests password reset functionality.
|
||||
*/
|
||||
public function testUserPasswordReset(): void {
|
||||
// Verify that accessing the password reset form without having the session
|
||||
// variables set results in an access denied message.
|
||||
$this->drupalGet(Url::fromRoute('user.reset.form', ['uid' => $this->account->id()]));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Try to reset the password for a completely invalid username.
|
||||
$this->drupalGet('user/password');
|
||||
$long_name = $this->randomMachineName(UserInterface::USERNAME_MAX_LENGTH + 10);
|
||||
$edit = ['name' => $long_name];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertCount(0, $this->drupalGetMails(['id' => 'user_password_reset']), 'No email was sent when requesting a password for an invalid user name.');
|
||||
$this->assertSession()->pageTextContains("The username or email address is invalid.");
|
||||
|
||||
// Try to reset the password for an invalid account.
|
||||
$this->drupalGet('user/password');
|
||||
$random_name = $this->randomMachineName();
|
||||
$edit = ['name' => $random_name];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertNoValidPasswordReset($random_name);
|
||||
|
||||
// Try to reset the password for a valid email address longer than
|
||||
// UserInterface::USERNAME_MAX_LENGTH (invalid username, valid email).
|
||||
// This should pass validation and print the generic message.
|
||||
$this->drupalGet('user/password');
|
||||
$long_name = $this->randomMachineName(UserInterface::USERNAME_MAX_LENGTH) . '@example.com';
|
||||
$edit = ['name' => $long_name];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertNoValidPasswordReset($long_name);
|
||||
|
||||
// Reset the password by username via the password reset page.
|
||||
$this->drupalGet('user/password');
|
||||
$edit = ['name' => $this->account->getAccountName()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertValidPasswordReset($edit['name']);
|
||||
|
||||
$resetURL = $this->getResetURL();
|
||||
$this->drupalGet($resetURL);
|
||||
// Ensure that the current URL does not contain the hash and timestamp.
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('user.reset.form', ['uid' => $this->account->id()]));
|
||||
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)');
|
||||
|
||||
// Ensure the password reset URL is not cached.
|
||||
$this->drupalGet($resetURL);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)');
|
||||
|
||||
// Check the one-time login page.
|
||||
$this->assertSession()->pageTextContains($this->account->getAccountName());
|
||||
$this->assertSession()->pageTextContains('This login can be used only once.');
|
||||
$this->assertSession()->titleEquals('Reset password | Drupal');
|
||||
|
||||
// Check successful login.
|
||||
$this->submitForm([], 'Log in');
|
||||
$this->assertSession()->linkExists('Log out');
|
||||
$this->assertSession()->titleEquals($this->account->getAccountName() . ' | Drupal');
|
||||
|
||||
// Try to save without entering password.
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Password field is required.');
|
||||
|
||||
// Change the forgotten password.
|
||||
$password = \Drupal::service('password_generator')->generate();
|
||||
$edit = ['pass[pass1]' => $password, 'pass[pass2]' => $password];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
|
||||
// Verify that the password reset session has been destroyed.
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Your current password is missing or incorrect; it's required to change the Password.");
|
||||
|
||||
// Log out, and try to log in again using the same one-time link.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet($resetURL);
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.');
|
||||
$this->drupalGet($resetURL . '/login');
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.');
|
||||
|
||||
// Request a new password again, this time using the email address.
|
||||
// Count email messages before to compare with after.
|
||||
$before = count($this->drupalGetMails(['id' => 'user_password_reset']));
|
||||
$this->drupalGet('user/password');
|
||||
$edit = ['name' => $this->account->getEmail()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertValidPasswordReset($edit['name']);
|
||||
$this->assertCount($before + 1, $this->drupalGetMails(['id' => 'user_password_reset']), 'Email sent when requesting password reset using email address.');
|
||||
|
||||
// Change the site name.
|
||||
// The site name token in the email will be replaced by this one.
|
||||
// cspell:ignore L'Equipe de l'Agriculture
|
||||
$config = $this->config('system.site');
|
||||
$config->set('name', "L'Equipe de l'Agriculture")->save();
|
||||
$this->rebuildContainer();
|
||||
// Request a new password using the email address.
|
||||
$this->drupalGet('user/password');
|
||||
$edit = ['name' => $this->account->getEmail()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
// Check that the email message body does not contain HTML entities
|
||||
// Assume the most recent email.
|
||||
$_emails = $this->drupalGetMails();
|
||||
$email = end($_emails);
|
||||
$this->assertEquals(htmlspecialchars_decode($email['body']), $email['body'], 'Email body contains HTML entities');
|
||||
// Change site name to 'Drupal'
|
||||
$config->set('name', "Drupal")->save();
|
||||
$this->rebuildContainer();
|
||||
// Visit the user edit page without pass-reset-token and make sure it does
|
||||
// not cause an error.
|
||||
$resetURL = $this->getResetURL();
|
||||
$this->drupalGet($resetURL);
|
||||
$this->submitForm([], 'Log in');
|
||||
$this->drupalGet('user/' . $this->account->id() . '/edit');
|
||||
$this->assertSession()->pageTextNotContains('Expected user_string to be a string, NULL given');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Create a password reset link as if the request time was 60 seconds older
|
||||
// than the allowed limit.
|
||||
$timeout = $this->config('user.settings')->get('password_reset_timeout');
|
||||
$bogus_timestamp = \Drupal::time()->getRequestTime() - $timeout - 60;
|
||||
$_uid = $this->account->id();
|
||||
$this->drupalGet("user/reset/$_uid/$bogus_timestamp/" . user_pass_rehash($this->account, $bogus_timestamp));
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has expired. Request a new one using the form below.');
|
||||
$this->drupalGet("user/reset/$_uid/$bogus_timestamp/" . user_pass_rehash($this->account, $bogus_timestamp) . '/login');
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has expired. Request a new one using the form below.');
|
||||
|
||||
// Create a user, block the account, and verify that a login link is denied.
|
||||
$timestamp = \Drupal::time()->getRequestTime() - 1;
|
||||
$blocked_account = $this->drupalCreateUser()->block();
|
||||
$blocked_account->save();
|
||||
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Verify a blocked user can not request a new password.
|
||||
$this->drupalGet('user/password');
|
||||
// Count email messages before to compare with after.
|
||||
$before = count($this->drupalGetMails(['id' => 'user_password_reset']));
|
||||
$edit = ['name' => $blocked_account->getAccountName()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertCount($before, $this->drupalGetMails(['id' => 'user_password_reset']), 'No email was sent when requesting password reset for a blocked account');
|
||||
|
||||
// Verify a password reset link is invalidated when the user's email address
|
||||
// changes.
|
||||
$this->drupalGet('user/password');
|
||||
$edit = ['name' => $this->account->getAccountName()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$old_email_reset_link = $this->getResetURL();
|
||||
$this->account->setEmail("1" . $this->account->getEmail());
|
||||
$this->account->save();
|
||||
$this->drupalGet($old_email_reset_link);
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.');
|
||||
$this->drupalGet($old_email_reset_link . '/login');
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.');
|
||||
|
||||
// Verify a password reset link will automatically log a user when /login is
|
||||
// appended.
|
||||
$this->drupalGet('user/password');
|
||||
$edit = ['name' => $this->account->getAccountName()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$reset_url = $this->getResetURL();
|
||||
$this->drupalGet($reset_url . '/login');
|
||||
$this->assertSession()->linkExists('Log out');
|
||||
$this->assertSession()->titleEquals($this->account->getAccountName() . ' | Drupal');
|
||||
|
||||
// Ensure blocked and deleted accounts can't access the user.reset.login
|
||||
// route.
|
||||
$this->drupalLogout();
|
||||
$timestamp = \Drupal::time()->getRequestTime() - 1;
|
||||
$blocked_account = $this->drupalCreateUser()->block();
|
||||
$blocked_account->save();
|
||||
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
$blocked_account->delete();
|
||||
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests password reset functionality when user has set preferred language.
|
||||
*/
|
||||
public function testUserPasswordResetPreferredLanguage(): void {
|
||||
// Set two new languages.
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
ConfigurableLanguage::createFromLangcode('zh-hant')->save();
|
||||
|
||||
$this->languageManager = \Drupal::languageManager();
|
||||
|
||||
// Set language prefixes.
|
||||
$config = $this->config('language.negotiation');
|
||||
$config->set('url.prefixes', ['en' => '', 'fr' => 'fr', 'zh-hant' => 'zh'])->save();
|
||||
$this->rebuildContainer();
|
||||
|
||||
foreach ($this->languagePrefixTestProvider() as $scenario) {
|
||||
[$setPreferredLangcode, $activeLangcode, $prefix, $visitingUrl, $expectedResetUrl, $unexpectedResetUrl] = array_values($scenario);
|
||||
$this->account->preferred_langcode = $setPreferredLangcode;
|
||||
$this->account->save();
|
||||
$this->assertSame($setPreferredLangcode, $this->account->getPreferredLangcode(FALSE));
|
||||
|
||||
// Test Default langcode is different from active langcode when visiting
|
||||
// different.
|
||||
if ($setPreferredLangcode !== 'en') {
|
||||
$this->drupalGet($prefix . '/user/password');
|
||||
$this->assertSame($activeLangcode, $this->getSession()->getResponseHeader('Content-language'));
|
||||
$this->assertSame('en', $this->languageManager->getDefaultLanguage()->getId());
|
||||
}
|
||||
|
||||
// Test password reset with language prefixes.
|
||||
$this->drupalGet($visitingUrl);
|
||||
$edit = ['name' => $this->account->getAccountName()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertValidPasswordReset($edit['name']);
|
||||
|
||||
$resetURL = $this->getResetURL();
|
||||
$this->assertStringContainsString($expectedResetUrl, $resetURL);
|
||||
$this->assertStringNotContainsString($unexpectedResetUrl, $resetURL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides scenarios for testUserPasswordResetPreferredLanguage().
|
||||
*
|
||||
* @return array
|
||||
* An array of scenarios.
|
||||
*/
|
||||
protected function languagePrefixTestProvider() {
|
||||
return [
|
||||
'Test language prefix set as \'\', visiting default with preferred language as en' => [
|
||||
'setPreferredLangcode' => 'en',
|
||||
'activeLangcode' => 'en',
|
||||
'prefix' => '',
|
||||
'visitingUrl' => 'user/password',
|
||||
'expectedResetUrl' => 'user/reset',
|
||||
'unexpectedResetUrl' => 'en/user/reset',
|
||||
],
|
||||
'Test language prefix set as fr, visiting zh with preferred language as fr' => [
|
||||
'setPreferredLangcode' => 'fr',
|
||||
'activeLangcode' => 'fr',
|
||||
'prefix' => 'fr',
|
||||
'visitingUrl' => 'zh/user/password',
|
||||
'expectedResetUrl' => 'fr/user/reset',
|
||||
'unexpectedResetUrl' => 'zh/user/reset',
|
||||
],
|
||||
'Test language prefix set as zh, visiting zh with preferred language as \'\'' => [
|
||||
'setPreferredLangcode' => '',
|
||||
'activeLangcode' => 'zh-hant',
|
||||
'prefix' => 'zh',
|
||||
'visitingUrl' => 'zh/user/password',
|
||||
'expectedResetUrl' => 'user/reset',
|
||||
'unexpectedResetUrl' => 'zh/user/reset',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves password reset email and extracts the login link.
|
||||
*/
|
||||
public function getResetURL() {
|
||||
// Assume the most recent email.
|
||||
$_emails = $this->drupalGetMails();
|
||||
$email = end($_emails);
|
||||
$urls = [];
|
||||
preg_match('#.+user/reset/.+#', $email['body'], $urls);
|
||||
|
||||
return $urls[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user password reset while logged in.
|
||||
*/
|
||||
public function testUserPasswordResetLoggedIn(): void {
|
||||
$another_account = $this->drupalCreateUser();
|
||||
$this->drupalLogin($another_account);
|
||||
$this->drupalGet('user/password');
|
||||
$this->submitForm([], 'Submit');
|
||||
|
||||
// Click the reset URL while logged and change our password.
|
||||
$resetURL = $this->getResetURL();
|
||||
// Log in as a different user.
|
||||
$this->drupalLogin($this->account);
|
||||
$this->drupalGet($resetURL);
|
||||
$this->assertSession()->pageTextContains("Another user ({$this->account->getAccountName()}) is already logged into the site on this computer, but you tried to use a one-time link for user {$another_account->getAccountName()}. Log out and try using the link again.");
|
||||
$this->assertSession()->linkExists('Log out');
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('user.logout')->toString());
|
||||
|
||||
// Verify that the invalid password reset page does not show the user name.
|
||||
$attack_reset_url = "user/reset/" . $another_account->id() . "/1/1";
|
||||
$this->drupalGet($attack_reset_url);
|
||||
$this->assertSession()->pageTextNotContains($another_account->getAccountName());
|
||||
$this->assertSession()->addressEquals('user/' . $this->account->id());
|
||||
$this->assertSession()->pageTextContains('The one-time login link you clicked is invalid.');
|
||||
|
||||
$another_account->delete();
|
||||
$this->drupalGet($resetURL);
|
||||
$this->assertSession()->pageTextContains('The one-time login link you clicked is invalid.');
|
||||
|
||||
// Log in.
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Reset the password by username via the password reset page.
|
||||
$this->drupalGet('user/password');
|
||||
$this->submitForm([], 'Submit');
|
||||
|
||||
// Click the reset URL while logged and change our password.
|
||||
$resetURL = $this->getResetURL();
|
||||
$this->drupalGet($resetURL);
|
||||
$this->submitForm([], 'Log in');
|
||||
|
||||
// Change the password.
|
||||
$password = \Drupal::service('password_generator')->generate();
|
||||
$edit = ['pass[pass1]' => $password, 'pass[pass2]' => $password];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
|
||||
// Logged in users should not be able to access the user.reset.login or the
|
||||
// user.reset.form routes.
|
||||
$timestamp = \Drupal::time()->getRequestTime() - 1;
|
||||
$this->drupalGet("user/reset/" . $this->account->id() . "/$timestamp/" . user_pass_rehash($this->account, $timestamp) . '/login');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalGet("user/reset/" . $this->account->id());
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the text box on incorrect login via link to password reset page.
|
||||
*/
|
||||
public function testUserResetPasswordTextboxNotFilled(): void {
|
||||
$this->drupalGet('user/login');
|
||||
$edit = [
|
||||
'name' => $this->randomMachineName(),
|
||||
'pass' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('user/login');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextContains("Unrecognized username or password. Forgot your password?");
|
||||
$this->assertSession()->linkExists("Forgot your password?");
|
||||
// Verify we don't pass the username as a query parameter.
|
||||
$this->assertSession()->linkByHrefNotExists(Url::fromRoute('user.pass', [], ['query' => ['name' => $edit['name']]])->toString());
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('user.pass')->toString());
|
||||
unset($edit['pass']);
|
||||
// Verify the field is empty by default.
|
||||
$this->drupalGet('user/password');
|
||||
$this->assertSession()->fieldValueEquals('name', '');
|
||||
// Ensure the name field value is not cached.
|
||||
$this->drupalGet('user/password', ['query' => ['name' => $edit['name']]]);
|
||||
$this->assertSession()->fieldValueEquals('name', $edit['name']);
|
||||
$this->drupalGet('user/password');
|
||||
$this->assertSession()->fieldValueNotEquals('name', $edit['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests password reset flood control for one user.
|
||||
*/
|
||||
public function testUserResetPasswordUserFloodControl(): void {
|
||||
\Drupal::configFactory()->getEditable('user.flood')
|
||||
->set('user_limit', 3)
|
||||
->save();
|
||||
|
||||
$edit = ['name' => $this->account->getAccountName()];
|
||||
|
||||
// Count email messages before to compare with after.
|
||||
$before = count($this->drupalGetMails(['id' => 'user_password_reset']));
|
||||
|
||||
// Try 3 requests that should not trigger flood control.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->drupalGet('user/password');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertValidPasswordReset($edit['name']);
|
||||
}
|
||||
|
||||
// Ensure 3 emails were sent.
|
||||
$this->assertCount($before + 3, $this->drupalGetMails(['id' => 'user_password_reset']), '3 emails sent without triggering flood control.');
|
||||
|
||||
// The next request should trigger flood control.
|
||||
$this->drupalGet('user/password');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
|
||||
// Ensure no further emails were sent.
|
||||
$this->assertCount($before + 3, $this->drupalGetMails(['id' => 'user_password_reset']), 'No further email was sent after triggering flood control.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests password reset flood control for one IP.
|
||||
*/
|
||||
public function testUserResetPasswordIpFloodControl(): void {
|
||||
\Drupal::configFactory()->getEditable('user.flood')
|
||||
->set('ip_limit', 3)
|
||||
->save();
|
||||
|
||||
// Try 3 requests that should not trigger flood control.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->drupalGet('user/password');
|
||||
$random_name = $this->randomMachineName();
|
||||
$edit = ['name' => $random_name];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
// Because we're testing with a random name, the password reset will not
|
||||
// be valid.
|
||||
$this->assertNoValidPasswordReset($random_name);
|
||||
$this->assertNoPasswordIpFlood();
|
||||
}
|
||||
|
||||
// The next request should trigger flood control.
|
||||
$this->drupalGet('user/password');
|
||||
$edit = ['name' => $this->randomMachineName()];
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertPasswordIpFlood();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user password reset flood control is cleared on successful reset.
|
||||
*/
|
||||
public function testUserResetPasswordUserFloodControlIsCleared(): void {
|
||||
\Drupal::configFactory()->getEditable('user.flood')
|
||||
->set('user_limit', 3)
|
||||
->save();
|
||||
|
||||
$edit = ['name' => $this->account->getAccountName()];
|
||||
|
||||
// Count email messages before to compare with after.
|
||||
$before = count($this->drupalGetMails(['id' => 'user_password_reset']));
|
||||
|
||||
// Try 3 requests that should not trigger flood control.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->drupalGet('user/password');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertValidPasswordReset($edit['name']);
|
||||
}
|
||||
|
||||
// Ensure 3 emails were sent.
|
||||
$this->assertCount($before + 3, $this->drupalGetMails(['id' => 'user_password_reset']), '3 emails sent without triggering flood control.');
|
||||
|
||||
// Use the last password reset URL which was generated.
|
||||
$reset_url = $this->getResetURL();
|
||||
$this->drupalGet($reset_url . '/login');
|
||||
$this->assertSession()->linkExists('Log out');
|
||||
$this->assertSession()->titleEquals($this->account->getAccountName() . ' | Drupal');
|
||||
$this->drupalLogout();
|
||||
|
||||
// The next request should *not* trigger flood control, since a successful
|
||||
// password reset should have cleared flood events for this user.
|
||||
$this->drupalGet('user/password');
|
||||
$this->submitForm($edit, 'Submit');
|
||||
$this->assertValidPasswordReset($edit['name']);
|
||||
|
||||
// Ensure another email was sent.
|
||||
$this->assertCount($before + 4, $this->drupalGetMails(['id' => 'user_password_reset']), 'Another email was sent after clearing flood control.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user password reset flood control is cleared on admin reset.
|
||||
*/
|
||||
public function testUserResetPasswordUserFloodControlAdmin(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer account settings',
|
||||
'administer users',
|
||||
]);
|
||||
\Drupal::configFactory()->getEditable('user.flood')
|
||||
->set('user_limit', 3)
|
||||
->save();
|
||||
|
||||
$edit = [
|
||||
'name' => $this->account->getAccountName(),
|
||||
'pass' => 'wrong_password',
|
||||
];
|
||||
|
||||
// Try 3 requests that should not trigger flood control.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->drupalGet('user/login');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextNotContains('There have been more than 3 failed login attempts for this account. It is temporarily blocked.');
|
||||
}
|
||||
$this->drupalGet('user/login');
|
||||
$this->submitForm($edit, 'Log in');
|
||||
$this->assertSession()->pageTextContains('There have been more than 3 failed login attempts for this account. It is temporarily blocked.');
|
||||
|
||||
$password = $this->randomMachineName();
|
||||
$edit = [
|
||||
'pass[pass1]' => $password,
|
||||
'pass[pass2]' => $password,
|
||||
];
|
||||
// Log in as admin and change the user password.
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('user/' . $this->account->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalLogout();
|
||||
|
||||
$edit = [
|
||||
'name' => $this->account->getAccountName(),
|
||||
'pass' => $password,
|
||||
];
|
||||
|
||||
// The next request should *not* trigger flood control, since the
|
||||
// password change should have cleared flood events for this user.
|
||||
$this->account->passRaw = $password;
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
$this->assertSession()->pageTextNotContains('There have been more than 3 failed login attempts for this account. It is temporarily blocked.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make assertions about a valid password reset.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertValidPasswordReset(string $name): void {
|
||||
$this->assertSession()->pageTextContains("If $name is a valid account, an email will be sent with instructions to reset your password.");
|
||||
$this->assertMail('to', $this->account->getEmail(), 'Password email sent to user.');
|
||||
$subject = 'Replacement login information for ' . $this->account->getAccountName() . ' at Drupal';
|
||||
$this->assertMail('subject', $subject, 'Password reset email subject is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make assertions about an invalid password reset.
|
||||
*
|
||||
* @param string $name
|
||||
* The user name.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertNoValidPasswordReset(string $name): void {
|
||||
// This message is the same as the valid reset for privacy reasons.
|
||||
$this->assertSession()->pageTextContains("If $name is a valid account, an email will be sent with instructions to reset your password.");
|
||||
// The difference is that no email is sent.
|
||||
$this->assertCount(0, $this->drupalGetMails(['id' => 'user_password_reset']), 'No email was sent when requesting a password for an invalid account.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes assertions about a password reset triggering IP flood control.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertPasswordIpFlood(): void {
|
||||
$this->assertSession()->pageTextContains('Too many password recovery requests from your IP address. It is temporarily blocked. Try again later or contact the site administrator.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes assertions about a password reset not triggering IP flood control.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertNoPasswordIpFlood(): void {
|
||||
$this->assertSession()->pageTextNotContains('Too many password recovery requests from your IP address. It is temporarily blocked. Try again later or contact the site administrator.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that users cannot forge password reset URLs of other users.
|
||||
*/
|
||||
public function testResetImpersonation(): void {
|
||||
// Create two identical user accounts except for the user name. They must
|
||||
// have the same empty password, so we can't use $this->drupalCreateUser().
|
||||
$edit = [];
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$edit['mail'] = $edit['name'] . '@example.com';
|
||||
$edit['status'] = 1;
|
||||
$user1 = User::create($edit);
|
||||
$user1->save();
|
||||
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$user2 = User::create($edit);
|
||||
$user2->save();
|
||||
|
||||
// Unique password hashes are automatically generated, the only way to
|
||||
// change that is to update it directly in the database.
|
||||
Database::getConnection()->update('users_field_data')
|
||||
->fields(['pass' => NULL])
|
||||
->condition('uid', [$user1->id(), $user2->id()], 'IN')
|
||||
->execute();
|
||||
\Drupal::entityTypeManager()->getStorage('user')->resetCache();
|
||||
$user1 = User::load($user1->id());
|
||||
$user2 = User::load($user2->id());
|
||||
|
||||
$this->assertEquals($user2->getPassword(), $user1->getPassword(), 'Both users have the same password hash.');
|
||||
|
||||
// The password reset URL must not be valid for the second user when only
|
||||
// the user ID is changed in the URL.
|
||||
$reset_url = user_pass_reset_url($user1);
|
||||
$attack_reset_url = str_replace("user/reset/{$user1->id()}", "user/reset/{$user2->id()}", $reset_url);
|
||||
$this->drupalGet($attack_reset_url);
|
||||
// Verify that the invalid password reset page does not show the user name.
|
||||
$this->assertSession()->pageTextNotContains($user2->getAccountName());
|
||||
$this->assertSession()->addressEquals('user/password');
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.');
|
||||
$this->drupalGet($attack_reset_url . '/login');
|
||||
// Verify that the invalid password reset page does not show the user name.
|
||||
$this->assertSession()->pageTextNotContains($user2->getAccountName());
|
||||
$this->assertSession()->addressEquals('user/password');
|
||||
$this->assertSession()->pageTextContains('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the autocomplete attribute is present.
|
||||
*/
|
||||
public function testResetFormHasAutocompleteAttribute(): void {
|
||||
$this->drupalGet('user/password');
|
||||
$field = $this->getSession()->getPage()->findField('name');
|
||||
$this->assertEquals('username', $field->getAttribute('autocomplete'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests adding and removing permissions via the UI.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserPermissionsAdminTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests granting and revoking permissions via the UI sorts permissions.
|
||||
*/
|
||||
public function testPermissionsSorting(): void {
|
||||
$role = Role::create(['id' => 'test_role', 'label' => 'Test role']);
|
||||
// Start the role with a permission that is near the end of the alphabet.
|
||||
$role->grantPermission('view user email addresses');
|
||||
$role->save();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer permissions',
|
||||
]));
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Add a permission that is near the start of the alphabet.
|
||||
$this->submitForm([
|
||||
'test_role[change own username]' => 1,
|
||||
], 'Save permissions');
|
||||
|
||||
// Check that permissions are sorted alphabetically.
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('user_role');
|
||||
/** @var \Drupal\user\Entity\Role $role */
|
||||
$role = $storage->loadUnchanged($role->id());
|
||||
$this->assertEquals([
|
||||
'change own username',
|
||||
'view user email addresses',
|
||||
], $role->getPermissions());
|
||||
|
||||
// Remove the first permission, resulting in a single permission in the
|
||||
// first key of the array.
|
||||
$this->submitForm([
|
||||
'test_role[change own username]' => 0,
|
||||
], 'Save permissions');
|
||||
/** @var \Drupal\user\Entity\Role $role */
|
||||
$role = $storage->loadUnchanged($role->id());
|
||||
$this->assertEquals([
|
||||
'view user email addresses',
|
||||
], $role->getPermissions());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Verifies role permissions can be added and removed via the permissions page.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserPermissionsTest extends BrowserTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
|
||||
/**
|
||||
* User with admin privileges.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* User's role ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rid;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user_config_override_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer permissions',
|
||||
'access user profiles',
|
||||
'administer site configuration',
|
||||
'administer modules',
|
||||
'administer account settings',
|
||||
]);
|
||||
|
||||
// Find the new role ID.
|
||||
$all_rids = $this->adminUser->getRoles();
|
||||
unset($all_rids[array_search(RoleInterface::AUTHENTICATED_ID, $all_rids)]);
|
||||
$this->rid = reset($all_rids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing user permissions through the permissions pages.
|
||||
*/
|
||||
public function testUserPermissionChanges(): void {
|
||||
$permissions_hash_generator = $this->container->get('user_permissions_hash_generator');
|
||||
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('user_role');
|
||||
|
||||
// Create an additional role and mark it as admin role.
|
||||
Role::create(['is_admin' => TRUE, 'id' => 'administrator', 'label' => 'Administrator'])->save();
|
||||
$storage->resetCache();
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$rid = $this->rid;
|
||||
$account = $this->adminUser;
|
||||
$previous_permissions_hash = $permissions_hash_generator->generate($account);
|
||||
$this->assertSame($previous_permissions_hash, $permissions_hash_generator->generate($this->loggedInUser));
|
||||
|
||||
// Add a permission.
|
||||
$this->assertFalse($account->hasPermission('administer users'), 'User does not have "administer users" permission.');
|
||||
$edit = [];
|
||||
$edit[$rid . '[administer users]'] = TRUE;
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->submitForm($edit, 'Save permissions');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$storage->resetCache();
|
||||
$this->assertTrue($account->hasPermission('administer users'), 'User now has "administer users" permission.');
|
||||
$current_permissions_hash = $permissions_hash_generator->generate($account);
|
||||
$this->assertSame($current_permissions_hash, $permissions_hash_generator->generate($this->loggedInUser));
|
||||
$this->assertNotEquals($previous_permissions_hash, $current_permissions_hash, 'Permissions hash has changed.');
|
||||
$previous_permissions_hash = $current_permissions_hash;
|
||||
|
||||
// Remove a permission.
|
||||
$this->assertTrue($account->hasPermission('access user profiles'), 'User has "access user profiles" permission.');
|
||||
$edit = [];
|
||||
$edit[$rid . '[access user profiles]'] = FALSE;
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->submitForm($edit, 'Save permissions');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$storage->resetCache();
|
||||
$this->assertFalse($account->hasPermission('access user profiles'), 'User no longer has "access user profiles" permission.');
|
||||
$current_permissions_hash = $permissions_hash_generator->generate($account);
|
||||
$this->assertSame($current_permissions_hash, $permissions_hash_generator->generate($this->loggedInUser));
|
||||
$this->assertNotEquals($previous_permissions_hash, $current_permissions_hash, 'Permissions hash has changed.');
|
||||
|
||||
// Permissions can be changed using the module-specific pages with the same
|
||||
// result.
|
||||
$edit = [];
|
||||
$edit[$rid . '[access user profiles]'] = TRUE;
|
||||
$this->drupalGet('admin/people/permissions/module/user');
|
||||
$this->submitForm($edit, 'Save permissions');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$storage->resetCache();
|
||||
$this->assertTrue($account->hasPermission('access user profiles'), 'User again has "access user profiles" permission.');
|
||||
$current_permissions_hash = $permissions_hash_generator->generate($account);
|
||||
$this->assertSame($current_permissions_hash, $permissions_hash_generator->generate($this->loggedInUser));
|
||||
$this->assertEquals($previous_permissions_hash, $current_permissions_hash, 'Permissions hash has reverted.');
|
||||
|
||||
// Ensure that the admin role doesn't have any checkboxes.
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
foreach (array_keys($this->container->get('user.permissions')->getPermissions()) as $permission) {
|
||||
$this->assertSession()->checkboxChecked('administrator[' . $permission . ']');
|
||||
$this->assertSession()->fieldDisabled('administrator[' . $permission . ']');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests assigning of permissions for the administrator role.
|
||||
*/
|
||||
public function testAdministratorRole(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/people/role-settings');
|
||||
|
||||
// Verify that the administration role is none by default.
|
||||
$this->assertTrue($this->assertSession()->optionExists('edit-user-admin-role', '')->isSelected());
|
||||
|
||||
$this->assertFalse(Role::load($this->rid)->isAdmin());
|
||||
|
||||
// Set the user's role to be the administrator role.
|
||||
$edit = [];
|
||||
$edit['user_admin_role'] = $this->rid;
|
||||
$this->drupalGet('admin/people/role-settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Check that the success message appears.
|
||||
$this->assertSession()->pageTextContains('The role settings have been updated.');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('user_role')->resetCache();
|
||||
$this->assertTrue(Role::load($this->rid)->isAdmin());
|
||||
|
||||
// Enable block module and ensure the 'administer news feeds'
|
||||
// permission is assigned by default.
|
||||
\Drupal::service('module_installer')->install(['block']);
|
||||
|
||||
$this->assertTrue($this->adminUser->hasPermission('administer blocks'), 'The permission was automatically assigned to the administrator role');
|
||||
|
||||
// Ensure that selecting '- None -' removes the admin role.
|
||||
$edit = [];
|
||||
$edit['user_admin_role'] = '';
|
||||
$this->drupalGet('admin/people/role-settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Check that the success message appears.
|
||||
$this->assertSession()->pageTextContains('The role settings have been updated.');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('user_role')->resetCache();
|
||||
\Drupal::configFactory()->reset();
|
||||
$this->assertFalse(Role::load($this->rid)->isAdmin());
|
||||
|
||||
// Manually create two admin roles, in that case the single select should be
|
||||
// hidden.
|
||||
Role::create(['id' => 'admin_role_0', 'is_admin' => TRUE, 'label' => 'Admin role 0'])->save();
|
||||
Role::create(['id' => 'admin_role_1', 'is_admin' => TRUE, 'label' => 'Admin role 1'])->save();
|
||||
$this->drupalGet('admin/people/role-settings');
|
||||
$this->assertSession()->fieldNotExists('user_admin_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify proper permission changes by user_role_change_permissions().
|
||||
*/
|
||||
public function testUserRoleChangePermissions(): void {
|
||||
$permissions_hash_generator = $this->container->get('user_permissions_hash_generator');
|
||||
|
||||
$rid = $this->rid;
|
||||
$account = $this->adminUser;
|
||||
$previous_permissions_hash = $permissions_hash_generator->generate($account);
|
||||
|
||||
// Verify current permissions.
|
||||
$this->assertFalse($account->hasPermission('administer users'), 'User does not have "administer users" permission.');
|
||||
$this->assertTrue($account->hasPermission('access user profiles'), 'User has "access user profiles" permission.');
|
||||
$this->assertTrue($account->hasPermission('administer site configuration'), 'User has "administer site configuration" permission.');
|
||||
|
||||
// Change permissions.
|
||||
$permissions = [
|
||||
'administer users' => 1,
|
||||
'access user profiles' => 0,
|
||||
];
|
||||
user_role_change_permissions($rid, $permissions);
|
||||
|
||||
// Verify proper permission changes.
|
||||
$this->assertTrue($account->hasPermission('administer users'), 'User now has "administer users" permission.');
|
||||
$this->assertFalse($account->hasPermission('access user profiles'), 'User no longer has "access user profiles" permission.');
|
||||
$this->assertTrue($account->hasPermission('administer site configuration'), 'User still has "administer site configuration" permission.');
|
||||
|
||||
// Verify the permissions hash has changed.
|
||||
$current_permissions_hash = $permissions_hash_generator->generate($account);
|
||||
$this->assertNotEquals($previous_permissions_hash, $current_permissions_hash, 'Permissions hash has changed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify 'access content' is listed in the correct location.
|
||||
*/
|
||||
public function testAccessContentPermission(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// When Node is not installed the 'access content' permission is listed next
|
||||
// to 'access site reports'.
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$next_row = $this->xpath('//tr[@data-drupal-selector=\'edit-permissions-access-content\']/following-sibling::tr[1]');
|
||||
$this->assertEquals('edit-permissions-access-site-reports', $next_row[0]->getAttribute('data-drupal-selector'));
|
||||
|
||||
// When Node is installed the 'access content' permission is listed next to
|
||||
// to 'view own unpublished content'.
|
||||
\Drupal::service('module_installer')->install(['node']);
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$next_row = $this->xpath('//tr[@data-drupal-selector=\'edit-permissions-access-content\']/following-sibling::tr[1]');
|
||||
$this->assertEquals('edit-permissions-view-own-unpublished-content', $next_row[0]->getAttribute('data-drupal-selector'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that module-specific pages have correct access.
|
||||
*/
|
||||
public function testAccessModulePermission(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// When Node is not installed, the node-permissions page is not available.
|
||||
$this->drupalGet('admin/people/permissions/module/node');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Modules that do not create permissions have no permissions pages.
|
||||
\Drupal::service('module_installer')->install(['automated_cron']);
|
||||
$this->drupalGet('admin/people/permissions/module/automated_cron');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalGet('admin/people/permissions/module/node,automated_cron');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// When Node is installed, the node-permissions page is available.
|
||||
\Drupal::service('module_installer')->install(['node']);
|
||||
$this->drupalGet('admin/people/permissions/module/node');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->drupalGet('admin/people/permissions/module/node,automated_cron');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Anonymous users cannot access any of these pages.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('admin/people/permissions/module/node');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalGet('admin/people/permissions/module/automated_cron');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalGet('admin/people/permissions/module/node,automated_cron');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that bundle-specific pages work properly.
|
||||
*/
|
||||
public function testAccessBundlePermission(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
\Drupal::service('module_installer')->install(['contact', 'taxonomy']);
|
||||
$this->grantPermissions(Role::load($this->rid), ['administer contact forms', 'administer taxonomy']);
|
||||
|
||||
// Bundles that do not have permissions have no permissions pages.
|
||||
$edit = [];
|
||||
$edit['label'] = 'Test contact type';
|
||||
$edit['id'] = 'test_contact_type';
|
||||
$edit['recipients'] = 'webmaster@example.com';
|
||||
$this->drupalGet('admin/structure/contact/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('Contact form ' . $edit['label'] . ' has been added.');
|
||||
$this->drupalGet('admin/structure/contact/manage/test_contact_type/permissions');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('No permissions found.');
|
||||
|
||||
// Permissions can be changed using the bundle-specific pages.
|
||||
$edit = [];
|
||||
$edit['name'] = 'Test vocabulary';
|
||||
$edit['vid'] = 'test_vocabulary';
|
||||
$this->drupalGet('admin/structure/taxonomy/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/test_vocabulary/overview/permissions');
|
||||
$this->assertSession()->checkboxNotChecked('authenticated[create terms in test_vocabulary]');
|
||||
$this->assertSession()->fieldExists('authenticated[create terms in test_vocabulary]')->check();
|
||||
$this->getSession()->getPage()->pressButton('Save permissions');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->assertSession()->checkboxChecked('authenticated[create terms in test_vocabulary]');
|
||||
|
||||
// Typos produce 404 response, not server errors.
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/test_typo/overview/permissions');
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
|
||||
// Anonymous users cannot access any of these pages.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/test_vocabulary/overview/permissions');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalGet('admin/structure/contact/manage/test_contact_type/permissions');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that access check does not trigger warnings.
|
||||
*
|
||||
* The access check for /admin/structure/comment/manage/comment/permissions is
|
||||
* \Drupal\user\Form\EntityPermissionsForm::EntityPermissionsForm::access().
|
||||
*/
|
||||
public function testBundlePermissionError(): void {
|
||||
\Drupal::service('module_installer')->install(['comment', 'dblog', 'field_ui', 'node']);
|
||||
// Set up the node and comment field. Use the 'default' view mode since
|
||||
// 'full' is not defined, so it will not be added to the config entity.
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
$this->addDefaultCommentField('node', 'article', comment_view_mode: 'default');
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->grantPermissions(Role::load($this->rid), ['access site reports', 'administer comment display']);
|
||||
|
||||
// Access both the Manage display and permission page, which is not
|
||||
// accessible currently.
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet('/admin/structure/comment/manage/comment/display');
|
||||
$assert_session->statusCodeEquals(200);
|
||||
$this->drupalGet('/admin/structure/comment/manage/comment/permissions');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('No permissions found.');
|
||||
|
||||
// Ensure there are no warnings in the log.
|
||||
$this->drupalGet('/admin/reports/dblog');
|
||||
$assert_session->statusCodeEquals(200);
|
||||
$assert_session->pageTextContains('Session opened');
|
||||
$assert_session->pageTextNotContains("Entity view display 'node.article.default': Component");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the permission form does not use overridden config.
|
||||
*
|
||||
* @see \Drupal\user_config_override_test\ConfigOverrider
|
||||
*/
|
||||
public function testOverriddenPermission(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->assertSession()->checkboxNotChecked('anonymous[access content]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that module header rows in the permissions table have a single cell.
|
||||
*/
|
||||
public function testPermissionTableHtml(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
\Drupal::service('module_installer')->install(['user_permissions_test']);
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
|
||||
// Verify that if a permission has the same name as a module, that its
|
||||
// table cells aren't combined into the module's header row. The header row
|
||||
// should have a single cell in that case.
|
||||
$header_row = $this->xpath('//tr[@data-drupal-selector=\'edit-permissions-module-user-permissions-test\'][count(td)=1]');
|
||||
$this->assertNotEmpty($header_row);
|
||||
}
|
||||
|
||||
}
|
||||
191
web/core/modules/user/tests/src/Functional/UserPictureTest.php
Normal file
191
web/core/modules/user/tests/src/Functional/UserPictureTest.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManager;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests user picture functionality.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserPictureTest extends BrowserTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
use CommentTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'test_user_config',
|
||||
'node',
|
||||
'comment',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A regular user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// This test expects unused managed files to be marked temporary and then
|
||||
// cleaned up by file_cron().
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'access comments',
|
||||
'post comments',
|
||||
'skip comment approval',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation, display, and deletion of user pictures.
|
||||
*/
|
||||
public function testCreateDeletePicture(): void {
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
// Save a new picture.
|
||||
$image = current($this->drupalGetTestFiles('image'));
|
||||
$file = $this->saveUserPicture($image);
|
||||
|
||||
// Verify that the image is displayed on the user account page.
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->responseContains(StreamWrapperManager::getTarget($file->getFileUri()));
|
||||
|
||||
// Delete the picture.
|
||||
$edit = [];
|
||||
$this->drupalGet('user/' . $this->webUser->id() . '/edit');
|
||||
$this->submitForm($edit, 'Remove');
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Call file_cron() to clean up the file. Make sure the timestamp
|
||||
// of the file is older than the system.file.temporary_maximum_age
|
||||
// configuration value. We use an UPDATE statement because using the API
|
||||
// would set the timestamp.
|
||||
Database::getConnection()->update('file_managed')
|
||||
->fields([
|
||||
'changed' => \Drupal::time()->getRequestTime() - ($this->config('system.file')->get('temporary_maximum_age') + 1),
|
||||
])
|
||||
->condition('fid', $file->id())
|
||||
->execute();
|
||||
\Drupal::service('cron')->run();
|
||||
|
||||
// Verify that the image has been deleted.
|
||||
$this->assertNull(File::load($file->id()), 'File was removed from the database.');
|
||||
// Clear out PHP's file stat cache so we see the current value.
|
||||
clearstatcache(TRUE, $file->getFileUri());
|
||||
$this->assertFileDoesNotExist($file->getFileUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests embedded users on node pages.
|
||||
*/
|
||||
public function testPictureOnNodeComment(): void {
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
$this->addDefaultCommentField('node', 'article');
|
||||
|
||||
// Save a new picture.
|
||||
$image = current($this->drupalGetTestFiles('image'));
|
||||
$file = $this->saveUserPicture($image);
|
||||
|
||||
$node = $this->drupalCreateNode(['type' => 'article']);
|
||||
|
||||
// Enable user pictures on nodes.
|
||||
$this->config('system.theme.global')->set('features.node_user_picture', TRUE)->save();
|
||||
|
||||
$image_style_id = $this->config('core.entity_view_display.user.user.compact')->get('content.user_picture.settings.image_style');
|
||||
$style = ImageStyle::load($image_style_id);
|
||||
$image_url = \Drupal::service('file_url_generator')->transformRelative($style->buildUrl($file->getFileUri()));
|
||||
$alt_text = 'Profile picture for user ' . $this->webUser->getAccountName();
|
||||
|
||||
// Verify that the image is displayed on the node page.
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$elements = $this->cssSelect('article > footer img[alt="' . $alt_text . '"][src="' . $image_url . '"]');
|
||||
$this->assertCount(1, $elements, 'User picture with alt text found on node page.');
|
||||
|
||||
// Enable user pictures on comments, instead of nodes.
|
||||
$this->config('system.theme.global')
|
||||
->set('features.node_user_picture', FALSE)
|
||||
->set('features.comment_user_picture', TRUE)
|
||||
->save();
|
||||
|
||||
$edit = [
|
||||
'comment_body[0][value]' => $this->randomString(),
|
||||
];
|
||||
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$elements = $this->cssSelect('#comment-1 img[alt="' . $alt_text . '"][src="' . $image_url . '"]');
|
||||
$this->assertCount(1, $elements, 'User picture with alt text found on the comment.');
|
||||
|
||||
// Disable user pictures on comments and nodes.
|
||||
$this->config('system.theme.global')
|
||||
->set('features.node_user_picture', FALSE)
|
||||
->set('features.comment_user_picture', FALSE)
|
||||
->save();
|
||||
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertSession()->responseNotContains(StreamWrapperManager::getTarget($file->getFileUri()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the user picture for the test user.
|
||||
*/
|
||||
public function saveUserPicture($image) {
|
||||
$edit = ['files[user_picture_0]' => \Drupal::service('file_system')->realpath($image->uri)];
|
||||
$this->drupalGet('user/' . $this->webUser->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Load actual user data from database.
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
$account = $user_storage->load($this->webUser->id());
|
||||
return File::load($account->user_picture->target_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user picture field with a non-standard field formatter.
|
||||
*
|
||||
* @see user_user_view_alter()
|
||||
*/
|
||||
public function testUserViewAlter(): void {
|
||||
\Drupal::service('module_installer')->install(['image_module_test']);
|
||||
// Set dummy_image_formatter to the default view mode of user entity.
|
||||
EntityViewDisplay::load('user.user.default')->setComponent('user_picture', [
|
||||
'region' => 'content',
|
||||
'type' => 'dummy_image_formatter',
|
||||
])->save();
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('Dummy');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\ResourceTestBase;
|
||||
use Drupal\user\UserInterface;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Tests registration of user using REST.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserRegistrationRestTest extends ResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $resourceConfigId = 'user_registration';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'rest'];
|
||||
|
||||
/**
|
||||
* Entity type ID for this storage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static string $entityTypeId;
|
||||
|
||||
const USER_EMAIL_DOMAIN = '@example.com';
|
||||
|
||||
const TEST_EMAIL_DOMAIN = 'simpletest@example.com';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$auth = isset(static::$auth) ? [static::$auth] : [];
|
||||
$this->provisionResource([static::$format], $auth);
|
||||
|
||||
$this->setUpAuthorization('POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that only anonymous users can register users.
|
||||
*/
|
||||
public function testRegisterUser(): void {
|
||||
$config = $this->config('user.settings');
|
||||
|
||||
// Test out different setting User Registration and Email Verification.
|
||||
// Allow visitors to register with no email verification.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS);
|
||||
$config->set('verify_mail', 0);
|
||||
$config->save();
|
||||
$user = $this->registerUser('Palmer.Eldritch');
|
||||
$this->assertFalse($user->isBlocked());
|
||||
$this->assertNotEmpty($user->getPassword());
|
||||
$email_count = count($this->drupalGetMails());
|
||||
|
||||
$this->assertEquals(1, $email_count);
|
||||
|
||||
// Attempt to register without sending a password.
|
||||
$response = $this->registerRequest('PhilipK.Dick', FALSE);
|
||||
$this->assertResourceErrorResponse(422, "No password provided.", $response);
|
||||
|
||||
// Attempt to register with a password when email verification is on.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS);
|
||||
$config->set('verify_mail', 1);
|
||||
$config->save();
|
||||
$response = $this->registerRequest('UrsulaK.LeGuin');
|
||||
$this->assertResourceErrorResponse(422, 'A Password cannot be specified. It will be generated on login.', $response);
|
||||
|
||||
// Allow visitors to register with email verification.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS);
|
||||
$config->set('verify_mail', 1);
|
||||
$config->save();
|
||||
$name = 'Jason.Taverner';
|
||||
$user = $this->registerUser($name, FALSE);
|
||||
$this->assertNotEmpty($user->getPassword());
|
||||
$this->assertFalse($user->isBlocked());
|
||||
$this->resetAll();
|
||||
|
||||
$this->assertMailString('body', 'You may now log in by clicking this link', 1);
|
||||
|
||||
// Allow visitors to register with Admin approval and no email verification.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
|
||||
$config->set('verify_mail', 0);
|
||||
$config->save();
|
||||
$name = 'Alex';
|
||||
$user = $this->registerUser($name);
|
||||
$this->resetAll();
|
||||
$this->assertNotEmpty($user->getPassword());
|
||||
$this->assertTrue($user->isBlocked());
|
||||
$this->assertMailString('body', 'Your application for an account is', 2);
|
||||
$this->assertMailString('body', 'Alex has applied for an account', 2);
|
||||
|
||||
// Allow visitors to register with Admin approval and email verification.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
|
||||
$config->set('verify_mail', 1);
|
||||
$config->save();
|
||||
$name = 'PhilipK.Dick';
|
||||
$user = $this->registerUser($name, FALSE);
|
||||
$this->resetAll();
|
||||
$this->assertNotEmpty($user->getPassword());
|
||||
$this->assertTrue($user->isBlocked());
|
||||
|
||||
$this->assertMailString('body', 'Your application for an account is', 2);
|
||||
$this->assertMailString('body', 'PhilipK.Dick has applied for an account', 2);
|
||||
|
||||
// Verify that an authenticated user cannot register a new user, despite
|
||||
// being granted permission to do so because only anonymous users can
|
||||
// register themselves, authenticated users with the necessary permissions
|
||||
// can POST a new user to the "user" REST resource.
|
||||
$this->initAuthentication();
|
||||
$response = $this->registerRequest($this->account->getAccountName());
|
||||
$this->assertResourceErrorResponse(403, "Only anonymous users can register a user.", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the request body.
|
||||
*
|
||||
* @param string $name
|
||||
* Name.
|
||||
* @param bool $include_password
|
||||
* Include Password.
|
||||
* @param bool $include_email
|
||||
* Include Email.
|
||||
*
|
||||
* @return array
|
||||
* Return the request body.
|
||||
*/
|
||||
protected function createRequestBody($name, $include_password = TRUE, $include_email = TRUE): array {
|
||||
$request_body = [
|
||||
'langcode' => [['value' => 'en']],
|
||||
'name' => [['value' => $name]],
|
||||
];
|
||||
|
||||
if ($include_email) {
|
||||
$request_body['mail'] = [['value' => $name . self::USER_EMAIL_DOMAIN]];
|
||||
}
|
||||
|
||||
if ($include_password) {
|
||||
$request_body['pass']['value'] = 'SuperSecretPassword';
|
||||
}
|
||||
|
||||
return $request_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to generate the request body.
|
||||
*
|
||||
* @param array $request_body
|
||||
* The request body array.
|
||||
*
|
||||
* @return array
|
||||
* Return the request options.
|
||||
*/
|
||||
protected function createRequestOptions(array $request_body) {
|
||||
$request_options = $this->getAuthenticationRequestOptions('POST');
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, static::$format);
|
||||
$request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
|
||||
|
||||
return $request_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a user via REST resource.
|
||||
*
|
||||
* @param string $name
|
||||
* User name.
|
||||
* @param bool $include_password
|
||||
* Include the password.
|
||||
* @param bool $include_email
|
||||
* Include the email?
|
||||
*
|
||||
* @return bool|\Drupal\user\Entity\User
|
||||
* Return bool or the user.
|
||||
*/
|
||||
protected function registerUser($name, $include_password = TRUE, $include_email = TRUE) {
|
||||
// Verify that an anonymous user can register.
|
||||
$response = $this->registerRequest($name, $include_password, $include_email);
|
||||
$this->assertResourceResponse(200, FALSE, $response);
|
||||
$user = user_load_by_name($name);
|
||||
$this->assertNotEmpty($user, 'User was create as expected');
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a REST user registration request.
|
||||
*
|
||||
* @param string $name
|
||||
* The name.
|
||||
* @param bool $include_password
|
||||
* Include the password?
|
||||
* @param bool $include_email
|
||||
* Include the email?
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* Return the Response.
|
||||
*/
|
||||
protected function registerRequest($name, $include_password = TRUE, $include_email = TRUE): ResponseInterface {
|
||||
$user_register_url = Url::fromRoute('user.register')
|
||||
->setRouteParameter('_format', static::$format);
|
||||
$request_body = $this->createRequestBody($name, $include_password, $include_email);
|
||||
$request_options = $this->createRequestOptions($request_body);
|
||||
$response = $this->request('POST', $user_register_url, $request_options);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method): void {
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
$this->grantPermissionsToAuthenticatedRole(['restful post user_registration']);
|
||||
$this->grantPermissionsToAnonymousRole(['restful post user_registration']);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \UnexpectedValueException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertNormalizationEdgeCases($method, Url $url, array $request_options): void {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessCacheability() {
|
||||
return new CacheableMetadata();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,423 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests registration of user under different configurations.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserRegistrationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests user registration with email verification enabled.
|
||||
*/
|
||||
public function testRegistrationWithEmailVerification(): void {
|
||||
$config = $this->config('user.settings');
|
||||
// Require email verification.
|
||||
$config->set('verify_mail', TRUE)->save();
|
||||
|
||||
// Set registration to administrator only and ensure the user registration
|
||||
// page is inaccessible.
|
||||
$config->set('register', UserInterface::REGISTER_ADMINISTRATORS_ONLY)->save();
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Allow registration by site visitors without administrator approval.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS)->save();
|
||||
$edit = [];
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('A welcome message with further instructions has been sent to your email address.');
|
||||
|
||||
/** @var EntityStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
$accounts = $storage->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$this->assertTrue($new_user->isActive(), 'New account is active after registration.');
|
||||
$resetURL = user_pass_reset_url($new_user);
|
||||
$this->drupalGet($resetURL);
|
||||
$this->assertSession()->titleEquals('Set password | Drupal');
|
||||
|
||||
// Allow registration by site visitors, but require administrator approval.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
|
||||
$edit = [];
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->container->get('entity_type.manager')->getStorage('user')->resetCache();
|
||||
$accounts = $storage->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$this->assertFalse($new_user->isActive(), 'New account is blocked until approved by an administrator.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user registration without email verification.
|
||||
*/
|
||||
public function testRegistrationWithoutEmailVerification(): void {
|
||||
$config = $this->config('user.settings');
|
||||
// Don't require email verification and allow registration by site visitors
|
||||
// without administrator approval.
|
||||
$config
|
||||
->set('verify_mail', FALSE)
|
||||
->set('register', UserInterface::REGISTER_VISITORS)
|
||||
->save();
|
||||
|
||||
$edit = [];
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
|
||||
// Try entering a mismatching password.
|
||||
$edit['pass[pass1]'] = '99999.0';
|
||||
$edit['pass[pass2]'] = '99999';
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('The specified passwords do not match.');
|
||||
|
||||
// Enter a correct password.
|
||||
$edit['pass[pass1]'] = $new_pass = $this->randomMachineName();
|
||||
$edit['pass[pass2]'] = $new_pass;
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->container->get('entity_type.manager')->getStorage('user')->resetCache();
|
||||
$accounts = $this->container->get('entity_type.manager')->getStorage('user')
|
||||
->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$this->assertNotNull($new_user, 'New account successfully created with matching passwords.');
|
||||
$this->assertSession()->pageTextContains('Registration successful. You are now logged in.');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Allow registration by site visitors, but require administrator approval.
|
||||
$config->set('register', UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
|
||||
$edit = [];
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
$edit['pass[pass1]'] = $pass = $this->randomMachineName();
|
||||
$edit['pass[pass2]'] = $pass;
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('Thank you for applying for an account. Your account is currently pending approval by the site administrator.');
|
||||
|
||||
// Try to log in before administrator approval.
|
||||
$auth = [
|
||||
'name' => $name,
|
||||
'pass' => $pass,
|
||||
];
|
||||
$this->drupalGet('user/login');
|
||||
$this->submitForm($auth, 'Log in');
|
||||
$this->assertSession()->pageTextContains('The username ' . $name . ' has not been activated or is blocked.');
|
||||
|
||||
// Activate the new account.
|
||||
$accounts = $this->container->get('entity_type.manager')->getStorage('user')
|
||||
->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = [
|
||||
'status' => 1,
|
||||
];
|
||||
$this->drupalGet('user/' . $new_user->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Log in after administrator approval.
|
||||
$this->drupalGet('user/login');
|
||||
$this->submitForm($auth, 'Log in');
|
||||
$this->assertSession()->pageTextContains('Member for');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user registration with email duplicates.
|
||||
*/
|
||||
public function testRegistrationEmailDuplicates(): void {
|
||||
// Don't require email verification and allow registration by site visitors
|
||||
// without administrator approval.
|
||||
$this->config('user.settings')
|
||||
->set('verify_mail', FALSE)
|
||||
->set('register', UserInterface::REGISTER_VISITORS)
|
||||
->save();
|
||||
|
||||
// Set up a user to check for duplicates.
|
||||
$duplicate_user = $this->drupalCreateUser();
|
||||
|
||||
$edit = [];
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$edit['mail'] = $duplicate_user->getEmail();
|
||||
|
||||
// Attempt to create a new account using an existing email address.
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('The email address ' . $duplicate_user->getEmail() . ' is already taken.');
|
||||
|
||||
// Attempt to bypass duplicate email registration validation by adding
|
||||
// spaces.
|
||||
$edit['mail'] = ' ' . $duplicate_user->getEmail() . ' ';
|
||||
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('The email address ' . $duplicate_user->getEmail() . ' is already taken.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that UUID isn't cached in form state on register form.
|
||||
*
|
||||
* This is a regression test for https://www.drupal.org/node/2500527 to ensure
|
||||
* that the form is not cached on GET requests.
|
||||
*/
|
||||
public function testUuidFormState(): void {
|
||||
\Drupal::service('module_installer')->install(['image']);
|
||||
|
||||
// Add a picture field in order to ensure that no form cache is written,
|
||||
// which breaks registration of more than 1 user every 6 hours.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'user_picture',
|
||||
'entity_type' => 'user',
|
||||
'type' => 'image',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => 'user_picture',
|
||||
'entity_type' => 'user',
|
||||
'bundle' => 'user',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$form_display = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'user',
|
||||
'bundle' => 'user',
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
$form_display->setComponent('user_picture', [
|
||||
'type' => 'image_image',
|
||||
]);
|
||||
$form_display->save();
|
||||
|
||||
// Don't require email verification and allow registration by site visitors
|
||||
// without administrator approval.
|
||||
$this->config('user.settings')
|
||||
->set('verify_mail', FALSE)
|
||||
->set('register', UserInterface::REGISTER_VISITORS)
|
||||
->save();
|
||||
|
||||
$edit = [];
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$edit['mail'] = $edit['name'] . '@example.com';
|
||||
$edit['pass[pass2]'] = $edit['pass[pass1]'] = $this->randomMachineName();
|
||||
|
||||
// Create one account.
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$user_storage = \Drupal::entityTypeManager()->getStorage('user');
|
||||
|
||||
$this->assertNotEmpty($user_storage->loadByProperties(['name' => $edit['name']]));
|
||||
$this->drupalLogout();
|
||||
|
||||
// Create a second account.
|
||||
$edit['name'] = $this->randomMachineName();
|
||||
$edit['mail'] = $edit['name'] . '@example.com';
|
||||
$edit['pass[pass2]'] = $edit['pass[pass1]'] = $this->randomMachineName();
|
||||
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->assertNotEmpty($user_storage->loadByProperties(['name' => $edit['name']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user registration with default values.
|
||||
*/
|
||||
public function testRegistrationDefaultValues(): void {
|
||||
// Don't require email verification and allow registration by site visitors
|
||||
// without administrator approval.
|
||||
$config_user_settings = $this->config('user.settings')
|
||||
->set('verify_mail', FALSE)
|
||||
->set('register', UserInterface::REGISTER_VISITORS)
|
||||
->save();
|
||||
|
||||
// Set the default timezone to Brussels.
|
||||
$config_system_date = $this->config('system.date')
|
||||
->set('timezone.user.configurable', 1)
|
||||
->set('timezone.default', 'Europe/Brussels')
|
||||
->save();
|
||||
|
||||
// Check the presence of expected cache tags.
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:user.settings');
|
||||
|
||||
$edit = [];
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
$edit['pass[pass1]'] = $new_pass = $this->randomMachineName();
|
||||
$edit['pass[pass2]'] = $new_pass;
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
|
||||
// Check user fields.
|
||||
$accounts = $this->container->get('entity_type.manager')->getStorage('user')
|
||||
->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$this->assertEquals($name, $new_user->getAccountName(), 'Username matches.');
|
||||
$this->assertEquals($mail, $new_user->getEmail(), 'Email address matches.');
|
||||
// Verify that the creation time is correct.
|
||||
$this->assertGreaterThan(\Drupal::time()->getRequestTime() - 20, $new_user->getCreatedTime());
|
||||
$this->assertEquals($config_user_settings->get('register') == UserInterface::REGISTER_VISITORS ? 1 : 0, $new_user->isActive(), 'Correct status field.');
|
||||
$this->assertEquals($config_system_date->get('timezone.default'), $new_user->getTimezone(), 'Correct time zone field.');
|
||||
$this->assertEquals(\Drupal::languageManager()->getDefaultLanguage()->getId(), $new_user->langcode->value, 'Correct language field.');
|
||||
$this->assertEquals(\Drupal::languageManager()->getDefaultLanguage()->getId(), $new_user->preferred_langcode->value, 'Correct preferred language field.');
|
||||
$this->assertEquals($mail, $new_user->init->value, 'Correct init field.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests username and email field constraints on user registration.
|
||||
*
|
||||
* @see \Drupal\user\Plugin\Validation\Constraint\UserNameUnique
|
||||
* @see \Drupal\user\Plugin\Validation\Constraint\UserMailUnique
|
||||
*/
|
||||
public function testUniqueFields(): void {
|
||||
$account = $this->drupalCreateUser();
|
||||
|
||||
$edit = ['mail' => 'test@example.com', 'name' => $account->getAccountName()];
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains("The username {$account->getAccountName()} is already taken.");
|
||||
|
||||
$edit = ['mail' => $account->getEmail(), 'name' => $this->randomString()];
|
||||
$this->drupalGet('user/register');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains("The email address {$account->getEmail()} is already taken.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Field API fields on user registration forms.
|
||||
*/
|
||||
public function testRegistrationWithUserFields(): void {
|
||||
// Create a field on 'user' entity type.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'test_user_field',
|
||||
'entity_type' => 'user',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'label' => 'Some user field',
|
||||
'bundle' => 'user',
|
||||
'required' => TRUE,
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('user', 'user')
|
||||
->setComponent('test_user_field', ['type' => 'test_field_widget'])
|
||||
->save();
|
||||
$display_repository->getFormDisplay('user', 'user', 'register')
|
||||
->save();
|
||||
|
||||
// Check that the field does not appear on the registration form.
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertSession()->pageTextNotContains($field->label());
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:core.entity_form_display.user.user.register');
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:user.settings');
|
||||
|
||||
// Have the field appear on the registration form.
|
||||
$display_repository->getFormDisplay('user', 'user', 'register')
|
||||
->setComponent('test_user_field', ['type' => 'test_field_widget'])
|
||||
->save();
|
||||
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertSession()->pageTextContains($field->label());
|
||||
$this->assertRegistrationFormCacheTagsWithUserFields();
|
||||
|
||||
// Check that validation errors are correctly reported.
|
||||
$edit = [];
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
// Missing input in required field.
|
||||
$edit['test_user_field[0][value]'] = '';
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertRegistrationFormCacheTagsWithUserFields();
|
||||
$this->assertSession()->pageTextContains("{$field->label()} field is required.");
|
||||
// Invalid input.
|
||||
$edit['test_user_field[0][value]'] = '-1';
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertRegistrationFormCacheTagsWithUserFields();
|
||||
$this->assertSession()->pageTextContains("{$field->label()} does not accept the value -1.");
|
||||
|
||||
// Submit with valid data.
|
||||
$value = rand(1, 255);
|
||||
$edit['test_user_field[0][value]'] = $value;
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
// Check user fields.
|
||||
$accounts = $this->container->get('entity_type.manager')->getStorage('user')
|
||||
->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$this->assertEquals($value, $new_user->test_user_field->value, 'The field value was correctly saved.');
|
||||
|
||||
// Check that the 'add more' button works.
|
||||
$field_storage->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$field_storage->save();
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertRegistrationFormCacheTagsWithUserFields();
|
||||
// Add two inputs.
|
||||
$value = rand(1, 255);
|
||||
$edit = [];
|
||||
$edit['test_user_field[0][value]'] = $value;
|
||||
$this->submitForm($edit, 'Add another item');
|
||||
$this->submitForm($edit, 'Add another item');
|
||||
// Submit with three values.
|
||||
$edit['test_user_field[1][value]'] = $value + 1;
|
||||
$edit['test_user_field[2][value]'] = $value + 2;
|
||||
$edit['name'] = $name = $this->randomMachineName();
|
||||
$edit['mail'] = $mail = $edit['name'] . '@example.com';
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
// Check user fields.
|
||||
$accounts = $this->container->get('entity_type.manager')->getStorage('user')
|
||||
->loadByProperties(['name' => $name, 'mail' => $mail]);
|
||||
$new_user = reset($accounts);
|
||||
$this->assertEquals($value, $new_user->test_user_field[0]->value, 'The field value was correctly saved.');
|
||||
$this->assertEquals($value + 1, $new_user->test_user_field[1]->value, 'The field value was correctly saved.');
|
||||
$this->assertEquals($value + 2, $new_user->test_user_field[2]->value, 'The field value was correctly saved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the presence of cache tags on registration form with user fields.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertRegistrationFormCacheTagsWithUserFields(): void {
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:core.entity_form_display.user.user.register');
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:field.field.user.user.test_user_field');
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:field.storage.user.test_user_field');
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:user.settings');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the requirements checks of the User module.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserRequirementsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that the requirements check can detect a missing anonymous user.
|
||||
*/
|
||||
public function testAnonymousUser(): void {
|
||||
// Remove the anonymous user.
|
||||
\Drupal::database()
|
||||
->delete('users')
|
||||
->condition('uid', 0)
|
||||
->execute();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
]));
|
||||
$this->drupalGet('/admin/reports/status');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains("The anonymous user does not exist.");
|
||||
}
|
||||
|
||||
}
|
||||
143
web/core/modules/user/tests/src/Functional/UserRoleAdminTest.php
Normal file
143
web/core/modules/user/tests/src/Functional/UserRoleAdminTest.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests adding, editing and deleting user roles and changing role weights.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserRoleAdminTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* User with admin privileges.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer permissions',
|
||||
'administer users',
|
||||
]);
|
||||
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'test_role_admin_test_local_tasks_block']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding, renaming and deleting roles.
|
||||
*/
|
||||
public function testRoleAdministration(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
|
||||
// Test presence of tab.
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->assertSession()->elementsCount('xpath', '//div[@id="block-test-role-admin-test-local-tasks-block"]/ul/li/a[contains(., "Roles")]', 1);
|
||||
|
||||
// Test adding a role. (In doing so, we use a role name that happens to
|
||||
// correspond to an integer, to test that the role administration pages
|
||||
// correctly distinguish between role names and IDs.)
|
||||
$role_name = '123';
|
||||
$edit = ['label' => $role_name, 'id' => $role_name];
|
||||
$this->drupalGet('admin/people/roles/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Role 123 has been added.");
|
||||
$role = Role::load($role_name);
|
||||
$this->assertIsObject($role);
|
||||
|
||||
// Check that the role was created in site default language.
|
||||
$this->assertEquals($default_langcode, $role->language()->getId());
|
||||
|
||||
// Verify permissions local task can be accessed when editing a role.
|
||||
$this->drupalGet("admin/people/roles/manage/{$role->id()}");
|
||||
$local_tasks_block = $this->assertSession()->elementExists('css', '#block-test-role-admin-test-local-tasks-block');
|
||||
$local_tasks_block->clickLink('Permissions');
|
||||
$this->assertSession()->fieldExists("{$role->id()}[change own username]");
|
||||
|
||||
// Try adding a duplicate role.
|
||||
$this->drupalGet('admin/people/roles/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The machine-readable name is already in use. It must be unique.");
|
||||
|
||||
// Test renaming a role.
|
||||
$role_name = '456';
|
||||
$edit = ['label' => $role_name];
|
||||
$this->drupalGet("admin/people/roles/manage/{$role->id()}");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Role {$role_name} has been updated.");
|
||||
$new_role = Role::load($role->id());
|
||||
$this->assertEquals($role_name, $new_role->label(), 'The role name has been successfully changed.');
|
||||
|
||||
// Test deleting a role.
|
||||
$this->drupalGet("admin/people/roles/manage/{$role->id()}");
|
||||
$this->clickLink('Delete');
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains("Role {$role_name} has been deleted.");
|
||||
$this->assertSession()->linkByHrefNotExists("admin/people/roles/manage/{$role->id()}", 'Role edit link removed.');
|
||||
$this->assertNull(Role::load($role->id()), 'A deleted role can no longer be loaded.');
|
||||
|
||||
// Make sure that the system-defined roles can be edited via the user
|
||||
// interface.
|
||||
$this->drupalGet('admin/people/roles/manage/' . RoleInterface::ANONYMOUS_ID);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('Delete role');
|
||||
$this->drupalGet('admin/people/roles/manage/' . RoleInterface::AUTHENTICATED_ID);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('Delete role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user role weight change operation and ordering.
|
||||
*/
|
||||
public function testRoleWeightOrdering(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$roles = Role::loadMultiple();
|
||||
$weight = count($roles);
|
||||
$new_role_weights = [];
|
||||
$saved_rids = [];
|
||||
|
||||
// Change the role weights to make the roles in reverse order.
|
||||
$edit = [];
|
||||
foreach ($roles as $role) {
|
||||
$edit['entities[' . $role->id() . '][weight]'] = $weight;
|
||||
$new_role_weights[$role->id()] = $weight;
|
||||
$saved_rids[] = $role->id();
|
||||
$weight--;
|
||||
}
|
||||
$this->drupalGet('admin/people/roles');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The role settings have been updated.');
|
||||
|
||||
// Load up the user roles with the new weights.
|
||||
$roles = Role::loadMultiple();
|
||||
$rids = [];
|
||||
// Test that the role weights have been correctly saved.
|
||||
foreach ($roles as $role) {
|
||||
$this->assertEquals($role->getWeight(), $new_role_weights[$role->id()]);
|
||||
$rids[] = $role->id();
|
||||
}
|
||||
// The order of the roles should be reversed.
|
||||
$this->assertSame(array_reverse($saved_rids), $rids);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that users can be assigned and unassigned roles.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserRolesAssignmentTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer permissions',
|
||||
'administer users',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Test that user can be assigned role and that the role can be removed again.
|
||||
*/
|
||||
public function testAssignAndRemoveRole(): void {
|
||||
$rid = $this->drupalCreateRole(['administer users']);
|
||||
$account = $this->drupalCreateUser();
|
||||
|
||||
// Assign the role to the user.
|
||||
$this->drupalGet('user/' . $account->id() . '/edit');
|
||||
$this->submitForm(["roles[{$rid}]" => $rid], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->assertSession()->checkboxChecked('edit-roles-' . $rid);
|
||||
$this->userLoadAndCheckRoleAssigned($account, $rid);
|
||||
|
||||
// Remove the role from the user.
|
||||
$this->drupalGet('user/' . $account->id() . '/edit');
|
||||
$this->submitForm(["roles[{$rid}]" => FALSE], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->assertSession()->checkboxNotChecked('edit-roles-' . $rid);
|
||||
$this->userLoadAndCheckRoleAssigned($account, $rid, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests assigning a role at user creation and removing the role.
|
||||
*/
|
||||
public function testCreateUserWithRole(): void {
|
||||
$rid = $this->drupalCreateRole(['administer users']);
|
||||
// Create a new user and add the role at the same time.
|
||||
$edit = [
|
||||
'name' => $this->randomMachineName(),
|
||||
'mail' => $this->randomMachineName() . '@example.com',
|
||||
'pass[pass1]' => $pass = $this->randomString(),
|
||||
'pass[pass2]' => $pass,
|
||||
"roles[$rid]" => $rid,
|
||||
];
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('Created a new user account for ' . $edit['name'] . '.');
|
||||
// Get the newly added user.
|
||||
$account = user_load_by_name($edit['name']);
|
||||
|
||||
$this->drupalGet('user/' . $account->id() . '/edit');
|
||||
$this->assertSession()->checkboxChecked('edit-roles-' . $rid);
|
||||
$this->userLoadAndCheckRoleAssigned($account, $rid);
|
||||
|
||||
// Remove the role again.
|
||||
$this->drupalGet('user/' . $account->id() . '/edit');
|
||||
$this->submitForm(["roles[{$rid}]" => FALSE], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
$this->assertSession()->checkboxNotChecked('edit-roles-' . $rid);
|
||||
$this->userLoadAndCheckRoleAssigned($account, $rid, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check role on user object.
|
||||
*
|
||||
* @param object $account
|
||||
* The user account to check.
|
||||
* @param string $rid
|
||||
* The role ID to search for.
|
||||
* @param bool $is_assigned
|
||||
* (optional) Whether to assert that $rid exists (TRUE) or not (FALSE).
|
||||
* Defaults to TRUE.
|
||||
*/
|
||||
private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE): void {
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
$user_storage->resetCache([$account->id()]);
|
||||
$account = $user_storage->load($account->id());
|
||||
if ($is_assigned) {
|
||||
$this->assertContains($rid, $account->getRoles());
|
||||
}
|
||||
else {
|
||||
$this->assertNotContains($rid, $account->getRoles());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
147
web/core/modules/user/tests/src/Functional/UserSearchTest.php
Normal file
147
web/core/modules/user/tests/src/Functional/UserSearchTest.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verifies that sensitive information is hidden from unauthorized users.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserSearchTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['search'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests various user search functionalities and permission-based restrictions.
|
||||
*/
|
||||
public function testUserSearch(): void {
|
||||
// Verify that a user without 'administer users' permission cannot search
|
||||
// for users by email address. Additionally, ensure that the username has a
|
||||
// plus sign to ensure searching works with that.
|
||||
$user1 = $this->drupalCreateUser([
|
||||
'access user profiles',
|
||||
'search content',
|
||||
], "foo+bar");
|
||||
$this->drupalLogin($user1);
|
||||
$keys = $user1->getEmail();
|
||||
$edit = ['keys' => $keys];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains('Your search yielded no results.');
|
||||
$this->assertSession()->pageTextContains('no results');
|
||||
|
||||
// Verify that a non-matching query gives an appropriate message.
|
||||
$keys = 'nomatch';
|
||||
$edit = ['keys' => $keys];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains('no results');
|
||||
|
||||
// Verify that a user with search permission can search for users by name.
|
||||
$keys = $user1->getAccountName();
|
||||
$edit = ['keys' => $keys];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->linkExists($keys, 0, 'Search by username worked for non-admin user');
|
||||
|
||||
// Verify that searching by sub-string works too.
|
||||
$subkey = substr($keys, 1, 5);
|
||||
$edit = ['keys' => $subkey];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->linkExists($keys, 0, 'Search by username substring worked for non-admin user');
|
||||
|
||||
// Verify that wildcard search works.
|
||||
$subkey = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
|
||||
$edit = ['keys' => $subkey];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->linkExists($keys, 0, 'Search with wildcard worked for non-admin user');
|
||||
|
||||
// Verify that a user with 'administer users' permission can search by
|
||||
// email.
|
||||
$user2 = $this->drupalCreateUser([
|
||||
'administer users',
|
||||
'access user profiles',
|
||||
'search content',
|
||||
]);
|
||||
$this->drupalLogin($user2);
|
||||
$keys = $user2->getEmail();
|
||||
$edit = ['keys' => $keys];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains($keys);
|
||||
$this->assertSession()->pageTextContains($user2->getAccountName());
|
||||
|
||||
// Verify that a substring works too for email.
|
||||
$subkey = substr($keys, 1, 5);
|
||||
$edit = ['keys' => $subkey];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains($keys);
|
||||
$this->assertSession()->pageTextContains($user2->getAccountName());
|
||||
|
||||
// Verify that wildcard search works for email
|
||||
$subkey = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
|
||||
$edit = ['keys' => $subkey];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains($user2->getAccountName());
|
||||
|
||||
// Verify that if they search by user name, they see email address too.
|
||||
$keys = $user1->getAccountName();
|
||||
$edit = ['keys' => $keys];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains($keys);
|
||||
$this->assertSession()->pageTextContains($user1->getEmail());
|
||||
|
||||
// Create a blocked user.
|
||||
$blocked_user = $this->drupalCreateUser();
|
||||
$blocked_user->block();
|
||||
$blocked_user->save();
|
||||
|
||||
// Verify that users with "administer users" permissions can see blocked
|
||||
// accounts in search results.
|
||||
$edit = ['keys' => $blocked_user->getAccountName()];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains($blocked_user->getAccountName());
|
||||
|
||||
// Verify that users without "administer users" permissions do not see
|
||||
// blocked accounts in search results.
|
||||
$this->drupalLogin($user1);
|
||||
$edit = ['keys' => $blocked_user->getAccountName()];
|
||||
$this->drupalGet('search/user');
|
||||
$this->submitForm($edit, 'Search');
|
||||
$this->assertSession()->pageTextContains('Your search yielded no results.');
|
||||
|
||||
// Ensure that a user without access to user profiles cannot access the
|
||||
// user search page.
|
||||
$user3 = $this->drupalCreateUser(['search content']);
|
||||
$this->drupalLogin($user3);
|
||||
$this->drupalGet('search/user');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Ensure that a user without search permission cannot access the user
|
||||
// search page.
|
||||
$user4 = $this->drupalCreateUser(['access user profiles']);
|
||||
$this->drupalLogin($user4);
|
||||
$this->drupalGet('search/user');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test 'sub-admin' account with permission to edit some users but without 'administer users' permission.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserSubAdminTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user_access_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests create and cancel forms as 'sub-admin'.
|
||||
*/
|
||||
public function testSubAdmin(): void {
|
||||
$user = $this->drupalCreateUser(['sub-admin']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Test that the create user page has admin fields.
|
||||
$this->drupalGet('admin/people/create');
|
||||
$this->assertSession()->fieldExists("edit-name");
|
||||
$this->assertSession()->fieldExists("edit-notify");
|
||||
|
||||
// Not 'status' or 'roles' as they require extra permission.
|
||||
$this->assertSession()->fieldNotExists("edit-status-0");
|
||||
$this->assertSession()->fieldNotExists("edit-role");
|
||||
|
||||
// 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->drupalGet('admin/people/create');
|
||||
$this->submitForm($edit, 'Create new account');
|
||||
$this->assertSession()->pageTextContains('Created a new user account for ' . $edit['name'] . '. No email has been sent.');
|
||||
|
||||
// Test that the cancel user page has admin fields.
|
||||
$cancel_user = $this->createUser();
|
||||
$this->drupalGet('user/' . $cancel_user->id() . '/cancel');
|
||||
$this->assertSession()->responseContains('Are you sure you want to cancel the account ' . $cancel_user->getAccountName() . '?');
|
||||
$this->assertSession()->responseContains('Disable the account and keep its content.');
|
||||
|
||||
// Test that cancel confirmation gives an admin style message.
|
||||
$this->submitForm([], 'Confirm');
|
||||
$this->assertSession()->pageTextContains('Account ' . $cancel_user->getAccountName() . ' has been disabled.');
|
||||
|
||||
// Repeat with permission to select account cancellation method.
|
||||
$user
|
||||
->addRole($this->drupalCreateRole(['select account cancellation method']))
|
||||
->save();
|
||||
$cancel_user = $this->createUser();
|
||||
$this->drupalGet('user/' . $cancel_user->id() . '/cancel');
|
||||
$this->assertSession()->pageTextContains('Cancellation method');
|
||||
}
|
||||
|
||||
}
|
||||
111
web/core/modules/user/tests/src/Functional/UserTimeZoneTest.php
Normal file
111
web/core/modules/user/tests/src/Functional/UserTimeZoneTest.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Set a user time zone and verify that dates are displayed in local time.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserTimeZoneTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'system_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the display of dates and time when user-configurable time zones are set.
|
||||
*/
|
||||
public function testUserTimeZone(): void {
|
||||
// Setup date/time settings for Los Angeles time.
|
||||
$this->config('system.date')
|
||||
->set('timezone.user.configurable', 1)
|
||||
->set('timezone.default', 'America/Los_Angeles')
|
||||
->save();
|
||||
|
||||
// Load the 'medium' date format, which is the default for node creation
|
||||
// time, and override it. Since we are testing time zones with Daylight
|
||||
// Saving Time, and need to future proof against changes to the zoneinfo
|
||||
// database, we choose the 'I' format placeholder instead of a
|
||||
// human-readable zone name. With 'I', a 1 means the date is in DST, and 0
|
||||
// if not.
|
||||
DateFormat::load('medium')
|
||||
->setPattern('Y-m-d H:i I')
|
||||
->save();
|
||||
|
||||
// Create a user account and login.
|
||||
$web_user = $this->drupalCreateUser();
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create some nodes with different authored-on dates.
|
||||
// Two dates in PST (winter time):
|
||||
$date1 = '2007-03-09 21:00:00 -0800';
|
||||
$date2 = '2007-03-11 01:00:00 -0800';
|
||||
// One date in PDT (summer time):
|
||||
$date3 = '2007-03-20 21:00:00 -0700';
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
$node1 = $this->drupalCreateNode(['created' => strtotime($date1), 'type' => 'article']);
|
||||
$node2 = $this->drupalCreateNode(['created' => strtotime($date2), 'type' => 'article']);
|
||||
$node3 = $this->drupalCreateNode(['created' => strtotime($date3), 'type' => 'article']);
|
||||
|
||||
// Confirm date format and time zone.
|
||||
$this->drupalGet('node/' . $node1->id());
|
||||
// Date should be PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-09 21:00 0');
|
||||
$this->drupalGet('node/' . $node2->id());
|
||||
// Date should be PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-11 01:00 0');
|
||||
$this->drupalGet('node/' . $node3->id());
|
||||
// Date should be PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-20 21:00 1');
|
||||
|
||||
// Change user time zone to Santiago time.
|
||||
$edit = [];
|
||||
$edit['mail'] = $web_user->getEmail();
|
||||
$edit['timezone'] = 'America/Santiago';
|
||||
$this->drupalGet("user/" . $web_user->id() . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
|
||||
// Confirm date format and time zone.
|
||||
$this->drupalGet('node/' . $node1->id());
|
||||
// Date should be Chile summer time, five hours ahead of PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-10 02:00 1');
|
||||
$this->drupalGet('node/' . $node2->id());
|
||||
// Date should be Chile time, four hours ahead of PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-11 05:00 0');
|
||||
$this->drupalGet('node/' . $node3->id());
|
||||
// Date should be Chile time, three hours ahead of PDT.
|
||||
$this->assertSession()->pageTextContains('2007-03-21 00:00 0');
|
||||
|
||||
// Ensure that anonymous users also use the default timezone.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('node/' . $node1->id());
|
||||
// Date should be PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-09 21:00 0');
|
||||
$this->drupalGet('node/' . $node2->id());
|
||||
// Date should be PST.
|
||||
$this->assertSession()->pageTextContains('2007-03-11 01:00 0');
|
||||
$this->drupalGet('node/' . $node3->id());
|
||||
// Date should be PDT.
|
||||
$this->assertSession()->pageTextContains('2007-03-20 21:00 1');
|
||||
|
||||
// Format a date without accessing the current user at all and
|
||||
// ensure that it uses the default timezone.
|
||||
$this->drupalGet('/system-test/date');
|
||||
// Date should be PST.
|
||||
$this->assertSession()->pageTextContains('2016-01-13 08:29 0');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the replacement of user tokens.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserTokenReplaceTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'user_hooks_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected bool $useOneTimeLoginLinks = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a user, then tests the tokens generated from it.
|
||||
*/
|
||||
public function testUserTokenReplacement(): void {
|
||||
$token_service = \Drupal::token();
|
||||
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
|
||||
$url_options = [
|
||||
'absolute' => TRUE,
|
||||
'language' => $language_interface,
|
||||
];
|
||||
|
||||
\Drupal::keyValue('user_hooks_test')->set('user_format_name_alter', TRUE);
|
||||
\Drupal::keyValue('user_hooks_test')->set('user_format_name_alter_safe', TRUE);
|
||||
|
||||
// Create two users and log them in one after another.
|
||||
$user1 = $this->drupalCreateUser([]);
|
||||
$user2 = $this->drupalCreateUser([]);
|
||||
$this->drupalLogin($user1);
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($user2);
|
||||
|
||||
$account = User::load($user1->id());
|
||||
$global_account = User::load(\Drupal::currentUser()->id());
|
||||
|
||||
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
|
||||
$date_formatter = $this->container->get('date.formatter');
|
||||
|
||||
// Generate and test tokens.
|
||||
$tests = [];
|
||||
$tests['[user:uid]'] = $account->id();
|
||||
$tests['[user:uuid]'] = $account->uuid();
|
||||
$tests['[user:name]'] = $account->getAccountName();
|
||||
$tests['[user:account-name]'] = $account->getAccountName();
|
||||
$tests['[user:display-name]'] = $account->getDisplayName();
|
||||
$tests['[user:mail]'] = $account->getEmail();
|
||||
$tests['[user:url]'] = $account->toUrl('canonical', $url_options)->toString();
|
||||
$tests['[user:edit-url]'] = $account->toUrl('edit-form', $url_options)->toString();
|
||||
$tests['[user:last-login]'] = $date_formatter->format($account->getLastLoginTime(), 'medium', '', NULL, $language_interface->getId());
|
||||
$tests['[user:last-login:short]'] = $date_formatter->format($account->getLastLoginTime(), 'short', '', NULL, $language_interface->getId());
|
||||
$tests['[user:created]'] = $date_formatter->format($account->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
|
||||
$tests['[user:created:short]'] = $date_formatter->format($account->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
|
||||
$tests['[current-user:name]'] = $global_account->getAccountName();
|
||||
$tests['[current-user:account-name]'] = $global_account->getAccountName();
|
||||
$tests['[current-user:display-name]'] = $global_account->getDisplayName();
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($account);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[user:uid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:uuid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:account-name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:display-name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:mail]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:url]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:edit-url]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
// This test runs with the Language module enabled, which means config is
|
||||
// overridden by LanguageConfigFactoryOverride (to provide translations of
|
||||
// config). This causes the interface language cache context to be added for
|
||||
// config entities. The four next tokens use DateFormat Config entities, and
|
||||
// therefore have the interface language cache context.
|
||||
$bubbleable_metadata->addCacheContexts(['languages:language_interface']);
|
||||
$metadata_tests['[user:last-login]'] = $bubbleable_metadata->addCacheTags(['rendered']);
|
||||
$metadata_tests['[user:last-login:short]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[user:created]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[user:created:short]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[current-user:name]'] = $base_bubbleable_metadata->merge(BubbleableMetadata::createFromObject($global_account)->addCacheContexts(['user']));
|
||||
$metadata_tests['[current-user:account-name]'] = $base_bubbleable_metadata->merge(BubbleableMetadata::createFromObject($global_account)->addCacheContexts(['user']));
|
||||
$metadata_tests['[current-user:display-name]'] = $base_bubbleable_metadata->merge(BubbleableMetadata::createFromObject($global_account)->addCacheContexts(['user']));
|
||||
|
||||
// Test to make sure that we generated something for each token.
|
||||
$this->assertNotContains(0, array_map('strlen', $tests), 'No empty tokens generated.');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, ['user' => $account], ['langcode' => $language_interface->getId()], $bubbleable_metadata);
|
||||
$this->assertSame((string) $expected, (string) $output, "Failed test case: {$input}");
|
||||
$this->assertEquals($metadata_tests[$input], $bubbleable_metadata);
|
||||
}
|
||||
|
||||
// Generate tokens for the anonymous user.
|
||||
$anonymous_user = User::load(0);
|
||||
$tests = [];
|
||||
$tests['[user:uid]'] = 'not yet assigned';
|
||||
$tests['[user:uuid]'] = $anonymous_user->uuid();
|
||||
$tests['[user:display-name]'] = $anonymous_user->getDisplayName();
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($anonymous_user);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[user:uid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:uuid]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
|
||||
$metadata_tests['[user:display-name]'] = $bubbleable_metadata;
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, ['user' => $anonymous_user], ['langcode' => $language_interface->getId()], $bubbleable_metadata);
|
||||
$this->assertSame((string) $expected, (string) $output, "Failed test case: {$input}");
|
||||
$this->assertEquals($metadata_tests[$input], $bubbleable_metadata);
|
||||
}
|
||||
|
||||
// Generate login and cancel link.
|
||||
$tests = [];
|
||||
$tests['[user:one-time-login-url]'] = user_pass_reset_url($account);
|
||||
$tests['[user:cancel-url]'] = user_cancel_url($account);
|
||||
|
||||
// Generate tokens with interface language.
|
||||
$link = Url::fromRoute('user.page', [], ['absolute' => TRUE])->toString();
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, ['user' => $account], ['langcode' => $language_interface->getId(), 'callback' => 'user_mail_tokens', 'clear' => TRUE]);
|
||||
$this->assertStringStartsWith($link, $output, 'Generated URL is in interface language.');
|
||||
}
|
||||
|
||||
// Generate tokens with the user's preferred language.
|
||||
$account->preferred_langcode = 'de';
|
||||
$account->save();
|
||||
$link = Url::fromRoute('user.page', [], ['language' => \Drupal::languageManager()->getLanguage($account->getPreferredLangcode()), 'absolute' => TRUE])->toString();
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, ['user' => $account], ['callback' => 'user_mail_tokens', 'clear' => TRUE]);
|
||||
$this->assertStringStartsWith($link, $output, "Generated URL is in the user's preferred language.");
|
||||
}
|
||||
|
||||
// Generate tokens with one specific language.
|
||||
$link = Url::fromRoute('user.page', [], ['language' => \Drupal::languageManager()->getLanguage('de'), 'absolute' => TRUE])->toString();
|
||||
foreach ($tests as $input => $expected) {
|
||||
foreach ([$user1, $user2] as $account) {
|
||||
$output = $token_service->replace($input, ['user' => $account], ['langcode' => 'de', 'callback' => 'user_mail_tokens', 'clear' => TRUE]);
|
||||
$this->assertStringStartsWith($link, $output, "Generated URL in the requested language.");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate user display name tokens when safe markup is returned.
|
||||
\Drupal::keyValue('user_hooks_test')->set('user_format_name_alter_safe', TRUE);
|
||||
$input = '[user:display-name] [current-user:display-name]';
|
||||
$expected = "<em>{$user1->id()}</em> <em>{$user2->id()}</em>";
|
||||
$output = $token_service->replace($input, ['user' => $user1]);
|
||||
$this->assertSame($expected, (string) $output);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional;
|
||||
|
||||
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
|
||||
|
||||
/**
|
||||
* Tests the User Translation UI.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserTranslationUITest extends ContentTranslationUITestBase {
|
||||
|
||||
/**
|
||||
* The user name of the test user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultCacheContexts = [
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'url.query_args:_wrapper_format',
|
||||
'user.permissions',
|
||||
'url.site',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'user',
|
||||
'views',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->entityTypeId = 'user';
|
||||
$this->testLanguageSelector = FALSE;
|
||||
$this->name = $this->randomMachineName();
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('user')->resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTranslatorPermissions(): array {
|
||||
return array_merge(parent::getTranslatorPermissions(), ['administer users']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNewEntityValues($langcode) {
|
||||
// User name is not translatable hence we use a fixed value.
|
||||
return ['name' => $this->name] + parent::getNewEntityValues($langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doTestTranslationEdit(): void {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$languages = $this->container->get('language_manager')->getLanguages();
|
||||
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
// We only want to test the title for non-english translations.
|
||||
if ($langcode != 'en') {
|
||||
$options = ['language' => $languages[$langcode]];
|
||||
$url = $entity->toUrl('edit-form', $options);
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains("{$entity->getTranslation($langcode)->label()} [{$languages[$langcode]->getName()} translation]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translated user deletion.
|
||||
*/
|
||||
public function testTranslatedUserDeletion(): void {
|
||||
$this->drupalLogin($this->administrator);
|
||||
$entity_id = $this->createEntity($this->getNewEntityValues('en'), 'en');
|
||||
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->load($entity_id);
|
||||
$translated_entity = $entity->addTranslation('fr');
|
||||
$translated_entity->save();
|
||||
|
||||
$url = $entity->toUrl(
|
||||
'edit-form',
|
||||
['language' => $this->container->get('language_manager')->getLanguage('en')]
|
||||
);
|
||||
$this->drupalGet($url);
|
||||
$this->clickLink('Cancel account');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\user\Plugin\views\access\Role;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests views role access plugin.
|
||||
*
|
||||
* @group user
|
||||
* @see \Drupal\user\Plugin\views\access\Role
|
||||
*/
|
||||
class AccessRoleTest extends AccessTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_access_role'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['user_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests role access plugin.
|
||||
*/
|
||||
public function testAccessRole(): void {
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = \Drupal::entityTypeManager()->getStorage('view')->load('test_access_role');
|
||||
$display = &$view->getDisplay('default');
|
||||
$display['display_options']['access']['options']['role'] = [
|
||||
$this->normalRole => $this->normalRole,
|
||||
];
|
||||
$view->save();
|
||||
$this->container->get('router.builder')->rebuildIfNeeded();
|
||||
$expected = [
|
||||
'config' => ['user.role.' . $this->normalRole],
|
||||
'module' => ['user', 'views_test_data'],
|
||||
];
|
||||
$this->assertSame($expected, $view->calculateDependencies()->getDependencies());
|
||||
|
||||
$executable = Views::executableFactory()->get($view);
|
||||
$executable->setDisplay('page_1');
|
||||
|
||||
$access_plugin = $executable->display_handler->getPlugin('access');
|
||||
$this->assertInstanceOf(Role::class, $access_plugin);
|
||||
|
||||
// Test the access() method on the access plugin.
|
||||
$this->assertFalse($executable->display_handler->access($this->webUser));
|
||||
$this->assertTrue($executable->display_handler->access($this->normalUser));
|
||||
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('test-role');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertCacheContext('user.roles');
|
||||
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->drupalGet('test-role');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertCacheContext('user.roles');
|
||||
|
||||
// Test allowing multiple roles.
|
||||
$view = Views::getView('test_access_role')->storage;
|
||||
$display = &$view->getDisplay('default');
|
||||
$display['display_options']['access']['options']['role'] = [
|
||||
$this->normalRole => $this->normalRole,
|
||||
'anonymous' => 'anonymous',
|
||||
];
|
||||
$view->save();
|
||||
$this->container->get('router.builder')->rebuildIfNeeded();
|
||||
|
||||
// Ensure that the list of roles is sorted correctly, if the generated role
|
||||
// ID comes before 'anonymous', see https://www.drupal.org/node/2398259.
|
||||
$roles = ['user.role.anonymous', 'user.role.' . $this->normalRole];
|
||||
sort($roles);
|
||||
$expected = [
|
||||
'config' => $roles,
|
||||
'module' => ['user', 'views_test_data'],
|
||||
];
|
||||
$this->assertSame($expected, $view->calculateDependencies()->getDependencies());
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('test-role');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertCacheContext('user.roles');
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('test-role');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertCacheContext('user.roles');
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->drupalGet('test-role');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertCacheContext('user.roles');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests access on render caching.
|
||||
*/
|
||||
public function testRenderCaching(): void {
|
||||
$view = Views::getView('test_access_role');
|
||||
$display = &$view->storage->getDisplay('default');
|
||||
$display['display_options']['cache'] = [
|
||||
'type' => 'tag',
|
||||
];
|
||||
$display['display_options']['access']['options']['role'] = [
|
||||
$this->normalRole => $this->normalRole,
|
||||
];
|
||||
$view->save();
|
||||
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
/** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
|
||||
$account_switcher = \Drupal::service('account_switcher');
|
||||
|
||||
// First access as user with access.
|
||||
$build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
|
||||
$account_switcher->switchTo($this->normalUser);
|
||||
$result = $renderer->renderInIsolation($build);
|
||||
$this->assertContains('user.roles', $build['#cache']['contexts']);
|
||||
$this->assertEquals(['config:views.view.test_access_role'], $build['#cache']['tags']);
|
||||
$this->assertEquals(Cache::PERMANENT, $build['#cache']['max-age']);
|
||||
$this->assertNotSame('', $result);
|
||||
|
||||
// Then without access.
|
||||
$build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
|
||||
$account_switcher->switchTo($this->webUser);
|
||||
$result = $renderer->renderInIsolation($build);
|
||||
// @todo Fix this in https://www.drupal.org/node/2551037,
|
||||
// DisplayPluginBase::applyDisplayCacheabilityMetadata() is not invoked when
|
||||
// using buildBasicRenderable() and a Views access plugin returns FALSE.
|
||||
// $this->assertContains('user.roles', $build['#cache']['contexts']);
|
||||
// $this->assertEquals([], $build['#cache']['tags']);
|
||||
$this->assertEquals(Cache::PERMANENT, $build['#cache']['max-age']);
|
||||
$this->assertEquals('', $result);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
/**
|
||||
* A common test base class for the user access plugin tests.
|
||||
*/
|
||||
abstract class AccessTestBase extends UserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block'];
|
||||
|
||||
/**
|
||||
* Contains a user object that has no special permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* Contains a user object that has the 'views_test_data test permission'.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $normalUser;
|
||||
|
||||
/**
|
||||
* Contains a role ID that is used by the webUser.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $webRole;
|
||||
|
||||
/**
|
||||
* Contains a role ID that is used by the normalUser.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $normalRole;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = []): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
$this->enableViewsTestModule();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser();
|
||||
$roles = $this->webUser->getRoles();
|
||||
$this->webRole = $roles[0];
|
||||
|
||||
$this->normalRole = $this->drupalCreateRole([]);
|
||||
$this->normalUser = $this->drupalCreateUser([
|
||||
'views_test_data test permission',
|
||||
]);
|
||||
$this->normalUser->addRole($this->normalRole)->save();
|
||||
// @todo when all the plugin information is cached make a reset function and
|
||||
// call it here.
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests if entity access is respected on a user bulk form.
|
||||
*
|
||||
* @group user
|
||||
* @see \Drupal\user\Plugin\views\field\UserBulkForm
|
||||
* @see \Drupal\user\Tests\Views\BulkFormTest
|
||||
*/
|
||||
class BulkFormAccessTest extends UserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user_access_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_user_bulk_form'];
|
||||
|
||||
/**
|
||||
* Tests if users that may not be edited, can not be edited in bulk.
|
||||
*/
|
||||
public function testUserEditAccess(): void {
|
||||
// Create an authenticated user.
|
||||
$no_edit_user = $this->drupalCreateUser([], 'no_edit');
|
||||
// Ensure this account is not blocked.
|
||||
$this->assertFalse($no_edit_user->isBlocked(), 'The user is not blocked.');
|
||||
|
||||
// Log in as user admin.
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Ensure that the account "no_edit" can not be edited.
|
||||
$this->drupalGet('user/' . $no_edit_user->id() . '/edit');
|
||||
$this->assertFalse($no_edit_user->access('update', $admin_user));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Test blocking the account "no_edit".
|
||||
$edit = [
|
||||
'user_bulk_form[' . ($no_edit_user->id() - 1) . ']' => TRUE,
|
||||
'action' => 'user_block_user_action',
|
||||
];
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->assertSession()->pageTextContains("No access to execute Block the selected user(s) on the User {$no_edit_user->label()}.");
|
||||
|
||||
// Re-load the account "no_edit" and ensure it is not blocked.
|
||||
$no_edit_user = User::load($no_edit_user->id());
|
||||
$this->assertFalse($no_edit_user->isBlocked(), 'The user is not blocked.');
|
||||
|
||||
// Create a normal user which can be edited by the admin user
|
||||
$normal_user = $this->drupalCreateUser();
|
||||
$this->assertTrue($normal_user->access('update', $admin_user));
|
||||
|
||||
$edit = [
|
||||
'user_bulk_form[' . ($normal_user->id() - 1) . ']' => TRUE,
|
||||
'action' => 'user_block_user_action',
|
||||
];
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
|
||||
$normal_user = User::load($normal_user->id());
|
||||
$this->assertTrue($normal_user->isBlocked(), 'The user is blocked.');
|
||||
|
||||
// Log in as user without the 'administer users' permission.
|
||||
$this->drupalLogin($this->drupalCreateUser());
|
||||
|
||||
$edit = [
|
||||
'user_bulk_form[' . ($normal_user->id() - 1) . ']' => TRUE,
|
||||
'action' => 'user_unblock_user_action',
|
||||
];
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
|
||||
// Re-load the normal user and ensure it is still blocked.
|
||||
$normal_user = User::load($normal_user->id());
|
||||
$this->assertTrue($normal_user->isBlocked(), 'The user is still blocked.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if users that may not be deleted, can not be deleted in bulk.
|
||||
*/
|
||||
public function testUserDeleteAccess(): void {
|
||||
// Create two authenticated users.
|
||||
$account = $this->drupalCreateUser([], 'no_delete');
|
||||
$account2 = $this->drupalCreateUser([], 'may_delete');
|
||||
|
||||
// Log in as user admin.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer users']));
|
||||
|
||||
// Ensure that the account "no_delete" can not be deleted.
|
||||
$this->drupalGet('user/' . $account->id() . '/cancel');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
// Ensure that the account "may_delete" *can* be deleted.
|
||||
$this->drupalGet('user/' . $account2->id() . '/cancel');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Test deleting the accounts "no_delete" and "may_delete".
|
||||
$edit = [
|
||||
'user_bulk_form[' . ($account->id() - 1) . ']' => TRUE,
|
||||
'user_bulk_form[' . ($account2->id() - 1) . ']' => TRUE,
|
||||
'action' => 'user_cancel_user_action',
|
||||
];
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
$edit = [
|
||||
'user_cancel_method' => 'user_cancel_delete',
|
||||
];
|
||||
$this->submitForm($edit, 'Confirm');
|
||||
|
||||
// Ensure the account "no_delete" still exists.
|
||||
$account = User::load($account->id());
|
||||
$this->assertNotNull($account, 'The user "no_delete" is not deleted.');
|
||||
// Ensure the account "may_delete" no longer exists.
|
||||
$account = User::load($account2->id());
|
||||
$this->assertNull($account, 'The user "may_delete" is deleted.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests a user bulk form.
|
||||
*
|
||||
* @group user
|
||||
* @see \Drupal\user\Plugin\views\field\UserBulkForm
|
||||
*/
|
||||
class BulkFormTest extends UserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['views_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_user_bulk_form', 'test_user_bulk_form_combine_filter'];
|
||||
|
||||
/**
|
||||
* Tests the user bulk form.
|
||||
*/
|
||||
public function testBulkForm(): void {
|
||||
// Log in as a user without 'administer users'.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer permissions']));
|
||||
$user_storage = $this->container->get('entity_type.manager')->getStorage('user');
|
||||
|
||||
// Create a user which actually can change users.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer users']));
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->assertNotEmpty($this->cssSelect('#edit-action option'));
|
||||
|
||||
// Test submitting the page with no selection.
|
||||
$edit = [
|
||||
'action' => 'user_block_user_action',
|
||||
];
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
$this->assertSession()->pageTextContains('No users selected.');
|
||||
|
||||
// Assign a role to a user.
|
||||
$account = $user_storage->load($this->users[0]->id());
|
||||
$roles = Role::loadMultiple();
|
||||
unset($roles[RoleInterface::ANONYMOUS_ID]);
|
||||
unset($roles[RoleInterface::AUTHENTICATED_ID]);
|
||||
$role = key($roles);
|
||||
|
||||
$this->assertFalse($account->hasRole($role), 'The user currently does not have a custom role.');
|
||||
$edit = [
|
||||
'user_bulk_form[1]' => TRUE,
|
||||
'action' => 'user_add_role_action.' . $role,
|
||||
];
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
// Re-load the user and check their roles.
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->hasRole($role), 'The user now has the custom role.');
|
||||
|
||||
$edit = [
|
||||
'user_bulk_form[1]' => TRUE,
|
||||
'action' => 'user_remove_role_action.' . $role,
|
||||
];
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
// Re-load the user and check their roles.
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertFalse($account->hasRole($role), 'The user no longer has the custom role.');
|
||||
|
||||
// Block a user using the bulk form.
|
||||
$this->assertTrue($account->isActive(), 'The user is not blocked.');
|
||||
$this->assertSession()->pageTextContains($account->label());
|
||||
$edit = [
|
||||
'user_bulk_form[1]' => TRUE,
|
||||
'action' => 'user_block_user_action',
|
||||
];
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
// Re-load the user and check their status.
|
||||
$account = $user_storage->load($account->id());
|
||||
$this->assertTrue($account->isBlocked(), 'The user is blocked.');
|
||||
$this->assertSession()->pageTextNotContains($account->label());
|
||||
|
||||
// Remove the user status filter from the view.
|
||||
$view = Views::getView('test_user_bulk_form');
|
||||
$view->removeHandler('default', 'filter', 'status');
|
||||
$view->storage->save();
|
||||
|
||||
// Ensure the anonymous user is found.
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->assertSession()->pageTextContains($this->config('user.settings')->get('anonymous'));
|
||||
|
||||
// Attempt to block the anonymous user.
|
||||
$edit = [
|
||||
'user_bulk_form[0]' => TRUE,
|
||||
'action' => 'user_block_user_action',
|
||||
];
|
||||
$this->submitForm($edit, 'Apply to selected items');
|
||||
$anonymous_account = $user_storage->load(0);
|
||||
$this->assertTrue($anonymous_account->isBlocked(), 'Ensure the anonymous user got blocked.');
|
||||
|
||||
// Test the list of available actions with a value that contains a dot.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer permissions',
|
||||
'administer views',
|
||||
'administer users',
|
||||
]));
|
||||
$action_id = 'user_add_role_action.' . $role;
|
||||
$edit = [
|
||||
'options[include_exclude]' => 'exclude',
|
||||
"options[selected_actions][$action_id]" => $action_id,
|
||||
];
|
||||
$this->drupalGet('admin/structure/views/nojs/handler/test_user_bulk_form/default/field/user_bulk_form');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->assertSession()->optionNotExists('edit-action', $action_id);
|
||||
$edit['options[include_exclude]'] = 'include';
|
||||
$this->drupalGet('admin/structure/views/nojs/handler/test_user_bulk_form/default/field/user_bulk_form');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet('test-user-bulk-form');
|
||||
$this->assertSession()->optionExists('edit-action', $action_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the user bulk form with a combined field filter on the bulk column.
|
||||
*/
|
||||
public function testBulkFormCombineFilter(): void {
|
||||
// Add a user.
|
||||
User::load($this->users[0]->id());
|
||||
$view = Views::getView('test_user_bulk_form_combine_filter');
|
||||
$errors = $view->validate();
|
||||
$this->assertEquals(sprintf('Field User: Bulk update set in Global: Combine fields filter is not usable for this filter type. Combined field filter only works for simple fields.'), reset($errors['default']));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
use Drupal\Tests\views\Functional\ViewTestBase;
|
||||
|
||||
/**
|
||||
* Tests the permission field handler ui.
|
||||
*
|
||||
* @group user
|
||||
* @see \Drupal\user\Plugin\views\filter\Permissions
|
||||
*/
|
||||
class FilterPermissionUiTest extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_filter_permission'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['user', 'user_test_views', 'views_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['user_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
|
||||
$this->enableViewsTestModule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic filter handler settings in the UI.
|
||||
*/
|
||||
public function testHandlerUI(): void {
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer views',
|
||||
'administer users',
|
||||
]));
|
||||
|
||||
$this->drupalGet('admin/structure/views/view/test_filter_permission/edit/default');
|
||||
// Verify that the handler summary is correctly displaying the selected
|
||||
// permission.
|
||||
$this->assertSession()->linkExists('User: Permission (= View user information)');
|
||||
$this->submitForm([], 'Save');
|
||||
// Verify that we can save the view.
|
||||
$this->assertSession()->pageTextNotContains('No valid values found on filter: User: Permission.');
|
||||
$this->assertSession()->pageTextContains('The view test_filter_permission has been saved.');
|
||||
|
||||
// Verify that the handler summary is also correct when multiple values are
|
||||
// selected in the filter.
|
||||
$edit = [
|
||||
'options[value][]' => [
|
||||
'access user profiles',
|
||||
'administer views',
|
||||
],
|
||||
];
|
||||
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_permission/default/filter/permission');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
$this->assertSession()->linkExists('User: Permission (or View us…)');
|
||||
$this->submitForm([], 'Save');
|
||||
// Verify that we can save the view.
|
||||
$this->assertSession()->pageTextNotContains('No valid values found on filter: User: Permission.');
|
||||
$this->assertSession()->pageTextContains('The view test_filter_permission has been saved.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the handler of the user: role field.
|
||||
*
|
||||
* @group user
|
||||
* @see views_handler_field_user_name
|
||||
*/
|
||||
class HandlerFieldRoleTest extends UserTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_views_handler_field_role'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the rendering of user roles in a Views field handler.
|
||||
*/
|
||||
public function testRole(): void {
|
||||
// Create a couple of roles for the view.
|
||||
$role_name_a = 'a' . $this->randomMachineName(8);
|
||||
$this->drupalCreateRole(['access content'], $role_name_a, '<em>' . $role_name_a . '</em>', 9);
|
||||
|
||||
$role_name_b = 'b' . $this->randomMachineName(8);
|
||||
$this->drupalCreateRole(['access content'], $role_name_b, $role_name_b, 8);
|
||||
|
||||
$role_name_not_assigned = $this->randomMachineName(8);
|
||||
$this->drupalCreateRole(['access content'], $role_name_not_assigned, $role_name_not_assigned);
|
||||
|
||||
// Add roles to user 1.
|
||||
$user = User::load(1);
|
||||
$user->addRole($role_name_a)->addRole($role_name_b)->save();
|
||||
|
||||
$this->drupalLogin($this->createUser(['access user profiles']));
|
||||
$this->drupalGet('/test-views-handler-field-role');
|
||||
// Verify that the view test_views_handler_field_role renders role assigned
|
||||
// to user in the correct order and markup in role names is escaped.
|
||||
$this->assertSession()->responseContains($role_name_b . Html::escape('<em>' . $role_name_a . '</em>'));
|
||||
// Verify that the view test_views_handler_field_role does not render a role
|
||||
// not assigned to a user.
|
||||
$this->assertSession()->pageTextNotContains($role_name_not_assigned);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Views;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the handler of the user: name field.
|
||||
*
|
||||
* @group user
|
||||
* @see views_handler_field_user_name
|
||||
*/
|
||||
class HandlerFieldUserNameTest extends UserTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_views_handler_field_user_name'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the rendering of the user name field in Views.
|
||||
*/
|
||||
public function testUserName(): void {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$new_user = $this->drupalCreateUser(['access user profiles']);
|
||||
$this->drupalLogin($new_user);
|
||||
|
||||
// Set defaults.
|
||||
$view = Views::getView('test_views_handler_field_user_name');
|
||||
$view->initHandlers();
|
||||
$view->field['name']->options['link_to_user'] = TRUE;
|
||||
$view->field['name']->options['type'] = 'user_name';
|
||||
$view->field['name']->init($view, $view->getDisplay('default'));
|
||||
$view->field['name']->options['id'] = 'name';
|
||||
$this->executeView($view);
|
||||
|
||||
$anon_name = $this->config('user.settings')->get('anonymous');
|
||||
$view->result[0]->_entity->setUsername('');
|
||||
$view->result[0]->_entity->uid->value = 0;
|
||||
$render = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertStringContainsString($anon_name, $render, 'For user 0 it should use the default anonymous name by default.');
|
||||
|
||||
$render = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $new_user) {
|
||||
return $view->field['name']->advancedRender($view->result[$new_user->id()]);
|
||||
});
|
||||
$this->assertStringContainsString($new_user->getDisplayName(), $render, 'If link to user is checked the username should be part of the output.');
|
||||
$this->assertStringContainsString('user/' . $new_user->id(), $render, 'If link to user is checked the link to the user should appear as well.');
|
||||
|
||||
$view->field['name']->options['link_to_user'] = FALSE;
|
||||
$view->field['name']->options['type'] = 'string';
|
||||
$render = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $new_user) {
|
||||
return $view->field['name']->advancedRender($view->result[$new_user->id()]);
|
||||
});
|
||||
$this->assertEquals($new_user->getDisplayName(), $render, 'If the user is not linked the username should be printed out for a normal user.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the field handler works when no additional fields are added.
|
||||
*/
|
||||
public function testNoAdditionalFields(): void {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_views_handler_field_user_name');
|
||||
$this->executeView($view);
|
||||
|
||||
$username = $this->randomMachineName();
|
||||
$view->result[0]->_entity->setUsername($username);
|
||||
$view->result[0]->_entity->uid->value = 1;
|
||||
$render = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertStringContainsString($username, $render, 'If link to user is checked the username should be part of the output.');
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user