diff --git a/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php b/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php index cecf468..96636ba 100644 --- a/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php +++ b/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php @@ -38,6 +38,8 @@ class MigrateBlockTest extends MigrateDrupal7TestBase { */ protected function setUp() { parent::setUp(); + $this->installSchema('system', ['router']); + $this->container->get('router.builder')->rebuild(); $this->installConfig(static::$modules); $this->installEntitySchema('block_content'); diff --git a/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php b/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php index 6c7277e..b50e236 100644 --- a/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php +++ b/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php @@ -28,6 +28,8 @@ class MigrateCommentTest extends MigrateDrupal7TestBase { protected function setUp() { parent::setUp(); + $this->installSchema('system', ['router']); + $this->container->get('router.builder')->rebuild(); $this->installConfig(static::$modules); $this->installEntitySchema('node'); $this->installEntitySchema('comment'); diff --git a/core/modules/filter/src/FilterPermissions.php b/core/modules/filter/src/FilterPermissions.php index 8f4c597..9dc4188 100644 --- a/core/modules/filter/src/FilterPermissions.php +++ b/core/modules/filter/src/FilterPermissions.php @@ -64,6 +64,13 @@ public function permissions() { '#markup' => $this->t('Warning: This permission may have security implications depending on how the text format is configured.'), '#suffix' => '' ], + // This permission is generated in behalf of $format text format, thus + // we add this text format as a config dependency. + 'dependencies' => [ + $format->getConfigDependencyKey() => [ + $format->getConfigDependencyName(), + ], + ], ]; } } diff --git a/core/modules/filter/src/Tests/FilterAPITest.php b/core/modules/filter/src/Tests/FilterAPITest.php index 263b31e..47762c1 100644 --- a/core/modules/filter/src/Tests/FilterAPITest.php +++ b/core/modules/filter/src/Tests/FilterAPITest.php @@ -283,6 +283,8 @@ function testProcessedTextElement() { * Tests the function of the typed data type. */ function testTypedDataAPI() { + $this->installSchema('system', ['router']); + $this->container->get('router.builder')->rebuild(); $definition = DataDefinition::create('filter_format'); $data = \Drupal::typedDataManager()->create($definition); diff --git a/core/modules/user/src/Entity/Role.php b/core/modules/user/src/Entity/Role.php index 8421215..05d9040 100644 --- a/core/modules/user/src/Entity/Role.php +++ b/core/modules/user/src/Entity/Role.php @@ -186,4 +186,67 @@ public function preSave(EntityStorageInterface $storage) { } } + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + parent::calculateDependencies(); + // Load all permissions. + $permissions = \Drupal::service('user.permissions')->getPermissions(); + foreach ($this->permissions as $permission) { + // @todo Remove this if() when https://www.drupal.org/node/2569741 is in. + if (isset($permissions[$permission])) { + // Depend on the module that is providing this permissions. + $this->addDependency('module', $permissions[$permission]['provider']); + // Depend on any other dependencies defined by permissions granted to + // this role. + if (!empty($permissions[$permission]['dependencies'])) { + foreach ($permissions[$permission]['dependencies'] as $type => $dependencies) { + foreach ($dependencies as $dependency) { + $this->addDependency($type, $dependency); + } + } + } + } + } + return $this->dependencies; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + // Load all permissions. + $permissions = \Drupal::service('user.permissions')->getPermissions(); + + // Merge the permission provider into the module dependency list. + array_walk($permissions, function(&$permission) { + $permission['dependencies']['module'][] = $permission['provider']; + $permission['dependencies']['module'] = array_unique($permission['dependencies']['module']); + }); + + $removed_permissions = []; + foreach ($dependencies as $type => $items) { + foreach ($items as $key => $dependency) { + // Get the dependency name, based on dependency type. + $name = in_array($type, ['config', 'content']) ? $dependency->getConfigDependencyName() : $dependency; + foreach ($this->permissions as $permission) { + if (!empty($permissions[$permission]['dependencies'][$type])) { + if (in_array($name, $permissions[$permission]['dependencies'][$type])) { + $removed_permissions[] = $permission; + break; + } + } + } + } + } + if ($removed_permissions) { + $this->permissions = array_diff($this->permissions, $removed_permissions); + $changed = TRUE; + } + + return $changed; + } + } diff --git a/core/modules/user/src/PermissionHandler.php b/core/modules/user/src/PermissionHandler.php index ac6e9bb..b3d109a 100644 --- a/core/modules/user/src/PermissionHandler.php +++ b/core/modules/user/src/PermissionHandler.php @@ -38,6 +38,11 @@ * # (optional) Boolean, when set to true a warning about site security will * # be displayed on the Permissions page. Defaults to false. * restrict access: false + * # (optional) Dependency array following the same structure as the return + * # config entities dependencies. + * dependencies: + * config: + * - node.type.article * * # An array of callables used to generate dynamic permissions. * permission_callbacks: @@ -46,6 +51,7 @@ * - Drupal\filter\FilterPermissions::permissions * @endcode * + * @see \Drupal\user\PermissionHandlerInterface::getPermissions() * @see filter.permissions.yml * @see \Drupal\filter\FilterPermissions * @see user_api @@ -135,10 +141,9 @@ public function moduleProvidesPermissions($module_name) { * Builds all permissions provided by .permissions.yml files. * * @return array[] - * Each return permission is an array with the following keys: - * - title: The title of the permission. - * - description: The description of the permission, defaults to NULL. - * - provider: The provider of the permission. + * An array with the same structure as ::getPermissions(). + * + * @see \Drupal\user\PermissionHandlerInterface::getPermissions() */ protected function buildPermissionsYaml() { $all_permissions = array(); @@ -198,10 +203,9 @@ protected function buildPermissionsYaml() { * The permissions to be sorted. * * @return array[] - * Each return permission is an array with the following keys: - * - title: The title of the permission. - * - description: The description of the permission, defaults to NULL. - * - provider: The provider of the permission. + * An array with the same structure as ::getPermissions(). + * + * @see \Drupal\user\PermissionHandlerInterface::getPermissions() */ protected function sortPermissions(array $all_permissions = array()) { // Get a list of all the modules providing permissions and sort by diff --git a/core/modules/user/src/PermissionHandlerInterface.php b/core/modules/user/src/PermissionHandlerInterface.php index 269af11..a0f3a3e 100644 --- a/core/modules/user/src/PermissionHandlerInterface.php +++ b/core/modules/user/src/PermissionHandlerInterface.php @@ -39,7 +39,20 @@ * permissions to have a clear, consistent security warning that is the * same across the site. Use the 'description' key instead to provide any * information that is specific to the permission you are defining. + * - dependencies: (optional) An array of dependency entities used when + * building this permission name, structured in the same way as the return + * of ConfigEntityInterface::calculateDependencies(). For example if this + * permission was generated as effect of the existence of node type + * 'article', then value of the dependency key is: + * @code + * 'dependencies' => ['config' => ['node.type.article']] + * @endcode + * The module providing this permission doesn't have to be added as + * dependency, because is automatically parsed, stored and retrieved from + * the 'provider' key. * - provider: (optional) The provider name of the permission. + * + * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() */ public function getPermissions(); diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserRoleTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserRoleTest.php index e78bb7f..67d1cfd 100644 --- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserRoleTest.php +++ b/core/modules/user/src/Tests/Migrate/d6/MigrateUserRoleTest.php @@ -23,6 +23,8 @@ class MigrateUserRoleTest extends MigrateDrupal6TestBase { */ protected function setUp() { parent::setUp(); + $this->installSchema('system', ['router']); + $this->container->get('router.builder')->rebuild(); $this->executeMigrations(['d6_filter_format', 'd6_user_role']); } diff --git a/core/modules/user/src/Tests/UserRoleDeleteTest.php b/core/modules/user/src/Tests/UserRoleDeleteTest.php index 81396ac..b0a8f11 100644 --- a/core/modules/user/src/Tests/UserRoleDeleteTest.php +++ b/core/modules/user/src/Tests/UserRoleDeleteTest.php @@ -7,7 +7,11 @@ namespace Drupal\user\Tests; +use Drupal\Component\Utility\Unicode; +use Drupal\filter\Entity\FilterFormat; +use Drupal\node\Entity\NodeType; use Drupal\simpletest\KernelTestBase; +use Drupal\user\Entity\Role; use Drupal\user\Entity\User; /** @@ -73,7 +77,76 @@ public function testRoleDeleteUserRoleReferenceDelete() { // Check that user does not have role one. $this->assertFalse($user->hasRole('test_role_one')); $this->assertTrue($user->hasRole('test_role_two')); + } + + /** + * Tests the removal of user role dependencies. + */ + public function testDependenciesRemoval() { + $this->enableModules(['node', 'filter']); + $this->installSchema('system', ['router']); + $this->container->get('router.builder')->rebuild(); + + /** @var \Drupal\user\RoleInterface $role */ + $role = Role::create([ + 'id' => $rid = Unicode::strtolower($this->randomMachineName()), + 'label' => $this->randomString(), + ]); + $role->save(); + + /** @var \Drupal\node\NodeTypeInterface $node_type */ + $node_type = NodeType::create([ + 'type' => Unicode::strtolower($this->randomMachineName()), + 'name' => $this->randomString(), + ]); + $node_type->save(); + // Create a new text format to be used by role $role. + $format = FilterFormat::create([ + 'format' => Unicode::strtolower($this->randomMachineName()), + 'name' => $this->randomString(), + ]); + $format->save(); + + $permission_format = "use text format {$format->id()}"; + $permission_node_type = "edit any {$node_type->id()} content"; + + // Grant $role with permission to use $format and edit $node_type. + $role + ->grantPermission($permission_format) + ->grantPermission($permission_node_type) + ->save(); + + // The role $role has the permissions to use $format and edit $node_type. + $role = Role::load($rid); + $this->assertTrue($role->hasPermission($permission_format)); + $this->assertTrue($role->hasPermission($permission_node_type)); + + // The $role config entity exists before removing dependencies. + $this->assertNotNull($role = Role::load($rid)); + + // Remove the format. + $format->delete(); + + // The $role config entity exists after removing the config dependency. + if ($this->assertNotNull($role = Role::load($rid))) { + // The $format permission should have been revoked. + $this->assertFalse($role->hasPermission($permission_format)); + $this->assertTrue($role->hasPermission($permission_node_type)); + } + + // We have to manually trigger the removal of configuration belonging to the + // module because KernelTestBase::disableModules() is not aware of this. + $this->container->get('config.manager')->uninstall('module', 'node'); + // Disable the node module. + $this->disableModules(['node']); + + // The $role config entity exists after removing the module dependency. + if ($this->assertNotNull($role = Role::load($rid))) { + // The $node_type permission should have been revoked too. + $this->assertFalse($role->hasPermission($permission_format)); + $this->assertFalse($role->hasPermission($permission_node_type)); + } } }