diff --git a/core/modules/rest/config/install/rest.settings.yml b/core/modules/rest/config/install/rest.settings.yml index e2699fc..a3c8d25 100644 --- a/core/modules/rest/config/install/rest.settings.yml +++ b/core/modules/rest/config/install/rest.settings.yml @@ -25,6 +25,11 @@ resources: - hal_json supported_auth: - basic_auth + OPTIONS: + supported_formats: + - hal_json + supported_auth: + - basic_auth # Multiple formats and multiple authentication providers can be defined for a # resource: diff --git a/core/modules/rest/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml index b9cb652..fd44827 100644 --- a/core/modules/rest/config/schema/rest.schema.yml +++ b/core/modules/rest/config/schema/rest.schema.yml @@ -14,6 +14,9 @@ rest.settings: rest_resource: type: mapping mapping: + OPTIONS: + type: rest_request + label: 'OPTIONS method settings' GET: type: rest_request label: 'GET method settings' diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml index c25d692..2c69173 100644 --- a/core/modules/rest/rest.services.yml +++ b/core/modules/rest/rest.services.yml @@ -30,3 +30,8 @@ services: logger.channel.rest: parent: logger.channel_base arguments: ['rest'] + rest.options_subscriber: + class: Drupal\rest\Routing\OptionsRequestSubscriber + arguments: ['@access_manager', '@current_user'] + tags: + - {name: event_subscriber} diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 7b3e49e..3448cb2 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -194,6 +194,21 @@ public function delete(EntityInterface $entity) { } /** + * Responds to entity OPTIONS requests. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity object. + * + * @return \Drupal\rest\ResourceResponse + * The HTTP response object. + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function options(EntityInterface $entity) { + return new ResourceResponse(NULL, 204); + } + + /** * Verifies that the whole entity does not violate any validation constraints. * * @param \Drupal\Core\Entity\EntityInterface $entity diff --git a/core/modules/rest/src/Routing/OptionsRequestSubscriber.php b/core/modules/rest/src/Routing/OptionsRequestSubscriber.php new file mode 100644 index 0000000..6298cf3 --- /dev/null +++ b/core/modules/rest/src/Routing/OptionsRequestSubscriber.php @@ -0,0 +1,116 @@ +accessManager = $access_manager; + $this->account = $account; + } + + /** + * Handles OPTIONS requests. + */ + public function onKernelRequest(GetResponseEvent $event) { + $request = $event->getRequest(); + if ($request->isMethod('OPTIONS')) { + $allowed_methods = implode(' ', $this->getAllowedMethods($request)); + $accepts = $request->getAcceptableContentTypes(); + $accept = array_shift($accepts); + $headers = array( + 'Allow' => $allowed_methods, + // TODO use $accept instead + 'Content-Type' => $request->getMimeType('json'), + ); + $response = new Response(NULL, 200, $headers); + $event->setResponse($response); + $event->stopPropagation(); + } + } + + /** + * Check which methods are allowed for the current request. + */ + protected function getAllowedMethods(Request $request) { + $allow = array(); + $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT); + if (isset($route)) { + foreach ($this->availableMethods as $method) { + $request->setMethod($method); + if ($this->accessManager->check($route, $request, $this->account)) { + $allow[] = $method; + } + } + } + return $allow; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequest', 32); + return $events; + } + +}