Unverified Commit 94361f33 authored by alexpott's avatar alexpott

Issue #3038254 by phenaproxima, seanB, Wim Leers, yogeshmpawar, effulgentsia,...

Issue #3038254 by phenaproxima, seanB, Wim Leers, yogeshmpawar, effulgentsia, alexpott, larowlan: Delegate media library access to the "thing" that opened the library
parent 834c8148
services:
media_library.ui_builder:
class: Drupal\media_library\MediaLibraryUiBuilder
arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder']
arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder', '@media_library.opener_resolver']
media_library.route_subscriber:
class: Drupal\media_library\Routing\RouteSubscriber
tags:
......@@ -12,3 +12,4 @@ services:
- [setContainer, ['@service_container']]
media_library.opener.field_widget:
class: Drupal\media_library\MediaLibraryFieldWidgetOpener
arguments: ['@entity_type.manager']
......@@ -2,8 +2,12 @@
namespace Drupal\media_library;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
......@@ -11,11 +15,92 @@
*/
class MediaLibraryFieldWidgetOpener implements MediaLibraryOpenerInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* MediaLibraryFieldWidgetOpener constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function checkAccess(MediaLibraryState $state, AccountInterface $account) {
throw new \Exception('Not yet implemented, see https://www.drupal.org/project/drupal/issues/3038254.');
$parameters = $state->getOpenerParameters() + ['entity_id' => NULL];
$process_result = function ($result) {
if ($result instanceof RefinableCacheableDependencyInterface) {
$result->addCacheContexts(['url.query_args']);
}
return $result;
};
// Forbid access if any of the required parameters are missing.
foreach (['entity_type_id', 'bundle', 'field_name'] as $key) {
if (empty($parameters[$key])) {
return $process_result(AccessResult::forbidden("$key parameter is missing."));
}
}
$entity_type_id = $parameters['entity_type_id'];
$bundle = $parameters['bundle'];
$field_name = $parameters['field_name'];
// Since we defer to a field to determine access, ensure we are dealing with
// a fieldable entity type.
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
throw new \LogicException("The media library can only be opened by fieldable entities.");
}
$storage = $this->entityTypeManager->getStorage($entity_type_id);
$access_handler = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
if ($parameters['entity_id']) {
$entity = $storage->load($parameters['entity_id']);
$entity_access = $access_handler->access($entity, 'update', $account, TRUE);
}
else {
$entity_access = $access_handler->createAccess($bundle, $account, [], TRUE);
}
// If entity-level access is denied, there's no point in continuing.
if (!$entity_access->isAllowed()) {
return $process_result($entity_access);
}
// If the entity has not been loaded, create it in memory now.
if (!isset($entity)) {
$values = [];
if ($bundle_key = $entity_type->getKey('bundle')) {
$values[$bundle_key] = $bundle;
}
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$entity = $storage->create($values);
}
$items = $entity->get($field_name);
$field_definition = $items->getFieldDefinition();
if ($field_definition->getType() !== 'entity_reference') {
throw new \LogicException('Expected the media library to be opened by an entity reference field.');
}
if ($field_definition->getFieldStorageDefinition()->getSetting('target_type') !== 'media') {
throw new \LogicException('Expected the media library to be opened by an entity reference field that target media items.');
}
$field_access = $access_handler->fieldAccess('edit', $field_definition, $account, $items, TRUE);
return $process_result($entity_access->andIf($field_access));
}
/**
......
......@@ -53,6 +53,13 @@ class MediaLibraryUiBuilder {
*/
protected $viewsExecutableFactory;
/**
* The media library opener resolver.
*
* @var \Drupal\media_library\OpenerResolverInterface
*/
protected $openerResolver;
/**
* Constructs a MediaLibraryUiBuilder instance.
*
......@@ -64,12 +71,19 @@ class MediaLibraryUiBuilder {
* The views executable factory.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The currently active request object.
* @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
* The opener resolver.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder, OpenerResolverInterface $opener_resolver = NULL) {
$this->entityTypeManager = $entity_type_manager;
$this->request = $request_stack->getCurrentRequest();
$this->viewsExecutableFactory = $views_executable_factory;
$this->formBuilder = $form_builder;
if (!$opener_resolver) {
@trigger_error('The media_library.opener_resolver service must be passed to ' . __METHOD__ . ' and will be required before Drupal 9.0.0.', E_USER_DEPRECATED);
$opener_resolver = \Drupal::service('media_library.opener_resolver');
}
$this->openerResolver = $opener_resolver;
}
/**
......@@ -159,7 +173,7 @@ protected function buildLibraryContent(MediaLibraryState $state) {
* Check access to the media library.
*
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) Run access checks for this account.
* Run access checks for this account.
* @param \Drupal\media_library\MediaLibraryState $state
* (optional) The current state of the media library, derived from the
* current request.
......@@ -167,10 +181,10 @@ protected function buildLibraryContent(MediaLibraryState $state) {
* @return \Drupal\Core\Access\AccessResult
* The access result.
*/
public function checkAccess(AccountInterface $account = NULL, MediaLibraryState $state = NULL) {
public function checkAccess(AccountInterface $account, MediaLibraryState $state = NULL) {
if (!$state) {
try {
MediaLibraryState::fromRequest($this->request);
$state = MediaLibraryState::fromRequest($this->request);
}
catch (BadRequestHttpException $e) {
return AccessResult::forbidden($e->getMessage());
......@@ -189,8 +203,16 @@ public function checkAccess(AccountInterface $account = NULL, MediaLibraryState
return AccessResult::forbidden('The media library widget display does not exist.')
->addCacheableDependency($view);
}
return AccessResult::allowedIfHasPermission($account, 'view media')
// The user must at least be able to view media in order to access the media
// library.
$can_view_media = AccessResult::allowedIfHasPermission($account, 'view media')
->addCacheableDependency($view);
// Delegate any further access checking to the opener service nominated by
// the media library state.
return $this->openerResolver->get($state)->checkAccess($state, $account)
->andIf($can_view_media);
}
/**
......
......@@ -461,9 +461,17 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
// This particular media library opener needs some extra metadata for its
// \Drupal\media_library\MediaLibraryOpenerInterface::getSelectionResponse()
// to be able to target the element whose 'data-media-library-widget-value'
// attribute is the same as $field_widget_id.
// attribute is the same as $field_widget_id. The entity ID, entity type ID,
// bundle, field name are used for access checking.
$entity = $items->getEntity();
$state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_media_type_ids, $selected_type_id, $remaining, [
'field_widget_id' => $field_widget_id,
'entity_type_id' => $entity->getEntityTypeId(),
'bundle' => $entity->bundle(),
'field_name' => $field_name,
// The entity ID needs to be a string to ensure that the media library
// state generates its tamper-proof hash in a consistent way.
'entity_id' => (string) $entity->id(),
]);
// Add a button that will load the Media library in a modal using AJAX.
......
<?php
/**
* @file
* Contains hook implementations for the media_library_test module.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_entity_field_access().
*/
function media_library_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
return AccessResult::forbiddenIf($field_definition->getName() === 'field_media_no_access', 'Field access denied by test module');
}
......@@ -332,7 +332,13 @@ public function testWidgetAccess() {
// Create a working state.
$allowed_types = ['type_one', 'type_two', 'type_three', 'type_four'];
$state = MediaLibraryState::create('test', $allowed_types, 'type_three', 2);
// The opener parameters are not relevant to the test, but the opener
// expects them to be there or it will deny access.
$state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_types, 'type_three', 2, [
'entity_type_id' => 'node',
'bundle' => 'basic_page',
'field_name' => 'field_unlimited_media',
]);
$url_options = ['query' => $state->all()];
// Verify that unprivileged users can't access the widget view.
......@@ -344,8 +350,10 @@ public function testWidgetAccess() {
$assert_session->responseContains('Access denied');
// Allow users with 'view media' permission to access the media library view
// and controller.
// and controller. Since we are using the node entity type in the state
// object, ensure the user also has permission to work with those.
$this->grantPermissions($role, [
'create basic_page content',
'view media',
]);
$this->drupalGet('admin/content/media-widget', $url_options);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment