Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AUCIR-199: Some genericizing. #37

Merged
merged 29 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b1e8b33
Theorhetical, better caching.
adam-vessey Mar 25, 2024
27a93f8
Move to trait.
adam-vessey Apr 3, 2024
41f1f7d
Move over to tracker class.
adam-vessey Apr 3, 2024
2f2d43d
Drop extra newline.
adam-vessey Apr 3, 2024
3ffce3d
Add return typehints.
adam-vessey Apr 3, 2024
8bcda95
Can also be null.
adam-vessey Apr 3, 2024
b744925
Relay index tracking down to files, as necessary.
adam-vessey Apr 3, 2024
787ae75
Adjust media_type loading.
adam-vessey Apr 3, 2024
de3eb89
Fix up intersection.
adam-vessey Apr 4, 2024
209c7ce
Move to use Solr joins instead.
adam-vessey Apr 8, 2024
2360edb
Rework over to make use of joins.
adam-vessey Apr 9, 2024
7e8d650
Rework tracker logic slightly.
adam-vessey Apr 9, 2024
dd17601
Coding standards.
adam-vessey Apr 9, 2024
1e5dddc
Merge branch 'feature/better-caching' into feature/generalize
adam-vessey Apr 9, 2024
039969e
Fix up the direction of multiplicity with the values.
adam-vessey Apr 9, 2024
26e0b90
Add in the fix to the old processor.
adam-vessey Apr 9, 2024
7b226fd
Attempt to soften the dependency.
adam-vessey Apr 9, 2024
87f1cbd
Move calculation of current IP ranges.
adam-vessey Apr 9, 2024
f89a4c9
Pull "info" apart to separate settings.
adam-vessey Apr 9, 2024
3d11969
Adjustments.
adam-vessey Apr 10, 2024
1e2a77c
Carry-through more of constant use.
adam-vessey Apr 10, 2024
49ac2b0
Pull the conditions we need to alter to deal with collections out.
adam-vessey Apr 10, 2024
a323d51
Move over to event handling.
adam-vessey Apr 11, 2024
e036ba6
Drop extra newline.
adam-vessey Apr 11, 2024
ad63a22
Add something of a comment.
adam-vessey Apr 11, 2024
f29f33f
Slight refactor.
adam-vessey Apr 11, 2024
8a4783e
Document our processors.
adam-vessey Apr 11, 2024
3c3f984
Bit more docs.
adam-vessey Apr 11, 2024
6aaa639
Add explicit return of null.
adam-vessey Apr 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,50 @@ See [the module's docs for more info](modules/migrate_embargoes_to_embargo/READM
Configuration options can be set at `admin/config/content/embargo`,
including a contact email and notification message.

Embargoes can be managed at `admin/content/embargo`.
Embargoes can be managed at `admin/content/embargo`.

To add an IP range for use on embargoes, navigate to
`admin/content/embargo/range` and click 'Add IP range'. Ranges
created via this method can then be used as IP address whitelists when creating
embargoes. This [CIDR to IPv4 Conversion utility](https://www.ipaddressguide.com/cidr)
embargoes. This [CIDR to IPv4 Conversion utility](https://www.ipaddressguide.com/cidr)
can be helpful in creating valid CIDR IP ranges.

### `search_api` processor(s)

We have multiple `search_api` processors which attempt to constrain search
results based on the effects of embargoes on the entities represented by search
results, including:

- `embargo_processor` ("Embargo access (deprecated)")
- Adds additional properties to the indexed rows, requiring additional index
maintenance on mutation of the entities under consideration, but should
theoretically work with any `search_api` backend
- `embargo_join_process` ("Embargo access, join-wise")
- Requires Solr/Solarium-compatible index, and indexing of embargo entities in
the same index as the node/media/files to be search, tracking necessary info
and performing
[Solr joins](https://solr.apache.org/guide/solr/latest/query-guide/join-query-parser.html)
to constrain results

Typically, only one should be used in any particular index.

## Usage

### Applying an embargo

An embargo can be applied to an existing node by clicking the
"Embargoes" tab on a node, or navigating to
An embargo can be applied to an existing node by clicking the
"Embargoes" tab on a node, or navigating to
`embargoes/node/{node_id}`. From here, an embargo can be applied if it doesn't
already exist, and existing embargoes can be modified or removed.

## Known Issues
Embargoed items may show up in search results. To work around this at a cost to performance you can enable access checking in your search views.
## Known Issues/FAQ

- Embargoed items show up in search results
- Enable one of our `search_api` processors to handle applying embargo restrictions.
- "Embargo access, join-wise" does not show up as an available processor
- Ensure embargo entities are being indexed in the given index.
- Ensure that eligible node/media/files entities are being indexed in the
given index.

## Troubleshooting/Issues

Expand Down
110 changes: 54 additions & 56 deletions embargo.module
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
* Hook implementations.
*/

use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\embargo\EmbargoInterface;
use Drupal\embargo\EmbargoStorage;
use Drupal\islandora_hierarchical_access\LUTGeneratorInterface;
use Drupal\media\MediaInterface;
use Drupal\node\NodeInterface;

/**
* Implements hook_entity_type_alter().
*/
function embargo_entity_type_alter(array &$entity_types) {
function embargo_entity_type_alter(array &$entity_types) : void {
$applicable_entity_types = EmbargoStorage::applicableEntityTypes();
foreach ($applicable_entity_types as $entity_type_id) {
$entity_type = &$entity_types[$entity_type_id];
Expand All @@ -27,7 +27,7 @@ function embargo_entity_type_alter(array &$entity_types) {
/**
* Implements hook_entity_access().
*/
function embargo_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
function embargo_entity_access(EntityInterface $entity, $operation, AccountInterface $account) : AccessResultInterface {
/** @var \Drupal\embargo\Access\EmbargoAccessCheckInterface $service */
$service = \Drupal::service('access_check.embargo');
return $service->access($entity, $account);
Expand All @@ -36,7 +36,7 @@ function embargo_entity_access(EntityInterface $entity, $operation, AccountInter
/**
* Implements hook_file_download().
*/
function embargo_file_download($uri) {
function embargo_file_download($uri) : null|array|int {
$files = \Drupal::entityTypeManager()
->getStorage('file')
->loadByProperties(['uri' => $uri]);
Expand All @@ -47,12 +47,14 @@ function embargo_file_download($uri) {
return -1;
}
}

return NULL;
}

/**
* Implements hook_query_TAG_alter() for `node_access` tagged queries.
*/
function embargo_query_node_access_alter(AlterableInterface $query) {
function embargo_query_node_access_alter(AlterableInterface $query) : void {
/** @var \Drupal\embargo\Access\QueryTagger $tagger */
$tagger = \Drupal::service('embargo.query_tagger');
$tagger->tagNode($query);
Expand All @@ -61,7 +63,7 @@ function embargo_query_node_access_alter(AlterableInterface $query) {
/**
* Implements hook_theme().
*/
function embargo_theme($existing, $type, $theme, $path) {
function embargo_theme($existing, $type, $theme, $path) : array {
return [
'embargo_ip_access_exemption' => [
'template' => 'embargo-ip-access-exemption',
Expand All @@ -87,72 +89,68 @@ function embargo_theme($existing, $type, $theme, $path) {
* Implements hook_ENTITY_TYPE_insert() for embargo entities.
*/
function embargo_embargo_insert(EntityInterface $entity) : void {
_embargo_search_api_track($entity);
/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->track($entity);
}

/**
* Implements hook_ENTITY_TYPE_update() for embargo entities.
*/
function embargo_embargo_update(EntityInterface $entity) : void {
_embargo_search_api_track($entity);
/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->track($entity);
}

/**
* Implements hook_ENTITY_TYPE_delete() for embargo entities.
*/
function embargo_embargo_delete(EntityInterface $entity) : void {
_embargo_search_api_track($entity);
/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->track($entity);
}

/**
* Helper; deal with updating indexes of related items.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The embargo instance.
* Implements hook_ENTITY_TYPE_delete() for node entities.
*/
function _embargo_search_api_track(EntityInterface $entity) : void {
assert($entity instanceof EmbargoInterface);
if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
return;
}
function embargo_node_delete(EntityInterface $entity) : void {
assert($entity instanceof NodeInterface);
/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->propagateChildren($entity);
}

// On updates, deal with the original value, in addition to the new.
if (isset($entity->original)) {
_embargo_search_api_track($entity->original);
}
/**
* Implements hook_ENTITY_TYPE_insert() for media entities.
*/
function embargo_media_insert(EntityInterface $entity) : void {
assert($entity instanceof MediaInterface);

if (!($node = $entity->getEmbargoedNode())) {
// No embargoed node?
return;
}
/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->mediaWriteReaction($entity);
}

/** @var \Drupal\search_api\Plugin\search_api\datasource\ContentEntityTrackingManager $tracking_manager */
$tracking_manager = \Drupal::getContainer()->get('search_api.entity_datasource.tracking_manager');
/** @var \Drupal\search_api\Utility\TrackingHelperInterface $tracking_helper */
$tracking_helper = \Drupal::getContainer()->get('search_api.tracking_helper');

$track = function (ContentEntityInterface $entity) use ($tracking_manager, $tracking_helper) {
$tracking_manager->trackEntityChange($entity);
$tracking_helper->trackReferencedEntityUpdate($entity);
};

$track($node);

$results = \Drupal::database()->select(LUTGeneratorInterface::TABLE_NAME, 'lut')
->fields('lut', ['mid', 'fid'])
->condition('nid', $node->id())
->execute();
$media_ids = array_unique($results->fetchCol(/* 0 */));
$file_ids = array_unique($results->fetchCol(1));

$entity_type_manager = \Drupal::entityTypeManager();
/** @var \Drupal\media\MediaInterface $media */
foreach ($entity_type_manager->getStorage('media')->loadMultiple($media_ids) as $media) {
$track($media);
}
/** @var \Drupal\file\FileInterface $file */
foreach ($entity_type_manager->getStorage('file')->loadMultiple($file_ids) as $file) {
$track($file);
}
/**
* Implements hook_ENTITY_TYPE_update() for media entities.
*/
function embargo_media_update(EntityInterface $entity) : void {
assert($entity instanceof MediaInterface);

/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->mediaWriteReaction($entity);
}

/**
* Implements hook_ENTITY_TYPE_delete() for media entities.
*/
function embargo_media_delete(EntityInterface $entity) : void {
assert($entity instanceof MediaInterface);

/** @var \Drupal\embargo\SearchApiTracker $tracker */
$tracker = \Drupal::service('embargo.search_api_tracker_helper');
$tracker->mediaDeleteReaction($entity);
}
26 changes: 25 additions & 1 deletion embargo.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ services:
- '@entity_type.manager'
- '@datetime.time'
- '@date.formatter'
- '@event_dispatcher'
embargo.route_subscriber:
class: Drupal\embargo\Routing\EmbargoRouteSubscriber
arguments: ['@entity_type.manager']
tags:
- { name: event_subscriber }
- { name: 'event_subscriber' }
embargo.ip_range_redirect:
class: '\Drupal\embargo\EventSubscriber\IpRangeRedirect'
arguments:
Expand All @@ -38,3 +39,26 @@ services:
- '@service_container'
tags:
- { name: 'event_subscriber' }
embargo.search_api_tracker_helper:
class: Drupal\embargo\SearchApiTracker
factory: [null, 'create']
arguments:
- '@service_container'
embargo.search_api_solr_join_processor_event_subscriber:
class: Drupal\embargo\EventSubscriber\EmbargoJoinProcessorEventSubscriber
factory: [null, 'create']
arguments:
- '@service_container'
tags:
- { name: 'event_subscriber' }
cache_context.ip.embargo_range:
class: Drupal\embargo\Cache\Context\IpRangeCacheContext
arguments:
- '@request_stack'
- '@entity_type.manager'
tags:
- { name: 'cache.context' }
embargo.tagging_event_subscriber:
class: Drupal\embargo\EventSubscriber\TaggingEventSubscriber
tags:
- { name: 'event_subscriber' }
5 changes: 4 additions & 1 deletion src/Access/QueryTagger.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Drupal\islandora_hierarchical_access\Access\QueryConjunctionTrait;
use Drupal\islandora_hierarchical_access\TaggedTargetsTrait;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
* Handles tagging entity queries with access restrictions for embargoes.
Expand All @@ -32,14 +33,16 @@ public function __construct(
Connection $database,
EntityTypeManagerInterface $entity_type_manager,
TimeInterface $time,
DateFormatterInterface $date_formatter
DateFormatterInterface $date_formatter,
EventDispatcherInterface $event_dispatcher,
) {
$this->user = $user;
$this->currentIp = $request_stack->getCurrentRequest()->getClientIp();
$this->database = $database;
$this->entityTypeManager = $entity_type_manager;
$this->time = $time;
$this->dateFormatter = $date_formatter;
$this->setEventDispatcher($event_dispatcher);
}

/**
Expand Down
81 changes: 81 additions & 0 deletions src/Cache/Context/IpRangeCacheContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Drupal\embargo\Cache\Context;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
* Applicable embargo IP range cache context info.
*/
class IpRangeCacheContext implements CacheContextInterface {

/**
* Memoized ranges.
*
* @var \Drupal\embargo\IpRangeInterface[]
*/
protected array $ranges;

/**
* Constructor.
*/
public function __construct(
protected RequestStack $requestStack,
protected EntityTypeManagerInterface $entityTypeManager,
) {
// No-op, other than stashing properties.
}

/**
* {@inheritDoc}
*/
public static function getLabel() {
return \t('Embargo, Applicable IP Ranges');
}

/**
* {@inheritDoc}
*/
public function getContext() {
$range_keys = array_keys($this->getRanges());
sort($range_keys, SORT_NUMERIC);
return implode(',', $range_keys);
}

/**
* {@inheritDoc}
*/
public function getCacheableMetadata() {
$cache_meta = new CacheableMetadata();

foreach ($this->getRanges() as $range) {
$cache_meta->addCacheableDependency($range);
}

return $cache_meta;
}

/**
* Get any IP range entities associated with the current IP address.
*
* @return \Drupal\embargo\IpRangeInterface[]
* Any relevant IP range entities.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getRanges() : array {
if (!isset($this->ranges)) {
/** @var \Drupal\embargo\IpRangeStorageInterface $embargo_ip_range_storage */
$embargo_ip_range_storage = $this->entityTypeManager->getStorage('embargo_ip_range');
$this->ranges = $embargo_ip_range_storage->getApplicableIpRanges($this->requestStack->getCurrentRequest()
->getClientIp());
}

return $this->ranges;
}

}
Loading
Loading