Skip to content

Commit 6021e67

Browse files
committed
Merge branch '1.3'
Conflicts: EventListener/UserContextSubscriber.php Resources/config/user_context.xml composer.json
2 parents 6bb4495 + 5ffcbf5 commit 6021e67

File tree

7 files changed

+186
-3
lines changed

7 files changed

+186
-3
lines changed

DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ private function loadUserContext(ContainerBuilder $container, XmlFileLoader $loa
201201
->replaceArgument(4, $config['hash_cache_ttl'])
202202
->replaceArgument(5, $config['always_vary_on_context_hash']);
203203

204+
$container->getDefinition($this->getAlias().'.user_context.anonymous_request_matcher')
205+
->replaceArgument(0, $config['user_identifier_headers']);
206+
204207
if ($config['logout_handler']['enabled']) {
205208
$container->getDefinition($this->getAlias().'.user_context.logout_handler')
206209
->replaceArgument(1, $config['user_identifier_headers'])

EventListener/UserContextSubscriber.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCache\UserContext\HashGenerator;
1515
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpFoundation\Request;
1617
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
1718
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
1819
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -66,13 +67,23 @@ class UserContextSubscriber implements EventSubscriberInterface
6667
*/
6768
private $addVaryOnHash;
6869

70+
/**
71+
* Used to exclude anonymous requests (no authentication nor session) from user hash sanity check.
72+
* It prevents issues when the hash generator that is used returns a customized value for anonymous users,
73+
* that differs from the documented, hardcoded one.
74+
*
75+
* @var RequestMatcherInterface
76+
*/
77+
private $anonymousRequestMatcher;
78+
6979
public function __construct(
7080
RequestMatcherInterface $requestMatcher,
7181
HashGenerator $hashGenerator,
7282
array $userIdentifierHeaders = array('Cookie', 'Authorization'),
7383
$hashHeader = 'X-User-Context-Hash',
7484
$ttl = 0,
75-
$addVaryOnHash = true
85+
$addVaryOnHash = true,
86+
RequestMatcherInterface $anonymousRequestMatcher = null
7687
) {
7788
if (!count($userIdentifierHeaders)) {
7889
throw new \InvalidArgumentException('The user context must vary on some request headers');
@@ -83,6 +94,7 @@ public function __construct(
8394
$this->hashHeader = $hashHeader;
8495
$this->ttl = $ttl;
8596
$this->addVaryOnHash = $addVaryOnHash;
97+
$this->anonymousRequestMatcher = $anonymousRequestMatcher;
8698
}
8799

88100
/**
@@ -100,7 +112,7 @@ public function onKernelRequest(GetResponseEvent $event)
100112
}
101113

102114
if (!$this->requestMatcher->matches($event->getRequest())) {
103-
if ($event->getRequest()->headers->has($this->hashHeader)) {
115+
if ($event->getRequest()->headers->has($this->hashHeader) && !$this->isAnonymous($event->getRequest())) {
104116
$this->hash = $this->hashGenerator->generateHash();
105117
}
106118

@@ -127,6 +139,20 @@ public function onKernelRequest(GetResponseEvent $event)
127139
$event->setResponse($response);
128140
}
129141

142+
/**
143+
* Tests if $request is an anonymous request or not.
144+
*
145+
* For backward compatibility reasons, true will be returned if no anonymous request matcher was provided.
146+
*
147+
* @param Request $request
148+
*
149+
* @return bool
150+
*/
151+
private function isAnonymous(Request $request)
152+
{
153+
return $this->anonymousRequestMatcher ? $this->anonymousRequestMatcher->matches($request) : false;
154+
}
155+
130156
/**
131157
* Add the context hash header to the headers to vary on if the header was
132158
* present in the request.

Resources/config/user_context.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<argument />
2929
<argument />
3030
<argument />
31+
<argument type="service" id="fos_http_cache.user_context.anonymous_request_matcher" />
3132
<tag name="kernel.event_subscriber" />
3233
</service>
3334

@@ -40,5 +41,9 @@
4041
<argument />
4142
<argument />
4243
</service>
44+
45+
<service id="fos_http_cache.user_context.anonymous_request_matcher" class="FOS\HttpCacheBundle\UserContext\AnonymousRequestMatcher">
46+
<argument type="collection" />
47+
</service>
4348
</services>
4449
</container>

Tests/Unit/EventListener/UserContextSubscriberTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,39 @@ public function testFullRequestHashOk()
199199
$this->assertContains('X-Hash', $event->getResponse()->headers->get('Vary'));
200200
}
201201

202+
/**
203+
* If the request is an anonymous one, no hash should be generated/validated.
204+
*/
205+
public function testFullAnonymousRequestHashNotGenerated()
206+
{
207+
$request = new Request();
208+
$request->setMethod('GET');
209+
$request->headers->set('X-Hash', 'anonymous-hash');
210+
211+
$requestMatcher = $this->getRequestMatcher($request, false);
212+
$hashGenerator = \Mockery::mock('\FOS\HttpCache\UserContext\HashGenerator');
213+
$hashGenerator->shouldReceive('generateHash')->never();
214+
215+
$anonymousRequestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
216+
$anonymousRequestMatcher->shouldReceive('matches')->andReturn(true);
217+
218+
// onKernelRequest
219+
$userContextSubscriber = new UserContextSubscriber($requestMatcher, $hashGenerator, array('X-SessionId'), 'X-Hash', 0, true, $anonymousRequestMatcher);
220+
$event = $this->getKernelRequestEvent($request);
221+
222+
$userContextSubscriber->onKernelRequest($event);
223+
224+
$response = $event->getResponse();
225+
226+
$this->assertNull($response);
227+
228+
// onKernelResponse
229+
$event = $this->getKernelResponseEvent($request);
230+
$userContextSubscriber->onKernelResponse($event);
231+
232+
$this->assertContains('X-Hash', $event->getResponse()->headers->get('Vary'));
233+
}
234+
202235
/**
203236
* If there is no hash in the requests but session changed, prevent setting bad cache.
204237
*/
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
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+
namespace FOS\HttpCacheBundle\Tests\Unit\UserContext;
13+
14+
use FOS\HttpCacheBundle\UserContext\AnonymousRequestMatcher;
15+
use PHPUnit_Framework_TestCase;
16+
use Symfony\Component\HttpFoundation\Request;
17+
18+
class AnonymousRequestMatcherTest extends PHPUnit_Framework_TestCase
19+
{
20+
public function testMatchAnonymousRequest()
21+
{
22+
$request = new Request();
23+
24+
$requestMatcher = new AnonymousRequestMatcher(['Cookie', 'Authorization']);
25+
26+
$this->assertTrue($requestMatcher->matches($request));
27+
}
28+
29+
public function testNoMatchIfCookie()
30+
{
31+
$request = new Request();
32+
$request->headers->set('Cookie', 'PHPSESSID7e476fc9f29f69d2ad6f11dbcd663b42=25f6d9c5a843e3c948cd26902385a527');
33+
$request->cookies->set('PHPSESSID7e476fc9f29f69d2ad6f11dbcd663b42', '25f6d9c5a843e3c948cd26902385a527');
34+
35+
$requestMatcher = new AnonymousRequestMatcher(['Cookie', 'Authorization']);
36+
37+
$this->assertFalse($requestMatcher->matches($request));
38+
}
39+
40+
public function testNoMatchIfEmptyCookieHeader()
41+
{
42+
$request = new Request();
43+
$request->headers->set('Cookie', '');
44+
45+
$requestMatcher = new AnonymousRequestMatcher(['Cookie', 'Authorization']);
46+
47+
$this->assertTrue($requestMatcher->matches($request));
48+
}
49+
50+
public function testNoMatchIfAuthenticationHeader()
51+
{
52+
$request = new Request();
53+
$request->headers->set('Authorization', 'foo: bar');
54+
55+
$requestMatcher = new AnonymousRequestMatcher(['Cookie', 'Authorization']);
56+
57+
$this->assertFalse($requestMatcher->matches($request));
58+
}
59+
60+
public function testMatchEmptyCookieHeaderHeader()
61+
{
62+
$request = new Request();
63+
$request->headers->set('Cookie', '');
64+
65+
$requestMatcher = new AnonymousRequestMatcher(['Cookie', 'Authorization']);
66+
67+
$this->assertTrue($requestMatcher->matches($request));
68+
}
69+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
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+
namespace FOS\HttpCacheBundle\UserContext;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
17+
/**
18+
* Matches anonymous requests using a list of identification headers.
19+
*/
20+
class AnonymousRequestMatcher implements RequestMatcherInterface
21+
{
22+
private $userIdentifierHeaders;
23+
24+
/**
25+
* @param array $userIdentifierHeaders List of request headers that authenticate a non-anonymous request
26+
*/
27+
public function __construct(array $userIdentifierHeaders)
28+
{
29+
$this->userIdentifierHeaders = $userIdentifierHeaders;
30+
}
31+
32+
public function matches(Request $request)
33+
{
34+
foreach ($this->userIdentifierHeaders as $header) {
35+
if ($request->headers->has($header)) {
36+
if (strtolower($header) === 'cookie' && 0 === $request->cookies->count()) {
37+
// ignore empty cookie header
38+
continue;
39+
}
40+
41+
return false;
42+
}
43+
}
44+
45+
return true;
46+
}
47+
}

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"mockery/mockery": "0.9.*",
3131
"monolog/monolog": "*",
3232
"sensio/framework-extra-bundle": "^3.0",
33-
"symfony/symfony": "^2.3||^3.0",
33+
"symfony/symfony": "^2.8||^3.0",
3434
"symfony/phpunit-bridge": "^2.7||^3.0",
3535
"symfony/expression-language": "^2.4||^3.0",
3636
"symfony/monolog-bundle": "^2.3||^3.0",

0 commit comments

Comments
 (0)