Skip to content

Commit

Permalink
Merge pull request #414 from FriendsOfSymfony/merge-1.4-to-master
Browse files Browse the repository at this point in the history
Merge 1.4 to master
  • Loading branch information
dbu committed Mar 23, 2018
2 parents 629a06f + 47b8dbf commit 9c9afe9
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 16 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,12 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
default cache tagging system, but if you can install the varnish modules in
your system, it is recommended to update to xkey.

### Symfony user context
* You can now also specify which headers are used for
authentication to detect anonymous requests. By default, the headers are the
previously hardcoded `Authorization`, `HTTP_AUTHORIZATION` and
`PHP_AUTH_USER`.

2.1.3
-----

Expand Down Expand Up @@ -159,6 +165,19 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
tags are formatted, use the new `header_formatter` option with a
`TagHeaderFormatter`.

1.4.5
-----

* Symfony user context: You can now also specify which headers are used for
authentication to detect anonymous requests. By default, the headers are the
previously hardcoded `Authorization`, `HTTP_AUTHORIZATION` and
`PHP_AUTH_USER`.

1.4.4
-----

* Avoid problem with [http_method_override](http://symfony.com/doc/current/reference/configuration/framework.html#configuration-framework-http-method-override).

1.4.3
-----

Expand Down
6 changes: 6 additions & 0 deletions doc/symfony-cache-configuration.rst
Expand Up @@ -287,7 +287,13 @@ options through the constructor:

**default**: ``GET``

* **user_identifier_headers**: List of request headers that authenticate a non-anonymous request.

**default**: ``['Authorization', 'HTTP_AUTHORIZATION', 'PHP_AUTH_USER']``

* **session_name_prefix**: Prefix for session cookies. Must match your PHP session configuration.
If cookies are not relevant in your application, you can set this to ``false`` to ignore any
cookies. (**Only set this to ``false`` if you do not use sessions at all.**)

**default**: ``PHPSESSID``

Expand Down
41 changes: 25 additions & 16 deletions src/SymfonyCache/UserContextListener.php
Expand Up @@ -11,6 +11,7 @@

namespace FOS\HttpCache\SymfonyCache;

use FOS\HttpCache\UserContext\AnonymousRequestMatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -50,7 +51,10 @@ class UserContextListener implements EventSubscriberInterface
* match the setup for the Vary header in the backend application.
* - user_hash_uri: Target URI used in the request for user context hash generation.
* - user_hash_method: HTTP Method used with the hash lookup request for user context hash generation.
* - user_identifier_headers: List of request headers that authenticate a non-anonymous request.
* - session_name_prefix: Prefix for session cookies. Must match your PHP session configuration.
* To completely ignore the cookies header and consider requests with cookies
* anonymous, pass false for this option.
*
* @param array $options Options to overwrite the default options
*
Expand All @@ -65,9 +69,19 @@ public function __construct(array $options = [])
'user_hash_header' => 'X-User-Context-Hash',
'user_hash_uri' => '/_fos_user_context_hash',
'user_hash_method' => 'GET',
'user_identifier_headers' => ['Authorization', 'HTTP_AUTHORIZATION', 'PHP_AUTH_USER'],
'session_name_prefix' => 'PHPSESSID',
]);

$resolver->setAllowedTypes('anonymous_hash', ['null', 'string']);
$resolver->setAllowedTypes('user_hash_accept_header', ['string']);
$resolver->setAllowedTypes('user_hash_header', ['string']);
$resolver->setAllowedTypes('user_hash_uri', ['string']);
$resolver->setAllowedTypes('user_hash_method', ['string']);
// actually string[] but that is not supported by symfony < 3.4
$resolver->setAllowedTypes('user_identifier_headers', ['array']);
$resolver->setAllowedTypes('session_name_prefix', ['string', 'boolean']);

$this->options = $resolver->resolve($options);
}

Expand Down Expand Up @@ -126,6 +140,12 @@ public function preHandle(CacheEvent $event)
protected function cleanupHashLookupRequest(Request $hashLookupRequest, Request $originalRequest)
{
$sessionIds = [];
if (!$this->options['session_name_prefix']) {
$hashLookupRequest->headers->remove('Cookie');

return;
}

foreach ($originalRequest->cookies as $name => $value) {
if ($this->isSessionName($name)) {
$sessionIds[$name] = $value;
Expand Down Expand Up @@ -186,23 +206,12 @@ private function getUserHash(HttpKernelInterface $kernel, Request $request)
*/
private function isAnonymous(Request $request)
{
// You might have to enable rewriting of the Authorization header in your server config or .htaccess:
// RewriteEngine On
// RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
if ($request->server->has('AUTHORIZATION') ||
$request->server->has('HTTP_AUTHORIZATION') ||
$request->server->has('PHP_AUTH_USER')
) {
return false;
}

foreach ($request->cookies as $name => $value) {
if ($this->isSessionName($name)) {
return false;
}
}
$anonymousRequestMatcher = new AnonymousRequestMatcher([
'user_identifier_headers' => $this->options['user_identifier_headers'],
'session_name_prefix' => $this->options['session_name_prefix'],
]);

return true;
return $anonymousRequestMatcher->matches($request);
}

/**
Expand Down
67 changes: 67 additions & 0 deletions src/UserContext/AnonymousRequestMatcher.php
@@ -0,0 +1,67 @@
<?php

/*
* This file is part of the FOSHttpCache package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\HttpCache\UserContext;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* Matches anonymous requests using a list of identification headers.
*/
class AnonymousRequestMatcher implements RequestMatcherInterface
{
/**
* @var array
*/
private $options;

/**
* @param array $options Configuration for the matcher. All options are required because this matcher is usually
* created by the UserContextListener which provides the default values.
*
* @throws \InvalidArgumentException if unknown keys are found in $options
*/
public function __construct(array $options = [])
{
$resolver = new OptionsResolver();
$resolver->setRequired(['user_identifier_headers', 'session_name_prefix']);

// actually string[] but that is not supported by symfony < 3.4
$resolver->setAllowedTypes('user_identifier_headers', ['array']);
$resolver->setAllowedTypes('session_name_prefix', ['string', 'boolean']);

$this->options = $resolver->resolve($options);
}

public function matches(Request $request)
{
// You might have to enable rewriting of the Authorization header in your server config or .htaccess:
// RewriteEngine On
// RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
foreach ($this->options['user_identifier_headers'] as $header) {
if ($request->headers->has($header)) {
return false;
}
}

if ($this->options['session_name_prefix']) {
foreach ($request->cookies as $name => $value) {
if (0 === strpos($name, $this->options['session_name_prefix'])) {
return false;
}
}
}

return true;
}
}
30 changes: 30 additions & 0 deletions tests/Unit/SymfonyCache/UserContextListenerTest.php
Expand Up @@ -223,6 +223,36 @@ function (Request $request) use ($that, $hashRequest) {
$this->assertSame($expectedContextHash, $request->headers->get($options['user_hash_header']));
}

/**
* When the session_name_prefix is set to false, the cookie header is completely ignored.
*
* This test does not have authentication headers and thus considers the request anonymous.
*/
public function testUserHashUserIgnoreCookies()
{
$userContextListener = new UserContextListener([
'session_name_prefix' => false,
'anonymous_hash' => '38015b703d82206ebc01d17a39c727e5',
]);

$sessionId1 = 'my_session_id';
$cookies = [
'PHPSESSID' => $sessionId1,
];
$cookieString = "PHPSESSID=$sessionId1";
$request = Request::create('/foo', 'GET', [], $cookies, [], ['Cookie' => $cookieString]);

$this->kernel->shouldNotReceive('handle');
$event = new CacheEvent($this->kernel, $request);

$userContextListener->preHandle($event);
$response = $event->getResponse();

$this->assertNull($response);
$this->assertTrue($request->headers->has('X-User-Context-Hash'));
$this->assertSame('38015b703d82206ebc01d17a39c727e5', $request->headers->get('X-User-Context-Hash'));
}

/**
* @dataProvider provideConfigOptions
*/
Expand Down

0 comments on commit 9c9afe9

Please sign in to comment.