class AccessPolicyProcessor
Same name in other branches
- 10 core/lib/Drupal/Core/Session/AccessPolicyProcessor.php \Drupal\Core\Session\AccessPolicyProcessor
Processes access policies into permissions for an account.
Hierarchy
- class \Drupal\Core\Session\AccessPolicyProcessor implements \Drupal\Core\Session\AccessPolicyProcessorInterface
Expanded class hierarchy of AccessPolicyProcessor
1 file declares its use of AccessPolicyProcessor
- AccessPolicyProcessorTest.php in core/
tests/ Drupal/ Tests/ Core/ Session/ AccessPolicyProcessorTest.php
File
-
core/
lib/ Drupal/ Core/ Session/ AccessPolicyProcessor.php, line 12
Namespace
Drupal\Core\SessionView source
class AccessPolicyProcessor implements AccessPolicyProcessorInterface {
/**
* The access policies.
*
* @var \Drupal\Core\Session\AccessPolicyInterface[]
*/
protected array $accessPolicies = [];
public function __construct(VariationCacheInterface $variationCache, VariationCacheInterface $variationStatic, CacheBackendInterface $static, AccountProxyInterface $currentUser, AccountSwitcherInterface $accountSwitcher) {
}
/**
* {@inheritdoc}
*/
public function addAccessPolicy(AccessPolicyInterface $access_policy) : void {
$this->accessPolicies[] = $access_policy;
}
/**
* {@inheritdoc}
*/
public function processAccessPolicies(AccountInterface $account, string $scope = AccessPolicyInterface::SCOPE_DRUPAL) : CalculatedPermissionsInterface {
$persistent_cache_contexts = $this->getPersistentCacheContexts($scope);
$initial_cacheability = (new CacheableMetadata())->addCacheContexts($persistent_cache_contexts);
$cache_keys = [
'access_policies',
$scope,
];
// Whether to switch the user account during cache storage and retrieval.
//
// This is necessary because permissions may be stored varying by the user
// cache context or one of its child contexts. Because we may be calculating
// permissions for an account other than the current user, we need to ensure
// that the cache ID for said entry is set according to the passed in
// account's data.
//
// Sadly, there is currently no way to reuse the cache context logic outside
// of the caching layer. If we every get a system that allows us to process
// cache contexts with a provided environmental value (such as the current
// user), then we should update the logic below to use that instead.
//
// For the time being, we set the current user to the passed in account if
// they differ, calculate the permissions and then immediately switch back.
// It's the cleanest solution we could come up with that doesn't involve
// copying half of the caching layer and that still allows us to use the
// VariationCache for accounts other than the current user.
$switch_account = FALSE;
if ($this->currentUser
->id() !== $account->id()) {
foreach ($persistent_cache_contexts as $cache_context) {
[
$cache_context_root,
] = explode('.', $cache_context, 2);
if ($cache_context_root === 'user') {
$switch_account = TRUE;
$this->accountSwitcher
->switchTo($account);
break;
}
}
}
// Wrap the whole cache retrieval or calculation in a try-finally so that we
// always switch back to the original account after the return statement or
// if an exception was thrown.
try {
// Retrieve the permissions from the static cache if available.
if ($static_cache = $this->variationStatic
->get($cache_keys, $initial_cacheability)) {
return $static_cache->data;
}
// Retrieve the permissions from the persistent cache if available.
if ($cache = $this->variationCache
->get($cache_keys, $initial_cacheability)) {
$calculated_permissions = $cache->data;
$cacheability = CacheableMetadata::createFromObject($calculated_permissions);
// Convert the calculated permissions into an immutable value object and
// store it in the static cache so that we don't have to do the same
// conversion every time we call for the calculated permissions from a
// warm static cache.
$calculated_permissions = new CalculatedPermissions($calculated_permissions);
$this->variationStatic
->set($cache_keys, $calculated_permissions, $cacheability, $initial_cacheability);
return $calculated_permissions;
}
// Otherwise build the permissions from scratch.
// Build mode, allow all access policies to add initial data.
$calculated_permissions = new RefinableCalculatedPermissions();
foreach ($this->accessPolicies as $access_policy) {
if (!$access_policy->applies($scope)) {
continue;
}
$policy_permissions = $access_policy->calculatePermissions($account, $scope);
if (!$this->validateScope($scope, $policy_permissions)) {
throw new AccessPolicyScopeException(sprintf('The access policy "%s" returned permissions for scopes other than "%s".', get_class($access_policy), $scope));
}
$calculated_permissions = $calculated_permissions->merge($policy_permissions);
}
// Alter mode, allow all access policies to alter the complete build.
foreach ($this->accessPolicies as $access_policy) {
if (!$access_policy->applies($scope)) {
continue;
}
$access_policy->alterPermissions($account, $scope, $calculated_permissions);
if (!$this->validateScope($scope, $calculated_permissions)) {
throw new AccessPolicyScopeException(sprintf('The access policy "%s" altered permissions in a scope other than "%s".', get_class($access_policy), $scope));
}
}
// Apply a cache tag to easily flush the calculated permissions.
$calculated_permissions->addCacheTags([
'access_policies',
]);
// First store the actual calculated permissions in the persistent cache,
// along with the final cache contexts after all calculations have run. We
// need to store the RefinableCalculatedPermissions in the persistent
// cache, so we can still get the final cacheability from it for when we
// run into a persistent cache hit but not a static one. At that point, if
// we had stored a CalculatedPermissions object, we would no longer be
// able to ask for its cache contexts.
$cacheability = CacheableMetadata::createFromObject($calculated_permissions);
$this->variationCache
->set($cache_keys, $calculated_permissions, $cacheability, $initial_cacheability);
// Then convert the calculated permissions to an immutable value object
// and store it in the static cache so that we don't have to do the same
// conversion every time we call for the calculated permissions from a
// warm static cache.
$calculated_permissions = new CalculatedPermissions($calculated_permissions);
$this->variationStatic
->set($cache_keys, $calculated_permissions, $cacheability, $initial_cacheability);
// Return the permissions as an immutable value object.
return $calculated_permissions;
} finally {
if ($switch_account) {
$this->accountSwitcher
->switchBack();
}
}
}
/**
* Gets the persistent cache contexts of all policies within a given scope.
*
* @param string $scope
* The scope to get the persistent cache contexts for.
*
* @return string[]
* The persistent cache contexts of all policies within the scope.
*/
protected function getPersistentCacheContexts(string $scope) : array {
$cid = 'access_policies:access_policy_processor:contexts:' . $scope;
// Retrieve the contexts from the regular static cache if available.
if ($static_cache = $this->static
->get($cid)) {
return $static_cache->data;
}
$contexts = [];
foreach ($this->accessPolicies as $access_policy) {
if ($access_policy->applies($scope)) {
$contexts[] = $access_policy->getPersistentCacheContexts();
}
}
$contexts = array_merge(...$contexts);
// Store the contexts in the regular static cache.
$this->static
->set($cid, $contexts);
return $contexts;
}
/**
* Validates if calculated permissions all match a single scope.
*
* @param string $scope
* The scope to match.
* @param \Drupal\Core\Session\CalculatedPermissionsInterface $calculated_permissions
* The calculated permissions that should match the scope.
*
* @return bool
* Whether the calculated permissions match the scope.
*/
protected function validateScope(string $scope, CalculatedPermissionsInterface $calculated_permissions) : bool {
$actual_scopes = $calculated_permissions->getScopes();
return empty($actual_scopes) || $actual_scopes === [
$scope,
];
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overriden Title |
---|---|---|---|---|
AccessPolicyProcessor::$accessPolicies | protected | property | The access policies. | |
AccessPolicyProcessor::addAccessPolicy | public | function | Adds an access policy. | Overrides AccessPolicyProcessorInterface::addAccessPolicy |
AccessPolicyProcessor::getPersistentCacheContexts | protected | function | Gets the persistent cache contexts of all policies within a given scope. | |
AccessPolicyProcessor::processAccessPolicies | public | function | Processes the access policies for an account within a given scope. | Overrides AccessPolicyProcessorInterface::processAccessPolicies |
AccessPolicyProcessor::validateScope | protected | function | Validates if calculated permissions all match a single scope. | |
AccessPolicyProcessor::__construct | public | function |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.