entity_mesh-1.1.1/tests/src/Kernel/EntityMeshPermissionsTest.php

tests/src/Kernel/EntityMeshPermissionsTest.php
<?php

namespace Drupal\Tests\entity_mesh\Kernel;

use Drupal\Core\Session\AccountInterface;
use Drupal\filter\Entity\FilterFormat;
use Drupal\node\Entity\Node;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\Role;

/**
 * Tests entity_mesh permissions handling.
 *
 * @group entity_mesh
 */
class EntityMeshPermissionsTest extends KernelTestBase {

  use ContentTypeCreationTrait;
  use UserCreationTrait;

  /**
   * Modules to enable.
   *
   * @var array<string>
   */
  protected static $modules = [
    'system',
    'node',
    'user',
    'field',
    'filter',
    'text',
    'language',
    'entity_mesh',
    'path_alias',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Install the necessary schemas.
    $this->installEntitySchema('configurable_language');
    $this->installEntitySchema('node');
    $this->installEntitySchema('user');
    $this->installEntitySchema('path_alias');
    $this->installSchema('entity_mesh', ['entity_mesh']);
    $this->installConfig(['filter', 'node', 'system', 'language', 'entity_mesh']);
    $this->installSchema('node', ['node_access']);

    $this->createContentType(['type' => 'page', 'name' => 'Page']);

    $config = $this->config('language.negotiation');
    $config->set('url.prefixes', ['en' => 'en'])
      ->save();

    // Enable the body field in the default view mode.
    $this->container->get('entity_display.repository')
      ->getViewDisplay('node', 'page', 'full')
      ->setComponent('body', [
        // Show label above the body content.
        'label' => 'above',
        // Render as basic text.
        'type' => 'text_default',
      ])
      ->save();

    $filter_format = FilterFormat::load('basic_html');
    if (!$filter_format) {
      $filter_format = FilterFormat::create([
        'format' => 'basic_html',
        'name' => 'Basic HTML',
        'filters' => [],
      ]);
      $filter_format->save();
    }

    if (!Role::load(AccountInterface::ANONYMOUS_ROLE)) {
      Role::create(['id' => AccountInterface::ANONYMOUS_ROLE, 'label' => 'Anonymous user'])->save();
    }

    if (!Role::load(AccountInterface::AUTHENTICATED_ROLE)) {
      Role::create(['id' => AccountInterface::AUTHENTICATED_ROLE, 'label' => 'Authenticated user'])->save();
    }

    // Create a custom role for testing.
    if (!Role::load('editor')) {
      Role::create(['id' => 'editor', 'label' => 'Editor'])->save();
    }

    // Create a custom role for testing.
    if (!Role::load('redactor')) {
      Role::create(['id' => 'redactor', 'label' => 'Redactor'])->save();
    }

    $this->grantPermissions(Role::load(AccountInterface::ANONYMOUS_ROLE), [
      'access content',
    ]);

    $this->grantPermissions(Role::load(AccountInterface::AUTHENTICATED_ROLE), [
      'access content',
      'bypass node access',
    ]);

    $this->grantPermissions(Role::load('editor'), [
      'access content',
      'bypass node access',
    ]);
  }

  /**
   * Tests that entities without anonymous access are excluded from analysis.
   */
  public function testEntitiesWithoutAnonymousAccessAreExcluded() {

    $html_node = '
      <p>External link https schema: <a href="https://example.com">Example</a></p>
      <p>External link http schema: <a href="http://example.com">Example</a></p>
      <p>Internal broken link: <a href="/non-existent">Broken</a></p>
      <p>Iframe content: <iframe src="https://example.com/iframe"></iframe></p>
      <p>Mailto link: <a href="mailto:hola@metadrop.net">Mailto link</a></p>
      <p>Tel link: <a href="tel:+34654654654">Tel link</a></p>
    ';

    // Create an unpublished node that anonymous users cannot access.
    $restricted_node = Node::create([
      'type' => 'page',
      'title' => 'Unpublished Node (No Anonymous Access)',
    // Owner user ID.
      'uid' => 1,
    // Unpublished.
      'status' => 0,
      'body' => [
        'value' => $html_node,
        'format' => 'basic_html',
      ],
    ]);
    $restricted_node->save();

    // Create a published node that anonymous users can access.
    $public_node = Node::create([
      'type' => 'page',
      'title' => 'Published Node (Anonymous Access)',
    // Owner user ID.
      'uid' => 1,
    // Published.
      'status' => 1,
      'body' => [
        'value' => $html_node,
        'format' => 'basic_html',
      ],
    ]);

    $public_node->save();

    // Verify anonymous user can access public node but not restricted.
    $anonymous = $this->container->get('entity_type.manager')->getStorage('user')->load(0);

    // Confirm access permissions.
    $this->assertTrue($public_node->access('view', $anonymous), 'Anonymous user should have access to public node');
    $this->assertFalse($restricted_node->access('view', $anonymous), 'Anonymous user should NOT have access to restricted node');

    // Update nodes to trigger entity_mesh processing via hook_entity_update.
    // Entity mesh processes entities on insert/update/delete hooks.
    $restricted_node->setTitle('Updated: ' . $restricted_node->getTitle());
    $restricted_node->save();

    $public_node->setTitle('Updated: ' . $public_node->getTitle());
    $public_node->save();

    // Query the entity_mesh table to check which nodes were processed.
    $database = $this->container->get('database');

    // Get all sources from the repository.
    $query = $database->select('entity_mesh', 'em')
      ->fields('em', ['source_entity_type', 'source_entity_id']);
    $results = $query->execute()->fetchAll();

    // Convert results to a more usable format.
    $processed_entity_ids = [];
    foreach ($results as $result) {
      $processed_entity_ids[] = $result->source_entity_id;
    }

    // Assert that the public node was processed.
    $this->assertContains($public_node->id(), $processed_entity_ids, 'Public node should be included in entity_mesh analysis.');

    // Assert that the restricted node was NOT processed
    // (this test should fail initially).
    $this->assertNotContains($restricted_node->id(), $processed_entity_ids, 'Restricted node should be excluded from entity_mesh analysis.');
  }

  /**
   * Test to verify access by configuring a specific role.
   */
  public function testEntitiesWithSpecificRoleAccess() {
    // Configure entity mesh to analyze as authenticated user with editor role.
    $config = $this->config('entity_mesh.settings');
    $config->set('analyzer_account', [
      'type' => 'authenticated',
      'roles' => ['editor'],
      'user_id' => NULL,
    ]);
    $config->save();

    // Clear the repository service cache.
    \Drupal::service('entity_mesh.repository')->clearMeshAccountCache();

    // Create a user with editor user.
    $editor_user = $this->createUser(['access content', 'bypass node access']);
    $editor_user->addRole('editor');
    $editor_user->save();

    $this->baseCheckEditorAccess($editor_user);
  }

  /**
   * Test to verify access by configuring a specific user.
   */
  public function testEntitiesWithSpecificUser() {
    // Create a user with editor role.
    $editor_user = $this->createUser(['access content', 'bypass node access']);
    $editor_user->addRole('editor');
    $editor_user->setUsername('test_editor_user');
    $editor_user->save();

    // Configure entity mesh to analyze as authenticated user with editor role.
    $config = $this->config('entity_mesh.settings');
    $config->set('analyzer_account', [
      'type' => 'user',
      'roles' => NULL,
      'user_id' => (int) $editor_user->id(),
    ]);
    $config->save();

    // Clear the repository service cache.
    \Drupal::service('entity_mesh.repository')->clearMeshAccountCache();

    $this->baseCheckEditorAccess($editor_user);
  }

  /**
   * Tests entities accessible by users with specific roles.
   */
  protected function baseCheckEditorAccess($editor_user) {

    $use_cases = $this->createUseCases();
    $unpublished_node = $use_cases['unpublished_node'];
    $published_node = $use_cases['published_node'];

    // Create an anonymous user.
    $anonymous = $this->container->get('entity_type.manager')->getStorage('user')->load(0);

    // Node only accessible by editor.
    $this->assertTrue($unpublished_node->access('view', $editor_user), 'Editor user should have access to unpublished node');
    $this->assertFalse($unpublished_node->access('view', $anonymous), 'Anonymous user should NOT have access to unpublished node');

    // Node accessible by editor and anonymous.
    $this->assertTrue($published_node->access('view', $anonymous), 'Anonymous user should have access to published node');
    $this->assertTrue($published_node->access('view', $editor_user), 'Editor user should have access to published node');

    // Trigger entity_mesh processing.
    $unpublished_node->setTitle('Updated: ' . $unpublished_node->getTitle());
    $unpublished_node->save();

    $published_node->setTitle('Updated: ' . $published_node->getTitle());
    $published_node->save();

    // Query the entity_mesh table.
    $database = $this->container->get('database');
    $query = $database->select('entity_mesh', 'em')
      ->fields('em', [
        'source_entity_type',
        'source_entity_id',
        'target_entity_id',
        'target_entity_type',
        'subcategory',
        'target_link_type',
      ]);
    $results = $query->execute()->fetchAll();

    $link_accessible_to_unpublished_node = FALSE;

    $processed_entity_ids = [];
    foreach ($results as $result) {
      $processed_entity_ids[] = $result->source_entity_id;
      if ($result->source_entity_id === $published_node->id() &&
        $result->target_entity_id === $unpublished_node->id() &&
        $result->target_link_type === 'internal' &&
        $result->subcategory === 'link'
      ) {
        $link_accessible_to_unpublished_node = TRUE;
      }
    }

    // Assert that the editor accessible node was processed.
    $this->assertContains($published_node->id(), $processed_entity_ids, 'Editor and Anonymous accessible node should be included in entity_mesh analysis.');
    $this->assertContains($unpublished_node->id(), $processed_entity_ids, 'Editor accessible node should be included in entity_mesh analysis.');

    // Check that appears as accessible the unpublish node.
    $this->assertTrue($link_accessible_to_unpublished_node, 'Link to unpublished node should be included in entity_mesh analysis as accessible.');
  }

  /**
   * Creates test use cases with specific permissions.
   *
   * @return array
   *   An array containing the created nodes and their IDs.
   */
  protected function createUseCases(): array {
    // First create the unpublished node with specific ID and external links.
    $unpublished_node_html = '
      <p>External link https schema: <a href="https://example.com">Example External</a></p>
      <p>External link http schema: <a href="http://external-site.com">HTTP External</a></p>
      <p>Mailto link: <a href="mailto:contact@example.com">Email Contact</a></p>
      <p>Tel link: <a href="tel:+34123456789">Phone Contact</a></p>
      <p>Iframe content: <iframe src="https://embedded.com/iframe"></iframe></p>
    ';

    // Create a redactor user with editor role.
    $redactor_user = $this->createUser(['access content']);
    $redactor_user->addRole('redactor');
    $redactor_user->setUsername('test_redactor_user');
    $redactor_user->save();

    $unpublished_node = Node::create([
      'type' => 'page',
      'title' => 'Unpublished Node with External Links',
      'uid' => $redactor_user->id(),
      'status' => 0,
      'body' => [
        'value' => $unpublished_node_html,
        'format' => 'basic_html',
      ],
    ]);
    $unpublished_node->save();
    $unpublished_node_id = $unpublished_node->id();

    // Create the published node that links to the unpublished node.
    $published_node_html = sprintf('
      <p>This is a published node with content.</p>
      <p>Link to unpublished content: <a href="/node/%d">Unpublished Node Link</a></p>
      <p>Another internal link: <a href="/node/%d">Link to same unpublished node</a></p>
      <p>Some regular content here.</p>
    ', $unpublished_node_id, $unpublished_node_id);

    $published_node = Node::create([
      'type' => 'page',
      'title' => 'Published Node with Link to Unpublished',
      'uid' => 1,
      'status' => 1,
      'body' => [
        'value' => $published_node_html,
        'format' => 'basic_html',
      ],
    ]);
    $published_node->save();
    $published_node_id = $published_node->id();

    return [
      'unpublished_node' => $unpublished_node,
      'unpublished_node_id' => $unpublished_node_id,
      'published_node' => $published_node,
      'published_node_id' => $published_node_id,
    ];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc