summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2016-06-19 13:41:10 (GMT)
committerAlex Pott2016-06-19 13:41:10 (GMT)
commit871da5e4f723b5b1928956c495a0f148e4cbbaff (patch)
tree3f6e42c521915dd5c71c9077264ae144c1bbbbb8
parent9bb1c3691c257f1a89c7545da9f1ec2aaa7c4dc9 (diff)
Issue #2308745 by Alumei, dawehner, Wim Leers, larowlan, Arla, alexpott, g.oechsler, R.Muilwijk, Berdir, catch, klausi, clemens.tolboom, MattA, Crell: Remove rest.settings.yml, use rest_resource config entities
-rw-r--r--core/lib/Drupal/Core/CoreServiceProvider.php2
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/AuthenticationProviderPass.php27
-rw-r--r--core/modules/page_cache/src/Tests/PageCacheTest.php2
-rw-r--r--core/modules/rest/config/install/rest.settings.yml47
-rw-r--r--core/modules/rest/config/optional/rest.resource.entity.node.yml29
-rw-r--r--core/modules/rest/config/schema/rest.schema.yml28
-rw-r--r--core/modules/rest/rest.install21
-rw-r--r--core/modules/rest/rest.permissions.yml3
-rw-r--r--core/modules/rest/rest.post_update.php39
-rw-r--r--core/modules/rest/rest.services.yml2
-rw-r--r--core/modules/rest/src/Entity/ConfigDependencies.php199
-rw-r--r--core/modules/rest/src/Entity/RestResourceConfig.php261
-rw-r--r--core/modules/rest/src/Plugin/ResourceBase.php2
-rw-r--r--core/modules/rest/src/Plugin/rest/resource/EntityResource.php35
-rw-r--r--core/modules/rest/src/RequestHandler.php54
-rw-r--r--core/modules/rest/src/RestPermissions.php29
-rw-r--r--core/modules/rest/src/RestResourceConfigInterface.php56
-rw-r--r--core/modules/rest/src/Routing/ResourceRoutes.php119
-rw-r--r--core/modules/rest/src/Tests/AuthTest.php2
-rw-r--r--core/modules/rest/src/Tests/CreateTest.php2
-rw-r--r--core/modules/rest/src/Tests/DeleteTest.php2
-rw-r--r--core/modules/rest/src/Tests/NodeTest.php2
-rw-r--r--core/modules/rest/src/Tests/PageCacheTest.php12
-rw-r--r--core/modules/rest/src/Tests/RESTTestBase.php57
-rw-r--r--core/modules/rest/src/Tests/ReadTest.php1
-rw-r--r--core/modules/rest/src/Tests/ResourceTest.php78
-rw-r--r--core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php63
-rw-r--r--core/modules/rest/src/Tests/UpdateTest.php2
-rw-r--r--core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php69
-rw-r--r--core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php192
-rw-r--r--core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php44
-rw-r--r--core/modules/rest/tests/src/Kernel/RequestHandlerTest.php46
-rw-r--r--core/modules/rest/tests/src/Kernel/RestLinkManagerTest.php2
-rw-r--r--core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php134
-rw-r--r--core/modules/system/src/Tests/System/ResponseGeneratorTest.php2
-rw-r--r--core/modules/system/src/Tests/Update/UpdatePathTestBase.php10
-rw-r--r--core/modules/system/system.install6
-rw-r--r--core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/AuthenticationProviderPassTest.php44
38 files changed, 1468 insertions, 257 deletions
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 9117891..013dec1 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -4,6 +4,7 @@ namespace Drupal\Core;
use Drupal\Core\Cache\Context\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
+use Drupal\Core\DependencyInjection\Compiler\AuthenticationProviderPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass;
use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass;
@@ -90,6 +91,7 @@ class CoreServiceProvider implements ServiceProviderInterface, ServiceModifierIn
$container->addCompilerPass(new ListCacheBinsPass());
$container->addCompilerPass(new CacheContextsPass());
$container->addCompilerPass(new ContextProvidersPass());
+ $container->addCompilerPass(new AuthenticationProviderPass());
// Register plugin managers.
$container->addCompilerPass(new PluginManagerPass());
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/AuthenticationProviderPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/AuthenticationProviderPass.php
new file mode 100644
index 0000000..20708e1
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/AuthenticationProviderPass.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Registers the authentication_providers container parameter.
+ */
+class AuthenticationProviderPass implements CompilerPassInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container) {
+ $authentication_providers = [];
+ foreach ($container->findTaggedServiceIds('authentication_provider') as $service_id => $attributes) {
+ $authentication_provider = $attributes[0]['provider_id'];
+ if ($provider_tag = $container->getDefinition($service_id)->getTag('_provider')) {
+ $authentication_providers[$authentication_provider] = $provider_tag[0]['provider'];
+ }
+ }
+ $container->setParameter('authentication_providers', $authentication_providers);
+ }
+
+}
diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php
index 9194e65..64e4ee9 100644
--- a/core/modules/page_cache/src/Tests/PageCacheTest.php
+++ b/core/modules/page_cache/src/Tests/PageCacheTest.php
@@ -131,7 +131,7 @@ class PageCacheTest extends WebTestBase {
$this->assertRaw('{"content":"oh hai this is json"}', 'The correct Json response was returned.');
// Enable REST support for nodes and hal+json.
- \Drupal::service('module_installer')->install(['node', 'rest', 'hal']);
+ \Drupal::service('module_installer')->install(['node', 'rest', 'hal', 'basic_auth']);
$this->drupalCreateContentType(['type' => 'article']);
$node = $this->drupalCreateNode(['type' => 'article']);
$node_uri = $node->urlInfo();
diff --git a/core/modules/rest/config/install/rest.settings.yml b/core/modules/rest/config/install/rest.settings.yml
index d3aa82b..2d8185e 100644
--- a/core/modules/rest/config/install/rest.settings.yml
+++ b/core/modules/rest/config/install/rest.settings.yml
@@ -1,50 +1,3 @@
-# Enable all methods on nodes.
-# You must install Hal and Basic_auth modules for this to work. Also, if you are
-# going to use Basic_auth in a production environment then you should consider
-# setting up SSL.
-# There are alternatives to Basic_auth in contrib such as OAuth module.
-resources:
- entity:node:
- 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
-
-# Multiple formats and multiple authentication providers can be defined for a
-# resource:
-#
-# resources:
-# entity:node:
-# GET:
-# supported_formats:
-# - json
-# - hal_json
-# - xml
-# supported_auth:
-# - cookie
-# - basic_auth
-#
-# hal_json is the only format supported for POST and PATCH methods.
-#
-# The full documentation is located at
-# https://www.drupal.org/documentation/modules/rest.
-
# Set the domain for REST type and relation links.
# If left blank, the site's domain will be used.
link_domain: ~
diff --git a/core/modules/rest/config/optional/rest.resource.entity.node.yml b/core/modules/rest/config/optional/rest.resource.entity.node.yml
new file mode 100644
index 0000000..0cf4d78
--- /dev/null
+++ b/core/modules/rest/config/optional/rest.resource.entity.node.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/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml
index 8014b31..41bc2bf 100644
--- a/core/modules/rest/config/schema/rest.schema.yml
+++ b/core/modules/rest/config/schema/rest.schema.yml
@@ -1,20 +1,15 @@
# Schema for the configuration files of the REST module.
-
rest.settings:
type: config_object
label: 'REST settings'
mapping:
- resources:
- type: sequence
- label: 'Resources'
- sequence:
- type: rest_resource
- label: 'Resource'
link_domain:
type: string
label: 'Domain of the relation'
-rest_resource:
+# 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:
GET:
@@ -45,3 +40,20 @@ rest_request:
sequence:
type: string
label: 'Authentication'
+
+rest.resource.*:
+ type: config_entity
+ label: 'REST resource config'
+ mapping:
+ id:
+ type: string
+ label: 'REST resource config ID'
+ plugin_id:
+ type: string
+ label: 'REST resource plugin id'
+ granularity:
+ type: string
+ label: 'REST resource configuration granularity'
+ configuration:
+ type: rest_resource.[%parent.granularity]
+ label: 'REST resource configuration'
diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install
index 4bca69b..601206d 100644
--- a/core/modules/rest/rest.install
+++ b/core/modules/rest/rest.install
@@ -21,3 +21,24 @@ function rest_requirements($phase) {
}
return $requirements;
}
+
+/**
+ * @defgroup updates-8.1.x-to-8.2.x
+ * @{
+ * Update functions from 8.1.x to 8.2.x.
+ */
+
+/**
+ * Install the REST config entity type and fix old settings-based config.
+ */
+function rest_update_8201() {
+ \Drupal::entityDefinitionUpdateManager()->installEntityType(\Drupal::entityTypeManager()->getDefinition('rest_resource_config'));
+ \Drupal::state()->set('rest_update_8201_resources', \Drupal::config('rest.settings')->get('resources'));
+ \Drupal::configFactory()->getEditable('rest.settings')
+ ->clear('resources')
+ ->save();
+}
+
+/**
+ * @} End of "defgroup updates-8.1.x-to-8.2.x".
+ */
diff --git a/core/modules/rest/rest.permissions.yml b/core/modules/rest/rest.permissions.yml
index 2ab7154..171a284 100644
--- a/core/modules/rest/rest.permissions.yml
+++ b/core/modules/rest/rest.permissions.yml
@@ -1,2 +1,5 @@
permission_callbacks:
- Drupal\rest\RestPermissions::permissions
+
+administer rest resources:
+ title: 'Administer REST resource configuration'
diff --git a/core/modules/rest/rest.post_update.php b/core/modules/rest/rest.post_update.php
new file mode 100644
index 0000000..7068662
--- /dev/null
+++ b/core/modules/rest/rest.post_update.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for Rest.
+ */
+
+use Drupal\rest\Entity\RestResourceConfig;
+use Drupal\rest\RestResourceConfigInterface;
+
+/**
+ * @addtogroup updates-8.1.x-to-8.2.x
+ * @{
+ */
+
+/**
+ * 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()
+ */
+function rest_post_update_create_rest_resource_config_entities() {
+ $resources = \Drupal::state()->get('rest_update_8201_resources', []);
+ foreach ($resources as $key => $resource) {
+ $resource = RestResourceConfig::create([
+ 'id' => str_replace(':', '.', $key),
+ 'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
+ 'configuration' => $resource,
+ ]);
+ $resource->save();
+ }
+}
+
+/**
+ * @} End of "addtogroup updates-8.1.x-to-8.2.x".
+ */
diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml
index 6b613e3..c702719 100644
--- a/core/modules/rest/rest.services.yml
+++ b/core/modules/rest/rest.services.yml
@@ -24,7 +24,7 @@ services:
arguments: ['@cache.default', '@entity.manager', '@module_handler', '@config.factory', '@request_stack']
rest.resource_routes:
class: Drupal\rest\Routing\ResourceRoutes
- arguments: ['@plugin.manager.rest', '@config.factory', '@logger.channel.rest']
+ arguments: ['@plugin.manager.rest', '@entity_type.manager', '@logger.channel.rest']
tags:
- { name: 'event_subscriber' }
logger.channel.rest:
diff --git a/core/modules/rest/src/Entity/ConfigDependencies.php b/core/modules/rest/src/Entity/ConfigDependencies.php
new file mode 100644
index 0000000..72fc8cc
--- /dev/null
+++ b/core/modules/rest/src/Entity/ConfigDependencies.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Drupal\rest\Entity;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\rest\RestResourceConfigInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Calculates rest resource config dependencies.
+ */
+class ConfigDependencies implements ContainerInjectionInterface {
+
+ /**
+ * The serialization format providers, keyed by format.
+ *
+ * @var string[]
+ */
+ protected $formatProviders;
+
+ /**
+ * The authentication providers, keyed by ID.
+ *
+ * @var string[]
+ */
+ protected $authProviders;
+
+ /**
+ * Creates a new ConfigDependencies instance.
+ *
+ * @param string[] $format_providers
+ * The serialization format providers, keyed by format.
+ * @param string[] $auth_providers
+ * The authentication providers, keyed by ID.
+ */
+ public function __construct(array $format_providers, array $auth_providers) {
+ $this->formatProviders = $format_providers;
+ $this->authProviders = $auth_providers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->getParameter('serializer.format_providers'),
+ $container->getParameter('authentication_providers')
+ );
+ }
+
+ /**
+ * 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()
+ */
+ 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.
+ }
+ }
+
+ /**
+ * 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) {
+ // Add dependencies based on the supported authentication providers.
+ foreach ($rest_config->getAuthenticationProviders($request_method) as $auth) {
+ if (isset($this->authProviders[$auth])) {
+ $module_name = $this->authProviders[$auth];
+ $dependencies['module'][] = $module_name;
+ }
+ }
+ // Add dependencies based on the supported authentication formats.
+ foreach ($rest_config->getFormats($request_method) as $format) {
+ if (isset($this->formatProviders[$format])) {
+ $module_name = $this->formatProviders[$format];
+ $dependencies['module'][] = $module_name;
+ }
+ }
+ }
+
+ return $dependencies;
+ }
+
+ /**
+ * 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.
+ *
+ * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
+ */
+ 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.
+ }
+ }
+
+ /**
+ * 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.
+ */
+ protected function onDependencyRemovalForMethodGranularity(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 (array_keys($rest_config->get('configuration')) as $request_method) {
+ foreach ($removed_formats as $format) {
+ if (in_array($format, $rest_config->getFormats($request_method))) {
+ $configuration[$request_method]['supported_formats'] = array_diff($configuration[$request_method]['supported_formats'], $removed_formats);
+ }
+ }
+ foreach ($removed_auth as $auth) {
+ if (in_array($auth, $rest_config->getAuthenticationProviders($request_method))) {
+ $configuration[$request_method]['supported_auth'] = array_diff($configuration[$request_method]['supported_auth'], $removed_auth);
+ }
+ }
+ if (empty($configuration[$request_method]['supported_auth'])) {
+ // Remove the key if there are no more authentication providers
+ // supported by this request method.
+ unset($configuration[$request_method]['supported_auth']);
+ }
+ if (empty($configuration[$request_method]['supported_formats'])) {
+ // Remove the key if there are no more formats supported by this
+ // request method.
+ unset($configuration[$request_method]['supported_formats']);
+ }
+ if (empty($configuration[$request_method])) {
+ // Remove the request method altogether if it no longer has any
+ // supported authentication providers or formats.
+ unset($configuration[$request_method]);
+ }
+ }
+ }
+ if (!empty($configuration_before != $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;
+ }
+
+}
diff --git a/core/modules/rest/src/Entity/RestResourceConfig.php b/core/modules/rest/src/Entity/RestResourceConfig.php
new file mode 100644
index 0000000..f2b8b0e
--- /dev/null
+++ b/core/modules/rest/src/Entity/RestResourceConfig.php
@@ -0,0 +1,261 @@
+<?php
+
+namespace Drupal\rest\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
+use Drupal\rest\RestResourceConfigInterface;
+
+/**
+ * Defines a RestResourceConfig configuration entity class.
+ *
+ * @ConfigEntityType(
+ * id = "rest_resource_config",
+ * label = @Translation("REST resource configuration"),
+ * config_prefix = "resource",
+ * admin_permission = "administer rest resources",
+ * label_callback = "getLabelFromPlugin",
+ * entity_keys = {
+ * "id" = "id"
+ * },
+ * config_export = {
+ * "id",
+ * "plugin_id",
+ * "granularity",
+ * "configuration"
+ * }
+ * )
+ */
+class RestResourceConfig extends ConfigEntityBase implements RestResourceConfigInterface {
+
+ /**
+ * The REST resource config id.
+ *
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * The REST resource plugin id.
+ *
+ * @var string
+ */
+ 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
+ */
+ protected $configuration;
+
+ /**
+ * The rest resource plugin manager.
+ *
+ * @var \Drupal\Component\Plugin\PluginManagerInterface
+ */
+ protected $pluginManager;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $values, $entity_type) {
+ parent::__construct($values, $entity_type);
+ // The config entity id looks like the plugin id but uses __ instead of :
+ // because : is not valid for config entities.
+ if (!isset($this->plugin_id) && isset($this->id)) {
+ // Generate plugin_id on first entity creation.
+ $this->plugin_id = str_replace('.', ':', $this->id);
+ }
+ }
+
+ /**
+ * The label callback for this configuration entity.
+ *
+ * @return string The label.
+ */
+ protected function getLabelFromPlugin() {
+ $plugin_definition = $this->getResourcePluginManager()
+ ->getDefinition(['id' => $this->plugin_id]);
+ return $plugin_definition['label'];
+ }
+
+ /**
+ * Returns the resource plugin manager.
+ *
+ * @return \Drupal\Component\Plugin\PluginManagerInterface
+ */
+ protected function getResourcePluginManager() {
+ if (!isset($this->pluginManager)) {
+ $this->pluginManager = \Drupal::service('plugin.manager.rest');
+ }
+ return $this->pluginManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResourcePlugin() {
+ return $this->getPluginCollections()['resource']->get($this->plugin_id);
+ }
+
+ /**
+ * {@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.
+ }
+ }
+
+ /**
+ * Retrieves a list of supported HTTP methods for this resource.
+ *
+ * @return string[]
+ * A list of supported HTTP methods.
+ */
+ protected function getMethodsForMethodGranularity() {
+ $methods = array_keys($this->configuration);
+ return array_map([$this, 'normalizeRestMethod'], $methods);
+ }
+
+ /**
+ * {@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.
+ }
+ }
+
+ /**
+ * Retrieves a list of supported authentication providers.
+ *
+ * @param string $method
+ * The request method e.g GET or POST.
+ *
+ * @return string[]
+ * A list of supported authentication provider IDs.
+ */
+ public function getAuthenticationProvidersForMethodGranularity($method) {
+ $method = $this->normalizeRestMethod($method);
+ if (in_array($method, $this->getMethods()) && isset($this->configuration[$method]['supported_auth'])) {
+ return $this->configuration[$method]['supported_auth'];
+ }
+ return [];
+ }
+
+ /**
+ * {@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.
+ }
+ }
+
+ /**
+ * Retrieves a list of supported response formats.
+ *
+ * @param string $method
+ * The request method e.g GET or POST.
+ *
+ * @return string[]
+ * A list of supported format IDs.
+ */
+ protected function getFormatsForMethodGranularity($method) {
+ $method = $this->normalizeRestMethod($method);
+ if (in_array($method, $this->getMethods()) && isset($this->configuration[$method]['supported_formats'])) {
+ return $this->configuration[$method]['supported_formats'];
+ }
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPluginCollections() {
+ return [
+ 'resource' => new DefaultSingleLazyPluginCollection($this->getResourcePluginManager(), $this->plugin_id, [])
+ ];
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function calculateDependencies() {
+ parent::calculateDependencies();
+
+ foreach ($this->getRestResourceDependencies()->calculateDependencies($this) as $type => $dependencies) {
+ foreach ($dependencies as $dependency) {
+ $this->addDependency($type, $dependency);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onDependencyRemoval(array $dependencies) {
+ $parent = parent::onDependencyRemoval($dependencies);
+
+ // 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.
+ $changed = $this->getRestResourceDependencies()->onDependencyRemoval($this, $dependencies);
+ return $parent || $changed;
+ }
+
+ /**
+ * Returns the REST resource dependencies.
+ *
+ * @return \Drupal\rest\Entity\ConfigDependencies
+ */
+ protected function getRestResourceDependencies() {
+ return \Drupal::service('class_resolver')->getInstanceFromDefinition(ConfigDependencies::class);
+ }
+
+ /**
+ * Normalizes the method to upper case and check validity.
+ *
+ * @param string $method
+ * The request method.
+ *
+ * @return string
+ * The normalised request method.
+ *
+ * @throws \InvalidArgumentException
+ * If the method is not supported.
+ */
+ protected function normalizeRestMethod($method) {
+ $valid_methods = ['GET', 'POST', 'PATCH', 'DELETE'];
+ $normalised_method = strtoupper($method);
+ if (!in_array($normalised_method, $valid_methods)) {
+ throw new \InvalidArgumentException('The method is not supported.');
+ }
+ return $normalised_method;
+ }
+
+}
diff --git a/core/modules/rest/src/Plugin/ResourceBase.php b/core/modules/rest/src/Plugin/ResourceBase.php
index 33cb3aa..549ac54 100644
--- a/core/modules/rest/src/Plugin/ResourceBase.php
+++ b/core/modules/rest/src/Plugin/ResourceBase.php
@@ -194,8 +194,6 @@ abstract class ResourceBase extends PluginBase implements ContainerFactoryPlugin
$route = new Route($canonical_path, array(
'_controller' => 'Drupal\rest\RequestHandler::handle',
- // Pass the resource plugin ID along as default property.
- '_plugin' => $this->pluginId,
), array(
'_permission' => "restful $lower_method $this->pluginId",
),
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index b01033f..7e620df 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -2,6 +2,7 @@
namespace Drupal\rest\Plugin\rest\resource;
+use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Config\Entity\ConfigEntityType;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
@@ -9,9 +10,9 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
-use Drupal\rest\ModifiedResourceResponse;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\rest\ModifiedResourceResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -32,14 +33,14 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
* }
* )
*/
-class EntityResource extends ResourceBase {
+class EntityResource extends ResourceBase implements DependentPluginInterface {
/**
- * The entity type manager.
+ * The entity type targeted by this resource.
*
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ * @var \Drupal\Core\Entity\EntityTypeInterface
*/
- protected $entityTypeManager;
+ protected $entityType;
/**
* Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
@@ -50,16 +51,16 @@ class EntityResource extends ResourceBase {
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager
* @param array $serializer_formats
* The available serialization formats.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- * The entity type manager.
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, EntityTypeManagerInterface $entity_type_manager) {
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
- $this->entityTypeManager = $entity_type_manager;
+ $this->entityType = $entity_type_manager->getDefinition($plugin_definition['entity_type']);
}
/**
@@ -70,9 +71,9 @@ class EntityResource extends ResourceBase {
$configuration,
$plugin_id,
$plugin_definition,
+ $container->get('entity_type.manager'),
$container->getParameter('serializer.formats'),
- $container->get('logger.factory')->get('rest'),
- $container->get('entity_type.manager')
+ $container->get('logger.factory')->get('rest')
);
}
@@ -331,8 +332,16 @@ class EntityResource extends ResourceBase {
* TRUE if the entity is a Config Entity, FALSE otherwise.
*/
protected function isConfigEntityResource() {
- $entity_type_id = $this->getPluginDefinition()['entity_type'];
- return $this->entityTypeManager->getDefinition($entity_type_id) instanceof ConfigEntityType;
+ return $this->entityType instanceof ConfigEntityType;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ if (isset($this->entityType)) {
+ return ['module' => [$this->entityType->getProvider()]];
+ }
}
}
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 5bbb9be..2c3310a 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -2,11 +2,14 @@
namespace Drupal\rest;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -17,11 +20,35 @@ use Symfony\Component\Serializer\SerializerInterface;
/**
* Acts as intermediate request forwarder for resource plugins.
*/
-class RequestHandler implements ContainerAwareInterface {
+class RequestHandler implements ContainerAwareInterface, ContainerInjectionInterface {
use ContainerAwareTrait;
/**
+ * The resource configuration storage.
+ *
+ * @var \Drupal\Core\Entity\EntityStorageInterface
+ */
+ protected $resourceStorage;
+
+ /**
+ * Creates a new RequestHandler instance.
+ *
+ * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
+ * The resource configuration storage.
+ */
+ public function __construct(EntityStorageInterface $entity_storage) {
+ $this->resourceStorage = $entity_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static($container->get('entity_type.manager')->getStorage('rest_resource_config'));
+ }
+
+ /**
* Handles a web API request.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
@@ -33,13 +60,12 @@ class RequestHandler implements ContainerAwareInterface {
* The response object.
*/
public function handle(RouteMatchInterface $route_match, Request $request) {
-
- $plugin = $route_match->getRouteObject()->getDefault('_plugin');
$method = strtolower($request->getMethod());
- $resource = $this->container
- ->get('plugin.manager.rest')
- ->createInstance($plugin);
+ $resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config');
+ /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
+ $resource_config = $this->resourceStorage->load($resource_config_id);
+ $resource = $resource_config->getResourcePlugin();
// Deserialize incoming data if available.
/** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
@@ -53,9 +79,8 @@ class RequestHandler implements ContainerAwareInterface {
// formats are configured allow all and hope that the serializer knows the
// format. If the serializer cannot handle it an exception will be thrown
// that bubbles up to the client.
- $config = $this->container->get('config.factory')->get('rest.settings')->get('resources');
- $method_settings = $config[$plugin][$request->getMethod()];
- if (empty($method_settings['supported_formats']) || in_array($format, $method_settings['supported_formats'])) {
+ $request_method = $request->getMethod();
+ if (in_array($format, $resource_config->getFormats($request_method))) {
$definition = $resource->getPluginDefinition();
$class = $definition['serialization_class'];
try {
@@ -101,7 +126,7 @@ class RequestHandler implements ContainerAwareInterface {
}
return $response instanceof ResourceResponseInterface ?
- $this->renderResponse($request, $response, $serializer, $format) :
+ $this->renderResponse($request, $response, $serializer, $format, $resource_config) :
$response;
}
@@ -132,6 +157,8 @@ class RequestHandler implements ContainerAwareInterface {
* The serializer to use.
* @param string $format
* The response format.
+ * @param \Drupal\rest\RestResourceConfigInterface $resource_config
+ * The resource config.
*
* @return \Drupal\rest\ResourceResponse
* The altered response.
@@ -139,7 +166,7 @@ class RequestHandler implements ContainerAwareInterface {
* @todo Add test coverage for language negotiation contexts in
* https://www.drupal.org/node/2135829.
*/
- protected function renderResponse(Request $request, ResourceResponseInterface $response, SerializerInterface $serializer, $format) {
+ protected function renderResponse(Request $request, ResourceResponseInterface $response, SerializerInterface $serializer, $format, RestResourceConfigInterface $resource_config) {
$data = $response->getResponseData();
if ($response instanceof CacheableResponseInterface) {
@@ -153,9 +180,8 @@ class RequestHandler implements ContainerAwareInterface {
$response->addCacheableDependency($context->pop());
}
- // Add rest settings config's cache tags.
- $response->addCacheableDependency($this->container->get('config.factory')
- ->get('rest.settings'));
+ // Add rest config's cache tags.
+ $response->addCacheableDependency($resource_config);
}
else {
$output = $serializer->serialize($data, $format);
diff --git a/core/modules/rest/src/RestPermissions.php b/core/modules/rest/src/RestPermissions.php
index 57e95e4..473c192 100644
--- a/core/modules/rest/src/RestPermissions.php
+++ b/core/modules/rest/src/RestPermissions.php
@@ -2,8 +2,8 @@
namespace Drupal\rest;
-use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\rest\Plugin\Type\ResourcePluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -20,30 +20,30 @@ class RestPermissions implements ContainerInjectionInterface {
protected $restPluginManager;
/**
- * The config factory.
+ * The REST resource config storage.
*
- * @var \Drupal\Core\Config\ConfigFactoryInterface
+ * @var \Drupal\Core\Entity\EntityManagerInterface
*/
- protected $configFactory;
+ protected $resourceConfigStorage;
/**
* Constructs a new RestPermissions instance.
*
* @param \Drupal\rest\Plugin\Type\ResourcePluginManager $rest_plugin_manager
* The rest resource plugin manager.
- * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
- * The config factory.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
*/
- public function __construct(ResourcePluginManager $rest_plugin_manager, ConfigFactoryInterface $config_factory) {
+ public function __construct(ResourcePluginManager $rest_plugin_manager, EntityTypeManagerInterface $entity_type_manager) {
$this->restPluginManager = $rest_plugin_manager;
- $this->configFactory = $config_factory;
+ $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
- return new static($container->get('plugin.manager.rest'), $container->get('config.factory'));
+ return new static($container->get('plugin.manager.rest'), $container->get('entity_type.manager'));
}
/**
@@ -53,12 +53,11 @@ class RestPermissions implements ContainerInjectionInterface {
*/
public function permissions() {
$permissions = [];
- $resources = $this->configFactory->get('rest.settings')->get('resources');
- if ($resources && $enabled = array_intersect_key($this->restPluginManager->getDefinitions(), $resources)) {
- foreach ($enabled as $id => $resource) {
- $plugin = $this->restPluginManager->createInstance($id);
- $permissions = array_merge($permissions, $plugin->permissions());
- }
+ /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
+ $resource_configs = $this->resourceConfigStorage->loadMultiple();
+ foreach ($resource_configs as $resource_config) {
+ $plugin = $resource_config->getResourcePlugin();
+ $permissions = array_merge($permissions, $plugin->permissions());
}
return $permissions;
}
diff --git a/core/modules/rest/src/RestResourceConfigInterface.php b/core/modules/rest/src/RestResourceConfigInterface.php
new file mode 100644
index 0000000..7f34bc4
--- /dev/null
+++ b/core/modules/rest/src/RestResourceConfigInterface.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\rest;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
+
+/**
+ * Defines a configuration entity to store enabled REST resources.
+ */
+interface RestResourceConfigInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
+
+ /**
+ * Granularity value for per-method configuration.
+ */
+ const METHOD_GRANULARITY = 'method';
+
+ /**
+ * Retrieves the REST resource plugin.
+ *
+ * @return \Drupal\rest\Plugin\ResourceInterface
+ * The resource plugin
+ */
+ public function getResourcePlugin();
+
+ /**
+ * Retrieves a list of supported HTTP methods.
+ *
+ * @return string[]
+ * A list of supported HTTP methods.
+ */
+ public function getMethods();
+
+ /**
+ * Retrieves a list of supported authentication providers.
+ *
+ * @param string $method
+ * The request method e.g GET or POST.
+ *
+ * @return string[]
+ * A list of supported authentication provider IDs.
+ */
+ public function getAuthenticationProviders($method);
+
+ /**
+ * Retrieves a list of supported response formats.
+ *
+ * @param string $method
+ * The request method e.g GET or POST.
+ *
+ * @return string[]
+ * A list of supported format IDs.
+ */
+ public function getFormats($method);
+
+}
diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php
index e88f18b..5d3ae30 100644
--- a/core/modules/rest/src/Routing/ResourceRoutes.php
+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -2,9 +2,10 @@
namespace Drupal\rest\Routing;
-use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\rest\Plugin\Type\ResourcePluginManager;
+use Drupal\rest\RestResourceConfigInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\RouteCollection;
@@ -21,11 +22,11 @@ class ResourceRoutes extends RouteSubscriberBase {
protected $manager;
/**
- * The Drupal configuration factory.
+ * The REST resource config storage.
*
- * @var \Drupal\Core\Config\ConfigFactoryInterface
+ * @var \Drupal\Core\Entity\EntityManagerInterface
*/
- protected $config;
+ protected $resourceConfigStorage;
/**
* A logger instance.
@@ -39,14 +40,14 @@ class ResourceRoutes extends RouteSubscriberBase {
*
* @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager
* The resource plugin manager.
- * @param \Drupal\Core\Config\ConfigFactoryInterface $config
- * The configuration factory holding resource settings.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
- public function __construct(ResourcePluginManager $manager, ConfigFactoryInterface $config, LoggerInterface $logger) {
+ public function __construct(ResourcePluginManager $manager, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
$this->manager = $manager;
- $this->config = $config;
+ $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
$this->logger = $logger;
}
@@ -58,56 +59,68 @@ class ResourceRoutes extends RouteSubscriberBase {
* @return array
*/
protected function alterRoutes(RouteCollection $collection) {
- $routes = array();
-
- // Silently ignore resources that are in the settings but are not defined on
- // the plugin manager currently. That avoids exceptions when REST module is
- // enabled before another module that provides the resource plugin specified
- // in the settings.
- // @todo Remove in https://www.drupal.org/node/2308745
- $resources = $this->config->get('rest.settings')->get('resources') ?: array();
- $enabled_resources = array_intersect_key($resources, $this->manager->getDefinitions());
- if (count($resources) != count($enabled_resources)) {
- trigger_error('rest.settings lists resources relying on the following missing plugins: ' . implode(', ', array_keys(array_diff_key($resources, $enabled_resources))));
+ // Iterate over all enabled REST resource configs.
+ /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
+ $resource_configs = $this->resourceConfigStorage->loadMultiple();
+ // Iterate over all enabled resource plugins.
+ foreach ($resource_configs as $resource_config) {
+ $resource_routes = $this->getRoutesForResourceConfig($resource_config);
+ $collection->addCollection($resource_routes);
}
+ }
- // Iterate over all enabled resource plugins.
- foreach ($enabled_resources as $id => $enabled_methods) {
- $plugin = $this->manager->createInstance($id);
- foreach ($plugin->routes() as $name => $route) {
- // @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]) && $method && isset($enabled_methods[$method])) {
- $route->setRequirement('_access_rest_csrf', 'TRUE');
-
- // Check that authentication providers are defined.
- if (empty($enabled_methods[$method]['supported_auth']) || !is_array($enabled_methods[$method]['supported_auth'])) {
- $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $id));
- continue;
- }
-
- // Check that formats are defined.
- if (empty($enabled_methods[$method]['supported_formats']) || !is_array($enabled_methods[$method]['supported_formats'])) {
- $this->logger->error('At least one format must be defined for resource @id', array(':id' => $id));
- continue;
- }
-
- // 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, $enabled_methods[$method]['supported_formats'])) {
- continue;
- }
-
- // The configuration seems legit at this point, so we set the
- // authentication provider and add the route.
- $route->setOption('_auth', $enabled_methods[$method]['supported_auth']);
- $routes["rest.$name"] = $route;
- $collection->add("rest.$name", $route);
+ /**
+ * Provides all routes for a given REST resource config.
+ *
+ * This method determines where a resource is reachable, what path
+ * replacements are used, the required HTTP method for the operation etc.
+ *
+ * @param \Drupal\rest\RestResourceConfigInterface $rest_resource_config
+ * The rest resource config.
+ *
+ * @return \Symfony\Component\Routing\RouteCollection
+ * The route collection.
+ */
+ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_resource_config) {
+ $plugin = $rest_resource_config->getResourcePlugin();
+ $collection = new RouteCollection();
+
+ foreach ($plugin->routes() as $name => $route) {
+ /** @var \Symfony\Component\Routing\Route $route */
+ // @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->getFormats($method)) {
+ $route->setRequirement('_access_rest_csrf', 'TRUE');
+
+ // Check that authentication providers are defined.
+ 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->getFormats($method))) {
+ $this->logger->error('At least one format must be defined for resource @id', array(':id' => $rest_resource_config->id()));
+ continue;
}
+
+ // 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->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->getAuthenticationProviders($method));
+ $route->setDefault('_rest_resource_config', $rest_resource_config->id());
+ $collection->add("rest.$name", $route);
}
+
}
+ return $collection;
}
}
diff --git a/core/modules/rest/src/Tests/AuthTest.php b/core/modules/rest/src/Tests/AuthTest.php
index 08db9ab..e9fb7d7 100644
--- a/core/modules/rest/src/Tests/AuthTest.php
+++ b/core/modules/rest/src/Tests/AuthTest.php
@@ -16,7 +16,7 @@ class AuthTest extends RESTTestBase {
*
* @var array
*/
- public static $modules = array('basic_auth', 'hal', 'rest', 'entity_test', 'comment');
+ public static $modules = array('basic_auth', 'hal', 'rest', 'entity_test');
/**
* Tests reading from an authenticated resource.
diff --git a/core/modules/rest/src/Tests/CreateTest.php b/core/modules/rest/src/Tests/CreateTest.php
index 035480b..28c8af6 100644
--- a/core/modules/rest/src/Tests/CreateTest.php
+++ b/core/modules/rest/src/Tests/CreateTest.php
@@ -23,7 +23,7 @@ class CreateTest extends RESTTestBase {
*
* @var array
*/
- public static $modules = array('hal', 'rest', 'entity_test', 'comment');
+ public static $modules = array('hal', 'rest', 'entity_test', 'comment', 'node');
/**
* The 'serializer' service.
diff --git a/core/modules/rest/src/Tests/DeleteTest.php b/core/modules/rest/src/Tests/DeleteTest.php
index ccba38e..4cc8016 100644
--- a/core/modules/rest/src/Tests/DeleteTest.php
+++ b/core/modules/rest/src/Tests/DeleteTest.php
@@ -16,7 +16,7 @@ class DeleteTest extends RESTTestBase {
*
* @var array
*/
- public static $modules = array('hal', 'rest', 'entity_test');
+ public static $modules = array('hal', 'rest', 'entity_test', 'node');
/**
* Tests several valid and invalid delete requests on all entity types.
diff --git a/core/modules/rest/src/Tests/NodeTest.php b/core/modules/rest/src/Tests/NodeTest.php
index 7f0ed81..9eedd2c 100644
--- a/core/modules/rest/src/Tests/NodeTest.php
+++ b/core/modules/rest/src/Tests/NodeTest.php
@@ -19,7 +19,7 @@ class NodeTest extends RESTTestBase {
*
* @var array
*/
- public static $modules = array('hal', 'rest', 'comment');
+ public static $modules = array('hal', 'rest', 'comment', 'node');
/**
* Enables node specific REST API configuration and authentication.
diff --git a/core/modules/rest/src/Tests/PageCacheTest.php b/core/modules/rest/src/Tests/PageCacheTest.php
index 0ce66da..2cf0d43 100644
--- a/core/modules/rest/src/Tests/PageCacheTest.php
+++ b/core/modules/rest/src/Tests/PageCacheTest.php
@@ -76,7 +76,7 @@ class PageCacheTest extends RESTTestBase {
$this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'MISS');
- $this->assertCacheTag('config:rest.settings');
+ $this->assertCacheTag('config:rest.resource.entity.entity_test');
$this->assertCacheTag('entity_test:1');
$this->assertCacheTag('entity_test_access:field_test_text');
@@ -84,17 +84,17 @@ class PageCacheTest extends RESTTestBase {
$this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'HIT');
- $this->assertCacheTag('config:rest.settings');
+ $this->assertCacheTag('config:rest.resource.entity.entity_test');
$this->assertCacheTag('entity_test:1');
$this->assertCacheTag('entity_test_access:field_test_text');
- // Trigger a config save which should clear the page cache, so we should get
- // a cache miss now for the same request.
- $this->config('rest.settings')->save();
+ // Trigger a resource config save which should clear the page cache, so we
+ // should get a cache miss now for the same request.
+ $this->resourceConfigStorage->load('entity.entity_test')->save();
$this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'MISS');
- $this->assertCacheTag('config:rest.settings');
+ $this->assertCacheTag('config:rest.resource.entity.entity_test');
$this->assertCacheTag('entity_test:1');
$this->assertCacheTag('entity_test_access:field_test_text');
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index ad5f13d..4779b6b 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -4,6 +4,7 @@ namespace Drupal\rest\Tests;
use Drupal\Core\Config\Entity\ConfigEntityType;
use Drupal\node\NodeInterface;
+use Drupal\rest\RestResourceConfigInterface;
use Drupal\simpletest\WebTestBase;
/**
@@ -12,6 +13,13 @@ use Drupal\simpletest\WebTestBase;
abstract class RESTTestBase extends WebTestBase {
/**
+ * The REST resource config storage.
+ *
+ * @var \Drupal\Core\Entity\EntityStorageInterface
+ */
+ protected $resourceConfigStorage;
+
+ /**
* The default serialization format to use for testing REST operations.
*
* @var string
@@ -52,15 +60,18 @@ abstract class RESTTestBase extends WebTestBase {
*
* @var array
*/
- public static $modules = array('rest', 'entity_test', 'node');
+ public static $modules = array('rest', 'entity_test');
protected function setUp() {
parent::setUp();
$this->defaultFormat = 'hal_json';
$this->defaultMimeType = 'application/hal+json';
$this->defaultAuth = array('cookie');
+ $this->resourceConfigStorage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
// Create a test content type for node testing.
- $this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest'));
+ if (in_array('node', static::$modules)) {
+ $this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest'));
+ }
}
/**
@@ -268,29 +279,49 @@ abstract class RESTTestBase extends WebTestBase {
* @param array $auth
* (Optional) The list of valid authentication methods.
*/
- protected function enableService($resource_type, $method = 'GET', $format = NULL, $auth = NULL) {
- // Enable REST API for this entity type.
- $config = $this->config('rest.settings');
- $settings = array();
-
+ protected function enableService($resource_type, $method = 'GET', $format = NULL, array $auth = []) {
if ($resource_type) {
+ // Enable REST API for this entity type.
+ $resource_config_id = str_replace(':', '.', $resource_type);
+ // get entity by id
+ /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
+ $resource_config = $this->resourceConfigStorage->load($resource_config_id);
+ if (!$resource_config) {
+ $resource_config = $this->resourceConfigStorage->create([
+ 'id' => $resource_config_id,
+ 'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
+ 'configuration' => []
+ ]);
+ }
+ $configuration = $resource_config->get('configuration');
+
if (is_array($format)) {
- $settings[$resource_type][$method]['supported_formats'] = $format;
+ for ($i = 0; $i < count($format); $i++) {
+ $configuration[$method]['supported_formats'][] = $format[$i];
+ }
}
else {
if ($format == NULL) {
$format = $this->defaultFormat;
}
- $settings[$resource_type][$method]['supported_formats'][] = $format;
+ $configuration[$method]['supported_formats'][] = $format;
}
- if ($auth == NULL) {
+ if (!is_array($auth) || empty($auth)) {
$auth = $this->defaultAuth;
}
- $settings[$resource_type][$method]['supported_auth'] = $auth;
+ foreach ($auth as $auth_provider) {
+ $configuration[$method]['supported_auth'][] = $auth_provider;
+ }
+
+ $resource_config->set('configuration', $configuration);
+ $resource_config->save();
+ }
+ else {
+ foreach ($this->resourceConfigStorage->loadMultiple() as $resource_config) {
+ $resource_config->delete();
+ }
}
- $config->set('resources', $settings);
- $config->save();
$this->rebuildCache();
}
diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php
index be6d4ae..5baaf2d 100644
--- a/core/modules/rest/src/Tests/ReadTest.php
+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -22,6 +22,7 @@ class ReadTest extends RESTTestBase {
public static $modules = [
'hal',
'rest',
+ 'node',
'entity_test',
'config_test',
'taxonomy',
diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php
index df99cc6..e4d6c00 100644
--- a/core/modules/rest/src/Tests/ResourceTest.php
+++ b/core/modules/rest/src/Tests/ResourceTest.php
@@ -2,8 +2,8 @@
namespace Drupal\rest\Tests;
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Session\AccountInterface;
+use Drupal\rest\RestResourceConfigInterface;
use Drupal\user\Entity\Role;
/**
@@ -32,9 +32,7 @@ class ResourceTest extends RESTTestBase {
*/
protected function setUp() {
parent::setUp();
- $this->config = $this->config('rest.settings');
-
- // Create an entity programmatically.
+ // Create an entity programmatic.
$this->entity = $this->entityCreate('entity_test');
$this->entity->save();
@@ -47,20 +45,17 @@ class ResourceTest extends RESTTestBase {
* Tests that a resource without formats cannot be enabled.
*/
public function testFormats() {
- $settings = array(
- 'entity:entity_test' => array(
- 'GET' => array(
- 'supported_auth' => array(
+ $this->resourceConfigStorage->create([
+ 'id' => 'entity.entity_test',
+ 'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
+ 'configuration' => [
+ 'GET' => [
+ 'supported_auth' => [
'basic_auth',
- ),
- ),
- ),
- );
-
- // Attempt to enable the resource.
- $this->config->set('resources', $settings);
- $this->config->save();
- $this->rebuildCache();
+ ],
+ ],
+ ],
+ ])->save();
// Verify that accessing the resource returns 406.
$response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
@@ -77,20 +72,17 @@ class ResourceTest extends RESTTestBase {
* Tests that a resource without authentication cannot be enabled.
*/
public function testAuthentication() {
- $settings = array(
- 'entity:entity_test' => array(
- 'GET' => array(
- 'supported_formats' => array(
+ $this->resourceConfigStorage->create([
+ 'id' => 'entity.entity_test',
+ 'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
+ 'configuration' => [
+ 'GET' => [
+ 'supported_formats' => [
'hal_json',
- ),
- ),
- ),
- );
-
- // Attempt to enable the resource.
- $this->config->set('resources', $settings);
- $this->config->save();
- $this->rebuildCache();
+ ],
+ ],
+ ],
+ ])->save();
// Verify that accessing the resource returns 401.
$response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
@@ -118,30 +110,4 @@ class ResourceTest extends RESTTestBase {
}
}
- /**
- * Tests that a resource with a missing plugin does not cause an exception.
- */
- public function testMissingPlugin() {
- $settings = array(
- 'entity:nonexisting' => array(
- 'GET' => array(
- 'supported_formats' => array(
- 'hal_json',
- ),
- ),
- ),
- );
-
- try {
- // Attempt to enable the resource.
- $this->config->set('resources', $settings);
- $this->config->save();
- $this->rebuildCache();
- $this->pass('rest.settings referencing a missing REST resource plugin does not cause an exception.');
- }
- catch (PluginNotFoundException $e) {
- $this->fail('rest.settings referencing a missing REST resource plugin caused an exception.');
- }
- }
-
}
diff --git a/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php b/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php
new file mode 100644
index 0000000..9100f16
--- /dev/null
+++ b/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\rest\Tests\Update;
+
+use Drupal\rest\RestResourceConfigInterface;
+use Drupal\system\Tests\Update\UpdatePathTestBase;
+
+/**
+ * Tests that rest.settings is converted to rest_resource_config entities.
+ *
+ * @see https://www.drupal.org/node/2308745
+ *
+ * @group rest
+ */
+class RestConfigurationEntitiesUpdateTest extends UpdatePathTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['rest', 'serialization'];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDatabaseDumpFiles() {
+ $this->databaseDumpFiles = [
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+ __DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php',
+ ];
+ }
+
+ /**
+ * Tests rest_update_8201().
+ */
+ public function testResourcesConvertedToConfigEntities() {
+ /** @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.
+ $rest_settings = $this->config('rest.settings');
+ $this->assertTrue(array_key_exists('resources', $rest_settings->getRawData()));
+ $this->assertTrue(array_key_exists('entity:node', $rest_settings->getRawData()['resources']));
+ $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.
+ $rest_settings = $this->config('rest.settings');
+ $this->assertFalse(array_key_exists('resources', $rest_settings->getRawData()));
+ $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(['module' => ['basic_auth', 'node', 'serialization']], $node_resource_config_entity->getDependencies());
+ }
+
+}
diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php
index 98df739..1d99cf2 100644
--- a/core/modules/rest/src/Tests/UpdateTest.php
+++ b/core/modules/rest/src/Tests/UpdateTest.php
@@ -23,7 +23,7 @@ class UpdateTest extends RESTTestBase {
*
* @var array
*/
- public static $modules = ['hal', 'rest', 'entity_test', 'comment'];
+ public static $modules = ['hal', 'rest', 'entity_test', 'node', 'comment'];
/**
* {@inheritdoc}
diff --git a/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php b/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php
new file mode 100644
index 0000000..72a143b
--- /dev/null
+++ b/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains database additions to drupal-8.bare.standard.php.gz for testing the
+ * upgrade path of rest_update_8201().
+ */
+
+use Drupal\Core\Database\Database;
+
+$connection = Database::getConnection();
+
+// Set the schema version.
+$connection->insert('key_value')
+ ->fields([
+ 'collection' => 'system.schema',
+ 'name' => 'rest',
+ 'value' => 'i:8000;',
+ ])
+ ->fields([
+ 'collection' => 'system.schema',
+ 'name' => 'serialization',
+ 'value' => 'i:8000;',
+ ])
+ ->fields([
+ 'collection' => 'system.schema',
+ 'name' => 'basic_auth',
+ 'value' => 'i:8000;',
+ ])
+ ->execute();
+
+// Update core.extension.
+$extensions = $connection->select('config')
+ ->fields('config', ['data'])
+ ->condition('collection', '')
+ ->condition('name', 'core.extension')
+ ->execute()
+ ->fetchField();
+$extensions = unserialize($extensions);
+$extensions['module']['basic_auth'] = 8000;
+$extensions['module']['rest'] = 8000;
+$extensions['module']['serialization'] = 8000;
+$connection->update('config')
+ ->fields([
+ 'data' => serialize($extensions),
+ ])
+ ->condition('collection', '')
+ ->condition('name', 'core.extension')
+ ->execute();
+
+// Install the rest configuration.
+$config = [
+ 'resources' => [
+ 'entity:node' => [
+ 'GET' => [
+ 'supported_formats' => ['json'],
+ 'supported_auth' => ['basic_auth'],
+ ],
+ ],
+ ],
+ 'link_domain' => '~',
+];
+$data = $connection->insert('config')
+ ->fields([
+ 'name' => 'rest.settings',
+ 'data' => serialize($config),
+ 'collection' => ''
+ ])
+ ->execute();
diff --git a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php
new file mode 100644
index 0000000..b9a9062
--- /dev/null
+++ b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Drupal\Tests\rest\Kernel\Entity;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\rest\Entity\ConfigDependencies;
+use Drupal\rest\Entity\RestResourceConfig;
+use Drupal\rest\RestResourceConfigInterface;
+
+/**
+ * @coversDefaultClass \Drupal\rest\Entity\ConfigDependencies
+ *
+ * @group rest
+ */
+class ConfigDependenciesTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = ['rest', 'entity_test', 'serialization'];
+
+ /**
+ * @covers ::calculateDependencies
+ * @covers ::calculateDependenciesForMethodGranularity
+ */
+ public function testCalculateDependencies() {
+ $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'],
+ ],
+ ],
+ ]);
+
+ $result = $config_dependencies->calculateDependencies($rest_config);
+ $this->assertEquals(['module' => [
+ 'serialization', 'basic_auth', 'hal',
+ ]], $result);
+ }
+
+ /**
+ * @covers ::onDependencyRemoval
+ * @covers ::calculateDependenciesForMethodGranularity
+ */
+ public function testOnDependencyRemovalRemoveUnrelatedDependency() {
+ $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'],
+ ],
+ ],
+ ]);
+
+ $this->assertFalse($config_dependencies->onDependencyRemoval($rest_config, ['module' => ['node']]));
+ $this->assertEquals([
+ 'GET' => [
+ 'supported_auth' => ['cookie'],
+ 'supported_formats' => ['json'],
+ ],
+ 'POST' => [
+ 'supported_auth' => ['basic_auth'],
+ 'supported_formats' => ['hal_json'],
+ ],
+ ], $rest_config->get('configuration'));
+ }
+
+ /**
+ * @covers ::onDependencyRemoval
+ * @covers ::calculateDependenciesForMethodGranularity
+ */
+ public function testOnDependencyRemovalRemoveFormat() {
+ $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'],
+ ],
+ ],
+ ]);
+
+ $this->assertTrue($config_dependencies->onDependencyRemoval($rest_config, ['module' => ['hal']]));
+ $this->assertEquals(['json'], $rest_config->getFormats('GET'));
+ $this->assertEquals([], $rest_config->getFormats('POST'));
+ $this->assertEquals([
+ 'GET' => [
+ 'supported_auth' => ['cookie'],
+ 'supported_formats' => ['json'],
+ ],
+ 'POST' => [
+ 'supported_auth' => ['basic_auth'],
+ ],
+ ], $rest_config->get('configuration'));
+ }
+
+ /**
+ * @covers ::onDependencyRemoval
+ * @covers ::calculateDependenciesForMethodGranularity
+ */
+ public function testOnDependencyRemovalRemoveAuth() {
+ $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'],
+ ],
+ ],
+ ]);
+
+ $this->assertTrue($config_dependencies->onDependencyRemoval($rest_config, ['module' => ['basic_auth']]));
+ $this->assertEquals(['cookie'], $rest_config->getAuthenticationProviders('GET'));
+ $this->assertEquals([], $rest_config->getAuthenticationProviders('POST'));
+ $this->assertEquals([
+ 'GET' => [
+ 'supported_auth' => ['cookie'],
+ 'supported_formats' => ['json'],
+ ],
+ 'POST' => [
+ 'supported_formats' => ['hal_json'],
+ ],
+ ], $rest_config->get('configuration'));
+ }
+
+ /**
+ * @covers ::onDependencyRemoval
+ * @covers ::calculateDependenciesForMethodGranularity
+ */
+ public function testOnDependencyRemovalRemoveAuthAndFormats() {
+ $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'],
+ ],
+ ],
+ ]);
+
+ $this->assertTrue($config_dependencies->onDependencyRemoval($rest_config, ['module' => ['basic_auth', 'hal']]));
+ $this->assertEquals(['json'], $rest_config->getFormats('GET'));
+ $this->assertEquals(['cookie'], $rest_config->getAuthenticationProviders('GET'));
+ $this->assertEquals([], $rest_config->getFormats('POST'));
+ $this->assertEquals([], $rest_config->getAuthenticationProviders('POST'));
+ $this->assertEquals([
+ 'GET' => [
+ 'supported_auth' => ['cookie'],
+ 'supported_formats' => ['json'],
+ ],
+ ], $rest_config->get('configuration'));
+ }
+
+}
diff --git a/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php
new file mode 100644
index 0000000..89e9b1d
--- /dev/null
+++ b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Tests\rest\Kernel\Entity;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\rest\Entity\RestResourceConfig;
+use Drupal\rest\RestResourceConfigInterface;
+
+/**
+ * @coversDefaultClass \Drupal\rest\Entity\RestResourceConfig
+ *
+ * @group rest
+ */
+class RestResourceConfigTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = ['rest', 'entity_test', 'serialization', 'basic_auth', 'user', 'hal'];
+
+ /**
+ * @covers ::calculateDependencies
+ */
+ public function testCalculateDependencies() {
+ $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->calculateDependencies();
+ $this->assertEquals(['module' => ['basic_auth', 'entity_test', 'hal', 'serialization', 'user']], $rest_config->getDependencies());
+ }
+
+}
diff --git a/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
index 36a482c..e09b175 100644
--- a/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
+++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
@@ -1,18 +1,14 @@
<?php
-/**
- * @file
- * Contains \Drupal\Tests\rest\Kernel\RequestHandlerTest.
- */
-
namespace Drupal\Tests\rest\Kernel;
+use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\KernelTests\KernelTestBase;
use Drupal\rest\Plugin\ResourceBase;
-use Drupal\rest\Plugin\Type\ResourcePluginManager;
use Drupal\rest\RequestHandler;
use Drupal\rest\ResourceResponse;
+use Drupal\rest\RestResourceConfigInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
@@ -32,11 +28,19 @@ class RequestHandlerTest extends KernelTestBase {
public static $modules = ['serialization', 'rest'];
/**
+ * The entity storage.
+ *
+ * @var \Prophecy\Prophecy\ObjectProphecy
+ */
+ protected $entityStorage;
+
+ /**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
- $this->requestHandler = new RequestHandler();
+ $this->entityStorage = $this->prophesize(EntityStorageInterface::class);
+ $this->requestHandler = new RequestHandler($this->entityStorage->reveal());
$this->requestHandler->setContainer($this->container);
}
@@ -47,17 +51,19 @@ class RequestHandlerTest extends KernelTestBase {
*/
public function testBaseHandler() {
$request = new Request();
- $route_match = new RouteMatch('test', new Route('/rest/test', ['_plugin' => 'restplugin', '_format' => 'json']));
+ $route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin', '_format' => 'json']));
$resource = $this->prophesize(StubRequestHandlerResourcePlugin::class);
$resource->get(NULL, $request)
->shouldBeCalled();
- // Setup stub plugin manager that will return our plugin.
- $stub = $this->prophesize(ResourcePluginManager::class);
- $stub->createInstance('restplugin')
- ->willReturn($resource->reveal());
- $this->container->set('plugin.manager.rest', $stub->reveal());
+ // Setup the configuration.
+ $config = $this->prophesize(RestResourceConfigInterface::class);
+ $config->getResourcePlugin()->willReturn($resource->reveal());
+ $config->getCacheContexts()->willReturn([]);
+ $config->getCacheTags()->willReturn([]);
+ $config->getCacheMaxAge()->willReturn(12);
+ $this->entityStorage->load('restplugin')->willReturn($config->reveal());
// Response returns NULL this time because response from plugin is not
// a ResourceResponse so it is passed through directly.
@@ -89,15 +95,17 @@ class RequestHandlerTest extends KernelTestBase {
*/
public function testSerialization($data) {
$request = new Request();
- $route_match = new RouteMatch('test', new Route('/rest/test', ['_plugin' => 'restplugin', '_format' => 'json']));
+ $route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin', '_format' => 'json']));
$resource = $this->prophesize(StubRequestHandlerResourcePlugin::class);
- // Setup stub plugin manager that will return our plugin.
- $stub = $this->prophesize(ResourcePluginManager::class);
- $stub->createInstance('restplugin')
- ->willReturn($resource->reveal());
- $this->container->set('plugin.manager.rest', $stub->reveal());
+ // Setup the configuration.
+ $config = $this->prophesize(RestResourceConfigInterface::class);
+ $config->getResourcePlugin()->willReturn($resource->reveal());
+ $config->getCacheContexts()->willReturn([]);
+ $config->getCacheTags()->willReturn([]);
+ $config->getCacheMaxAge()->willReturn(12);
+ $this->entityStorage->load('restplugin')->willReturn($config->reveal());
$response = new ResourceResponse($data);
$resource->get(NULL, $request)
diff --git a/core/modules/rest/tests/src/Kernel/RestLinkManagerTest.php b/core/modules/rest/tests/src/Kernel/RestLinkManagerTest.php
index fc7c41d..ab558e5 100644
--- a/core/modules/rest/tests/src/Kernel/RestLinkManagerTest.php
+++ b/core/modules/rest/tests/src/Kernel/RestLinkManagerTest.php
@@ -14,7 +14,7 @@ class RestLinkManagerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
- public static $modules = ['rest', 'rest_test', 'system'];
+ public static $modules = ['serialization', 'rest', 'rest_test', 'system'];
/**
* {@inheritdoc}
diff --git a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php
new file mode 100644
index 0000000..753d3c1
--- /dev/null
+++ b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace Drupal\serialization\EventSubscriber;
+
+use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\Serializer\SerializerInterface;
+
+/**
+ * Handles default error responses in serialization formats.
+ */
+class DefaultExceptionSubscriber extends HttpExceptionSubscriberBase {
+
+ /**
+ * The serializer.
+ *
+ * @var \Symfony\Component\Serializer\Serializer
+ */
+ protected $serializer;
+
+ /**
+ * The available serialization formats.
+ *
+ * @var array
+ */
+ protected $serializerFormats = [];
+
+ /**
+ * DefaultExceptionSubscriber constructor.
+ *
+ * @param \Symfony\Component\Serializer\SerializerInterface $serializer
+ * The serializer service.
+ * @param array $serializer_formats
+ * The available serialization formats.
+ */
+ public function __construct(SerializerInterface $serializer, array $serializer_formats) {
+ $this->serializer = $serializer;
+ $this->serializerFormats = $serializer_formats;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getHandledFormats() {
+ return $this->serializerFormats;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static function getPriority() {
+ // This will fire after the most common HTML handler, since HTML requests
+ // are still more common than HTTP requests.
+ return -75;
+ }
+
+ /**
+ * Handles a 400 error for HTTP.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The event to process.
+ */
+ public function on400(GetResponseForExceptionEvent $event) {
+ $this->setEventResponse($event, Response::HTTP_BAD_REQUEST);
+ }
+
+ /**
+ * Handles a 403 error for HTTP.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The event to process.
+ */
+ public function on403(GetResponseForExceptionEvent $event) {
+ $this->setEventResponse($event, Response::HTTP_FORBIDDEN);
+ }
+
+ /**
+ * Handles a 404 error for HTTP.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The event to process.
+ */
+ public function on404(GetResponseForExceptionEvent $event) {
+ $this->setEventResponse($event, Response::HTTP_NOT_FOUND);
+ }
+
+ /**
+ * Handles a 405 error for HTTP.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The event to process.
+ */
+ public function on405(GetResponseForExceptionEvent $event) {
+ $this->setEventResponse($event, Response::HTTP_METHOD_NOT_ALLOWED);
+ }
+
+ /**
+ * Handles a 406 error for HTTP.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The event to process.
+ */
+ public function on406(GetResponseForExceptionEvent $event) {
+ $this->setEventResponse($event, Response::HTTP_NOT_ACCEPTABLE);
+ }
+
+ /**
+ * Handles a 422 error for HTTP.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The event to process.
+ */
+ public function on422(GetResponseForExceptionEvent $event) {
+ $this->setEventResponse($event, Response::HTTP_UNPROCESSABLE_ENTITY);
+ }
+
+ /**
+ * Sets the Response for the exception event.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+ * The current exception event.
+ * @param int $status
+ * The HTTP status code to set for the response.
+ */
+ protected function setEventResponse(GetResponseForExceptionEvent $event, $status) {
+ $format = $event->getRequest()->getRequestFormat();
+ $content = ['message' => $event->getException()->getMessage()];
+ $encoded_content = $this->serializer->serialize($content, $format);
+ $response = new Response($encoded_content, $status);
+ $event->setResponse($response);
+ }
+
+}
diff --git a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
index 9d6cf4f..d0d22a2 100644
--- a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
+++ b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
@@ -16,7 +16,7 @@ class ResponseGeneratorTest extends RESTTestBase {
*
* @var array
*/
- public static $modules = array('hal', 'rest', 'node');
+ public static $modules = array('hal', 'rest', 'node', 'basic_auth');
/**
* {@inheritdoc}
diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
index 19e1fcf..82169ad 100644
--- a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
+++ b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
@@ -265,7 +265,15 @@ abstract class UpdatePathTestBase extends WebTestBase {
}
// Ensure that the update hooks updated all entity schema.
- $this->assertFalse(\Drupal::service('entity.definition_update_manager')->needsUpdates(), 'After all updates ran, entity schema is up to date.');
+ $needs_updates = \Drupal::entityDefinitionUpdateManager()->needsUpdates();
+ $this->assertFalse($needs_updates, 'After all updates ran, entity schema is up to date.');
+ if ($needs_updates) {
+ foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $summary) {
+ foreach ($summary as $message) {
+ $this->fail($message);
+ }
+ }
+ }
}
/**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index b8f5f5f..1f4c3ad 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1138,7 +1138,11 @@ function system_update_8004() {
$manager = \Drupal::entityDefinitionUpdateManager();
foreach (array_keys(\Drupal::entityManager()
->getDefinitions()) as $entity_type_id) {
- $manager->updateEntityType($manager->getEntityType($entity_type_id));
+ // Only update the entity type if it already exists. This condition is
+ // needed in case new entity types are introduced after this update.
+ if ($entity_type = $manager->getEntityType($entity_type_id)) {
+ $manager->updateEntityType($entity_type);
+ }
}
}
diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/AuthenticationProviderPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/AuthenticationProviderPassTest.php
new file mode 100644
index 0000000..6f99d09
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/AuthenticationProviderPassTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Tests\Core\DependencyInjection\Compiler;
+
+use Drupal\Core\DependencyInjection\Compiler\AuthenticationProviderPass;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\Serializer\Serializer;
+
+/**
+ * @coversDefaultClass \Drupal\Core\DependencyInjection\Compiler\AuthenticationProviderPass
+ * @group DependencyInjection
+ */
+class AuthenticationProviderPassTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @covers ::process
+ */
+ public function testEncoders() {
+ $container = new ContainerBuilder();
+ $container->setDefinition('serializer', new Definition(Serializer::class, [[], []]));
+
+ $definition = new Definition('TestClass');
+ $definition->addTag('authentication_provider', ['provider_id' => 'bunny_auth']);
+ $definition->addTag('_provider', ['provider' => 'test_provider_a']);
+ $container->setDefinition('test_provider_a.authentication.bunny_auth', $definition);
+
+ $definition = new Definition('TestClass');
+ $definition->addTag('authentication_provider', ['provider_id' => 'llama_auth', 'priority' => 100]);
+ $definition->addTag('_provider', ['provider' => 'test_provider_a']);
+ $container->setDefinition('test_provider_a.authentication.llama_auth', $definition);
+
+ $definition = new Definition('TestClass');
+ $definition->addTag('authentication_provider', ['provider_id' => 'camel_auth', 'priority' => -100]);
+ $definition->addTag('_provider', ['provider' => 'test_provider_b']);
+ $container->setDefinition('test_provider_b.authentication.camel_auth', $definition);
+
+ $compiler_pass = new AuthenticationProviderPass();
+ $compiler_pass->process($container);
+
+ $this->assertEquals(['bunny_auth' => 'test_provider_a', 'llama_auth' => 'test_provider_a', 'camel_auth' => 'test_provider_b'], $container->getParameter('authentication_providers'));
+ }
+
+}