updated core from 8.4 to 8.5 : bug with login_destination
This commit is contained in:
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace Drupal\rest\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\CacheableResponse;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\rest\ResourceResponseInterface;
|
||||
use Drupal\serialization\Normalizer\CacheableNormalizerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
@@ -79,7 +81,7 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
|
||||
* Determines the format to respond in.
|
||||
*
|
||||
* Respects the requested format if one is specified. However, it is common to
|
||||
* forget to specify a request format in case of a POST or PATCH. Rather than
|
||||
* forget to specify a response format in case of a POST or PATCH. Rather than
|
||||
* simply throwing an error, we apply the robustness principle: when POSTing
|
||||
* or PATCHing using a certain format, you probably expect a response in that
|
||||
* same format.
|
||||
@@ -94,43 +96,53 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
|
||||
*/
|
||||
public function getResponseFormat(RouteMatchInterface $route_match, Request $request) {
|
||||
$route = $route_match->getRouteObject();
|
||||
$acceptable_request_formats = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : [];
|
||||
$acceptable_content_type_formats = $route->hasRequirement('_content_type_format') ? explode('|', $route->getRequirement('_content_type_format')) : [];
|
||||
$acceptable_formats = $request->isMethodCacheable() ? $acceptable_request_formats : $acceptable_content_type_formats;
|
||||
$acceptable_response_formats = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : [];
|
||||
$acceptable_request_formats = $route->hasRequirement('_content_type_format') ? explode('|', $route->getRequirement('_content_type_format')) : [];
|
||||
$acceptable_formats = $request->isMethodCacheable() ? $acceptable_response_formats : $acceptable_request_formats;
|
||||
|
||||
$requested_format = $request->getRequestFormat();
|
||||
$content_type_format = $request->getContentType();
|
||||
|
||||
// If an acceptable format is requested, then use that. Otherwise, including
|
||||
// and particularly when the client forgot to specify a format, then use
|
||||
// heuristics to select the format that is most likely expected.
|
||||
if (in_array($requested_format, $acceptable_formats)) {
|
||||
// If an acceptable response format is requested, then use that. Otherwise,
|
||||
// including and particularly when the client forgot to specify a response
|
||||
// format, then use heuristics to select the format that is most likely
|
||||
// expected.
|
||||
if (in_array($requested_format, $acceptable_response_formats, TRUE)) {
|
||||
return $requested_format;
|
||||
}
|
||||
|
||||
// If a request body is present, then use the format corresponding to the
|
||||
// request body's Content-Type for the response, if it's an acceptable
|
||||
// format for the request.
|
||||
elseif (!empty($request->getContent()) && in_array($content_type_format, $acceptable_content_type_formats)) {
|
||||
if (!empty($request->getContent()) && in_array($content_type_format, $acceptable_request_formats, TRUE)) {
|
||||
return $content_type_format;
|
||||
}
|
||||
|
||||
// Otherwise, use the first acceptable format.
|
||||
elseif (!empty($acceptable_formats)) {
|
||||
if (!empty($acceptable_formats)) {
|
||||
return $acceptable_formats[0];
|
||||
}
|
||||
|
||||
// Sometimes, there are no acceptable formats, e.g. DELETE routes.
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a resource response body.
|
||||
*
|
||||
* Serialization can invoke rendering (e.g., generating URLs), but the
|
||||
* serialization API does not provide a mechanism to collect the
|
||||
* bubbleable metadata associated with that (e.g., language and other
|
||||
* contexts), so instead, allow those to "leak" and collect them here in
|
||||
* a render context.
|
||||
* During serialization, encoders and normalizers are able to explicitly
|
||||
* bubble cacheability metadata via the 'cacheability' key-value pair in the
|
||||
* received context. This bubbled cacheability metadata will be applied to the
|
||||
* the response.
|
||||
*
|
||||
* In versions of Drupal prior to 8.5, implicit bubbling of cacheability
|
||||
* metadata was allowed because there was no explicit cacheability metadata
|
||||
* bubbling API. To maintain backwards compatibility, we continue to support
|
||||
* this, but support for this will be dropped in Drupal 9.0.0. This is
|
||||
* especially useful when interacting with APIs that implicitly invoke
|
||||
* rendering (for example: generating URLs): this allows those to "leak", and
|
||||
* we collect their bubbled cacheability metadata automatically in a render
|
||||
* context.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
@@ -150,14 +162,25 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
|
||||
|
||||
// If there is data to send, serialize and set it as the response body.
|
||||
if ($data !== NULL) {
|
||||
$serialization_context = [
|
||||
'request' => $request,
|
||||
CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata(),
|
||||
];
|
||||
|
||||
// @deprecated In Drupal 8.5.0, will be removed before Drupal 9.0.0. Use
|
||||
// explicit cacheability metadata bubbling instead. (The wrapping call to
|
||||
// executeInRenderContext() will be removed before Drupal 9.0.0.)
|
||||
$context = new RenderContext();
|
||||
$output = $this->renderer
|
||||
->executeInRenderContext($context, function () use ($serializer, $data, $format) {
|
||||
return $serializer->serialize($data, $format);
|
||||
->executeInRenderContext($context, function () use ($serializer, $data, $format, $serialization_context) {
|
||||
return $serializer->serialize($data, $format, $serialization_context);
|
||||
});
|
||||
|
||||
if ($response instanceof CacheableResponseInterface && !$context->isEmpty()) {
|
||||
$response->addCacheableDependency($context->pop());
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
if (!$context->isEmpty()) {
|
||||
@trigger_error('Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling. See https://www.drupal.org/node/2918937', E_USER_DEPRECATED);
|
||||
$response->addCacheableDependency($context->pop());
|
||||
}
|
||||
$response->addCacheableDependency($serialization_context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY]);
|
||||
}
|
||||
|
||||
$response->setContent($output);
|
||||
|
||||
@@ -37,6 +37,7 @@ class RestConfigSubscriber implements EventSubscriberInterface {
|
||||
*/
|
||||
public function onSave(ConfigCrudEvent $event) {
|
||||
$saved_config = $event->getConfig();
|
||||
// @see \Drupal\rest\Plugin\rest\resource\EntityResource::permissions()
|
||||
if ($saved_config->getName() === 'rest.settings' && $event->isChanged('bc_entity_resource_permissions')) {
|
||||
$this->routerBuilder->setRebuildNeeded();
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ class EntityDeriver implements ContainerDeriverInterface {
|
||||
if (!isset($this->derivatives)) {
|
||||
// Add in the default plugin configuration and the resource type.
|
||||
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
if ($entity_type->isInternal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->derivatives[$entity_type_id] = [
|
||||
'id' => 'entity:' . $entity_type_id,
|
||||
'entity_type' => $entity_type_id,
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Drupal\rest\Plugin;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Routing\BcRoute;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
@@ -114,29 +115,24 @@ abstract class ResourceBase extends PluginBase implements ContainerFactoryPlugin
|
||||
|
||||
$methods = $this->availableMethods();
|
||||
foreach ($methods as $method) {
|
||||
$route = $this->getBaseRoute($canonical_path, $method);
|
||||
$path = $method === 'POST'
|
||||
? $create_path
|
||||
: $canonical_path;
|
||||
$route = $this->getBaseRoute($path, $method);
|
||||
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
$route->setPath($create_path);
|
||||
$collection->add("$route_name.$method", $route);
|
||||
break;
|
||||
// Note that '_format' and '_content_type_format' route requirements are
|
||||
// added in ResourceRoutes::getRoutesForResourceConfig().
|
||||
$collection->add("$route_name.$method", $route);
|
||||
|
||||
case 'GET':
|
||||
case 'HEAD':
|
||||
// Restrict GET and HEAD requests to the media type specified in the
|
||||
// HTTP Accept headers.
|
||||
foreach ($this->serializerFormats as $format_name) {
|
||||
// Expose one route per available format.
|
||||
$format_route = clone $route;
|
||||
$format_route->addRequirements(['_format' => $format_name]);
|
||||
$collection->add("$route_name.$method.$format_name", $format_route);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$collection->add("$route_name.$method", $route);
|
||||
break;
|
||||
// BC: the REST module originally created per-format GET routes, instead
|
||||
// of a single route. To minimize the surface of this BC layer, this uses
|
||||
// route definitions that are as empty as possible, plus an outbound route
|
||||
// processor.
|
||||
// @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
|
||||
if ($method === 'GET' || $method === 'HEAD') {
|
||||
foreach ($this->serializerFormats as $format_name) {
|
||||
$collection->add("$route_name.$method.$format_name", (new BcRoute())->setRequirement('_format', $format_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\TypedData\PrimitiveInterface;
|
||||
use Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException;
|
||||
use Drupal\rest\Plugin\ResourceBase;
|
||||
use Drupal\rest\ResourceResponse;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -122,7 +122,7 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
public function get(EntityInterface $entity) {
|
||||
$entity_access = $entity->access('view', NULL, TRUE);
|
||||
if (!$entity_access->isAllowed()) {
|
||||
throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'view'));
|
||||
throw new CacheableAccessDeniedHttpException($entity_access, $entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'view'));
|
||||
}
|
||||
|
||||
$response = new ResourceResponse($entity, 200);
|
||||
@@ -201,41 +201,6 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values from the field item list casted to the correct type.
|
||||
*
|
||||
* Values are casted to the correct type so we can determine whether or not
|
||||
* something has changed. REST formats such as JSON support typed data but
|
||||
* Drupal's database API will return values as strings. Currently, only
|
||||
* primitive data types know how to cast their values to the correct type.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldItemListInterface $field_item_list
|
||||
* The field item list to retrieve its data from.
|
||||
*
|
||||
* @return mixed[][]
|
||||
* The values from the field item list casted to the correct type. The array
|
||||
* of values returned is a multidimensional array keyed by delta and the
|
||||
* property name.
|
||||
*/
|
||||
protected function getCastedValueFromFieldItemList(FieldItemListInterface $field_item_list) {
|
||||
$value = $field_item_list->getValue();
|
||||
|
||||
foreach ($value as $delta => $field_item_value) {
|
||||
/** @var \Drupal\Core\Field\FieldItemInterface $field_item */
|
||||
$field_item = $field_item_list->get($delta);
|
||||
$properties = $field_item->getProperties(TRUE);
|
||||
// Foreach field value we check whether we know the underlying property.
|
||||
// If we exists we try to cast the value.
|
||||
foreach ($field_item_value as $property_name => $property_value) {
|
||||
if (isset($properties[$property_name]) && ($property = $field_item->get($property_name)) && $property instanceof PrimitiveInterface) {
|
||||
$value[$delta][$property_name] = $property->getCastedValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to entity PATCH requests.
|
||||
*
|
||||
@@ -262,38 +227,18 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'update'));
|
||||
}
|
||||
|
||||
// Overwrite the received properties.
|
||||
$entity_keys = $entity->getEntityType()->getKeys();
|
||||
// Overwrite the received fields.
|
||||
foreach ($entity->_restSubmittedFields as $field_name) {
|
||||
$field = $entity->get($field_name);
|
||||
|
||||
// Entity key fields need special treatment: together they uniquely
|
||||
// identify the entity. Therefore it does not make sense to modify any of
|
||||
// them. However, rather than throwing an error, we just ignore them as
|
||||
// long as their specified values match their current values.
|
||||
if (in_array($field_name, $entity_keys, TRUE)) {
|
||||
// @todo Work around the wrong assumption that entity keys need special
|
||||
// treatment, when only read-only fields need it.
|
||||
// This will be fixed in https://www.drupal.org/node/2824851.
|
||||
if ($entity->getEntityTypeId() == 'comment' && $field_name == 'status' && !$original_entity->get($field_name)->access('edit')) {
|
||||
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
||||
}
|
||||
|
||||
// Unchanged values for entity keys don't need access checking.
|
||||
if ($this->getCastedValueFromFieldItemList($original_entity->get($field_name)) === $this->getCastedValueFromFieldItemList($entity->get($field_name))) {
|
||||
continue;
|
||||
}
|
||||
// It is not possible to set the language to NULL as it is automatically
|
||||
// re-initialized. As it must not be empty, skip it if it is.
|
||||
elseif (isset($entity_keys['langcode']) && $field_name === $entity_keys['langcode'] && $field->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// It is not possible to set the language to NULL as it is automatically
|
||||
// re-initialized. As it must not be empty, skip it if it is.
|
||||
// @todo Remove in https://www.drupal.org/project/drupal/issues/2933408.
|
||||
if ($entity->getEntityType()->hasKey('langcode') && $field_name === $entity->getEntityType()->getKey('langcode') && $field->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$original_entity->get($field_name)->access('edit')) {
|
||||
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
||||
if ($this->checkPatchFieldAccess($original_entity->get($field_name), $field)) {
|
||||
$original_entity->set($field_name, $field->getValue());
|
||||
}
|
||||
$original_entity->set($field_name, $field->getValue());
|
||||
}
|
||||
|
||||
// Validate the received data before saving.
|
||||
@@ -310,6 +255,49 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given field should be PATCHed.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldItemListInterface $original_field
|
||||
* The original (stored) value for the field.
|
||||
* @param \Drupal\Core\Field\FieldItemListInterface $received_field
|
||||
* The received value for the field.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the field should be PATCHed or not.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* Thrown when the user sending the request is not allowed to update the
|
||||
* field. Only thrown when the user could not abuse this information to
|
||||
* determine the stored value.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function checkPatchFieldAccess(FieldItemListInterface $original_field, FieldItemListInterface $received_field) {
|
||||
// If the user is allowed to edit the field, it is always safe to set the
|
||||
// received value. We may be setting an unchanged value, but that is ok.
|
||||
if ($original_field->access('edit')) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// The user might not have access to edit the field, but still needs to
|
||||
// submit the current field value as part of the PATCH request. For
|
||||
// example, the entity keys required by denormalizers. Therefore, if the
|
||||
// received value equals the stored value, return FALSE without throwing an
|
||||
// exception. But only for fields that the user has access to view, because
|
||||
// the user has no legitimate way of knowing the current value of fields
|
||||
// that they are not allowed to view, and we must not make the presence or
|
||||
// absence of a 403 response a way to find that out.
|
||||
if ($original_field->access('view') && $original_field->equals($received_field)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// It's helpful and safe to let the user know when they are not allowed to
|
||||
// update a field.
|
||||
$field_name = $received_field->getName();
|
||||
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to entity DELETE requests.
|
||||
*
|
||||
|
||||
@@ -70,7 +70,7 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mimeType;
|
||||
protected $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
@@ -109,6 +109,13 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
*/
|
||||
protected $authenticationProviders;
|
||||
|
||||
/**
|
||||
* The serialization format providers, keyed by format.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $formatProviders;
|
||||
|
||||
/**
|
||||
* Constructs a RestExport object.
|
||||
*
|
||||
@@ -126,8 +133,10 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
* The renderer.
|
||||
* @param string[] $authentication_providers
|
||||
* The authentication providers, keyed by ID.
|
||||
* @param string[] $serializer_format_providers
|
||||
* The serialization format providers, keyed by format.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer, array $authentication_providers) {
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer, array $authentication_providers, array $serializer_format_providers) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
|
||||
|
||||
$this->renderer = $renderer;
|
||||
@@ -139,6 +148,7 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
$this->authenticationProviderIds = array_keys($authentication_providers);
|
||||
// For BC reasons we keep around authenticationProviders as before.
|
||||
$this->authenticationProviders = $authentication_providers;
|
||||
$this->formatProviders = $serializer_format_providers;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,8 +162,8 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
$container->get('router.route_provider'),
|
||||
$container->get('state'),
|
||||
$container->get('renderer'),
|
||||
$container->getParameter('authentication_providers')
|
||||
|
||||
$container->getParameter('authentication_providers'),
|
||||
$container->getParameter('serializer.format_providers')
|
||||
);
|
||||
}
|
||||
/**
|
||||
@@ -162,21 +172,22 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
|
||||
parent::initDisplay($view, $display, $options);
|
||||
|
||||
$request_content_type = $this->view->getRequest()->getRequestFormat();
|
||||
// Only use the requested content type if it's not 'html'. If it is then
|
||||
// default to 'json' to aid debugging.
|
||||
// @todo Remove the need for this when we have better content negotiation.
|
||||
if ($request_content_type != 'html') {
|
||||
$this->setContentType($request_content_type);
|
||||
}
|
||||
// If the requested content type is 'html' and the default 'json' is not
|
||||
// selected as a format option in the view display, fallback to the first
|
||||
// format in the array.
|
||||
elseif (!empty($options['style']['options']['formats']) && !isset($options['style']['options']['formats'][$this->getContentType()])) {
|
||||
$this->setContentType(reset($options['style']['options']['formats']));
|
||||
// If the default 'json' format is not selected as a format option in the
|
||||
// view display, fallback to the first format available for the default.
|
||||
if (!empty($options['style']['options']['formats']) && !isset($options['style']['options']['formats'][$this->getContentType()])) {
|
||||
$default_format = reset($options['style']['options']['formats']);
|
||||
$this->setContentType($default_format);
|
||||
}
|
||||
|
||||
$this->setMimeType($this->view->getRequest()->getMimeType($this->contentType));
|
||||
// Only use the requested content type if it's not 'html'. This allows
|
||||
// still falling back to the default for things like views preview.
|
||||
$request_content_type = $this->view->getRequest()->getRequestFormat();
|
||||
|
||||
if ($request_content_type !== 'html') {
|
||||
$this->setContentType($request_content_type);
|
||||
}
|
||||
|
||||
$this->setMimeType($this->view->getRequest()->getMimeType($this->getContentType()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,17 +361,21 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
|
||||
if ($route = $collection->get("view.$view_id.$display_id")) {
|
||||
$style_plugin = $this->getPlugin('style');
|
||||
// REST exports should only respond to get methods.
|
||||
|
||||
// REST exports should only respond to GET methods.
|
||||
$route->setMethods(['GET']);
|
||||
|
||||
// Format as a string using pipes as a delimiter.
|
||||
if ($formats = $style_plugin->getFormats()) {
|
||||
// Allow a REST Export View to be returned with an HTML-only accept
|
||||
// format. That allows browsers or other non-compliant systems to access
|
||||
// the view, as it is unlikely to have a conflicting HTML representation
|
||||
// anyway.
|
||||
$route->setRequirement('_format', implode('|', $formats + ['html']));
|
||||
$formats = $style_plugin->getFormats();
|
||||
|
||||
// If there are no configured formats, add all formats that serialization
|
||||
// is known to support.
|
||||
if (!$formats) {
|
||||
$formats = $this->getFormatOptions();
|
||||
}
|
||||
|
||||
// Format as a string using pipes as a delimiter.
|
||||
$route->setRequirement('_format', implode('|', $formats));
|
||||
|
||||
// Add authentication to the route if it was set. If no authentication was
|
||||
// set, the default authentication will be used, which is cookie based by
|
||||
// default.
|
||||
@@ -444,15 +459,15 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
$build['#suffix'] = '</pre>';
|
||||
unset($build['#markup']);
|
||||
}
|
||||
elseif ($this->view->getRequest()->getFormat($this->view->element['#content_type']) !== 'html') {
|
||||
// This display plugin is primarily for returning non-HTML formats.
|
||||
// However, we still invoke the renderer to collect cacheability metadata.
|
||||
// Because the renderer is designed for HTML rendering, it filters
|
||||
// #markup for XSS unless it is already known to be safe, but that filter
|
||||
// only works for HTML. Therefore, we mark the contents as safe to bypass
|
||||
// the filter. So long as we are returning this in a non-HTML response
|
||||
// (checked above), this is safe, because an XSS attack only works when
|
||||
// executed by an HTML agent.
|
||||
else {
|
||||
// This display plugin is for returning non-HTML formats. However, we
|
||||
// still invoke the renderer to collect cacheability metadata. Because the
|
||||
// renderer is designed for HTML rendering, it filters #markup for XSS
|
||||
// unless it is already known to be safe, but that filter only works for
|
||||
// HTML. Therefore, we mark the contents as safe to bypass the filter. So
|
||||
// long as we are returning this in a non-HTML response,
|
||||
// this is safe, because an XSS attack only works when executed by an HTML
|
||||
// agent.
|
||||
// @todo Decide how to support non-HTML in the render API in
|
||||
// https://www.drupal.org/node/2501313.
|
||||
$build['#markup'] = ViewsRenderPipelineMarkup::create($build['#markup']);
|
||||
@@ -492,4 +507,15 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of format options.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of format options. Both key and value are the same.
|
||||
*/
|
||||
protected function getFormatOptions() {
|
||||
$formats = array_keys($this->formatProviders);
|
||||
return array_combine($formats, $formats);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,64 +2,103 @@
|
||||
|
||||
namespace Drupal\rest;
|
||||
|
||||
use Drupal\Component\Utility\ArgumentsResolver;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Drupal\rest\Plugin\ResourceInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* Acts as intermediate request forwarder for resource plugins.
|
||||
*
|
||||
* @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
|
||||
*/
|
||||
class RequestHandler implements ContainerAwareInterface, ContainerInjectionInterface {
|
||||
|
||||
use ContainerAwareTrait;
|
||||
class RequestHandler implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The resource configuration storage.
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $resourceStorage;
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The serializer.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* Creates a new RequestHandler instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
|
||||
* The resource configuration storage.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface $serializer
|
||||
* The serializer.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $entity_storage) {
|
||||
$this->resourceStorage = $entity_storage;
|
||||
public function __construct(ConfigFactoryInterface $config_factory, SerializerInterface $serializer) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity_type.manager')->getStorage('rest_resource_config'));
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('serializer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a web API request.
|
||||
* Handles a REST API request.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HTTP request object.
|
||||
* @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
|
||||
* REST resource config entity ID.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response object.
|
||||
* @return \Drupal\rest\ResourceResponseInterface|\Symfony\Component\HttpFoundation\Response
|
||||
* The REST resource response.
|
||||
*/
|
||||
public function handle(RouteMatchInterface $route_match, Request $request) {
|
||||
public function handle(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
|
||||
$response = $this->delegateToRestResourcePlugin($route_match, $request, $_rest_resource_config->getResourcePlugin());
|
||||
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
$response->addCacheableDependency($_rest_resource_config);
|
||||
// Add global rest settings config's cache tag, for BC flags.
|
||||
// @see \Drupal\rest\Plugin\rest\resource\EntityResource::permissions()
|
||||
// @see \Drupal\rest\EventSubscriber\RestConfigSubscriber
|
||||
// @todo Remove in https://www.drupal.org/node/2893804
|
||||
$response->addCacheableDependency($this->configFactory->get('rest.settings'));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the normalized HTTP request method of the matched route.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
*
|
||||
* @return string
|
||||
* The normalized HTTP request method.
|
||||
*/
|
||||
protected static function getNormalizedRequestMethod(RouteMatchInterface $route_match) {
|
||||
// Symfony is built to transparently map HEAD requests to a GET request. In
|
||||
// the case of the REST module's RequestHandler though, we essentially have
|
||||
// our own light-weight routing system on top of the Drupal/symfony routing
|
||||
@@ -73,18 +112,34 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
|
||||
// @see \Symfony\Component\HttpFoundation\Response::prepare()
|
||||
$method = strtolower($route_match->getRouteObject()->getMethods()[0]);
|
||||
assert(count($route_match->getRouteObject()->getMethods()) === 1);
|
||||
return $method;
|
||||
}
|
||||
|
||||
$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();
|
||||
|
||||
/**
|
||||
* Deserializes request body, if any.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HTTP request object.
|
||||
* @param \Drupal\rest\Plugin\ResourceInterface $resource
|
||||
* The REST resource plugin.
|
||||
*
|
||||
* @return array|null
|
||||
* An object normalization, ikf there is a valid request body. NULL if there
|
||||
* is no request body.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
|
||||
* Thrown if the request body cannot be decoded.
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
|
||||
* Thrown if the request body cannot be denormalized.
|
||||
*/
|
||||
protected function deserialize(RouteMatchInterface $route_match, Request $request, ResourceInterface $resource) {
|
||||
// Deserialize incoming data if available.
|
||||
/** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
|
||||
$serializer = $this->container->get('serializer');
|
||||
$received = $request->getContent();
|
||||
$unserialized = NULL;
|
||||
if (!empty($received)) {
|
||||
$method = static::getNormalizedRequestMethod($route_match);
|
||||
$format = $request->getContentType();
|
||||
|
||||
$definition = $resource->getPluginDefinition();
|
||||
@@ -92,7 +147,7 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
|
||||
// First decode the request data. We can then determine if the
|
||||
// serialized data was malformed.
|
||||
try {
|
||||
$unserialized = $serializer->decode($received, $format, ['request_method' => $method]);
|
||||
$unserialized = $this->serializer->decode($received, $format, ['request_method' => $method]);
|
||||
}
|
||||
catch (UnexpectedValueException $e) {
|
||||
// If an exception was thrown at this stage, there was a problem
|
||||
@@ -103,7 +158,7 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
|
||||
// Then attempt to denormalize if there is a serialization class.
|
||||
if (!empty($definition['serialization_class'])) {
|
||||
try {
|
||||
$unserialized = $serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
|
||||
$unserialized = $this->serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
|
||||
}
|
||||
// These two serialization exception types mean there was a problem
|
||||
// with the structure of the decoded data and it's not valid.
|
||||
@@ -116,8 +171,125 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
|
||||
}
|
||||
}
|
||||
|
||||
return $unserialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates an incoming request to the appropriate REST resource plugin.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HTTP request object.
|
||||
* @param \Drupal\rest\Plugin\ResourceInterface $resource
|
||||
* The REST resource plugin.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
|
||||
* The REST resource response.
|
||||
*/
|
||||
protected function delegateToRestResourcePlugin(RouteMatchInterface $route_match, Request $request, ResourceInterface $resource) {
|
||||
$unserialized = $this->deserialize($route_match, $request, $resource);
|
||||
$method = static::getNormalizedRequestMethod($route_match);
|
||||
|
||||
// Determine the request parameters that should be passed to the resource
|
||||
// plugin.
|
||||
$argument_resolver = $this->createArgumentResolver($route_match, $unserialized, $request);
|
||||
try {
|
||||
$arguments = $argument_resolver->getArguments([$resource, $method]);
|
||||
}
|
||||
catch (\RuntimeException $exception) {
|
||||
@trigger_error('Passing in arguments the legacy way is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Provide the right parameter names in the method, similar to controllers. See https://www.drupal.org/node/2894819', E_USER_DEPRECATED);
|
||||
$arguments = $this->getLegacyParameters($route_match, $unserialized, $request);
|
||||
}
|
||||
|
||||
// Invoke the operation on the resource plugin.
|
||||
return call_user_func_array([$resource, $method], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an argument resolver, containing all REST parameters.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param mixed $unserialized
|
||||
* The unserialized data.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\ArgumentsResolver
|
||||
* An instance of the argument resolver containing information like the
|
||||
* 'entity' we process and the 'unserialized' content from the request body.
|
||||
*/
|
||||
protected function createArgumentResolver(RouteMatchInterface $route_match, $unserialized, Request $request) {
|
||||
$route = $route_match->getRouteObject();
|
||||
|
||||
// Defaults for the parameters defined on the route object need to be added
|
||||
// to the raw arguments.
|
||||
$raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults();
|
||||
|
||||
$route_arguments = $route_match->getParameters()->all();
|
||||
$upcasted_route_arguments = $route_arguments;
|
||||
|
||||
// For request methods that have request bodies, ResourceInterface plugin
|
||||
// methods historically receive the unserialized request body as the N+1th
|
||||
// method argument, where N is the number of route parameters specified on
|
||||
// the accompanying route. To be able to use the argument resolver, which is
|
||||
// not based on position but on name and typehint, specify commonly used
|
||||
// names here. Similarly, those methods receive the original stored object
|
||||
// as the first method argument.
|
||||
|
||||
$route_arguments_entity = NULL;
|
||||
// Try to find a parameter which is an entity.
|
||||
foreach ($route_arguments as $value) {
|
||||
if ($value instanceof EntityInterface) {
|
||||
$route_arguments_entity = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($request->getMethod(), ['PATCH', 'POST'], TRUE)) {
|
||||
$upcasted_route_arguments['entity'] = $unserialized;
|
||||
$upcasted_route_arguments['data'] = $unserialized;
|
||||
$upcasted_route_arguments['unserialized'] = $unserialized;
|
||||
$upcasted_route_arguments['original_entity'] = $route_arguments_entity;
|
||||
}
|
||||
else {
|
||||
$upcasted_route_arguments['entity'] = $route_arguments_entity;
|
||||
}
|
||||
|
||||
// Parameters which are not defined on the route object, but still are
|
||||
// essential for access checking are passed as wildcards to the argument
|
||||
// resolver.
|
||||
$wildcard_arguments = [$route, $route_match];
|
||||
$wildcard_arguments[] = $request;
|
||||
if (isset($unserialized)) {
|
||||
$wildcard_arguments[] = $unserialized;
|
||||
}
|
||||
|
||||
return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the parameter usable without an argument resolver.
|
||||
*
|
||||
* This creates an list of parameters in a statically defined order.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match
|
||||
* @param mixed $unserialized
|
||||
* The unserialized data.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0.0. Use the
|
||||
* argument resolver method instead, see ::createArgumentResolver().
|
||||
*
|
||||
* @see https://www.drupal.org/node/2894819
|
||||
*
|
||||
* @return array
|
||||
* An array of parameters.
|
||||
*/
|
||||
protected function getLegacyParameters(RouteMatchInterface $route_match, $unserialized, Request $request) {
|
||||
$route_parameters = $route_match->getParameters();
|
||||
$parameters = [];
|
||||
// Filter out all internal parameters starting with "_".
|
||||
@@ -127,15 +299,7 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the operation on the resource plugin.
|
||||
$response = call_user_func_array([$resource, $method], array_merge($parameters, [$unserialized, $request]));
|
||||
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
// Add rest config's cache tags.
|
||||
$response->addCacheableDependency($resource_config);
|
||||
}
|
||||
|
||||
return $response;
|
||||
return array_merge($parameters, [$unserialized, $request]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
||||
use Drupal\rest\LinkManager\LinkManager;
|
||||
use Drupal\rest\LinkManager\RelationLinkManager;
|
||||
use Drupal\rest\LinkManager\TypeLinkManager;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
@@ -28,21 +28,21 @@ class RestServiceProvider implements ServiceProviderInterface {
|
||||
// @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
// Use hal.link_manager instead.
|
||||
// @see https://www.drupal.org/node/2830467
|
||||
$service_definition = new DefinitionDecorator(new Reference('hal.link_manager'));
|
||||
$service_definition = new ChildDefinition(new Reference('hal.link_manager'));
|
||||
$service_definition->setClass(LinkManager::class);
|
||||
$container->setDefinition('rest.link_manager', $service_definition);
|
||||
|
||||
// @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
// Use hal.link_manager.type instead.
|
||||
// @see https://www.drupal.org/node/2830467
|
||||
$service_definition = new DefinitionDecorator(new Reference('hal.link_manager.type'));
|
||||
$service_definition = new ChildDefinition(new Reference('hal.link_manager.type'));
|
||||
$service_definition->setClass(TypeLinkManager::class);
|
||||
$container->setDefinition('rest.link_manager.type', $service_definition);
|
||||
|
||||
// @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
// Use hal.link_manager.relation instead.
|
||||
// @see https://www.drupal.org/node/2830467
|
||||
$service_definition = new DefinitionDecorator(new Reference('hal.link_manager.relation'));
|
||||
$service_definition = new ChildDefinition(new Reference('hal.link_manager.relation'));
|
||||
$service_definition->setClass(RelationLinkManager::class);
|
||||
$container->setDefinition('rest.link_manager.relation', $service_definition);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\rest\RouteProcessor;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Processes the BC REST routes, to ensure old route names continue to work.
|
||||
*/
|
||||
class RestResourceGetRouteProcessorBC implements OutboundRouteProcessorInterface {
|
||||
|
||||
/**
|
||||
* The available serialization formats.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $serializerFormats = [];
|
||||
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* Constructs a RestResourceGetRouteProcessorBC object.
|
||||
*
|
||||
* @param string[] $serializer_formats
|
||||
* The available serialization formats.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
*/
|
||||
public function __construct(array $serializer_formats, RouteProviderInterface $route_provider) {
|
||||
$this->serializerFormats = $serializer_formats;
|
||||
$this->routeProvider = $route_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
$route_name_parts = explode('.', $route_name);
|
||||
// BC: the REST module originally created per-format GET routes, instead
|
||||
// of a single route. To minimize the surface of this BC layer, this uses
|
||||
// route definitions that are as empty as possible, plus an outbound route
|
||||
// processor.
|
||||
// @see \Drupal\rest\Plugin\ResourceBase::routes()
|
||||
if ($route_name_parts[0] === 'rest' && $route_name_parts[count($route_name_parts) - 2] === 'GET' && in_array($route_name_parts[count($route_name_parts) - 1], $this->serializerFormats, TRUE)) {
|
||||
array_pop($route_name_parts);
|
||||
$redirected_route_name = implode('.', $route_name_parts);
|
||||
@trigger_error(sprintf("The '%s' route is deprecated since version 8.5.x and will be removed in 9.0.0. Use the '%s' route instead.", $route_name, $redirected_route_name), E_USER_DEPRECATED);
|
||||
static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites one route's metadata with the other's.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $target_route
|
||||
* The route whose metadata to overwrite.
|
||||
* @param \Symfony\Component\Routing\Route $source_route
|
||||
* The route whose metadata to read from.
|
||||
*
|
||||
* @see \Symfony\Component\Routing\Route
|
||||
*/
|
||||
protected static function overwriteRoute(Route $target_route, Route $source_route) {
|
||||
$target_route->setPath($source_route->getPath());
|
||||
$target_route->setDefaults($source_route->getDefaults());
|
||||
$target_route->setRequirements($source_route->getRequirements());
|
||||
$target_route->setOptions($source_route->getOptions());
|
||||
$target_route->setHost($source_route->getHost());
|
||||
$target_route->setSchemes($source_route->getSchemes());
|
||||
$target_route->setMethods($source_route->getMethods());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -92,8 +92,11 @@ class ResourceRoutes implements EventSubscriberInterface {
|
||||
/** @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)) {
|
||||
// Only expose routes
|
||||
// - that have an explicit method and allow >=1 format for that method
|
||||
// - that exist for BC
|
||||
// @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
|
||||
if (($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) || $route->hasOption('bc_route')) {
|
||||
$route->setRequirement('_csrf_request_header_token', 'TRUE');
|
||||
|
||||
// Check that authentication providers are defined.
|
||||
@@ -108,24 +111,34 @@ class ResourceRoutes implements EventSubscriberInterface {
|
||||
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;
|
||||
// Remove BC routes for unsupported formats.
|
||||
if ($route->getOption('bc_route') === TRUE) {
|
||||
$format_requirement = $route->getRequirement('_format');
|
||||
if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getFormats($method))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The configuration has been validated, so we update the route to:
|
||||
// - set the allowed response body content types/formats for methods
|
||||
// that may send response bodies
|
||||
// - set the allowed request body content types/formats for methods that
|
||||
// allow request bodies to be sent
|
||||
// - set the allowed authentication providers
|
||||
if (in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'PATCH'], TRUE)) {
|
||||
$route->addRequirements(['_format' => implode('|', $rest_resource_config->getFormats($method))]);
|
||||
}
|
||||
if (in_array($method, ['POST', 'PATCH', 'PUT'], TRUE)) {
|
||||
// Restrict the incoming HTTP Content-type header to the allowed
|
||||
// formats.
|
||||
$route->addRequirements(['_content_type_format' => implode('|', $rest_resource_config->getFormats($method))]);
|
||||
}
|
||||
$route->setOption('_auth', $rest_resource_config->getAuthenticationProviders($method));
|
||||
$route->setDefault('_rest_resource_config', $rest_resource_config->id());
|
||||
$parameters = $route->getOption('parameters') ?: [];
|
||||
$route->setOption('parameters', $parameters + [
|
||||
'_rest_resource_config' => [
|
||||
'type' => 'entity:' . $rest_resource_config->getEntityTypeId(),
|
||||
],
|
||||
]);
|
||||
$collection->add("rest.$name", $route);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user