diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php index 94dba60..879ddb9 100644 --- a/core/lib/Drupal/Core/Access/AccessManager.php +++ b/core/lib/Drupal/Core/Access/AccessManager.php @@ -14,6 +14,8 @@ /** * Attaches access check services to routes and runs them on request. + * + * @see \Drupal\Tests\Core\Access\AccessManagerTest */ class AccessManager extends ContainerAware { @@ -22,7 +24,7 @@ class AccessManager extends ContainerAware { * * @var array */ - protected $checkIds; + protected $checkIds = array(); /** * Array of access check objects keyed by service id. @@ -89,17 +91,44 @@ protected function applies(Route $route) { * * @param \Symfony\Component\Routing\Route $route * The route to check access to. - * @param \Symfony\Commponent\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Request $request * The incoming request object. * + * @return bool + * Returns TRUE if the user has access to the route, else FALSE. + * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * If any access check denies access or none explicitly approve. */ public function check(Route $route, Request $request) { - $access = FALSE; $checks = $route->getOption('_access_checks') ?: array(); - // No checks == deny by default. + $conjunction = $route->getOption('_access_conjunction') ?: 'AND'; + + if ($conjunction == 'AND') { + return $this->checkAnd($checks, $route, $request); + } + else { + return $this->checkOr($checks, $route, $request); + } + } + + /** + * Checks access for AND conjunction. + * + * @param array $checks + * Contains the list of checks on the route definition. + * @param \Symfony\Component\Routing\Route $route + * The route to check access to. + * @param \Symfony\Component\HttpFoundation\Request $request + * The incoming request object. + * + * @return bool + * Returns TRUE if the user has access to the route, else FALSE. + */ + protected function checkAnd(array $checks, Route $route, Request $request) { + $access = FALSE; + foreach ($checks as $service_id) { if (empty($this->checks[$service_id])) { $this->loadCheck($service_id); @@ -107,12 +136,10 @@ public function check(Route $route, Request $request) { $service_access = $this->checks[$service_id]->access($route, $request); if ($service_access === FALSE) { - // A check has denied access, no need to continue checking. $access = FALSE; break; } - elseif ($service_access === TRUE) { - // A check has explicitly granted access, so we need to remember that. + if ($service_access === TRUE) { $access = TRUE; } } @@ -121,6 +148,41 @@ public function check(Route $route, Request $request) { } /** + * Checks access for OR conjunction. + * + * @param array $checks + * Contains the list of checks on the route definition. + * @param \Symfony\Component\Routing\Route $route + * The route to check access to. + * @param \Symfony\Component\HttpFoundation\Request $request + * The incoming request object. + * + * @return bool + * Returns TRUE if the user has access to the route, else FALSE. + */ + protected function checkOr(array $checks, $route, $request) { + // No checks == deny by default. + $access = FALSE; + + foreach ($checks as $service_id) { + if (empty($this->checks[$service_id])) { + $this->loadCheck($service_id); + } + + $service_access = $this->checks[$service_id]->access($route, $request); + if ($service_access === TRUE) { + return TRUE; + } + if ($service_access === FALSE) { + $access = FALSE; + break; + } + } + + return $access; + } + + /** * Lazy-loads access check services. * * @param string $service_id diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php index 074dfd0..224e206 100644 --- a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php +++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php @@ -26,6 +26,14 @@ public function applies(Route $route) { * Implements AccessCheckInterface::access(). */ public function access(Route $route, Request $request) { - return $route->getRequirement('_access') === 'TRUE'; + if ($route->getRequirement('_access') === 'TRUE') { + return TRUE; + } + elseif ($route->getRequirement('_access') === 'FALSE') { + return FALSE; + } + else { + return NULL; + } } } diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php new file mode 100644 index 0000000..77248dc --- /dev/null +++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php @@ -0,0 +1,33 @@ +getRequirements()); + } + + /** + * {@inheritdoc} + */ + public function access(Route $route, Request $request) { + return $route->getRequirement('_test_access') === 'TRUE' ?: NULL; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php new file mode 100644 index 0000000..9ee2852 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php @@ -0,0 +1,195 @@ + 'Access manager tests', + 'description' => 'Test for the AccessManager object.', + 'group' => 'Routing', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->container = new ContainerBuilder(); + $this->accessManager = new AccessManager(); + $this->accessManager->setContainer($this->container); + + $this->routeCollection = new RouteCollection(); + $this->routeCollection->add('test_route_1', new Route('/test-route-1')); + $this->routeCollection->add('test_route_2', new Route('/test-route-2', array(), array('_access' => 'TRUE'))); + $this->routeCollection->add('test_route_3', new Route('/test-route-3', array(), array('_access' => 'FALSE'))); + } + + /** + * Tests \Drupal\Core\Access\AccessManager::setChecks(). + */ + public function testSetChecks() { + // Check setChecks without any access checker defined yet. + $this->accessManager->setChecks($this->routeCollection); + + foreach ($this->routeCollection->all() as $route) { + $this->assertNull($route->getOption('_access_checks')); + } + + + $this->setupAccessChecker(); + + $this->accessManager->setChecks($this->routeCollection); + + $this->assertEquals($this->routeCollection->get('test_route_1')->getOption('_access_checks'), NULL); + $this->assertEquals($this->routeCollection->get('test_route_2')->getOption('_access_checks'), array('test_access_default')); + $this->assertEquals($this->routeCollection->get('test_route_3')->getOption('_access_checks'), array('test_access_default')); + } + + /** + * Tests \Drupal\Core\Access\AccessManager::check(). + */ + public function testCheck() { + $request = new Request(); + + // Check check without any access checker defined yet. + foreach ($this->routeCollection->all() as $route) { + $this->assertFalse($this->accessManager->check($route, $request)); + } + + $this->setupAccessChecker(); + + // An access checker got setup, but the routes haven't been setup using + // setChecks. + foreach ($this->routeCollection->all() as $route) { + $this->assertFalse($this->accessManager->check($route, $request)); + } + + $this->accessManager->setChecks($this->routeCollection); + + $this->assertFalse($this->accessManager->check($this->routeCollection->get('test_route_1'), $request)); + $this->assertTrue($this->accessManager->check($this->routeCollection->get('test_route_2'), $request)); + $this->assertFalse($this->accessManager->check($this->routeCollection->get('test_route_3'), $request)); + } + + /** + * Test \Drupal\Core\Access\AccessManager::check() with conjunctions. + */ + public function testCheckConjunctions() { + $this->setupAccessChecker(); + $access_check = new DefinedTestAccessCheck(); + $this->container->register('test_access_defined', $access_check); + $this->accessManager->addCheckService('test_access_defined'); + + $request = new Request(); + + $this->routeCollection->add('test_route_4', new Route('/test-route-4', array(), array( + '_access' => 'TRUE', + '_test_access' => 'TRUE', + ))); + + $this->routeCollection->add('test_route_5', new Route('/test-route-5', array(), array( + '_access' => 'TRUE', + '_test_access' => 'TRUE', + )), array('_access_conjunction' => 'OR')); + + $this->routeCollection->add('test_route_6', new Route('/test-route-6', array(), array( + '_access' => 'TRUE', + '_test_access' => 'TRUE', + )), array('_access_conjunction' => 'AND')); + + $this->routeCollection->add('test_route_7', new Route('/test-route-7', array(), array( + '_access' => 'FALSE', + '_test_access' => 'TRUE', + )), array('_access_conjunction' => 'OR')); + + $this->routeCollection->add('test_route_8', new Route('/test-route-8', array(), array( + '_access' => 'FALSE', + '_test_access' => 'TRUE', + )), array('_access_conjunction' => 'AND')); + + $this->routeCollection->add('test_route_9', new Route('/test-route-9', array(), array( + '_access' => 'NULL', + '_test_access' => 'TRUE', + )), array('_access_conjunction' => 'AND')); + + $this->routeCollection->add('test_route_10', new Route('/test-route-10', array(), array( + '_access' => 'NULL', + '_test_access' => 'TRUE', + )), array('_access_conjunction' => 'OR')); + + $this->routeCollection->add('test_route_11', new Route('/test-route-11', array(), array( + '_access' => 'NULL', + '_test_access' => 'FALSE', + )), array('_access_conjunction' => 'OR')); + + $this->accessManager->setChecks($this->routeCollection); + + $expected = array(); + $expected['test_route_4'] = TRUE; + $expected['test_route_5'] = TRUE; + $expected['test_route_6'] = TRUE; + $expected['test_route_7'] = FALSE; + $expected['test_route_8'] = FALSE; + $expected['test_route_9'] = TRUE; + $expected['test_route_10'] = TRUE; + $expected['test_route_11'] = FALSE; + + foreach ($expected as $route_name => $expected_access) { + $this->assertSame($this->accessManager->check($this->routeCollection->get($route_name), $request), $expected_access); + } + } + + /** + * Adds a default access check service to the container and the access manager. + */ + protected function setupAccessChecker() { + $access_check = new DefaultAccessCheck(); + $this->container->register('test_access_default', $access_check); + $this->accessManager->addCheckService('test_access_default'); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php index d96825c..bb96686 100644 --- a/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php +++ b/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php @@ -62,13 +62,15 @@ public function testApplies() { * Test the access method. */ public function testAccess() { - $route = new Route('/test-route'); $request = new Request(array()); - $route->addRequirements(array('_access' => 'FALSE')); + $route = new Route('/test-route', array(), array('_access' => 'NULL')); + $this->assertNull($this->accessChecker->access($route, $request)); + + $route = new Route('/test-route', array(), array('_access' => 'FALSE')); $this->assertFalse($this->accessChecker->access($route, $request)); - $route->addRequirements(array('_access' => 'TRUE')); + $route = new Route('/test-route', array(), array('_access' => 'TRUE')); $this->assertTrue($this->accessChecker->access($route, $request)); }