Skip to content

Commit

Permalink
Issue #2287071 by Wim Leers, alexpott, chx: Add cacheability metadata…
Browse files Browse the repository at this point in the history
… to access checks.
  • Loading branch information
Nathaniel Catchpole committed Sep 17, 2014
1 parent c96b314 commit 4a9d5ef
Show file tree
Hide file tree
Showing 121 changed files with 2,217 additions and 851 deletions.
38 changes: 0 additions & 38 deletions lib/Drupal/Core/Access/AccessInterface.php

This file was deleted.

87 changes: 49 additions & 38 deletions lib/Drupal/Core/Access/AccessManager.php
Expand Up @@ -9,6 +9,7 @@

use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RequestHelper;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
Expand Down Expand Up @@ -42,7 +43,7 @@ class AccessManager implements ContainerAwareInterface, AccessManagerInterface {
/**
* Array of access check objects keyed by service id.
*
* @var array
* @var \Drupal\Core\Routing\Access\AccessInterface[]
*/
protected $checks;

Expand Down Expand Up @@ -192,7 +193,7 @@ protected function applies(Route $route) {
/**
* {@inheritdoc}
*/
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, Request $route_request = NULL) {
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, Request $route_request = NULL, $return_as_object = FALSE) {
try {
$route = $this->routeProvider->getRouteByName($route_name, $parameters);
if (empty($route_request)) {
Expand All @@ -207,32 +208,38 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun
$parameters[RouteObjectInterface::ROUTE_OBJECT] = $route;
$route_request->attributes->add($this->paramConverterManager->convert($parameters));
}
return $this->check($route, $route_request, $account);
return $this->check($route, $route_request, $account, $return_as_object);
}
catch (RouteNotFoundException $e) {
return FALSE;
// Cacheable until extensions change.
$result = AccessResult::forbidden()->addCacheTags(array('extension' => TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
catch (ParamNotConvertedException $e) {
return FALSE;
// Uncacheable because conversion of the parameter may not have been
// possible due to dynamic circumstances.
$result = AccessResult::forbidden()->setCacheable(FALSE);
return $return_as_object ? $result : $result->isAllowed();
}
}

/**
* {@inheritdoc}
*/
public function check(Route $route, Request $request, AccountInterface $account = NULL) {
public function check(Route $route, Request $request, AccountInterface $account = NULL, $return_as_object = FALSE) {
if (!isset($account)) {
$account = $this->currentUser;
}
$checks = $route->getOption('_access_checks') ?: array();
$conjunction = $route->getOption('_access_mode') ?: static::ACCESS_MODE_ALL;

if ($conjunction == static::ACCESS_MODE_ALL) {
return $this->checkAll($checks, $route, $request, $account);
$result = $this->checkAll($checks, $route, $request, $account);
}
else {
return $this->checkAny($checks, $route, $request, $account);
$result = $this->checkAny($checks, $route, $request, $account);
}
return $return_as_object ? $result : $result->isAllowed();
}

/**
Expand All @@ -247,30 +254,39 @@ public function check(Route $route, Request $request, AccountInterface $account
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*
* @return bool
* Returns TRUE if the user has access to the route, else FALSE.
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*
* @see \Drupal\Core\Access\AccessResultInterface::andIf()
*/
protected function checkAll(array $checks, Route $route, Request $request, AccountInterface $account) {
$access = FALSE;

$results = array();
foreach ($checks as $service_id) {
if (empty($this->checks[$service_id])) {
$this->loadCheck($service_id);
}

$service_access = $this->performCheck($service_id, $route, $request, $account);
$result = $this->performCheck($service_id, $route, $request, $account);
$results[] = $result;

if ($service_access === AccessInterface::ALLOW) {
$access = TRUE;
}
else {
// On both KILL and DENY stop.
$access = FALSE;
// Stop as soon as the first non-allowed check is encountered.
if (!$result->isAllowed()) {
break;
}
}

return $access;
if (empty($results)) {
// No opinion.
return AccessResult::create();
}
else {
/** @var \Drupal\Core\Access\AccessResultInterface $result */
$result = array_shift($results);
foreach ($results as $other) {
$result->andIf($other);
}
return $result;
}
}

/**
Expand All @@ -285,29 +301,23 @@ protected function checkAll(array $checks, Route $route, Request $request, Accou
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*
* @return bool
* Returns TRUE if the user has access to the route, else FALSE.
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*
* @see \Drupal\Core\Access\AccessResultInterface::orIf()
*/
protected function checkAny(array $checks, $route, $request, AccountInterface $account) {
// No checks == deny by default.
$access = FALSE;
// No opinion by default.
$result = AccessResult::create();

foreach ($checks as $service_id) {
if (empty($this->checks[$service_id])) {
$this->loadCheck($service_id);
}

$service_access = $this->performCheck($service_id, $route, $request, $account);

if ($service_access === AccessInterface::ALLOW) {
$access = TRUE;
}
if ($service_access === AccessInterface::KILL) {
return FALSE;
}
$result = $result->orIf($this->performCheck($service_id, $route, $request, $account));
}

return $access;
return $result;
}

/**
Expand All @@ -325,16 +335,17 @@ protected function checkAny(array $checks, $route, $request, AccountInterface $a
* @throws \Drupal\Core\Access\AccessException
* Thrown when the access check returns an invalid value.
*
* @return string
* A \Drupal\Core\Access\AccessInterface constant value.
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function performCheck($service_id, $route, $request, $account) {
$callable = array($this->checks[$service_id], $this->checkMethods[$service_id]);
$arguments = $this->argumentsResolver->getArguments($callable, $route, $request, $account);
/** @var \Drupal\Core\Access\AccessResultInterface $service_access **/
$service_access = call_user_func_array($callable, $arguments);

if (!in_array($service_access, array(AccessInterface::ALLOW, AccessInterface::DENY, AccessInterface::KILL), TRUE)) {
throw new AccessException("Access error in $service_id. Access services can only return AccessInterface::ALLOW, AccessInterface::DENY, or AccessInterface::KILL constants.");
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;
Expand Down
39 changes: 24 additions & 15 deletions lib/Drupal/Core/Access/AccessManagerInterface.php
Expand Up @@ -18,20 +18,17 @@
interface AccessManagerInterface {

/**
* All access checkers have to return AccessInterface::ALLOW.
* All access checkers must return an AccessResultInterface object where
* ::isAllowed() is TRUE.
*
* self::ACCESS_MODE_ALL is the default behavior.
*
* @see \Drupal\Core\Access\AccessInterface::ALLOW
*/
const ACCESS_MODE_ALL = 'ALL';

/**
* At least one access checker has to return AccessInterface::ALLOW
* and none should return AccessInterface::KILL.
*
* @see \Drupal\Core\Access\AccessInterface::ALLOW
* @see \Drupal\Core\Access\AccessInterface::KILL
* At least one access checker must return an AccessResultInterface object
* where ::isAllowed() is TRUE and none may return one where ::isForbidden()
* is TRUE.
*/
const ACCESS_MODE_ANY = 'ANY';

Expand All @@ -43,18 +40,24 @@ interface AccessManagerInterface {
* @param string $route_name
* The route to check access to.
* @param array $parameters
* Optional array of values to substitute into the route path patern.
* 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 \Symfony\Component\HttpFoundation\Request $route_request
* Optional incoming request object. If not provided, one will be built
* using the route information and the current request from the container.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
* @return bool
* Returns TRUE if the user has access to the route, otherwise 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, Request $route_request = NULL);
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, Request $route_request = NULL, $return_as_object = FALSE);

/**
* For each route, saves a list of applicable access checks to the route.
Expand Down Expand Up @@ -89,10 +92,16 @@ public function addCheckService($service_id, $service_method, array $applies_che
* @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
* Returns TRUE if the user has access to the route, otherwise 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(Route $route, Request $request, AccountInterface $account = NULL);
public function check(Route $route, Request $request, AccountInterface $account = NULL, $return_as_object = FALSE);

}

0 comments on commit 4a9d5ef

Please sign in to comment.