Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessArgumentsResolverFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Component\Utility\ArgumentsResolver;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Resolves the arguments to pass to an access check callable.
|
||||
*/
|
||||
class AccessArgumentsResolverFactory implements AccessArgumentsResolverFactoryInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL) {
|
||||
$route = $route_match->getRouteObject();
|
||||
|
||||
// Defaults for the parameters defined on the route object need to be added
|
||||
// to the raw arguments.
|
||||
$raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults();
|
||||
|
||||
$upcasted_route_arguments = $route_match->getParameters()->all();
|
||||
|
||||
// Parameters which are not defined on the route object, but still are
|
||||
// essential for access checking are passed as wildcards to the argument
|
||||
// resolver. An access-check method with a parameter of type Route,
|
||||
// RouteMatchInterface, AccountInterface or Request will receive those
|
||||
// arguments regardless of the parameter name.
|
||||
$wildcard_arguments = [$route, $route_match, $account];
|
||||
if (isset($request)) {
|
||||
$wildcard_arguments[] = $request;
|
||||
}
|
||||
|
||||
return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Constructs the arguments resolver instance to use when running access checks.
|
||||
*/
|
||||
interface AccessArgumentsResolverFactoryInterface {
|
||||
|
||||
/**
|
||||
* Returns the arguments resolver to use when running access checks.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object to be checked.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account being checked.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* Optional, the request object.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\ArgumentsResolverInterface
|
||||
* The parametrized arguments resolver instance.
|
||||
*/
|
||||
public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL);
|
||||
|
||||
}
|
29
core/lib/Drupal/Core/Access/AccessCheckInterface.php
Normal file
29
core/lib/Drupal/Core/Access/AccessCheckInterface.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessCheckInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
|
||||
/**
|
||||
* An access check service determines access rules for particular routes.
|
||||
*/
|
||||
interface AccessCheckInterface extends RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* Declares whether the access check applies to a specific route or not.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to consider attaching to.
|
||||
*
|
||||
* @return array
|
||||
* An array of route requirement keys this access checker applies to.
|
||||
*/
|
||||
public function applies(Route $route);
|
||||
|
||||
}
|
17
core/lib/Drupal/Core/Access/AccessException.php
Normal file
17
core/lib/Drupal/Core/Access/AccessException.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* An exception thrown for access errors.
|
||||
*
|
||||
* Examples could be invalid access callback return values, or invalid access
|
||||
* objects being used.
|
||||
*/
|
||||
class AccessException extends \RuntimeException {
|
||||
}
|
178
core/lib/Drupal/Core/Access/AccessManager.php
Normal file
178
core/lib/Drupal/Core/Access/AccessManager.php
Normal file
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Component\Utility\ArgumentsResolverInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
|
||||
/**
|
||||
* Attaches access check services to routes and runs them on request.
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Access\AccessManagerTest
|
||||
*/
|
||||
class AccessManager implements AccessManagerInterface {
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The paramconverter manager.
|
||||
*
|
||||
* @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
|
||||
*/
|
||||
protected $paramConverterManager;
|
||||
|
||||
/**
|
||||
* The access arguments resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface
|
||||
*/
|
||||
protected $argumentsResolverFactory;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The check provider.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CheckProviderInterface
|
||||
*/
|
||||
protected $checkProvider;
|
||||
|
||||
/**
|
||||
* Constructs a AccessManager instance.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
* @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $paramconverter_manager
|
||||
* The param converter manager.
|
||||
* @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $arguments_resolver_factory
|
||||
* The access arguments resolver.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param CheckProviderInterface $check_provider
|
||||
*/
|
||||
public function __construct(RouteProviderInterface $route_provider, ParamConverterManagerInterface $paramconverter_manager, AccessArgumentsResolverFactoryInterface $arguments_resolver_factory, AccountInterface $current_user, CheckProviderInterface $check_provider) {
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->paramConverterManager = $paramconverter_manager;
|
||||
$this->argumentsResolverFactory = $arguments_resolver_factory;
|
||||
$this->currentUser = $current_user;
|
||||
$this->checkProvider = $check_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
try {
|
||||
$route = $this->routeProvider->getRouteByName($route_name, $parameters);
|
||||
|
||||
// ParamConverterManager relies on the route name and object being
|
||||
// available from the parameters array.
|
||||
$parameters[RouteObjectInterface::ROUTE_NAME] = $route_name;
|
||||
$parameters[RouteObjectInterface::ROUTE_OBJECT] = $route;
|
||||
$upcasted_parameters = $this->paramConverterManager->convert($parameters + $route->getDefaults());
|
||||
|
||||
$route_match = new RouteMatch($route_name, $route, $upcasted_parameters, $parameters);
|
||||
return $this->check($route_match, $account, NULL, $return_as_object);
|
||||
}
|
||||
catch (RouteNotFoundException $e) {
|
||||
// Cacheable until extensions change.
|
||||
$result = AccessResult::forbidden()->addCacheTags(['config:core.extension']);
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
catch (ParamNotConvertedException $e) {
|
||||
// Uncacheable because conversion of the parameter may not have been
|
||||
// possible due to dynamic circumstances.
|
||||
$result = AccessResult::forbidden()->setCacheMaxAge(0);
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequest(Request $request, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$route_match = RouteMatch::createFromRequest($request);
|
||||
return $this->check($route_match, $account, $request, $return_as_object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(RouteMatchInterface $route_match, AccountInterface $account = NULL, Request $request = NULL, $return_as_object = FALSE) {
|
||||
if (!isset($account)) {
|
||||
$account = $this->currentUser;
|
||||
}
|
||||
$route = $route_match->getRouteObject();
|
||||
$checks = $route->getOption('_access_checks') ?: array();
|
||||
|
||||
// Filter out checks which require the incoming request.
|
||||
if (!isset($request)) {
|
||||
$checks = array_diff($checks, $this->checkProvider->getChecksNeedRequest());
|
||||
}
|
||||
|
||||
$result = AccessResult::neutral();
|
||||
if (!empty($checks)) {
|
||||
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request);
|
||||
|
||||
if (!$checks) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
$result = AccessResult::allowed();
|
||||
foreach ($checks as $service_id) {
|
||||
$result = $result->andIf($this->performCheck($service_id, $arguments_resolver));
|
||||
}
|
||||
}
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the specified access check.
|
||||
*
|
||||
* @param string $service_id
|
||||
* The access check service ID to use.
|
||||
* @param \Drupal\Component\Utility\ArgumentsResolverInterface $arguments_resolver
|
||||
* The parametrized arguments resolver instance.
|
||||
*
|
||||
* @throws \Drupal\Core\Access\AccessException
|
||||
* Thrown when the access check returns an invalid value.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
protected function performCheck($service_id, ArgumentsResolverInterface $arguments_resolver) {
|
||||
$callable = $this->checkProvider->loadCheck($service_id);
|
||||
$arguments = $arguments_resolver->getArguments($callable);
|
||||
/** @var \Drupal\Core\Access\AccessResultInterface $service_access **/
|
||||
$service_access = call_user_func_array($callable, $arguments);
|
||||
|
||||
if (!$service_access instanceof AccessResultInterface) {
|
||||
throw new AccessException("Access error in $service_id. Access services must return an object that implements AccessResultInterface.");
|
||||
}
|
||||
|
||||
return $service_access;
|
||||
}
|
||||
|
||||
|
||||
}
|
88
core/lib/Drupal/Core/Access/AccessManagerInterface.php
Normal file
88
core/lib/Drupal/Core/Access/AccessManagerInterface.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for attaching and running access check services.
|
||||
*/
|
||||
interface AccessManagerInterface {
|
||||
|
||||
/**
|
||||
* Checks a named route with parameters against applicable access check services.
|
||||
*
|
||||
* Determines whether the route is accessible or not.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The route to check access to.
|
||||
* @param array $parameters
|
||||
* Optional array of values to substitute into the route path pattern.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
* user.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
/**
|
||||
* Execute access checks against the incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* The incoming request.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
* user.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function checkRequest(Request $request, AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
/**
|
||||
* Checks a route against applicable access check services.
|
||||
*
|
||||
* Determines whether the route is accessible or not.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
* user.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* Optional, a request. Only supply this parameter when checking the
|
||||
* incoming request, do not specify when checking routes on output.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function check(RouteMatchInterface $route_match, AccountInterface $account = NULL, Request $request = NULL, $return_as_object = FALSE);
|
||||
|
||||
}
|
451
core/lib/Drupal/Core/Access/AccessResult.php
Normal file
451
core/lib/Drupal/Core/Access/AccessResult.php
Normal file
|
@ -0,0 +1,451 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResult.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Config\ConfigBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Value object for passing an access result with cacheability metadata.
|
||||
*
|
||||
* The access result itself — excluding the cacheability metadata — is
|
||||
* immutable. There are subclasses for each of the three possible access results
|
||||
* themselves:
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultAllowed
|
||||
* @see \Drupal\Core\Access\AccessResultForbidden
|
||||
* @see \Drupal\Core\Access\AccessResultNeutral
|
||||
*
|
||||
* When using ::orIf() and ::andIf(), cacheability metadata will be merged
|
||||
* accordingly as well.
|
||||
*/
|
||||
abstract class AccessResult implements AccessResultInterface, CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* The cache context IDs (to vary a cache item ID based on active contexts).
|
||||
*
|
||||
* @see \Drupal\Core\Cache\Context\CacheContextInterface
|
||||
* @see \Drupal\Core\Cache\Context\CacheContextsManager::convertTokensToKeys()
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $contexts;
|
||||
|
||||
/**
|
||||
* The cache tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tags;
|
||||
|
||||
/**
|
||||
* The maximum caching time in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxAge;
|
||||
|
||||
/**
|
||||
* Constructs a new AccessResult object.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->resetCacheContexts()
|
||||
->resetCacheTags()
|
||||
// Max-age must be non-zero for an access result to be cacheable.
|
||||
// Typically, cache items are invalidated via associated cache tags, not
|
||||
// via a maximum age.
|
||||
->setCacheMaxAge(Cache::PERMANENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isNeutral() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function neutral() {
|
||||
return new AccessResultNeutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isAllowed() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* isAllowed() will be TRUE.
|
||||
*/
|
||||
public static function allowed() {
|
||||
return new AccessResultAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isForbidden() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* isForbidden() will be TRUE.
|
||||
*/
|
||||
public static function forbidden() {
|
||||
return new AccessResultForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an allowed or neutral access result.
|
||||
*
|
||||
* @param bool $condition
|
||||
* The condition to evaluate.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral()
|
||||
* will be TRUE.
|
||||
*/
|
||||
public static function allowedIf($condition) {
|
||||
return $condition ? static::allowed() : static::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a forbidden or neutral access result.
|
||||
*
|
||||
* @param bool $condition
|
||||
* The condition to evaluate.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
|
||||
* will be TRUE.
|
||||
*/
|
||||
public static function forbiddenIf($condition) {
|
||||
return $condition ? static::forbidden(): static::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an allowed access result if the permission is present, neutral otherwise.
|
||||
*
|
||||
* Checks the permission and adds a 'user.permissions' cache context.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account for which to check a permission.
|
||||
* @param string $permission
|
||||
* The permission to check for.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If the account has the permission, isAllowed() will be TRUE, otherwise
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function allowedIfHasPermission(AccountInterface $account, $permission) {
|
||||
return static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an allowed access result if the permissions are present, neutral otherwise.
|
||||
*
|
||||
* Checks the permission and adds a 'user.permissions' cache contexts.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account for which to check permissions.
|
||||
* @param array $permissions
|
||||
* The permissions to check.
|
||||
* @param string $conjunction
|
||||
* (optional) 'AND' if all permissions are required, 'OR' in case just one.
|
||||
* Defaults to 'AND'
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If the account has the permissions, isAllowed() will be TRUE, otherwise
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function allowedIfHasPermissions(AccountInterface $account, array $permissions, $conjunction = 'AND') {
|
||||
$access = FALSE;
|
||||
|
||||
if ($conjunction == 'AND' && !empty($permissions)) {
|
||||
$access = TRUE;
|
||||
foreach ($permissions as $permission) {
|
||||
if (!$permission_access = $account->hasPermission($permission)) {
|
||||
$access = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission_access = $account->hasPermission($permission)) {
|
||||
$access = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultAllowed
|
||||
*/
|
||||
public function isAllowed() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultForbidden
|
||||
*/
|
||||
public function isForbidden() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultNeutral
|
||||
*/
|
||||
public function isNeutral() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
sort($this->contexts);
|
||||
return $this->contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cache contexts associated with the access result.
|
||||
*
|
||||
* @param string[] $contexts
|
||||
* An array of cache context IDs, used to generate a cache ID.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCacheContexts(array $contexts) {
|
||||
$this->contexts = array_unique(array_merge($this->contexts, $contexts));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets cache contexts (to the empty array).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCacheContexts() {
|
||||
$this->contexts = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cache tags associated with the access result.
|
||||
*
|
||||
* @param array $tags
|
||||
* An array of cache tags.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCacheTags(array $tags) {
|
||||
$this->tags = Cache::mergeTags($this->tags, $tags);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets cache tags (to the empty array).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCacheTags() {
|
||||
$this->tags = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum age for which this access result may be cached.
|
||||
*
|
||||
* @param int $max_age
|
||||
* The maximum time in seconds that this access result may be cached.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCacheMaxAge($max_age) {
|
||||
$this->maxAge = $max_age;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the "user.permissions" cache context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cachePerPermissions() {
|
||||
$this->addCacheContexts(array('user.permissions'));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the "user" cache context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cachePerUser() {
|
||||
$this->addCacheContexts(array('user'));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the entity's cache tag.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity whose cache tag to set on the access result.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cacheUntilEntityChanges(EntityInterface $entity) {
|
||||
$this->addCacheTags($entity->getCacheTags());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the configuration object's cache tag.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigBase $configuration
|
||||
* The configuration object whose cache tag to set on the access result.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
|
||||
$this->addCacheTags($configuration->getCacheTags());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function orIf(AccessResultInterface $other) {
|
||||
$merge_other = FALSE;
|
||||
// $other's cacheability metadata is merged if $merge_other gets set to TRUE
|
||||
// and this happens in three cases:
|
||||
// 1. $other's access result is the one that determines the combined access
|
||||
// result.
|
||||
// 2. This access result is not cacheable and $other's access result is the
|
||||
// same. i.e. attempt to return a cacheable access result.
|
||||
// 3. Neither access result is 'forbidden' and both are cacheable: inherit
|
||||
// the other's cacheability metadata because it may turn into a
|
||||
// 'forbidden' for another value of the cache contexts in the
|
||||
// cacheability metadata. In other words: this is necessary to respect
|
||||
// the contagious nature of the 'forbidden' access result.
|
||||
// e.g. we have two access results A and B. Neither is forbidden. A is
|
||||
// globally cacheable (no cache contexts). B is cacheable per role. If we
|
||||
// don't have merging case 3, then A->orIf(B) will be globally cacheable,
|
||||
// which means that even if a user of a different role logs in, the
|
||||
// cached access result will be used, even though for that other role, B
|
||||
// is forbidden!
|
||||
if ($this->isForbidden() || $other->isForbidden()) {
|
||||
$result = static::forbidden();
|
||||
if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
elseif ($this->isAllowed() || $other->isAllowed()) {
|
||||
$result = static::allowed();
|
||||
if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result = static::neutral();
|
||||
if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
$result->inheritCacheability($this);
|
||||
if ($merge_other) {
|
||||
$result->inheritCacheability($other);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function andIf(AccessResultInterface $other) {
|
||||
// The other access result's cacheability metadata is merged if $merge_other
|
||||
// gets set to TRUE. It gets set to TRUE in one case: if the other access
|
||||
// result is used.
|
||||
$merge_other = FALSE;
|
||||
if ($this->isForbidden() || $other->isForbidden()) {
|
||||
$result = static::forbidden();
|
||||
if (!$this->isForbidden()) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
elseif ($this->isAllowed() && $other->isAllowed()) {
|
||||
$result = static::allowed();
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
else {
|
||||
$result = static::neutral();
|
||||
if (!$this->isNeutral()) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
$result->inheritCacheability($this);
|
||||
if ($merge_other) {
|
||||
$result->inheritCacheability($other);
|
||||
// If this access result is not cacheable, then an AND with another access
|
||||
// result must also not be cacheable, except if the other access result
|
||||
// has isForbidden() === TRUE. isForbidden() access results are contagious
|
||||
// in that they propagate regardless of the other value.
|
||||
if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) {
|
||||
$result->setCacheMaxAge(0);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits the cacheability of the other access result, if any.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessResultInterface $other
|
||||
* The other access result, whose cacheability (if any) to inherit.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inheritCacheability(AccessResultInterface $other) {
|
||||
if ($other instanceof CacheableDependencyInterface) {
|
||||
if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) {
|
||||
$this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
|
||||
}
|
||||
else {
|
||||
$this->setCacheMaxAge($other->getCacheMaxAge());
|
||||
}
|
||||
$this->addCacheContexts($other->getCacheContexts());
|
||||
$this->addCacheTags($other->getCacheTags());
|
||||
}
|
||||
// If any of the access results don't provide cacheability metadata, then
|
||||
// we cannot cache the combined access result, for we may not make
|
||||
// assumptions.
|
||||
else {
|
||||
$this->setCacheMaxAge(0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
22
core/lib/Drupal/Core/Access/AccessResultAllowed.php
Normal file
22
core/lib/Drupal/Core/Access/AccessResultAllowed.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultAllowed.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating an allowed access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultAllowed extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAllowed() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
22
core/lib/Drupal/Core/Access/AccessResultForbidden.php
Normal file
22
core/lib/Drupal/Core/Access/AccessResultForbidden.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultForbidden.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating a forbidden access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultForbidden extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isForbidden() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
103
core/lib/Drupal/Core/Access/AccessResultInterface.php
Normal file
103
core/lib/Drupal/Core/Access/AccessResultInterface.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Interface for access result value objects.
|
||||
*
|
||||
* IMPORTANT NOTE: You have to call isAllowed() when you want to know whether
|
||||
* someone has access. Just using
|
||||
* @code
|
||||
* if ($access_result) {
|
||||
* // The user has access!
|
||||
* }
|
||||
* else {
|
||||
* // The user doesn't have access!
|
||||
* }
|
||||
* @endcode
|
||||
* would never enter the else-statement and hence introduce a critical security
|
||||
* issue.
|
||||
*/
|
||||
interface AccessResultInterface {
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is explicitly allowed.
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isForbidden() and isNeutral() are FALSE.
|
||||
*/
|
||||
public function isAllowed();
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is explicitly forbidden.
|
||||
*
|
||||
* This is a kill switch — both orIf() and andIf() will result in
|
||||
* isForbidden() if either results are isForbidden().
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isAllowed() and isNeutral() are FALSE.
|
||||
*/
|
||||
public function isForbidden();
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is not yet determined.
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isAllowed() and isForbidden() are FALSE.
|
||||
*/
|
||||
public function isNeutral();
|
||||
|
||||
/**
|
||||
* Combine this access result with another using OR.
|
||||
*
|
||||
* When OR-ing two access results, the result is:
|
||||
* - isForbidden() in either ⇒ isForbidden()
|
||||
* - otherwise if isAllowed() in either ⇒ isAllowed()
|
||||
* - otherwise both must be isNeutral() ⇒ isNeutral()
|
||||
*
|
||||
* Truth table:
|
||||
* @code
|
||||
* |A N F
|
||||
* --+-----
|
||||
* A |A A F
|
||||
* N |A N F
|
||||
* F |F F F
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessResultInterface $other
|
||||
* The other access result to OR this one with.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function orIf(AccessResultInterface $other);
|
||||
|
||||
/**
|
||||
* Combine this access result with another using AND.
|
||||
*
|
||||
* When AND-ing two access results, the result is:
|
||||
* - isForbidden() in either ⇒ isForbidden()
|
||||
* - otherwise, if isAllowed() in both ⇒ isAllowed()
|
||||
* - otherwise, one of them is isNeutral() ⇒ isNeutral()
|
||||
*
|
||||
* Truth table:
|
||||
* @code
|
||||
* |A N F
|
||||
* --+-----
|
||||
* A |A N F
|
||||
* N |N N F
|
||||
* F |F F F
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessResultInterface $other
|
||||
* The other access result to AND this one with.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function andIf(AccessResultInterface $other);
|
||||
|
||||
}
|
22
core/lib/Drupal/Core/Access/AccessResultNeutral.php
Normal file
22
core/lib/Drupal/Core/Access/AccessResultNeutral.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultNeutral.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating a neutral access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultNeutral extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isNeutral() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
39
core/lib/Drupal/Core/Access/AccessibleInterface.php
Normal file
39
core/lib/Drupal/Core/Access/AccessibleInterface.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessibleInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Interface for checking access.
|
||||
*
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
interface AccessibleInterface {
|
||||
|
||||
/**
|
||||
* Checks data value access.
|
||||
*
|
||||
* @param string $operation
|
||||
* The operation to be performed.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) The user for which to check access, or NULL to check access
|
||||
* for the current user. Defaults to NULL.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
}
|
170
core/lib/Drupal/Core/Access/CheckProvider.php
Normal file
170
core/lib/Drupal/Core/Access/CheckProvider.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CheckProvider.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Loads access checkers from the container.
|
||||
*/
|
||||
class CheckProvider extends ContainerAware implements CheckProviderInterface {
|
||||
|
||||
/**
|
||||
* Array of registered access check service ids.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $checkIds = array();
|
||||
|
||||
/**
|
||||
* Array of access check objects keyed by service id.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\Access\AccessInterface[]
|
||||
*/
|
||||
protected $checks;
|
||||
|
||||
/**
|
||||
* Array of access check method names keyed by service ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $checkMethods = array();
|
||||
|
||||
/**
|
||||
* Array of access checks which only will be run on the incoming request.
|
||||
*/
|
||||
protected $checksNeedsRequest = array();
|
||||
|
||||
/**
|
||||
* An array to map static requirement keys to service IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $staticRequirementMap;
|
||||
|
||||
/**
|
||||
* An array to map dynamic requirement keys to service IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dynamicRequirementMap;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addCheckService($service_id, $service_method, array $applies_checks = array(), $needs_incoming_request = FALSE) {
|
||||
$this->checkIds[] = $service_id;
|
||||
$this->checkMethods[$service_id] = $service_method;
|
||||
if ($needs_incoming_request) {
|
||||
$this->checksNeedsRequest[$service_id] = $service_id;
|
||||
}
|
||||
foreach ($applies_checks as $applies_check) {
|
||||
$this->staticRequirementMap[$applies_check][] = $service_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChecksNeedRequest() {
|
||||
return $this->checksNeedsRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setChecks(RouteCollection $routes) {
|
||||
$this->loadDynamicRequirementMap();
|
||||
foreach ($routes as $route) {
|
||||
if ($checks = $this->applies($route)) {
|
||||
$route->setOption('_access_checks', $checks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCheck($service_id) {
|
||||
if (empty($this->checks[$service_id])) {
|
||||
if (!in_array($service_id, $this->checkIds)) {
|
||||
throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id));
|
||||
}
|
||||
|
||||
$check = $this->container->get($service_id);
|
||||
|
||||
if (!($check instanceof AccessInterface)) {
|
||||
throw new AccessException('All access checks must implement AccessInterface.');
|
||||
}
|
||||
if (!is_callable(array($check, $this->checkMethods[$service_id]))) {
|
||||
throw new AccessException(sprintf('Access check method %s in service %s must be callable.', $this->checkMethods[$service_id], $service_id));
|
||||
}
|
||||
|
||||
$this->checks[$service_id] = $check;
|
||||
}
|
||||
return [$this->checks[$service_id], $this->checkMethods[$service_id]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which registered access checks apply to a route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to get list of access checks for.
|
||||
*
|
||||
* @return array
|
||||
* An array of service ids for the access checks that apply to passed
|
||||
* route.
|
||||
*/
|
||||
protected function applies(Route $route) {
|
||||
$checks = array();
|
||||
|
||||
// Iterate through map requirements from appliesTo() on access checkers.
|
||||
// Only iterate through all checkIds if this is not used.
|
||||
foreach ($route->getRequirements() as $key => $value) {
|
||||
if (isset($this->staticRequirementMap[$key])) {
|
||||
foreach ($this->staticRequirementMap[$key] as $service_id) {
|
||||
$checks[] = $service_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, see if any dynamic access checkers apply.
|
||||
foreach ($this->dynamicRequirementMap as $service_id) {
|
||||
if ($this->checks[$service_id]->applies($route)) {
|
||||
$checks[] = $service_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $checks;
|
||||
}
|
||||
/**
|
||||
* Compiles a mapping of requirement keys to access checker service IDs.
|
||||
*/
|
||||
protected function loadDynamicRequirementMap() {
|
||||
if (isset($this->dynamicRequirementMap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set them here, so we can use the isset() check above.
|
||||
$this->dynamicRequirementMap = array();
|
||||
|
||||
foreach ($this->checkIds as $service_id) {
|
||||
if (empty($this->checks[$service_id])) {
|
||||
$this->loadCheck($service_id);
|
||||
}
|
||||
|
||||
// Add the service ID to an array that will be iterated over.
|
||||
if ($this->checks[$service_id] instanceof AccessCheckInterface) {
|
||||
$this->dynamicRequirementMap[] = $service_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
69
core/lib/Drupal/Core/Access/CheckProviderInterface.php
Normal file
69
core/lib/Drupal/Core/Access/CheckProviderInterface.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CheckProviderInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides the available access checkers by service IDs.
|
||||
*
|
||||
* Access checker services are added by ::addCheckService calls and are loaded
|
||||
* by ::loadCheck.
|
||||
*
|
||||
* The checker provider service and the actual checking is separated in order
|
||||
* to not require the full access manager on route build time.
|
||||
*/
|
||||
interface CheckProviderInterface {
|
||||
|
||||
|
||||
/**
|
||||
* For each route, saves a list of applicable access checks to the route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $routes
|
||||
* A collection of routes to apply checks to.
|
||||
*/
|
||||
public function setChecks(RouteCollection $routes);
|
||||
|
||||
/**
|
||||
* Registers a new AccessCheck by service ID.
|
||||
*
|
||||
* @param string $service_id
|
||||
* The ID of the service in the Container that provides a check.
|
||||
* @param string $service_method
|
||||
* The method to invoke on the service object for performing the check.
|
||||
* @param array $applies_checks
|
||||
* (optional) An array of route requirement keys the checker service applies
|
||||
* to.
|
||||
* @param bool $needs_incoming_request
|
||||
* (optional) True if access-check method only acts on an incoming request.
|
||||
*/
|
||||
public function addCheckService($service_id, $service_method, array $applies_checks = array(), $needs_incoming_request = FALSE);
|
||||
|
||||
/**
|
||||
* Lazy-loads access check services.
|
||||
*
|
||||
* @param string $service_id
|
||||
* The service id of the access check service to load.
|
||||
*
|
||||
* @return callable
|
||||
* A callable access check.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the service hasn't been registered in addCheckService().
|
||||
* @throws \Drupal\Core\Access\AccessException
|
||||
* Thrown when the service doesn't implement the required interface.
|
||||
*/
|
||||
public function loadCheck($service_id);
|
||||
|
||||
/**
|
||||
* A list of checks that needs the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChecksNeedRequest();
|
||||
}
|
72
core/lib/Drupal/Core/Access/CsrfAccessCheck.php
Normal file
72
core/lib/Drupal/Core/Access/CsrfAccessCheck.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CsrfAccessCheck.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Allows access to routes to be controlled by a '_csrf_token' parameter.
|
||||
*
|
||||
* To use this check, add a "token" GET parameter to URLs of which the value is
|
||||
* a token generated by \Drupal::csrfToken()->get() using the same value as the
|
||||
* "_csrf_token" parameter in the route.
|
||||
*/
|
||||
class CsrfAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Constructs a CsrfAccessCheck object.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(CsrfTokenGenerator $csrf_token) {
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access based on a CSRF token for the request.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, Request $request, RouteMatchInterface $route_match) {
|
||||
$parameters = $route_match->getRawParameters();
|
||||
$path = ltrim($route->getPath(), '/');
|
||||
// Replace the path parameters with values from the parameters array.
|
||||
foreach ($parameters as $param => $value) {
|
||||
$path = str_replace("{{$param}}", $value, $path);
|
||||
}
|
||||
|
||||
if ($this->csrfToken->validate($request->query->get('token'), $path)) {
|
||||
$result = AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
$result = AccessResult::forbidden();
|
||||
}
|
||||
// Not cacheable because the CSRF token is highly dynamic.
|
||||
return $result->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
117
core/lib/Drupal/Core/Access/CsrfTokenGenerator.php
Normal file
117
core/lib/Drupal/Core/Access/CsrfTokenGenerator.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CsrfTokenGenerator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\PrivateKey;
|
||||
use Drupal\Core\Session\MetadataBag;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* Generates and validates CSRF tokens.
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Access\CsrfTokenGeneratorTest
|
||||
*/
|
||||
class CsrfTokenGenerator {
|
||||
|
||||
/**
|
||||
* The private key service.
|
||||
*
|
||||
* @var \Drupal\Core\PrivateKey
|
||||
*/
|
||||
protected $privateKey;
|
||||
|
||||
/**
|
||||
* The session metadata bag.
|
||||
*
|
||||
* @var \Drupal\Core\Session\MetadataBag
|
||||
*/
|
||||
protected $sessionMetadata;
|
||||
|
||||
/**
|
||||
* Constructs the token generator.
|
||||
*
|
||||
* @param \Drupal\Core\PrivateKey $private_key
|
||||
* The private key service.
|
||||
* @param \Drupal\Core\Session\MetadataBag $session_metadata
|
||||
* The session metadata bag.
|
||||
*/
|
||||
public function __construct(PrivateKey $private_key, MetadataBag $session_metadata) {
|
||||
$this->privateKey = $private_key;
|
||||
$this->sessionMetadata = $session_metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a token based on $value, the user session, and the private key.
|
||||
*
|
||||
* The generated token is based on the session of the current user. Normally,
|
||||
* anonymous users do not have a session, so the generated token will be
|
||||
* different on every page request. To generate a token for users without a
|
||||
* session, manually start a session prior to calling this function.
|
||||
*
|
||||
* @param string $value
|
||||
* (optional) An additional value to base the token on.
|
||||
*
|
||||
* @return string
|
||||
* A 43-character URL-safe token for validation, based on the token seed,
|
||||
* the hash salt provided by Settings::getHashSalt(), and the
|
||||
* 'drupal_private_key' configuration variable.
|
||||
*
|
||||
* @see \Drupal\Core\Site\Settings::getHashSalt()
|
||||
* @see \Symfony\Component\HttpFoundation\Session\SessionInterface::start()
|
||||
*/
|
||||
public function get($value = '') {
|
||||
$seed = $this->sessionMetadata->getCsrfTokenSeed();
|
||||
if (empty($seed)) {
|
||||
$seed = Crypt::randomBytesBase64();
|
||||
$this->sessionMetadata->setCsrfTokenSeed($seed);
|
||||
}
|
||||
|
||||
return $this->computeToken($seed, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a token based on $value, the user session, and the private key.
|
||||
*
|
||||
* @param string $token
|
||||
* The token to be validated.
|
||||
* @param string $value
|
||||
* (optional) An additional value to base the token on.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE for a valid token, FALSE for an invalid token.
|
||||
*/
|
||||
public function validate($token, $value = '') {
|
||||
$seed = $this->sessionMetadata->getCsrfTokenSeed();
|
||||
if (empty($seed)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $token === $this->computeToken($seed, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a token based on $value, the token seed, and the private key.
|
||||
*
|
||||
* @param string $seed
|
||||
* The per-session token seed.
|
||||
* @param string $value
|
||||
* (optional) An additional value to base the token on.
|
||||
*
|
||||
* @return string
|
||||
* A 43-character URL-safe token for validation, based on the token seed,
|
||||
* the hash salt provided by Settings::getHashSalt(), and the
|
||||
* 'drupal_private_key' configuration variable.
|
||||
*
|
||||
* @see \Drupal\Core\Site\Settings::getHashSalt()
|
||||
*/
|
||||
protected function computeToken($seed, $value = '') {
|
||||
return Crypt::hmacBase64($value, $seed . $this->privateKey->get() . Settings::getHashSalt());
|
||||
}
|
||||
|
||||
}
|
74
core/lib/Drupal/Core/Access/CustomAccessCheck.php
Normal file
74
core/lib/Drupal/Core/Access/CustomAccessCheck.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CustomAccessCheck.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Defines an access checker that allows specifying a custom method for access.
|
||||
*
|
||||
* You should only use it when you are sure that the access callback will not be
|
||||
* reused. Good examples in core are Edit or Toolbar module.
|
||||
*
|
||||
* The method is called on another instance of the controller class, so you
|
||||
* cannot reuse any stored property of your actual controller instance used
|
||||
* to generate the output.
|
||||
*/
|
||||
class CustomAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The arguments resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface
|
||||
*/
|
||||
protected $argumentsResolverFactory;
|
||||
|
||||
/**
|
||||
* Constructs a CustomAccessCheck instance.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $arguments_resolver_factory
|
||||
* The arguments resolver factory.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, AccessArgumentsResolverFactoryInterface $arguments_resolver_factory) {
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->argumentsResolverFactory = $arguments_resolver_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for the account and route using the custom access checker.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object to be checked.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account being checked.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
|
||||
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
|
||||
$arguments = $arguments_resolver->getArguments($callable);
|
||||
|
||||
return call_user_func_array($callable, $arguments);
|
||||
}
|
||||
|
||||
}
|
39
core/lib/Drupal/Core/Access/DefaultAccessCheck.php
Normal file
39
core/lib/Drupal/Core/Access/DefaultAccessCheck.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\DefaultAccessCheck.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Allows access to routes to be controlled by an '_access' boolean parameter.
|
||||
*/
|
||||
class DefaultAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access to the route based on the _access parameter.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route) {
|
||||
if ($route->getRequirement('_access') === 'TRUE') {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
elseif ($route->getRequirement('_access') === 'FALSE') {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
else {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
Normal file
57
core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\RouteProcessorCsrf.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Processes the outbound route to handle the CSRF token.
|
||||
*/
|
||||
class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Constructs a RouteProcessorCsrf object.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
function __construct(CsrfTokenGenerator $csrf_token) {
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
|
||||
if ($route->hasRequirement('_csrf_token')) {
|
||||
$path = ltrim($route->getPath(), '/');
|
||||
// Replace the path parameters with values from the parameters array.
|
||||
foreach ($parameters as $param => $value) {
|
||||
$path = str_replace("{{$param}}", $value, $path);
|
||||
}
|
||||
// Adding this to the parameters means it will get merged into the query
|
||||
// string when the route is compiled.
|
||||
$parameters['token'] = $this->csrfToken->get($path);
|
||||
if ($cacheable_metadata) {
|
||||
// Tokens are per user and per session, so not cacheable.
|
||||
// @todo Improve in https://www.drupal.org/node/2351015.
|
||||
$cacheable_metadata->setCacheMaxAge(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue