From e7f061d5d40dca7b4aafabf903652fab1cd436ab Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Fri, 15 Jun 2018 12:50:06 +0100
Subject: [PATCH] Issue #2773645 by AdamPS, Xilis, yogeshmpawar, Wim Leers,
 Berdir: Allow hook_entity_field_access() to grant field-level access to User
 fields: 'forbidden' -> 'neutral'

---
 .../user/src/UserAccessControlHandler.php     |   8 +-
 .../user_access_test/user_access_test.module  |  16 +
 .../user_access_test.permissions.yml          |   2 +
 .../views.view.test_user_fields_access.yml    | 478 ++++++++++++++++++
 .../Views/UserFieldsAccessChangeTest.php      |  53 ++
 5 files changed, 553 insertions(+), 4 deletions(-)
 create mode 100644 core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml
 create mode 100644 core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
 create mode 100644 core/modules/user/tests/src/Functional/Views/UserFieldsAccessChangeTest.php

diff --git a/core/modules/user/src/UserAccessControlHandler.php b/core/modules/user/src/UserAccessControlHandler.php
index 8ff01d144763..19fe6b692c34 100644
--- a/core/modules/user/src/UserAccessControlHandler.php
+++ b/core/modules/user/src/UserAccessControlHandler.php
@@ -106,7 +106,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
           return AccessResult::allowed()->cachePerPermissions()->cachePerUser();
         }
         else {
-          return AccessResult::forbidden();
+          return AccessResult::neutral();
         }
 
       case 'preferred_langcode':
@@ -116,7 +116,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
         // Allow view access to own mail address and other personalization
         // settings.
         if ($operation == 'view') {
-          return $is_own_account ? AccessResult::allowed()->cachePerUser() : AccessResult::forbidden();
+          return $is_own_account ? AccessResult::allowed()->cachePerUser() : AccessResult::neutral();
         }
         // Anyone that can edit the user can also edit this field.
         return AccessResult::allowed()->cachePerPermissions();
@@ -127,14 +127,14 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
 
       case 'created':
         // Allow viewing the created date, but not editing it.
-        return ($operation == 'view') ? AccessResult::allowed() : AccessResult::forbidden();
+        return ($operation == 'view') ? AccessResult::allowed() : AccessResult::neutral();
 
       case 'roles':
       case 'status':
       case 'access':
       case 'login':
       case 'init':
-        return AccessResult::forbidden();
+        return AccessResult::neutral();
     }
 
     return parent::checkFieldAccess($operation, $field_definition, $account, $items);
diff --git a/core/modules/user/tests/modules/user_access_test/user_access_test.module b/core/modules/user/tests/modules/user_access_test/user_access_test.module
index 470a76a2dc01..ff9f0b2db874 100644
--- a/core/modules/user/tests/modules/user_access_test/user_access_test.module
+++ b/core/modules/user/tests/modules/user_access_test/user_access_test.module
@@ -6,6 +6,9 @@
  */
 
 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;
 
 /**
@@ -22,3 +25,16 @@ function user_access_test_user_access(User $entity, $operation, $account) {
   }
   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();
+}
diff --git a/core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml b/core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml
new file mode 100644
index 000000000000..dadf7ba30b5a
--- /dev/null
+++ b/core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml
@@ -0,0 +1,2 @@
+sub-admin:
+  title: 'Administer users with no roles'
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
new file mode 100644
index 000000000000..5bad08f090b6
--- /dev/null
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
@@ -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: {  }
diff --git a/core/modules/user/tests/src/Functional/Views/UserFieldsAccessChangeTest.php b/core/modules/user/tests/src/Functional/Views/UserFieldsAccessChangeTest.php
new file mode 100644
index 000000000000..1a238579c91c
--- /dev/null
+++ b/core/modules/user/tests/src/Functional/Views/UserFieldsAccessChangeTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Tests\user\Functional\Views;
+
+/**
+ * Checks if user fields access permissions can be modified by other modules.
+ *
+ * @group user
+ */
+class UserFieldsAccessChangeTest 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_fields_access'];
+
+  /**
+   * Tests if another module can change field access.
+   */
+  public function testUserFieldAccess() {
+    $path = 'test_user_fields_access';
+    $this->drupalGet($path);
+
+    // User has access to name and created date by default.
+    $this->assertText(t('Name'));
+    $this->assertText(t('Created'));
+
+    // User does not by default have access to init, mail and status.
+    $this->assertNoText(t('Init'));
+    $this->assertNoText(t('Email'));
+    $this->assertNoText(t('Status'));
+
+    // Assign sub-admin role to grant extra access.
+    $user = $this->drupalCreateUser(['sub-admin']);
+    $this->drupalLogin($user);
+    $this->drupalGet($path);
+
+    // Access for init, mail and status is added in hook_entity_field_access().
+    $this->assertText(t('Init'));
+    $this->assertText(t('Email'));
+    $this->assertText(t('Status'));
+  }
+
+}
-- 
GitLab