Compare commits
33 Commits
Author | SHA1 | Date |
---|---|---|
Fabien Potencier | e8d2a2ed1f | |
Fabien Potencier | 5484ff9b43 | |
Fabien Potencier | a4987c6105 | |
Nicolas Grekas | 1db2d2eda8 | |
Nicolas Grekas | 7f912bbb78 | |
Nicolas Grekas | 6ad5fe68c2 | |
Nicolas Grekas | 12adeb90df | |
Fabien Potencier | 4639525c79 | |
Fabien Potencier | b8082cb94a | |
Fabien Potencier | c7986c2af6 | |
Nicolas Grekas | ea4121ae55 | |
Christian Flothmann | a8d95ddb1f | |
Christian Flothmann | 2ed66b2ace | |
Fabien Potencier | 9b3c298c17 | |
Christian Flothmann | a10c07cd57 | |
Fabien Potencier | 42bc43fd70 | |
Fabien Potencier | b46fc93785 | |
ismail1432 | 1ebe203413 | |
Fabien Potencier | e1f817ffe2 | |
Nicolas Grekas | 8e1b906ba0 | |
Fabien Potencier | 6ff58021c7 | |
Fabien Potencier | 9d3989faa8 | |
Christian Flothmann | 86a039a547 | |
Fabien Potencier | f6f2f97a65 | |
Ryan Weaver | 51fa731abb | |
Ryan Weaver | ab384b0f34 | |
Ryan Weaver | 614b83f95d | |
Fabien Potencier | 1e1cf5cf68 | |
Nicolas Grekas | e0c9884fa6 | |
Fabien Potencier | b66afe62e8 | |
Fabien Potencier | 98e128ccee | |
Fabien Potencier | 909c9ae5e7 | |
Fabien Potencier | 0c3890fe63 |
34
.travis.yml
34
.travis.yml
|
@ -36,6 +36,7 @@ cache:
|
|||
directories:
|
||||
- .phpunit
|
||||
- php-$MIN_PHP
|
||||
- ~/php-ext
|
||||
|
||||
services:
|
||||
- memcached
|
||||
|
@ -108,10 +109,26 @@ before_install:
|
|||
[[ $PHP = 5.* ]] && echo extension = memcache.so >> $INI
|
||||
if [[ $PHP = 5.* ]]; then
|
||||
echo extension = mongo.so >> $INI
|
||||
elif [[ $PHP = 7.* ]]; then
|
||||
echo extension = mongodb.so >> $INI
|
||||
fi
|
||||
|
||||
# tpecl is a helper to compile and cache php extensions
|
||||
tpecl () {
|
||||
local ext_name=$1
|
||||
local ext_so=$2
|
||||
local INI=$3
|
||||
local ext_dir=$(php -r "echo ini_get('extension_dir');")
|
||||
local ext_cache=~/php-ext/$(basename $ext_dir)/$ext_name
|
||||
|
||||
if [[ -e $ext_cache/$ext_so ]]; then
|
||||
echo extension = $ext_cache/$ext_so >> $INI
|
||||
else
|
||||
mkdir -p $ext_cache
|
||||
echo yes | pecl install -f $ext_name &&
|
||||
cp $ext_dir/$ext_so $ext_cache
|
||||
fi
|
||||
}
|
||||
export -f tpecl
|
||||
|
||||
# Matrix lines for intermediate PHP versions are skipped for pull requests
|
||||
if [[ ! $deps && ! $PHP = ${MIN_PHP%.*} && ! $PHP = hhvm* && $TRAVIS_PULL_REQUEST != false ]]; then
|
||||
deps=skip
|
||||
|
@ -130,10 +147,17 @@ before_install:
|
|||
- |
|
||||
# Install extra PHP extensions
|
||||
if [[ ! $skip && $PHP = 5.* ]]; then
|
||||
([[ $deps ]] || tfold ext.symfony_debug 'cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo extension = $(pwd)/modules/symfony_debug.so >> '"$INI") &&
|
||||
tfold ext.apcu4 'echo yes | pecl install -f apcu-4.0.11'
|
||||
([[ $deps ]] || tfold ext.symfony_debug 'cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo extension = $(pwd)/modules/symfony_debug.so >> '"$INI")
|
||||
tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI
|
||||
elif [[ ! $skip && $PHP = 7.* ]]; then
|
||||
tfold ext.apcu5 'echo yes | pecl install -f apcu-5.1.6'
|
||||
# install libsodium
|
||||
sudo add-apt-repository ppa:ondrej/php -y
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install libsodium-dev -y
|
||||
|
||||
tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI
|
||||
tfold ext.libsodium tpecl libsodium sodium.so $INI
|
||||
tfold ext.mongodb tpecl mongodb-1.4.0RC1 mongodb.so $INI
|
||||
fi
|
||||
|
||||
- |
|
||||
|
|
|
@ -7,6 +7,56 @@ in 3.3 minor versions.
|
|||
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
|
||||
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.3.0...v3.3.1
|
||||
|
||||
* 3.3.18 (2018-08-01)
|
||||
|
||||
* security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas)
|
||||
* security #cve-2018-14773 [HttpFoundation] Remove support for legacy and risky HTTP headers (nicolas-grekas)
|
||||
|
||||
* 3.3.17 (2018-05-25)
|
||||
|
||||
* security #cve-2018-11407 [Ldap] cast to string when checking empty passwords
|
||||
* security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured
|
||||
* security #cve-2018-11406 clear CSRF tokens when the user is logged out
|
||||
* security #cve-2018-11385 migrating session for UsernamePasswordJsonAuthenticationListener
|
||||
* security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode
|
||||
|
||||
* 3.3.16 (2018-01-29)
|
||||
|
||||
* bug #25922 [HttpFoundation] Use the correct syntax for session gc based on Pdo driver (tanasecosminromeo)
|
||||
* bug #25933 Disable CSP header on exception pages only in debug (ostrolucky)
|
||||
* bug #25926 [Form] Fixed Button::setParent() when already submitted (HeahDude)
|
||||
* bug #25927 [Form] Fixed submitting disabled buttons (HeahDude)
|
||||
* bug #25891 [DependencyInjection] allow null values for root nodes in YAML configs (xabbuh)
|
||||
* bug #24864 Have weak_vendors ignore deprecations from outside (greg0ire)
|
||||
* bug #25848 [Validator] add missing parent isset and add test (Simperfit)
|
||||
* bug #25869 [Process] Skip environment variables with false value in Process (francoispluchino)
|
||||
* bug #25864 [Yaml] don't split lines on carriage returns when dumping (xabbuh)
|
||||
* bug #25861 do not conflict with egulias/email-validator 2.0+ (xabbuh)
|
||||
* bug #25851 [Validator] Conflict with egulias/email-validator 2.0 (emodric)
|
||||
* bug #25837 [SecurityBundle] Don't register in memory users as services (chalasr)
|
||||
* bug #25835 [HttpKernel] DebugHandlersListener should always replace the existing exception handler (nicolas-grekas)
|
||||
* bug #25829 [Debug] Always decorate existing exception handlers to deal with fatal errors (nicolas-grekas)
|
||||
* bug #25824 Fixing a bug where the dump() function depended on bundle ordering (weaverryan)
|
||||
* bug #25789 Enableable ArrayNodeDefinition is disabled for empty configuration (kejwmen)
|
||||
* bug #25822 [Cache] Fix handling of apcu_fetch() edgy behavior (nicolas-grekas)
|
||||
* bug #25816 Problem in phar see mergerequest #25579 (betzholz)
|
||||
* bug #25781 [Form] Disallow transform dates beyond the year 9999 (curry684)
|
||||
* bug #25287 [Serializer] DateTimeNormalizer handling of null and empty values (returning it instead of new object) (Simperfit)
|
||||
* bug #25812 Copied NO language files to the new NB locale (derrabus)
|
||||
* bug #25801 [Router] Skip anonymous classes when loading annotated routes (pierredup)
|
||||
* bug #25657 [Security] Fix fatal error on non string username (chalasr)
|
||||
* bug #25791 [Routing] Make sure we only build routes once (sroze)
|
||||
* bug #25799 Fixed Request::__toString ignoring cookies (Toflar)
|
||||
* bug #25755 [Debug] prevent infinite loop with faulty exception handlers (nicolas-grekas)
|
||||
* bug #25771 [Validator] 19 digits VISA card numbers are valid (xabbuh)
|
||||
* bug #25751 [FrameworkBundle] Add the missing `enabled` session attribute (sroze)
|
||||
* bug #25750 [HttpKernel] Turn bad hosts into 400 instead of 500 (nicolas-grekas)
|
||||
* bug #25490 [Serializer] Fixed throwing exception with option JSON_PARTIAL_OUTPUT_ON_ERROR (diversantvlz)
|
||||
* bug #25709 Tweaked some styles in the profiler tables (javiereguiluz)
|
||||
* bug #25696 [FrameworkBundle] Fix using "annotations.cached_reader" in after-removing passes (nicolas-grekas)
|
||||
* feature #25669 [Security] Fail gracefully if the security token cannot be unserialized from the session (thewilkybarkid)
|
||||
* bug #25700 Run simple-phpunit with --no-suggest option (ro0NL)
|
||||
|
||||
* 3.3.15 (2018-01-05)
|
||||
|
||||
* bug #25532 [HttpKernel] Disable CSP header on exception pages (ostrolucky)
|
||||
|
|
10
appveyor.yml
10
appveyor.yml
|
@ -18,8 +18,8 @@ install:
|
|||
- mkdir c:\php && cd c:\php
|
||||
- appveyor DownloadFile https://raw.githubusercontent.com/symfony/binary-utils/master/cacert.pem
|
||||
- appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-5.5.9-nts-Win32-VC11-x86.zip
|
||||
- 7z x php-5.5.9-nts-Win32-VC11-x86.zip -y >nul
|
||||
- appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-7.1.3-Win32-VC14-x86.zip
|
||||
- 7z x php-7.1.3-Win32-VC14-x86.zip -y >nul
|
||||
- cd ext
|
||||
- appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-4.0.10-5.5-nts-vc11-x86.zip
|
||||
- 7z x php_apcu-4.0.10-5.5-nts-vc11-x86.zip -y >nul
|
||||
|
@ -45,19 +45,21 @@ install:
|
|||
- echo extension=php_pdo_sqlite.dll >> php.ini-max
|
||||
- echo extension=php_curl.dll >> php.ini-max
|
||||
- echo curl.cainfo=c:\php\cacert.pem >> php.ini-max
|
||||
- copy /Y php.ini-max php.ini
|
||||
- copy /Y php.ini-min php.ini
|
||||
- echo extension=php_openssl.dll >> php.ini
|
||||
- cd c:\projects\symfony
|
||||
- IF NOT EXIST composer.phar (appveyor DownloadFile https://getcomposer.org/download/1.3.0/composer.phar)
|
||||
- php composer.phar self-update
|
||||
- copy /Y .composer\* %APPDATA%\Composer\
|
||||
- php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit
|
||||
- IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev)
|
||||
- php -dmemory_limit=-1 composer.phar update --no-progress --no-suggest --ansi
|
||||
- php composer.phar config platform.php 5.5.9
|
||||
- php composer.phar update --no-progress --no-suggest --ansi
|
||||
- php phpunit install
|
||||
|
||||
test_script:
|
||||
- SET X=0
|
||||
- cd c:\php && 7z x php-7.1.3-Win32-VC14-x86.zip -y >nul && copy /Y php.ini-min php.ini
|
||||
- cd c:\php && copy /Y php.ini-min php.ini
|
||||
- cd c:\projects\symfony
|
||||
- php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel!
|
||||
- cd c:\php && 7z x php-5.5.9-nts-Win32-VC11-x86.zip -y >nul && copy /Y php.ini-min php.ini
|
||||
|
|
|
@ -59,6 +59,10 @@ $foo = new FooTestCase();
|
|||
$foo->testLegacyFoo();
|
||||
$foo->testNonLegacyBar();
|
||||
|
||||
register_shutdown_function(function () {
|
||||
exit('I get precedence over any exit statements inside the deprecation error handler.');
|
||||
});
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Unsilenced deprecation notices (3)
|
||||
|
@ -80,3 +84,4 @@ Other deprecation notices (1)
|
|||
|
||||
1x: root deprecation
|
||||
|
||||
I get precedence over any exit statements inside the deprecation error handler.
|
||||
|
|
|
@ -21,6 +21,8 @@ class FooTestCase
|
|||
{
|
||||
@trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
|
||||
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
|
||||
@trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
|
||||
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
public function testNonLegacyBar()
|
||||
|
|
|
@ -37,4 +37,3 @@ Unsilenced deprecation notices (1)
|
|||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
|
|
|
@ -15,9 +15,11 @@ while (!file_exists($vendor.'/vendor')) {
|
|||
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
require_once __DIR__.'/../../bootstrap.php';
|
||||
eval("@trigger_error('who knows where I come from?', E_USER_DEPRECATED);")
|
||||
eval("@trigger_error('who knows where I come from?', E_USER_DEPRECATED);");
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: who knows where I come from?
|
||||
|
|
|
@ -23,3 +23,5 @@ include 'phar://deprecation.phar/deprecation.php';
|
|||
--EXPECTF--
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: I come from… afar! :D
|
||||
|
|
|
@ -20,10 +20,21 @@ require __DIR__.'/fake_vendor/acme/lib/deprecation_riddled.php';
|
|||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Unsilenced deprecation notices (2)
|
||||
Unsilenced deprecation notices (3)
|
||||
|
||||
2x: unsilenced foo deprecation
|
||||
2x in FooTestCase::testLegacyFoo
|
||||
|
||||
1x: unsilenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Remaining vendor deprecation notices (1)
|
||||
|
||||
Legacy deprecation notices (1)
|
||||
1x: silenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Legacy deprecation notices (2)
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: root deprecation
|
||||
|
|
|
@ -26,7 +26,7 @@ class AddSessionDomainConstraintPass implements CompilerPassInterface
|
|||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) {
|
||||
if (!$container->hasParameter('session.storage.options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ class AddSessionDomainConstraintPass implements CompilerPassInterface
|
|||
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%s' : sprintf('(?:%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
|
||||
$domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://').$domainRegexp;
|
||||
|
||||
// if the service doesn't exist, an exception must be thrown - ignoring would put security at risk
|
||||
$container->findDefinition('security.http_utils')->addArgument(sprintf('{^%s$}i', $domainRegexp));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
|
||||
*/
|
||||
class RegisterCsrfTokenClearingLogoutHandlerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$csrfTokenStorage = $container->findDefinition('security.csrf.token_storage');
|
||||
$csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass());
|
||||
|
||||
if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$container->register('security.logout.handler.csrf_token_clearing', 'Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler')
|
||||
->addArgument(new Reference('security.csrf.token_storage'))
|
||||
->setPublic(false);
|
||||
|
||||
$container->findDefinition('security.logout_listener')->addMethodCall('addHandler', array(new Reference('security.logout.handler.csrf_token_clearing')));
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
namespace Symfony\Bundle\SecurityBundle;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
@ -59,6 +60,7 @@ class SecurityBundle extends Bundle
|
|||
$extension->addUserProviderFactory(new InMemoryFactory());
|
||||
$extension->addUserProviderFactory(new LdapFactory());
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
||||
$container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,19 @@ class AddSessionDomainConstraintPassTest extends TestCase
|
|||
$this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://pirate.com/foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
|
||||
* @expectedExceptionMessage You have requested a non-existent service "security.http_utils".
|
||||
*/
|
||||
public function testNoHttpUtils()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->setParameter('session.storage.options', array());
|
||||
|
||||
$pass = new AddSessionDomainConstraintPass();
|
||||
$pass->process($container);
|
||||
}
|
||||
|
||||
private function createContainer($sessionStorageOptions)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||
|
||||
return array(
|
||||
new FrameworkBundle(),
|
||||
new SecurityBundle(),
|
||||
);
|
|
@ -0,0 +1,26 @@
|
|||
imports:
|
||||
- { resource: ./../config/framework.yml }
|
||||
|
||||
security:
|
||||
encoders:
|
||||
Symfony\Component\Security\Core\User\User: plaintext
|
||||
|
||||
providers:
|
||||
in_memory:
|
||||
memory:
|
||||
users:
|
||||
johannes: { password: test, roles: [ROLE_USER] }
|
||||
|
||||
firewalls:
|
||||
default:
|
||||
form_login:
|
||||
check_path: login
|
||||
remember_me: true
|
||||
require_previous_session: false
|
||||
remember_me:
|
||||
always_remember_me: true
|
||||
secret: secret
|
||||
logout:
|
||||
invalidate_session: false
|
||||
anonymous: ~
|
||||
stateless: true
|
|
@ -0,0 +1,5 @@
|
|||
login:
|
||||
path: /login
|
||||
|
||||
logout:
|
||||
path: /logout
|
|
@ -18,7 +18,7 @@
|
|||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"ext-xml": "*",
|
||||
"symfony/security": "~3.3.13|~3.4-beta5",
|
||||
"symfony/security": "~3.3.17|~3.4.11",
|
||||
"symfony/dependency-injection": "~3.3",
|
||||
"symfony/http-kernel": "~3.3",
|
||||
"symfony/polyfill-php70": "~1.0"
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.3.18
|
||||
------
|
||||
|
||||
* [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL`
|
||||
HTTP headers has been dropped for security reasons.
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
|
|
|
@ -1802,18 +1802,7 @@ class Request
|
|||
{
|
||||
$requestUri = '';
|
||||
|
||||
if ($this->headers->has('X_ORIGINAL_URL')) {
|
||||
// IIS with Microsoft Rewrite Module
|
||||
$requestUri = $this->headers->get('X_ORIGINAL_URL');
|
||||
$this->headers->remove('X_ORIGINAL_URL');
|
||||
$this->server->remove('HTTP_X_ORIGINAL_URL');
|
||||
$this->server->remove('UNENCODED_URL');
|
||||
$this->server->remove('IIS_WasUrlRewritten');
|
||||
} elseif ($this->headers->has('X_REWRITE_URL')) {
|
||||
// IIS with ISAPI_Rewrite
|
||||
$requestUri = $this->headers->get('X_REWRITE_URL');
|
||||
$this->headers->remove('X_REWRITE_URL');
|
||||
} elseif ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
|
||||
if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
|
||||
// IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
|
||||
$requestUri = $this->server->get('UNENCODED_URL');
|
||||
$this->server->remove('UNENCODED_URL');
|
||||
|
@ -2071,6 +2060,11 @@ class Request
|
|||
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
|
||||
$forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
|
||||
$forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
|
||||
if (self::HEADER_CLIENT_PORT === $type) {
|
||||
foreach ($forwardedValues as $k => $v) {
|
||||
$forwardedValues[$k] = substr_replace($v, '0.0.0.0', 0, strrpos($v, ':'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $ip) {
|
||||
|
|
|
@ -494,6 +494,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
|||
$selectSql = $this->getSelectSql();
|
||||
$selectStmt = $this->pdo->prepare($selectSql);
|
||||
$selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||
$insertStmt = null;
|
||||
|
||||
do {
|
||||
$selectStmt->execute();
|
||||
|
@ -509,6 +510,11 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
|||
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
|
||||
}
|
||||
|
||||
if (null !== $insertStmt) {
|
||||
$this->rollback();
|
||||
throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
|
||||
}
|
||||
|
||||
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
|
||||
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
|
||||
// until other connections to the session are committed.
|
||||
|
|
|
@ -1899,20 +1899,6 @@ class RequestTest extends TestCase
|
|||
public function iisRequestUriProvider()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array(
|
||||
'X_ORIGINAL_URL' => '/foo/bar',
|
||||
),
|
||||
array(),
|
||||
'/foo/bar',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'X_REWRITE_URL' => '/foo/bar',
|
||||
),
|
||||
array(),
|
||||
'/foo/bar',
|
||||
),
|
||||
array(
|
||||
array(),
|
||||
array(
|
||||
|
@ -1921,36 +1907,6 @@ class RequestTest extends TestCase
|
|||
),
|
||||
'/foo/bar',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'X_ORIGINAL_URL' => '/foo/bar',
|
||||
),
|
||||
array(
|
||||
'HTTP_X_ORIGINAL_URL' => '/foo/bar',
|
||||
),
|
||||
'/foo/bar',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'X_ORIGINAL_URL' => '/foo/bar',
|
||||
),
|
||||
array(
|
||||
'IIS_WasUrlRewritten' => '1',
|
||||
'UNENCODED_URL' => '/foo/bar',
|
||||
),
|
||||
'/foo/bar',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'X_ORIGINAL_URL' => '/foo/bar',
|
||||
),
|
||||
array(
|
||||
'HTTP_X_ORIGINAL_URL' => '/foo/bar',
|
||||
'IIS_WasUrlRewritten' => '1',
|
||||
'UNENCODED_URL' => '/foo/bar',
|
||||
),
|
||||
'/foo/bar',
|
||||
),
|
||||
array(
|
||||
array(),
|
||||
array(
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Fragment;
|
|||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
@ -76,7 +77,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
|
|||
|
||||
$level = ob_get_level();
|
||||
try {
|
||||
return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||
return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||
} catch (\Exception $e) {
|
||||
// we dispatch the exception event to trigger the logging
|
||||
// the response that comes back is simply ignored
|
||||
|
@ -109,24 +110,6 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
|
|||
$cookies = $request->cookies->all();
|
||||
$server = $request->server->all();
|
||||
|
||||
// Override the arguments to emulate a sub-request.
|
||||
// Sub-request object will point to localhost as client ip and real client ip
|
||||
// will be included into trusted header for client ip
|
||||
try {
|
||||
if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) {
|
||||
$currentXForwardedFor = $request->headers->get('X_FORWARDED_FOR', '');
|
||||
|
||||
$server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp();
|
||||
} elseif (method_exists(Request::class, 'getTrustedHeaderName') && $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP, false)) {
|
||||
$currentXForwardedFor = $request->headers->get($trustedHeaderName, '');
|
||||
|
||||
$server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp();
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
$server['REMOTE_ADDR'] = '127.0.0.1';
|
||||
unset($server['HTTP_IF_MODIFIED_SINCE']);
|
||||
unset($server['HTTP_IF_NONE_MATCH']);
|
||||
|
||||
|
|
|
@ -440,27 +440,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
|||
$this->surrogate->addSurrogateCapability($request);
|
||||
}
|
||||
|
||||
// modify the X-Forwarded-For header if needed
|
||||
$forwardedFor = $request->headers->get('X-Forwarded-For');
|
||||
if ($forwardedFor) {
|
||||
$request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR'));
|
||||
} else {
|
||||
$request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR'));
|
||||
}
|
||||
|
||||
// fix the client IP address by setting it to 127.0.0.1 as HttpCache
|
||||
// is always called from the same process as the backend.
|
||||
$request->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
// make sure HttpCache is a trusted proxy
|
||||
if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) {
|
||||
$trustedProxies[] = '127.0.0.1';
|
||||
Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL);
|
||||
}
|
||||
|
||||
// always a "master" request (as the real master request can be in cache)
|
||||
$response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch);
|
||||
// FIXME: we probably need to also catch exceptions if raw === true
|
||||
$response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch);
|
||||
|
||||
// we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC
|
||||
if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) {
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\HttpCache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SubRequestHandler
|
||||
{
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public static function handle(HttpKernelInterface $kernel, Request $request, $type, $catch)
|
||||
{
|
||||
// save global state related to trusted headers and proxies
|
||||
$trustedProxies = Request::getTrustedProxies();
|
||||
$trustedHeaderSet = Request::getTrustedHeaderSet();
|
||||
$trustedHeaders = array(
|
||||
Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED, false),
|
||||
Request::HEADER_X_FORWARDED_FOR => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_FOR, false),
|
||||
Request::HEADER_X_FORWARDED_HOST => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_HOST, false),
|
||||
Request::HEADER_X_FORWARDED_PROTO => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_PROTO, false),
|
||||
Request::HEADER_X_FORWARDED_PORT => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_PORT, false),
|
||||
);
|
||||
|
||||
// remove untrusted values
|
||||
$remoteAddr = $request->server->get('REMOTE_ADDR');
|
||||
if (!IpUtils::checkIp($remoteAddr, $trustedProxies)) {
|
||||
foreach (array_filter($trustedHeaders) as $name) {
|
||||
$request->headers->remove($name);
|
||||
}
|
||||
}
|
||||
|
||||
// compute trusted values, taking any trusted proxies into account
|
||||
$trustedIps = array();
|
||||
$trustedValues = array();
|
||||
foreach (array_reverse($request->getClientIps()) as $ip) {
|
||||
$trustedIps[] = $ip;
|
||||
$trustedValues[] = sprintf('for="%s"', $ip);
|
||||
}
|
||||
if ($ip !== $remoteAddr) {
|
||||
$trustedIps[] = $remoteAddr;
|
||||
$trustedValues[] = sprintf('for="%s"', $remoteAddr);
|
||||
}
|
||||
|
||||
// set trusted values, reusing as much as possible the global trusted settings
|
||||
if ($name = $trustedHeaders[Request::HEADER_FORWARDED]) {
|
||||
$trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme());
|
||||
$request->headers->set($name, implode(', ', $trustedValues));
|
||||
}
|
||||
if ($name = $trustedHeaders[Request::HEADER_X_FORWARDED_FOR]) {
|
||||
$request->headers->set($name, implode(', ', $trustedIps));
|
||||
}
|
||||
if (!$name && !$trustedHeaders[Request::HEADER_FORWARDED]) {
|
||||
Request::setTrustedProxies($trustedProxies, $trustedHeaderSet | Request::HEADER_X_FORWARDED_FOR);
|
||||
$request->headers->set(Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_FOR, false), implode(', ', $trustedIps));
|
||||
}
|
||||
|
||||
// fix the client IP address by setting it to 127.0.0.1,
|
||||
// which is the core responsibility of this method
|
||||
$request->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
// ensure 127.0.0.1 is set as trusted proxy
|
||||
if (!IpUtils::checkIp('127.0.0.1', $trustedProxies)) {
|
||||
Request::setTrustedProxies(array_merge($trustedProxies, array('127.0.0.1')), Request::getTrustedHeaderSet());
|
||||
}
|
||||
|
||||
try {
|
||||
return $kernel->handle($request, $type, $catch);
|
||||
} finally {
|
||||
// restore global state
|
||||
Request::setTrustedProxies($trustedProxies, $trustedHeaderSet);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,12 +61,12 @@ abstract class Kernel implements KernelInterface, TerminableInterface
|
|||
|
||||
private $projectDir;
|
||||
|
||||
const VERSION = '3.3.16-DEV';
|
||||
const VERSION_ID = 30316;
|
||||
const VERSION = '3.3.18';
|
||||
const VERSION_ID = 30318;
|
||||
const MAJOR_VERSION = 3;
|
||||
const MINOR_VERSION = 3;
|
||||
const RELEASE_VERSION = 16;
|
||||
const EXTRA_VERSION = 'DEV';
|
||||
const RELEASE_VERSION = 18;
|
||||
const EXTRA_VERSION = '';
|
||||
|
||||
const END_OF_MAINTENANCE = '01/2018';
|
||||
const END_OF_LIFE = '07/2018';
|
||||
|
|
|
@ -46,7 +46,7 @@ class InlineFragmentRendererTest extends TestCase
|
|||
$subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller');
|
||||
$subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en'));
|
||||
$subRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
|
||||
$subRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest));
|
||||
|
||||
|
@ -99,7 +99,10 @@ class InlineFragmentRendererTest extends TestCase
|
|||
{
|
||||
Request::setTrustedProxies(array(), 0);
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/')));
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
$this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent());
|
||||
|
||||
Request::setTrustedProxies(array(), -1);
|
||||
|
@ -190,8 +193,8 @@ class InlineFragmentRendererTest extends TestCase
|
|||
|
||||
if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) {
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
|
||||
}
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
|
||||
|
@ -202,7 +205,7 @@ class InlineFragmentRendererTest extends TestCase
|
|||
|
||||
public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled()
|
||||
{
|
||||
Request::setTrustedProxies(array(), 0);
|
||||
Request::setTrustedProxies(array(), Request::HEADER_FORWARDED);
|
||||
|
||||
$this->testESIHeaderIsKeptInSubrequest();
|
||||
|
||||
|
@ -212,16 +215,52 @@ class InlineFragmentRendererTest extends TestCase
|
|||
public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest()
|
||||
{
|
||||
$expectedSubRequest = Request::create('/');
|
||||
if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) {
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
|
||||
}
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
$request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*'));
|
||||
$strategy->render('/', $request);
|
||||
}
|
||||
|
||||
public function testFirstTrustedProxyIsSetAsRemote()
|
||||
{
|
||||
Request::setTrustedProxies(array('1.1.1.1'), -1);
|
||||
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$strategy->render('/', $request);
|
||||
|
||||
Request::setTrustedProxies(array(), -1);
|
||||
}
|
||||
|
||||
public function testIpAddressOfRangedTrustedProxyIsSetAsRemote()
|
||||
{
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
Request::setTrustedProxies(array('1.1.1.1/24'), -1);
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$strategy->render('/', $request);
|
||||
|
||||
Request::setTrustedProxies(array(), -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Kernel expecting a request equals to $request
|
||||
* Allows delta in comparison in case REQUEST_TIME changed by 1 second.
|
||||
|
|
|
@ -1336,64 +1336,69 @@ class HttpCacheTest extends HttpCacheTestCase
|
|||
$this->setNextResponse();
|
||||
$this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
|
||||
|
||||
$this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR'));
|
||||
$this->kernel->assert(function ($backendRequest) {
|
||||
$this->assertSame('127.0.0.1', $backendRequest->server->get('REMOTE_ADDR'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTrustedProxyData
|
||||
*/
|
||||
public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected)
|
||||
public function testHttpCacheIsSetAsATrustedProxy(array $existing)
|
||||
{
|
||||
Request::setTrustedProxies($existing, Request::HEADER_X_FORWARDED_ALL);
|
||||
|
||||
$this->setNextResponse();
|
||||
$this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
|
||||
$this->assertSame($existing, Request::getTrustedProxies());
|
||||
|
||||
$this->assertEquals($expected, Request::getTrustedProxies());
|
||||
$existing = array_unique(array_merge($existing, array('127.0.0.1')));
|
||||
$this->kernel->assert(function ($backendRequest) use ($existing) {
|
||||
$this->assertSame($existing, Request::getTrustedProxies());
|
||||
$this->assertsame('10.0.0.1', $backendRequest->getClientIp());
|
||||
});
|
||||
|
||||
Request::setTrustedProxies(array(), -1);
|
||||
}
|
||||
|
||||
public function getTrustedProxyData()
|
||||
{
|
||||
return array(
|
||||
array(array(), array('127.0.0.1')),
|
||||
array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')),
|
||||
array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')),
|
||||
array(array()),
|
||||
array(array('10.0.0.2')),
|
||||
array(array('10.0.0.2', '127.0.0.1')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getXForwardedForData
|
||||
* @dataProvider getForwardedData
|
||||
*/
|
||||
public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected)
|
||||
public function testForwarderHeaderForForwardedRequests($forwarded, $expected)
|
||||
{
|
||||
$this->setNextResponse();
|
||||
$server = array('REMOTE_ADDR' => '10.0.0.1');
|
||||
if (false !== $xForwardedFor) {
|
||||
$server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor;
|
||||
if (null !== $forwarded) {
|
||||
Request::setTrustedProxies($server, -1);
|
||||
$server['HTTP_FORWARDED'] = $forwarded;
|
||||
}
|
||||
$this->request('GET', '/', $server);
|
||||
|
||||
$this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
|
||||
$this->kernel->assert(function ($backendRequest) use ($expected) {
|
||||
$this->assertSame($expected, $backendRequest->headers->get('Forwarded'));
|
||||
});
|
||||
|
||||
Request::setTrustedProxies(array(), -1);
|
||||
}
|
||||
|
||||
public function getXForwardedForData()
|
||||
public function getForwardedData()
|
||||
{
|
||||
return array(
|
||||
array(false, '10.0.0.1'),
|
||||
array('10.0.0.2', '10.0.0.2, 10.0.0.1'),
|
||||
array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'),
|
||||
array(null, 'for="10.0.0.1";host="localhost";proto=http'),
|
||||
array('for=10.0.0.2', 'for="10.0.0.2";host="localhost";proto=http, for="10.0.0.1"'),
|
||||
array('for=10.0.0.2, for=10.0.0.3', 'for="10.0.0.2";host="localhost";proto=http, for="10.0.0.3", for="10.0.0.1"'),
|
||||
);
|
||||
}
|
||||
|
||||
public function testXForwarderForHeaderForPassRequests()
|
||||
{
|
||||
$this->setNextResponse();
|
||||
$server = array('REMOTE_ADDR' => '10.0.0.1');
|
||||
$this->request('POST', '/', $server);
|
||||
|
||||
$this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
|
||||
}
|
||||
|
||||
public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses()
|
||||
{
|
||||
$time = \DateTime::createFromFormat('U', time());
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\HttpCache;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
class SubRequestHandlerTest extends TestCase
|
||||
{
|
||||
private static $globalState;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
self::$globalState = $this->getGlobalState();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
Request::setTrustedProxies(self::$globalState[0], self::$globalState[1]);
|
||||
}
|
||||
|
||||
public function testTrustedHeadersAreKept()
|
||||
{
|
||||
Request::setTrustedProxies(array('10.0.0.1'), -1);
|
||||
$globalState = $this->getGlobalState();
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('X-Forwarded-For', '10.0.0.2');
|
||||
$request->headers->set('X-Forwarded-Host', 'Good');
|
||||
$request->headers->set('X-Forwarded-Port', '1234');
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) {
|
||||
$this->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$this->assertSame('10.0.0.2', $request->getClientIp());
|
||||
$this->assertSame('Good', $request->headers->get('X-Forwarded-Host'));
|
||||
$this->assertSame('1234', $request->headers->get('X-Forwarded-Port'));
|
||||
$this->assertSame('https', $request->headers->get('X-Forwarded-Proto'));
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame($globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
public function testUntrustedHeadersAreRemoved()
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('X-Forwarded-For', '10.0.0.2');
|
||||
$request->headers->set('X-Forwarded-Host', 'Evil');
|
||||
$request->headers->set('X-Forwarded-Port', '1234');
|
||||
$request->headers->set('X-Forwarded-Proto', 'http');
|
||||
$request->headers->set('Forwarded', 'Evil2');
|
||||
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) {
|
||||
$this->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$this->assertSame('10.0.0.1', $request->getClientIp());
|
||||
$this->assertFalse($request->headers->has('X-Forwarded-Host'));
|
||||
$this->assertFalse($request->headers->has('X-Forwarded-Port'));
|
||||
$this->assertFalse($request->headers->has('X-Forwarded-Proto'));
|
||||
$this->assertSame('for="10.0.0.1";host="localhost";proto=http', $request->headers->get('Forwarded'));
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame(self::$globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
public function testTrustedForwardedHeader()
|
||||
{
|
||||
Request::setTrustedProxies(array('10.0.0.1'), -1);
|
||||
$globalState = $this->getGlobalState();
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('Forwarded', 'for="10.0.0.2";host="foo.bar:1234";proto=https');
|
||||
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) {
|
||||
$this->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$this->assertSame('10.0.0.2', $request->getClientIp());
|
||||
$this->assertSame('foo.bar:1234', $request->getHttpHost());
|
||||
$this->assertSame('https', $request->getScheme());
|
||||
$this->assertSame(1234, $request->getPort());
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame($globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
public function testTrustedXForwardedForHeader()
|
||||
{
|
||||
Request::setTrustedProxies(array('10.0.0.1'), -1);
|
||||
$globalState = $this->getGlobalState();
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('X-Forwarded-For', '10.0.0.2');
|
||||
$request->headers->set('X-Forwarded-Host', 'foo.bar');
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) {
|
||||
$this->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$this->assertSame('10.0.0.2', $request->getClientIp());
|
||||
$this->assertSame('foo.bar', $request->getHttpHost());
|
||||
$this->assertSame('https', $request->getScheme());
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame($globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
private function getGlobalState()
|
||||
{
|
||||
return array(
|
||||
Request::getTrustedProxies(),
|
||||
Request::getTrustedHeaderSet(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestSubRequestHandlerKernel implements HttpKernelInterface
|
||||
{
|
||||
private $assertCallback;
|
||||
|
||||
public function __construct(\Closure $assertCallback)
|
||||
{
|
||||
$this->assertCallback = $assertCallback;
|
||||
}
|
||||
|
||||
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
|
||||
{
|
||||
$assertCallback = $this->assertCallback;
|
||||
$assertCallback($request, $type, $catch);
|
||||
|
||||
return new Response();
|
||||
}
|
||||
}
|
|
@ -39,15 +39,25 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface,
|
|||
parent::__construct(new EventDispatcher(), $this, null, $this);
|
||||
}
|
||||
|
||||
public function getBackendRequest()
|
||||
public function assert(\Closure $callback)
|
||||
{
|
||||
return $this->backendRequest;
|
||||
$trustedConfig = array(Request::getTrustedProxies(), Request::getTrustedHeaderSet());
|
||||
|
||||
list($trustedProxies, $trustedHeaderSet, $backendRequest) = $this->backendRequest;
|
||||
Request::setTrustedProxies($trustedProxies, $trustedHeaderSet);
|
||||
|
||||
try {
|
||||
$callback($backendRequest);
|
||||
} finally {
|
||||
list($trustedProxies, $trustedHeaderSet) = $trustedConfig;
|
||||
Request::setTrustedProxies($trustedProxies, $trustedHeaderSet);
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false)
|
||||
{
|
||||
$this->catch = $catch;
|
||||
$this->backendRequest = $request;
|
||||
$this->backendRequest = array(Request::getTrustedProxies(), Request::getTrustedHeaderSet(), $request);
|
||||
|
||||
return parent::handle($request, $type, $catch);
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ class LdapBindAuthenticationProvider extends UserAuthenticationProvider
|
|||
$username = $token->getUsername();
|
||||
$password = $token->getCredentials();
|
||||
|
||||
if ('' === $password) {
|
||||
if ('' === (string) $password) {
|
||||
throw new BadCredentialsException('The presented password must not be empty.');
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,23 @@ class LdapBindAuthenticationProviderTest extends TestCase
|
|||
$reflection->invoke($provider, new User('foo', null), new UsernamePasswordToken('foo', '', 'key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
|
||||
* @expectedExceptionMessage The presented password must not be empty.
|
||||
*/
|
||||
public function testNullPasswordShouldThrowAnException()
|
||||
{
|
||||
$userProvider = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserProviderInterface')->getMock();
|
||||
$ldap = $this->getMockBuilder('Symfony\Component\Ldap\LdapClientInterface')->getMock();
|
||||
$userChecker = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserCheckerInterface')->getMock();
|
||||
|
||||
$provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap);
|
||||
$reflection = new \ReflectionMethod($provider, 'checkAuthentication');
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
$reflection->invoke($provider, new User('foo', null), new UsernamePasswordToken('foo', null, 'key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
|
||||
* @expectedExceptionMessage The presented password is invalid.
|
||||
|
|
|
@ -113,4 +113,32 @@ class NativeSessionTokenStorageTest extends TestCase
|
|||
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
|
||||
$this->assertFalse($this->storage->hasToken('token_id'));
|
||||
}
|
||||
|
||||
public function testClearRemovesAllTokensFromTheConfiguredNamespace()
|
||||
{
|
||||
$this->storage->setToken('foo', 'bar');
|
||||
$this->storage->clear();
|
||||
|
||||
$this->assertFalse($this->storage->hasToken('foo'));
|
||||
$this->assertArrayNotHasKey(self::SESSION_NAMESPACE, $_SESSION);
|
||||
}
|
||||
|
||||
public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces()
|
||||
{
|
||||
$_SESSION['foo']['bar'] = 'baz';
|
||||
$this->storage->clear();
|
||||
|
||||
$this->assertArrayHasKey('foo', $_SESSION);
|
||||
$this->assertArrayHasKey('bar', $_SESSION['foo']);
|
||||
$this->assertSame('baz', $_SESSION['foo']['bar']);
|
||||
}
|
||||
|
||||
public function testClearDoesNotRemoveNonNamespacedSessionValues()
|
||||
{
|
||||
$_SESSION['foo'] = 'baz';
|
||||
$this->storage->clear();
|
||||
|
||||
$this->assertArrayHasKey('foo', $_SESSION);
|
||||
$this->assertSame('baz', $_SESSION['foo']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
namespace Symfony\Component\Security\Csrf\Tests\TokenStorage;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
|
||||
|
||||
/**
|
||||
|
@ -22,7 +24,7 @@ class SessionTokenStorageTest extends TestCase
|
|||
const SESSION_NAMESPACE = 'foobar';
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
|
@ -33,118 +35,53 @@ class SessionTokenStorageTest extends TestCase
|
|||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->session = new Session(new MockArraySessionStorage());
|
||||
$this->storage = new SessionTokenStorage($this->session, self::SESSION_NAMESPACE);
|
||||
}
|
||||
|
||||
public function testStoreTokenInClosedSession()
|
||||
public function testStoreTokenInNotStartedSessionStartsTheSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('set')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
|
||||
|
||||
$this->storage->setToken('token_id', 'TOKEN');
|
||||
|
||||
$this->assertTrue($this->session->isStarted());
|
||||
}
|
||||
|
||||
public function testStoreTokenInActiveSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->never())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('set')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
|
||||
|
||||
$this->session->start();
|
||||
$this->storage->setToken('token_id', 'TOKEN');
|
||||
|
||||
$this->assertSame('TOKEN', $this->session->get(self::SESSION_NAMESPACE.'/token_id'));
|
||||
}
|
||||
|
||||
public function testCheckTokenInClosedSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(false));
|
||||
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('has')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
|
||||
$this->assertTrue($this->storage->hasToken('token_id'));
|
||||
$this->assertTrue($this->session->isStarted());
|
||||
}
|
||||
|
||||
public function testCheckTokenInActiveSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(true));
|
||||
$this->session->start();
|
||||
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
|
||||
|
||||
$this->session->expects($this->never())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('has')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
|
||||
$this->assertTrue($this->storage->hasToken('token_id'));
|
||||
}
|
||||
|
||||
public function testGetExistingTokenFromClosedSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('has')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('get')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue('RESULT'));
|
||||
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
|
||||
|
||||
$this->assertSame('RESULT', $this->storage->getToken('token_id'));
|
||||
$this->assertTrue($this->session->isStarted());
|
||||
}
|
||||
|
||||
public function testGetExistingTokenFromActiveSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->never())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('has')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('get')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue('RESULT'));
|
||||
$this->session->start();
|
||||
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
|
||||
|
||||
$this->assertSame('RESULT', $this->storage->getToken('token_id'));
|
||||
}
|
||||
|
@ -154,18 +91,6 @@ class SessionTokenStorageTest extends TestCase
|
|||
*/
|
||||
public function testGetNonExistingTokenFromClosedSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('has')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->storage->getToken('token_id');
|
||||
}
|
||||
|
||||
|
@ -174,86 +99,61 @@ class SessionTokenStorageTest extends TestCase
|
|||
*/
|
||||
public function testGetNonExistingTokenFromActiveSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->never())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('has')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->session->start();
|
||||
$this->storage->getToken('token_id');
|
||||
}
|
||||
|
||||
public function testRemoveNonExistingTokenFromClosedSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('remove')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
$this->assertNull($this->storage->removeToken('token_id'));
|
||||
}
|
||||
|
||||
public function testRemoveNonExistingTokenFromActiveSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->never())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('remove')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue(null));
|
||||
$this->session->start();
|
||||
|
||||
$this->assertNull($this->storage->removeToken('token_id'));
|
||||
}
|
||||
|
||||
public function testRemoveExistingTokenFromClosedSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('remove')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
|
||||
|
||||
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
|
||||
}
|
||||
|
||||
public function testRemoveExistingTokenFromActiveSession()
|
||||
{
|
||||
$this->session->expects($this->any())
|
||||
->method('isStarted')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->session->expects($this->never())
|
||||
->method('start');
|
||||
|
||||
$this->session->expects($this->once())
|
||||
->method('remove')
|
||||
->with(self::SESSION_NAMESPACE.'/token_id')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
$this->session->start();
|
||||
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
|
||||
|
||||
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
|
||||
}
|
||||
|
||||
public function testClearRemovesAllTokensFromTheConfiguredNamespace()
|
||||
{
|
||||
$this->storage->setToken('foo', 'bar');
|
||||
$this->storage->clear();
|
||||
|
||||
$this->assertFalse($this->storage->hasToken('foo'));
|
||||
$this->assertFalse($this->session->has(self::SESSION_NAMESPACE.'/foo'));
|
||||
}
|
||||
|
||||
public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces()
|
||||
{
|
||||
$this->session->set('foo/bar', 'baz');
|
||||
$this->storage->clear();
|
||||
|
||||
$this->assertTrue($this->session->has('foo/bar'));
|
||||
$this->assertSame('baz', $this->session->get('foo/bar'));
|
||||
}
|
||||
|
||||
public function testClearDoesNotRemoveNonNamespacedSessionValues()
|
||||
{
|
||||
$this->session->set('foo', 'baz');
|
||||
$this->storage->clear();
|
||||
|
||||
$this->assertTrue($this->session->has('foo'));
|
||||
$this->assertSame('baz', $this->session->get('foo'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Csrf\TokenStorage;
|
||||
|
||||
/**
|
||||
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
|
||||
*/
|
||||
interface ClearableTokenStorageInterface extends TokenStorageInterface
|
||||
{
|
||||
/**
|
||||
* Removes all CSRF tokens.
|
||||
*/
|
||||
public function clear();
|
||||
}
|
|
@ -18,7 +18,7 @@ use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException;
|
|||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class NativeSessionTokenStorage implements TokenStorageInterface
|
||||
class NativeSessionTokenStorage implements ClearableTokenStorageInterface
|
||||
{
|
||||
/**
|
||||
* The namespace used to store values in the session.
|
||||
|
@ -96,6 +96,14 @@ class NativeSessionTokenStorage implements TokenStorageInterface
|
|||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
unset($_SESSION[$this->namespace]);
|
||||
}
|
||||
|
||||
private function startSession()
|
||||
{
|
||||
if (PHP_SESSION_NONE === session_status()) {
|
||||
|
|
|
@ -19,7 +19,7 @@ use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException;
|
|||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class SessionTokenStorage implements TokenStorageInterface
|
||||
class SessionTokenStorage implements ClearableTokenStorageInterface
|
||||
{
|
||||
/**
|
||||
* The namespace used to store values in the session.
|
||||
|
@ -92,4 +92,16 @@ class SessionTokenStorage implements TokenStorageInterface
|
|||
|
||||
return $this->session->remove($this->namespace.'/'.$tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
foreach (array_keys($this->session->all()) as $key) {
|
||||
if (0 === strpos($key, $this->namespace.'/')) {
|
||||
$this->session->remove($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ class GuardAuthenticatorHandler
|
|||
*/
|
||||
public function authenticateWithToken(TokenInterface $token, Request $request)
|
||||
{
|
||||
$this->migrateSession($request);
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
||||
if (null !== $this->dispatcher) {
|
||||
|
@ -133,4 +134,12 @@ class GuardAuthenticatorHandler
|
|||
is_object($response) ? get_class($response) : gettype($response)
|
||||
));
|
||||
}
|
||||
|
||||
private function migrateSession(Request $request)
|
||||
{
|
||||
if (!$request->hasSession() || !$request->hasPreviousSession()) {
|
||||
return;
|
||||
}
|
||||
$request->getSession()->migrate(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,9 @@ abstract class AbstractPreAuthenticatedListener implements ListenerInterface
|
|||
if (null !== $this->logger) {
|
||||
$this->logger->info('Pre-authentication successful.', array('token' => (string) $token));
|
||||
}
|
||||
|
||||
$this->migrateSession($request);
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
||||
if (null !== $this->dispatcher) {
|
||||
|
@ -114,4 +117,12 @@ abstract class AbstractPreAuthenticatedListener implements ListenerInterface
|
|||
* @return array An array composed of the user and the credentials
|
||||
*/
|
||||
abstract protected function getPreAuthenticatedData(Request $request);
|
||||
|
||||
private function migrateSession(Request $request)
|
||||
{
|
||||
if (!$request->hasSession() || !$request->hasPreviousSession()) {
|
||||
return;
|
||||
}
|
||||
$request->getSession()->migrate(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Symfony\Component\Security\Http\Firewall;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
|
@ -70,6 +71,9 @@ class BasicAuthenticationListener implements ListenerInterface
|
|||
|
||||
try {
|
||||
$token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
|
||||
|
||||
$this->migrateSession($request);
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
} catch (AuthenticationException $e) {
|
||||
$token = $this->tokenStorage->getToken();
|
||||
|
@ -88,4 +92,12 @@ class BasicAuthenticationListener implements ListenerInterface
|
|||
$event->setResponse($this->authenticationEntryPoint->start($request, $e));
|
||||
}
|
||||
}
|
||||
|
||||
private function migrateSession(Request $request)
|
||||
{
|
||||
if (!$request->hasSession() || !$request->hasPreviousSession()) {
|
||||
return;
|
||||
}
|
||||
$request->getSession()->migrate(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,8 @@ class DigestAuthenticationListener implements ListenerInterface
|
|||
$this->logger->info('Digest authentication successful.', array('username' => $digestAuth->getUsername(), 'received' => $digestAuth->getResponse()));
|
||||
}
|
||||
|
||||
$this->migrateSession($request);
|
||||
|
||||
$this->tokenStorage->setToken(new UsernamePasswordToken($user, $user->getPassword(), $this->providerKey));
|
||||
}
|
||||
|
||||
|
@ -133,6 +135,14 @@ class DigestAuthenticationListener implements ListenerInterface
|
|||
|
||||
$event->setResponse($this->authenticationEntryPoint->start($request, $authException));
|
||||
}
|
||||
|
||||
private function migrateSession(Request $request)
|
||||
{
|
||||
if (!$request->hasSession() || !$request->hasPreviousSession()) {
|
||||
return;
|
||||
}
|
||||
$request->getSession()->migrate(true);
|
||||
}
|
||||
}
|
||||
|
||||
class DigestData
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Symfony\Component\Security\Http\Firewall;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
|
@ -85,6 +86,9 @@ class SimplePreAuthenticationListener implements ListenerInterface
|
|||
}
|
||||
|
||||
$token = $this->authenticationManager->authenticate($token);
|
||||
|
||||
$this->migrateSession($request);
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
||||
if (null !== $this->dispatcher) {
|
||||
|
@ -119,4 +123,12 @@ class SimplePreAuthenticationListener implements ListenerInterface
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function migrateSession(Request $request)
|
||||
{
|
||||
if (!$request->hasSession() || !$request->hasPreviousSession()) {
|
||||
return;
|
||||
}
|
||||
$request->getSession()->migrate(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,8 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface
|
|||
$this->logger->info('User has been authenticated successfully.', array('username' => $token->getUsername()));
|
||||
}
|
||||
|
||||
$this->migrateSession($request);
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
||||
if (null !== $this->eventDispatcher) {
|
||||
|
@ -184,4 +186,12 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function migrateSession(Request $request)
|
||||
{
|
||||
if (!$request->hasSession() || !$request->hasPreviousSession()) {
|
||||
return;
|
||||
}
|
||||
$request->getSession()->migrate(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Http\Logout;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
|
||||
|
||||
/**
|
||||
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
|
||||
*/
|
||||
class CsrfTokenClearingLogoutHandler implements LogoutHandlerInterface
|
||||
{
|
||||
private $csrfTokenStorage;
|
||||
|
||||
public function __construct(ClearableTokenStorageInterface $csrfTokenStorage)
|
||||
{
|
||||
$this->csrfTokenStorage = $csrfTokenStorage;
|
||||
}
|
||||
|
||||
public function logout(Request $request, Response $response, TokenInterface $token)
|
||||
{
|
||||
$this->csrfTokenStorage->clear();
|
||||
}
|
||||
}
|
|
@ -47,6 +47,8 @@ class SessionAuthenticationStrategy implements SessionAuthenticationStrategyInte
|
|||
return;
|
||||
|
||||
case self::MIGRATE:
|
||||
// Note: this logic is duplicated in several authentication listeners
|
||||
// until Symfony 5.0 due to a security fix with BC compat
|
||||
$request->getSession()->migrate(true);
|
||||
|
||||
return;
|
||||
|
|
|
@ -27,8 +27,8 @@ interface SessionAuthenticationStrategyInterface
|
|||
/**
|
||||
* This performs any necessary changes to the session.
|
||||
*
|
||||
* This method is called before the TokenStorage is populated with a
|
||||
* Token, and only by classes inheriting from AbstractAuthenticationListener.
|
||||
* This method should be called before the TokenStorage is populated with a
|
||||
* Token. It should be used by authentication listeners when a session is used.
|
||||
*/
|
||||
public function onAuthentication(Request $request, TokenInterface $token);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Http\Tests\Logout;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
|
||||
use Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler;
|
||||
|
||||
class CsrfTokenClearingLogoutHandlerTest extends TestCase
|
||||
{
|
||||
private $session;
|
||||
private $csrfTokenStorage;
|
||||
private $csrfTokenClearingLogoutHandler;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->session = new Session(new MockArraySessionStorage());
|
||||
$this->csrfTokenStorage = new SessionTokenStorage($this->session, 'foo');
|
||||
$this->csrfTokenStorage->setToken('foo', 'bar');
|
||||
$this->csrfTokenStorage->setToken('foobar', 'baz');
|
||||
$this->csrfTokenClearingLogoutHandler = new CsrfTokenClearingLogoutHandler($this->csrfTokenStorage);
|
||||
}
|
||||
|
||||
public function testCsrfTokenCookieWithSameNamespaceIsRemoved()
|
||||
{
|
||||
$this->assertSame('bar', $this->session->get('foo/foo'));
|
||||
$this->assertSame('baz', $this->session->get('foo/foobar'));
|
||||
|
||||
$this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock());
|
||||
|
||||
$this->assertFalse($this->csrfTokenStorage->hasToken('foo'));
|
||||
$this->assertFalse($this->csrfTokenStorage->hasToken('foobar'));
|
||||
|
||||
$this->assertFalse($this->session->has('foo/foo'));
|
||||
$this->assertFalse($this->session->has('foo/foobar'));
|
||||
}
|
||||
|
||||
public function testCsrfTokenCookieWithDifferentNamespaceIsNotRemoved()
|
||||
{
|
||||
$barNamespaceCsrfSessionStorage = new SessionTokenStorage($this->session, 'bar');
|
||||
$barNamespaceCsrfSessionStorage->setToken('foo', 'bar');
|
||||
$barNamespaceCsrfSessionStorage->setToken('foobar', 'baz');
|
||||
|
||||
$this->assertSame('bar', $this->session->get('foo/foo'));
|
||||
$this->assertSame('baz', $this->session->get('foo/foobar'));
|
||||
$this->assertSame('bar', $this->session->get('bar/foo'));
|
||||
$this->assertSame('baz', $this->session->get('bar/foobar'));
|
||||
|
||||
$this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock());
|
||||
|
||||
$this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foo'));
|
||||
$this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foobar'));
|
||||
$this->assertSame('bar', $barNamespaceCsrfSessionStorage->getToken('foo'));
|
||||
$this->assertSame('baz', $barNamespaceCsrfSessionStorage->getToken('foobar'));
|
||||
$this->assertFalse($this->csrfTokenStorage->hasToken('foo'));
|
||||
$this->assertFalse($this->csrfTokenStorage->hasToken('foobar'));
|
||||
|
||||
$this->assertFalse($this->session->has('foo/foo'));
|
||||
$this->assertFalse($this->session->has('foo/foobar'));
|
||||
$this->assertSame('bar', $this->session->get('bar/foo'));
|
||||
$this->assertSame('baz', $this->session->get('bar/foobar'));
|
||||
}
|
||||
}
|
|
@ -27,9 +27,12 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"symfony/routing": "~2.8|~3.0",
|
||||
"symfony/security-csrf": "~2.8|~3.0",
|
||||
"symfony/security-csrf": "~2.8.41|~3.3.17|~3.4.11",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/security-csrf": ">=2.8.0,<2.8.41 || >=3.0.0,<3.3.17 || >=3.4.0,<3.4.11"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/security-csrf": "For using tokens to protect authentication/logout attempts",
|
||||
"symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs"
|
||||
|
|
Reference in New Issue