Skip to content

Commit c60c5e9

Browse files
authored
Merge pull request #410 from FriendsOfSymfony/user_identifier_headers
Expand AnonymousRequestMatcher by user_identifier_headers option
2 parents 6276652 + e60ad47 commit c60c5e9

File tree

5 files changed

+104
-13
lines changed

5 files changed

+104
-13
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
Changelog
22
=========
33

4+
1.4.5
5+
-----
6+
7+
* Symfony user context: You can now also specify which headers are used for
8+
authentication to detect anonymous requests. By default, the headers are the
9+
previously hardcoded `Authorization`, `HTTP_AUTHORIZATION` and
10+
`PHP_AUTH_USER`.
11+
12+
1.4.4
13+
-----
14+
15+
* Avoid problem with [http_method_override](http://symfony.com/doc/current/reference/configuration/framework.html#configuration-framework-http-method-override).
16+
417
1.4.3
518
-----
619

doc/symfony-cache-configuration.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,13 @@ options through the constructor:
147147

148148
**default**: ``GET``
149149

150+
* **user_identifier_headers**: List of request headers that authenticate a non-anonymous request.
151+
152+
**default**: ``['Authorization', 'HTTP_AUTHORIZATION', 'PHP_AUTH_USER']``
153+
150154
* **session_name_prefix**: Prefix for session cookies. Must match your PHP session configuration.
155+
If cookies are not relevant in your application, you can set this to ``false`` to ignore any
156+
cookies. (**Only set this to ``false`` if you do not use sessions at all.**)
151157

152158
**default**: ``PHPSESSID``
153159

src/SymfonyCache/UserContextSubscriber.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpFoundation\Request;
1717
use Symfony\Component\HttpFoundation\Response;
1818
use Symfony\Component\HttpKernel\HttpKernelInterface;
19+
use Symfony\Component\HttpKernel\Kernel;
1920
use Symfony\Component\OptionsResolver\OptionsResolver;
2021

2122
/**
@@ -51,7 +52,10 @@ class UserContextSubscriber implements EventSubscriberInterface
5152
* match the setup for the Vary header in the backend application.
5253
* - user_hash_uri: Target URI used in the request for user context hash generation.
5354
* - user_hash_method: HTTP Method used with the hash lookup request for user context hash generation.
55+
* - user_identifier_headers: List of request headers that authenticate a non-anonymous request.
5456
* - session_name_prefix: Prefix for session cookies. Must match your PHP session configuration.
57+
* To completely ignore the cookies header and consider requests with cookies
58+
* anonymous, pass false for this option.
5559
*
5660
* @param array $options Options to overwrite the default options
5761
*
@@ -66,8 +70,21 @@ public function __construct(array $options = array())
6670
'user_hash_header' => 'X-User-Context-Hash',
6771
'user_hash_uri' => '/_fos_user_context_hash',
6872
'user_hash_method' => 'GET',
73+
'user_identifier_headers' => array('Authorization', 'HTTP_AUTHORIZATION', 'PHP_AUTH_USER'),
6974
'session_name_prefix' => 'PHPSESSID',
7075
));
76+
if (class_exists('Symfony\Component\HttpKernel\Kernel')
77+
&& (Kernel::MAJOR_VERSION > 2 || Kernel::MINOR_VERSION > 5)
78+
) {
79+
$resolver->setAllowedTypes('anonymous_hash', array('string'));
80+
$resolver->setAllowedTypes('user_hash_accept_header', array('string'));
81+
$resolver->setAllowedTypes('user_hash_header', array('string'));
82+
$resolver->setAllowedTypes('user_hash_uri', array('string'));
83+
$resolver->setAllowedTypes('user_hash_method', array('string'));
84+
// actually string[] but that is not supported by symfony < 3.4
85+
$resolver->setAllowedTypes('user_identifier_headers', array('array'));
86+
$resolver->setAllowedTypes('session_name_prefix', array('string', 'boolean'));
87+
}
7188

7289
$this->options = $resolver->resolve($options);
7390
}
@@ -126,6 +143,11 @@ public function preHandle(CacheEvent $event)
126143
*/
127144
protected function cleanupHashLookupRequest(Request $hashLookupRequest, Request $originalRequest)
128145
{
146+
if (!$this->options['session_name_prefix']) {
147+
$hashLookupRequest->headers->remove('Cookie');
148+
149+
return;
150+
}
129151
$sessionIds = array();
130152
foreach ($originalRequest->cookies as $name => $value) {
131153
if ($this->isSessionName($name)) {
@@ -186,7 +208,10 @@ private function getUserHash(HttpKernelInterface $kernel, Request $request)
186208
*/
187209
private function isAnonymous(Request $request)
188210
{
189-
$anonymousRequestMatcher = new AnonymousRequestMatcher($this->options['session_name_prefix']);
211+
$anonymousRequestMatcher = new AnonymousRequestMatcher(array(
212+
'user_identifier_headers' => $this->options['user_identifier_headers'],
213+
'session_name_prefix' => $this->options['session_name_prefix'],
214+
));
190215

191216
return $anonymousRequestMatcher->matches($request);
192217
}

src/UserContext/AnonymousRequestMatcher.php

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,56 @@
1313

1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
use Symfony\Component\HttpKernel\Kernel;
17+
use Symfony\Component\OptionsResolver\OptionsResolver;
1618

1719
/**
1820
* Matches anonymous requests using a list of identification headers.
1921
*/
2022
class AnonymousRequestMatcher implements RequestMatcherInterface
2123
{
22-
private $sessionNamePrefix;
24+
/**
25+
* @var array
26+
*/
27+
private $options;
2328

2429
/**
25-
* @param string $sessionNamePrefix Prefix for session cookies. Must match your PHP session configuration
30+
* @param array $options Configuration for the matcher. All options are required because this matcher is usually
31+
* created by the UserContextSubscriber which provides the default values.
32+
*
33+
* @throws \InvalidArgumentException if unknown keys are found in $options
2634
*/
27-
public function __construct($sessionNamePrefix)
35+
public function __construct(array $options = array())
2836
{
29-
$this->sessionNamePrefix = $sessionNamePrefix;
37+
$resolver = new OptionsResolver();
38+
$resolver->setRequired(array('user_identifier_headers', 'session_name_prefix'));
39+
if (class_exists('Symfony\Component\HttpKernel\Kernel')
40+
&& (Kernel::MAJOR_VERSION > 2 || Kernel::MINOR_VERSION > 5)
41+
) {
42+
// actually string[] but that is not supported by symfony < 3.4
43+
$resolver->setAllowedTypes('user_identifier_headers', array('array'));
44+
$resolver->setAllowedTypes('session_name_prefix', array('string', 'boolean'));
45+
}
46+
47+
$this->options = $resolver->resolve($options);
3048
}
3149

3250
public function matches(Request $request)
3351
{
3452
// You might have to enable rewriting of the Authorization header in your server config or .htaccess:
3553
// RewriteEngine On
3654
// RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
37-
if ($request->server->has('AUTHORIZATION') ||
38-
$request->server->has('HTTP_AUTHORIZATION') ||
39-
$request->server->has('PHP_AUTH_USER')
40-
) {
41-
return false;
55+
foreach ($this->options['user_identifier_headers'] as $header) {
56+
if ($request->headers->has($header)) {
57+
return false;
58+
}
4259
}
4360

44-
foreach ($request->cookies as $name => $value) {
45-
if (0 === strpos($name, $this->sessionNamePrefix)) {
46-
return false;
61+
if ($this->options['session_name_prefix']) {
62+
foreach ($request->cookies as $name => $value) {
63+
if (0 === strpos($name, $this->options['session_name_prefix'])) {
64+
return false;
65+
}
4766
}
4867
}
4968

tests/Unit/SymfonyCache/UserContextSubscriberTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,34 @@ public function testUserHashUserWithSession($arg, $options)
178178
$this->assertSame($expectedContextHash, $request->headers->get($options['user_hash_header']));
179179
}
180180

181+
/**
182+
* When the session_name_prefix is set to false, the cookie header is completely ignored.
183+
*
184+
* This test does not have authentication headers and thus considers the request anonymous.
185+
*/
186+
public function testUserHashUserIgnoreCookies()
187+
{
188+
$userContextSubscriber = new UserContextSubscriber([
189+
'session_name_prefix' => false,
190+
]);
191+
192+
$sessionId1 = 'my_session_id';
193+
$cookies = array(
194+
'PHPSESSID' => $sessionId1,
195+
);
196+
$cookieString = "PHPSESSID=$sessionId1";
197+
$request = Request::create('/foo', 'GET', array(), $cookies, array(), array('Cookie' => $cookieString));
198+
199+
$event = new CacheEvent($this->kernel, $request);
200+
201+
$userContextSubscriber->preHandle($event);
202+
$response = $event->getResponse();
203+
204+
$this->assertNull($response);
205+
$this->assertTrue($request->headers->has('X-User-Context-Hash'));
206+
$this->assertSame('38015b703d82206ebc01d17a39c727e5', $request->headers->get('X-User-Context-Hash'));
207+
}
208+
181209
/**
182210
* @dataProvider provideConfigOptions
183211
*/

0 commit comments

Comments
 (0)