diff --git a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php index 0ef52593255..0e5a57b50db 100644 --- a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php @@ -41,6 +41,10 @@ class UpdateModuleHandler extends ModuleHandler { return array('system'); // This is called during rebuild to find testing themes. case 'system_theme_info': + // Those are needed by user_access() to check access on update.php. + case 'entity_info': + case 'entity_load': + case 'user_role_load': return array(); // t() in system_stream_wrappers() needs this. Other schema calls aren't // supported. diff --git a/core/lib/Drupal/Core/Session/AccountInterface.php b/core/lib/Drupal/Core/Session/AccountInterface.php index 3771437d2d2..a53d08804e5 100644 --- a/core/lib/Drupal/Core/Session/AccountInterface.php +++ b/core/lib/Drupal/Core/Session/AccountInterface.php @@ -31,6 +31,17 @@ interface AccountInterface { */ public function getRoles(); + /** + * Checks whether a user has a certain permission. + * + * @param string $permission + * The permission string to check. + * + * @return bool + * TRUE if the user has the permission, FALSE otherwise. + */ + public function hasPermission($permission); + /** * Returns the session ID. * diff --git a/core/lib/Drupal/Core/Session/UserSession.php b/core/lib/Drupal/Core/Session/UserSession.php index b3dae614db8..7922d06302d 100644 --- a/core/lib/Drupal/Core/Session/UserSession.php +++ b/core/lib/Drupal/Core/Session/UserSession.php @@ -112,6 +112,26 @@ class UserSession implements AccountInterface { return $this->roles; } + /** + * {@inheritdoc} + */ + public function hasPermission($permission) { + // User #1 has all privileges. + if ((int) $this->id() === 1) { + return TRUE; + } + + $roles = \Drupal::entityManager()->getStorageController('user_role')->loadMultiple($this->getRoles()); + + foreach ($roles as $role) { + if ($role->hasPermission($permission)) { + return TRUE; + } + } + + return FALSE; + } + /** * {@inheritdoc} */ diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php index 413cdb71da3..b229ff101df 100644 --- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php +++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php @@ -215,6 +215,26 @@ class User extends EntityNG implements UserInterface { $this->set('roles', array_diff($this->getRoles(), array($rid))); } + /** + * {@inheritdoc} + */ + public function hasPermission($permission) { + // User #1 has all privileges. + if ((int) $this->id() === 1) { + return TRUE; + } + + $roles = \Drupal::entityManager()->getStorageController('user_role')->loadMultiple($this->getRoles()); + + foreach ($roles as $role) { + if ($role->hasPermission($permission)) { + return TRUE; + } + } + + return FALSE; + } + /** * {@inheritdoc} */ diff --git a/core/modules/user/lib/Drupal/user/RoleStorageController.php b/core/modules/user/lib/Drupal/user/RoleStorageController.php index fff106e932f..ff012f38e47 100644 --- a/core/modules/user/lib/Drupal/user/RoleStorageController.php +++ b/core/modules/user/lib/Drupal/user/RoleStorageController.php @@ -14,15 +14,6 @@ use Drupal\Core\Config\Entity\ConfigStorageController; */ class RoleStorageController extends ConfigStorageController implements RoleStorageControllerInterface { - /** - * {@inheritdoc} - */ - public function resetCache(array $ids = NULL) { - parent::resetCache($ids); - // Clear the user access cache. - drupal_static_reset('user_access'); - } - /** * {@inheritdoc} */ diff --git a/core/modules/user/lib/Drupal/user/UserBCDecorator.php b/core/modules/user/lib/Drupal/user/UserBCDecorator.php index 4568f00c94a..d705d56feeb 100644 --- a/core/modules/user/lib/Drupal/user/UserBCDecorator.php +++ b/core/modules/user/lib/Drupal/user/UserBCDecorator.php @@ -41,6 +41,13 @@ class UserBCDecorator extends EntityBCDecorator implements UserInterface { return $this->decorated->getRoles(); } + /** + * {@inheritdoc} + */ + public function hasPermission($permission) { + return $this->decorated->hasPermission($permission); + } + /** * {@inheritdoc} */ diff --git a/core/modules/user/lib/Drupal/user/UserInterface.php b/core/modules/user/lib/Drupal/user/UserInterface.php index fbede923d37..832be39719f 100644 --- a/core/modules/user/lib/Drupal/user/UserInterface.php +++ b/core/modules/user/lib/Drupal/user/UserInterface.php @@ -50,6 +50,17 @@ interface UserInterface extends EntityInterface, AccountInterface { */ public function removeRole($rid); + /** + * Checks whether a user has a certain permission. + * + * @param string $permission + * The permission string to check. + * + * @return bool + * TRUE if the user has the permission, FALSE otherwise. + */ + public function hasPermission($permission); + /** * Returns the hashed password. * diff --git a/core/modules/user/tests/Drupal/user/Tests/Plugin/Core/Entity/UserTest.php b/core/modules/user/tests/Drupal/user/Tests/Plugin/Core/Entity/UserTest.php new file mode 100644 index 00000000000..0758f63b617 --- /dev/null +++ b/core/modules/user/tests/Drupal/user/Tests/Plugin/Core/Entity/UserTest.php @@ -0,0 +1,46 @@ + 'User object', + 'description' => 'Tests the user object.', + 'group' => 'User', + ); + } + + /** + * {@inheritdoc} + */ + protected function createUserSession(array $rids = array()) { + $user = $this->getMockBuilder('Drupal\user\Plugin\Core\Entity\User') + ->disableOriginalConstructor() + ->setMethods(array('getRoles', 'id')) + ->getMock(); + $user->expects($this->any()) + ->method('id') + // @todo Also test the uid = 1 handling. + ->will($this->returnValue(0)); + $user->expects($this->any()) + ->method('getRoles') + ->will($this->returnValue($rids)); + return $user; + } + +} diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 50580ffc6b5..5127a4c5ccc 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -462,27 +462,7 @@ function user_access($string, AccountInterface $account = NULL) { $account = Drupal::request()->attributes->get('account') ?: $user; } - // User #1 has all privileges: - if ($account->id() == 1) { - return TRUE; - } - - // To reduce the number of SQL queries, we cache the user's permissions - // in a static variable. - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__); - } - $perm = &$drupal_static_fast['perm']; - if (!isset($perm[$account->id()])) { - $perm[$account->id()] = array(); - foreach (user_role_permissions($account->getRoles()) as $role_permissions) { - $perm[$account->id()] += array_fill_keys($role_permissions, TRUE); - } - } - - return isset($perm[$account->id()][$string]); + return $account->hasPermission($string); } /** @@ -1744,8 +1724,6 @@ function user_role_grant_permissions($rid, array $permissions = array()) { $role->grantPermission($permission); } $role->save(); - // Clear the user access cache. - drupal_static_reset('user_access'); } /** @@ -1766,8 +1744,6 @@ function user_role_revoke_permissions($rid, array $permissions = array()) { $role->revokePermission($permission); } $role->save(); - // Clear the user access cache. - drupal_static_reset('user_access'); } function user_multiple_cancel_confirm($form, &$form_state) { diff --git a/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php b/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php new file mode 100644 index 00000000000..1c26b56d3a6 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php @@ -0,0 +1,156 @@ + 'User session object', + 'description' => 'Tests the user session object.', + 'group' => 'Session', + ); + } + + /** + * Provides test data for getHasPermission(). + * + * @return array + */ + public function providerTestHasPermission() { + $data = array(); + $data[] = array('example permission', array('user_one', 'user_two'), array('user_last')); + $data[] = array('another example permission', array('user_two'), array('user_one', 'user_last')); + $data[] = array('final example permission', array(), array('user_one', 'user_two', 'user_last')); + + return $data; + } + + /** + * Setups a user session for the test. + * + * @param array $rids + * The rids of the user. + * + * @return \Drupal\Core\Session\AccountInterface + * The created user session. + */ + protected function createUserSession(array $rids = array()) { + return new UserSession(array('roles' => $rids)); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $roles = array(); + $roles['role_one'] = $this->getMockBuilder('Drupal\user\Plugin\Core\Entity\Role') + ->disableOriginalConstructor() + ->setMethods(array('hasPermission')) + ->getMock(); + $roles['role_one']->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValueMap(array( + array('example permission', TRUE), + array('another example permission', FALSE), + array('last example permission', FALSE), + ))); + + $roles['role_two'] = $this->getMockBuilder('Drupal\user\Plugin\Core\Entity\Role') + ->disableOriginalConstructor() + ->setMethods(array('hasPermission')) + ->getMock(); + $roles['role_two']->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValueMap(array( + array('example permission', TRUE), + array('another example permission', TRUE), + array('last example permission', FALSE), + ))); + + $role_storage = $this->getMockBuilder('Drupal\user\RoleStorageController') + ->disableOriginalConstructor() + ->setMethods(array('loadMultiple')) + ->getMock(); + $role_storage->expects($this->any()) + ->method('loadMultiple') + ->will($this->returnValueMap(array( + array(array(), array()), + array(NULL, $roles), + array(array('role_one'), array($roles['role_one'])), + array(array('role_two'), array($roles['role_two'])), + array(array('role_one', 'role_two'), array($roles['role_one'], $roles['role_two'])), + ))); + + $entity_manager = $this->getMockBuilder('Drupal\Core\Entity\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $entity_manager->expects($this->any()) + ->method('getStorageController') + ->with($this->equalTo('user_role')) + ->will($this->returnValue($role_storage)); + $container = new ContainerBuilder(); + $container->set('plugin.manager.entity', $entity_manager); + \Drupal::setContainer($container); + + $this->users['user_one'] = $this->createUserSession(array('role_one')); + $this->users['user_two'] = $this->createUserSession(array('role_one', 'role_two')); + $this->users['user_last'] = $this->createUserSession(); + } + + /** + * {@inheritdoc} + */ + protected function tearDown() { + parent::tearDown(); + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + } + + /** + * Tests the has permission method. + * + * @param string $permission + * The permission to check. + * @param \Drupal\Core\Session\AccountInterface[] $sessions_with_access + * The users with access. + * @param \Drupal\Core\Session\AccountInterface[] $sessions_without_access + * The users without access. + * + * @dataProvider providerTestHasPermission + * + * @see \Drupal\Core\Session\UserSession::hasPermission(). + */ + public function testHasPermission($permission, array $sessions_with_access, array $sessions_without_access) { + foreach ($sessions_with_access as $name) { + $this->assertTrue($this->users[$name]->hasPermission($permission)); + } + foreach ($sessions_without_access as $name) { + $this->assertFalse($this->users[$name]->hasPermission($permission)); + } + } + +}