diff --git a/core/core.services.yml b/core/core.services.yml index 9afeed255c9693178b88da049b1a759b20df117c..a8278352d9cf37089abf2141ac571b17121c4fb2 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -425,6 +425,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/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..159d9d52618f6da431141b89eef3bd7fe6b014b8 --- /dev/null +++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php @@ -0,0 +1,62 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Access\CustomAccessCheck. + */ + +namespace Drupal\Core\Access; + +use Drupal\Core\Controller\ControllerResolverInterface; +use Symfony\Component\HttpFoundation\Request; +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 StaticAccessCheckInterface { + + /** + * The controller resolver. + * + * @var \Drupal\Core\Controller\ControllerResolverInterface + */ + protected $controllerResolver; + + /** + * Constructs a CustomAccessCheck instance. + * + * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver + * The controller resolver. + */ + public function __construct(ControllerResolverInterface $controller_resolver) { + $this->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/Access/SubtreeAccess.php b/core/modules/toolbar/lib/Drupal/toolbar/Access/SubtreeAccess.php deleted file mode 100644 index 131bc6a027d276b229291b052ae8c51bd3705003..0000000000000000000000000000000000000000 --- a/core/modules/toolbar/lib/Drupal/toolbar/Access/SubtreeAccess.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\toolbar\Access\SubtreeAccess. - */ - -namespace Drupal\toolbar\Access; - -use Drupal\Core\Access\StaticAccessCheckInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Route; - -/** - * Defines a special access checker for the toolbar subtree route. - */ -class SubtreeAccess implements StaticAccessCheckInterface { - - /** - * {@inheritdoc} - */ - public function appliesTo() { - return array('_access_toolbar_subtree'); - } - - /** - * {@inheritdoc} - */ - public function access(Route $route, Request $request) { - $hash = $request->get('hash'); - return (user_access('access toolbar') && ($hash == _toolbar_get_subtrees_hash())) ? static::ALLOW : static::DENY; - } - -} diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php index 740455599f9797a79a1524e151aaa9af57bf27d6..3a7eb3c0d833937fb69100aa94736cf2aea6958b 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 609d0c70b4cd16a69e55729de3042cd5651848e1..a4aabb1c05c3f98df0d4100ada96b16ee8e4d90c 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/modules/toolbar/toolbar.services.yml b/core/modules/toolbar/toolbar.services.yml index c700b62941ad0fb2b9ce2939208b5a28125c35c0..7f269683c143d03c18f0440af7bced9a90be11a9 100644 --- a/core/modules/toolbar/toolbar.services.yml +++ b/core/modules/toolbar/toolbar.services.yml @@ -1,8 +1,4 @@ services: - access_check.toolbar_subtree: - class: Drupal\toolbar\Access\SubtreeAccess - tags: - - { name: access_check } cache.toolbar: class: Drupal\Core\Cache\CacheBackendInterface tags: 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 0000000000000000000000000000000000000000..5eb130571f5764bf1ed823055b8507a1e84c398a --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php @@ -0,0 +1,127 @@ +<?php + +/** + * @file + * Contains \Drupal\Tests\Core\Access\CustomAccessCheckTest. + */ + +namespace Drupal\Tests\Core\Access; + +use Drupal\Core\Access\AccessInterface; +use Drupal\Core\Access\CustomAccessCheck; +use Drupal\Tests\UnitTestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; + +/** + * Tests the custom access checker. + * + * @see \Drupal\Core\Access\CustomAccessCheck + */ +class CustomAccessCheckTest extends UnitTestCase { + + /** + * The access checker to test. + * + * @var \Drupal\Core\Access\CustomAccessCheck + */ + protected $accessChecker; + + /** + * The mocked controller resolver. + * + * @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $controllerResolver; + + public static function getInfo() { + return array( + 'name' => '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; + } + } + +}