-
Notifications
You must be signed in to change notification settings - Fork 8
Protect #139
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
Open
agentrickard
wants to merge
42
commits into
8.x-1.x
Choose a base branch
from
protect
base: 8.x-1.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Protect #139
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
8b7aeaa
Adds protection module code for testiong.
agentrickard 605e51e
Added more scaffold.
jasonpartyka 7d8b7e7
Generalized check to see if there is an entity being used, and added …
jasonpartyka 86f1869
Added functional tests for workbench_access_protect.
jasonpartyka 99f3018
Changed test run command for workbench access protect.
jasonpartyka 57b75e9
Changed test run command for workbench access protect.
jasonpartyka e0d8a7a
Changed test run command for workbench access protect.
jasonpartyka e1c695f
Changed test run command for workbench access protect.
jasonpartyka 096b9bc
Changed test run command for workbench access protect.
jasonpartyka ceaa67d
Changed test group & travis conifg.
jasonpartyka 8f473f2
Added check for bundle-wide usage. TODO: Limit the bundles we are che…
jasonpartyka c30d711
Fixed accidental copy.
jasonpartyka 4534529
Added an 'ids' index to the configuration so that we can check the bu…
jasonpartyka 4259001
Added IDs to schema.
jasonpartyka c4ca783
Corrected copy and paste mistake.
jasonpartyka 1132174
Changed type of test to Functional from Unit for workbench access pro…
jasonpartyka 53fca84
Updating test.
jasonpartyka bcc1f10
Moved workbench_access_protect test.
jasonpartyka 90ee9c5
Renamed the ids field to parent_ids
jasonpartyka 2c61b04
More renaming.
jasonpartyka 56ee594
Testing a change.
jasonpartyka be01f86
Forcing a test run.
jasonpartyka 3286023
Update DeleteAccessCheck.php
jasonpartyka 6e40db1
Merge pull request #130 from jasonpartyka/protect
agentrickard 1670de6
Merge branch '8.x-1.x' into protect
agentrickard 7fbcfa4
Removed bad unit test.
agentrickard d0fa08c
Removes references to parent_id variable.
agentrickard f328b01
Merge branch '8.x-1.x' into protect
agentrickard 03fcfa3
Cleans up DeleteAccessCheck.
agentrickard 35b5614
Cleans up some code style and naming issues.
agentrickard 2de4c8f
Renames taxonomy protect test properly.
agentrickard b1a1e96
Passing tests.
agentrickard 43a2cd9
Extends tests to cover user protection.
agentrickard 87436a8
Adds stubs for menu testing. Requires special handling in access checks.
agentrickard 4419c77
Handles cases where deleting a parent deletes its children, which we …
agentrickard 27fdb22
Adds todo for new test.
agentrickard 8c9bf60
Adds menu field note.
agentrickard 297281f
Ensure that we do not run checks for access when they are not required.
agentrickard 7487f5c
Adds api example file.
agentrickard 48a40b5
When checking content fields, ensure that we are pulling from workben…
agentrickard df4ba0e
Optimize lookups for access checks.
agentrickard 5378a8b
Fixes code style issues.
agentrickard File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
303 changes: 303 additions & 0 deletions
303
workbench_access_protect/src/Access/DeleteAccessCheck.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,303 @@ | ||
| <?php | ||
|
|
||
| namespace Drupal\workbench_access_protect\Access; | ||
|
|
||
| use Drupal\Core\Access\AccessResult; | ||
| use Drupal\Core\Entity\EntityTypeBundleInfoInterface; | ||
| use Drupal\Core\Entity\EntityTypeManager; | ||
| use Drupal\Core\Entity\EntityInterface; | ||
| use Drupal\field\Entity\FieldStorageConfig; | ||
| use Drupal\Core\Routing\CurrentRouteMatch; | ||
| use Drupal\workbench_access\UserSectionStorage; | ||
| use Drupal\Core\Entity\EntityFieldManager; | ||
| use Drupal\workbench_access\Entity\AccessSchemeInterface; | ||
|
|
||
| /** | ||
| * Class DeleteAccessCheck. | ||
| */ | ||
| class DeleteAccessCheck implements DeleteAccessCheckInterface { | ||
|
|
||
| /** | ||
| * User account. | ||
| * | ||
| * @var \Drupal\Core\Session\AccountInterface | ||
| */ | ||
| private $account; | ||
|
|
||
| /** | ||
| * Default object for current_route_match service. | ||
| * | ||
| * @var \Drupal\Core\Routing\CurrentRouteMatch | ||
| */ | ||
| private $route; | ||
|
|
||
| /** | ||
| * A class for storing and retrieving sections assigned to a user. | ||
| * | ||
| * @var \Drupal\workbench_access\UserSectionStorage | ||
| */ | ||
| private $userSectionStorage; | ||
|
|
||
| /** | ||
| * Manages the discovery of entity fields. | ||
| * | ||
| * @var \Drupal\Core\Entity\EntityFieldManager | ||
| */ | ||
| private $fieldManager; | ||
|
|
||
| /** | ||
| * Manages entity type plugin definitions. | ||
| * | ||
| * @var \Drupal\Core\Entity\EntityTypeManager | ||
| */ | ||
| private $entityTypeManager; | ||
|
|
||
| /** | ||
| * An interface for an entity type bundle info. | ||
| * | ||
| * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface | ||
| */ | ||
| private $entityTypeBundleInfo; | ||
|
|
||
| /** | ||
| * Constructs a new DeleteAccessCheck. | ||
| */ | ||
| public function __construct(CurrentRouteMatch $route, | ||
| UserSectionStorage $userSectionStorage, | ||
| EntityFieldManager $fieldManager, | ||
| EntityTypeManager $entityTypeManager, | ||
| EntityTypeBundleInfoInterface $entityTypeBundleInfo) { | ||
|
|
||
| $this->route = $route; | ||
| $this->userSectionStorage = $userSectionStorage; | ||
| $this->fieldManager = $fieldManager; | ||
| $this->entityTypeManager = $entityTypeManager; | ||
| $this->entityTypeBundleInfo = $entityTypeBundleInfo; | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * This method is used to determine if it is OK to delete. | ||
| * | ||
| * The check is based on whether or not it is being actively used for access | ||
| * control, and if content is assigned to it. If either of these statements | ||
| * is true, then 'forbidden' will be returned to prevent the term | ||
| * from being deleted. | ||
| * | ||
| * @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden | ||
| * Returns 'forbidden' if the term is being used for access control. | ||
| * Returns 'allowed' if the term is not being used for access control. | ||
| * | ||
| * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException | ||
| * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException | ||
| */ | ||
| public function access() { | ||
|
|
||
| if ($this->isDeleteAllowed()) { | ||
| return AccessResult::allowed(); | ||
| } | ||
|
|
||
| return AccessResult::forbidden(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function isDeleteAllowed(EntityInterface $entity) { | ||
| $return = TRUE; | ||
|
|
||
| // If this entity does not have users assigned to it for access control and | ||
| // is not assigned to any pieces of content, it is OK to delete it. | ||
| $assigned_members = $this->hasMembers($entity); | ||
| if ($assigned_members) { | ||
| return FALSE; | ||
| } | ||
|
|
||
| // Breaking up the IF statement is a performance gain, since most sites | ||
| // have more nodes than users. | ||
| $assigned_content = $this->hasContent($entity); | ||
| if ($assigned_content) { | ||
| $return = FALSE; | ||
| } | ||
|
|
||
| return $return; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function getBundles(EntityInterface $entity) { | ||
| $bundle = $this->entityTypeManager->getDefinitions()[$entity->getEntityTypeId()]->get('bundle_of'); | ||
| $bundles = $this->entityTypeBundleInfo->getBundleInfo($bundle); | ||
| return array_combine(array_keys($bundles), array_keys($bundles)); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function hasBundles(EntityInterface $entity) { | ||
| return !is_null($this->entityTypeManager->getDefinitions()[$entity->getEntityTypeId()]->get('bundle_of')); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function isDeleteAllowedBundle($bundle, $entity) { | ||
| $bundle_of = $this->entityTypeManager->getDefinitions()[$entity->getEntityTypeId()]->get('bundle_of'); | ||
| $entity_id_key = $this->entityTypeManager->getDefinitions()[$entity->getEntityTypeId()]->get('entity_keys')['id']; | ||
| $entities = $this->entityTypeManager->getStorage($bundle_of)->loadByProperties( | ||
| [$entity_id_key => $bundle] | ||
| ); | ||
|
|
||
| /* | ||
| * Cycle through the entities of this bundle. As soon as one is discovered | ||
| * as being actively used for access control, we can deny delete. | ||
| */ | ||
| foreach ($entities as $bundle) { | ||
| if ($this->isDeleteAllowed($bundle) === FALSE) { | ||
| return FALSE; | ||
| } | ||
| } | ||
|
|
||
| return TRUE; | ||
| } | ||
|
|
||
| /** | ||
| * Determines if this term has active members in it. | ||
| * | ||
| * @param \Drupal\Core\Entity\EntityInterface $entity | ||
| * The entity to inspect. | ||
| * | ||
| * @return bool | ||
| * TRUE if the term has members, FALSE otherwise. | ||
| */ | ||
| private function hasMembers(EntityInterface $entity) { | ||
| return (bool) (count($this->getActiveSections($entity)) > 0); | ||
| } | ||
|
|
||
| /** | ||
| * Inspects the given entity for active users. | ||
| * | ||
| * @param \Drupal\Core\Entity\EntityInterface $entity | ||
| * The entity to inspect. | ||
| * | ||
| * @return array | ||
| * An array of the users assigned to this section. | ||
| */ | ||
| private function getActiveSections(EntityInterface $entity) { | ||
| /** @var \Drupal\workbench_access\UserSectionStorageInterface $sectionStorage */ | ||
| $sectionStorage = $this->userSectionStorage; | ||
|
|
||
| $editors = array_reduce($this->entityTypeManager->getStorage('access_scheme')->loadMultiple(), | ||
| function (array $editors, AccessSchemeInterface $scheme) use ($sectionStorage, $entity) { | ||
| $editors += $sectionStorage->getEditors($scheme, $entity->id()); | ||
| return $editors; | ||
| }, []); | ||
|
|
||
| return $editors; | ||
| } | ||
|
|
||
| /** | ||
| * Inspects the given entity for active content. | ||
| * | ||
| * @param \Drupal\Core\Entity\EntityInterface $entity | ||
| * The entity to inspect. | ||
| * | ||
| * @return bool | ||
| * TRUE if content is assigned to this entity. | ||
| * FALSE if content is not assigned to this entity. | ||
| */ | ||
| private function hasContent(EntityInterface $entity) { | ||
| foreach ($this->getAllReferenceFields($entity) as $name => $fieldConfig) { | ||
| // Get the entity reference and determine if it is access controlled. | ||
| if ($fieldConfig instanceof FieldStorageConfig) { | ||
| // Base query. | ||
| $entities = \Drupal::entityQuery($fieldConfig->get('entity_type')); | ||
| // Check all children, if required. | ||
| // @TODO: allow this to be a configurable setting. | ||
| // @TODO: test coverage. | ||
| $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); | ||
| if (method_exists($storage, 'loadChildren')) { | ||
| $children = $storage->loadChildren($entity->id()); | ||
| if ($children) { | ||
| $ids = array_keys($children); | ||
| $ids[] = $entity->id(); | ||
| $entities->condition($fieldConfig->get('field_name'), $ids, 'IN'); | ||
| $children_checked = TRUE; | ||
| } | ||
| } | ||
| // Else just query this entity. | ||
| if (empty($children_checked)) { | ||
| $entities->condition($fieldConfig->get('field_name'), $entity->id()); | ||
| } | ||
| $result = $entities->range(0, 1)->execute(); | ||
| if (count($result) > 0) { | ||
| return TRUE; | ||
| } | ||
| } | ||
| // @TODO: check for menu field handling. | ||
| } | ||
|
|
||
| return FALSE; | ||
| } | ||
|
|
||
| /** | ||
| * Get all entities with referencing fields targeting the ID. | ||
| * | ||
| * @param \Drupal\Core\Entity\EntityInterface $entity | ||
| * The entity to inspect. | ||
| * | ||
| * @return array | ||
| * An array of entities with referencing fields targeting the given ID. | ||
| */ | ||
| private function getAllReferenceFields(EntityInterface $entity) { | ||
| // First, we are going to try to retrieve a cached instance. | ||
| $found_fields = \Drupal::cache()->get('workbench_access_protect'); | ||
| if ($found_fields === FALSE) { | ||
| $found_fields = []; | ||
| $scheme_storage = \Drupal::entityTypeManager() | ||
| ->getStorage('access_scheme'); | ||
| // Grab the fields configured for each access scheme and then check | ||
| // that they are within our list of protected entity types. | ||
| foreach ($scheme_storage->loadMultiple() as $scheme) { | ||
| $access_scheme = $scheme->getAccessScheme(); | ||
| $scheme_type = $scheme->get('scheme'); | ||
| $protect_list = workbench_access_protect_list($scheme_type); | ||
| if (in_array($entity->getEntityTypeId(), $protect_list, TRUE)) { | ||
| $configuration = $access_scheme->getConfiguration(); | ||
| foreach ($configuration['fields'] as $field_info) { | ||
| $fieldConfig = FieldStorageConfig::loadByName($field_info['entity_type'], $field_info['field']); | ||
| if ($fieldConfig) { | ||
| $found_fields[$field_info['field']] = $fieldConfig; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| $found_fields = $found_fields->data; | ||
| } | ||
|
|
||
| \Drupal::cache() | ||
| ->set('workbench_access_protect', $found_fields, \Drupal::time()->getRequestTime() + 60); | ||
|
|
||
| return $found_fields; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function isAccessControlled(EntityInterface $entity) { | ||
| $schemes = $this->entityTypeManager->getStorage('access_scheme')->loadMultiple(); | ||
| /** @var \Drupal\workbench_access\Entity\AccessScheme $scheme */ | ||
| foreach ($schemes as $scheme) { | ||
| foreach ($scheme->getAccessScheme()->getConfiguration() as $config) { | ||
| if (in_array($entity->bundle(), $config)) { | ||
| return TRUE; | ||
| } | ||
| } | ||
| } | ||
| return FALSE; | ||
| } | ||
|
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a call to a method here that will check to see if the term has children or not.
TODO: figure out how to abstract this so that this class doesn't have any explicit taxonomy or term references. Adding a method to the Drupal\workbench_access\Plugin\AccessControlHierarchy class to see if this scheme has children might be the way to do it.