Commit 5b2cb036 authored by catch's avatar catch

Issue #2847687 by jp.stacey, dawehner: EntityAccessCheck mandates instanceof...

Issue #2847687 by jp.stacey, dawehner: EntityAccessCheck mandates instanceof EntityInterface when it only needs AccessibleInterface
parent 0a3fb0a8
...@@ -16,28 +16,32 @@ class EntityAccessCheck implements AccessInterface { ...@@ -16,28 +16,32 @@ class EntityAccessCheck implements AccessInterface {
/** /**
* Checks access to the entity operation on the given route. * Checks access to the entity operation on the given route.
* *
* The value of the '_entity_access' key must be in the pattern * The route's '_entity_access' requirement must follow the pattern
* 'entity_slug_name.operation.' For example, this will check a node for * 'entity_stub_name.operation', where available operations are:
* 'update' access: * 'view', 'update', 'create', and 'delete'.
*
* For example, this route configuration invokes a permissions check for
* 'update' access to entities of type 'node':
* @code * @code
* pattern: '/foo/{node}/bar' * pattern: '/foo/{node}/bar'
* requirements: * requirements:
* _entity_access: 'node.update' * _entity_access: 'node.update'
* @endcode * @endcode
* And this will check a dynamic entity type: * And this will check 'delete' access to a dynamic entity type:
* @code * @code
* example.route: * example.route:
* path: foo/{entity_type}/{example} * path: foo/{entity_type}/{example}
* requirements: * requirements:
* _entity_access: example.update * _entity_access: example.delete
* options: * options:
* parameters: * parameters:
* example: * example:
* type: entity:{entity_type} * type: entity:{entity_type}
* @endcode * @endcode
* @see \Drupal\Core\ParamConverter\EntityConverter * The route match parameter corresponding to the stub name is checked to
* see if it is entity-like i.e. implements EntityInterface.
* *
* Available operations are 'view', 'update', 'create', and 'delete'. * @see \Drupal\Core\ParamConverter\EntityConverter
* *
* @param \Symfony\Component\Routing\Route $route * @param \Symfony\Component\Routing\Route $route
* The route to check against. * The route to check against.
...@@ -53,7 +57,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn ...@@ -53,7 +57,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
// Split the entity type and the operation. // Split the entity type and the operation.
$requirement = $route->getRequirement('_entity_access'); $requirement = $route->getRequirement('_entity_access');
list($entity_type, $operation) = explode('.', $requirement); list($entity_type, $operation) = explode('.', $requirement);
// If there is valid entity of the given entity type, check its access. // If $entity_type parameter is a valid entity, call its own access check.
$parameters = $route_match->getParameters(); $parameters = $route_match->getParameters();
if ($parameters->has($entity_type)) { if ($parameters->has($entity_type)) {
$entity = $parameters->get($entity_type); $entity = $parameters->get($entity_type);
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
use Drupal\node\NodeInterface; use Drupal\node\NodeInterface;
use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessCheck; use Drupal\Core\Entity\EntityAccessCheck;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Tests\UnitTestCase; use Drupal\Tests\UnitTestCase;
/** /**
...@@ -70,13 +72,53 @@ public function testAccessWithTypePlaceholder() { ...@@ -70,13 +72,53 @@ public function testAccessWithTypePlaceholder() {
$node = $node->reveal(); $node = $node->reveal();
/** @var \Drupal\Core\Routing\RouteMatchInterface|\Prophecy\Prophecy\ObjectProphecy $route_match */ /** @var \Drupal\Core\Routing\RouteMatchInterface|\Prophecy\Prophecy\ObjectProphecy $route_match */
$route_match = $this->prophesize(RouteMatchInterface::class); $route_match = $this->createRouteMatchForObject($node);
$route_match->getRawParameters()->willReturn(new ParameterBag(['entity_type' => 'node', 'var_name' => 1]));
$route_match->getParameters()->willReturn(new ParameterBag(['entity_type' => 'node', 'var_name' => $node]));
$route_match = $route_match->reveal();
$access_check = new EntityAccessCheck(); $access_check = new EntityAccessCheck();
$this->assertEquals(AccessResult::allowed(), $access_check->access($route, $route_match, $account)); $this->assertEquals(AccessResult::allowed(), $access_check->access($route, $route_match, $account));
} }
/**
* @covers ::access
*/
public function testAccessWithDifferentRouteParameters() {
$route = new Route(
'/foo/{var_name}',
[],
['_entity_access' => 'var_name.update'],
['parameters' => ['var_name' => ['type' => 'entity:node']]]
);
/** @var \Drupal\Core\Session\AccountInterface $account */
$account = $this->prophesize(AccountInterface::class)->reveal();
$access_check = new EntityAccessCheck();
// Confirm an EntityInterface route parameter's ::access() is called.
/** @var \Drupal\Core\Entity\EntityInterface|\Prophecy\Prophecy\ObjectProphecy $node */
$node = $this->prophesize(EntityInterface::class);
$node->access('update', $account, TRUE)->willReturn(AccessResult::allowed());
$route_match = $this->createRouteMatchForObject($node->reveal());
$this->assertEquals(AccessResult::allowed(), $access_check->access($route, $route_match, $account));
// AccessibleInterface is not entity-like: ::access() should not be called.
/** @var \Drupal\Core\Access\AccessibleInterface|\Prophecy\Prophecy\ObjectProphecy $node */
$node = $this->prophesize(AccessibleInterface::class);
$node->access('update', $account, TRUE)->willReturn(AccessResult::allowed());
$route_match = $this->createRouteMatchForObject($node->reveal());
$this->assertEquals(AccessResult::neutral(), $access_check->access($route, $route_match, $account));
}
/**
* Wrap any object with a route match, and return that.
*
* @param \stdClass $object
* Any object, including prophesized mocks based on interfaces.
* @return RouteMatchInterface
* A prophesized RouteMatchInterface.
*/
private function createRouteMatchForObject(\stdClass $object) {
$route_match = $this->prophesize(RouteMatchInterface::class);
$route_match->getRawParameters()->willReturn(new ParameterBag(['entity_type' => 'node', 'var_name' => 1]));
$route_match->getParameters()->willReturn(new ParameterBag(['entity_type' => 'node', 'var_name' => $object]));
return $route_match->reveal();
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment