Skip to content

Commit d4f5d44

Browse files
authored
Merge pull request #3265 from dunglas/fix_3262
Prevent adding Link headers for CORS preflight requets
2 parents 432c580 + 79e2ff6 commit d4f5d44

File tree

5 files changed

+90
-9
lines changed

5 files changed

+90
-9
lines changed

src/Hydra/EventListener/AddLinkHeaderListener.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Core\Api\UrlGeneratorInterface;
1717
use ApiPlatform\Core\JsonLd\ContextBuilder;
18+
use ApiPlatform\Core\Util\CorsTrait;
1819
use Fig\Link\GenericLinkProvider;
1920
use Fig\Link\Link;
2021
use Symfony\Component\HttpKernel\Event\ResponseEvent;
@@ -26,6 +27,8 @@
2627
*/
2728
final class AddLinkHeaderListener
2829
{
30+
use CorsTrait;
31+
2932
private $urlGenerator;
3033

3134
public function __construct(UrlGeneratorInterface $urlGenerator)
@@ -38,15 +41,20 @@ public function __construct(UrlGeneratorInterface $urlGenerator)
3841
*/
3942
public function onKernelResponse(ResponseEvent $event): void
4043
{
44+
$request = $event->getRequest();
45+
// Prevent issues with NelmioCorsBundle
46+
if ($this->isPreflightRequest($request)) {
47+
return;
48+
}
49+
4150
$apiDocUrl = $this->urlGenerator->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL);
4251
$link = new Link(ContextBuilder::HYDRA_NS.'apiDocumentation', $apiDocUrl);
4352

44-
$attributes = $event->getRequest()->attributes;
45-
if (null === $linkProvider = $attributes->get('_links')) {
46-
$attributes->set('_links', new GenericLinkProvider([$link]));
53+
if (null === $linkProvider = $request->attributes->get('_links')) {
54+
$request->attributes->set('_links', new GenericLinkProvider([$link]));
4755

4856
return;
4957
}
50-
$attributes->set('_links', $linkProvider->withLink($link));
58+
$request->attributes->set('_links', $linkProvider->withLink($link));
5159
}
5260
}

src/Mercure/EventListener/AddLinkHeaderListener.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Mercure\EventListener;
1515

1616
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
17+
use ApiPlatform\Core\Util\CorsTrait;
1718
use Fig\Link\GenericLinkProvider;
1819
use Fig\Link\Link;
1920
use Symfony\Component\HttpKernel\Event\ResponseEvent;
@@ -25,6 +26,8 @@
2526
*/
2627
final class AddLinkHeaderListener
2728
{
29+
use CorsTrait;
30+
2831
private $resourceMetadataFactory;
2932
private $hub;
3033

@@ -39,22 +42,27 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa
3942
*/
4043
public function onKernelResponse(ResponseEvent $event): void
4144
{
45+
$request = $event->getRequest();
46+
// Prevent issues with NelmioCorsBundle
47+
if ($this->isPreflightRequest($request)) {
48+
return;
49+
}
50+
4251
$link = new Link('mercure', $this->hub);
4352

44-
$attributes = $event->getRequest()->attributes;
4553
if (
46-
null === ($resourceClass = $attributes->get('_api_resource_class')) ||
54+
null === ($resourceClass = $request->attributes->get('_api_resource_class')) ||
4755
false === $this->resourceMetadataFactory->create($resourceClass)->getAttribute('mercure', false)
4856
) {
4957
return;
5058
}
5159

52-
if (null === $linkProvider = $attributes->get('_links')) {
53-
$attributes->set('_links', new GenericLinkProvider([$link]));
60+
if (null === $linkProvider = $request->attributes->get('_links')) {
61+
$request->attributes->set('_links', new GenericLinkProvider([$link]));
5462

5563
return;
5664
}
5765

58-
$attributes->set('_links', $linkProvider->withLink($link));
66+
$request->attributes->set('_links', $linkProvider->withLink($link));
5967
}
6068
}

src/Util/CorsTrait.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Util;
15+
16+
use Symfony\Component\HttpFoundation\Request;
17+
18+
/**
19+
* CORS utils.
20+
*
21+
* To be removed when https://github.com/symfony/symfony/pull/34391 wil be merged.
22+
*
23+
* @internal
24+
*
25+
* @author Kévin Dunglas <[email protected]>
26+
*/
27+
trait CorsTrait
28+
{
29+
public function isPreflightRequest(Request $request): bool
30+
{
31+
return $request->isMethod('OPTIONS') && $request->headers->has('Access-Control-Request-Method');
32+
}
33+
}

tests/Hydra/EventListener/AddLinkHeaderListenerTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,20 @@ public function provider(): array
5050
['<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')])])],
5151
];
5252
}
53+
54+
public function testSkipWhenPreflightRequest(): void
55+
{
56+
$request = new Request();
57+
$request->setMethod('OPTIONS');
58+
$request->headers->set('Access-Control-Request-Method', 'POST');
59+
60+
$event = $this->prophesize(ResponseEvent::class);
61+
$event->getRequest()->willReturn($request)->shouldBeCalled();
62+
63+
$urlGenerator = $this->prophesize(UrlGeneratorInterface::class);
64+
$listener = new AddLinkHeaderListener($urlGenerator->reveal());
65+
$listener->onKernelResponse($event->reveal());
66+
67+
$this->assertFalse($request->attributes->has('_links'));
68+
}
5369
}

tests/Mercure/EventListener/AddLinkHeaderListenerTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,20 @@ public function doNotAddProvider(): array
8080
[new Request([], [], ['_api_resource_class' => Dummy::class])],
8181
];
8282
}
83+
84+
public function testSkipWhenPreflightRequest(): void
85+
{
86+
$request = new Request();
87+
$request->setMethod('OPTIONS');
88+
$request->headers->set('Access-Control-Request-Method', 'POST');
89+
90+
$event = $this->prophesize(ResponseEvent::class);
91+
$event->getRequest()->willReturn($request)->shouldBeCalled();
92+
93+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
94+
$listener = new AddLinkHeaderListener($resourceMetadataFactory->reveal(), 'http://example.com/.well-known/mercure');
95+
$listener->onKernelResponse($event->reveal());
96+
97+
$this->assertFalse($request->attributes->has('_links'));
98+
}
8399
}

0 commit comments

Comments
 (0)