diff --git a/core/modules/content_moderation/src/Permissions.php b/core/modules/content_moderation/src/Permissions.php index a6d6ce505d..038d6d96ea 100644 --- a/core/modules/content_moderation/src/Permissions.php +++ b/core/modules/content_moderation/src/Permissions.php @@ -30,6 +30,9 @@ public function transitionPermissions() { '%workflow' => $workflow->label(), '%transition' => $transition->label(), ]), + 'dependencies' => [ + $workflow->getConfigDependencyKey() => [$workflow->getConfigDependencyName()], + ], ]; } } diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php index f17e4ac83c..8a41a5c160 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationPermissionsTest.php @@ -59,9 +59,19 @@ public function permissionsTestCases() { [ 'use simple_workflow transition publish' => [ 'title' => 'Simple Workflow workflow: Use Publish transition.', + 'dependencies' => [ + 'config' => [ + 'workflows.workflow.simple_workflow' + ] + ], ], 'use simple_workflow transition create_new_draft' => [ 'title' => 'Simple Workflow workflow: Use Create New Draft transition.', + 'dependencies' => [ + 'config' => [ + 'workflows.workflow.simple_workflow' + ] + ], ], ], ], diff --git a/core/modules/content_translation/src/ContentTranslationPermissions.php b/core/modules/content_translation/src/ContentTranslationPermissions.php index dc7959bb6c..4ecc20ea0f 100644 --- a/core/modules/content_translation/src/ContentTranslationPermissions.php +++ b/core/modules/content_translation/src/ContentTranslationPermissions.php @@ -72,6 +72,9 @@ public function contentPermissions() { $permission["translate $bundle $entity_type_id"] = [ 'title' => $this->t('Translate %bundle_label @entity_label', $t_args), ]; + if (($bundle_entity_type = $entity_type->getBundleEntityType()) && $bundle_entity = $this->entityManager->getStorage($bundle_entity_type)->load($bundle)) { + $permission["translate $bundle $entity_type_id"]['dependencies'][$bundle_entity->getConfigDependencyKey()][] = $bundle_entity->getConfigDependencyName(); + } } } break; @@ -80,6 +83,7 @@ public function contentPermissions() { if ($this->contentTranslationManager->isEnabled($entity_type_id)) { $permission["translate $entity_type_id"] = [ 'title' => $this->t('Translate @entity_label', $t_args), + 'dependencies' => ['module' => [$entity_type->getProvider()]], ]; } break; diff --git a/core/modules/content_translation/tests/src/Kernel/ContentTranslationPermissionsTest.php b/core/modules/content_translation/tests/src/Kernel/ContentTranslationPermissionsTest.php new file mode 100644 index 0000000000..bc94223401 --- /dev/null +++ b/core/modules/content_translation/tests/src/Kernel/ContentTranslationPermissionsTest.php @@ -0,0 +1,50 @@ +installEntitySchema('entity_test_mul'); + $this->installEntitySchema('entity_test_mul_with_bundle'); + EntityTestMulBundle::create([ + 'id' => 'test', + 'label' => 'Test label', + 'description' => 'My test description', + ])->save(); + + } + + /** + * Tests that enabling translation via the API triggers schema updates. + */ + public function testPermissions() { + $this->container->get('content_translation.manager')->setEnabled('entity_test_mul', 'entity_test_mul', TRUE); + $this->container->get('content_translation.manager')->setEnabled('entity_test_mul_with_bundle', 'test', TRUE); + $permissions = \Drupal::service('user.permissions')->getPermissions(); + $this->assertEquals(['entity_test'], $permissions['translate entity_test_mul']['dependencies']['module']); + $this->assertEquals(['entity_test.entity_test_mul_bundle.test'], $permissions['translate test entity_test_mul_with_bundle']['dependencies']['config']); + } + +} diff --git a/core/modules/field_ui/src/FieldUiPermissions.php b/core/modules/field_ui/src/FieldUiPermissions.php index be73db6658..b36124c8b9 100644 --- a/core/modules/field_ui/src/FieldUiPermissions.php +++ b/core/modules/field_ui/src/FieldUiPermissions.php @@ -48,17 +48,22 @@ public function fieldPermissions() { foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->get('field_ui_base_route')) { + // The permissions should depend on the module that provides the entity. + $dependencies = ['module' => [$entity_type->getProvider()]]; // Create a permission for each fieldable entity to manage // the fields and the display. $permissions['administer ' . $entity_type_id . ' fields'] = [ 'title' => $this->t('%entity_label: Administer fields', ['%entity_label' => $entity_type->getLabel()]), 'restrict access' => TRUE, + 'dependencies' => $dependencies, ]; $permissions['administer ' . $entity_type_id . ' form display'] = [ - 'title' => $this->t('%entity_label: Administer form display', ['%entity_label' => $entity_type->getLabel()]) + 'title' => $this->t('%entity_label: Administer form display', ['%entity_label' => $entity_type->getLabel()]), + 'dependencies' => $dependencies, ]; $permissions['administer ' . $entity_type_id . ' display'] = [ - 'title' => $this->t('%entity_label: Administer display', ['%entity_label' => $entity_type->getLabel()]) + 'title' => $this->t('%entity_label: Administer display', ['%entity_label' => $entity_type->getLabel()]), + 'dependencies' => $dependencies, ]; } } diff --git a/core/modules/filter/src/FilterPermissions.php b/core/modules/filter/src/FilterPermissions.php index fc22f3c4cc..181394dd81 100644 --- a/core/modules/filter/src/FilterPermissions.php +++ b/core/modules/filter/src/FilterPermissions.php @@ -59,6 +59,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/tests/src/Kernel/FilterCrudTest.php b/core/modules/filter/tests/src/Kernel/FilterCrudTest.php index b73b180bfe..0f76d09b0a 100644 --- a/core/modules/filter/tests/src/Kernel/FilterCrudTest.php +++ b/core/modules/filter/tests/src/Kernel/FilterCrudTest.php @@ -99,6 +99,11 @@ public function verifyTextFormat($format) { $this->assertEqual($filter_format->get('weight'), $format->get('weight'), format_string('filter_format_load: Proper weight for text format %format.', $t_args)); // Check that the filter was created in site default language. $this->assertEqual($format->language()->getId(), $default_langcode, format_string('filter_format_load: Proper language code for text format %format.', $t_args)); + + // Verify the permission exists and has the correct dependencies. + $permissions = \Drupal::service('user.permissions')->getPermissions(); + $this->assertTrue(isset($permissions[$format->getPermissionName()])); + $this->assertEquals(['config' => [$format->getConfigDependencyName()]], $permissions[$format->getPermissionName()]['dependencies']); } } diff --git a/core/modules/node/src/NodePermissions.php b/core/modules/node/src/NodePermissions.php index 1996360ae1..bf4a246b43 100644 --- a/core/modules/node/src/NodePermissions.php +++ b/core/modules/node/src/NodePermissions.php @@ -23,9 +23,20 @@ class NodePermissions { */ public function nodeTypePermissions() { $perms = []; + /* @var \Drupal\node\Entity\NodeType $type */ // Generate node permissions for all node types. foreach (NodeType::loadMultiple() as $type) { - $perms += $this->buildPermissions($type); + $perms_to_add = array_map( + function ($perm) use ($type) { + // This permission is generated in behalf of a node type, therefore + // add the node type as a config dependency. + $perm['dependencies'] = [ + $type->getConfigDependencyKey() => [$type->getConfigDependencyName()], + ]; + return $perm; + }, + $this->buildPermissions($type)); + $perms += $perms_to_add; } return $perms; diff --git a/core/modules/node/src/Tests/NodeTypeTest.php b/core/modules/node/src/Tests/NodeTypeTest.php index 70d3ac40b5..2fae5645d4 100644 --- a/core/modules/node/src/Tests/NodeTypeTest.php +++ b/core/modules/node/src/Tests/NodeTypeTest.php @@ -75,12 +75,17 @@ public function testNodeTypeCreation() { 'type' => 'foo', ]; $this->drupalPostForm('admin/structure/types/add', $edit, t('Save and manage fields')); - $type_exists = (bool) NodeType::load('foo'); - $this->assertTrue($type_exists, 'The new content type has been created in the database.'); - + $node_type = NodeType::load('foo'); + $this->assertTrue((bool) $type_exists, 'The new content type has been created in the database.'); $this->drupalGet('node/add'); $elements = $this->cssSelect('dl.node-type-list dt'); $this->assertEqual(4, count($elements)); + + // Ensure that the node type permissions have been created and have the + // correct dependencies. + $permissions = \Drupal::service('user.permissions')->getPermissions(); + $this->assertTrue(isset($permissions['create foo content'])); + $this->assertEqual(['config' => [$node_type->getConfigDependencyName()]], $permissions['create foo content']['dependencies']); } /** diff --git a/core/modules/rest/src/RestPermissions.php b/core/modules/rest/src/RestPermissions.php index 473c1924ee..8ecf6388cb 100644 --- a/core/modules/rest/src/RestPermissions.php +++ b/core/modules/rest/src/RestPermissions.php @@ -57,7 +57,17 @@ public function permissions() { $resource_configs = $this->resourceConfigStorage->loadMultiple(); foreach ($resource_configs as $resource_config) { $plugin = $resource_config->getResourcePlugin(); - $permissions = array_merge($permissions, $plugin->permissions()); + + $permissions_to_add = array_map(function ($permission_info) use ($resource_config) { + $permission_info += ['dependencies' => []]; + $permission_info['dependencies'] += [ + $resource_config->getConfigDependencyKey() => [ + $resource_config->getConfigDependencyName(), + ], + ]; + return $permission_info; + }, $plugin->permissions()); + $permissions = array_merge($permissions, $permissions_to_add); } return $permissions; } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php index 76aff4bccd..c2befa4aeb 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php @@ -11,7 +11,7 @@ /** * {@inheritdoc} */ - public static $modules = ['user']; + public static $modules = ['action', 'user']; /** * {@inheritdoc} @@ -70,15 +70,6 @@ protected function getExpectedNormalizedEntity() { ]; } - /** - * {@inheritdoc} - */ - protected function getExpectedCacheContexts() { - return [ - 'user.permissions', - ]; - } - /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php index 1f4b03a2cf..26eae2721a 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php @@ -11,7 +11,7 @@ /** * {@inheritdoc} */ - public static $modules = ['field', 'node']; + public static $modules = ['field', 'node', 'field_ui']; /** * {@inheritdoc} diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php index cc4cf588a7..34cc2908f3 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php @@ -11,7 +11,7 @@ /** * {@inheritdoc} */ - public static $modules = ['node']; + public static $modules = ['node', 'field_ui']; /** * {@inheritdoc} diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 418ddfbb4a..da30df9452 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -460,12 +460,17 @@ public function testGet() { if ($this->entity->getEntityType()->getLinkTemplates()) { $this->assertArrayHasKey('Link', $response->getHeaders()); $link_relation_type_manager = $this->container->get('plugin.manager.link_relation_type'); + // Filter out definitions that do not have a plugin. + // @see \Drupal\rest\Plugin\rest\resource\EntityResource::addLinkHeaders() + $link_relations = array_values(array_filter(array_keys($this->entity->getEntityType()->getLinkTemplates()), function ($relation_name) use ($link_relation_type_manager) { + return $link_relation_type_manager->hasDefinition($relation_name); + })); $expected_link_relation_headers = array_map(function ($relation_name) use ($link_relation_type_manager) { $link_relation_type = $link_relation_type_manager->createInstance($relation_name); return $link_relation_type->isRegistered() ? $link_relation_type->getRegisteredName() : $link_relation_type->getExtensionUri(); - }, array_keys($this->entity->getEntityType()->getLinkTemplates())); + }, $link_relations); $parse_rel_from_link_header = function ($value) use ($link_relation_type_manager) { $matches = []; if (preg_match('/rel="([^"]+)"/', $value, $matches) === 1) { diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php index ccc3aad3c0..5234703c1e 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php @@ -11,7 +11,7 @@ /** * {@inheritdoc} */ - public static $modules = ['node']; + public static $modules = ['node', 'field_ui']; /** * {@inheritdoc} diff --git a/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php index 05c05f478a..89ddd936cc 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php @@ -12,7 +12,7 @@ /** * {@inheritdoc} */ - public static $modules = ['field', 'node']; + public static $modules = ['field', 'node', 'field_ui']; /** * {@inheritdoc} diff --git a/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php index aafc2531e7..9eb5a3241c 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php @@ -10,7 +10,7 @@ /** * {@inheritdoc} */ - public static $modules = ['node']; + public static $modules = ['node', 'field_ui']; /** * {@inheritdoc} @@ -92,13 +92,4 @@ protected function getExpectedUnauthorizedAccessMessage($method) { } } - /** - * {@inheritdoc} - */ - protected function getExpectedCacheContexts() { - return [ - 'user.permissions', - ]; - } - } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php index 26ec7bb5c4..58c2dbc154 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php @@ -10,7 +10,7 @@ /** * {@inheritdoc} */ - public static $modules = ['views']; + public static $modules = ['views', 'views_ui']; /** * {@inheritdoc} @@ -87,13 +87,4 @@ protected function getNormalizedPostEntity() { // @todo Update in https://www.drupal.org/node/2300677. } - /** - * {@inheritdoc} - */ - protected function getExpectedCacheContexts() { - return [ - 'user.permissions', - ]; - } - } diff --git a/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php index 89e9b1dec5..5346e79d0b 100644 --- a/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php +++ b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php @@ -16,7 +16,7 @@ class RestResourceConfigTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['rest', 'entity_test', 'serialization', 'basic_auth', 'user', 'hal']; + public static $modules = ['rest', 'entity_test', 'serialization', 'basic_auth', 'user', 'hal', 'system']; /** * @covers ::calculateDependencies @@ -41,4 +41,33 @@ public function testCalculateDependencies() { $this->assertEquals(['module' => ['basic_auth', 'entity_test', 'hal', 'serialization', 'user']], $rest_config->getDependencies()); } + /** + * Tests permissions when bc_entity_resource_permissions is enabled. + */ + public function testPermissions() { + $this + ->config('rest.settings') + ->set('bc_entity_resource_permissions', TRUE) + ->save(); + $rest_config = RestResourceConfig::create([ + 'id' => 'test', + 'plugin_id' => 'entity:entity_test', + 'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY, + 'configuration' => [ + 'GET' => [ + 'supported_auth' => ['cookie'], + 'supported_formats' => ['json'], + ], + 'POST' => [ + 'supported_auth' => ['basic_auth'], + 'supported_formats' => ['hal_json'], + ], + ], + ]); + $rest_config->save(); + $permissions = \Drupal::service('user.permissions')->getPermissions(); + $this->assertTrue(isset($permissions['restful get entity:entity_test'])); + $this->assertEquals(['config' => [$rest_config->getConfigDependencyName()]], $permissions['restful get entity:entity_test']['dependencies']); + } + } diff --git a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml index d34d94940d..31aa1d614c 100644 --- a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml +++ b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml @@ -27,3 +27,7 @@ entity_test.entity_test_bundle.*: description: type: text label: 'Description' + +entity_test.entity_test_mul_bundle.*: + type: entity_test.entity_test_bundle.* + label: 'Entity test mul bundle' diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 025cb5fc2f..d595d4a320 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -71,6 +71,7 @@ function entity_test_entity_types($filter = NULL) { $types[] = 'entity_test_base_field_display'; $types[] = 'entity_test_string_id'; $types[] = 'entity_test_no_id'; + $types[] = 'entity_test_mul_with_bundle'; } $types[] = 'entity_test_mulrev'; $types[] = 'entity_test_mulrev_changed'; @@ -199,7 +200,7 @@ function entity_test_entity_bundle_info() { $bundles = []; $entity_types = \Drupal::entityManager()->getDefinitions(); foreach ($entity_types as $entity_type_id => $entity_type) { - if ($entity_type->getProvider() == 'entity_test' && $entity_type_id != 'entity_test_with_bundle') { + if ($entity_type->getProvider() == 'entity_test' && !in_array($entity_type_id, ['entity_test_with_bundle', 'entity_test_mul_with_bundle'], TRUE)) { $bundles[$entity_type_id] = \Drupal::state()->get($entity_type_id . '.bundles') ?: [$entity_type_id => ['label' => 'Entity Test Bundle']]; } } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulBundle.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulBundle.php new file mode 100644 index 0000000000..65e58b719c --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulBundle.php @@ -0,0 +1,77 @@ +description; + } + + /** + * {@inheritdoc} + */ + public function setDescription($description) { + $this->description = $description; + return $this; + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulWithBundle.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulWithBundle.php new file mode 100644 index 0000000000..fb7e0d9aed --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulWithBundle.php @@ -0,0 +1,49 @@ +buildPermissions($vocabulary); + $perms_to_add = array_map( + function ($perm) use ($vocabulary) { + // This permission is generated in behalf of a vocabulary, therefore + // add the vocabulary as a config dependency. + $perm['dependencies'] = [ + $vocabulary->getConfigDependencyKey() => [$vocabulary->getConfigDependencyName()], + ]; + return $perm; + }, + $this->buildPermissions($vocabulary)); + $permissions += $perms_to_add; } return $permissions; } diff --git a/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php b/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php index 989398e62f..2de7799b92 100644 --- a/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php +++ b/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php @@ -209,6 +209,11 @@ public function testTaxonomyVocabularyOverviewPermissions() { $assert_session->statusCodeEquals(200); $assert_session->pageTextContains('No terms available'); $assert_session->linkExists('Add term'); + + // Ensure the dynamic vocabulary permissions have the correct dependencies. + $permissions = \Drupal::service('user.permissions')->getPermissions(); + $this->assertTrue(isset($permissions['create terms in ' . $vocabulary1_id])); + $this->assertEquals(['config' => [$vocabulary1->getConfigDependencyName()]], $permissions['create terms in ' . $vocabulary1_id]['dependencies']); } /** diff --git a/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php b/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php index 9585c86eb4..02d85afc5f 100644 --- a/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php +++ b/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php @@ -7,6 +7,7 @@ use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Tests\BrowserTestBase; +use Drupal\user\Entity\Role; use Drupal\user\RoleInterface; /** @@ -99,6 +100,14 @@ protected function setUp() { * implementations. */ public function testModuleStatusChangeSubtreesHashCacheClear() { + // Give the user an admin role so permissions change when modules are + // installed and uninstalled. + $role = Role::load($this->createRole([])); + $role->setIsAdmin(TRUE); + $role->save(); + $this->adminUser->addRole($role->id()); + $this->adminUser->save(); + // Uninstall a module. $edit = []; $edit['uninstall[taxonomy]'] = TRUE; diff --git a/core/modules/user/src/Entity/Role.php b/core/modules/user/src/Entity/Role.php index 0226f94b78..582fa35dc3 100644 --- a/core/modules/user/src/Entity/Role.php +++ b/core/modules/user/src/Entity/Role.php @@ -186,4 +186,68 @@ 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 Why is this necessary? Surely for a permission to be on role it + // has to exist. + 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; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + // Load all permission definitions. + $permission_definitions = \Drupal::service('user.permissions')->getPermissions(); + + // Convert dependencies to a list of names to make it easy to check. + 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; + $dependencies[$type][$key] = $name; + } + } + + // If the permission is dependent on anything being deleted or uninstalled + // then remove the permission from the role. + foreach ($this->permissions as $key => $permission) { + if (in_array($permission_definitions[$permission]['provider'], $dependencies['module'], TRUE)) { + unset($this->permissions[$key]); + $changed = TRUE; + } + elseif (isset($permission_definitions[$permission]['dependencies'])) { + foreach ($permission_definitions[$permission]['dependencies'] as $type => $list) { + if (array_intersect($list, $dependencies[$type])) { + unset($this->permissions[$key]); + $changed = TRUE; + } + } + } + } + + return $changed; + } + } diff --git a/core/modules/user/src/PermissionHandler.php b/core/modules/user/src/PermissionHandler.php index 1f07b25d95..38559447bc 100644 --- a/core/modules/user/src/PermissionHandler.php +++ b/core/modules/user/src/PermissionHandler.php @@ -33,6 +33,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: @@ -41,6 +46,7 @@ * - Drupal\filter\FilterPermissions::permissions * @endcode * + * @see \Drupal\user\PermissionHandlerInterface::getPermissions() * @see filter.permissions.yml * @see \Drupal\filter\FilterPermissions * @see user_api @@ -130,10 +136,10 @@ 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 + * PermissionHandlerInterface::getPermissions(). + * + * @see \Drupal\user\PermissionHandlerInterface::getPermissions() */ protected function buildPermissionsYaml() { $all_permissions = []; @@ -193,10 +199,10 @@ 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 + * PermissionHandlerInterface::getPermissions(). + * + * @see \Drupal\user\PermissionHandlerInterface::getPermissions() */ protected function sortPermissions(array $all_permissions = []) { // 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 61526f339a..ec70d8e6fc 100644 --- a/core/modules/user/src/PermissionHandlerInterface.php +++ b/core/modules/user/src/PermissionHandlerInterface.php @@ -34,7 +34,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/tests/src/Kernel/UserRoleDeleteTest.php b/core/modules/user/tests/src/Kernel/UserRoleDeleteTest.php index cac470aaab..704b8d82cb 100644 --- a/core/modules/user/tests/src/Kernel/UserRoleDeleteTest.php +++ b/core/modules/user/tests/src/Kernel/UserRoleDeleteTest.php @@ -2,7 +2,11 @@ namespace Drupal\Tests\user\Kernel; +use Drupal\Component\Utility\Unicode; +use Drupal\filter\Entity\FilterFormat; use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\NodeType; +use Drupal\user\Entity\Role; use Drupal\user\Entity\User; /** @@ -71,4 +75,80 @@ public function testRoleDeleteUserRoleReferenceDelete() { } + /** + * 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()}"; + // Add two permissions with the same dependency to ensure both are removed + // and the role is not deleted. + $permission_node_type = "edit any {$node_type->id()} content"; + $permission_node_type_create = "create {$node_type->id()} content"; + + // Grant $role permission to access content, use $format, edit $node_type. + $role + ->grantPermission('access content') + ->grantPermission($permission_format) + ->grantPermission($permission_node_type) + ->grantPermission($permission_node_type_create) + ->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)); + $this->assertTrue($role->hasPermission($permission_node_type_create)); + + // Remove the format. + $format->delete(); + + // The $role config entity exists after removing the config dependency. + $role = Role::load($rid); + $this->assertNotNull($role); + // The $format permission should have been revoked. + $this->assertFalse($role->hasPermission($permission_format)); + $this->assertTrue($role->hasPermission($permission_node_type)); + $this->assertTrue($role->hasPermission($permission_node_type_create)); + + // 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. + $role = Role::load($rid); + $this->assertNotNull($role); + // The $node_type permission should have been revoked too. + $this->assertFalse($role->hasPermission($permission_format)); + $this->assertFalse($role->hasPermission($permission_node_type)); + $this->assertFalse($role->hasPermission($permission_node_type_create)); + // The 'access content' permission should not have been revoked. + $this->assertTrue($role->hasPermission('access content')); + } + } diff --git a/core/profiles/testing_config_overrides/config/install/user.role.authenticated.yml b/core/profiles/testing_config_overrides/config/install/user.role.authenticated.yml new file mode 100644 index 0000000000..1b210736be --- /dev/null +++ b/core/profiles/testing_config_overrides/config/install/user.role.authenticated.yml @@ -0,0 +1,11 @@ +langcode: en +status: true +dependencies: + module: + - tour +id: authenticated +label: 'Authenticated user' +weight: 1 +is_admin: false +permissions: + - 'access tour'