.../config/optional/rest.resource.entity__node.yml | 2 +- core/modules/rest/config/schema/rest.schema.yml | 15 ++-- core/modules/rest/rest.install | 3 +- .../modules/rest/src/Entity/ConfigDependencies.php | 22 +++-- .../modules/rest/src/Entity/RestResourceConfig.php | 95 ++++++++-------------- core/modules/rest/src/RequestHandler.php | 2 +- .../rest/src/RestResourceConfigInterface.php | 80 +++--------------- core/modules/rest/src/Routing/ResourceRoutes.php | 10 +-- core/modules/rest/src/Tests/RESTTestBase.php | 11 ++- core/modules/rest/src/Tests/ResourceTest.php | 36 ++++---- 10 files changed, 101 insertions(+), 175 deletions(-) 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 8125fa3..c7da93f 100644 --- a/core/modules/rest/config/optional/rest.resource.entity__node.yml +++ b/core/modules/rest/config/optional/rest.resource.entity__node.yml @@ -1,7 +1,7 @@ id: entity__node plugin_id: 'entity:node' +granularity: method configuration: - granularity: method GET: supported_formats: - hal_json diff --git a/core/modules/rest/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml index a7cf07d..41bc2bf 100644 --- a/core/modules/rest/config/schema/rest.schema.yml +++ b/core/modules/rest/config/schema/rest.schema.yml @@ -7,18 +7,10 @@ rest.settings: type: string label: 'Domain of the relation' -# The base type for REST resource configuration. -rest_resource_base: - type: mapping - mapping: - granularity: - type: string - label: 'Resource configuration granularity' - # Method-level granularity of REST resource configuration. # @todo Add resource-level granularity in https://www.drupal.org/node/2721595. rest_resource.method: - type: rest_resource_base + type: mapping mapping: GET: type: rest_request @@ -59,6 +51,9 @@ rest.resource.*: plugin_id: type: string label: 'REST resource plugin id' + granularity: + type: string + label: 'REST resource configuration granularity' configuration: - type: rest_resource.[granularity] + type: rest_resource.[%parent.granularity] label: 'REST resource configuration' diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install index e0597f9..ac4ce9c 100644 --- a/core/modules/rest/rest.install +++ b/core/modules/rest/rest.install @@ -34,7 +34,8 @@ function rest_update_8100() { foreach (\Drupal::config('rest.settings')->get('resources') as $key => $resource) { $resource = RestResourceConfig::create([ 'id' => str_replace(':', '__', $key), - 'configuration' => ['granularity' => 'method'] + $resource, + 'granularity' => 'method', + 'configuration' => $resource, ]); $resource->save(); } diff --git a/core/modules/rest/src/Entity/ConfigDependencies.php b/core/modules/rest/src/Entity/ConfigDependencies.php index ee9a2e5..5475d0d 100644 --- a/core/modules/rest/src/Entity/ConfigDependencies.php +++ b/core/modules/rest/src/Entity/ConfigDependencies.php @@ -49,7 +49,7 @@ public function __construct(array $available_rest_formats, array $available_rest * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() */ public function calculateDependencies(RestResourceConfigInterface $rest_config) { - $granularity = $rest_config->get('configuration')['granularity']; + $granularity = $rest_config->get('granularity'); if ($granularity === 'method') { return $this->calculateDependenciesForMethodGranularity($rest_config); } @@ -80,14 +80,14 @@ protected function calculateDependenciesForMethodGranularity(RestResourceConfigI } // Add dependencies based on the supported authentication providers. - foreach ($rest_config->getSupportedAuthenticationProviders($request_method) as $auth) { + foreach ($rest_config->getAuthenticationProviders($request_method) as $auth) { if (isset($this->availableRestAuthentications[$auth])) { $module_name = $this->availableRestAuthentications[$auth]; $dependencies['module'][] = $module_name; } } // Add dependencies based on the supported authentication formats. - foreach ($rest_config->getSupportedFormats($request_method) as $format) { + foreach ($rest_config->getFormats($request_method) as $format) { if (isset($this->availableRestFormats[$format])) { $module_name = $this->availableRestFormats[$format]; $dependencies['module'][] = $module_name; @@ -148,21 +148,25 @@ public function onDependencyRemoval(RestResourceConfigInterface $rest_config, ar // authentication providers and formats. foreach (array_keys($rest_config->get('configuration')) as $request_method) { foreach ($removed_formats as $format) { - if (in_array($format, $rest_config->getSupportedFormats($request_method))) { - $rest_config->removeSupportedFormat($request_method, $format); + if (in_array($format, $rest_config->getFormats($request_method))) { + $rest_config = array_filter($rest_config[$request_method]['supported_formats'], function ($val) use ($format) { + return ($val != $format); + }); } } foreach ($removed_auth as $auth) { - if (in_array($auth, $rest_config->getSupportedAuthenticationProviders($request_method))) { - $rest_config->removeSupportedAuthenticationProvider($request_method, $auth); + if (in_array($auth, $rest_config->getAuthenticationProviders($request_method))) { + $rest_config = array_filter($rest_config[$request_method]['supported_auth'], function ($val) use ($auth) { + return ($val != $auth); + }); } } - if (empty($rest_config->getSupportedAuthenticationProviders($request_method)) ){ + if (empty($rest_config->getAuthenticationProviders($request_method)) ){ // Remove the key if there are no more authentication providers // supported by this request method. unset($configuration[$request_method]['supported_auth']); } - if (empty($rest_config->getSupportedFormats($request_method))) { + if (empty($rest_config->getFormats($request_method))) { // Remove the key if there are no more formats supported by this // request method. unset($configuration[$request_method]['supported_formats']); diff --git a/core/modules/rest/src/Entity/RestResourceConfig.php b/core/modules/rest/src/Entity/RestResourceConfig.php index 362c061..dde5d2c 100644 --- a/core/modules/rest/src/Entity/RestResourceConfig.php +++ b/core/modules/rest/src/Entity/RestResourceConfig.php @@ -22,6 +22,7 @@ * config_export = { * "id", * "plugin_id", + * "granularity", * "configuration" * } * ) @@ -43,6 +44,15 @@ class RestResourceConfig extends ConfigEntityBase implements RestResourceConfigI protected $plugin_id; /** + * The REST resource configuration granularity. + * + * @todo Currently only 'method', but https://www.drupal.org/node/2721595 will add 'resource' + * + * @var string + */ + protected $granularity; + + /** * The REST resource configuration. * * @var array @@ -106,96 +116,61 @@ public function getResourcePlugin() { /** * {@inheritdoc} */ - public function isRequestMethodEnabled($method) { - $method = $this->normalizeRestMethod($method); - return isset($this->configuration[$method]); + public function getMethods() { + if ($this->granularity === 'method') { + return $this->getMethodsForMethodGranularity(); + } + else { + // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. + } } /** - * {@inheritdoc} + * Retrieves a list of supported HTTP methods for this resource. + * + * @return string[] + * A list of supported HTTP methods. */ - public function getSupportedAuthenticationProviders($method) { - $method = $this->normalizeRestMethod($method); - if ($this->isRequestMethodEnabled($method) && isset($this->configuration[$method]['supported_auth'])) { - return $this->configuration[$method]['supported_auth']; - } - return []; + protected function getMethodsForMethodGranularity() { + $methods = array_keys($this->configuration); + return array_map([$this, 'normalizeRestMethod'], $methods); } /** * {@inheritdoc} */ - public function addSupportedAuthenticationProvider($method, $auth) { - $method = $this->normalizeRestMethod($method); - if (!$this->isRequestMethodEnabled($method)) { - $this->configuration[$method] = ['supported_auth' => []]; + public function getAuthenticationProviders($method) { + if ($this->granularity === 'method') { + return $this->getAuthenticationProvidersForMethodGranularity($method); } - if (!isset($this->configuration[$method]['supported_auth'])){ - $this->configuration[$method]['supported_auth'] = []; + else { + // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595. } - if (!in_array($auth, $this->configuration[$method]['supported_auth'])) { - $this->configuration[$method]['supported_auth'][] = $auth; - } - return $this; } /** * {@inheritdoc} */ - public function removeSupportedAuthenticationProvider($method, $auth) { + public function getAuthenticationProvidersForMethodGranularity($method) { $method = $this->normalizeRestMethod($method); - if ($this->getSupportedAuthenticationProviders($method)) { - $new_auth = array_filter($this->configuration[$method]['supported_auth'], function ($val) use ($auth) { - return ($val != $auth); - }); - $this->configuration[$method]['supported_auth'] = $new_auth; + if (in_array($method, $this->getMethods()) && isset($this->configuration[$method]['supported_auth'])) { + return $this->configuration[$method]['supported_auth']; } - return $this; + return []; } /** * {@inheritdoc} */ - public function getSupportedFormats($method) { + public function getFormats($method) { $method = $this->normalizeRestMethod($method); - if ($this->isRequestMethodEnabled($method) && isset($this->configuration[$method]['supported_formats'])) { + if (in_array($method, $this->getMethods()) && isset($this->configuration[$method]['supported_formats'])) { return $this->configuration[$method]['supported_formats']; } return []; } /** - * {@inheritdoc} - */ - public function addSupportedFormat($method, $format) { - $method = $this->normalizeRestMethod($method); - if (!$this->isRequestMethodEnabled($method)) { - $this->configuration[$method] = ['supported_formats' => []]; - } - if (!isset($this->configuration[$method]['supported_formats'])){ - $this->configuration[$method]['supported_formats'] = []; - } - if (!in_array($format, $this->configuration[$method]['supported_formats'])) { - $this->configuration[$method]['supported_formats'][] = $format; - } - return $this; - } - - /** - * {@inheritdoc} - */ - public function removeSupportedFormat($method, $format) { - $method = $this->normalizeRestMethod($method); - if ($this->getSupportedFormats($method)) { - $new_auth = array_filter($this->configuration[$method]['supported_formats'], function ($val) use ($format) { - return ($val != $format); - }); - $this->configuration[$method]['supported_formats'] = $new_auth; - } - return $this; - } - - /** * Returns the plugin collections used by this entity. * * @return \Drupal\Component\Plugin\LazyPluginCollection[] diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index 54b0ded..212ae06 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -79,7 +79,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) { // format. If the serializer cannot handle it an exception will be thrown // that bubbles up to the client. $request_method = $request->getMethod(); - if (in_array($format, $resource_config->getSupportedFormats($request_method))) { + if (in_array($format, $resource_config->getFormats($request_method))) { $definition = $resource->getPluginDefinition(); $class = $definition['serialization_class']; try { diff --git a/core/modules/rest/src/RestResourceConfigInterface.php b/core/modules/rest/src/RestResourceConfigInterface.php index 296a1e6..eac95ac 100644 --- a/core/modules/rest/src/RestResourceConfigInterface.php +++ b/core/modules/rest/src/RestResourceConfigInterface.php @@ -19,93 +19,33 @@ public function getResourcePlugin(); /** - * Denotes whether a given request method is enabled for this resource. - * - * @param string $method - * The request method. - * @return bool - */ - public function isRequestMethodEnabled($method); - - /** - * Retrieves a list of supported authentication mechanisms - * for a specific request method. - * - * @param string $method - * The request method e.g GET or POST. + * Retrieves a list of supported HTTP methods. * * @return string[] - * An array of supported authentication provider plugin id's - */ - public function getSupportedAuthenticationProviders($method); - - /** - * Add the specified authentication provider to - * the list of supported authentication providers - * for the given request method. - * - * @param string $method - * The request method e.g GET or POST. - * @param string $auth - * An authentication provider plugin id. - * - * @return $this - */ - public function addSupportedAuthenticationProvider($method, $auth); - - /** - * Remove the specified authentication provider from - * the list of supported authentication providers - * for the given request method. - * - * @param string $method - * The request method e.g GET or POST. - * @param string $auth - * An authentication provider plugin id. - * - * @return $this + * A list of supported HTTP methods. */ - public function removeSupportedAuthenticationProvider($method, $auth); + public function getMethods(); /** - * Retrieves a list of supported formats for a specific request method. + * Retrieves a list of supported authentication providers. * * @param string $method * The request method e.g GET or POST. * * @return string[] - * An array of supported format plugin id's. - */ - public function getSupportedFormats($method); - - /** - * Adds the specified format. - * - * This format is added to the list of supported authentication providers for - * the given request method. - * - * @param string $method - * The request method e.g GET or POST. - * @param string $format - * A format plugin id. - * - * @return $this + * A list of supported authentication provider IDs. */ - public function addSupportedFormat($method, $format); + public function getAuthenticationProviders($method); /** - * Removes the specified format from. - * - * This format is removed from the list of supported authentication providers - * for the given request method. + * Retrieves a list of supported response formats. * * @param string $method * The request method e.g GET or POST. - * @param string $format - * A format plugin id. * - * @return $this + * @return string[] + * A list of supported format IDs. */ - public function removeSupportedFormat($method, $format); + public function getFormats($method); } diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php index ff119eb..5d3ae30 100644 --- a/core/modules/rest/src/Routing/ResourceRoutes.php +++ b/core/modules/rest/src/Routing/ResourceRoutes.php @@ -90,17 +90,17 @@ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_ // @todo: Are multiple methods possible here? $methods = $route->getMethods(); // Only expose routes where the method is enabled in the configuration. - if ($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getSupportedFormats($method)) { + if ($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) { $route->setRequirement('_access_rest_csrf', 'TRUE'); // Check that authentication providers are defined. - if (empty($rest_resource_config->getSupportedAuthenticationProviders($method))) { + if (empty($rest_resource_config->getAuthenticationProviders($method))) { $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $rest_resource_config->id())); continue; } // Check that formats are defined. - if (empty($rest_resource_config->getSupportedFormats($method))) { + if (empty($rest_resource_config->getFormats($method))) { $this->logger->error('At least one format must be defined for resource @id', array(':id' => $rest_resource_config->id())); continue; } @@ -108,13 +108,13 @@ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_ // If the route has a format requirement, then verify that the // resource has it. $format_requirement = $route->getRequirement('_format'); - if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getSupportedFormats($method))) { + if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getFormats($method))) { continue; } // The configuration seems legit at this point, so we set the // authentication provider and add the route. - $route->setOption('_auth', $rest_resource_config->getSupportedAuthenticationProviders($method)); + $route->setOption('_auth', $rest_resource_config->getAuthenticationProviders($method)); $route->setDefault('_rest_resource_config', $rest_resource_config->id()); $collection->add("rest.$name", $route); } diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index 8969a6a..ff18ba6 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -272,26 +272,29 @@ protected function enableService($resource_type, $method = 'GET', $format = NULL // get entity by id /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */ $resource_config = $this->resourceConfigStorage->load($resource_config_id); - $resource_config = $resource_config ?: $this->resourceConfigStorage->create(['id' => $resource_config_id, 'configuration' => ['granularity' => 'method']]); + $resource_config = $resource_config ?: $this->resourceConfigStorage->create(['id' => $resource_config_id, 'granularity' => 'method', 'configuration' => []]); + $configuration = $resource_config->get('configuration'); if (is_array($format)) { for ($i = 0; $i < count($format); $i++) { - $resource_config->addSupportedFormat($method, $format[$i]); + $configuration[$method]['supported_formats'][] = $format[$i]; } } else { if ($format == NULL) { $format = $this->defaultFormat; } - $resource_config->addSupportedFormat($method, $format); + $configuration[$method]['supported_formats'][] = $format; } if (!is_array($auth) || empty($auth)) { $auth = $this->defaultAuth; } foreach ($auth as $auth_provider) { - $resource_config->addSupportedAuthenticationProvider($method, $auth_provider); + $configuration[$method]['supported_auth'][] = $auth_provider; } + + $resource_config->set('configuration', $configuration); $resource_config->save(); } else { diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php index 1ab3970..509ca62 100644 --- a/core/modules/rest/src/Tests/ResourceTest.php +++ b/core/modules/rest/src/Tests/ResourceTest.php @@ -44,13 +44,17 @@ protected function setUp() { * Tests that a resource without formats cannot be enabled. */ public function testFormats() { - /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */ - $resource_config = $this->resourceConfigStorage->create(['id' => 'entity__entity_test', 'configuration' => ['granularity' => 'method']]); - // Attempt to enable the resource. - $resource_config - ->addSupportedAuthenticationProvider('GET', 'basic_auth') - ->save(); - $this->rebuildCache(); + $this->resourceConfigStorage->create([ + 'id' => 'entity__entity_test', + 'granularity' => 'method', + 'configuration' => [ + 'GET' => [ + 'supported_auth' => [ + 'basic_auth', + ], + ], + ], + ])->save(); // Verify that accessing the resource returns 406. $response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET'); @@ -67,13 +71,17 @@ public function testFormats() { * Tests that a resource without authentication cannot be enabled. */ public function testAuthentication() { - /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */ - $resource_config = $this->resourceConfigStorage->create(['id' => 'entity__entity_test', 'configuration' => ['granularity' => 'method']]); - // Attempt to enable the resource. - $resource_config - ->addSupportedFormat('GET', 'hal_json') - ->save(); - $this->rebuildCache(); + $this->resourceConfigStorage->create([ + 'id' => 'entity__entity_test', + 'granularity' => 'method', + 'configuration' => [ + 'GET' => [ + 'supported_formats' => [ + 'hal_json', + ], + ], + ], + ])->save(); // Verify that accessing the resource returns 401. $response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');