function NodeAccessJoinTest::testNodeAccessJoin

Same name in other branches
  1. 10 core/modules/node/tests/src/Functional/NodeAccessJoinTest.php \Drupal\Tests\node\Functional\NodeAccessJoinTest::testNodeAccessJoin()

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.

File

core/modules/node/tests/src/Functional/NodeAccessJoinTest.php, line 171

Class

NodeAccessJoinTest
Tests Node Access on join.

Namespace

Drupal\Tests\node\Functional

Code

public function testNodeAccessJoin() : void {
    $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 = str_starts_with($article, 'author');
            $is_private = str_ends_with($article, '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 (str_ends_with($reference, 'public')) {
                $count_s_public++;
                if (str_ends_with($reference2, 'public')) {
                    $count_s2_public++;
                }
            }
            // authorUser sees 'public','author_public', 'author_private' articles.
            if (str_ends_with($reference, 'public') || str_starts_with($reference, 'author')) {
                $count_s_author++;
                if (str_ends_with($reference2, 'public') || str_starts_with($reference2, '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
            // 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 (!str_ends_with($reference, 'private') && !str_ends_with($reference2, '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');
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.