diff --git a/embargo.services.yml b/embargo.services.yml index c6ef73b..4bcd728 100644 --- a/embargo.services.yml +++ b/embargo.services.yml @@ -62,3 +62,10 @@ services: class: Drupal\embargo\EventSubscriber\TaggingEventSubscriber tags: - { name: 'event_subscriber' } + cache_context.user.embargo__has_exemption: + class: Drupal\embargo\Cache\Context\UserExemptedCacheContext + arguments: + - '@current_user' + - '@entity_type.manager' + tags: + - { name: 'cache.context' } diff --git a/modules/migrate_embargoes_to_embargo/src/Plugin/migrate/source/Entity.php b/modules/migrate_embargoes_to_embargo/src/Plugin/migrate/source/Entity.php index 864c45e..9ba8ddc 100644 --- a/modules/migrate_embargoes_to_embargo/src/Plugin/migrate/source/Entity.php +++ b/modules/migrate_embargoes_to_embargo/src/Plugin/migrate/source/Entity.php @@ -40,7 +40,7 @@ public function __construct( $plugin_id, $plugin_definition, MigrationInterface $migration, - EntityTypeManagerInterface $entity_type_manager + EntityTypeManagerInterface $entity_type_manager, ) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration); @@ -56,7 +56,7 @@ public static function create( array $configuration, $plugin_id, $plugin_definition, - MigrationInterface $migration = NULL + MigrationInterface $migration = NULL, ) { return new static( $configuration, diff --git a/src/Access/EmbargoAccessCheck.php b/src/Access/EmbargoAccessCheck.php index 3e6dcf0..e1d0d1d 100644 --- a/src/Access/EmbargoAccessCheck.php +++ b/src/Access/EmbargoAccessCheck.php @@ -57,28 +57,40 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Req * {@inheritdoc} */ public function access(EntityInterface $entity, AccountInterface $user) { + $type = $this->entityTypeManager->getDefinition('embargo'); + $state = AccessResult::neutral(); + + if ($user->hasPermission('bypass embargo access')) { + return $state->setReason('User has embargo bypass permission.') + ->addCacheContexts(['user.permissions']); + } + /** @var \Drupal\embargo\EmbargoStorage $storage */ $storage = $this->entityTypeManager->getStorage('embargo'); + $state->addCacheTags($type->getListCacheTags()) + ->addCacheContexts($type->getListCacheContexts()); + $related_embargoes = $storage->getApplicableEmbargoes($entity); + if (empty($related_embargoes)) { + return $state->setReason('No embargo statements for the given entity.'); + } + + array_map([$state, 'addCacheableDependency'], $related_embargoes); + $embargoes = $storage->getApplicableNonExemptNonExpiredEmbargoes( $entity, $this->request->server->get('REQUEST_TIME'), $user, $this->request->getClientIp() ); - $state = AccessResult::forbiddenIf( + return $state->andIf(AccessResult::forbiddenIf( !empty($embargoes), $this->formatPlural( count($embargoes), '1 embargo preventing access.', '@count embargoes preventing access.' )->render() - ); - array_map([$state, 'addCacheableDependency'], $embargoes); + )); - $type = $this->entityTypeManager->getDefinition('embargo'); - $state->addCacheTags($type->getListCacheTags()) - ->addCacheContexts($type->getListCacheContexts()); - return $state; } } diff --git a/src/Cache/Context/UserExemptedCacheContext.php b/src/Cache/Context/UserExemptedCacheContext.php new file mode 100644 index 0000000..6bcc756 --- /dev/null +++ b/src/Cache/Context/UserExemptedCacheContext.php @@ -0,0 +1,77 @@ +isExempted() ? '1' : '0'; + } + + /** + * {@inheritDoc} + */ + public function getCacheableMetadata() { + return (new CacheableMetadata()) + ->addCacheContexts([$this->isExempted() ? 'user' : 'user.permissions']) + ->addCacheTags(['embargo_list']); + } + + /** + * Determine if the current user has _any_ exemptions. + * + * @return bool + * TRUE if the user is exempt to at least one embargo; otherwise, FALSE. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function isExempted() : bool { + if (!isset($this->exempted)) { + $results = $this->entityTypeManager->getStorage('embargo')->getQuery() + ->accessCheck(FALSE) + ->condition('exempt_users', $this->currentUser->id()) + ->range(0, 1) + ->execute(); + $this->exempted = !empty($results); + } + + return $this->exempted; + } + +} diff --git a/src/Entity/Embargo.php b/src/Entity/Embargo.php index e67f6ac..89349b8 100644 --- a/src/Entity/Embargo.php +++ b/src/Entity/Embargo.php @@ -12,6 +12,7 @@ use Drupal\Core\TypedData\Exception\MissingDataException; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\embargo\EmbargoInterface; +use Drupal\embargo\EmbargoStorageInterface; use Drupal\embargo\IpRangeInterface; use Drupal\node\NodeInterface; use Drupal\user\UserInterface; @@ -43,8 +44,6 @@ * "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider" * }, * }, - * list_cache_contexts = { "ip.embargo_range", "user" }, - * list_cache_tags = { "embargo_list", "embargo_ip_range_list" }, * base_table = "embargo", * admin_permission = "administer embargo", * entity_keys = { @@ -413,7 +412,7 @@ public function getCacheContexts() { $contexts = Cache::mergeContexts( parent::getCacheContexts(), $this->getEmbargoedNode()->getCacheContexts(), - [$this->getExemptUsers() ? 'user' : 'user.permissions'], + ['user.embargo__has_exemption'], ); if ($this->getExemptIps()) { @@ -458,11 +457,9 @@ public function ipIsExempt(string $ip): bool { protected function getListCacheTagsToInvalidate() : array { return array_merge( parent::getListCacheTagsToInvalidate(), - [ - 'node_list', - 'media_list', - 'file_list', - ] + array_map(function (string $type) { + return "{$type}_list"; + }, EmbargoStorageInterface::APPLICABLE_ENTITY_TYPES), ); } diff --git a/src/Entity/IpRange.php b/src/Entity/IpRange.php index 3283ac6..1794c53 100644 --- a/src/Entity/IpRange.php +++ b/src/Entity/IpRange.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\embargo\EmbargoStorageInterface; use Drupal\embargo\IpRangeInterface; use Symfony\Component\HttpFoundation\IpUtils; @@ -40,7 +41,6 @@ * "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider" * }, * }, - * list_cache_tags = { "node_list", "media_list", "file_list" }, * base_table = "embargo_ip_range", * admin_permission = "administer embargo", * entity_keys = { @@ -250,4 +250,16 @@ public function getCacheContexts() { ]); } + /** + * {@inheritdoc} + */ + protected function getListCacheTagsToInvalidate() : array { + return array_merge( + parent::getListCacheTagsToInvalidate(), + array_map(function (string $type) { + return "{$type}_list"; + }, EmbargoStorageInterface::APPLICABLE_ENTITY_TYPES), + ); + } + } diff --git a/src/Plugin/search_api/processor/EmbargoJoinProcessor.php b/src/Plugin/search_api/processor/EmbargoJoinProcessor.php index 90b8d3f..90f4cf0 100644 --- a/src/Plugin/search_api/processor/EmbargoJoinProcessor.php +++ b/src/Plugin/search_api/processor/EmbargoJoinProcessor.php @@ -289,7 +289,7 @@ public function preprocessSearchQuery(QueryInterface $query) : void { // cacheability. 'ip.embargo_range', // Exemptable users, so need to deal with them. - 'user', + 'user.embargo__has_exemption', ]); // Embargo dates deal with granularity to the day. $query->mergeCacheMaxAge(24 * 3600); diff --git a/tests/src/Kernel/FileEmbargoTest.php b/tests/src/Kernel/FileEmbargoTest.php index de399fb..90d09cf 100644 --- a/tests/src/Kernel/FileEmbargoTest.php +++ b/tests/src/Kernel/FileEmbargoTest.php @@ -73,7 +73,7 @@ public function testEmbargoedNodeRelatedMediaFileAccessDenied($operation) { * @throws \Drupal\Core\Entity\EntityStorageException */ public function testDeletedEmbargoedFileRelatedMediaFileAccessAllowed( - $operation + $operation, ) { $node = $this->createNode(); $file = $this->createFile();