diff --git a/core/modules/rest/config/optional/rest.resource.entity.node.yml b/core/modules/rest/config/optional/rest.resource.entity.node.yml index caeed781a0a7a6d10f12b09bba093a12ea970015..1388245d20b238db95e1955e9ad24f77ccc2390b 100644 --- a/core/modules/rest/config/optional/rest.resource.entity.node.yml +++ b/core/modules/rest/config/optional/rest.resource.entity.node.yml @@ -7,25 +7,14 @@ dependencies: - node id: entity.node plugin_id: 'entity:node' -granularity: method +granularity: resource configuration: - GET: - supported_formats: - - hal_json - supported_auth: - - basic_auth - POST: - supported_formats: - - hal_json - supported_auth: - - basic_auth - PATCH: - supported_formats: - - hal_json - supported_auth: - - basic_auth - DELETE: - supported_formats: - - hal_json - supported_auth: - - basic_auth + methods: + - GET + - POST + - PATCH + - DELETE + formats: + - hal_json + authentication: + - basic_auth diff --git a/core/modules/rest/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml index 41bc2bf4a55988a750cac59e124ed7505f71c0dd..04f88a6fada02a9bdbb3ca4105f5e2c383c8c598 100644 --- a/core/modules/rest/config/schema/rest.schema.yml +++ b/core/modules/rest/config/schema/rest.schema.yml @@ -8,7 +8,6 @@ rest.settings: label: 'Domain of the relation' # Method-level granularity of REST resource configuration. -# @todo Add resource-level granularity in https://www.drupal.org/node/2721595. rest_resource.method: type: mapping mapping: @@ -25,6 +24,29 @@ rest_resource.method: type: rest_request label: 'DELETE method settings' +# Resource-level granularity of REST resource configuration. +rest_resource.resource: + type: mapping + mapping: + methods: + type: sequence + label: 'Supported methods' + sequence: + type: string + label: 'HTTP method' + formats: + type: sequence + label: 'Supported formats' + sequence: + type: string + label: 'Format' + authentication: + type: sequence + label: 'Supported authentication providers' + sequence: + type: string + label: 'Authentication provider' + rest_request: type: mapping mapping: diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install index 918b4f5ed9df6bcffaa58f6542623eab61dad282..4cfaa112fb0a2571c6cb7def60689bc5d1a33ffe 100644 --- a/core/modules/rest/rest.install +++ b/core/modules/rest/rest.install @@ -30,6 +30,8 @@ function rest_requirements($phase) { /** * Install the REST config entity type and fix old settings-based config. + * + * @see rest_post_update_create_rest_resource_config_entities() */ function rest_update_8201() { \Drupal::entityDefinitionUpdateManager()->installEntityType(\Drupal::entityTypeManager()->getDefinition('rest_resource_config')); diff --git a/core/modules/rest/rest.post_update.php b/core/modules/rest/rest.post_update.php index 706866217cef887e53d7731440ce2c83a6fb91b6..ecbc73c4059386b8d9d61c8eb811a1082e480a8d 100644 --- a/core/modules/rest/rest.post_update.php +++ b/core/modules/rest/rest.post_update.php @@ -16,11 +16,8 @@ /** * Create REST resource configuration entities. * - * @todo https://www.drupal.org/node/2721595 Automatically upgrade those REST - * resource config entities that have the same formats/auth mechanisms for all - * methods to "granular: resource". - * * @see rest_update_8201() + * @see https://www.drupal.org/node/2308745 */ function rest_post_update_create_rest_resource_config_entities() { $resources = \Drupal::state()->get('rest_update_8201_resources', []); @@ -34,6 +31,43 @@ function rest_post_update_create_rest_resource_config_entities() { } } +/** + * Simplify method-granularity REST resource config to resource-granularity. + * + * @see https://www.drupal.org/node/2721595 + */ +function rest_post_update_resource_granularity() { + /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_config_entities */ + $resource_config_entities = RestResourceConfig::loadMultiple(); + + foreach ($resource_config_entities as $resource_config_entity) { + if ($resource_config_entity->get('granularity') === RestResourceConfigInterface::METHOD_GRANULARITY) { + $configuration = $resource_config_entity->get('configuration'); + + $format_and_auth_configuration = []; + foreach (array_keys($configuration) as $method) { + $format_and_auth_configuration['format'][$method] = implode(',', $configuration[$method]['supported_formats']); + $format_and_auth_configuration['auth'][$method] = implode(',', $configuration[$method]['supported_auth']); + } + + // If each method has the same formats and the same authentication + // providers configured, convert it to 'granularity: resource', which has + // a simpler/less verbose configuration. + if (count(array_unique($format_and_auth_configuration['format'])) === 1 && count(array_unique($format_and_auth_configuration['auth'])) === 1) { + $first_method = array_keys($configuration)[0]; + $resource_config_entity->set('configuration', [ + 'methods' => array_keys($configuration), + 'formats' => $configuration[$first_method]['supported_formats'], + 'authentication' => $configuration[$first_method]['supported_auth'] + ]); + $resource_config_entity->set('granularity', RestResourceConfigInterface::RESOURCE_GRANULARITY); + $resource_config_entity->save(); + } + } + } +} + + /** * @} End of "addtogroup updates-8.1.x-to-8.2.x". */ diff --git a/core/modules/rest/src/Entity/ConfigDependencies.php b/core/modules/rest/src/Entity/ConfigDependencies.php index 72fc8cc54d6b994cae85000b3bc40b6521e2fa28..3b39ccf1c58a2269543587964963b25718908e1b 100644 --- a/core/modules/rest/src/Entity/ConfigDependencies.php +++ b/core/modules/rest/src/Entity/ConfigDependencies.php @@ -61,31 +61,25 @@ public static function create(ContainerInterface $container) { */ public function calculateDependencies(RestResourceConfigInterface $rest_config) { $granularity = $rest_config->get('granularity'); - if ($granularity === RestResourceConfigInterface::METHOD_GRANULARITY) { - return $this->calculateDependenciesForMethodGranularity($rest_config); - } - else { - throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet."); - // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. + + // Dependency calculation is the same for either granularity, the most + // notable difference is that for the 'resource' granularity, the same + // authentication providers and formats are supported for every method. + switch ($granularity) { + case RestResourceConfigInterface::METHOD_GRANULARITY: + $methods = $rest_config->getMethods(); + break; + case RestResourceConfigInterface::RESOURCE_GRANULARITY: + $methods = array_slice($rest_config->getMethods(), 0, 1); + break; + default: + throw new \InvalidArgumentException('Invalid granularity specified.'); } - } - /** - * Calculates dependencies of a specific rest resource configuration. - * - * @param \Drupal\rest\RestResourceConfigInterface $rest_config - * The rest configuration. - * - * @return string[][] - * Dependencies keyed by dependency type. - * - * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() - */ - protected function calculateDependenciesForMethodGranularity(RestResourceConfigInterface $rest_config) { // The dependency lists for authentication providers and formats // generated on container build. $dependencies = []; - foreach (array_keys($rest_config->get('configuration')) as $request_method) { + foreach ($methods as $request_method) { // Add dependencies based on the supported authentication providers. foreach ($rest_config->getAuthenticationProviders($request_method) as $auth) { if (isset($this->authProviders[$auth])) { @@ -102,6 +96,10 @@ protected function calculateDependenciesForMethodGranularity(RestResourceConfigI } } + if (isset($dependencies['module'])) { + sort($dependencies['module']); + } + return $dependencies; } @@ -121,12 +119,13 @@ protected function calculateDependenciesForMethodGranularity(RestResourceConfigI */ public function onDependencyRemoval(RestResourceConfigInterface $rest_config, array $dependencies) { $granularity = $rest_config->get('granularity'); - if ($granularity === RestResourceConfigInterface::METHOD_GRANULARITY) { - return $this->onDependencyRemovalForMethodGranularity($rest_config, $dependencies); - } - else { - throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet."); - // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. + switch ($granularity) { + case RestResourceConfigInterface::METHOD_GRANULARITY: + return $this->onDependencyRemovalForMethodGranularity($rest_config, $dependencies); + case RestResourceConfigInterface::RESOURCE_GRANULARITY: + return $this->onDependencyRemovalForResourceGranularity($rest_config, $dependencies); + default: + throw new \InvalidArgumentException('Invalid granularity specified.'); } } @@ -183,7 +182,71 @@ protected function onDependencyRemovalForMethodGranularity(RestResourceConfigInt } } } - if (!empty($configuration_before != $configuration)) { + if ($configuration_before != $configuration && !empty($configuration)) { + $rest_config->set('configuration', $configuration); + // Only mark the dependencies problems as fixed if there is any + // configuration left. + $changed = TRUE; + } + } + // If the dependency problems are not marked as fixed at this point they + // should be related to the resource plugin and the config entity should + // be deleted. + return $changed; + } + + /** + * Informs the entity that entities it depends on will be deleted. + * + * @param \Drupal\rest\RestResourceConfigInterface $rest_config + * The rest configuration. + * @param array $dependencies + * An array of dependencies that will be deleted keyed by dependency type. + * Dependency types are, for example, entity, module and theme. + * + * @return bool + * TRUE if the entity has been changed as a result, FALSE if not. + */ + public function onDependencyRemovalForResourceGranularity(RestResourceConfigInterface $rest_config, array $dependencies) { + $changed = FALSE; + // Only module-related dependencies can be fixed. All other types of + // dependencies cannot, because they were not generated based on supported + // authentication providers or formats. + if (isset($dependencies['module'])) { + // Try to fix dependencies. + $removed_auth = array_keys(array_intersect($this->authProviders, $dependencies['module'])); + $removed_formats = array_keys(array_intersect($this->formatProviders, $dependencies['module'])); + $configuration_before = $configuration = $rest_config->get('configuration'); + if (!empty($removed_auth) || !empty($removed_formats)) { + // Try to fix dependency problems by removing affected + // authentication providers and formats. + foreach ($removed_formats as $format) { + if (in_array($format, $rest_config->getFormats('GET'))) { + $configuration['formats'] = array_diff($configuration['formats'], $removed_formats); + } + } + foreach ($removed_auth as $auth) { + if (in_array($auth, $rest_config->getAuthenticationProviders('GET'))) { + $configuration['authentication'] = array_diff($configuration['authentication'], $removed_auth); + } + } + if (empty($configuration['authentication'])) { + // Remove the key if there are no more authentication providers + // supported. + unset($configuration['authentication']); + } + if (empty($configuration['formats'])) { + // Remove the key if there are no more formats supported. + unset($configuration['formats']); + } + if (empty($configuration['authentication']) || empty($configuration['formats'])) { + // If there no longer are any supported authentication providers or + // formats, this REST resource can no longer function, and so we + // cannot fix this config entity to keep it working. + $configuration = []; + } + } + if ($configuration_before != $configuration && !empty($configuration)) { $rest_config->set('configuration', $configuration); // Only mark the dependencies problems as fixed if there is any // configuration left. diff --git a/core/modules/rest/src/Entity/RestResourceConfig.php b/core/modules/rest/src/Entity/RestResourceConfig.php index f2b8b0ee4a13de92536ab32105bbf3c93120d144..a31a1a4cf1f6aa605e3e11637d5d690aa0577662 100644 --- a/core/modules/rest/src/Entity/RestResourceConfig.php +++ b/core/modules/rest/src/Entity/RestResourceConfig.php @@ -45,7 +45,9 @@ class RestResourceConfig extends ConfigEntityBase implements RestResourceConfigI /** * The REST resource configuration granularity. * - * @todo Currently only 'method', but https://www.drupal.org/node/2721595 will add 'resource' + * Currently either: + * - \Drupal\rest\RestResourceConfigInterface::METHOD_GRANULARITY + * - \Drupal\rest\RestResourceConfigInterface::RESOURCE_GRANULARITY * * @var string */ @@ -112,12 +114,13 @@ public function getResourcePlugin() { * {@inheritdoc} */ public function getMethods() { - if ($this->granularity === RestResourceConfigInterface::METHOD_GRANULARITY) { - return $this->getMethodsForMethodGranularity(); - } - else { - throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet."); - // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. + switch ($this->granularity) { + case RestResourceConfigInterface::METHOD_GRANULARITY: + return $this->getMethodsForMethodGranularity(); + case RestResourceConfigInterface::RESOURCE_GRANULARITY: + return $this->configuration['methods']; + default: + throw new \InvalidArgumentException('Invalid granularity specified.'); } } @@ -136,12 +139,13 @@ protected function getMethodsForMethodGranularity() { * {@inheritdoc} */ public function getAuthenticationProviders($method) { - if ($this->granularity === RestResourceConfigInterface::METHOD_GRANULARITY) { - return $this->getAuthenticationProvidersForMethodGranularity($method); - } - else { - throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet."); - // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. + switch ($this->granularity) { + case RestResourceConfigInterface::METHOD_GRANULARITY: + return $this->getAuthenticationProvidersForMethodGranularity($method); + case RestResourceConfigInterface::RESOURCE_GRANULARITY: + return $this->configuration['authentication']; + default: + throw new \InvalidArgumentException('Invalid granularity specified.'); } } @@ -166,12 +170,13 @@ public function getAuthenticationProvidersForMethodGranularity($method) { * {@inheritdoc} */ public function getFormats($method) { - if ($this->granularity === RestResourceConfigInterface::METHOD_GRANULARITY) { - return $this->getFormatsForMethodGranularity($method); - } - else { - throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet."); - // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. + switch ($this->granularity) { + case RestResourceConfigInterface::METHOD_GRANULARITY: + return $this->getFormatsForMethodGranularity($method); + case RestResourceConfigInterface::RESOURCE_GRANULARITY: + return $this->configuration['formats']; + default: + throw new \InvalidArgumentException('Invalid granularity specified.'); } } diff --git a/core/modules/rest/src/RestResourceConfigInterface.php b/core/modules/rest/src/RestResourceConfigInterface.php index 7f34bc43f55f6c82084337773889523326413f06..601bb0edc9ef37b7396e2eb6e0cbdad2665d4ae1 100644 --- a/core/modules/rest/src/RestResourceConfigInterface.php +++ b/core/modules/rest/src/RestResourceConfigInterface.php @@ -15,6 +15,11 @@ interface RestResourceConfigInterface extends ConfigEntityInterface, EntityWithP */ const METHOD_GRANULARITY = 'method'; + /** + * Granularity value for per-resource configuration. + */ + const RESOURCE_GRANULARITY = 'resource'; + /** * Retrieves the REST resource plugin. * diff --git a/core/modules/rest/src/Tests/Update/ResourceGranularityUpdateTest.php b/core/modules/rest/src/Tests/Update/ResourceGranularityUpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c8cf89afcc74c8c40aa94a7166e61789c7aaaca3 --- /dev/null +++ b/core/modules/rest/src/Tests/Update/ResourceGranularityUpdateTest.php @@ -0,0 +1,71 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php', + ]; + } + + /** + * Tests rest_post_update_simplify_resource_granularity(). + */ + public function testMethodGranularityConvertedToResourceGranularity() { + /** @var \Drupal\Core\Entity\EntityStorageInterface $resource_config_storage */ + $resource_config_storage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config'); + + // Make sure we have the expected values before the update. + $resource_config_entities = $resource_config_storage->loadMultiple(); + $this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities)); + $this->assertIdentical('method', $resource_config_entities['entity.node']->get('granularity')); + $this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity')); + $this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity')); + + // Read the existing 'entity:comment' and 'entity:user' resource + // configuration so we can verify it after the update. + $comment_resource_configuration = $resource_config_entities['entity.comment']->get('configuration'); + $user_resource_configuration = $resource_config_entities['entity.user']->get('configuration'); + + $this->runUpdates(); + + // Make sure we have the expected values after the update. + $resource_config_entities = $resource_config_storage->loadMultiple(); + $this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities)); + // 'entity:node' should be updated. + $this->assertIdentical('resource', $resource_config_entities['entity.node']->get('granularity')); + $this->assertidentical($resource_config_entities['entity.node']->get('configuration'), [ + 'methods' => ['GET', 'POST', 'PATCH', 'DELETE'], + 'formats' => ['hal_json'], + 'authentication' => ['basic_auth'], + ]); + // 'entity:comment' should be unchanged. + $this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity')); + $this->assertIdentical($comment_resource_configuration, $resource_config_entities['entity.comment']->get('configuration')); + // 'entity:user' should be unchanged. + $this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity')); + $this->assertIdentical($user_resource_configuration, $resource_config_entities['entity.user']->get('configuration')); + } + +} diff --git a/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php b/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php index 9100f1697402624e918f41fe3009517afd4510a0..097da35bb1bd13ebe39a4f81da5f7376d7ed4892 100644 --- a/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php +++ b/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php @@ -9,6 +9,8 @@ * Tests that rest.settings is converted to rest_resource_config entities. * * @see https://www.drupal.org/node/2308745 + * @see rest_update_8201() + * @see rest_post_update_create_rest_resource_config_entities() * * @group rest */ @@ -43,10 +45,6 @@ public function testResourcesConvertedToConfigEntities() { $resource_config_entities = $resource_config_storage->loadMultiple(); $this->assertIdentical([], array_keys($resource_config_entities)); - // Read the existing 'entity:node' resource configuration so we can verify - // it after the update. - $node_configuration = $rest_settings->getRawData()['resources']['entity:node']; - $this->runUpdates(); // Make sure we have the expected values after the update. @@ -55,8 +53,12 @@ public function testResourcesConvertedToConfigEntities() { $resource_config_entities = $resource_config_storage->loadMultiple(); $this->assertIdentical(['entity.node'], array_keys($resource_config_entities)); $node_resource_config_entity = $resource_config_entities['entity.node']; - $this->assertIdentical(RestResourceConfigInterface::METHOD_GRANULARITY, $node_resource_config_entity->get('granularity')); - $this->assertIdentical($node_configuration, $node_resource_config_entity->get('configuration')); + $this->assertIdentical(RestResourceConfigInterface::RESOURCE_GRANULARITY, $node_resource_config_entity->get('granularity')); + $this->assertIdentical([ + 'methods' => ['GET'], + 'formats' => ['json'], + 'authentication' => ['basic_auth'], + ], $node_resource_config_entity->get('configuration')); $this->assertIdentical(['module' => ['basic_auth', 'node', 'serialization']], $node_resource_config_entity->getDependencies()); } diff --git a/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php b/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php new file mode 100644 index 0000000000000000000000000000000000000000..d657afcf9c13b177c952c1f144825d7b6a007ee3 Binary files /dev/null and b/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php differ diff --git a/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml b/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e86bab477f87c9be65d0924cb97a7ae8f955472 --- /dev/null +++ b/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml @@ -0,0 +1,32 @@ +id: entity.comment +plugin_id: 'entity:comment' +granularity: method +configuration: + GET: + supported_formats: + - hal_json + # This resource has a method-specific format. + # @see \Drupal\rest\Tests\Update\ResourceGranularityUpdateTest + - xml + supported_auth: + - basic_auth + POST: + supported_formats: + - hal_json + supported_auth: + - basic_auth + PATCH: + supported_formats: + - hal_json + supported_auth: + - basic_auth + DELETE: + supported_formats: + - hal_json + supported_auth: + - basic_auth +dependencies: + module: + - node + - basic_auth + - hal diff --git a/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml b/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml new file mode 100644 index 0000000000000000000000000000000000000000..0cf4d789894870110d5694377eb09f6b4f2c0aac --- /dev/null +++ b/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml @@ -0,0 +1,29 @@ +id: entity.node +plugin_id: 'entity:node' +granularity: method +configuration: + GET: + supported_formats: + - hal_json + supported_auth: + - basic_auth + POST: + supported_formats: + - hal_json + supported_auth: + - basic_auth + PATCH: + supported_formats: + - hal_json + supported_auth: + - basic_auth + DELETE: + supported_formats: + - hal_json + supported_auth: + - basic_auth +dependencies: + module: + - node + - basic_auth + - hal diff --git a/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml b/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml new file mode 100644 index 0000000000000000000000000000000000000000..593a260b681f51987e8a35f849c66dc42beca2e2 --- /dev/null +++ b/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml @@ -0,0 +1,32 @@ +id: entity.user +plugin_id: 'entity:user' +granularity: method +configuration: + GET: + supported_formats: + - hal_json + supported_auth: + - basic_auth + # This resource has a method-specific authentication. + # @see \Drupal\rest\Tests\Update\ResourceGranularityUpdateTest + - oauth + POST: + supported_formats: + - hal_json + supported_auth: + - basic_auth + PATCH: + supported_formats: + - hal_json + supported_auth: + - basic_auth + DELETE: + supported_formats: + - hal_json + supported_auth: + - basic_auth +dependencies: + module: + - node + - basic_auth + - hal diff --git a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php index b9a9062a42ea334c8ca06c3390763b2e8e242792..86e80f2df7ad67463a6f79ce6703806f41e13517 100644 --- a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php +++ b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php @@ -21,72 +21,78 @@ class ConfigDependenciesTest extends KernelTestBase { /** * @covers ::calculateDependencies - * @covers ::calculateDependenciesForMethodGranularity + * + * @dataProvider providerBasicDependencies */ - public function testCalculateDependencies() { + public function testCalculateDependencies(array $configuration) { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); - $rest_config = RestResourceConfig::create([ - '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 = RestResourceConfig::create($configuration); $result = $config_dependencies->calculateDependencies($rest_config); $this->assertEquals(['module' => [ - 'serialization', 'basic_auth', 'hal', + 'basic_auth', 'hal', 'serialization', ]], $result); } /** * @covers ::onDependencyRemoval - * @covers ::calculateDependenciesForMethodGranularity + * @covers ::onDependencyRemovalForMethodGranularity + * @covers ::onDependencyRemovalForResourceGranularity + * + * @dataProvider providerBasicDependencies */ - public function testOnDependencyRemovalRemoveUnrelatedDependency() { + public function testOnDependencyRemovalRemoveUnrelatedDependency(array $configuration) { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); - $rest_config = RestResourceConfig::create([ - '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 = RestResourceConfig::create($configuration); $this->assertFalse($config_dependencies->onDependencyRemoval($rest_config, ['module' => ['node']])); - $this->assertEquals([ - 'GET' => [ - 'supported_auth' => ['cookie'], - 'supported_formats' => ['json'], + $this->assertEquals($configuration['configuration'], $rest_config->get('configuration')); + } + + /** + * @return array + * An array with numerical keys: + * 0. The original REST resource configuration. + */ + public function providerBasicDependencies() { + return [ + 'method' => [ + [ + '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'], + ], + ], + ], ], - 'POST' => [ - 'supported_auth' => ['basic_auth'], - 'supported_formats' => ['hal_json'], + 'resource' => [ + [ + 'plugin_id' => 'entity:entity_test', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['GET', 'POST'], + 'formats' => ['json', 'hal_json'], + 'authentication' => ['cookie', 'basic_auth'], + ], + ], ], - ], $rest_config->get('configuration')); + ]; } /** * @covers ::onDependencyRemoval - * @covers ::calculateDependenciesForMethodGranularity + * @covers ::onDependencyRemovalForMethodGranularity */ - public function testOnDependencyRemovalRemoveFormat() { + public function testOnDependencyRemovalRemoveFormatForMethodGranularity() { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); $rest_config = RestResourceConfig::create([ @@ -120,7 +126,7 @@ public function testOnDependencyRemovalRemoveFormat() { /** * @covers ::onDependencyRemoval - * @covers ::calculateDependenciesForMethodGranularity + * @covers ::onDependencyRemovalForMethodGranularity */ public function testOnDependencyRemovalRemoveAuth() { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); @@ -156,7 +162,7 @@ public function testOnDependencyRemovalRemoveAuth() { /** * @covers ::onDependencyRemoval - * @covers ::calculateDependenciesForMethodGranularity + * @covers ::onDependencyRemovalForMethodGranularity */ public function testOnDependencyRemovalRemoveAuthAndFormats() { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); @@ -189,4 +195,95 @@ public function testOnDependencyRemovalRemoveAuthAndFormats() { ], $rest_config->get('configuration')); } + /** + * @covers ::onDependencyRemoval + * @covers ::onDependencyRemovalForResourceGranularity + * + * @dataProvider providerOnDependencyRemovalForResourceGranularity + */ + public function testOnDependencyRemovalForResourceGranularity(array $configuration, $module, $expected_configuration) { + assert('is_string($module)'); + assert('$expected_configuration === FALSE || is_array($expected_configuration)'); + $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); + + $rest_config = RestResourceConfig::create($configuration); + + $this->assertSame(!empty($expected_configuration), $config_dependencies->onDependencyRemoval($rest_config, ['module' => [$module]])); + if (!empty($expected_configuration)) { + $this->assertEquals($expected_configuration, $rest_config->get('configuration')); + } + } + + /** + * @return array + * An array with numerical keys: + * 0. The original REST resource configuration. + * 1. The module to uninstall (the dependency that is about to be removed). + * 2. The expected configuration after uninstalling this module. + */ + public function providerOnDependencyRemovalForResourceGranularity() { + return [ + 'resource with multiple formats' => [ + [ + 'plugin_id' => 'entity:entity_test', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['GET', 'POST'], + 'formats' => ['json', 'hal_json'], + 'authentication' => ['cookie', 'basic_auth'], + ], + ], + 'hal', + [ + 'methods' => ['GET', 'POST'], + 'formats' => ['json'], + 'authentication' => ['cookie', 'basic_auth'], + ] + ], + 'resource with only HAL+JSON format' => [ + [ + 'plugin_id' => 'entity:entity_test', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['GET', 'POST'], + 'formats' => ['hal_json'], + 'authentication' => ['cookie', 'basic_auth'], + ], + ], + 'hal', + FALSE + ], + 'resource with multiple authentication providers' => [ + [ + 'plugin_id' => 'entity:entity_test', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['GET', 'POST'], + 'formats' => ['json', 'hal_json'], + 'authentication' => ['cookie', 'basic_auth'], + ], + ], + 'basic_auth', + [ + 'methods' => ['GET', 'POST'], + 'formats' => ['json', 'hal_json'], + 'authentication' => ['cookie'], + ] + ], + 'resource with only basic_auth authentication' => [ + [ + 'plugin_id' => 'entity:entity_test', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['GET', 'POST'], + 'formats' => ['json', 'hal_json'], + 'authentication' => ['basic_auth'], + ], + ], + 'basic_auth', + FALSE, + ], + ]; + } + } diff --git a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php index d0d22a27ea15db9637fa537b36874495a2f810b0..d2f8f5fbd51e08e2dceeb56365a6e97dddc935b4 100644 --- a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php +++ b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php @@ -53,13 +53,18 @@ function testGeneratorHeaderAdded() { $this->assertEqual('text/html; charset=UTF-8', $this->drupalGetHeader('Content-Type')); $this->assertEqual($expectedGeneratorHeader, $this->drupalGetHeader('X-Generator')); - // Enable rest API for nodes - $this->enableService('entity:node', 'GET', 'json'); + // Enable cookie-based authentication for the entity:node REST resource. + /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */ + $resource_config = $this->resourceConfigStorage->load('entity.node'); + $configuration = $resource_config->get('configuration'); + $configuration['authentication'][] = 'cookie'; + $resource_config->set('configuration', $configuration)->save(); + $this->rebuildCache(); // Tests to see if this also works for a non-html request - $this->httpRequest($node->urlInfo()->setOption('query', ['_format' => 'json']), 'GET'); + $this->httpRequest($node->urlInfo()->setOption('query', ['_format' => 'hal_json']), 'GET'); $this->assertResponse(200); - $this->assertEqual('application/json', $this->drupalGetHeader('Content-Type')); + $this->assertEqual('application/hal+json', $this->drupalGetHeader('Content-Type')); $this->assertEqual($expectedGeneratorHeader, $this->drupalGetHeader('X-Generator')); }