monster_menus-9.0.x-dev/modules/mm_media/tests/src/Kernel/DownloadTest.php

modules/mm_media/tests/src/Kernel/DownloadTest.php
<?php

namespace Drupal\Tests\mm_media\Kernel;

use Drupal\Core\StreamWrapper\PrivateStream;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\media\Entity\MediaType;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\Media;
use Drupal\media\MediaTypeInterface;
use Drupal\monster_menus\Constants;
use Drupal\monster_menus\Entity\MMTree;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeTypeInterface;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;

/**
 * Tests access permissions on media nodes.
 *
 * @group media
 */
class DownloadTest extends KernelTestBase {

  use NodeCreationTrait {
    getNodeByTitle as drupalGetNodeByTitle;
    createNode as drupalCreateNode;
  }
  use UserCreationTrait {
    createAdminRole as drupalCreateAdminRole;
  }

  /**
   * Modules to install.
   *
   * @var array
   */
  protected static $modules = [
    'image',
    'user',
    'field',
    'system',
    'text',
    'filter',
    'block',
    'node',
    'file',
    'media',
    'monster_menus',
    'mm_media',
    'mm_media_fileref_type',
    'editor',
    'editor_test',
  ];

  /**
   * UID of the test media's and node's owner.
   *
   * @var int
   */
  private $ownerUid = 9999;

  private $users = [];

  private $cleanupFilenames = [];

  final public const ACCESS_ALLOWED = 'allowed';
  final public const ACCESS_DENIED = 'denied';
  final public const ACCESS_UNSET = 'unset';
  final public const NOT_FOUND = 'not found';

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

    $this->setInstallProfile('standard');
    $this->installSchema('file', 'file_usage');
    $this->installSchema('system', 'sequences');
    // We need almost all of MM's tables because we are calling its hook_install().
    $this->installSchema('monster_menus', ['mm_vgroup_query', 'mm_tree_parents', 'mm_node_write', 'mm_node_info', 'mm_node2tree', 'mm_recycle', 'mm_node_schedule', 'mm_node_reorder', 'mm_tree_flags', 'mm_tree_block', 'mm_group', 'mm_tree_access', 'mm_cascaded_settings', 'mm_archive', 'mm_virtual_group']);
    $this->installEntitySchema('user');
    $this->installEntitySchema('file');
    $this->installEntitySchema('mm_tree');
    $this->installEntitySchema('media');
    $this->installEntitySchema('node');
    $this->installEntitySchema('block');
    $this->installConfig(['field', 'system', 'image', 'file', 'text', 'filter', 'node', 'media', 'mm_media', 'mm_media_fileref_type']);
    $this->container->get('module_installer')->install(['mm_media_fileref_type']);

    // The tests we are doing require a real file on disk, so we can't use vfs.

    // Add file_private_path setting.
    $request = Request::create('/');
    $site_path = DrupalKernel::findSitePath($request);
    $privatePath = $site_path . '/private';
    $this->setSetting('file_private_path', $privatePath);

    // Ensure that the private files directory exists.
    $fs = $this->container->get('file_system');
    $fs->mkdir($privatePath, NULL, TRUE);

    $this->currentUser = $this->container->get('current_user');

    // Call MM's hook_install().
    \Drupal::moduleHandler()->invoke('monster_menus', 'install');

    // Add text format.
    $filtered_html_format = FilterFormat::create([
      'format' => 'filtered_html',
      'name' => 'Filtered HTML',
      'weight' => 0,
      'filters' => [],
    ]);
    $filtered_html_format->save();

    // Set up text editor.
    $editor = Editor::create([
      'format' => 'filtered_html',
      'editor' => 'unicorn',
    ]);
    $editor->save();

    // Create a node type for testing.
    /** @var NodeTypeInterface $type */
    $type = NodeType::create(['type' => 'page', 'name' => 'page']);
    $type->save();
    node_add_body_field($type);
  }

  /**
   * @inheritDoc
   */
  protected function tearDown(): void {
    $privatePath = Settings::get('file_private_path');
    foreach ($this->cleanupFilenames as $fn) {
      unlink($privatePath . '/' . $fn);
    }
    rmdir($privatePath);
    parent::tearDown();
  }

  /**
   * {@inheritdoc}
   */
  public function register(ContainerBuilder $container) {
    parent::register($container);

    $container->register('stream_wrapper.private', PrivateStream::class)
      ->addTag('stream_wrapper', ['scheme' => 'private']);
  }

  /**
   * Tests private file download access.
   */
  public function testPrivateDownloadAccess() {
    $testDownload = function ($target, $scheme = 'private') {
      $uri = $scheme . '://' . $target;

      if (\Drupal::service('stream_wrapper_manager')->isValidScheme($scheme) && file_exists($uri)) {
        try {
          $headers = \Drupal::moduleHandler()
              ->invoke('mm_media', 'file_download', [$uri]) ?? [];

          if (is_array($headers)) {
            if (count($headers)) {
              return $this::ACCESS_ALLOWED;
            }
          }
          else if ($headers === -1) {
            return $this::ACCESS_DENIED;
          }

          return $this::ACCESS_UNSET;
        }
        catch (HttpException $e) {
          if ($e->getStatusCode() == 304) {
            // Not Modified
            return $this::ACCESS_ALLOWED;
          }
        }

        return $this::ACCESS_DENIED;
      }

      return $this::NOT_FOUND;
    };

    $setupUserWithRole = function($access_modes, $can) {
      $label = join('+', $access_modes);
      $role = Role::create(['id' => $this->randomMachineName(8), 'label' => "$label Role"]);
      foreach ($access_modes as $access_mode) {
        $role->grantPermission($access_mode);
      }
      $role->save();
      $user = User::create(['name' => "Can $label", 'status' => 1, 'roles' => [$role->id()]]);
      $user->save();
      $this->users[$label] = ['user' => $user, 'can' => $can];
    };

    $mediaType = $this->createMediaType('file');
    $setupNodeWithMedia = function($filename, $catlist = []) use ($mediaType) {
      $media = $this->generateMedia($filename, $mediaType);
      return $this->createNode([
        'type' => 'media_test',
        'uid' => $this->ownerUid,
        'field_media_field' => [['target_id' => $media->id()]],
        'mm_catlist' => $catlist,
      ]);
    };

    $setupNodeBodyWithMedia = function($filename, $catlist = []) {
      $file = $this->generateFile($filename);
      $body_value = '<img src="test.jpg" data-entity-type="file" data-entity-uuid="' . $file->uuid() . '" />';
      $body = [[
        'value' => $body_value,
        'format' => 'filtered_html',
      ]];
      $this->createNode([
        'type' => 'page',
        'title' => 'test',
        'uid' => $this->ownerUid,
        'body' => $body,
        'mm_catlist' => $catlist,
      ]);
    };

    $user = User::create(['name' => 'Media owner', 'status' => 1, 'uid' => $this->ownerUid]);
    $user->save();
    $this->users = [
      'anonymous'   => [
        'user' => User::getAnonymousUser(),
        'can' => [
          'no usage' => $this::ACCESS_UNSET,
          'no longer used' => $this::ACCESS_UNSET,
          'no longer used temp' => $this::ACCESS_DENIED,
          'no node' => $this::ACCESS_DENIED,
          'on orphan node' => $this::ACCESS_DENIED,
          'on public node' => $this::ACCESS_ALLOWED,
          'on unreadable node' => $this::ACCESS_DENIED,
          'same node both places' => $this::ACCESS_ALLOWED,
          'same file both places' => $this::ACCESS_ALLOWED,
          'public and in bin' => $this::ACCESS_ALLOWED,
          'same node, public in bin' => $this::ACCESS_DENIED,
          'in bin' => $this::ACCESS_DENIED,
          'in body of public node' => $this::ACCESS_ALLOWED,
          'in body of unreadable node' => $this::ACCESS_DENIED,
        ]],
      'media owner' => [
        'user' => $user,
        'can' => [
          'no usage' => $this::ACCESS_ALLOWED,
          'no longer used' => $this::ACCESS_ALLOWED,
          'no longer used temp' => $this::ACCESS_ALLOWED,
          'no node' => $this::ACCESS_ALLOWED,
          'on orphan node' => $this::ACCESS_ALLOWED,
          'on public node' => $this::ACCESS_ALLOWED,
          'on unreadable node' => $this::ACCESS_ALLOWED,
          'same node both places' => $this::ACCESS_ALLOWED,
          'same file both places' => $this::ACCESS_ALLOWED,
          'public and in bin' => $this::ACCESS_ALLOWED,
          'same node, public in bin' => $this::ACCESS_ALLOWED,
          'in bin' => $this::ACCESS_ALLOWED,
          'in body of public node' => $this::ACCESS_ALLOWED,
          'in body of unreadable node' => $this::ACCESS_ALLOWED,
        ]],
    ];
    $this->drupalCreateAdminRole('administrator');
    Role::create([
      'id' => RoleInterface::ANONYMOUS_ID,
      'label' => 'Anonymous user',
    ])->save();
    Role::create([
      'id' => RoleInterface::AUTHENTICATED_ID,
      'label' => 'Authenticated user',
    ])->save();
    $setupUserWithRole(['administer all menus'], [
      'no usage' => $this::ACCESS_UNSET,
      'no longer used' => $this::ACCESS_UNSET,
      'no longer used temp' => $this::ACCESS_DENIED,
      'no node' => $this::ACCESS_DENIED,
      'on orphan node' => $this::ACCESS_ALLOWED,
      'on public node' => $this::ACCESS_ALLOWED,
      'on unreadable node' => $this::ACCESS_ALLOWED,
      'same node both places' => $this::ACCESS_ALLOWED,
      'same file both places' => $this::ACCESS_ALLOWED,
      'public and in bin' => $this::ACCESS_ALLOWED,
      'same node, public in bin' => $this::ACCESS_ALLOWED,
      'in bin' => $this::ACCESS_ALLOWED,
      'in body of public node' => $this::ACCESS_ALLOWED,
      'in body of unreadable node' => $this::ACCESS_ALLOWED,
    ]);
    $setupUserWithRole(['bypass node access'], [
      'no usage' => $this::ACCESS_UNSET,
      'no longer used' => $this::ACCESS_UNSET,
      'no longer used temp' => $this::ACCESS_DENIED,
      'no node' => $this::ACCESS_DENIED,
      'on orphan node' => $this::ACCESS_DENIED,
      'on public node' => $this::ACCESS_ALLOWED,
      'on unreadable node' => $this::ACCESS_DENIED,
      'same node both places' => $this::ACCESS_ALLOWED,
      'same file both places' => $this::ACCESS_ALLOWED,
      'public and in bin' => $this::ACCESS_ALLOWED,
      'same node, public in bin' => $this::ACCESS_ALLOWED,
      'in bin' => $this::ACCESS_ALLOWED,
      'in body of public node' => $this::ACCESS_ALLOWED,
      'in body of unreadable node' => $this::ACCESS_DENIED,
    ]);
    $user = User::create(['name' => 'No roles', 'roles' => [], 'status' => 1]);
    $user->save();
    $this->users['no roles'] = ['user' => $user, 'can' => [
      'no usage' => $this::ACCESS_UNSET,
      'no longer used' => $this::ACCESS_UNSET,
      'no longer used temp' => $this::ACCESS_DENIED,
      'no node' => $this::ACCESS_DENIED,
      'on orphan node' => $this::ACCESS_DENIED,
      'on public node' => $this::ACCESS_ALLOWED,
      'on unreadable node' => $this::ACCESS_DENIED,
      'same node both places' => $this::ACCESS_ALLOWED,
      'same file both places' => $this::ACCESS_ALLOWED,
      'public and in bin' => $this::ACCESS_ALLOWED,
      'same node, public in bin' => $this::ACCESS_DENIED,
      'in bin' => $this::ACCESS_DENIED,
      'in body of public node' => $this::ACCESS_ALLOWED,
      'in body of unreadable node' => $this::ACCESS_DENIED,
    ]];
    $user = User::create(['name' => 'admin', 'roles' => ['administrator'], 'status' => 1]);
    $user->save();
    $this->users['admin'] = ['user' => $user, 'can' => [
      'no usage' => $this::ACCESS_UNSET,
      'no longer used' => $this::ACCESS_UNSET,
      'no longer used temp' => $this::ACCESS_DENIED,
      'no node' => $this::ACCESS_DENIED,
      'on orphan node' => $this::ACCESS_ALLOWED,
      'on public node' => $this::ACCESS_ALLOWED,
      'on unreadable node' => $this::ACCESS_ALLOWED,
      'same node both places' => $this::ACCESS_ALLOWED,
      'same file both places' => $this::ACCESS_ALLOWED,
      'public and in bin' => $this::ACCESS_ALLOWED,
      'same node, public in bin' => $this::ACCESS_ALLOWED,
      'in bin' => $this::ACCESS_ALLOWED,
      'in body of public node' => $this::ACCESS_ALLOWED,
      'in body of unreadable node' => $this::ACCESS_ALLOWED,
    ]];

    $this->generateFile($filename['no usage'] = 'no_usage.txt');

    $this->generateMedia($filename['no longer used'] = 'no_longer_used.txt', $mediaType)->delete();

    // Mark unused managed files as temporary.
    $this->config('file.settings')
      ->set('make_unused_managed_files_temporary', TRUE)
      ->save();
    $this->generateMedia($filename['no longer used temp'] = 'no_longer_used_temp.txt', $mediaType)->delete();

    $this->generateMedia($filename['no node'] = 'no_node.txt', $mediaType);

    $setupNodeWithMedia($filename['on orphan node'] = 'on_orphan_node.txt');

    $publicPage = MMTree::create(['parent' => mm_home_mmtid(), 'name' => 'Public', 'alias' => 'public', 'default_mode' => Constants::MM_PERMS_READ]);
    $publicPage->save();
    $publicCatlist = [$publicPage->id() => $publicPage->label()];
    $setupNodeWithMedia($filename['on public node'] = 'on_public_node.txt', $publicCatlist);

    $unreadablePage = MMTree::create(['parent' => mm_home_mmtid(), 'name' => 'Unreadable', 'alias' => 'unreadable']);
    $unreadablePage->save();
    $unreadableCatlist = [$unreadablePage->id() => $unreadablePage->label()];
    $setupNodeWithMedia($filename['on unreadable node'] = 'on_unreadable_node.txt', $unreadableCatlist);

    $setupNodeWithMedia($filename['same node both places'] = 'same_node_both_places.txt', $publicCatlist + $unreadableCatlist);

    $node = $setupNodeWithMedia($filename['same file both places'] = 'same_file_both_places.txt', $publicCatlist);
    $this->createNode([
      'type' => 'media_test',
      'uid' => $this->ownerUid,
      'field_media_field' => $node->field_media_field,
      'mm_catlist' => $unreadableCatlist,
    ]);

    $node = $setupNodeWithMedia($filename['public and in bin'] = 'public_and_in_bin.txt', $publicCatlist);
    mm_content_move_to_bin(NULL, $this->createNode([
      'type' => 'media_test',
      'uid' => $this->ownerUid,
      'field_media_field' => $node->field_media_field,
      'mm_catlist' => $publicCatlist,
    ])->id());

    $node = $setupNodeWithMedia($filename['same node, public in bin'] = 'same_node_public_binned.txt', $publicCatlist + $unreadableCatlist);
    mm_content_move_to_bin(NULL, [$node->id() => [$publicPage->id()]]);

    $node = $setupNodeWithMedia($filename['in bin'] = 'in_bin.txt', $publicCatlist);
    mm_content_move_to_bin(NULL, $node->id());

    $setupNodeBodyWithMedia($filename['in body of public node'] = 'in_public_body.txt', $publicCatlist);
    $setupNodeBodyWithMedia($filename['in body of unreadable node'] = 'in_unreadable_body.txt', $unreadableCatlist);

    $mm_media_settings = $this->config('mm_media.settings');
    foreach ($this->users as $user_data) {
      \Drupal::currentUser()->setAccount($user_data['user']);
      foreach ([FALSE, TRUE] as $cache_public_media) {
        $mm_media_settings->set('cache_public_media', $cache_public_media)->save();
        foreach ($user_data['can'] as $mode => $can) {
          $name = $user_data['user']->getDisplayName() ?? 'anonymous';
          $message = sprintf('mode = [%s], user = [%s (%d)], cache_public_media = %d', $mode, $name, $user_data['user']->id(), $cache_public_media);
          $this->assertEquals($can, $testDownload($filename[$mode]), $message);
        }
      }
    }
  }

  /**
   * Create a media type for a source plugin.
   *
   * @param string $media_source_name
   *   The name of the media source.
   *
   * @return \Drupal\media\MediaTypeInterface
   *   A media type.
   */
  protected function createMediaType($media_source_name) {
    $id = strtolower($this->randomMachineName());
    $media_type = MediaType::create([
      'id' => __FUNCTION__ . $id,
      'label' => $id,
      'source' => $media_source_name,
      'new_revision' => FALSE,
    ]);
    $media_type->save();
    $source_field = $media_type->getSource()->createSourceField($media_type);
    // The media type form creates a source field if it does not exist yet. The
    // same must be done in a kernel test, since it does not use that form.
    // @see \Drupal\media\MediaTypeForm::save()
    $source_field->getFieldStorageDefinition()->save();
    // The source field storage has been created, now the field can be saved.
    $source_field->save();
    $source_configuration = $media_type->getSource()->getConfiguration();
    $source_configuration['source_field'] = $source_field->getName();
    $media_type->set('source_configuration', $source_configuration)->save();

    return $media_type;
  }

  /**
   * Helper to generate file entity.
   *
   * @param string $filename
   *   String filename with extension.
   *
   * @return File
   *   A file entity.
   */
  protected function generateFile($filename) {
    $uri = 'private://' . $filename;
    if ($fp = fopen($uri, 'c+')) {
      $this->cleanupFilenames[] = $filename;
      fwrite($fp, str_repeat('a', 3000));
      fclose($fp);
    }
    else {
      throw new \Exception("Can't create $uri");
    }

    $file = File::create([
      'uri' => $uri,
      'uid' => $this->ownerUid,
    ]);
    $file->setPermanent();
    $file->save();

    return $file;
  }

  /**
   * Helper to generate media entity.
   *
   * @param string $filename
   *   String filename with extension.
   * @param MediaTypeInterface $media_type
   *   The the media type.
   *
   * @return Media
   *   A media entity.
   */
  protected function generateMedia($filename, MediaTypeInterface $media_type) {
    $file = $this->generateFile($filename);
    $media = Media::create([
      'bundle' => $media_type->id(),
      'name' => 'Mr. Jones',
      'field_media_file' => [
        'target_id' => $file->id(),
      ],
    ]);
    $media->save();
    return $media;
  }

}

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

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