core/modules/rest/config/schema/rest.schema.yml | 1 + .../modules/rest/src/Entity/ConfigDependencies.php | 66 ++++---- .../rest/src/RestResourceConfigInterface.php | 5 + .../src/Kernel/Entity/ConfigDependenciesTest.php | 182 ++++++++++++++++----- 4 files changed, 178 insertions(+), 76 deletions(-) diff --git a/core/modules/rest/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml index 080c9c5..04f88a6 100644 --- a/core/modules/rest/config/schema/rest.schema.yml +++ b/core/modules/rest/config/schema/rest.schema.yml @@ -24,6 +24,7 @@ rest_resource.method: type: rest_request label: 'DELETE method settings' +# Resource-level granularity of REST resource configuration. rest_resource.resource: type: mapping mapping: diff --git a/core/modules/rest/src/Entity/ConfigDependencies.php b/core/modules/rest/src/Entity/ConfigDependencies.php index 5629141..5d5bb63 100644 --- a/core/modules/rest/src/Entity/ConfigDependencies.php +++ b/core/modules/rest/src/Entity/ConfigDependencies.php @@ -92,6 +92,8 @@ public function calculateDependencies(RestResourceConfigInterface $rest_config) } } + sort($dependencies['module']); + return $dependencies; } @@ -172,7 +174,7 @@ 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. @@ -204,50 +206,42 @@ public function onDependencyRemovalForResourceGranularity(RestResourceConfigInte // authentication providers or formats. if (isset($dependencies['module'])) { // Try to fix dependencies. - $removed_auth = []; - $removed_formats = []; - foreach ($dependencies['module'] as $dep_module) { - // Check if the removed dependency module contained an authentication - // provider. - foreach ($this->authProviders as $auth => $auth_module) { - if ($dep_module != $auth_module) { - continue; - } - $removed_auth[] = $auth; - } - // Check if the removed dependency module contained a format. - foreach ($this->formatProviders as $format => $format_module) { - if ($dep_module != $format_module) { - continue; - } - $removed_formats[] = $format; - } - } - $configuration = $rest_config->get('configuration'); + $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, $configuration['formats'])) { - $configuration = array_filter($configuration['formats'], function ($val) use ($format) { - return $val !== $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, $configuration['authentication'])) { - $configuration = array_filter($configuration['authentication'], function ($val) use ($auth) { - return $val !== $auth; - }); + if (in_array($auth, $rest_config->getAuthenticationProviders('GET'))) { + $configuration['authentication'] = array_diff($configuration['authentication'], $removed_auth); } } - // Only mark the dependencies problems as fixed if there is still >=1 - // format available and >=1 authentication provider available. - if (!empty($configuration['formats']) && !empty($configuration['authentication'])) { - $changed = TRUE; + 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 = []; } } - else { - // Dependencies were removed, but they did not affect the formats or - // authentication providers used by this entity. + 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; } } diff --git a/core/modules/rest/src/RestResourceConfigInterface.php b/core/modules/rest/src/RestResourceConfigInterface.php index 7f34bc4..0792634 100644 --- a/core/modules/rest/src/RestResourceConfigInterface.php +++ b/core/modules/rest/src/RestResourceConfigInterface.php @@ -15,6 +15,11 @@ */ const METHOD_GRANULARITY = 'method'; + /** + * Granularity value for per-resourceconfiguration. + */ + const RESOURCE_GRANULARITY = 'resource'; + /** * Retrieves the REST resource plugin. * diff --git a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php index cd2d28b..4743a03 100644 --- a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php +++ b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php @@ -21,69 +21,78 @@ class ConfigDependenciesTest extends KernelTestBase { /** * @covers ::calculateDependencies + * + * @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 ::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 ::onDependencyRemovalForMethodGranularity */ - public function testOnDependencyRemovalRemoveFormat() { + public function testOnDependencyRemovalRemoveFormatForMethodGranularity() { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); $rest_config = RestResourceConfig::create([ @@ -117,6 +126,7 @@ public function testOnDependencyRemovalRemoveFormat() { /** * @covers ::onDependencyRemoval + * @covers ::onDependencyRemovalForMethodGranularity */ public function testOnDependencyRemovalRemoveAuth() { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); @@ -152,6 +162,7 @@ public function testOnDependencyRemovalRemoveAuth() { /** * @covers ::onDependencyRemoval + * @covers ::onDependencyRemovalForMethodGranularity */ public function testOnDependencyRemovalRemoveAuthAndFormats() { $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']); @@ -184,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, + ], + ]; + } + }