diff --git a/core/core.services.yml b/core/core.services.yml index 608c439..d6e74ab 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -411,6 +411,11 @@ services: class: Drupal\Core\Theme\ThemeAccessCheck tags: - { name: access_check } + access_check.custom: + class: Drupal\Core\Access\CustomAccessCheck + arguments: ['@controller_resolver'] + tags: + - { name: access_check } maintenance_mode_subscriber: class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber tags: diff --git a/core/lib/Drupal/Core/Access/AccessException.php b/core/lib/Drupal/Core/Access/AccessException.php new file mode 100644 index 0000000..ac13536 --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessException.php @@ -0,0 +1,15 @@ +checks[$service_id]->access($route, $request); + + if (!in_array($service_access, array(AccessInterface::ALLOW, AccessInterface::DENY, AccessInterface::KILL))) { + throw new AccessException('Access services can only return AccessInterface::ALLOW, AccessInterface::DENY, or AccessInterface::KILL constants.'); + } + if ($service_access === AccessInterface::ALLOW) { $access = TRUE; } @@ -295,6 +300,11 @@ protected function checkAny(array $checks, $route, $request) { } $service_access = $this->checks[$service_id]->access($route, $request); + + if (!in_array($service_access, array(AccessInterface::ALLOW, AccessInterface::DENY, AccessInterface::KILL))) { + throw new AccessException('Access services can only return AccessInterface::ALLOW, AccessInterface::DENY, or AccessInterface::KILL constants.'); + } + if ($service_access === AccessInterface::ALLOW) { $access = TRUE; } diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php new file mode 100644 index 0000000..20cd388 --- /dev/null +++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php @@ -0,0 +1,55 @@ +controllerResolver = $controller_resolver; + } + + /** + * {@inheritdoc} + */ + public function appliesTo() { + return array('_custom_access'); + } + + /** + * {@inheritdoc} + */ + public function access(Route $route, Request $request) { + $access_controller = $route->getRequirement('_custom_access'); + + $controller = $this->controllerResolver->getControllerFromDefinition($access_controller); + $arguments = $this->controllerResolver->getArguments($request, $controller); + + return call_user_func_array($controller, $arguments); + } + +} diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php index 7404555..3a7eb3c 100644 --- a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php +++ b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php @@ -7,12 +7,15 @@ namespace Drupal\toolbar\Routing; +use Drupal\Core\Access\AccessInterface; +use Drupal\Core\Controller\ControllerBase; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; /** * Defines a controller for the toolbar module. */ -class ToolbarController { +class ToolbarController extends ControllerBase { /** * Returns the rendered subtree of each top-level toolbar link. @@ -27,4 +30,12 @@ public function subtreesJsonp() { return $response; } + /** + * Checks access for the subtree controller. + */ + public function checkSubTreeAccess(Request $request) { + $hash = $request->get('hash'); + return ($this->currentUser()->hasPermission('access toolbar') && ($hash == _toolbar_get_subtrees_hash())) ? AccessInterface::ALLOW : AccessInterface::DENY; + } + } diff --git a/core/modules/toolbar/toolbar.routing.yml b/core/modules/toolbar/toolbar.routing.yml index 609d0c7..a4aabb1 100644 --- a/core/modules/toolbar/toolbar.routing.yml +++ b/core/modules/toolbar/toolbar.routing.yml @@ -3,4 +3,4 @@ toolbar.subtrees: defaults: _controller: '\Drupal\toolbar\Routing\ToolbarController::subtreesJsonp' requirements: - _access_toolbar_subtree: 'TRUE' + _custom_access: '\Drupal\toolbar\Routing\ToolbarController::checkSubTreeAccess' diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php index d12f5c0..aca2dd7 100644 --- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php @@ -461,6 +461,67 @@ public function testCheckNamedRouteWithNonExistingRoute() { } /** + * Tests that an access checker throws an exception for not allowed values. + * + * @dataProvider testCheckExceptionProvider + * + * @expectedException \Drupal\Core\Access\AccessException + */ + public function testCheckException($return_value) { + $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + + // Setup a test route for each access configuration. + $requirements = array( + '_test_incorrect_value' => 'TRUE', + ); + $options = array( + '_access_checks' => array( + 'test_incorrect_value', + ), + ); + $route = new Route('', array(), $requirements, $options); + + $this->routeProvider->expects($this->any()) + ->method('getRouteByName') + ->will($this->returnValue($route)); + + $this->setupAccessChecker(); + + $request = new Request(); + + // Register a service that will return an incorrect value. + $access_check = $this->getMock('Drupal\Core\Access\StaticAccessCheckInterface'); + $access_check->expects($this->any()) + ->method('appliesTo') + ->will($this->returnValue(array('_test_incorrect_value'))); + $access_check->expects($this->any()) + ->method('access') + ->will($this->returnValue($return_value)); + $this->container->register('test_incorrect_value', $access_check); + $this->accessManager->addCheckService('test_incorrect_value'); + + $this->accessManager->checkNamedRoute('test_incorrect_value', array(), $request); + } + + /** + * Data provider for testCheckException. + * + * @return array + */ + public function testCheckExceptionProvider() { + return array( + array(TRUE), + array(FALSE), + array(NULL), + array(array()), + array(array(1)), + array('string'), + array(0), + array(1), + ); + } + + /** * Converts AccessCheckInterface constants to a string. * * @param mixed $constant diff --git a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php new file mode 100644 index 0000000..5eb1305 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php @@ -0,0 +1,127 @@ + 'Custom access check', + 'description' => 'Tests the custom access checker.', + 'group' => 'Access' + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface'); + $this->accessChecker = new CustomAccessCheck($this->controllerResolver); + } + + + /** + * Tests the appliesTo method. + */ + public function testAppliesTo() { + $this->assertEquals($this->accessChecker->appliesTo(), array('_custom_access')); + } + + /** + * Test the access method. + */ + public function testAccess() { + $request = new Request(array()); + + $this->controllerResolver->expects($this->at(0)) + ->method('getControllerFromDefinition') + ->with('\Drupal\Tests\Core\Access\TestController::accessDeny') + ->will($this->returnValue(array(new TestController(), 'accessDeny'))); + + $this->controllerResolver->expects($this->at(1)) + ->method('getArguments') + ->will($this->returnValue(array())); + + $this->controllerResolver->expects($this->at(2)) + ->method('getControllerFromDefinition') + ->with('\Drupal\Tests\Core\Access\TestController::accessAllow') + ->will($this->returnValue(array(new TestController(), 'accessAllow'))); + + $this->controllerResolver->expects($this->at(3)) + ->method('getArguments') + ->will($this->returnValue(array())); + + $this->controllerResolver->expects($this->at(4)) + ->method('getControllerFromDefinition') + ->with('\Drupal\Tests\Core\Access\TestController::accessParameter') + ->will($this->returnValue(array(new TestController(), 'accessParameter'))); + + $this->controllerResolver->expects($this->at(5)) + ->method('getArguments') + ->will($this->returnValue(array('parameter' => 'TRUE'))); + + $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessDeny')); + $this->assertNull($this->accessChecker->access($route, $request)); + + $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessAllow')); + $this->assertTrue($this->accessChecker->access($route, $request)); + + $route = new Route('/test-route', array('parameter' => 'TRUE'), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessParameter')); + $this->assertTrue($this->accessChecker->access($route, $request)); + } + +} + +class TestController { + + public function accessAllow() { + return AccessInterface::ALLOW; + } + + public function accessDeny() { + return AccessInterface::DENY; + } + + public function accessParameter($parameter) { + if ($parameter == 'TRUE') { + return AccessInterface::ALLOW; + } + else { + return AccessInterface::DENY; + } + } + +}