Skip to content

Commit

Permalink
Prevent adding Link headers for CORS preflight requets
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Nov 15, 2019
1 parent 432c580 commit 79e2ff6
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 9 deletions.
16 changes: 12 additions & 4 deletions src/Hydra/EventListener/AddLinkHeaderListener.php
Expand Up @@ -15,6 +15,7 @@

use ApiPlatform\Core\Api\UrlGeneratorInterface;
use ApiPlatform\Core\JsonLd\ContextBuilder;
use ApiPlatform\Core\Util\CorsTrait;
use Fig\Link\GenericLinkProvider;
use Fig\Link\Link;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
Expand All @@ -26,6 +27,8 @@
*/
final class AddLinkHeaderListener
{
use CorsTrait;

private $urlGenerator;

public function __construct(UrlGeneratorInterface $urlGenerator)
Expand All @@ -38,15 +41,20 @@ public function __construct(UrlGeneratorInterface $urlGenerator)
*/
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
// Prevent issues with NelmioCorsBundle
if ($this->isPreflightRequest($request)) {
return;
}

$apiDocUrl = $this->urlGenerator->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL);
$link = new Link(ContextBuilder::HYDRA_NS.'apiDocumentation', $apiDocUrl);

$attributes = $event->getRequest()->attributes;
if (null === $linkProvider = $attributes->get('_links')) {
$attributes->set('_links', new GenericLinkProvider([$link]));
if (null === $linkProvider = $request->attributes->get('_links')) {
$request->attributes->set('_links', new GenericLinkProvider([$link]));

return;
}
$attributes->set('_links', $linkProvider->withLink($link));
$request->attributes->set('_links', $linkProvider->withLink($link));
}
}
18 changes: 13 additions & 5 deletions src/Mercure/EventListener/AddLinkHeaderListener.php
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Core\Mercure\EventListener;

use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Util\CorsTrait;
use Fig\Link\GenericLinkProvider;
use Fig\Link\Link;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
Expand All @@ -25,6 +26,8 @@
*/
final class AddLinkHeaderListener
{
use CorsTrait;

private $resourceMetadataFactory;
private $hub;

Expand All @@ -39,22 +42,27 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa
*/
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
// Prevent issues with NelmioCorsBundle
if ($this->isPreflightRequest($request)) {
return;
}

$link = new Link('mercure', $this->hub);

$attributes = $event->getRequest()->attributes;
if (
null === ($resourceClass = $attributes->get('_api_resource_class')) ||
null === ($resourceClass = $request->attributes->get('_api_resource_class')) ||
false === $this->resourceMetadataFactory->create($resourceClass)->getAttribute('mercure', false)
) {
return;
}

if (null === $linkProvider = $attributes->get('_links')) {
$attributes->set('_links', new GenericLinkProvider([$link]));
if (null === $linkProvider = $request->attributes->get('_links')) {
$request->attributes->set('_links', new GenericLinkProvider([$link]));

return;
}

$attributes->set('_links', $linkProvider->withLink($link));
$request->attributes->set('_links', $linkProvider->withLink($link));
}
}
33 changes: 33 additions & 0 deletions src/Util/CorsTrait.php
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Util;

use Symfony\Component\HttpFoundation\Request;

/**
* CORS utils.
*
* To be removed when https://github.com/symfony/symfony/pull/34391 wil be merged.
*
* @internal
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
trait CorsTrait
{
public function isPreflightRequest(Request $request): bool
{
return $request->isMethod('OPTIONS') && $request->headers->has('Access-Control-Request-Method');
}
}
16 changes: 16 additions & 0 deletions tests/Hydra/EventListener/AddLinkHeaderListenerTest.php
Expand Up @@ -50,4 +50,20 @@ public function provider(): array
['<https://demo.mercure.rocks/hub>; rel="mercure",<http://example.com/docs>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"', new Request([], [], ['_links' => new GenericLinkProvider([new Link('mercure', 'https://demo.mercure.rocks/hub')])])],
];
}

public function testSkipWhenPreflightRequest(): void
{
$request = new Request();
$request->setMethod('OPTIONS');
$request->headers->set('Access-Control-Request-Method', 'POST');

$event = $this->prophesize(ResponseEvent::class);
$event->getRequest()->willReturn($request)->shouldBeCalled();

$urlGenerator = $this->prophesize(UrlGeneratorInterface::class);
$listener = new AddLinkHeaderListener($urlGenerator->reveal());
$listener->onKernelResponse($event->reveal());

$this->assertFalse($request->attributes->has('_links'));
}
}
16 changes: 16 additions & 0 deletions tests/Mercure/EventListener/AddLinkHeaderListenerTest.php
Expand Up @@ -80,4 +80,20 @@ public function doNotAddProvider(): array
[new Request([], [], ['_api_resource_class' => Dummy::class])],
];
}

public function testSkipWhenPreflightRequest(): void
{
$request = new Request();
$request->setMethod('OPTIONS');
$request->headers->set('Access-Control-Request-Method', 'POST');

$event = $this->prophesize(ResponseEvent::class);
$event->getRequest()->willReturn($request)->shouldBeCalled();

$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
$listener = new AddLinkHeaderListener($resourceMetadataFactory->reveal(), 'http://example.com/.well-known/mercure');
$listener->onKernelResponse($event->reveal());

$this->assertFalse($request->attributes->has('_links'));
}
}

0 comments on commit 79e2ff6

Please sign in to comment.