Skip to content

Commit 5f9af58

Browse files
dbuddeboer
authored andcommitted
allow configuration to run with no firewall on some paths. fix #72
cleanups on user context. fix test adjust to hash generator refactoring update doc
1 parent f08cf24 commit 5f9af58

File tree

8 files changed

+119
-44
lines changed

8 files changed

+119
-44
lines changed

DependencyInjection/Compiler/UserContextListenerPass.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace FOS\HttpCacheBundle\DependencyInjection\Compiler;
44

5+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
56
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
67
use Symfony\Component\DependencyInjection\ContainerBuilder;
78
use Symfony\Component\DependencyInjection\Reference;
@@ -24,8 +25,15 @@ public function process(ContainerBuilder $container)
2425

2526
$definition = $container->getDefinition('fos_http_cache.user_context.hash_generator');
2627

28+
$providers = array();
2729
foreach ($container->findTaggedServiceIds(self::TAG_NAME) as $id => $parameters) {
28-
$definition->addMethodCall('registerProvider', array(new Reference($id)));
30+
$providers[] = new Reference($id);
2931
}
32+
33+
if (!count($providers)) {
34+
throw new InvalidConfigurationException('No user context providers found. Either tag providers or disable fos_http_cache.user_context');
35+
}
36+
37+
$definition->addArgument($providers);
3038
}
3139
}

DependencyInjection/Configuration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private function addUserContextListenerSection(ArrayNodeDefinition $rootNode)
7979
->end()
8080
->arrayNode('user_identifier_headers')
8181
->prototype('scalar')->end()
82-
->treatNullLike(array('Cookie', 'Authentication'))
82+
->defaultValue(array('Cookie', 'Authorization'))
8383
->info('List of headers that contains the unique identifier for the user in the hash request.')
8484
->end()
8585
->scalarNode('user_hash_header')

EventListener/UserContextSubscriber.php

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ class UserContextSubscriber implements EventSubscriberInterface
5050
public function __construct(
5151
RequestMatcherInterface $requestMatcher,
5252
HashGenerator $hashGenerator,
53-
$userIdentifierHeaders = array('Vary', 'Authorization'),
53+
array $userIdentifierHeaders = array('Cookie', 'Authorization'),
5454
$hashHeader = "X-User-Context-Hash",
5555
$ttl = 0
5656
)
5757
{
58+
if (!count($userIdentifierHeaders)) {
59+
throw new \InvalidArgumentException('The user context must vary on some request headers');
60+
}
5861
$this->requestMatcher = $requestMatcher;
5962
$this->hashGenerator = $hashGenerator;
6063
$this->userIdentifierHeaders = $userIdentifierHeaders;
@@ -111,17 +114,19 @@ public function onKernelResponse(FilterResponseEvent $event)
111114
return;
112115
}
113116

114-
// Only set vary header if we have the hash header
115-
if (!$event->getRequest()->headers->has($this->hashHeader)) {
116-
return;
117-
}
118-
119117
$response = $event->getResponse();
120-
121118
$vary = $response->getVary();
122119

123-
if (!in_array($this->hashHeader, $vary)) {
124-
$vary[] = $this->hashHeader;
120+
if ($event->getRequest()->headers->has($this->hashHeader)) {
121+
if (!in_array($this->hashHeader, $vary)) {
122+
$vary[] = $this->hashHeader;
123+
}
124+
} else {
125+
foreach ($this->userIdentifierHeaders as $header) {
126+
if (!in_array($header, $vary)) {
127+
$vary[] = $header;
128+
}
129+
}
125130
}
126131

127132
$response->setVary($vary, true);

Resources/config/user_context.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</service>
3131

3232
<service id="fos_http_cache.user_context.role_provider" class="%fos_http_cache.user_context.role_provider.class%" abstract="true">
33-
<argument type="service" id="security.context" />
33+
<argument type="service" id="security.context" on-invalid="ignore" />
3434
</service>
3535
</services>
3636
</container>

Resources/doc/user-context.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,45 @@ other responses are set to vary on the hash header.
2121
Additionally, the bundle provides a service that builds the user context hash
2222
from context providers.
2323

24-
## Configuring the Context Event Subscriber
24+
## Configuring the User Context Event Subscriber
25+
26+
First you need to set up your caching proxy as explained in
27+
[the user context documentation](http://foshttpcache.readthedocs.org/en/latest/user-context.html)
28+
29+
Then add the route you specified in the hash lookup request to the Symfony
30+
routing configuration, so that the user context event subscriber can get
31+
triggered:
32+
33+
```yaml
34+
# app/config/routing.yml
35+
user_context_hash:
36+
/user-context-hash
37+
```
38+
39+
.. note::
40+
41+
This route is never actually used, as the context event subscriber will act
42+
before a controller would be called. But the user context is handled only
43+
after security happened. Security in turn only happens after the routing.
44+
If the routing does not find a route, the request is aborted with a "not
45+
found" error.
46+
47+
.. caution::
48+
49+
If you are using Symfony2 security, make sure that this route is route is
50+
[inside the firewall](http://symfony.com/doc/current/book/security.html) for
51+
which you are doing the cache groups.
2552
2653
Enable the event subscriber with:
2754
28-
``` yaml
29-
# app/config.yml
55+
```yaml
56+
# app/config/config.yml
3057
fos_http_cache:
3158
user_context:
3259
enabled: true
3360
```
3461
35-
This will enable the subscriber with the default settings. You need to
36-
configure your caching proxy as explained in
37-
[the user context documentation](http://foshttpcache.readthedocs.org/en/latest/user-context.html)
62+
This will enable the subscriber with the default settings.
3863
3964
Note: Enabling happens automatically if you configure any of the options on the
4065
user_context.

Tests/Unit/EventListener/UserContextSubscriberTest.php

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
1010
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
1111
use Symfony\Component\HttpKernel\HttpKernelInterface;
12+
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
1213

1314
class UserContextSubscriberTest extends \PHPUnit_Framework_TestCase
1415
{
@@ -17,21 +18,21 @@ public function testOnKernelRequest()
1718
$request = new Request();
1819
$request->setMethod('HEAD');
1920

20-
$requestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
21-
$requestMatcher->shouldReceive('matches')->with($request)->andReturn(true);
21+
$requestMatcher = $this->getRequestMatcher($request, true);
22+
$hashGenerator = \Mockery::mock('\FOS\HttpCache\UserContext\HashGenerator');
23+
$hashGenerator->shouldReceive('generateHash')->andReturn('hash');
2224

23-
$userContextSubscriber = new UserContextSubscriber($requestMatcher, new HashGenerator(), 'X-SessionId', 'X-Hash');
25+
$userContextSubscriber = new UserContextSubscriber($requestMatcher, $hashGenerator, array('X-SessionId'), 'X-Hash');
2426
$event = $this->getKernelRequestEvent($request);
2527

2628
$userContextSubscriber->onKernelRequest($event);
2729

2830
$response = $event->getResponse();
29-
$hash = hash('sha256', serialize(array()));
3031

3132

3233
$this->assertNotNull($response);
3334
$this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response);
34-
$this->assertEquals($hash, $response->headers->get('X-Hash'));
35+
$this->assertEquals('hash', $response->headers->get('X-Hash'));
3536
$this->assertNull($response->headers->get('Vary'));
3637
$this->assertEquals('max-age=0, no-cache, private', $response->headers->get('Cache-Control'));
3738
}
@@ -41,20 +42,20 @@ public function testOnKernelRequestCached()
4142
$request = new Request();
4243
$request->setMethod('HEAD');
4344

44-
$requestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
45-
$requestMatcher->shouldReceive('matches')->with($request)->andReturn(true);
45+
$requestMatcher = $this->getRequestMatcher($request, true);
46+
$hashGenerator = \Mockery::mock('\FOS\HttpCache\UserContext\HashGenerator');
47+
$hashGenerator->shouldReceive('generateHash')->andReturn('hash');
4648

47-
$userContextSubscriber = new UserContextSubscriber($requestMatcher, new HashGenerator(), 'X-SessionId', 'X-Hash', 30);
49+
$userContextSubscriber = new UserContextSubscriber($requestMatcher, $hashGenerator, array('X-SessionId'), 'X-Hash', 30);
4850
$event = $this->getKernelRequestEvent($request);
4951

5052
$userContextSubscriber->onKernelRequest($event);
5153

5254
$response = $event->getResponse();
53-
$hash = hash('sha256', serialize(array()));
5455

5556
$this->assertNotNull($response);
5657
$this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response);
57-
$this->assertEquals($hash, $response->headers->get('X-Hash'));
58+
$this->assertEquals('hash', $response->headers->get('X-Hash'));
5859
$this->assertEquals('X-SessionId', $response->headers->get('Vary'));
5960
$this->assertEquals('max-age=30, private', $response->headers->get('Cache-Control'));
6061
}
@@ -64,16 +65,15 @@ public function testOnKernelRequestNotMatched()
6465
$request = new Request();
6566
$request->setMethod('HEAD');
6667

67-
$requestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
68-
$requestMatcher->shouldReceive('matches')->with($request)->andReturn(false);
68+
$requestMatcher = $this->getRequestMatcher($request, false);
69+
$hashGenerator = \Mockery::mock('\FOS\HttpCache\UserContext\HashGenerator');
6970

70-
$userContextSubscriber = new UserContextSubscriber($requestMatcher, new HashGenerator(), 'X-SessionId', 'X-Hash');
71+
$userContextSubscriber = new UserContextSubscriber($requestMatcher, $hashGenerator, array('X-SessionId'), 'X-Hash');
7172
$event = $this->getKernelRequestEvent($request);
7273

7374
$userContextSubscriber->onKernelRequest($event);
7475

7576
$response = $event->getResponse();
76-
$hash = hash('sha256', serialize(array()));
7777

7878
$this->assertNull($response);
7979
}
@@ -84,31 +84,34 @@ public function testOnKernelResponse()
8484
$request->setMethod('HEAD');
8585
$request->headers->set('X-Hash', 'hash');
8686

87-
$requestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
88-
$requestMatcher->shouldReceive('matches')->with($request)->andReturn(false);
87+
$requestMatcher = $this->getRequestMatcher($request, false);
88+
$hashGenerator = \Mockery::mock('\FOS\HttpCache\UserContext\HashGenerator');
8989

90-
$userContextSubscriber = new UserContextSubscriber($requestMatcher, new HashGenerator(), 'X-SessionId', 'X-Hash');
90+
$userContextSubscriber = new UserContextSubscriber($requestMatcher, $hashGenerator, array('X-SessionId'), 'X-Hash');
9191
$event = $this->getKernelResponseEvent($request);
9292

9393
$userContextSubscriber->onKernelResponse($event);
9494

9595
$this->assertContains('X-Hash', $event->getResponse()->headers->get('Vary'));
9696
}
9797

98+
/**
99+
* If there is no hash in the request, vary on the user identifier.
100+
*/
98101
public function testOnKernelResponseNotCached()
99102
{
100103
$request = new Request();
101104
$request->setMethod('HEAD');
102105

103-
$requestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
104-
$requestMatcher->shouldReceive('matches')->with($request)->andReturn(false);
106+
$requestMatcher = $this->getRequestMatcher($request, false);
107+
$hashGenerator = \Mockery::mock('\FOS\HttpCache\UserContext\HashGenerator');
105108

106-
$userContextSubscriber = new UserContextSubscriber($requestMatcher, new HashGenerator(), 'X-SessionId', 'X-Hash');
109+
$userContextSubscriber = new UserContextSubscriber($requestMatcher, $hashGenerator, array('X-SessionId'), 'X-Hash');
107110
$event = $this->getKernelResponseEvent($request);
108111

109112
$userContextSubscriber->onKernelResponse($event);
110113

111-
$this->assertNull($event->getResponse()->headers->get('Vary'));
114+
$this->assertEquals('X-SessionId', $event->getResponse()->headers->get('Vary'));
112115
}
113116

114117
protected function getKernelRequestEvent(Request $request)
@@ -129,4 +132,18 @@ protected function getKernelResponseEvent(Request $request, Response $response =
129132
$response ?: new Response()
130133
);
131134
}
135+
136+
/**
137+
* @param Request $request
138+
* @param bool $match
139+
*
140+
* @return \Mockery\MockInterface|RequestMatcherInterface
141+
*/
142+
private function getRequestMatcher(Request $request, $match)
143+
{
144+
$requestMatcher = \Mockery::mock('\Symfony\Component\HttpFoundation\RequestMatcherInterface');
145+
$requestMatcher->shouldReceive('matches')->with($request)->andReturn($match);
146+
147+
return $requestMatcher;
148+
}
132149
}

UserContext/RequestMatcher.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function __construct($accept = 'application/vnd.fos.user-context-hash', $
2222
*/
2323
public function matches(Request $request)
2424
{
25-
if ($this->accept != $request->headers->get('accept', null)) {
25+
if ($this->accept !== null && $this->accept != $request->headers->get('accept', null)) {
2626
return false;
2727
}
2828

@@ -32,4 +32,4 @@ public function matches(Request $request)
3232

3333
return true;
3434
}
35-
}
35+
}

UserContext/RoleProvider.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,50 @@
44

55
use FOS\HttpCache\UserContext\ContextProviderInterface;
66
use FOS\HttpCache\UserContext\UserContext;
7+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
8+
use Symfony\Component\Security\Core\Role\RoleInterface;
79
use Symfony\Component\Security\Core\SecurityContextInterface;
810

911
/**
10-
* RoleProvider add roles to the UserContext for the hash generation
12+
* The RoleProvider adds roles to the UserContext for the hash generation.
1113
*/
1214
class RoleProvider implements ContextProviderInterface
1315
{
16+
/**
17+
* @var SecurityContextInterface|null
18+
*/
1419
private $context;
1520

16-
public function __construct(SecurityContextInterface $context)
21+
/**
22+
* Create the role provider with a security context.
23+
*
24+
* The security context is optional to not fail on routes that have no
25+
* firewall. It is however not valid to call updateUserContext when not in
26+
* a firewall context.
27+
*
28+
* @param SecurityContextInterface|null $context
29+
*/
30+
public function __construct(SecurityContextInterface $context = null)
1731
{
1832
$this->context = $context;
1933
}
2034

2135
/**
2236
* {@inheritDoc}
37+
*
38+
* @throws InvalidConfigurationException when called without a security context being set.
2339
*/
2440
public function updateUserContext(UserContext $context)
2541
{
42+
if (null === $this->context) {
43+
throw new InvalidConfigurationException('The context hash URL must be under a firewall.');
44+
}
45+
2646
if (null === $token = $this->context->getToken()) {
2747
return;
2848
}
2949

30-
$roles = array_map(function ($role) {
50+
$roles = array_map(function (RoleInterface $role) {
3151
return $role->getRole();
3252
}, $token->getRoles());
3353

0 commit comments

Comments
 (0)