Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,22 @@
<?php
/**
* @file
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
* upgrade path of https://www.drupal.org/node/2587275.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Replace the user.mail configuration because the dump contains the right token
// already.
$connection->delete('config')->condition('name', 'user.mail')->execute();
$connection->insert('config')
->fields(['collection', 'name', 'data'])
->values([
'collection' => '',
'name' => 'user.mail',
'data' => "a:10:{s:14:\"cancel_confirm\";a:2:{s:4:\"body\";s:369:\"[user:name],\n\nA request to cancel your account has been made at [site:name].\n\nYou may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:\n\n[user:cancel-url]\n\nNOTE: The cancellation of your account is not reversible.\n\nThis link expires in one day and nothing will happen if it is not used.\n\n-- [site:name] team\";s:7:\"subject\";s:59:\"Account cancellation request for [user:name] at [site:name]\";}s:14:\"password_reset\";a:2:{s:4:\"body\";s:397:\"[user:name],\n\nA request to reset the password for your account has been made at [site:name].\n\nYou may now log in by clicking this link or copying and pasting it to your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\n\n-- [site:name] team\";s:7:\"subject\";s:60:\"Replacement login information for [user:name] at [site:name]\";}s:22:\"register_admin_created\";a:2:{s:4:\"body\";s:463:\"[user:name],\n\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team\";s:7:\"subject\";s:58:\"An administrator created an account for you at [site:name]\";}s:29:\"register_no_approval_required\";a:2:{s:4:\"body\";s:437:\"[user:name],\n\nThank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team\";s:7:\"subject\";s:46:\"Account details for [user:name] at [site:name]\";}s:25:\"register_pending_approval\";a:2:{s:4:\"body\";s:281:\"[user:name],\n\nThank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another email containing information about how to log in, set your password, and other details.\n\n\n-- [site:name] team\";s:7:\"subject\";s:71:\"Account details for [user:name] at [site:name] (pending admin approval)\";}s:31:\"register_pending_approval_admin\";a:2:{s:4:\"body\";s:56:\"[user:name] has applied for an account.\n\n[user:edit-url]\";s:7:\"subject\";s:71:\"Account details for [user:name] at [site:name] (pending admin approval)\";}s:16:\"status_activated\";a:2:{s:4:\"body\";s:446:\"[user:name],\n\nYour account at [site:name] has been activated.\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team\";s:7:\"subject\";s:57:\"Account details for [user:name] at [site:name] (approved)\";}s:14:\"status_blocked\";a:2:{s:4:\"body\";s:89:\"[user:name],\n\nYour account on [site:account-name] has been blocked.\n\n-- [site:name] team\";s:7:\"subject\";s:56:\"Account details for [user:name] at [site:name] (blocked)\";}s:15:\"status_canceled\";a:2:{s:4:\"body\";s:82:\"[user:name],\n\nYour account on [site:name] has been canceled.\n\n-- [site:name] team\";s:7:\"subject\";s:57:\"Account details for [user:name] at [site:name] (canceled)\";}s:8:\"langcode\";s:2:\"en\";}",
])->execute();

View file

@ -0,0 +1,6 @@
name: 'User access tests'
type: module
description: 'Support module for user access testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Dummy module implementing hook_user_access() to test if entity access is respected.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
/**
* Implements hook_ENTITY_TYPE_access() for entity type "user".
*/
function user_access_test_user_access(User $entity, $operation, $account) {
if ($entity->getUsername() == "no_edit" && $operation == "update") {
// Deny edit access.
return AccessResult::forbidden();
}
if ($entity->getUsername() == "no_delete" && $operation == "delete") {
// Deny delete access.
return AccessResult::forbidden();
}
return AccessResult::neutral();
}
/**
* Implements hook_entity_field_access().
*/
function user_access_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
// Account with role sub-admin can view the status, init and mail fields for user with no roles.
if ($operation === 'view' && in_array($field_definition->getName(), ['status', 'init', 'mail'])) {
if (($items == NULL) || (count($items->getEntity()->getRoles()) == 1)) {
return AccessResult::allowedIfHasPermission($account, 'sub-admin');
}
}
return AccessResult::neutral();
}

View file

@ -0,0 +1,2 @@
sub-admin:
title: 'Administer users with no roles'

View file

@ -0,0 +1,6 @@
name: 'User custom phpass params test'
type: module
description: 'Support module for testing custom phpass password algorithm parameters.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,4 @@
services:
password:
class: Drupal\Core\Password\PhpassHashedPassword
arguments: [19]

View file

@ -0,0 +1,6 @@
name: 'User module form tests'
type: module
description: 'Support module for user form testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,14 @@
<?php
/**
* @file
* Support module for user form testing.
*/
/**
* Implements hook_form_FORM_ID_alter() for user_cancel_form().
*/
function user_form_test_form_user_cancel_form_alter(&$form, &$form_state) {
$form['user_cancel_confirm']['#default_value'] = FALSE;
$form['access']['#value'] = \Drupal::currentUser()->hasPermission('cancel other accounts');
}

View file

@ -0,0 +1,2 @@
cancel other accounts:
title: 'Cancel other user accounts'

View file

@ -0,0 +1,6 @@
user_form_test.cancel:
path: '/user_form_test_cancel/{user}'
defaults:
_entity_form: 'user.cancel'
requirements:
_permission: 'cancel other accounts'

View file

@ -0,0 +1,6 @@
name: 'User module hooks tests'
type: module
description: 'Support module for user hooks testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,23 @@
<?php
/**
* @file
* Support module for user hooks testing.
*/
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_user_format_name_alter().
*/
function user_hooks_test_user_format_name_alter(&$name, AccountInterface $account) {
if (\Drupal::state()->get('user_hooks_test_user_format_name_alter', FALSE)) {
if (\Drupal::state()->get('user_hooks_test_user_format_name_alter_safe', FALSE)) {
$name = new FormattableMarkup('<em>@uid</em>', ['@uid' => $account->id()]);
}
else {
$name = '<em>' . $account->id() . '</em>';
}
}
}

View file

@ -0,0 +1,34 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_access_perm
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,45 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_access_role
label: ''
module: views
description: ''
tag: ''
base_table: views_test_data
base_field: nid
core: '8'
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: Master
id: default
position: 0
page_1:
display_options:
path: test-role
display_plugin: page
display_title: Page
id: page_1
position: 1

View file

@ -0,0 +1,139 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_field_permission
label: null
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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: { }

View file

@ -0,0 +1,119 @@
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
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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: { }
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- user
tags: { }

View file

@ -0,0 +1,142 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_filter_permission
label: null
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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

View file

@ -0,0 +1,93 @@
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
core: 8.0-dev
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: Master
id: default
position: 0

View file

@ -0,0 +1,63 @@
langcode: en
status: true
dependencies:
module:
- node
id: test_plugin_argument_default_current_user
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,65 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_user_bulk_form
label: ''
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: null
display_options:
style:
type: table
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

View file

@ -0,0 +1,243 @@
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
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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: { }

View file

@ -0,0 +1,60 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_user_changed
label: ''
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: nid
core: '8'
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'
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: Master
id: default
position: 0
page_1:
display_options:
path: test_user_changed
display_plugin: page
display_title: Page
id: page_1
position: 0

View file

@ -0,0 +1,131 @@
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
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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

View file

@ -0,0 +1,478 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_user_fields_access
label: ''
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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: { }
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: { }

View file

@ -0,0 +1,62 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_user_name
label: ''
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: nid
core: '8'
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
anonymous: '0'
entity_type: user
entity_field: uid
display_plugin: default
display_title: Master
id: default
position: 0
page_1:
display_options:
path: test_user_name
display_plugin: page
display_title: Page
id: page_1
position: 0

View file

@ -0,0 +1,115 @@
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
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,220 @@
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
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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: ''
default_argument_skip_url: false
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

View file

@ -0,0 +1,38 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_user_uid_argument
label: null
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: nid
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,40 @@
langcode: en
status: true
dependencies: { }
id: test_view_argument_validate_user
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,40 @@
langcode: en
status: true
dependencies: { }
id: test_view_argument_validate_username
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,177 @@
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
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
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

View file

@ -0,0 +1,63 @@
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
core: '8'
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: Master
id: default
position: 0

View file

@ -0,0 +1,9 @@
name: 'User test views'
type: module
description: 'Provides default views for views user tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- drupal:user
- drupal:views

View file

@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\views_ui\Functional\UITestBase;
use Drupal\views\Tests\ViewTestData;
/**
* 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'];
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'user_test_views'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
ViewTestData::createTestViews(get_class($this), ['user_test_views']);
}
/**
* Tests the role access plugin UI.
*/
public function testAccessRoleUI() {
$entity_manager = $this->container->get('entity.manager');
$entity_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->drupalPostForm($access_url, ['access_options[role][custom_role]' => 1], t('Apply'));
$this->assertResponse(200);
$this->drupalPostForm(NULL, [], t('Save'));
$view = $entity_manager->getStorage('view')->load('test_access_role');
$display = $view->getDisplay('default');
$this->assertEqual($display['display_options']['access']['options']['role'], ['custom_role' => 'custom_role']);
// Test changing access plugin from role to none.
$this->drupalPostForm('admin/structure/views/nojs/display/test_access_role/default/access', ['access[type]' => 'none'], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
// Verify that role option is not set.
$view = $entity_manager->getStorage('view')->load('test_access_role');
$display = $view->getDisplay('default');
$this->assertFalse(isset($display['display_options']['access']['options']['role']));
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\Tests\user\Functional\Hal;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\user\Functional\Rest\RoleResourceTestBase;
/**
* @group hal
*/
class RoleHalJsonAnonTest extends RoleResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\user\Functional\Hal;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\user\Functional\Rest\RoleResourceTestBase;
/**
* @group hal
*/
class RoleHalJsonBasicAuthTest extends RoleResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal', 'basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\user\Functional\Hal;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\user\Functional\Rest\RoleResourceTestBase;
/**
* @group hal
*/
class RoleHalJsonCookieTest extends RoleResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,65 @@
<?php
namespace Drupal\Tests\user\Functional\Hal;
use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\user\Functional\Rest\UserResourceTestBase;
/**
* @group hal
*/
class UserHalJsonAnonTest extends UserResourceTestBase {
use HalEntityNormalizationTrait;
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$default_normalization = parent::getExpectedNormalizedEntity();
$normalization = $this->applyHalFieldNormalization($default_normalization);
return $normalization + [
'_links' => [
'self' => [
'href' => $this->baseUrl . '/user/3?_format=hal_json',
],
'type' => [
'href' => $this->baseUrl . '/rest/type/user/user',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return parent::getNormalizedPostEntity() + [
'_links' => [
'type' => [
'href' => $this->baseUrl . '/rest/type/user/user',
],
],
];
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\user\Functional\Hal;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class UserHalJsonBasicAuthTest extends UserHalJsonAnonTest {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\Tests\user\Functional\Hal;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class UserHalJsonCookieTest extends UserHalJsonAnonTest {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,24 @@
<?php
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';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\user\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class RoleJsonBasicAuthTest extends RoleResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
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';
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\user\Functional\Rest;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\Role;
abstract class RoleResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public 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',
'name' => $this->randomString(),
]);
$role->save();
return $role;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'uuid' => $this->entity->uuid(),
'weight' => 2,
'langcode' => 'en',
'status' => TRUE,
'dependencies' => [],
'id' => 'llama',
'label' => NULL,
'is_admin' => NULL,
'permissions' => [],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
}

View file

@ -0,0 +1,26 @@
<?php
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';
}

View file

@ -0,0 +1,36 @@
<?php
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}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,31 @@
<?php
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';
}

View file

@ -0,0 +1,24 @@
<?php
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';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\user\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class UserJsonBasicAuthTest extends UserResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
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';
}

View file

@ -0,0 +1,329 @@
<?php
namespace Drupal\Tests\user\Functional\Rest;
use Drupal\Core\Url;
use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\User;
use GuzzleHttp\RequestOptions;
abstract class UserResourceTestBase extends EntityResourceTestBase {
use BcTimestampNormalizerUnixTestTrait;
/**
* {@inheritdoc}
*/
public 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;
/**
* {@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' => [
$this->formatExpectedTimestampItemValues(123456789),
],
'changed' => [
$this->formatExpectedTimestampItemValues($this->entity->getChangedTime()),
],
'default_langcode' => [
[
'value' => TRUE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'name' => [
[
'value' => 'Dramallama',
],
],
];
}
/**
* Tests PATCHing security-sensitive base fields of the logged in account.
*/
public function testPatchDxForSecuritySensitiveBaseFields() {
// The anonymous user is never allowed to modify itself.
if (!static::$auth) {
$this->markTestSkipped();
}
$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() {
// The anonymous user is never allowed to modify other users.
if (!static::$auth) {
$this->markTestSkipped();
}
$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) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'access user profiles' permission is required and the user must be active.";
case 'PATCH':
return "You are not authorized to update this user entity.";
case 'DELETE':
return 'You are not authorized to delete this user entity.';
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessCacheability() {
// @see \Drupal\user\UserAccessControlHandler::checkAccess()
return parent::getExpectedUnauthorizedAccessCacheability()
->addCacheTags(['user:3']);
}
}

View file

@ -0,0 +1,34 @@
<?php
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}
*/
public function testPatchDxForSecuritySensitiveBaseFields() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
}

View file

@ -0,0 +1,52 @@
<?php
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}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
/**
* {@inheritdoc}
*/
public function testPatchDxForSecuritySensitiveBaseFields() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
/**
* {@inheritdoc}
*/
public function testPatchSecurityOtherUser() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
}

View file

@ -0,0 +1,47 @@
<?php
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}
*/
public function testPatchDxForSecuritySensitiveBaseFields() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
/**
* {@inheritdoc}
*/
public function testPatchSecurityOtherUser() {
// Deserialization of the XML format is not supported.
$this->markTestSkipped();
}
}

View file

@ -0,0 +1,270 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Core\Url;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\ResourceTestBase;
use GuzzleHttp\RequestOptions;
use Drupal\Core\Test\AssertMailTrait;
/**
* Tests user registration via REST resource.
*
* @group user
*/
class RestRegisterUserTest extends ResourceTestBase {
use CookieResourceTestTrait;
use AssertMailTrait {
getMails as drupalGetMails;
}
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected static $resourceConfigId = 'user_registration';
/**
* {@inheritdoc}
*/
public static $modules = ['hal', 'user'];
const USER_EMAIL_DOMAIN = '@example.com';
const TEST_EMAIL_DOMAIN = 'simpletest@example.com';
/**
* {@inheritdoc}
*/
public function setUp() {
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() {
$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', USER_REGISTER_VISITORS);
$config->set('verify_mail', 0);
$config->save();
$user = $this->registerUser('Palmer.Eldritch');
$this->assertFalse($user->isBlocked());
$this->assertFalse(empty($user->getPassword()));
$email_count = count($this->drupalGetMails());
$this->assertEquals($email_count, 0);
// Attempt to register without sending a password.
$response = $this->registerRequest('Rick.Deckard', FALSE);
$this->assertResourceErrorResponse(422, "No password provided.", $response);
// Attempt to register with a password when e-mail verification is on.
$config->set('register', USER_REGISTER_VISITORS);
$config->set('verify_mail', 1);
$config->save();
$response = $this->registerRequest('Estraven', TRUE);
$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', USER_REGISTER_VISITORS);
$config->set('verify_mail', 1);
$config->save();
$name = 'Jason.Taverner';
$user = $this->registerUser($name, FALSE);
$this->assertTrue(empty($user->getPassword()));
$this->assertTrue($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', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
$config->set('verify_mail', 0);
$config->save();
$name = 'Argaven';
$user = $this->registerUser($name);
$this->resetAll();
$this->assertFalse(empty($user->getPassword()));
$this->assertTrue($user->isBlocked());
$this->assertMailString('body', 'Your application for an account is', 2);
$this->assertMailString('body', 'Argaven has applied for an account', 2);
// Allow visitors to register with Admin approval and e-mail verification.
$config->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
$config->set('verify_mail', 1);
$config->save();
$name = 'Bob.Arctor';
$user = $this->registerUser($name, FALSE);
$this->resetAll();
$this->assertTrue(empty($user->getPassword()));
$this->assertTrue($user->isBlocked());
$this->assertMailString('body', 'Your application for an account is', 2);
$this->assertMailString('body', 'Bob.Arctor 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) {
global $base_url;
$request_body = [
'_links' => ['type' => ["href" => $base_url . "/rest/type/user/user"]],
'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->assertFalse(empty($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) {
$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) {
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) {}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {}
/**
* {@inheritdoc}
*/
protected function getExpectedBcUnauthorizedAccessMessage($method) {}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessCacheability() {}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\user\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests user email token upgrade path.
*
* @group Update
* @group legacy
*/
class UserUpdateEmailToken extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../fixtures/update/drupal-8.user-email-token-2587275.php',
];
}
/**
* Tests that email token in status_blocked of user.mail is updated.
*/
public function testEmailToken() {
$mail = \Drupal::config('user.mail')->get('status_blocked');
$this->assertTrue(strpos($mail['body'], '[site:account-name]'));
$this->runUpdates();
$mail = \Drupal::config('user.mail')->get('status_blocked');
$this->assertFalse(strpos($mail['body'], '[site:account-name]'));
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\Tests\user\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests user permissions sort upgrade path.
*
* @group Update
* @group legacy
*/
class UserUpdateOrderPermissionsTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
];
}
/**
* Tests that permissions are ordered by machine name.
*/
public function testPermissionsOrder() {
$authenticated = \Drupal::config('user.role.authenticated');
$permissions = $authenticated->get('permissions');
sort($permissions);
$this->assertNotSame($permissions, $authenticated->get('permissions'));
$this->runUpdates();
$authenticated = \Drupal::config('user.role.authenticated');
$this->assertSame($permissions, $authenticated->get('permissions'));
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests user-account links.
*
* @group user
*/
class UserAccountLinksTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['menu_ui', 'block', 'test_page_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('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() {
// 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".
$link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', [
':menu_class' => 'menu',
':href' => 'user',
':text' => 'My account',
]);
$this->assertEqual(count($link), 1, 'My account link is in secondary menu.');
$link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', [
':menu_class' => 'menu',
':href' => 'user/logout',
':text' => 'Log out',
]);
$this->assertEqual(count($link), 1, 'Log out link is in secondary menu.');
// 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.
$link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', [
':menu_class' => 'menu',
':href' => 'user/login',
':text' => 'Log in',
]);
$this->assertEqual(count($link), 1, 'Log in link is in secondary menu.');
}
/**
* Tests disabling the 'My account' link.
*/
public function testDisabledAccountLink() {
// 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.
$link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', [
':menu_class' => 'menu',
':href' => 'user',
':text' => 'My account',
]);
$this->assertEqual(count($link), 1, 'My account link is in the secondary menu.');
// 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->assertFieldChecked($label[0]->getText(), "The 'My account' link is enabled by default.");
// Disable the 'My account' link.
$edit['links[menu_plugin_id:user.page][enabled]'] = FALSE;
$this->drupalPostForm('admin/structure/menu/manage/account', $edit, t('Save'));
// Get the homepage.
$this->drupalGet('<front>');
// Verify that the 'My account' link does not appear when disabled.
$link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', [
':menu_class' => 'menu',
':href' => 'user',
':text' => 'My account',
]);
$this->assertEqual(count($link), 0, 'My account link is not in the secondary menu.');
}
/**
* Tests page title is set correctly on user account tabs.
*/
public function testAccountPageTitles() {
// Default page titles are suffixed with the site name - Drupal.
$title_suffix = ' | Drupal';
$this->drupalGet('user');
$this->assertTitle('Log in' . $title_suffix, "Page title of /user is 'Log in'");
$this->drupalGet('user/login');
$this->assertTitle('Log in' . $title_suffix, "Page title of /user/login is 'Log in'");
$this->drupalGet('user/register');
$this->assertTitle('Create new account' . $title_suffix, "Page title of /user/register is 'Create new account' for anonymous users.");
$this->drupalGet('user/password');
$this->assertTitle('Reset your password' . $title_suffix, "Page title of /user/register is 'Reset your password' for anonymous users.");
// 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->assertLink(t('My account'), 0, "Page title of /user is 'My Account' in menus for registered users");
$this->assertLinkByHref(\Drupal::urlGenerator()->generate('user.page'), 0);
}
}

View file

@ -0,0 +1,184 @@
<?php
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;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'language', 'language_test'];
protected function setUp() {
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() {
$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->assertNoFieldByXPath($this->constructFieldXpath('id', 'edit-preferred-admin-langcode'), NULL, 'Administration pages language selector not available.');
}
/**
* Tests that admin language negotiation is configurable only if enabled.
*/
public function testUserAdminLanguageConfigurationAvailableWithAdminLanguageNegotiation() {
$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->assertNoFieldByXPath($this->constructFieldXpath('id', 'edit-preferred-admin-langcode'), NULL, 'Administration pages language selector not available.');
// Checks with user administration pages language negotiation enabled.
$this->setLanguageNegotiation();
$this->drupalGet($path);
// Ensure administration pages language settings widget is available.
$this->assertFieldByXPath($this->constructFieldXpath('id', 'edit-preferred-admin-langcode'), NULL, 'Administration pages language selector is available.');
}
/**
* Tests that the admin language is configurable only for administrators.
*
* If a user has the permission "access administration pages", 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() {
$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->assertFieldByXPath($this->constructFieldXpath('id', 'edit-preferred-admin-langcode'), NULL, 'Administration pages language selector available for admins.');
// Ensure administration pages language setting is hidden for non-admins.
$this->drupalLogin($this->regularUser);
$path = 'user/' . $this->regularUser->id() . '/edit';
$this->drupalGet($path);
$this->assertNoFieldByXPath($this->constructFieldXpath('id', 'edit-preferred-admin-langcode'), NULL, 'Administration pages language selector not available for regular user.');
}
/**
* Tests the actual language negotiation.
*/
public function testActualNegotiation() {
$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->assertText('Language negotiation method: language-default');
$this->drupalGet('xx/' . $path);
$this->assertText('Language negotiation method: language-url');
// Set a preferred language code for the user.
$edit = [];
$edit['preferred_admin_langcode'] = 'xx';
$this->drupalPostForm($path, $edit, t('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->assertText('Language negotiation method: language-user-admin');
$this->drupalGet('xx/' . $path);
$this->assertText('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->assertText('Language negotiation method: language-user-admin');
$this->drupalGet('xx/' . $path);
$this->assertText('Language negotiation method: language-user-admin');
// Unset the preferred language code for the user.
$edit = [];
$edit['preferred_admin_langcode'] = '';
$this->drupalPostForm($path, $edit, t('Save'));
$this->drupalGet($path);
$this->assertText('Language negotiation method: language-default');
$this->drupalGet('xx/' . $path);
$this->assertText('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) {
$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->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
}
/**
* Helper method for adding a custom language.
*/
public function addCustomLanguage() {
$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->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
}
}

View file

@ -0,0 +1,98 @@
<?php
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 {
/**
* Tests the listing.
*/
public function testUserListing() {
$this->drupalGet('admin/people');
$this->assertResponse(403, 'Anonymous user does not have access to the user admin listing.');
// 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);
$account->addRole($rid_2);
$account->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);
$this->drupalGet('admin/people');
$this->assertResponse(200, 'The admin user has access to the user admin listing.');
$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]->getText();
$roles = [];
$account_roles = $account_columns[2]->findAll('css', 'td div 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->assertFalse(array_keys(array_diff_key($result_accounts, $accounts)), 'Ensure all accounts are listed.');
foreach ($result_accounts as $name => $values) {
$this->assertEqual($values['status'] == t('active'), $accounts[$name]->status->value, 'Ensure the status is displayed properly.');
}
$expected_roles = ['custom_role_1', 'custom_role_2'];
$this->assertEqual($result_accounts[$role_account_name]['roles'], $expected_roles, 'Ensure roles are listed properly.');
$this->assertEqual($result_accounts[$timestamp_user]['member_for'], \Drupal::service('date.formatter')->formatTimeDiffSince($accounts[$timestamp_user]->created->value), 'Ensure the right member time is displayed.');
$this->assertEqual($result_accounts[$timestamp_user]['last_access'], 'never', 'Ensure the last access time is "never".');
}
}

View file

@ -0,0 +1,205 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\RoleInterface;
/**
* Tests user administration page functionality.
*
* @group user
*/
class UserAdminTest extends BrowserTestBase {
use AssertMailTrait {
getMails as drupalGetMails;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy', 'views'];
/**
* Registers a user and deletes it.
*/
public function testUserAdmin() {
$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.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->assertText($user_a->getUsername(), 'Found user A on admin users page');
$this->assertText($user_b->getUsername(), 'Found user B on admin users page');
$this->assertText($user_c->getUsername(), 'Found user C on admin users page');
$this->assertText($admin_user->getUsername(), 'Found Admin user on admin users page');
// Test for existence of edit link in table.
$link = $user_a->link(t('Edit'), 'edit-form', ['query' => ['destination' => $user_a->url('collection')]]);
$this->assertRaw($link, 'Found user A edit link on admin users page');
// Test exposed filter elements.
foreach (['user', 'role', 'permission', 'status'] as $field) {
$this->assertField("edit-$field", "$field exposed filter found.");
}
// Make sure the reduce duplicates element from the ManyToOneHelper is not
// displayed.
$this->assertNoField('edit-reduce-duplicates', 'Reduce duplicates form element not found in exposed filters.');
// Filter the users by name/email.
$this->drupalGet('admin/people', ['query' => ['user' => $user_a->getUsername()]]);
$result = $this->xpath('//table/tbody/tr');
$this->assertEqual(1, count($result), 'Filter by username returned the right amount.');
$this->assertEqual($user_a->getUsername(), $result[0]->find('xpath', '/td[2]/span')->getText(), 'Filter by username returned the right user.');
$this->drupalGet('admin/people', ['query' => ['user' => $user_a->getEmail()]]);
$result = $this->xpath('//table/tbody/tr');
$this->assertEqual(1, count($result), 'Filter by username returned the right amount.');
$this->assertEqual($user_a->getUsername(), $result[0]->find('xpath', '/td[2]/span')->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->assertNoText($user_a->getUsername(), 'User A not on filtered by perm admin users page');
$this->assertText($user_b->getUsername(), 'Found user B on filtered by perm admin users page');
$this->assertText($user_c->getUsername(), 'Found user C on filtered by perm admin users page');
// 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->assertNoText($user_a->getUsername(), 'User A not on filtered by role on admin users page');
$this->assertNoText($user_b->getUsername(), 'User B not on filtered by role on admin users page');
$this->assertText($user_c->getUsername(), 'User C on filtered by role on admin users page');
// 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->drupalPostForm('admin/people', $edit, t('Apply to selected items'), [
// Sort the table by username so that we know reliably which user will be
// targeted with the blocking action.
'query' => ['order' => 'name', 'sort' => 'asc'],
]);
$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.');
$user_storage->resetCache([$user_c->id()]);
$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->assertNoText($user_a->getUsername(), 'User A not on filtered by status on admin users page');
$this->assertNoText($user_b->getUsername(), 'User B not on filtered by status on admin users page');
$this->assertText($user_c->getUsername(), 'User C on filtered by status on admin users page');
// Test unblocking of a user from /admin/people page and sending of activation mail
$editunblock = [];
$editunblock['action'] = 'user_unblock_user_action';
$editunblock['user_bulk_form[4]'] = TRUE;
$this->drupalPostForm('admin/people', $editunblock, t('Apply to selected items'), [
// Sort the table by username so that we know reliably which user will be
// targeted with the blocking action.
'query' => ['order' => 'name', 'sort' => 'asc'],
]);
$user_storage->resetCache([$user_c->id()]);
$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([]);
$user_storage->resetCache([$user_d->id()]);
$account1 = $user_storage->load($user_d->id());
$this->drupalPostForm('user/' . $account1->id() . '/edit', ['status' => 0], t('Save'));
$user_storage->resetCache([$user_d->id()]);
$account1 = $user_storage->load($user_d->id());
$this->assertTrue($account1->isBlocked(), 'User D blocked');
$this->drupalPostForm('user/' . $account1->id() . '/edit', ['status' => TRUE], t('Save'));
$user_storage->resetCache([$user_d->id()]);
$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() {
// 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->assertRaw('id="edit-mail-notification-address"', 'Notification Email address field exists');
$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', USER_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->drupalPostForm('user/register', $edit, t('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->assertTrue(count($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->assertTrue(count($user_mail), 'New user mail to user is sent from configured Notification Email address');
}
}

View file

@ -0,0 +1,134 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber;
use Drupal\Tests\BrowserTestBase;
/**
* Tests user blocks.
*
* @group user
*/
class UserBlocksTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'views'];
/**
* A user with the 'administer blocks' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['administer blocks']);
$this->drupalLogin($this->adminUser);
$this->drupalPlaceBlock('user_login_block');
$this->drupalLogout($this->adminUser);
}
/**
* Tests that user login block is hidden from user/login.
*/
public function testUserLoginBlockVisibility() {
// 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);
$elements = $this->xpath('//div[contains(@class,"block-user-login-block") and @role="form"]');
if ($expected_visibility) {
$this->assertTrue(!empty($elements), 'User login block in path "' . $path . '" should be visible');
}
else {
$this->assertTrue(empty($elements), 'User login block in path "' . $path . '" should not be visible');
}
}
}
/**
* Test the user login block.
*/
public function testUserLoginBlock() {
// Create a user with some permission that anonymous users lack.
$user = $this->drupalCreateUser(['administer permissions']);
// Log in using the block.
$edit = [];
$edit['name'] = $user->getUsername();
$edit['pass'] = $user->passRaw;
$this->drupalPostForm('admin/people/permissions', $edit, t('Log in'));
$this->assertNoText(t('User login'), 'Logged in.');
// Check that we are still on the same page.
$this->assertUrl(\Drupal::url('user.admin_permissions', [], ['absolute' => TRUE]), [], 'Still on the same page after login for access denied page');
// Now, log out and repeat with a non-403 page.
$this->drupalLogout();
$this->drupalGet('filter/tips');
$this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER));
$this->drupalPostForm(NULL, $edit, t('Log in'));
$this->assertNoText(t('User login'), 'Logged in.');
$this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');
// Log out again and repeat with a non-403 page including query arguments.
$this->drupalLogout();
$this->drupalGet('filter/tips', ['query' => ['foo' => 'bar']]);
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER));
$this->drupalPostForm(NULL, $edit, t('Log in'));
$this->assertNoText(t('User login'), 'Logged in.');
$this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');
$this->assertTrue(strpos($this->getUrl(), '/filter/tips?foo=bar') !== FALSE, 'Correct query arguments are displayed after login');
// Repeat with different query arguments.
$this->drupalLogout();
$this->drupalGet('filter/tips', ['query' => ['foo' => 'baz']]);
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER));
$this->drupalPostForm(NULL, $edit, t('Log in'));
$this->assertNoText(t('User login'), 'Logged in.');
$this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');
$this->assertTrue(strpos($this->getUrl(), '/filter/tips?foo=baz') !== FALSE, '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->drupalPostForm('http://example.com/', $edit, t('Log in'), ['external' => FALSE]);
// Check that we remain on the site after login.
$this->assertUrl($user->url('canonical', ['absolute' => TRUE]), [], 'Redirected to user profile page after login from the frontpage');
// 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->drupalPostForm('filter/tips', $edit, t('Log in'));
$this->assertText(t('Unrecognized username or password. Forgot your password?'));
$this->drupalGet('filter/tips');
$this->assertNoText(t('Unrecognized username or password. Forgot your password?'));
}
/**
* Updates the access column for a user.
*/
private function updateAccess($uid, $access = REQUEST_TIME) {
db_update('users_field_data')
->condition('uid', $uid)
->fields(['access' => $access])
->execute();
}
}

View file

@ -0,0 +1,56 @@
<?php
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}
*/
public static $modules = ['user'];
/**
* {@inheritdoc}
*/
protected function setUp() {
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() {
return ['user:0', 'user:1'];
}
}

View file

@ -0,0 +1,601 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\comment\CommentInterface;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Tests\CommentTestTrait;
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;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'comment'];
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
}
/**
* Attempt to cancel account without permission.
*/
public function testUserCancelWithoutPermission() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
$user_storage = $this->container->get('entity.manager')->getStorage('user');
// Create a user.
$account = $this->drupalCreateUser([]);
$this->drupalLogin($account);
// Load a real user object.
$user_storage->resetCache([$account->id()]);
$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->assertNoRaw(t('Cancel account'), 'No cancel account button displayed.');
// Attempt bogus account cancellation request confirmation.
$timestamp = $account->getLastLoginTime();
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$this->assertResponse(403, 'Bogus cancelling request rejected.');
$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->assertTrue(($test_node->getOwnerId() == $account->id() && $test_node->isPublished()), 'Node of the user has not been altered.');
}
/**
* Test ability to change the permission for canceling users.
*/
public function testUserCancelChangePermission() {
\Drupal::service('module_installer')->install(['user_form_test']);
\Drupal::service('router.builder')->rebuild();
$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->drupalPostForm('user_form_test_cancel/' . $account->id(), [], t('Cancel account'));
// Confirm deletion.
$this->assertRaw(t('%name has been deleted.', ['%name' => $account->getUsername()]), 'User deleted.');
$this->assertFalse(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() {
$user_storage = $this->container->get('entity.manager')->getStorage('user');
\Drupal::service('module_installer')->install(['views']);
\Drupal::service('router.builder')->rebuild();
// 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->drupalPostForm('admin/people', $edit, t('Apply to selected items'));
// Verify that uid 1's account was not cancelled.
$user_storage->resetCache([1]);
$user1 = $user_storage->load(1);
$this->assertTrue($user1->isActive(), 'User #1 still exists and is not blocked.');
}
/**
* Attempt invalid account cancellations.
*/
public function testUserCancelInvalid() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
$user_storage = $this->container->get('entity.manager')->getStorage('user');
// Create a user.
$account = $this->drupalCreateUser(['cancel account']);
$this->drupalLogin($account);
// Load a real user object.
$user_storage->resetCache([$account->id()]);
$account = $user_storage->load($account->id());
// Create a node.
$node = $this->drupalCreateNode(['uid' => $account->id()]);
// Attempt to cancel account.
$this->drupalPostForm('user/' . $account->id() . '/edit', NULL, t('Cancel account'));
// Confirm account cancellation.
$timestamp = time();
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// 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->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Bogus cancelling request rejected.');
$user_storage->resetCache([$account->id()]);
$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->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Expired cancel account request rejected.');
$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->assertTrue(($test_node->getOwnerId() == $account->id() && $test_node->isPublished()), 'Node of the user has not been altered.');
}
/**
* Disable account and keep all content.
*/
public function testUserBlock() {
$this->config('user.settings')->set('cancel_method', 'user_cancel_block')->save();
$user_storage = $this->container->get('entity.manager')->getStorage('user');
// Create a user.
$web_user = $this->drupalCreateUser(['cancel account']);
$this->drupalLogin($web_user);
// Load a real user object.
$user_storage->resetCache([$web_user->id()]);
$account = $user_storage->load($web_user->id());
// Attempt to cancel account.
$this->drupalGet('user/' . $account->id() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
$this->assertText(t('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.'), 'Informs that all content will be remain as is.');
$this->assertNoText(t('Select the method to cancel the account above.'), 'Does not allow user to select account cancellation method.');
// Confirm account cancellation.
$timestamp = time();
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache([$account->id()]);
$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->assertRaw(t('%name has been disabled.', ['%name' => $account->getUsername()]), "Confirmation message displayed to user.");
}
/**
* Disable account and unpublish all content.
*/
public function testUserBlockUnpublish() {
$node_storage = $this->container->get('entity.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.manager')->getStorage('user');
// Create a user.
$account = $this->drupalCreateUser(['cancel account']);
$this->drupalLogin($account);
// Load a real user object.
$user_storage->resetCache([$account->id()]);
$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() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
$this->assertText(t('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.'), 'Informs that all content will be unpublished.');
// Confirm account cancellation.
$timestamp = time();
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// 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->assertRaw(t('%name has been disabled.', ['%name' => $account->getUsername()]), "Confirmation message displayed to user.");
$user_storage->resetCache([$account->id()]);
$account = $user_storage->load($account->id());
$this->assertTrue($account->isBlocked(), 'User has been blocked.');
// Confirm user's content has been unpublished.
$node_storage->resetCache([$node->id()]);
$test_node = $node_storage->load($node->id());
$this->assertFalse($test_node->isPublished(), 'Node of the user has been unpublished.');
$test_node = node_revision_load($node->getRevisionId());
$this->assertFalse($test_node->isPublished(), 'Node revision of the user has been unpublished.');
$storage = \Drupal::entityManager()->getStorage('comment');
$storage->resetCache([$comment->id()]);
$comment = $storage->load($comment->id());
$this->assertFalse($comment->isPublished(), 'Comment of the user has been unpublished.');
}
/**
* Delete account and anonymize all content.
*/
public function testUserAnonymize() {
$node_storage = $this->container->get('entity.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.manager')->getStorage('user');
// Create a user.
$account = $this->drupalCreateUser(['cancel account']);
$this->drupalLogin($account);
// Load a real user object.
$user_storage->resetCache([$account->id()]);
$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() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
$this->assertRaw(t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.', ['%anonymous-name' => $this->config('user.settings')->get('anonymous')]), 'Informs that all content will be attributed to anonymous account.');
// Confirm account cancellation.
$timestamp = time();
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache([$account->id()]);
$this->assertFalse($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();
$node_storage->resetCache([$node->id()]);
$test_node = $node_storage->load($node->id());
$this->assertTrue(($test_node->getOwnerId() == 0 && $test_node->isPublished()), 'Node of the user has been attributed to anonymous user.');
$test_node = node_revision_load($revision, TRUE);
$this->assertTrue(($test_node->getRevisionUser()->id() == 0 && $test_node->isPublished()), 'Node revision of the user has been attributed to anonymous user.');
$node_storage->resetCache([$revision_node->id()]);
$test_node = $node_storage->load($revision_node->id());
$this->assertTrue(($test_node->getOwnerId() != 0 && $test_node->isPublished()), "Current revision of the user's node was not attributed to anonymous user.");
$storage = \Drupal::entityManager()->getStorage('comment');
$storage->resetCache([$comment->id()]);
$test_comment = $storage->load($comment->id());
$this->assertTrue(($test_comment->getOwnerId() == 0 && $test_comment->isPublished()), 'Comment of the user has been attributed to anonymous user.');
$this->assertEqual($test_comment->getAuthorName(), $anonymous_user->getDisplayName(), 'Comment of the user has been attributed to anonymous user name.');
// Confirm that the confirmation message made it through to the end user.
$this->assertRaw(t('%name has been deleted.', ['%name' => $account->getUsername()]), "Confirmation message displayed to user.");
}
/**
* Delete account and anonymize all content using a batch process.
*/
public function testUserAnonymizeBatch() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
$user_storage = $this->container->get('entity.manager')->getStorage('user');
// Create a user.
$account = $this->drupalCreateUser(['cancel account']);
$this->drupalLogin($account);
// Load a real user object.
$user_storage->resetCache([$account->id()]);
$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() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
$this->assertRaw(t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.', ['%anonymous-name' => $this->config('user.settings')->get('anonymous')]), 'Informs that all content will be attributed to anonymous account.');
// Confirm account cancellation.
$timestamp = time();
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache([$account->id()]);
$this->assertFalse($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->assertTrue(($test_node->getOwnerId() == 0 && $test_node->isPublished()), 'Node ' . $test_node->id() . ' of the user has been attributed to anonymous user.');
}
}
/**
* Delete account and remove all content.
*/
public function testUserDelete() {
$node_storage = $this->container->get('entity.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.manager')->getStorage('user');
// Create a user.
$account = $this->drupalCreateUser(['cancel account', 'post comments', 'skip comment approval']);
$this->drupalLogin($account);
// Load a real user object.
$user_storage->resetCache([$account->id()]);
$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->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit, t('Preview'));
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertText(t('Your comment has been posted.'));
$comments = entity_load_multiple_by_properties('comment', ['subject' => $edit['subject[0][value]']]);
$comment = reset($comments);
$this->assertTrue($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() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
$this->assertText(t('Your account will be removed and all account information deleted. All of your content will also be deleted.'), 'Informs that all content will be deleted.');
// Confirm account cancellation.
$timestamp = time();
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache([$account->id()]);
$this->assertFalse($user_storage->load($account->id()), 'User is not found in the database.');
// Confirm that user's content has been deleted.
$node_storage->resetCache([$node->id()]);
$this->assertFalse($node_storage->load($node->id()), 'Node of the user has been deleted.');
$this->assertFalse(node_revision_load($revision), 'Node revision of the user has been deleted.');
$node_storage->resetCache([$revision_node->id()]);
$this->assertTrue($node_storage->load($revision_node->id()), "Current revision of the user's node was not deleted.");
\Drupal::entityManager()->getStorage('comment')->resetCache([$comment->id()]);
$this->assertFalse(Comment::load($comment->id()), 'Comment of the user has been deleted.');
// Confirm that the confirmation message made it through to the end user.
$this->assertRaw(t('%name has been deleted.', ['%name' => $account->getUsername()]), "Confirmation message displayed to user.");
}
/**
* Create an administrative user and delete another user.
*/
public function testUserCancelByAdmin() {
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
// Create a regular user.
$account = $this->drupalCreateUser([]);
// Create administrative user.
$admin_user = $this->drupalCreateUser(['administer users']);
$this->drupalLogin($admin_user);
// Delete regular user.
$this->drupalGet('user/' . $account->id() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertRaw(t('Are you sure you want to cancel the account %name?', ['%name' => $account->getUsername()]), 'Confirmation form to cancel account displayed.');
$this->assertText(t('Select the method to cancel the account above.'), 'Allows to select account cancellation method.');
// Confirm deletion.
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertRaw(t('%name has been deleted.', ['%name' => $account->getUsername()]), 'User deleted.');
$this->assertFalse(User::load($account->id()), 'User is not found in the database.');
}
/**
* Tests deletion of a user account without an email address.
*/
public function testUserWithoutEmailCancelByAdmin() {
$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() . '/edit');
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertRaw(t('Are you sure you want to cancel the account %name?', ['%name' => $account->getUsername()]), 'Confirmation form to cancel account displayed.');
$this->assertText(t('Select the method to cancel the account above.'), 'Allows to select account cancellation method.');
// Confirm deletion.
$this->drupalPostForm(NULL, NULL, t('Cancel account'));
$this->assertRaw(t('%name has been deleted.', ['%name' => $account->getUsername()]), 'User deleted.');
$this->assertFalse(User::load($account->id()), 'User is not found in the database.');
}
/**
* Create an administrative user and mass-delete other users.
*/
public function testMassUserCancelByAdmin() {
\Drupal::service('module_installer')->install(['views']);
\Drupal::service('router.builder')->rebuild();
$this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save();
$user_storage = $this->container->get('entity.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->drupalPostForm('admin/people', $edit, t('Apply to selected items'));
$this->assertText(t('Are you sure you want to cancel these user accounts?'), 'Confirmation form to cancel accounts displayed.');
$this->assertText(t('When cancelling these accounts'), 'Allows to select account cancellation method.');
$this->assertText(t('Require email confirmation to cancel account'), 'Allows to send confirmation mail.');
$this->assertText(t('Notify user when account is canceled'), 'Allows to send notification mail.');
// Confirm deletion.
$this->drupalPostForm(NULL, NULL, t('Cancel accounts'));
$status = TRUE;
foreach ($users as $account) {
$status = $status && (strpos($this->getTextContent(), $account->getUsername() . ' has been deleted.') !== FALSE);
$user_storage->resetCache([$account->id()]);
$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->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
$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.
$user_storage->resetCache([1]);
$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() {
\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->assertTrue(empty($load2));
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the create user administration page.
*
* @group user
*/
class UserCreateFailMailTest extends BrowserTestBase {
/**
* Modules to enable
*
* @var array
*/
public static $modules = ['system_mail_failure_test'];
/**
* Tests the create user administration page.
*/
public function testUserAdd() {
$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->drupalPostForm('admin/people/create', $edit, t('Create new account'));
$this->assertText(t('Unable to send email. Contact the site administrator if the problem persists.'));
$this->assertNoText(t('A welcome message with further instructions has been emailed to the new user @name.', ['@name' => $edit['name']]));
}
}

View file

@ -0,0 +1,135 @@
<?php
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;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['image'];
/**
* Create a user through the administration interface and ensure that it
* displays in the user list.
*/
public function testUserAdd() {
$user = $this->drupalCreateUser(['administer users']);
$this->drupalLogin($user);
$this->assertEqual($user->getCreatedTime(), REQUEST_TIME, 'Creating a user sets default "created" timestamp.');
$this->assertEqual($user->getChangedTime(), REQUEST_TIME, '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' => t('Your virtual face or picture.'),
'required' => FALSE,
'settings' => [
'file_extensions' => 'png gif jpg jpeg',
'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->assertFieldbyId('edit-status-0', 0, 'The user status option Blocked exists.', 'User login');
$this->assertFieldbyId('edit-status-1', 1, 'The user status option Active exists.', 'User login');
$this->assertFieldByXPath('//input[@type="radio" and @id="edit-status-1" and @checked="checked"]', NULL, 'Default setting for user status is active.');
// Test that browser autocomplete behavior does not occur.
$this->assertNoRaw('data-user-info-from-browser', 'Ensure form attribute, data-user-info-from-browser, does not exist.');
// Test that the password strength indicator displays.
$config = $this->config('user.settings');
$config->set('password_strength', TRUE)->save();
$this->drupalGet('admin/people/create');
$this->assertRaw(t('Password strength:'), 'The password strength indicator is displayed.');
$config->set('password_strength', FALSE)->save();
$this->drupalGet('admin/people/create');
$this->assertNoRaw(t('Password strength:'), 'The password strength indicator is not displayed.');
// 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->drupalPostForm('admin/people/create', $edit, t('Create new account'));
if ($notify) {
$this->assertText(t('A welcome message with further instructions has been emailed to the new user @name.', ['@name' => $edit['name']]), 'User created');
$this->assertEqual(count($this->drupalGetMails()), 1, 'Notification email sent');
}
else {
$this->assertText(t('Created a new user account for @name. No email has been sent.', ['@name' => $edit['name']]), 'User created');
$this->assertEqual(count($this->drupalGetMails()), 0, 'Notification email not sent');
}
$this->drupalGet('admin/people');
$this->assertText($edit['name'], 'User found in list of users');
$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->drupalPostForm('admin/people/create', $edit, t('Create new account'));
$this->assertText("Created a new user account for $name. No email has been sent");
$this->assertNoText('Password field is required');
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\User;
/**
* Tests account deleting of users.
*
* @group user
*/
class UserDeleteTest extends BrowserTestBase {
/**
* Test deleting multiple users.
*/
public function testUserDeleteMultiple() {
// Create a few users with permissions, so roles will be created.
$user_a = $this->drupalCreateUser(['access user profiles']);
$user_b = $this->drupalCreateUser(['access user profiles']);
$user_c = $this->drupalCreateUser(['access user profiles']);
$uids = [$user_a->id(), $user_b->id(), $user_c->id()];
// These users should have a role
$query = db_select('user__roles', 'r');
$roles_created = $query
->fields('r', ['entity_id'])
->condition('entity_id', $uids, 'IN')
->countQuery()
->execute()
->fetchField();
$this->assertTrue($roles_created > 0, 'Role assignments created for new users and deletion of role assignments can be tested');
// We should be able to load one of the users.
$this->assertTrue(User::load($user_a->id()), 'User is created and deletion of user can be tested');
// Delete the users.
user_delete_multiple($uids);
// Test if the roles assignments are deleted.
$query = db_select('user__roles', 'r');
$roles_after_deletion = $query
->fields('r', ['entity_id'])
->condition('entity_id', $uids, 'IN')
->countQuery()
->execute()
->fetchField();
$this->assertTrue($roles_after_deletion == 0, 'Role assignments deleted along with users');
// Test if the users are deleted, User::load() will return NULL.
$this->assertNull(User::load($user_a->id()), format_string('User with id @uid deleted.', ['@uid' => $user_a->id()]));
$this->assertNull(User::load($user_b->id()), format_string('User with id @uid deleted.', ['@uid' => $user_b->id()]));
$this->assertNull(User::load($user_c->id()), format_string('User with id @uid deleted.', ['@uid' => $user_c->id()]));
}
}

View file

@ -0,0 +1,154 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\BrowserTestBase;
/**
* Tests user edit page.
*
* @group user
*/
class UserEditTest extends BrowserTestBase {
/**
* Test user edit page.
*/
public function testUserEdit() {
// 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->getUsername();
$this->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t('The username %name is already taken.', ['%name' => $edit['name']]));
// Check that the default value in user name field
// is the raw value and not a formatted one.
\Drupal::state()->set('user_hooks_test_user_format_name_alter', TRUE);
\Drupal::service('module_installer')->install(['user_hooks_test']);
Cache::invalidateTags(['rendered']);
$this->drupalGet('user/' . $user1->id() . '/edit');
$this->assertFieldByName('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->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertText(t("The specified passwords do not match."), 'Typing mismatched passwords displays an error message.');
$edit['pass[pass1]'] = $this->randomMachineName();
$edit['pass[pass2]'] = '';
$this->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertText(t("The specified passwords do not match."), 'Typing mismatched passwords displays an error message.');
// 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->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", ['%name' => t('Email')]));
$edit['current_pass'] = $user1->passRaw;
$this->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t("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->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", ['%name' => t('Password')]));
// Try again with the current password.
$edit['current_pass'] = $user1->passRaw;
$this->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t("The changes have been saved."));
// Make sure the changed timestamp is updated.
$this->assertEqual($user1->getChangedTime(), REQUEST_TIME, '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->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t('Password strength:'), 'The password strength indicator is displayed.');
$config->set('password_strength', FALSE)->save();
$this->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertNoRaw(t('Password strength:'), 'The password strength indicator is not displayed.');
// 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->assertNoFieldChecked('edit-status-0');
$this->assertFieldChecked('edit-status-1');
$edit = ['status' => 0];
$this->drupalPostForm('user/' . $user1->id() . '/edit', $edit, t('Save'));
$this->assertText(t('The changes have been saved.'));
$this->assertFieldChecked('edit-status-0');
$this->assertNoFieldChecked('edit-status-1');
$edit = ['status' => 1];
$this->drupalPostForm('user/' . $user1->id() . '/edit', $edit, t('Save'));
$this->assertText(t('The changes have been saved.'));
$this->assertNoFieldChecked('edit-status-0');
$this->assertFieldChecked('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() {
$admin = $this->drupalCreateUser(['administer users']);
$this->drupalLogin($admin);
// Create a regular user.
$user1 = $this->drupalCreateUser([]);
$edit = ['pass[pass1]' => '0', 'pass[pass2]' => '0'];
$this->drupalPostForm("user/" . $user1->id() . "/edit", $edit, t('Save'));
$this->assertRaw(t("The changes have been saved."));
}
/**
* Tests editing of a user account without an email address.
*/
public function testUserWithoutEmailEdit() {
// 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->drupalPostForm("user/" . $user1->id() . "/edit", ['mail' => ''], t('Save'));
$this->assertRaw(t("The changes have been saved."));
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests user edited own account can still log in.
*
* @group user
*/
class UserEditedOwnAccountTest extends BrowserTestBase {
public function testUserEditedOwnAccount() {
// Change account setting 'Who can register accounts?' to Administrators
// only.
$this->config('user.settings')->set('register', USER_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->drupalPostForm('user/' . $account->id() . '/edit', $edit, t('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);
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\User;
/**
* Tests specific parts of the user entity like the URI callback and the label
* callback.
*
* @group user
*/
class UserEntityCallbacksTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'user_hooks_test'];
/**
* An authenticated user to use for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $account;
/**
* An anonymous user to use for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $anonymous;
protected function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser();
$this->anonymous = User::create(['uid' => 0]);
}
/**
* Test label callback.
*/
public function testLabelCallback() {
$this->assertEqual($this->account->label(), $this->account->getUsername(), 'The username should be used as label');
// Setup a random anonymous name to be sure the name is used.
$name = $this->randomMachineName();
$this->config('user.settings')->set('anonymous', $name)->save();
$this->assertEqual($this->anonymous->label(), $name, 'The variable anonymous should be used for name of uid 0');
$this->assertEqual($this->anonymous->getDisplayName(), $name, 'The variable anonymous should be used for display name of uid 0');
$this->assertEqual($this->anonymous->getUserName(), '', 'The raw anonymous user name should be empty string');
// Set to test the altered username.
\Drupal::state()->set('user_hooks_test_user_format_name_alter', TRUE);
$this->assertEqual($this->account->getDisplayName(), '<em>' . $this->account->id() . '</em>', 'The user display name should be altered.');
$this->assertEqual($this->account->getUsername(), $this->account->name->value, 'The user name should not be altered.');
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Tests whether proper language is stored for new users and access to language
* selector.
*
* @group user
*/
class UserLanguageCreationTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'language'];
/**
* Functional test for language handling during user creation.
*/
public function testLocalUserCreation() {
// 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->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
$this->assertText(t('Language detection configuration saved.'), 'Set language negotiation.');
// 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->assertOptionSelected("edit-preferred-langcode", $langcode, 'Global language set in the language selector.');
// 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->drupalPostForm($langcode . '/admin/people/create', $edit, t('Create new account'));
$user = user_load_by_name($username);
$this->assertEqual($user->getPreferredLangcode(), $langcode, 'New user has correct preferred language set.');
$this->assertEqual($user->language()->getId(), $langcode, '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->assertNoFieldByName('language[fr]', 'Language selector is not accessible.');
$username = $this->randomMachineName(10);
$edit = [
'name' => $username,
'mail' => $this->randomMachineName(4) . '@example.com',
];
$this->drupalPostForm($langcode . '/user/register', $edit, t('Create new account'));
$user = user_load_by_name($username);
$this->assertEqual($user->getPreferredLangcode(), $langcode, 'New user has correct preferred language set.');
$this->assertEqual($user->language()->getId(), $langcode, 'New user has correct profile language set.');
// Test if the admin can use the language selector and if the
// correct language is was saved.
$user_edit = $langcode . '/user/' . $user->id() . '/edit';
$this->drupalLogin($admin_user);
$this->drupalGet($user_edit);
$this->assertOptionSelected("edit-preferred-langcode", $langcode, 'Language selector is accessible and correct language is selected.');
// 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->drupalPostForm($user_edit, $edit, t('Save'));
$this->drupalLogin($user);
$this->drupalGet($user_edit);
$this->assertOptionSelected("edit-preferred-langcode", $langcode, 'Language selector is accessible and correct language is selected.');
}
}

View file

@ -0,0 +1,67 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'language'];
/**
* Test if user can change their default language.
*/
public function testUserLanguageConfiguration() {
// 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->drupalPostForm('admin/config/regional/language/add', $edit, t('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->assertText(t('Language'), 'Language selector available.');
// Ensure custom language is present.
$this->assertText($name, 'Language present on form.');
// Switch to our custom language.
$edit = [
'preferred_langcode' => $langcode,
];
$this->drupalPostForm($path, $edit, t('Save'));
// Ensure form was submitted successfully.
$this->assertText(t('The changes have been saved.'), 'Changes were saved.');
// Check if language was changed.
$this->assertOptionSelected('edit-preferred-langcode', $langcode, 'Default language successfully updated.');
$this->drupalLogout();
}
}

View file

@ -0,0 +1,544 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Core\Flood\DatabaseBackend;
use Drupal\Core\Test\AssertMailTrait;
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 Drupal\hal\Encoder\JsonEncoder as HALJsonEncoder;
use Symfony\Component\Serializer\Serializer;
/**
* Tests login and password reset via direct HTTP.
*
* @group user
*/
class UserLoginHttpTest extends BrowserTestBase {
use AssertMailTrait {
getMails as drupalGetMails;
}
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['hal'];
/**
* The cookie jar.
*
* @var \GuzzleHttp\Cookie\CookieJar
*/
protected $cookies;
/**
* The serializer.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->cookies = new CookieJar();
$encoders = [new JsonEncoder(), new XmlEncoder(), new HALJsonEncoder()];
$this->serializer = new Serializer([], $encoders);
}
/**
* Executes a login HTTP request.
*
* @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') {
$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() {
// 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->doTestLogin('json');
$this->doTestLogin('xml');
$this->doTestLogin('hal_json');
}
/**
* Do login testing for a given serialization format.
*
* @param string $format
* Serialization format.
*/
protected function doTestLogin($format) {
$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->getUsername();
$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($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($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.
*
* @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') {
$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() {
// 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->doTestPasswordReset('json', $account);
$this->doTestPasswordReset('xml', $account);
$this->doTestPasswordReset('hal_json', $account);
}
/**
* 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() {
$this->container->get('database')->delete(DatabaseBackend::TABLE_NAME)->execute();
}
/**
* Tests the global login flood control.
*
* @see \Drupal\basic_auth\Tests\Authentication\BasicAuthTest::testGlobalLoginFloodControl
* @see \Drupal\user\Tests\UserLoginTest::testGlobalLoginFloodControl
*/
public function testGlobalLoginFloodControl() {
$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->getUsername(), $incorrect_user->passRaw);
$this->assertEquals('400', $response->getStatusCode());
}
// IP limit has reached to its limit. Even valid user credentials will fail.
$response = $this->loginRequest($user->getUsername(), $user->passRaw);
$this->assertHttpResponseWithMessage($response, '403', 'Access is blocked because of IP based flood prevention.');
}
/**
* 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 mixed $expected_body
* The expected response body.
*/
protected function assertHttpResponse(ResponseInterface $response, $expected_code, $expected_body) {
$this->assertEquals($expected_code, $response->getStatusCode());
$this->assertEquals($expected_body, (string) $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.
*/
protected function assertHttpResponseWithMessage(ResponseInterface $response, $expected_code, $expected_message, $format = 'json') {
$this->assertEquals($expected_code, $response->getStatusCode());
$this->assertEquals($expected_message, $this->getResultValue($response, 'message', $format));
}
/**
* Test the per-user login flood control.
*
* @see \Drupal\user\Tests\UserLoginTest::testPerUserLoginFloodControl
* @see \Drupal\basic_auth\Tests\Authentication\BasicAuthTest::testPerUserLoginFloodControl
*/
public function testPerUserLoginFloodControl() {
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->getUsername(), $incorrect_user1->passRaw);
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.');
}
// A successful login will reset the per-user flood control count.
$response = $this->loginRequest($user1->getUsername(), $user1->passRaw);
$result_data = $this->serializer->decode($response->getBody(), 'json');
$this->logoutRequest('json', $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->getUsername(), $incorrect_user1->passRaw);
$this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.');
}
// 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->getUsername(), $user1->passRaw);
// Depending on the uid_only setting the error message will be different.
if ($uid_only_setting) {
$excepted_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.';
}
else {
$excepted_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.';
}
$this->assertHttpResponseWithMessage($response, 403, $excepted_message);
}
}
/**
* Executes a logout HTTP request.
*
* @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 = '') {
/** @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;
}
/**
* Test csrf protection of User Logout route.
*/
public function testLogoutCsrfProtection() {
$client = \Drupal::httpClient();
$login_status_url = $this->getLoginStatusUrlString();
$account = $this->drupalCreateUser();
$name = $account->getUsername();
$pass = $account->passRaw;
$response = $this->loginRequest($name, $pass);
$this->assertEquals(200, $response->getStatusCode());
$result_data = $this->serializer->decode($response->getBody(), 'json');
$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('json');
$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('json', '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('json', $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.
*
* @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) {
$response = $this->passwordRequest([], $format);
$this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name or credentials.mail', $format);
$response = $this->passwordRequest(['name' => 'dramallama'], $format);
$this->assertHttpResponseWithMessage($response, 400, 'Unrecognized username or email address.', $format);
$response = $this->passwordRequest(['mail' => 'llama@drupal.org'], $format);
$this->assertHttpResponseWithMessage($response, 400, 'Unrecognized username or email address.', $format);
$account
->block()
->save();
$response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
$this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
$response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
$this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
$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() {
$_emails = $this->drupalGetMails();
$email = end($_emails);
$urls = [];
preg_match('#.+user/reset/.+#', $email['body'], $urls);
$resetURL = $urls[0];
$this->drupalGet($resetURL);
$this->drupalPostForm(NULL, NULL, 'Log in');
}
}

View file

@ -0,0 +1,178 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\User;
/**
* Ensure that login works as expected.
*
* @group user
*/
class UserLoginTest extends BrowserTestBase {
/**
* Tests login with destination.
*/
public function testLoginCacheTagsAndDestination() {
$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->assertCacheTag('config:system.site');
$user = $this->drupalCreateUser([]);
$this->drupalGet('user/login', ['query' => ['destination' => 'foo']]);
$edit = ['name' => $user->getUserName(), 'pass' => $user->passRaw];
$this->drupalPostForm(NULL, $edit, t('Log in'));
$this->assertUrl('foo', [], 'Redirected to the correct URL');
}
/**
* Test the global login flood control.
*/
public function testGlobalLoginFloodControl() {
$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');
}
/**
* Test the per-user login flood control.
*/
public function testPerUserLoginFloodControl() {
$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);
}
// A successful login will reset the per-user flood control count.
$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');
}
/**
* Test that user password is re-hashed upon login after changing $count_log2.
*/
public function testPasswordRehashOnLogin() {
// Determine default log2 for phpass hashing algorithm
$default_count_log2 = 16;
// 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 should reflect $default_count_log2.
$user_storage = $this->container->get('entity.manager')->getStorage('user');
$account = User::load($account->id());
$this->assertIdentical($password_hasher->getCountLog2($account->getPassword()), $default_count_log2);
// Change the required number of iterations by loading a test-module
// containing the necessary container builder code and then verify that the
// users password gets rehashed during the login.
$overridden_count_log2 = 19;
\Drupal::service('module_installer')->install(['user_custom_phpass_params_test']);
$this->resetAll();
$account->passRaw = $password;
$this->drupalLogin($account);
// Load the stored user, which should have a different password hash now.
$user_storage->resetCache([$account->id()]);
$account = $user_storage->load($account->id());
$this->assertIdentical($password_hasher->getCountLog2($account->getPassword()), $overridden_count_log2);
$this->assertTrue($password_hasher->check($password, $account->getPassword()));
}
/**
* Make an unsuccessful login attempt.
*
* @param \Drupal\user\Entity\User $account
* A user object with name and passRaw attributes for the login attempt.
* @param mixed $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.
*/
public function assertFailedLogin($account, $flood_trigger = NULL) {
$edit = [
'name' => $account->getUsername(),
'pass' => $account->passRaw,
];
$this->drupalPostForm('user/login', $edit, t('Log in'));
$this->assertNoFieldByXPath("//input[@name='pass' and @value!='']", NULL, 'Password value attribute is blank.');
if (isset($flood_trigger)) {
if ($flood_trigger == 'user') {
$this->assertRaw(\Drupal::translation()->formatPlural($this->config('user.flood')->get('user_limit'), 'There has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', 'There have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => \Drupal::url('user.pass')]));
}
else {
// No uid, so the limit is IP-based.
$this->assertRaw(t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => \Drupal::url('user.pass')]));
}
}
else {
$this->assertText(t('Unrecognized username or password. Forgot your password?'));
}
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
/**
* Tests _user_mail_notify() use of user.settings.notify.*.
*
* @group user
*/
class UserMailNotifyTest extends EntityKernelTestBase {
use AssertMailTrait {
getMails as drupalGetMails;
}
/**
* Data provider for user mail testing.
*
* @return array
*/
public function userMailsProvider() {
return [
['cancel_confirm', ['cancel_confirm']],
['password_reset', ['password_reset']],
['status_activated', ['status_activated']],
['status_blocked', ['status_blocked']],
['status_canceled', ['status_canceled']],
['register_admin_created', ['register_admin_created']],
['register_no_approval_required', ['register_no_approval_required']],
['register_pending_approval', ['register_pending_approval', 'register_pending_approval_admin']],
];
}
/**
* Tests mails are sent when notify.$op is TRUE.
*
* @param string $op
* The operation being performed on the account.
* @param array $mail_keys
* The mail keys to test for.
*
* @dataProvider userMailsProvider
*/
public function testUserMailsSent($op, array $mail_keys) {
$this->config('user.settings')->set('notify.' . $op, TRUE)->save();
$return = _user_mail_notify($op, $this->createUser());
$this->assertTrue($return, '_user_mail_notify() returns TRUE.');
foreach ($mail_keys as $key) {
$filter = ['key' => $key];
$this->assertNotEmpty($this->getMails($filter), "Mails with $key exists.");
}
$this->assertCount(count($mail_keys), $this->getMails(), 'The expected number of emails sent.');
}
/**
* Tests mails are not sent when notify.$op is FALSE.
*
* @param string $op
* The operation being performed on the account.
* @param array $mail_keys
* The mail keys to test for. Ignored by this test because we assert that no
* mails at all are sent.
*
* @dataProvider userMailsProvider
*/
public function testUserMailsNotSent($op, array $mail_keys) {
$this->config('user.settings')->set('notify.' . $op, FALSE)->save();
$return = _user_mail_notify($op, $this->createUser());
$this->assertFalse($return, '_user_mail_notify() returns FALSE.');
$this->assertEmpty($this->getMails(), 'No emails sent by _user_mail_notify().');
}
}

View file

@ -0,0 +1,326 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\Core\Url;
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
use Drupal\user\Entity\User;
/**
* Ensure that password reset methods work as expected.
*
* @group user
*/
class UserPasswordResetTest extends PageCacheTagsTestBase {
use AssertMailTrait {
getMails as drupalGetMails;
}
/**
* The user object to test password resetting.
*
* @var \Drupal\user\UserInterface
*/
protected $account;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$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 = REQUEST_TIME - mt_rand(10, 100000);
db_update('users_field_data')
->fields(['login' => $account->getLastLoginTime()])
->condition('uid', $account->id())
->execute();
}
/**
* Tests password reset functionality.
*/
public function testUserPasswordReset() {
// 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->assertResponse(403);
// Try to reset the password for an invalid account.
$this->drupalGet('user/password');
$edit = ['name' => $this->randomMachineName(32)];
$this->drupalPostForm(NULL, $edit, t('Submit'));
$this->assertText(t('@name is not recognized as a username or an email address.', ['@name' => $edit['name']]), 'Validation error message shown when trying to request password for invalid account.');
$this->assertEqual(count($this->drupalGetMails(['id' => 'user_password_reset'])), 0, 'No email was sent when requesting a password for an invalid account.');
// Reset the password by username via the password reset page.
$edit['name'] = $this->account->getUsername();
$this->drupalPostForm(NULL, $edit, t('Submit'));
// Verify that the user was sent an email.
$this->assertMail('to', $this->account->getEmail(), 'Password email sent to user.');
$subject = t('Replacement login information for @username at @site', ['@username' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name')]);
$this->assertMail('subject', $subject, 'Password reset email subject is correct.');
$resetURL = $this->getResetURL();
$this->drupalGet($resetURL);
// Ensure that the current url does not contain the hash and timestamp.
$this->assertUrl(Url::fromRoute('user.reset.form', ['uid' => $this->account->id()]));
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
// Ensure the password reset URL is not cached.
$this->drupalGet($resetURL);
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
// Check the one-time login page.
$this->assertText($this->account->getUsername(), 'One-time login page contains the correct username.');
$this->assertText(t('This login can be used only once.'), 'Found warning about one-time login.');
$this->assertTitle(t('Reset password | Drupal'), 'Page title is "Reset password".');
// Check successful login.
$this->drupalPostForm(NULL, NULL, t('Log in'));
$this->assertLink(t('Log out'));
$this->assertTitle(t('@name | @site', ['@name' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name')]), 'Logged in using password reset link.');
// Change the forgotten password.
$password = user_password();
$edit = ['pass[pass1]' => $password, 'pass[pass2]' => $password];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('The changes have been saved.'), 'Forgotten password changed.');
// Verify that the password reset session has been destroyed.
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t("Your current password is missing or incorrect; it's required to change the Password."), 'Password needed to make profile changes.');
// Log out, and try to log in again using the same one-time link.
$this->drupalLogout();
$this->drupalGet($resetURL);
$this->drupalPostForm(NULL, NULL, t('Log in'));
$this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.');
// Request a new password again, this time using the email address.
$this->drupalGet('user/password');
// Count email messages before to compare with after.
$before = count($this->drupalGetMails(['id' => 'user_password_reset']));
$edit = ['name' => $this->account->getEmail()];
$this->drupalPostForm(NULL, $edit, t('Submit'));
$this->assertTrue(count($this->drupalGetMails(['id' => 'user_password_reset'])) === $before + 1, 'Email sent when requesting password reset using email address.');
// 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->drupalPostForm(NULL, NULL, t('Log in'));
$this->drupalGet('user/' . $this->account->id() . '/edit');
$this->assertNoText('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 = REQUEST_TIME - $timeout - 60;
$_uid = $this->account->id();
$this->drupalGet("user/reset/$_uid/$bogus_timestamp/" . user_pass_rehash($this->account, $bogus_timestamp));
$this->drupalPostForm(NULL, NULL, t('Log in'));
$this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.');
// Create a user, block the account, and verify that a login link is denied.
$timestamp = REQUEST_TIME - 1;
$blocked_account = $this->drupalCreateUser()->block();
$blocked_account->save();
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp));
$this->assertResponse(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->getUsername()];
$this->drupalPostForm(NULL, $edit, t('Submit'));
$this->assertRaw(t('%name is blocked or has not been activated yet.', ['%name' => $blocked_account->getUsername()]), 'Notified user blocked accounts can not request a new password');
$this->assertTrue(count($this->drupalGetMails(['id' => 'user_password_reset'])) === $before, '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->getUsername()];
$this->drupalPostForm(NULL, $edit, t('Submit'));
$old_email_reset_link = $this->getResetURL();
$this->account->setEmail("1" . $this->account->getEmail());
$this->account->save();
$this->drupalGet($old_email_reset_link);
$this->drupalPostForm(NULL, NULL, t('Log in'));
$this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.');
// Verify a password reset link will automatically log a user when /login is
// appended.
$this->drupalGet('user/password');
$edit = ['name' => $this->account->getUsername()];
$this->drupalPostForm(NULL, $edit, t('Submit'));
$reset_url = $this->getResetURL();
$this->drupalGet($reset_url . '/login');
$this->assertLink(t('Log out'));
$this->assertTitle(t('@name | @site', ['@name' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name')]), 'Logged in using password reset link.');
// Ensure blocked and deleted accounts can't access the user.reset.login
// route.
$this->drupalLogout();
$timestamp = REQUEST_TIME - 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->assertResponse(403);
$blocked_account->delete();
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login');
$this->assertResponse(403);
}
/**
* 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];
}
/**
* Test user password reset while logged in.
*/
public function testUserPasswordResetLoggedIn() {
$another_account = $this->drupalCreateUser();
$this->drupalLogin($another_account);
$this->drupalGet('user/password');
$this->drupalPostForm(NULL, NULL, t('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->assertRaw(new FormattableMarkup(
'Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please <a href=":logout">log out</a> and try using the link again.',
['%other_user' => $this->account->getUsername(), '%resetting_user' => $another_account->getUsername(), ':logout' => Url::fromRoute('user.logout')->toString()]
));
$another_account->delete();
$this->drupalGet($resetURL);
$this->assertText('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->drupalPostForm(NULL, NULL, t('Submit'));
// Click the reset URL while logged and change our password.
$resetURL = $this->getResetURL();
$this->drupalGet($resetURL);
$this->drupalPostForm(NULL, NULL, t('Log in'));
// Change the password.
$password = user_password();
$edit = ['pass[pass1]' => $password, 'pass[pass2]' => $password];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('The changes have been saved.'), 'Password changed.');
// Logged in users should not be able to access the user.reset.login or the
// user.reset.form routes.
$timestamp = REQUEST_TIME - 1;
$this->drupalGet("user/reset/" . $this->account->id() . "/$timestamp/" . user_pass_rehash($this->account, $timestamp) . '/login');
$this->assertResponse(403);
$this->drupalGet("user/reset/" . $this->account->id());
$this->assertResponse(403);
}
/**
* Prefill the text box on incorrect login via link to password reset page.
*/
public function testUserResetPasswordTextboxFilled() {
$this->drupalGet('user/login');
$edit = [
'name' => $this->randomMachineName(),
'pass' => $this->randomMachineName(),
];
$this->drupalPostForm('user/login', $edit, t('Log in'));
$this->assertRaw(t('Unrecognized username or password. <a href=":password">Forgot your password?</a>',
[':password' => \Drupal::url('user.pass', [], ['query' => ['name' => $edit['name']]])]));
unset($edit['pass']);
$this->drupalGet('user/password', ['query' => ['name' => $edit['name']]]);
$this->assertFieldByName('name', $edit['name'], 'User name found.');
// Ensure the name field value is not cached.
$this->drupalGet('user/password');
$this->assertNoFieldByName('name', $edit['name'], 'User name not found.');
}
/**
* Make sure that users cannot forge password reset URLs of other users.
*/
public function testResetImpersonation() {
// 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.
db_update('users_field_data')
->fields(['pass' => NULL])
->condition('uid', [$user1->id(), $user2->id()], 'IN')
->execute();
\Drupal::entityManager()->getStorage('user')->resetCache();
$user1 = User::load($user1->id());
$user2 = User::load($user2->id());
$this->assertEqual($user1->getPassword(), $user2->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);
$this->drupalPostForm(NULL, NULL, t('Log in'));
$this->assertNoText($user2->getUsername(), 'The invalid password reset page does not show the user name.');
$this->assertUrl('user/password', [], 'The user is redirected to the password reset request page.');
$this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
}
}

View file

@ -0,0 +1,188 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\RoleInterface;
use Drupal\user\Entity\Role;
/**
* Verify that role permissions can be added and removed via the permissions
* page.
*
* @group user
*/
class UserPermissionsTest extends BrowserTestBase {
/**
* User with admin privileges.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* User's role ID.
*
* @var string
*/
protected $rid;
protected function setUp() {
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);
}
/**
* Test changing user permissions through the permissions page.
*/
public function testUserPermissionChanges() {
$permissions_hash_generator = $this->container->get('user_permissions_hash_generator');
$storage = $this->container->get('entity.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->assertIdentical($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->drupalPostForm('admin/people/permissions', $edit, t('Save permissions'));
$this->assertText(t('The changes have been saved.'), 'Successful save message displayed.');
$storage->resetCache();
$this->assertTrue($account->hasPermission('administer users'), 'User now has "administer users" permission.');
$current_permissions_hash = $permissions_hash_generator->generate($account);
$this->assertIdentical($current_permissions_hash, $permissions_hash_generator->generate($this->loggedInUser));
$this->assertNotEqual($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->drupalPostForm('admin/people/permissions', $edit, t('Save permissions'));
$this->assertText(t('The changes have been saved.'), 'Successful save message displayed.');
$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->assertIdentical($current_permissions_hash, $permissions_hash_generator->generate($this->loggedInUser));
$this->assertNotEqual($previous_permissions_hash, $current_permissions_hash, 'Permissions hash has changed.');
// 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 . ']');
}
}
/**
* Test assigning of permissions for the administrator role.
*/
public function testAdministratorRole() {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/people/accounts');
// Verify that the administration role is none by default.
$this->assertOptionSelected('edit-user-admin-role', '', 'Administration role defaults to none.');
$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->drupalPostForm('admin/config/people/accounts', $edit, t('Save configuration'));
\Drupal::entityManager()->getStorage('user_role')->resetCache();
$this->assertTrue(Role::load($this->rid)->isAdmin());
// Enable aggregator module and ensure the 'administer news feeds'
// permission is assigned by default.
\Drupal::service('module_installer')->install(['aggregator']);
$this->assertTrue($this->adminUser->hasPermission('administer news feeds'), 'The permission was automatically assigned to the administrator role');
// Ensure that selecting '- None -' removes the admin role.
$edit = [];
$edit['user_admin_role'] = '';
$this->drupalPostForm('admin/config/people/accounts', $edit, t('Save configuration'));
\Drupal::entityManager()->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/config/people/accounts');
$this->assertNoFieldByName('user_admin_role');
}
/**
* Verify proper permission changes by user_role_change_permissions().
*/
public function testUserRoleChangePermissions() {
$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->assertNotEqual($previous_permissions_hash, $current_permissions_hash, 'Permissions hash has changed.');
}
/**
* Verify 'access content' is listed in the correct location.
*/
public function testAccessContentPermission() {
$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->assertEqual('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->assertEqual('edit-permissions-view-own-unpublished-content', $next_row[0]->getAttribute('data-drupal-selector'));
}
}

View file

@ -0,0 +1,154 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\BrowserTestBase;
use Drupal\file\Entity\File;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests user picture functionality.
*
* @group user
*/
class UserPictureTest extends BrowserTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
}
/**
* The profile to install as a basis for testing.
*
* Using the standard profile to test user picture config provided by the
* standard profile.
*
* @var string
*/
protected $profile = 'standard';
/**
* A regular user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
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() {
$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->assertRaw(file_uri_target($file->getFileUri()), 'User picture found on user account page.');
// Delete the picture.
$edit = [];
$this->drupalPostForm('user/' . $this->webUser->id() . '/edit', $edit, t('Remove'));
$this->drupalPostForm(NULL, [], t('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.
db_update('file_managed')
->fields([
'changed' => REQUEST_TIME - ($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->assertFalse(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->assertFalse(is_file($file->getFileUri()), 'File was removed from the file system.');
}
/**
* Tests embedded users on node pages.
*/
public function testPictureOnNodeComment() {
$this->drupalLogin($this->webUser);
// 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 = file_url_transform_relative($style->buildUrl($file->getfileUri()));
$alt_text = 'Profile picture for user ' . $this->webUser->getUsername();
// Verify that the image is displayed on the node page.
$this->drupalGet('node/' . $node->id());
$elements = $this->cssSelect('.node__meta .field--name-user-picture img[alt="' . $alt_text . '"][src="' . $image_url . '"]');
$this->assertEqual(count($elements), 1, '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->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit, t('Save'));
$elements = $this->cssSelect('.comment__meta .field--name-user-picture img[alt="' . $alt_text . '"][src="' . $image_url . '"]');
$this->assertEqual(count($elements), 1, '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->assertNoRaw(file_uri_target($file->getFileUri()), 'User picture not found on node and comment.');
}
/**
* 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->drupalPostForm('user/' . $this->webUser->id() . '/edit', $edit, t('Save'));
// Load actual user data from database.
$user_storage = $this->container->get('entity.manager')->getStorage('user');
$user_storage->resetCache([$this->webUser->id()]);
$account = $user_storage->load($this->webUser->id());
return File::load($account->user_picture->target_id);
}
}

View file

@ -0,0 +1,384 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Component\Render\FormattableMarkup;
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;
/**
* Tests registration of user under different configurations.
*
* @group user
*/
class UserRegistrationTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field_test'];
public function testRegistrationWithEmailVerification() {
$config = $this->config('user.settings');
// Require email verification.
$config->set('verify_mail', TRUE)->save();
// Set registration to administrator only.
$config->set('register', USER_REGISTER_ADMINISTRATORS_ONLY)->save();
$this->drupalGet('user/register');
$this->assertResponse(403, 'Registration page is inaccessible when only administrators can create accounts.');
// Allow registration by site visitors without administrator approval.
$config->set('register', USER_REGISTER_VISITORS)->save();
$edit = [];
$edit['name'] = $name = $this->randomMachineName();
$edit['mail'] = $mail = $edit['name'] . '@example.com';
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertText(t('A welcome message with further instructions has been sent to your email address.'), 'User registered successfully.');
/** @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->assertTitle(t('Set password | Drupal'), 'Page title is "Set password".');
// Allow registration by site visitors, but require administrator approval.
$config->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
$edit = [];
$edit['name'] = $name = $this->randomMachineName();
$edit['mail'] = $mail = $edit['name'] . '@example.com';
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->container->get('entity.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.');
}
public function testRegistrationWithoutEmailVerification() {
$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', USER_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->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertText(t('The specified passwords do not match.'), 'Typing mismatched passwords displays an error message.');
// Enter a correct password.
$edit['pass[pass1]'] = $new_pass = $this->randomMachineName();
$edit['pass[pass2]'] = $new_pass;
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->container->get('entity.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->assertText(t('Registration successful. You are now logged in.'), 'Users are logged in after registering.');
$this->drupalLogout();
// Allow registration by site visitors, but require administrator approval.
$config->set('register', USER_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->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertText(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.'), 'Users are notified of pending approval');
// Try to log in before administrator approval.
$auth = [
'name' => $name,
'pass' => $pass,
];
$this->drupalPostForm('user/login', $auth, t('Log in'));
$this->assertText(t('The username @name has not been activated or is blocked.', ['@name' => $name]), 'User cannot log in yet.');
// 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->drupalPostForm('user/' . $new_user->id() . '/edit', $edit, t('Save'));
$this->drupalLogout();
// Log in after administrator approval.
$this->drupalPostForm('user/login', $auth, t('Log in'));
$this->assertText(t('Member for'), 'User can log in after administrator approval.');
}
public function testRegistrationEmailDuplicates() {
// Don't require email verification and allow registration by site visitors
// without administrator approval.
$this->config('user.settings')
->set('verify_mail', FALSE)
->set('register', USER_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->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertText(t('The email address @email is already taken.', ['@email' => $duplicate_user->getEmail()]), 'Supplying an exact duplicate email address displays an error message');
// Attempt to bypass duplicate email registration validation by adding spaces.
$edit['mail'] = ' ' . $duplicate_user->getEmail() . ' ';
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertText(t('The email address @email is already taken.', ['@email' => $duplicate_user->getEmail()]), 'Supplying a duplicate email address with added whitespace displays an error message');
}
/**
* 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() {
\Drupal::service('module_installer')->install(['image']);
\Drupal::service('router.builder')->rebuild();
// 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', USER_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->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertResponse(200);
$user_storage = \Drupal::entityManager()->getStorage('user');
$this->assertTrue($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->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertResponse(200);
$this->assertTrue($user_storage->loadByProperties(['name' => $edit['name']]));
}
public function testRegistrationDefaultValues() {
// 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', USER_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->assertCacheTag('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->drupalPostForm(NULL, $edit, t('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->assertEqual($new_user->getUsername(), $name, 'Username matches.');
$this->assertEqual($new_user->getEmail(), $mail, 'Email address matches.');
$this->assertTrue(($new_user->getCreatedTime() > REQUEST_TIME - 20), 'Correct creation time.');
$this->assertEqual($new_user->isActive(), $config_user_settings->get('register') == USER_REGISTER_VISITORS ? 1 : 0, 'Correct status field.');
$this->assertEqual($new_user->getTimezone(), $config_system_date->get('timezone.default'), 'Correct time zone field.');
$this->assertEqual($new_user->langcode->value, \Drupal::languageManager()->getDefaultLanguage()->getId(), 'Correct language field.');
$this->assertEqual($new_user->preferred_langcode->value, \Drupal::languageManager()->getDefaultLanguage()->getId(), 'Correct preferred language field.');
$this->assertEqual($new_user->init->value, $mail, '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() {
$account = $this->drupalCreateUser();
$edit = ['mail' => 'test@example.com', 'name' => $account->getUsername()];
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertRaw(new FormattableMarkup('The username %value is already taken.', ['%value' => $account->getUsername()]));
$edit = ['mail' => $account->getEmail(), 'name' => $this->randomString()];
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertRaw(new FormattableMarkup('The email address %value is already taken.', ['%value' => $account->getEmail()]));
}
/**
* Tests Field API fields on user registration forms.
*/
public function testRegistrationWithUserFields() {
// 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();
entity_get_form_display('user', 'user', 'default')
->setComponent('test_user_field', ['type' => 'test_field_widget'])
->save();
entity_get_form_display('user', 'user', 'register')
->save();
// Check that the field does not appear on the registration form.
$this->drupalGet('user/register');
$this->assertNoText($field->label(), 'The field does not appear on user registration form');
$this->assertCacheTag('config:core.entity_form_display.user.user.register');
$this->assertCacheTag('config:user.settings');
// Have the field appear on the registration form.
entity_get_form_display('user', 'user', 'register')
->setComponent('test_user_field', ['type' => 'test_field_widget'])
->save();
$this->drupalGet('user/register');
$this->assertText($field->label(), 'The field appears on user registration form');
$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->drupalPostForm(NULL, $edit, t('Create new account'));
$this->assertRegistrationFormCacheTagsWithUserFields();
$this->assertRaw(t('@name field is required.', ['@name' => $field->label()]), 'Field validation error was correctly reported.');
// Invalid input.
$edit['test_user_field[0][value]'] = '-1';
$this->drupalPostForm(NULL, $edit, t('Create new account'));
$this->assertRegistrationFormCacheTagsWithUserFields();
$this->assertRaw(t('%name does not accept the value -1.', ['%name' => $field->label()]), 'Field validation error was correctly reported.');
// Submit with valid data.
$value = rand(1, 255);
$edit['test_user_field[0][value]'] = $value;
$this->drupalPostForm(NULL, $edit, t('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->assertEqual($new_user->test_user_field->value, $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->drupalPostForm(NULL, $edit, t('Add another item'));
$this->drupalPostForm(NULL, $edit, t('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->drupalPostForm(NULL, $edit, t('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->assertEqual($new_user->test_user_field[0]->value, $value, 'The field value was correctly saved.');
$this->assertEqual($new_user->test_user_field[1]->value, $value + 1, 'The field value was correctly saved.');
$this->assertEqual($new_user->test_user_field[2]->value, $value + 2, 'The field value was correctly saved.');
}
/**
* Asserts the presence of cache tags on registration form with user fields.
*/
protected function assertRegistrationFormCacheTagsWithUserFields() {
$this->assertCacheTag('config:core.entity_form_display.user.user.register');
$this->assertCacheTag('config:field.field.user.user.test_user_field');
$this->assertCacheTag('config:field.storage.user.test_user_field');
$this->assertCacheTag('config:user.settings');
}
}

View file

@ -0,0 +1,132 @@
<?php
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;
/**
* Modules to enable.
*
* @var string[]
*/
public static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['administer permissions', 'administer users']);
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Test adding, renaming and deleting roles.
*/
public function testRoleAdministration() {
$this->drupalLogin($this->adminUser);
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
// Test presence of tab.
$this->drupalGet('admin/people/permissions');
$tabs = $this->xpath('//ul[@class=:classes and //a[contains(., :text)]]', [
':classes' => 'tabs primary',
':text' => 'Roles',
]);
$this->assertEqual(count($tabs), 1, 'Found roles tab');
// 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->drupalPostForm('admin/people/roles/add', $edit, t('Save'));
$this->assertRaw(t('Role %label has been added.', ['%label' => 123]));
$role = Role::load($role_name);
$this->assertTrue(is_object($role), 'The role was successfully retrieved from the database.');
// Check that the role was created in site default language.
$this->assertEqual($role->language()->getId(), $default_langcode);
// Try adding a duplicate role.
$this->drupalPostForm('admin/people/roles/add', $edit, t('Save'));
$this->assertRaw(t('The machine-readable name is already in use. It must be unique.'), 'Duplicate role warning displayed.');
// Test renaming a role.
$role_name = '456';
$edit = ['label' => $role_name];
$this->drupalPostForm("admin/people/roles/manage/{$role->id()}", $edit, t('Save'));
$this->assertRaw(t('Role %label has been updated.', ['%label' => $role_name]));
\Drupal::entityManager()->getStorage('user_role')->resetCache([$role->id()]);
$new_role = Role::load($role->id());
$this->assertEqual($new_role->label(), $role_name, 'The role name has been successfully changed.');
// Test deleting a role.
$this->drupalGet("admin/people/roles/manage/{$role->id()}");
$this->clickLink(t('Delete'));
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertRaw(t('The role %label has been deleted.', ['%label' => $role_name]));
$this->assertNoLinkByHref("admin/people/roles/manage/{$role->id()}", 'Role edit link removed.');
\Drupal::entityManager()->getStorage('user_role')->resetCache([$role->id()]);
$this->assertFalse(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->assertResponse(200, 'Access granted when trying to edit the built-in anonymous role.');
$this->assertNoText(t('Delete role'), 'Delete button for the anonymous role is not present.');
$this->drupalGet('admin/people/roles/manage/' . RoleInterface::AUTHENTICATED_ID);
$this->assertResponse(200, 'Access granted when trying to edit the built-in authenticated role.');
$this->assertNoText(t('Delete role'), 'Delete button for the authenticated role is not present.');
}
/**
* Test user role weight change operation and ordering.
*/
public function testRoleWeightOrdering() {
$this->drupalLogin($this->adminUser);
$roles = user_roles();
$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->drupalPostForm('admin/people/roles', $edit, t('Save'));
$this->assertText(t('The role settings have been updated.'), 'The role settings form submitted successfully.');
// Load up the user roles with the new weights.
drupal_static_reset('user_roles');
$roles = user_roles();
$rids = [];
// Test that the role weights have been correctly saved.
foreach ($roles as $role) {
$this->assertEqual($role->getWeight(), $new_role_weights[$role->id()]);
$rids[] = $role->id();
}
// The order of the roles should be reversed.
$this->assertIdentical($rids, array_reverse($saved_rids));
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests that users can be assigned and unassigned roles.
*
* @group user
*/
class UserRolesAssignmentTest extends BrowserTestBase {
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(['administer permissions', 'administer users']);
$this->drupalLogin($admin_user);
}
/**
* Tests that a user can be assigned a role and that the role can be removed
* again.
*/
public function testAssignAndRemoveRole() {
$rid = $this->drupalCreateRole(['administer users']);
$account = $this->drupalCreateUser();
// Assign the role to the user.
$this->drupalPostForm('user/' . $account->id() . '/edit', ["roles[$rid]" => $rid], t('Save'));
$this->assertText(t('The changes have been saved.'));
$this->assertFieldChecked('edit-roles-' . $rid, 'Role is assigned.');
$this->userLoadAndCheckRoleAssigned($account, $rid);
// Remove the role from the user.
$this->drupalPostForm('user/' . $account->id() . '/edit', ["roles[$rid]" => FALSE], t('Save'));
$this->assertText(t('The changes have been saved.'));
$this->assertNoFieldChecked('edit-roles-' . $rid, 'Role is removed from user.');
$this->userLoadAndCheckRoleAssigned($account, $rid, FALSE);
}
/**
* Tests that when creating a user the role can be assigned. And that it can
* be removed again.
*/
public function testCreateUserWithRole() {
$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->drupalPostForm('admin/people/create', $edit, t('Create new account'));
$this->assertText(t('Created a new user account for @name.', ['@name' => $edit['name']]));
// Get the newly added user.
$account = user_load_by_name($edit['name']);
$this->drupalGet('user/' . $account->id() . '/edit');
$this->assertFieldChecked('edit-roles-' . $rid, 'Role is assigned.');
$this->userLoadAndCheckRoleAssigned($account, $rid);
// Remove the role again.
$this->drupalPostForm('user/' . $account->id() . '/edit', ["roles[$rid]" => FALSE], t('Save'));
$this->assertText(t('The changes have been saved.'));
$this->assertNoFieldChecked('edit-roles-' . $rid, 'Role is removed from user.');
$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) {
$user_storage = $this->container->get('entity.manager')->getStorage('user');
$user_storage->resetCache([$account->id()]);
$account = $user_storage->load($account->id());
if ($is_assigned) {
$this->assertFalse(array_search($rid, $account->getRoles()) === FALSE, 'The role is present in the user object.');
}
else {
$this->assertTrue(array_search($rid, $account->getRoles()) === FALSE, 'The role is not present in the user object.');
}
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\User;
/**
* Tests account saving for arbitrary new uid.
*
* @group user
*/
class UserSaveTest extends BrowserTestBase {
/**
* Test creating a user with arbitrary uid.
*/
public function testUserImport() {
// User ID must be a number that is not in the database.
$uids = \Drupal::entityManager()->getStorage('user')->getQuery()
->sort('uid', 'DESC')
->range(0, 1)
->execute();
$max_uid = reset($uids);
$test_uid = $max_uid + mt_rand(1000, 1000000);
$test_name = $this->randomMachineName();
// Create the base user, based on drupalCreateUser().
$user = User::create([
'name' => $test_name,
'uid' => $test_uid,
'mail' => $test_name . '@example.com',
'pass' => user_password(),
'status' => 1,
]);
$user->enforceIsNew();
$user->save();
// Test if created user exists.
$user_by_uid = User::load($test_uid);
$this->assertTrue($user_by_uid, 'Loading user by uid.');
$user_by_name = user_load_by_name($test_name);
$this->assertTrue($user_by_name, 'Loading user by name.');
}
/**
* Ensures that an existing password is unset after the user was saved.
*/
public function testExistingPasswordRemoval() {
/** @var \Drupal\user\Entity\User $user */
$user = User::create(['name' => $this->randomMachineName()]);
$user->save();
$user->setExistingPassword('existing password');
$this->assertNotNull($user->pass->existing);
$user->save();
$this->assertNull($user->pass->existing);
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the user search page and verifies that sensitive information is hidden
* from unauthorized users.
*
* @group user
*/
class UserSearchTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['search'];
public function testUserSearch() {
// 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->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText(t('Your search yielded no results.'), 'Search by email did not work for non-admin user');
$this->assertText('no results', 'Search by email gave no-match message');
// Verify that a non-matching query gives an appropriate message.
$keys = 'nomatch';
$edit = ['keys' => $keys];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText('no results', 'Non-matching search gave appropriate message');
// Verify that a user with search permission can search for users by name.
$keys = $user1->getUsername();
$edit = ['keys' => $keys];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertLink($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->drupalPostForm('search/user', $edit, t('Search'));
$this->assertLink($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->drupalPostForm('search/user', $edit, t('Search'));
$this->assertLink($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->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText($keys, 'Search by email works for administrative user');
$this->assertText($user2->getUsername(), 'Search by email resulted in username on page for administrative user');
// Verify that a substring works too for email.
$subkey = substr($keys, 1, 5);
$edit = ['keys' => $subkey];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText($keys, 'Search by email substring works for administrative user');
$this->assertText($user2->getUsername(), 'Search by email substring resulted in username on page for administrative user');
// Verify that wildcard search works for email
$subkey = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
$edit = ['keys' => $subkey];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText($user2->getUsername(), 'Search for email wildcard resulted in username on page for administrative user');
// Verify that if they search by user name, they see email address too.
$keys = $user1->getUsername();
$edit = ['keys' => $keys];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText($keys, 'Search by username works for admin user');
$this->assertText($user1->getEmail(), 'Search by username for admin shows email address too');
// 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->getUsername()];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText($blocked_user->getUsername(), 'Blocked users are listed on the user search results for users with the "administer users" permission.');
// Verify that users without "administer users" permissions do not see
// blocked accounts in search results.
$this->drupalLogin($user1);
$edit = ['keys' => $blocked_user->getUsername()];
$this->drupalPostForm('search/user', $edit, t('Search'));
$this->assertText(t('Your search yielded no results.'), 'Blocked users are hidden from the user search results.');
// Create a user without search permission, and one without user page view
// permission. Verify that neither one can access the user search page.
$user3 = $this->drupalCreateUser(['search content']);
$this->drupalLogin($user3);
$this->drupalGet('search/user');
$this->assertResponse('403', 'User without user profile access cannot search');
$user4 = $this->drupalCreateUser(['access user profiles']);
$this->drupalLogin($user4);
$this->drupalGet('search/user');
$this->assertResponse('403', 'User without search permission cannot search');
$this->drupalLogout();
}
}

View file

@ -0,0 +1,95 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'system_test'];
/**
* Tests the display of dates and time when user-configurable time zones are set.
*/
public function testUserTimeZone() {
// 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());
$this->assertText('2007-03-09 21:00 0', 'Date should be PST.');
$this->drupalGet('node/' . $node2->id());
$this->assertText('2007-03-11 01:00 0', 'Date should be PST.');
$this->drupalGet('node/' . $node3->id());
$this->assertText('2007-03-20 21:00 1', 'Date should be PDT.');
// Change user time zone to Santiago time.
$edit = [];
$edit['mail'] = $web_user->getEmail();
$edit['timezone'] = 'America/Santiago';
$this->drupalPostForm("user/" . $web_user->id() . "/edit", $edit, t('Save'));
$this->assertText(t('The changes have been saved.'), 'Time zone changed to Santiago time.');
// Confirm date format and time zone.
$this->drupalGet('node/' . $node1->id());
$this->assertText('2007-03-10 02:00 1', 'Date should be Chile summer time; five hours ahead of PST.');
$this->drupalGet('node/' . $node2->id());
$this->assertText('2007-03-11 05:00 0', 'Date should be Chile time; four hours ahead of PST');
$this->drupalGet('node/' . $node3->id());
$this->assertText('2007-03-21 00:00 0', 'Date should be Chile time; three hours ahead of PDT.');
// Ensure that anonymous users also use the default timezone.
$this->drupalLogout();
$this->drupalGet('node/' . $node1->id());
$this->assertText('2007-03-09 21:00 0', 'Date should be PST.');
$this->drupalGet('node/' . $node2->id());
$this->assertText('2007-03-11 01:00 0', 'Date should be PST.');
$this->drupalGet('node/' . $node3->id());
$this->assertText('2007-03-20 21:00 1', 'Date should be PDT.');
// Format a date without accessing the current user at all and
// ensure that it uses the default timezone.
$this->drupalGet('/system-test/date');
$this->assertText('2016-01-13 08:29 0', 'Date should be PST.');
}
}

View file

@ -0,0 +1,168 @@
<?php
namespace Drupal\Tests\user\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\User;
/**
* Generates text using placeholders for dummy content to check user token
* replacement.
*
* @group user
*/
class UserTokenReplaceTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'user_hooks_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ConfigurableLanguage::createFromLangcode('de')->save();
}
/**
* Creates a user, then tests the tokens generated from it.
*/
public function testUserTokenReplacement() {
$token_service = \Drupal::token();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$url_options = [
'absolute' => TRUE,
'language' => $language_interface,
];
\Drupal::state()->set('user_hooks_test_user_format_name_alter', TRUE);
\Drupal::state()->set('user_hooks_test_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());
// Generate and test tokens.
$tests = [];
$tests['[user:uid]'] = $account->id();
$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->url('canonical', $url_options);
$tests['[user:edit-url]'] = $account->url('edit-form', $url_options);
$tests['[user:last-login]'] = format_date($account->getLastLoginTime(), 'medium', '', NULL, $language_interface->getId());
$tests['[user:last-login:short]'] = format_date($account->getLastLoginTime(), 'short', '', NULL, $language_interface->getId());
$tests['[user:created]'] = format_date($account->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
$tests['[user:created:short]'] = format_date($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: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->assertFalse(in_array(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->assertEqual($output, $expected, new FormattableMarkup('User token %token replaced.', ['%token' => $input]));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
}
// Generate tokens for the anonymous user.
$anonymous_user = User::load(0);
$tests = [];
$tests['[user:uid]'] = t('not yet assigned');
$tests['[user:display-name]'] = $anonymous_user->getDisplayName();
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($anonymous_user);
$metadata_tests = [];
$metadata_tests['[user:uid]'] = $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->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', ['%token' => $input]));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
}
// 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 = \Drupal::url('user.page', [], ['absolute' => TRUE]);
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, ['user' => $account], ['langcode' => $language_interface->getId(), 'callback' => 'user_mail_tokens', 'clear' => TRUE]);
$this->assertTrue(strpos($output, $link) === 0, 'Generated URL is in interface language.');
}
// Generate tokens with the user's preferred language.
$account->preferred_langcode = 'de';
$account->save();
$link = \Drupal::url('user.page', [], ['language' => \Drupal::languageManager()->getLanguage($account->getPreferredLangcode()), 'absolute' => TRUE]);
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, ['user' => $account], ['callback' => 'user_mail_tokens', 'clear' => TRUE]);
$this->assertTrue(strpos($output, $link) === 0, "Generated URL is in the user's preferred language.");
}
// Generate tokens with one specific language.
$link = \Drupal::url('user.page', [], ['language' => \Drupal::languageManager()->getLanguage('de'), 'absolute' => TRUE]);
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->assertTrue(strpos($output, $link) === 0, "Generated URL in the requested language.");
}
}
// Generate user display name tokens when safe markup is returned.
// @see user_hooks_test_user_format_name_alter()
\Drupal::state()->set('user_hooks_test_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->assertEqual($output, $expected, new FormattableMarkup('User token %token does not escape safe markup.', ['%token' => 'display-name']));
}
}

View file

@ -0,0 +1,99 @@
<?php
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;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'content_translation', 'user', 'views'];
protected function setUp() {
$this->entityTypeId = 'user';
$this->testLanguageSelector = FALSE;
$this->name = $this->randomMachineName();
parent::setUp();
\Drupal::entityManager()->getStorage('user')->resetCache();
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
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() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$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->urlInfo('edit-form', $options);
$this->drupalGet($url);
$title = t('@title [%language translation]', [
'@title' => $entity->getTranslation($langcode)->label(),
'%language' => $languages[$langcode]->getName(),
]);
$this->assertRaw($title);
}
}
}
/**
* Test translated user deletion.
*/
public function testTranslatedUserDeletion() {
$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->drupalPostForm($url, [], t('Cancel account'));
$this->assertSession()->statusCodeEquals(200);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\user\Plugin\views\access\Permission;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Views;
/**
* Tests views perm access plugin.
*
* @group user
* @see \Drupal\user\Plugin\views\access\Permission
*/
class AccessPermissionTest extends AccessTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_access_perm'];
/**
* Tests perm access plugin.
*/
public function testAccessPerm() {
$view = Views::getView('test_access_perm');
$view->setDisplay();
$access_plugin = $view->display_handler->getPlugin('access');
$this->assertTrue($access_plugin instanceof Permission, 'Make sure the right class got instantiated.');
$this->assertEqual($access_plugin->pluginTitle(), t('Permission'));
$this->assertFalse($view->display_handler->access($this->webUser));
$this->assertTrue($view->display_handler->access($this->normalUser));
}
/**
* Tests access on render caching.
*/
public function testRenderCaching() {
$view = Views::getView('test_access_perm');
$display = &$view->storage->getDisplay('default');
$display['display_options']['cache'] = [
'type' => 'tag',
];
/** @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 without access.
$build = DisplayPluginBase::buildBasicRenderable('test_access_perm', 'default');
$account_switcher->switchTo($this->normalUser);
$result = $renderer->renderPlain($build);
$this->assertNotEqual($result, '');
// Then with access.
$build = DisplayPluginBase::buildBasicRenderable('test_access_perm', 'default');
$account_switcher->switchTo($this->webUser);
$result = $renderer->renderPlain($build);
$this->assertEqual($result, '');
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\Core\Cache\Cache;
use Drupal\system\Tests\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'];
/**
* Tests role access plugin.
*/
public function testAccessRole() {
/** @var \Drupal\views\ViewEntityInterface $view */
$view = \Drupal::entityManager()->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->assertIdentical($expected, $view->calculateDependencies()->getDependencies());
$executable = Views::executableFactory()->get($view);
$executable->setDisplay('page_1');
$access_plugin = $executable->display_handler->getPlugin('access');
$this->assertTrue($access_plugin instanceof Role, 'Make sure the right class got instantiated.');
// 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->assertResponse(403);
$this->assertCacheContext('user.roles');
$this->drupalLogin($this->normalUser);
$this->drupalGet('test-role');
$this->assertResponse(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->assertIdentical($expected, $view->calculateDependencies()->getDependencies());
$this->drupalLogin($this->webUser);
$this->drupalGet('test-role');
$this->assertResponse(403);
$this->assertCacheContext('user.roles');
$this->drupalLogout();
$this->drupalGet('test-role');
$this->assertResponse(200);
$this->assertCacheContext('user.roles');
$this->drupalLogin($this->normalUser);
$this->drupalGet('test-role');
$this->assertResponse(200);
$this->assertCacheContext('user.roles');
}
/**
* Tests access on render caching.
*/
public function testRenderCaching() {
$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->renderPlain($build);
$this->assertTrue(in_array('user.roles', $build['#cache']['contexts']));
$this->assertEqual(['config:views.view.test_access_role'], $build['#cache']['tags']);
$this->assertEqual(Cache::PERMANENT, $build['#cache']['max-age']);
$this->assertNotEqual($result, '');
// Then without access.
$build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
$account_switcher->switchTo($this->webUser);
$result = $renderer->renderPlain($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->assertTrue(in_array('user.roles', $build['#cache']['contexts']));
// $this->assertEqual([], $build['#cache']['tags']);
$this->assertEqual(Cache::PERMANENT, $build['#cache']['max-age']);
$this->assertEqual($result, '');
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
/**
* A common test base class for the user access plugin tests.
*/
abstract class AccessTestBase extends UserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public 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) {
parent::setUp($import_test_views);
$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);
$this->normalUser->save();
// @todo when all the plugin information is cached make a reset function and
// call it here.
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\views\Views;
/**
* Tests views user argument default plugin.
*
* @group user
*/
class ArgumentDefaultTest extends UserTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_plugin_argument_default_current_user'];
public function test_plugin_argument_default_current_user() {
// Create a user to test.
$account = $this->drupalCreateUser();
// Switch the user.
\Drupal::service('account_switcher')->switchTo($account);
$view = Views::getView('test_plugin_argument_default_current_user');
$view->initHandlers();
$this->assertEqual($view->argument['null']->getDefaultArgument(), $account->id(), 'Uid of the current user is used.');
// Switch back.
\Drupal::service('account_switcher')->switchBack();
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\Core\Form\FormState;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Drupal\views\Views;
/**
* Tests user argument validators for ID and name.
*
* @group user
*/
class ArgumentValidateTest extends UserTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view_argument_validate_user', 'test_view_argument_validate_username'];
/**
* A user for this test.
*
* @var \Drupal\user\UserInterface
*/
protected $account;
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->account = $this->drupalCreateUser();
}
/**
* Tests the User (ID) argument validator.
*/
public function testArgumentValidateUserUid() {
$account = $this->account;
$view = Views::getView('test_view_argument_validate_user');
$this->executeView($view);
$this->assertTrue($view->argument['null']->validateArgument($account->id()));
// Reset argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a valid numeric, but for a user that doesn't exist
$this->assertFalse($view->argument['null']->validateArgument(32));
$form = [];
$form_state = new FormState();
$view->argument['null']->buildOptionsForm($form, $form_state);
$sanitized_id = ArgumentPluginBase::encodeValidatorId('entity:user');
$this->assertTrue($form['validate']['options'][$sanitized_id]['roles']['#states']['visible'][':input[name="options[validate][options][' . $sanitized_id . '][restrict_roles]"]']['checked']);
}
/**
* Tests the UserName argument validator.
*/
public function testArgumentValidateUserName() {
$account = $this->account;
$view = Views::getView('test_view_argument_validate_username');
$this->executeView($view);
$this->assertTrue($view->argument['null']->validateArgument($account->getUsername()));
// Reset argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a valid string, but for a user that doesn't exist
$this->assertFalse($view->argument['null']->validateArgument($this->randomMachineName()));
}
}

View file

@ -0,0 +1,132 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\Component\Render\FormattableMarkup;
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user_access_test'];
/**
* 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() {
// 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->assertResponse(403, 'The user may not be edited.');
// Test blocking the account "no_edit".
$edit = [
'user_bulk_form[' . ($no_edit_user->id() - 1) . ']' => TRUE,
'action' => 'user_block_user_action',
];
$this->drupalPostForm('test-user-bulk-form', $edit, t('Apply to selected items'));
$this->assertResponse(200);
$this->assertRaw(new FormattableMarkup('No access to execute %action on the @entity_type_label %entity_label.', [
'%action' => 'Block the selected user(s)',
'@entity_type_label' => 'User',
'%entity_label' => $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->drupalPostForm('test-user-bulk-form', $edit, t('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->drupalPostForm('test-user-bulk-form', $edit, t('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() {
// 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->assertResponse(403, 'The user "no_delete" may not be deleted.');
// Ensure that the account "may_delete" *can* be deleted.
$this->drupalGet('user/' . $account2->id() . '/cancel');
$this->assertResponse(200, 'The user "may_delete" may be deleted.');
// 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->drupalPostForm('test-user-bulk-form', $edit, t('Apply to selected items'));
$edit = [
'user_cancel_method' => 'user_cancel_delete',
];
$this->drupalPostForm(NULL, $edit, t('Cancel accounts'));
// 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.');
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['views_ui'];
/**
* 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() {
// Log in as a user without 'administer users'.
$this->drupalLogin($this->drupalCreateUser(['administer permissions']));
$user_storage = $this->container->get('entity.manager')->getStorage('user');
// Create an user which actually can change users.
$this->drupalLogin($this->drupalCreateUser(['administer users']));
$this->drupalGet('test-user-bulk-form');
$result = $this->cssSelect('#edit-action option');
$this->assertTrue(count($result) > 0);
// Test submitting the page with no selection.
$edit = [
'action' => 'user_block_user_action',
];
$this->drupalPostForm('test-user-bulk-form', $edit, t('Apply to selected items'));
$this->assertText(t('No users selected.'));
// Assign a role to a user.
$account = $user_storage->load($this->users[0]->id());
$roles = user_role_names(TRUE);
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->drupalPostForm(NULL, $edit, t('Apply to selected items'));
// Re-load the user and check their roles.
$user_storage->resetCache([$account->id()]);
$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->drupalPostForm(NULL, $edit, t('Apply to selected items'));
// Re-load the user and check their roles.
$user_storage->resetCache([$account->id()]);
$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->assertRaw($account->label(), 'The user is found in the table.');
$edit = [
'user_bulk_form[1]' => TRUE,
'action' => 'user_block_user_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
// Re-load the user and check their status.
$user_storage->resetCache([$account->id()]);
$account = $user_storage->load($account->id());
$this->assertTrue($account->isBlocked(), 'The user is blocked.');
$this->assertNoRaw($account->label(), 'The user is not found in the table.');
// 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->assertText($this->config('user.settings')->get('anonymous'));
// Attempt to block the anonymous user.
$edit = [
'user_bulk_form[0]' => TRUE,
'action' => 'user_block_user_action',
];
$this->drupalPostForm(NULL, $edit, t('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->drupalPostForm('admin/structure/views/nojs/handler/test_user_bulk_form/default/field/user_bulk_form', $edit, t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
$this->drupalGet('test-user-bulk-form');
$this->assertNoOption('edit-action', $action_id);
$edit['options[include_exclude]'] = 'include';
$this->drupalPostForm('admin/structure/views/nojs/handler/test_user_bulk_form/default/field/user_bulk_form', $edit, t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
$this->drupalGet('test-user-bulk-form');
$this->assertOption('edit-action', $action_id);
}
/**
* Tests the user bulk form with a combined field filter on the bulk column.
*/
public function testBulkFormCombineFilter() {
// Add a user.
User::load($this->users[0]->id());
$view = Views::getView('test_user_bulk_form_combine_filter');
$errors = $view->validate();
$this->assertEqual(reset($errors['default']), t('Field %field set in %filter is not usable for this filter type. Combined field filter only works for simple fields.', ['%field' => 'User: Bulk update', '%filter' => 'Global: Combine fields filter']));
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* 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'];
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'user_test_views', 'views_ui'];
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
ViewTestData::createTestViews(get_class($this), ['user_test_views']);
$this->enableViewsTestModule();
}
/**
* Tests basic filter handler settings in the UI.
*/
public function testHandlerUI() {
$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->assertLink('User: Permission (= View user information)');
$this->drupalPostForm(NULL, [], 'Save');
// Verify that we can save the view.
$this->assertNoText('No valid values found on filter: User: Permission.');
$this->assertText('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->drupalPostForm('admin/structure/views/nojs/handler/test_filter_permission/default/filter/permission', $edit, 'Apply');
$this->assertLink('User: Permission (or View us…)');
$this->drupalPostForm(NULL, [], 'Save');
// Verify that we can save the view.
$this->assertNoText('No valid values found on filter: User: Permission.');
$this->assertText('The view test_filter_permission has been saved.');
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\views\Views;
/**
* Tests the handler of the user: uid Argument.
*
* @group user
*/
class HandlerArgumentUserUidTest extends UserTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_user_uid_argument'];
/**
* Tests the generated title of an user: uid argument.
*/
public function testArgumentTitle() {
$view = Views::getView('test_user_uid_argument');
// Tests an invalid user uid.
$this->executeView($view, [rand(1000, 10000)]);
$this->assertFalse($view->getTitle());
$view->destroy();
// Tests a valid user.
$account = $this->drupalCreateUser();
$this->executeView($view, [$account->id()]);
$this->assertEqual($view->getTitle(), $account->label());
$view->destroy();
// Tests the anonymous user.
$anonymous = $this->config('user.settings')->get('anonymous');
$this->executeView($view, [0]);
$this->assertEqual($view->getTitle(), $anonymous);
$view->destroy();
$view->getDisplay()->getHandler('argument', 'uid')->options['break_phrase'] = TRUE;
$this->executeView($view, [$account->id() . ',0']);
$this->assertEqual($view->getTitle(), $account->label() . ', ' . $anonymous);
$view->destroy();
$view->getDisplay()->getHandler('argument', 'uid')->options['break_phrase'] = TRUE;
$this->executeView($view, ['0,' . $account->id()]);
$this->assertEqual($view->getTitle(), $anonymous . ', ' . $account->label());
$view->destroy();
}
}

View file

@ -0,0 +1,46 @@
<?php
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'];
public function testRole() {
// Create a couple of roles for the view.
$rolename_a = 'a' . $this->randomMachineName(8);
$this->drupalCreateRole(['access content'], $rolename_a, '<em>' . $rolename_a . '</em>', 9);
$rolename_b = 'b' . $this->randomMachineName(8);
$this->drupalCreateRole(['access content'], $rolename_b, $rolename_b, 8);
$rolename_not_assigned = $this->randomMachineName(8);
$this->drupalCreateRole(['access content'], $rolename_not_assigned, $rolename_not_assigned);
// Add roles to user 1.
$user = User::load(1);
$user->addRole($rolename_a);
$user->addRole($rolename_b);
$user->save();
$this->drupalLogin($this->createUser(['access user profiles']));
$this->drupalGet('/test-views-handler-field-role');
$this->assertText($rolename_b . Html::escape('<em>' . $rolename_a . '</em>'), 'View test_views_handler_field_role renders role assigned to user in the correct order and markup in role names is escaped.');
$this->assertNoText($rolename_not_assigned, 'View test_views_handler_field_role does not render a role not assigned to a user.');
}
}

View file

@ -0,0 +1,81 @@
<?php
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'];
public function testUserName() {
/** @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 = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertTrue(strpos($render, $anon_name) !== FALSE, 'For user 0 it should use the default anonymous name by default.');
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $new_user) {
return $view->field['name']->advancedRender($view->result[$new_user->id()]);
});
$this->assertTrue(strpos($render, $new_user->getDisplayName()) !== FALSE, 'If link to user is checked the username should be part of the output.');
$this->assertTrue(strpos($render, 'user/' . $new_user->id()) !== FALSE, '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 = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $new_user) {
return $view->field['name']->advancedRender($view->result[$new_user->id()]);
});
$this->assertEqual($render, $new_user->getDisplayName(), '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() {
/** @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 = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
}
}

View file

@ -0,0 +1,183 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\views\Views;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests the handler of the user: name filter.
*
* @group user
* @see Views\user\Plugin\views\filter\Name
*/
class HandlerFilterUserNameTest extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['views_ui', 'user_test_views'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_user_name'];
/**
* Accounts used by this test.
*
* @var array
*/
protected $accounts = [];
/**
* Usernames of $accounts.
*
* @var array
*/
protected $names = [];
/**
* Stores the column map for this testCase.
*
* @var array
*/
public $columnMap = [
'uid' => 'uid',
];
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
ViewTestData::createTestViews(get_class($this), ['user_test_views']);
$this->enableViewsTestModule();
$this->accounts = [];
$this->names = [];
for ($i = 0; $i < 3; $i++) {
$this->accounts[] = $account = $this->drupalCreateUser();
$this->names[] = $account->label();
}
}
/**
* Tests just using the filter.
*/
public function testUserNameApi() {
$view = Views::getView('test_user_name');
$view->initHandlers();
$view->filter['uid']->value = [$this->accounts[0]->id()];
$this->executeView($view);
$this->assertIdenticalResultset($view, [['uid' => $this->accounts[0]->id()]], $this->columnMap);
$this->assertEqual($view->filter['uid']->getValueOptions(), NULL);
}
/**
* Tests using the user interface.
*/
public function testAdminUserInterface() {
$admin_user = $this->drupalCreateUser(['administer views', 'administer site configuration']);
$this->drupalLogin($admin_user);
$path = 'admin/structure/views/nojs/handler/test_user_name/default/filter/uid';
$this->drupalGet($path);
// Pass in an invalid username, the validation should catch it.
$users = [$this->randomMachineName()];
$users = array_map('strtolower', $users);
$edit = [
'options[value]' => implode(', ', $users),
];
$this->drupalPostForm($path, $edit, t('Apply'));
$this->assertRaw(t('There are no entities matching "%value".', ['%value' => implode(', ', $users)]));
// Pass in an invalid username and a valid username.
$random_name = $this->randomMachineName();
$users = [$random_name, $this->names[0]];
$users = array_map('strtolower', $users);
$edit = [
'options[value]' => implode(', ', $users),
];
$users = [$users[0]];
$this->drupalPostForm($path, $edit, t('Apply'));
$this->assertRaw(t('There are no entities matching "%value".', ['%value' => implode(', ', $users)]));
// Pass in just valid usernames.
$users = $this->names;
$users = array_map('strtolower', $users);
$edit = [
'options[value]' => implode(', ', $users),
];
$this->drupalPostForm($path, $edit, t('Apply'));
$this->assertNoRaw(t('There are no entities matching "%value".', ['%value' => implode(', ', $users)]));
}
/**
* Tests exposed filters.
*/
public function testExposedFilter() {
$path = 'test_user_name';
$options = [];
// Pass in an invalid username, the validation should catch it.
$users = [$this->randomMachineName()];
$users = array_map('strtolower', $users);
$options['query']['uid'] = implode(', ', $users);
$this->drupalGet($path, $options);
$this->assertRaw(t('There are no entities matching "%value".', ['%value' => implode(', ', $users)]));
// Pass in an invalid target_id in for the entity_autocomplete value format.
// There should be no errors, but all results should be returned as the
// default value for the autocomplete will not match any users so should
// be empty.
$options['query']['uid'] = [['target_id' => 9999]];
$this->drupalGet($path, $options);
// The actual result should contain all of the user ids.
foreach ($this->accounts as $account) {
$this->assertRaw($account->id());
}
// Pass in an invalid username and a valid username.
$users = [$this->randomMachineName(), $this->names[0]];
$users = array_map('strtolower', $users);
$options['query']['uid'] = implode(', ', $users);
$users = [$users[0]];
$this->drupalGet($path, $options);
$this->assertRaw(t('There are no entities matching "%value".', ['%value' => implode(', ', $users)]));
// Pass in just valid usernames.
$users = $this->names;
$options['query']['uid'] = implode(', ', $users);
$this->drupalGet($path, $options);
$this->assertNoRaw('Unable to find user');
// The actual result should contain all of the user ids.
foreach ($this->accounts as $account) {
$this->assertRaw($account->id());
}
// Pass in just valid user IDs in the entity_autocomplete target_id format.
$options['query']['uid'] = array_map(function ($account) {
return ['target_id' => $account->id()];
}, $this->accounts);
$this->drupalGet($path, $options);
$this->assertNoRaw('Unable to find user');
// The actual result should contain all of the user ids.
foreach ($this->accounts as $account) {
$this->assertRaw($account->id());
}
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
use Drupal\views\Views;
/**
* Tests the representative node relationship for users.
*
* @group user
*/
class RelationshipRepresentativeNodeTest extends UserTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_groupwise_user'];
/**
* Tests the relationship.
*/
public function testRelationship() {
$view = Views::getView('test_groupwise_user');
$this->executeView($view);
$map = ['node_field_data_users_field_data_nid' => 'nid', 'uid' => 'uid'];
$expected_result = [
[
'uid' => $this->users[1]->id(),
'nid' => $this->nodes[1]->id(),
],
[
'uid' => $this->users[0]->id(),
'nid' => $this->nodes[0]->id(),
],
];
$this->assertIdenticalResultset($view, $expected_result, $map);
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\Tests\user\Functional\Views;
/**
* Tests the handler of the user: roles argument.
*
* @group user
* @see \Drupal\user\Plugin\views\argument\RolesRid
*/
class RolesRidArgumentTest extends UserTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_user_roles_rid'];
/**
* Tests the generated title of a user: roles argument.
*/
public function testArgumentTitle() {
$role_id = $this->createRole([], 'markup_role_name', '<em>Role name with markup</em>');
$user = $this->createUser();
$user->addRole($role_id);
$user->save();
$this->drupalGet('/user_roles_rid_test/markup_role_name');
$this->assertEscaped('<em>Role name with markup</em>');
}
}

Some files were not shown because too many files have changed in this diff Show more