Skip to content

Commit 0e49410

Browse files
committed
Implemented Symfony reverse proxy support for user context hash.
Ref #26
1 parent 7321b95 commit 0e49410

File tree

3 files changed

+176
-1
lines changed

3 files changed

+176
-1
lines changed

EventListener/UserContextSubscriber.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public function onKernelRequest(GetResponseEvent $event)
103103
if ($this->ttl > 0) {
104104
$response->setClientTtl($this->ttl);
105105
$response->setVary($this->userIdentifierHeaders);
106+
$response->setPublic();
106107
} else {
107108
$response->setClientTtl(0);
108109
$response->headers->addCacheControlDirective('no-cache');

HttpCache.php

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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;
13+
14+
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache as BaseHttpCache;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpKernel\HttpKernelInterface;
18+
19+
/**
20+
* Base class for enhanced Symfony reverse proxy.
21+
*
22+
* @author Jérôme Vieilledent <[email protected]>
23+
*
24+
* {@inheritdoc}
25+
*/
26+
abstract class HttpCache extends BaseHttpCache
27+
{
28+
/**
29+
* Hash for anonymous user.
30+
*/
31+
const ANONYMOUS_HASH = '38015b703d82206ebc01d17a39c727e5';
32+
33+
/**
34+
* Accept header value to be used to request the user hash to the backend application.
35+
* It must match the one defined in FOSHttpCacheBundle's configuration.
36+
*/
37+
const USER_HASH_ACCEPT_HEADER = 'application/vnd.fos.user-context-hash';
38+
39+
/**
40+
* Name of the header the user context hash will be stored into.
41+
* It must match the one defined in FOSHttpCacheBundle's configuration.
42+
*/
43+
const USER_HASH_HEADER = 'X-User-Context-Hash';
44+
45+
/**
46+
* URI used with the forwarded request for user context hash generation.
47+
*/
48+
const USER_HASH_URI = '/_fos_user_context_hash';
49+
50+
/**
51+
* HTTP Method used with the forwarded request for user context hash generation.
52+
*/
53+
const USER_HASH_METHOD = 'GET';
54+
55+
/**
56+
* Prefix for session names.
57+
* Must match your session configuration.
58+
*/
59+
const SESSION_NAME_PREFIX = 'PHPSESSID';
60+
61+
/**
62+
* Generated user hash.
63+
*
64+
* @var string
65+
*/
66+
private $userHash;
67+
68+
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
69+
{
70+
if (!$this->isInternalRequest($request)) {
71+
// Prevent tampering attacks on the hash mechanism
72+
if ($request->headers->get('accept') === static::USER_HASH_ACCEPT_HEADER
73+
|| $request->headers->get(static::USER_HASH_HEADER) !== null) {
74+
return new Response('', 400);
75+
}
76+
77+
if ($request->isMethodSafe()) {
78+
$request->headers->set(static::USER_HASH_HEADER, $this->getUserHash($request));
79+
}
80+
}
81+
82+
return parent::handle($request, $type, $catch);
83+
}
84+
85+
/**
86+
* Checks if passed request object is to be considered internal (e.g. for user hash lookup).
87+
*
88+
* @param Request $request
89+
*
90+
* @return bool
91+
*/
92+
protected function isInternalRequest(Request $request)
93+
{
94+
return $request->attributes->get('internalRequest', false) === true;
95+
}
96+
97+
/**
98+
* Returns the user context hash for $request.
99+
*
100+
* @param Request $request
101+
*
102+
* @return string
103+
*/
104+
protected function getUserHash(Request $request)
105+
{
106+
if (isset($this->userHash)) {
107+
return $this->userHash;
108+
}
109+
110+
if ($this->isAnonymous($request)) {
111+
return $this->userHash = static::ANONYMOUS_HASH;
112+
}
113+
114+
// Forward the request to generate the user hash
115+
$forwardReq = $this->generateForwardRequest($request);
116+
$resp = $this->handle($forwardReq);
117+
// Store the user hash in memory for sub-requests (processed in the same thread).
118+
return $this->userHash = $resp->headers->get(static::USER_HASH_HEADER);
119+
}
120+
121+
/**
122+
* Checks if current request is considered anonymous.
123+
*
124+
* @param Request $request
125+
*
126+
* @return bool
127+
*/
128+
protected function isAnonymous(Request $request)
129+
{
130+
foreach ($request->cookies as $name => $value) {
131+
if ($this->isSessionName($name)) {
132+
return false;
133+
}
134+
}
135+
136+
return true;
137+
}
138+
139+
/**
140+
* Checks if passed string can be considered as a session name, such as would be used in cookies.
141+
*
142+
* @param string $name
143+
*
144+
* @return bool
145+
*/
146+
protected function isSessionName($name)
147+
{
148+
return strpos($name, static::SESSION_NAME_PREFIX) === 0;
149+
}
150+
151+
/**
152+
* Generates the request object that will be forwarded to get the user context hash.
153+
*
154+
* @param Request $request
155+
*
156+
* @return Request
157+
*/
158+
protected function generateForwardRequest(Request $request)
159+
{
160+
$forwardReq = Request::create(static::USER_HASH_URI, static::USER_HASH_METHOD, array(), $request->cookies->all(), array(), $request->server->all() );
161+
$forwardReq->attributes->set('internalRequest', true);
162+
$forwardReq->headers->set('Accept', static::USER_HASH_ACCEPT_HEADER);
163+
// Clean cookie header to only get proper sessionIds in it. This is to make the hash request cacheable.
164+
$sessionIds = array();
165+
foreach ($request->cookies as $name => $value) {
166+
if ($this->isSessionName($name)) {
167+
$sessionIds[] = $value;
168+
}
169+
}
170+
$forwardReq->headers->set('Cookie', implode('|', $sessionIds));
171+
172+
return $forwardReq;
173+
}
174+
}

Tests/Unit/EventListener/UserContextSubscriberTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function testOnKernelRequestCached()
9494
$this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response);
9595
$this->assertEquals('hash', $response->headers->get('X-Hash'));
9696
$this->assertEquals('X-SessionId', $response->headers->get('Vary'));
97-
$this->assertEquals('max-age=30, private', $response->headers->get('Cache-Control'));
97+
$this->assertEquals('max-age=30, public', $response->headers->get('Cache-Control'));
9898
}
9999

100100
public function testOnKernelRequestNotMatched()

0 commit comments

Comments
 (0)