Security update for permissions_by_term
[yaffs-website] / web / modules / contrib / permissions_by_term / modules / permissions_by_entity / src / Service / AccessChecker.php
diff --git a/web/modules/contrib/permissions_by_term/modules/permissions_by_entity/src/Service/AccessChecker.php b/web/modules/contrib/permissions_by_term/modules/permissions_by_entity/src/Service/AccessChecker.php
new file mode 100644 (file)
index 0000000..0911b9f
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+
+namespace Drupal\permissions_by_entity\Service;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\permissions_by_entity\Event\EntityFieldValueAccessDeniedEvent;
+use Drupal\permissions_by_entity\Event\PermissionsByEntityEvents;
+use Drupal\permissions_by_term\Service\AccessCheck;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Class AccessChecker.
+ *
+ * @package Drupal\permissions_by_entity\Service
+ */
+class AccessChecker extends AccessCheck implements AccessCheckerInterface {
+
+  /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  private $eventDispatcher;
+
+  /**
+   * The cache for checked entities.
+   *
+   * @var \Drupal\permissions_by_entity\Service\CheckedEntityCache
+   */
+  private $checkedEntityCache;
+
+  /**
+   * The entity field value access denied event.
+   *
+   * @var \Drupal\permissions_by_entity\Event\EntityFieldValueAccessDeniedEvent
+   */
+  private $event;
+
+  /**
+   * AccessChecker constructor.
+   *
+   * We override the constructor, because we do not need the entity manager.
+   *
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+   *   The event dispatcher.
+   * @param \Drupal\permissions_by_entity\Service\CheckedEntityCache $checked_entity_cache
+   *   The cache for checked entities.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The core entity type manager.
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection.
+   */
+  public function __construct(
+    EventDispatcherInterface $event_dispatcher,
+    CheckedEntityCache $checked_entity_cache,
+    EntityManagerInterface $entity_manager,
+    Connection $database
+  ) {
+    parent::__construct($database, $event_dispatcher);
+    $this->eventDispatcher = $event_dispatcher;
+    $this->checkedEntityCache = $checked_entity_cache;
+
+    $this->event = new EntityFieldValueAccessDeniedEvent();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isAccessAllowed(ContentEntityInterface $entity, $uid = FALSE) {
+    // Iterate over the fields the entity contains.
+    foreach ($entity->getFields() as $field) {
+
+      // We only need to check for entity reference fields
+      // which references to a taxonomy term.
+      if (
+        $field->getFieldDefinition()->getType() == 'entity_reference' &&
+        $field->getFieldDefinition()->getSetting('target_type') == 'taxonomy_term'
+      ) {
+
+        // Iterate over each referenced taxonomy term.
+        /** @var \Drupal\Core\Field\FieldItemInterface $item */
+        foreach ($field->getValue() as $item) {
+          // Let "Permissions By Term" do the actual check.
+          if (
+            !empty($item['target_id']) &&
+            !$this->isAccessAllowedByDatabase($item['target_id'], $uid)
+          ) {
+            // Return that the user is not allowed to access this entity.
+            return FALSE;
+          }
+        }
+      }
+
+      // Check if the field contains another content entity,
+      // that we need to check.
+      if ($field->entity && $field->entity instanceof ContentEntityInterface) {
+
+        // We need to iterate over the entities.
+        $num_values = $field->count();
+        if ($num_values > 0) {
+
+          // Iterate over the field values.
+          for ($i = 0; $i < $num_values; $i++) {
+
+            // Get the value of the current field index.
+            $field_value = $field->get($i);
+
+            // If the value is null or empty we continue with the next index of
+            // the loop.
+            if (!$field_value) {
+              continue;
+            }
+
+            // Get the field entity.
+            $field_entity = $field_value->entity;
+
+            // If the field entity is null we also continue with the next index
+            // of the loop.
+            if (!$field_entity) {
+              continue;
+            }
+
+            // It is possible, that the referenced field entity creates a
+            // circular dependency to the current entity. This will cause
+            // memory limit exhausted errors because there is no way out for
+            // the script. To avoid this, we need to be able to imagine if we
+            // already checked this field entity before. If so, we ignore this
+            // field entity, if not we can securely do a recursive call.
+            //
+            // Using own method to avoid "max nesting level error" trying to
+            // check if the field entity is stored in the entitiesChecked array.
+            if ($this->checkedEntityCache->isChecked($field_entity)) {
+              continue;
+            }
+            else {
+              // Add the current entity to the list of checked entities.
+              $this->checkedEntityCache->add($field_entity);
+            }
+
+            // Do a recursive call to check if the user is allowed to access
+            // this entity.
+            if (!$this->isAccessAllowed($field_entity, $uid)) {
+
+              // Dispatch an event to allow subscribers
+              // to do something in this case.
+              $this->event->setIndex($i);
+              $this->event->setField($field);
+              $this->event->setEntity($field_entity);
+              $this->event->setUid($uid);
+
+              $this->eventDispatcher
+                ->dispatch(
+                  PermissionsByEntityEvents::ENTITY_FIELD_VALUE_ACCESS_DENIED_EVENT,
+                  $this->event
+                );
+              $i = $this->event->getIndex();
+            }
+          }
+        }
+      }
+    }
+
+    return TRUE;
+  }
+
+}