diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml index df4a9de..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', '@entity.manager', '@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/RestEndpoint.php b/core/modules/rest/src/Entity/RestEndpoint.php index 8c81fdb..278a71c 100644 --- a/core/modules/rest/src/Entity/RestEndpoint.php +++ b/core/modules/rest/src/Entity/RestEndpoint.php @@ -6,15 +6,10 @@ namespace Drupal\rest\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; -use Drupal\Core\Config\Entity\ThirdPartySettingsInterface; use Drupal\Core\Entity\Entity; -use Drupal\Core\Entity\EntityWithPluginCollectionInterface; -use Drupal\Core\Plugin\DefaultLazyPluginCollection; use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection; use Drupal\rest\RestEndpointInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\TaggedContainerInterface; use Symfony\Component\Routing\RouteCollection; /** @@ -37,6 +32,7 @@ * ) */ class RestEndpoint extends ConfigEntityBase implements RestEndpointInterface { + use ContainerAwareTrait; /** @@ -246,79 +242,6 @@ public function removeSupportedFormat($method, $format) { } /** - * {@inheritdoc} - */ - public function routes() { - $collection = new RouteCollection(); - - $plugin = $this->getResourcePlugin(); - $plugin_id = $plugin->getPluginId(); - $plugin_def = $plugin->getPluginDefinition(); - - $canonical_path = isset($plugin_def['uri_paths']['canonical']) ? $plugin_def['uri_paths']['canonical'] : '/' . strtr($plugin_id, ':', '/') . '/{id}'; - $create_path = isset($definition['uri_paths']['https://www.drupal.org/link-relations/create']) ? $definition['uri_paths']['https://www.drupal.org/link-relations/create'] : '/' . strtr($plugin_id, ':', '/'); - - $route_name = strtr($plugin_id, ':', '.'); - - $methods = $plugin->availableMethods(); - foreach ($methods as $method) { - if ($this->isRequestMethodEnabled($method)) { - // Check that authentication providers are defined. - if (!$this->supportsAuthenticationProviders($method)) { - $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $plugin_id)); - continue; - } - - // Check that formats are defined. - if (!$this->hasSupportedFormats($method)) { - $this->logger->error('At least one format must be defined for resource @id', array(':id' => $plugin_id)); - continue; - } - - $route = $plugin->getBaseRoute($canonical_path, $method); - $route->setRequirement('_access_rest_csrf', 'TRUE'); - $route->setOption('_auth', $this->getSupportedAuthenticationProviders($method)); - $route->setDefault('_rest_endpoint', $this->id()); - - $supported_formats = $this->getSupportedFormats($method); - switch ($method) { - case 'POST': - $route->setPath($create_path); - // Restrict the incoming HTTP Content-type header to the known - // serialization formats. - $route->addRequirements(array('_content_type_format' => implode('|', $supported_formats))); - $collection->add("rest.$route_name.$method", $route); - break; - case 'PATCH': - // Restrict the incoming HTTP Content-type header to the known - // serialization formats. - $route->addRequirements(array('_content_type_format' => implode('|', $supported_formats))); - $collection->add("rest.$route_name.$method", $route); - break; - - case 'GET': - case 'HEAD': - // Restrict GET and HEAD requests to the media type specified in the - // HTTP Accept headers. - foreach ($supported_formats as $format_name) { - // Expose one route per available format. - $format_route = clone $route; - $format_route->addRequirements(array('_format' => $format_name)); - $collection->add("rest.$route_name.$method.$format_name", $format_route); - } - break; - - default: - $collection->add("rest.$route_name.$method", $route); - break; - } - } - } - - return $collection; - } - - /** * Returns the plugin collections used by this entity. * * @return \Drupal\Component\Plugin\LazyPluginCollection[] @@ -442,7 +365,7 @@ public function onDependencyRemoval(array $dependencies) { * @throws \InvalidArgumentException */ protected function normaliseRestMethod($method) { - $valid_methods = [ 'GET', 'POST', 'PATCH', 'DELETE' ]; + $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.'); diff --git a/core/modules/rest/src/Plugin/ResourceInterface.php b/core/modules/rest/src/Plugin/ResourceInterface.php index 847729c..3fc4f4e 100644 --- a/core/modules/rest/src/Plugin/ResourceInterface.php +++ b/core/modules/rest/src/Plugin/ResourceInterface.php @@ -8,7 +8,6 @@ namespace Drupal\rest\Plugin; use Drupal\Component\Plugin\PluginInspectionInterface; -use Symfony\Component\Routing\Route; /** * Specifies the publicly available methods of a resource plugin. @@ -21,6 +20,7 @@ * @ingroup third_party */ interface ResourceInterface extends PluginInspectionInterface { + /** * Provides an array of permissions suitable for .permissions.yml files. * diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 84c140f..38e7447 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageException; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\rest\Plugin\ResourceBase; use Drupal\rest\ResourceResponse; use Psr\Log\LoggerInterface; @@ -41,7 +42,7 @@ class EntityResource extends ResourceBase implements DependentPluginInterface { * * @var \Drupal\Core\Entity\EntityTypeInterface */ - protected $entity_type_definition; + protected $entityType; /** * Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object. @@ -52,17 +53,17 @@ class EntityResource extends ResourceBase implements DependentPluginInterface { * The plugin_id for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager + * @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. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, $serializer_formats, + 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->entity_type_definition = $entity_manager->getDefinition($plugin_definition['entity_type']); + $this->entityType = $entity_type_manager->getDefinition($plugin_definition['entity_type']); } /** @@ -73,7 +74,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('entity.manager'), + $container->get('entity_type.manager'), $container->getParameter('serializer.formats'), $container->get('logger.factory')->get('rest') ); @@ -293,8 +294,8 @@ public function getBaseRoute($canonical_path, $method) { * {@inheritdoc} */ public function calculateDependencies() { - if (isset($this->entity_type_definition)) { - return [ 'module' => [ $this->entity_type_definition->getProvider() ] ]; + if (isset($this->entityType)) { + return ['module' => [$this->entityType->getProvider()]]; } } } diff --git a/core/modules/rest/src/RestEndpointInterface.php b/core/modules/rest/src/RestEndpointInterface.php index 30d14b6..93618d7 100644 --- a/core/modules/rest/src/RestEndpointInterface.php +++ b/core/modules/rest/src/RestEndpointInterface.php @@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityWithPluginCollectionInterface; interface RestEndpointInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface { + /** * Retrieves the plugin id of the resource for this REST endpoint. * @@ -126,15 +127,4 @@ public function addSupportedFormat($method, $format); */ public function removeSupportedFormat($method, $format); - - /** - * Returns a collection of routes with URL path information for this REST endpoint. - * - * This method determines where a resource is reachable, what path - * replacements are used, the required HTTP method for the operation etc. - * - * @return \Symfony\Component\Routing\RouteCollection - * A collection of routes that should be registered for this REST endpoint. - */ - public function routes(); } diff --git a/core/modules/rest/src/RestPermissions.php b/core/modules/rest/src/RestPermissions.php index e68c60d..d134abb 100644 --- a/core/modules/rest/src/RestPermissions.php +++ b/core/modules/rest/src/RestPermissions.php @@ -8,7 +8,7 @@ namespace Drupal\rest; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\rest\Plugin\Type\ResourcePluginManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -29,26 +29,26 @@ class RestPermissions implements ContainerInjectionInterface { * * @var \Drupal\Core\Entity\EntityManagerInterface */ - protected $endpoint_storage; + protected $endpointStorage; /** * Constructs a new RestPermissions instance. * * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $rest_plugin_manager * The rest resource plugin manager. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. */ - public function __construct(ResourcePluginManager $rest_plugin_manager, EntityManagerInterface $entity_manager) { + public function __construct(ResourcePluginManager $rest_plugin_manager, EntityTypeManagerInterface $entity_type_manager) { $this->restPluginManager = $rest_plugin_manager; - $this->endpoint_storage = $entity_manager->getStorage('rest_endpoint'); + $this->endpointStorage = $entity_type_manager->getStorage('rest_endpoint'); } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('plugin.manager.rest'), $container->get('entity.manager')); + return new static($container->get('plugin.manager.rest'), $container->get('entity_type.manager')); } /** @@ -59,7 +59,7 @@ public static function create(ContainerInterface $container) { public function permissions() { $permissions = []; /** @var \Drupal\rest\RestEndpointInterface[] $endpoints */ - $endpoints = $this->endpoint_storage->loadMultiple(); + $endpoints = $this->endpointStorage->loadMultiple(); foreach ($endpoints as $endpoint) { $plugin = $endpoint->getResourcePlugin(); $permissions = array_merge($permissions, $plugin->permissions()); diff --git a/core/modules/rest/src/RestServiceProvider.php b/core/modules/rest/src/RestServiceProvider.php index ed129e2..9e51da4 100644 --- a/core/modules/rest/src/RestServiceProvider.php +++ b/core/modules/rest/src/RestServiceProvider.php @@ -11,7 +11,7 @@ use Drupal\Core\DependencyInjection\ServiceProviderInterface; /** - * Rest dependency injection container. + * Registers rest dependencies in the container. */ class RestServiceProvider implements ServiceProviderInterface { diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php index 4d5e108..850dffe 100644 --- a/core/modules/rest/src/Routing/ResourceRoutes.php +++ b/core/modules/rest/src/Routing/ResourceRoutes.php @@ -7,9 +7,10 @@ namespace Drupal\rest\Routing; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\rest\Plugin\Type\ResourcePluginManager; +use Drupal\rest\RestEndpointInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Routing\RouteCollection; @@ -30,7 +31,7 @@ class ResourceRoutes extends RouteSubscriberBase { * * @var \Drupal\Core\Entity\EntityManagerInterface */ - protected $endpoint_storage; + protected $endpointStorage; /** * A logger instance. @@ -44,14 +45,14 @@ class ResourceRoutes extends RouteSubscriberBase { * * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager * The resource plugin manager. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager + * @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, EntityManagerInterface $entity_manager, LoggerInterface $logger) { + public function __construct(ResourcePluginManager $manager, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) { $this->manager = $manager; - $this->endpoint_storage = $entity_manager->getStorage('rest_endpoint'); + $this->endpointStorage = $entity_type_manager->getStorage('rest_endpoint'); $this->logger = $logger; } @@ -65,11 +66,93 @@ public function __construct(ResourcePluginManager $manager, EntityManagerInterfa protected function alterRoutes(RouteCollection $collection) { // Iterate over all enabled REST endpoints. /** @var \Drupal\rest\RestEndpointInterface[] $endpoints */ - $endpoints = $this->endpoint_storage->loadMultiple(); + $endpoints = $this->endpointStorage->loadMultiple(); foreach ($endpoints as $endpoint) { - $endpoint_routes = $endpoint->routes(); + $endpoint_routes = $this->getRoutesForEndpoint($endpoint); $collection->addCollection($endpoint_routes); } } + /** + * Provides all routes for a given REST endpoint. + * + * This method determines where a resource is reachable, what path + * replacements are used, the required HTTP method for the operation etc. + * + * @param \Drupal\rest\RestEndpointInterface $rest_endpoint + * The rest endpoint. + * + * @return \Symfony\Component\Routing\RouteCollection + * The route collection. + */ + protected function getRoutesForEndpoint(RestEndpointInterface $rest_endpoint) { + $collection = new RouteCollection(); + + $plugin = $rest_endpoint->getResourcePlugin(); + $plugin_id = $plugin->getPluginId(); + $plugin_def = $plugin->getPluginDefinition(); + + $canonical_path = isset($plugin_def['uri_paths']['canonical']) ? $plugin_def['uri_paths']['canonical'] : '/' . strtr($plugin_id, ':', '/') . '/{id}'; + $create_path = isset($definition['uri_paths']['https://www.drupal.org/link-relations/create']) ? $definition['uri_paths']['https://www.drupal.org/link-relations/create'] : '/' . strtr($plugin_id, ':', '/'); + + $route_name = strtr($plugin_id, ':', '.'); + + $methods = $plugin->availableMethods(); + foreach ($methods as $method) { + if ($rest_endpoint->isRequestMethodEnabled($method)) { + // Check that authentication providers are defined. + if (!$rest_endpoint->supportsAuthenticationProviders($method)) { + $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $plugin_id)); + continue; + } + + // Check that formats are defined. + if (!$rest_endpoint->hasSupportedFormats($method)) { + $this->logger->error('At least one format must be defined for resource @id', array(':id' => $plugin_id)); + continue; + } + + $route = $plugin->getBaseRoute($canonical_path, $method); + $route->setRequirement('_access_rest_csrf', 'TRUE'); + $route->setOption('_auth', $rest_endpoint->getSupportedAuthenticationProviders($method)); + $route->setDefault('_rest_endpoint', $rest_endpoint->id()); + + $supported_formats = $rest_endpoint->getSupportedFormats($method); + switch ($method) { + case 'POST': + $route->setPath($create_path); + // Restrict the incoming HTTP Content-type header to the known + // serialization formats. + $route->addRequirements(array('_content_type_format' => implode('|', $supported_formats))); + $collection->add("rest.$route_name.$method", $route); + break; + case 'PATCH': + // Restrict the incoming HTTP Content-type header to the known + // serialization formats. + $route->addRequirements(array('_content_type_format' => implode('|', $supported_formats))); + $collection->add("rest.$route_name.$method", $route); + break; + + case 'GET': + case 'HEAD': + // Restrict GET and HEAD requests to the media type specified in the + // HTTP Accept headers. + foreach ($supported_formats as $format_name) { + // Expose one route per available format. + $format_route = clone $route; + $format_route->addRequirements(array('_format' => $format_name)); + $collection->add("rest.$route_name.$method.$format_name", $format_route); + } + break; + + default: + $collection->add("rest.$route_name.$method", $route); + break; + } + } + } + + return $collection; + } + } diff --git a/core/modules/rest/src/Tests/PageCacheTest.php b/core/modules/rest/src/Tests/PageCacheTest.php index 6d6bf58..d7d9077 100644 --- a/core/modules/rest/src/Tests/PageCacheTest.php +++ b/core/modules/rest/src/Tests/PageCacheTest.php @@ -50,7 +50,7 @@ public function testConfigChangePageCache() { // Trigger an endpoint save which should clear the page cache, so we should // get a cache miss now for the same request. - $this->endpoint_storage->load('entity__entity_test')->save(); + $this->endpointStorage->load('entity__entity_test')->save(); $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType); $this->assertResponse(200, 'HTTP response code is correct.'); $this->assertHeader('x-drupal-cache', 'MISS'); diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index f9445b8..fa75eaf 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -20,7 +20,7 @@ * * @var \Drupal\Core\Entity\EntityStorageInterface */ - protected $endpoint_storage; + protected $endpointStorage; /** * The default serialization format to use for testing REST operations. @@ -70,7 +70,7 @@ protected function setUp() { $this->defaultFormat = 'hal_json'; $this->defaultMimeType = 'application/hal+json'; $this->defaultAuth = array('cookie'); - $this->endpoint_storage = $this->container->get('entity.manager')->getStorage('rest_endpoint'); + $this->endpointStorage = $this->container->get('entity_type.manager')->getStorage('rest_endpoint'); // Create a test content type for node testing. if (in_array('node', static::$modules)) { $this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest')); @@ -263,8 +263,8 @@ protected function enableService($resource_type, $method = 'GET', $format = NULL $endpoint_id = str_replace(':', '__', $resource_type); // get entity by id /** @var \Drupal\rest\RestEndpointInterface $endpoint */ - $endpoint = $this->endpoint_storage->load($endpoint_id); - $endpoint = ($endpoint !== NULL) ? $endpoint : $this->endpoint_storage->create(['id' => $endpoint_id]); + $endpoint = $this->endpointStorage->load($endpoint_id); + $endpoint = ($endpoint !== NULL) ? $endpoint : $this->endpointStorage->create(['id' => $endpoint_id]); if ($format == NULL) { $format = $this->defaultFormat; @@ -279,7 +279,7 @@ protected function enableService($resource_type, $method = 'GET', $format = NULL } $endpoint->save(); } else { - foreach ($this->endpoint_storage->loadMultiple() AS $endpoint) { + foreach ($this->endpointStorage->loadMultiple() AS $endpoint) { $endpoint->delete(); } } diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php index f759388..22bbea1 100644 --- a/core/modules/rest/src/Tests/ResourceTest.php +++ b/core/modules/rest/src/Tests/ResourceTest.php @@ -6,7 +6,6 @@ */ namespace Drupal\rest\Tests; - use Drupal\Core\Session\AccountInterface; use Drupal\user\Entity\Role; @@ -36,7 +35,7 @@ class ResourceTest extends RESTTestBase { */ protected function setUp() { parent::setUp(); - // Create an entity programmatically. + // Create an entity programmatic. $this->entity = $this->entityCreate('entity_test'); $this->entity->save(); @@ -50,10 +49,11 @@ protected function setUp() { */ public function testFormats() { /** @var \Drupal\rest\RestEndpointInterface $endpoint */ - $endpoint = $this->endpoint_storage->create(['id' => 'entity__entity_test']); + $endpoint = $this->endpointStorage->create(['id' => 'entity__entity_test']); + // Attempt to enable the resource. $endpoint ->addSupportedAuthenticationProvider('GET', 'basic_auth') - ->save();// Attempt to enable the resource. + ->save(); $this->rebuildCache(); // Verify that accessing the resource returns 406. @@ -72,10 +72,11 @@ public function testFormats() { */ public function testAuthentication() { /** @var \Drupal\rest\RestEndpointInterface $endpoint */ - $endpoint = $this->endpoint_storage->create(['id' => 'entity__entity_test']); + $endpoint = $this->endpointStorage->create(['id' => 'entity__entity_test']); + // Attempt to enable the resource. $endpoint ->addSupportedFormat('GET', 'hal_json') - ->save();// Attempt to enable the resource. + ->save(); $this->rebuildCache(); // Verify that accessing the resource returns 401.