access_policy-1.0.x-dev/src/AccessPolicyDiscovery.php
src/AccessPolicyDiscovery.php
<?php namespace Drupal\access_policy; use Drupal\access_policy\Validation\ExecutionContext; use Drupal\access_policy\Validation\ExecutionContextInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Handles the discovery of access policies for a given user and entity. */ class AccessPolicyDiscovery implements AccessPolicyDiscoveryInterface { use StringTranslationTrait; /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * Access policy information service. * * @var \Drupal\access_policy\AccessPolicyInformation */ protected $accessPolicyInformation; /** * The selection rule plugin manager. * * @var \Drupal\access_policy\AccessPolicyHandlerManager */ protected $selectionRuleManager; /** * Array of allowed access policies, keyed by entity uuid. * * @var array */ protected $allowedPolicies = []; /** * Array of applicable access policies, keyed by entity uuid. * * @var array */ protected $applicablePolicies = []; /** * Constructs a new AccessPolicySelection service. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\access_policy\AccessPolicyInformation $access_policy_information * The access policy information service. * @param \Drupal\access_policy\AccessPolicyHandlerManager $selection_rule_manager * The selection rule manager. */ public function __construct(EntityTypeManagerInterface $entity_type_manager, AccessPolicyInformation $access_policy_information, AccessPolicyHandlerManager $selection_rule_manager) { $this->entityTypeManager = $entity_type_manager; $this->accessPolicyInformation = $access_policy_information; $this->selectionRuleManager = $selection_rule_manager; } /** * {@inheritdoc} */ public function getAllowedAccessPolicies(EntityInterface $entity, AccountInterface $account) { if (isset($this->allowedPolicies[$entity->uuid()])) { return $this->allowedPolicies[$entity->uuid()]; } $this->allowedPolicies[$entity->uuid()] = []; $policies = $this->getAccessPoliciesSorted($entity->getEntityType()); foreach ($policies as $policy) { if ($this->isAllowed($policy, $entity, $account)) { $this->allowedPolicies[$entity->uuid()][] = $policy; } } return $this->allowedPolicies[$entity->uuid()]; } /** * {@inheritdoc} */ public function getApplicablePolicies(EntityInterface $entity, AccountInterface $account) { if (isset($this->applicablePolicies[$entity->uuid()])) { return $this->applicablePolicies[$entity->uuid()]; } $this->applicablePolicies[$entity->uuid()] = []; $policies = $this->getAccessPoliciesSorted($entity->getEntityType()); if (!empty($policies)) { $candidates = []; foreach ($policies as $policy) { $is_allowed = $this->isAllowed($policy, $entity, $account); if ($is_allowed) { $selection_rules_valid = $this->selectionRulesValid($policy, $entity, $account); if ($selection_rules_valid) { // If this is the first policy and this is unique then return that // policy and exit. if (empty($candidates) && !$policy->isMultiple()) { $this->applicablePolicies[$entity->uuid()] = $policy; return [$this->applicablePolicies[$entity->uuid()]]; } // Group the policies by selection set if applicable. $selection_sets = $policy->getSelectionSet() ?? ['_empty']; foreach ($selection_sets as $selection_set) { $candidates[$selection_set][] = $policy; } } } } // Always return the first set of candidates. return $this->applicablePolicies[$entity->uuid()] = reset($candidates); } return FALSE; } /** * Determine whether a user is allowed to assign an access policy. * * This checks whether the user has the permission or if the policy is * applicable. * * @param \Drupal\Core\Entity\EntityInterface $policy * The access policy entity. * @param \Drupal\Core\Entity\EntityInterface $entity * The current entity. * @param \Drupal\Core\Session\AccountInterface $account * The current user. * * @return bool * TRUE if the user can use the policy; FALSE otherwise. */ protected function isAllowed(EntityInterface $policy, EntityInterface $entity, AccountInterface $account) { if ($this->isApplicable($policy, $entity) && $this->hasPermission($policy, $account)) { return TRUE; } return FALSE; } /** * Determine whether a user has the permission to assign an access policy. * * @param \Drupal\Core\Entity\EntityInterface $policy * The access policy entity. * @param \Drupal\Core\Session\AccountInterface $account * The current user. * * @return bool * TRUE if the user can use the policy; FALSE otherwise. */ protected function hasPermission(EntityInterface $policy, AccountInterface $account) { if (!$account->hasPermission('assign ' . $policy->id() . ' access policy')) { return FALSE; } return TRUE; } /** * Determine whether a policy is applicable for an entity. * * @param \Drupal\Core\Entity\EntityInterface $policy * The access policy entity. * @param \Drupal\Core\Entity\EntityInterface $entity * The current entity. * * @return bool * TRUE if the policy is applicable; FALSE otherwise. * * @throws \Drupal\Component\Plugin\Exception\PluginException */ protected function isApplicable(EntityInterface $policy, EntityInterface $entity) { // If the policy has selection rules, but they're not applicable to this // entity, then this policy is not allowed to be assigned. $has_handlers = !empty($policy->getHandlers('selection_rule')); $is_applicable = !empty($this->selectionRuleManager->getApplicableHandlers($policy, $entity)); if ($has_handlers && !$is_applicable) { return FALSE; } return TRUE; } /** * Determine whether the selection rules are valid. * * @param \Drupal\Core\Entity\EntityInterface $policy * The access policy. * @param \Drupal\Core\Entity\EntityInterface $entity * The current entity. * @param \Drupal\Core\Session\AccountInterface $account * The current user. * * @return bool * TRUE if the selection rule are valid; FALSE otherwise. */ protected function selectionRulesValid(EntityInterface $policy, EntityInterface $entity, AccountInterface $account) { $selection_rules = $this->selectionRuleManager->getHandlersFromPolicy($policy); if (!empty($selection_rules)) { $context = ExecutionContext::create('assign access policy', [ 'entity' => $entity, 'user' => $account, ]); return $this->validateSelectionRules($selection_rules, $context, $policy->getSelectionRuleOperator()); } return TRUE; } /** * Validate the selection rules. * * @param array $selection_rules * Array of selection rules. * @param \Drupal\access_policy\Validation\ExecutionContextInterface $context * The execution context. * @param string $operator * The selection rule operator. * * @return bool * TRUE if the selection rule passes; FALSE otherwise. */ protected function validateSelectionRules(array $selection_rules, ExecutionContextInterface $context, $operator = 'AND') { $access = TRUE; foreach ($selection_rules as $plugin) { $plugin->setContext($context); // Compute the final value that will be validated against. $plugin->getValue(); $entity = $context->get('entity'); $account = $context->get('user'); // @todo deprecate passing arguments to validate(). $access = $plugin->validate($entity, $account); switch ($operator) { // If all rules are required then fail immediately if any return false. case 'AND': if (!$access) { return FALSE; } break; // If any rules are required then pass immediately if any return true. case 'OR': if ($access) { return TRUE; } break; } } return $access; } /** * Get access policies sorted by ascending weight. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The entity type. * * @return array * Array of sorted access policies. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ private function getAccessPoliciesSorted(EntityTypeInterface $entity_type) { $policies = $this->accessPolicyInformation->getEnabledForEntitiesOfEntityType($entity_type); $sorted = []; if (!empty($policies)) { foreach ($policies as $policy) { $sorted[$policy->id()] = $policy->getWeight(); } asort($sorted); return $this->entityTypeManager->getStorage('access_policy') ->loadMultiple(array_keys($sorted)); } return []; } /** * Reset the access policy discovery cache. */ public function resetCache() { $this->allowedPolicies = NULL; $this->applicablePolicies = NULL; } }