--- 1349080-477.patch 2021-01-06 16:10:07.213708200 +0100 +++ 1349080-483.patch 2021-04-02 09:37:50.405631200 +0200 @@ -69,721 +69,6 @@ } } } -diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module -index 6ef55224d3..36219a1339 100644 ---- a/core/modules/node/tests/modules/node_access_test/node_access_test.module -+++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module -@@ -79,7 +79,7 @@ function node_access_test_node_grants($account, $op) { - function node_access_test_node_access_records(NodeInterface $node) { - $grants = []; - // For NodeAccessBaseTableTestCase, only set records for private nodes. -- if (!\Drupal::state()->get('node_access_test.private') || $node->private->value) { -+ if (!\Drupal::state()->get('node_access_test.private') || (isset($node->private) && $node->private->value)) { - // Groups 8888 and 8889 for the node_access_test realm both receive a view - // grant for all controlled nodes. See node_access_test_node_grants(). - $grants[] = [ -diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml -new file mode 100644 -index 0000000000..766f656fc2 ---- /dev/null -+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml -@@ -0,0 +1,337 @@ -+langcode: en -+status: true -+dependencies: -+ config: -+ - node.type.page -+ module: -+ - node -+ - user -+id: test_node_access_join -+label: 'Test Node Access Join' -+module: views -+description: '' -+tag: '' -+base_table: node_field_data -+base_field: nid -+display: -+ default: -+ display_plugin: default -+ id: default -+ display_title: Master -+ position: 0 -+ display_options: -+ access: -+ type: perm -+ options: -+ perm: 'access content' -+ 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: 1000 -+ 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: table -+ row: -+ type: fields -+ fields: -+ title: -+ id: title -+ table: node_field_data -+ field: title -+ entity_type: node -+ entity_field: title -+ 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 -+ settings: -+ link_to_entity: true -+ plugin_id: field -+ relationship: none -+ group_type: group -+ admin_label: '' -+ label: Title -+ 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: string -+ 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 -+ title_1: -+ id: title_1 -+ table: node_field_data -+ field: title -+ relationship: related_article -+ group_type: group -+ admin_label: '' -+ label: Article -+ 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: string -+ 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: node -+ entity_field: title -+ plugin_id: field -+ title_2: -+ id: title_2 -+ table: node_field_data -+ field: title -+ relationship: related_article_2 -+ group_type: group -+ admin_label: '' -+ label: 'Article 2' -+ 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: string -+ 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: node -+ entity_field: title -+ plugin_id: field -+ filters: -+ status: -+ value: '1' -+ table: node_field_data -+ field: status -+ plugin_id: boolean -+ entity_type: node -+ entity_field: status -+ id: status -+ expose: -+ operator: '' -+ group: 1 -+ type: -+ id: type -+ table: node_field_data -+ field: type -+ value: -+ page: page -+ entity_type: node -+ entity_field: type -+ plugin_id: bundle -+ sorts: -+ title: -+ id: title -+ table: node_field_data -+ field: title -+ relationship: none -+ group_type: group -+ admin_label: '' -+ order: ASC -+ exposed: false -+ expose: -+ label: '' -+ entity_type: node -+ entity_field: title -+ plugin_id: standard -+ title: 'Test Reference Access' -+ header: { } -+ footer: { } -+ empty: { } -+ relationships: -+ related_article: -+ id: related_article -+ table: node__related_article -+ field: related_article -+ relationship: none -+ group_type: group -+ admin_label: 'Page related article' -+ required: false -+ plugin_id: standard -+ related_article_2: -+ id: related_article_2 -+ table: node__related_article -+ field: related_article -+ relationship: related_article -+ group_type: group -+ admin_label: 'Article related article' -+ required: false -+ plugin_id: standard -+ arguments: { } -+ display_extenders: { } -+ cache_metadata: -+ max-age: 0 -+ contexts: -+ - 'languages:language_content' -+ - 'languages:language_interface' -+ - url.query_args -+ - 'user.node_grants:view' -+ - user.permissions -+ tags: { } -+ page_1: -+ display_plugin: page -+ id: page_1 -+ display_title: Page -+ position: 1 -+ display_options: -+ display_extenders: { } -+ path: test-node-access-join -+ cache_metadata: -+ max-age: 0 -+ contexts: -+ - 'languages:language_content' -+ - 'languages:language_interface' -+ - url.query_args -+ - 'user.node_grants:view' -+ - user.permissions -+ tags: { } -diff --git a/core/modules/node/tests/src/Functional/NodeAccessJoinTest.php b/core/modules/node/tests/src/Functional/NodeAccessJoinTest.php -new file mode 100644 -index 0000000000..3458cd92a6 ---- /dev/null -+++ b/core/modules/node/tests/src/Functional/NodeAccessJoinTest.php -@@ -0,0 +1,353 @@ -+ 'related_article', -+ 'entity_type' => 'node', -+ 'translatable' => FALSE, -+ 'entity_types' => [], -+ 'settings' => [ -+ 'target_type' => 'node', -+ ], -+ 'type' => 'entity_reference', -+ ]); -+ $field_storage->save(); -+ $field = FieldConfig::create([ -+ 'field_name' => 'related_article', -+ 'entity_type' => 'node', -+ 'bundle' => 'page', -+ 'label' => 'Related Article', -+ 'settings' => [ -+ 'handler' => 'default', -+ 'handler_settings' => [ -+ // Reference a single vocabulary. -+ 'target_bundles' => [ -+ 'article', -+ ], -+ ], -+ ], -+ ]); -+ $field->save(); -+ -+ $entity_display = \Drupal::service('entity_display.repository'); -+ $entity_display->getViewDisplay('node', 'page', 'default') -+ ->setComponent('related_article') -+ ->save(); -+ $entity_display->getFormDisplay('node', 'page', 'default') -+ ->setComponent('related_article', [ -+ 'type' => 'entity_reference_autocomplete', -+ ]) -+ ->save(); -+ -+ $field = FieldConfig::create([ -+ 'field_name' => 'related_article', -+ 'entity_type' => 'node', -+ 'bundle' => 'article', -+ 'label' => 'Related Article', -+ 'settings' => [ -+ 'handler' => 'default', -+ 'handler_settings' => [ -+ // Reference a single vocabulary. -+ 'target_bundles' => [ -+ 'article', -+ ], -+ ], -+ ], -+ ]); -+ $field->save(); -+ -+ $entity_display->getViewDisplay('node', 'article', 'default') -+ ->setComponent('related_article') -+ ->save(); -+ $entity_display->getFormDisplay('node', 'article', 'default') -+ ->setComponent('related_article', [ -+ 'type' => 'entity_reference_autocomplete', -+ ]) -+ ->save(); -+ -+ node_access_rebuild(); -+ \Drupal::state()->set('node_access_test.private', TRUE); -+ } -+ -+ /** -+ * Tests the accessibility of joined nodes. -+ * -+ * - Create two users with "access content" and "create article" permissions -+ * who can each access their own private articles but not others'. -+ * - Create article-type nodes with and without references to other articles. -+ * The articles and references represent all possible combinations of the -+ * tested access types. -+ * - Create page-type nodes referencing each of the articles, as well as a -+ * page with no reference. -+ * - Use a custom view that creates two joins between nodes and has a -+ * node_access tag. The view lists the page nodes, the article -+ * referenced by each page node, and the article referenced by each -+ * article. -+ * -+ * - Login with the author user and check that user does not have access to -+ * private nodes created by other users. Test access using total row -+ * count as well as checking for presence of individual page titles. -+ * - Repeat tests using a user with only the "access content" permission, -+ * confirming this user does not have access to any private nodes. -+ * - Repeat tests using a user with "access content" and "node test view" -+ * permissions, confirming this user sees the complete view. -+ */ -+ public function testNodeAccessJoin() { -+ -+ $permissions = ['access content', 'create article content']; -+ // User to add articles and test author access. -+ $this->authorUser = $this->drupalCreateUser($permissions); -+ // Another user to add articles whose private articles can not be accessed -+ // by authorUser. -+ $this->otherUser = $this->drupalCreateUser($permissions); -+ -+ // Create the articles. The articles are stored in an array keyed by -+ // $article and $reference2, where $article is the access type of the -+ // article itself, and $reference2 is the access type of the reference -+ // linked to by the article. 'public' articles are created by otherUser with -+ // private=0. 'private' articles are created by otherUser with private=1. -+ // 'author_public' articles are created by authorUser with private=0. -+ // 'author_private' articles are created by authorUser with private=1. -+ // 'no_reference' is used for references when there is no related article. -+ $access_type = ['public', 'private', 'author_public', 'author_private']; -+ $reference_access_type = array_merge(['no_reference'], $access_type); -+ -+ foreach ($reference_access_type as $reference2) { -+ foreach ($access_type as $article) { -+ $is_author = (substr($article, 0, 6) == 'author'); -+ $is_private = (substr($article, -7) == 'private'); -+ $edit = [ -+ 'type' => 'article', -+ 'uid' => $is_author ? $this->authorUser->id() : $this->otherUser->id(), -+ ]; -+ $edit['private'][0]['value'] = $is_private; -+ // The article names provide the access status of the article and the -+ // access status of the related article, if any. The naming system -+ // ensures that the text 'Article $article' will only appear in the view -+ // if an article with that access type is displayed in the view. The -+ // text '$article' alone will appear in the titles of other nodes that -+ // reference an article. -+ $edit['title'] = "Article $article - $reference2"; -+ if ($reference2 != 'no_reference') { -+ $edit['related_article'][0]['target_id'] = $this->articles[$reference2]['no_reference']; -+ } -+ $node = $this->drupalCreateNode($edit); -+ $this->articles[$article][$reference2] = $node->id(); -+ -+ $this->assertEquals((int) $is_private, (int) $node->private->value, 'The private status of the article node was properly set in the node_access_test table.' . $node->uid->target_id); -+ if ($reference2 != 'no_reference') { -+ $this->assertEquals((int) $this->articles[$reference2]['no_reference'], (int) $node->related_article->target_id, 'Proper article attached to article.'); -+ } -+ } -+ } -+ -+ // Add a blank 'no_reference' entry to the article list, so that a page with -+ // no reference gets created. -+ $this->articles['no_reference']['no_reference'] = NULL; -+ -+ $total = 0; -+ $count_s_total = $count_s2_total = 0; -+ $count_s_public = $count_s2_public = 0; -+ $count_s_author = $count_s2_author = 0; -+ $total_public = $total_author = 0; -+ -+ // Create page nodes referencing each article, as a page without reference. -+ foreach ($this->articles as $reference => $list) { -+ foreach ($list as $reference2 => $article_nid) { -+ $title = "Page - $reference"; -+ if ($reference != 'no_reference') { -+ $title .= " - $reference2"; -+ } -+ $edit = [ -+ 'type' => 'page', -+ 'title' => $title, -+ ]; -+ if ($article_nid) { -+ $edit['related_article'][0]['target_id'] = $article_nid; -+ } -+ $node = $this->drupalCreateNode($edit); -+ if ($article_nid) { -+ $this->assertEquals((int) $article_nid, (int) $node->related_article->target_id, 'Proper article attached to page.'); -+ } -+ -+ // Calculate totals expected for each user type. -+ $total++; -+ // Total number of primary and secondary references. -+ if ($reference != 'no_reference') { -+ $count_s_total++; -+ if ($reference2 != 'no_reference') { -+ $count_s2_total++; -+ } -+ } -+ // Public users only see 'public' and 'author_public' articles. -+ if (substr($reference, -6) == 'public') { -+ $count_s_public++; -+ if (substr($reference2, -6) == 'public') { -+ $count_s2_public++; -+ } -+ } -+ // authorUser sees 'public','author_public', 'author_private' articles. -+ if (substr($reference, -6) == 'public' || substr($reference, 0, 6) == 'author') { -+ $count_s_author++; -+ if (substr($reference2, -6) == 'public' || substr($reference2, 0, 6) == 'author') { -+ $count_s2_author++; -+ } -+ } -+ -+ // $total_public and $total_author are not currently in use -- but -+ // represent the totals when joins are handled by adding an is-null -+ // check (i.e., if inaccessible references caused the entire row to be -+ // be hidden from view, instead of hiding just one cell of the table). -+ // Count of pages where all related articles are accessible by -+ // public users. -+ if (substr($reference, -7) != 'private' && substr($reference2, -7) != 'private') { -+ $total_public++; -+ } -+ // Count of pages where all related articles are accessible by -+ // authorUser. -+ if ($reference != 'private' && $reference2 != 'private') { -+ $total_author++; -+ } -+ } -+ } -+ -+ // Generate a view listing all the pages, and check the view's content for -+ // users with three different access levels. -+ ViewTestData::createTestViews(get_class($this), ['node_test_views']); -+ -+ // Check the author of the 'author' articles. -+ $this->drupalLogin($this->authorUser); -+ $this->drupalGet('test-node-access-join'); -+ $chk_total = count($this->xpath("//td[@headers='view-title-table-column']")); -+ $this->assertEquals($chk_total, $total, 'Author should see ' . $total . ' rows. Actual: ' . $chk_total); -+ $chk_total = count($this->xpath("//td[@headers='view-title-1-table-column']/a")); -+ $this->assertEquals($chk_total, $count_s_author, 'Author should see ' . $count_s_author . ' primary references. Actual: ' . $chk_total); -+ $chk_total = count($this->xpath("//td[@headers='view-title-2-table-column']/a")); -+ $this->assertEquals($chk_total, $count_s2_author, 'Author should see ' . $count_s2_author . ' secondary references. Actual: ' . $chk_total); -+ -+ $session = $this->assertSession(); -+ $session->pageTextContains('Page - no_reference'); -+ $session->pageTextContains('Page - public - no_reference'); -+ $session->pageTextContains('Page - public - public'); -+ $session->pageTextContains('Page - author_private - no_reference'); -+ $session->pageTextContains('Article public'); -+ $session->pageTextNotContains('Article private'); -+ $session->pageTextContains('Article author_public'); -+ $session->pageTextContains('Article author_private'); -+ -+ // Check a regular user who did not author any articles. -+ $this->regularUser = $this->drupalCreateUser(['access content']); -+ $this->drupalLogin($this->regularUser); -+ $this->drupalGet('test-node-access-join'); -+ $chk_total = count($this->xpath("//td[@headers='view-title-table-column']")); -+ $this->assertEquals($chk_total, $total, 'Public user should see ' . $total . ' rows. Actual: ' . $chk_total); -+ $chk_total = count($this->xpath("//td[@headers='view-title-1-table-column']/a")); -+ $this->assertEquals($chk_total, $count_s_public, 'Public user should see ' . $count_s_public . ' primary references. Actual: ' . $chk_total); -+ $chk_total = count($this->xpath("//td[@headers='view-title-2-table-column']/a")); -+ $this->assertEquals($chk_total, $count_s2_public, 'Public user should see ' . $count_s2_public . ' secondary references. Actual: ' . $chk_total); -+ $session->pageTextContains('Page - no_reference'); -+ $session->pageTextContains('Page - public - no_reference'); -+ $session->pageTextContains('Page - public - public'); -+ $session->pageTextContains('Article public'); -+ $session->pageTextNotContains('Article private'); -+ $session->pageTextContains('Article author_public'); -+ $session->pageTextNotContains('Article author_private'); -+ -+ // Check that a user with 'node test view' permission, can view all pages -+ // and articles. -+ $this->accessUser = $this->drupalCreateUser([ -+ 'access content', -+ 'node test view', -+ ]); -+ $this->drupalLogin($this->accessUser); -+ $this->drupalGet('test-node-access-join'); -+ $chk_total = count($this->xpath("//td[@headers='view-title-table-column']")); -+ $this->assertEquals($chk_total, $total, 'Full-access user should see ' . $total . ' rows. Actual: ' . $chk_total); -+ $chk_total = count($this->xpath("//td[@headers='view-title-1-table-column']/a")); -+ $this->assertEquals($chk_total, $count_s_total, 'Full-access user should see ' . $count_s_total . ' primary references. Actual: ' . $chk_total); -+ $chk_total = count($this->xpath("//td[@headers='view-title-2-table-column']/a")); -+ $this->assertEquals($chk_total, $count_s2_total, 'Full-access user should see ' . $count_s2_total . ' secondary references. Actual: ' . $chk_total); -+ $session->pageTextContains('Page - no_reference'); -+ $session->pageTextContains('Page - public - no_reference'); -+ $session->pageTextContains('Page - public - public'); -+ $session->pageTextContains('Page - author_private - no_reference'); -+ $session->pageTextContains('Article public'); -+ $session->pageTextContains('Article private'); -+ $session->pageTextContains('Article author_public'); -+ $session->pageTextContains('Article author_private'); -+ } -+ -+} diff --git a/core/modules/node/tests/src/Kernel/NodeAccessTest.php b/core/modules/node/tests/src/Kernel/NodeAccessTest.php index c983667bc7..171fd5ca20 100644 --- a/core/modules/node/tests/src/Kernel/NodeAccessTest.php