Compare commits

...
This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.

33 Commits
5.4 ... 3.3

Author SHA1 Message Date
Fabien Potencier e8d2a2ed1f
Merge pull request #28099 from fabpot/release-3.3.18
released v3.3.18
2018-08-01 16:04:47 +02:00
Fabien Potencier 5484ff9b43 updated VERSION for 3.3.18 2018-08-01 16:04:30 +02:00
Fabien Potencier a4987c6105 updated CHANGELOG for 3.3.18 2018-08-01 16:04:07 +02:00
Nicolas Grekas 1db2d2eda8 security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas)
* commit '7f912bbb78':
  [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer
2018-08-01 14:50:17 +02:00
Nicolas Grekas 7f912bbb78 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer 2018-08-01 14:50:04 +02:00
Nicolas Grekas 6ad5fe68c2 security #cve-2018-14773 [HttpFoundation] Remove support for legacy and risky HTTP headers (nicolas-grekas)
This PR was merged into the 3.3 branch.

Discussion
----------

[3.3][HttpFoundation] Remove support for legacy and risky HTTP headers

Commits
-------

12adeb90df [HttpFoundation] Remove support for legacy and risky HTTP headers
2018-08-01 10:44:29 +02:00
Nicolas Grekas 12adeb90df [HttpFoundation] Remove support for legacy and risky HTTP headers 2018-07-31 23:39:15 +02:00
Fabien Potencier 4639525c79
Merge pull request #27377 from fabpot/release-3.3.17
released v3.3.17
2018-05-25 14:43:13 +02:00
Fabien Potencier b8082cb94a updated VERSION for 3.3.17 2018-05-25 14:42:53 +02:00
Fabien Potencier c7986c2af6 updated CHANGELOG for 3.3.17 2018-05-25 14:42:47 +02:00
Nicolas Grekas ea4121ae55 Make CI green for regular jobs 2018-05-25 13:34:02 +02:00
Christian Flothmann a8d95ddb1f do not mock the session in token storage tests 2018-05-24 21:17:17 +02:00
Christian Flothmann 2ed66b2ace fixtures config fix 2018-05-24 20:59:55 +02:00
Fabien Potencier 9b3c298c17 simplified code 2018-05-24 20:27:06 +02:00
Christian Flothmann a10c07cd57 fix version constraint 2018-05-24 20:18:48 +02:00
Fabien Potencier 42bc43fd70 simplified code 2018-05-24 17:50:12 +02:00
Fabien Potencier b46fc93785 security #cve-2018-11407 [Ldap] cast to string when checking empty passwords
* cve-2018-11407-3.3:
  [Ldap] cast to string when checking empty passwords
2018-05-24 15:26:31 +02:00
ismail1432 1ebe203413 [Ldap] cast to string when checking empty passwords 2018-05-24 15:26:23 +02:00
Fabien Potencier e1f817ffe2 security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured
* cve-2018-11408-3.3:
  [SecurityBundle] Fail if security.http_utils cannot be configured
2018-05-24 15:24:06 +02:00
Nicolas Grekas 8e1b906ba0 [SecurityBundle] Fail if security.http_utils cannot be configured 2018-05-24 15:23:59 +02:00
Fabien Potencier 6ff58021c7 fixed deps 2018-05-24 15:15:22 +02:00
Fabien Potencier 9d3989faa8 security #cve-2018-11406 clear CSRF tokens when the user is logged out
* cve-2018-11406-3.3:
  clear CSRF tokens when the user is logged out
2018-05-24 15:14:22 +02:00
Christian Flothmann 86a039a547 clear CSRF tokens when the user is logged out 2018-05-24 14:48:51 +02:00
Fabien Potencier f6f2f97a65 security #cve-2018-11385 migrating session for UsernamePasswordJsonAuthenticationListener
* cve-2018-11385-3.3:
  migrating session for UsernamePasswordJsonAuthenticationListener
  Adding session authentication strategy to Guard to avoid session fixation
  Adding session strategy to ALL listeners to avoid *any* possible fixation
2018-05-23 15:55:11 +02:00
Ryan Weaver 51fa731abb migrating session for UsernamePasswordJsonAuthenticationListener 2018-05-23 15:55:00 +02:00
Ryan Weaver ab384b0f34 Adding session authentication strategy to Guard to avoid session
fixation
2018-05-23 15:54:39 +02:00
Ryan Weaver 614b83f95d Adding session strategy to ALL listeners to avoid *any* possible
fixation
2018-05-23 15:54:27 +02:00
Fabien Potencier 1e1cf5cf68 security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode
* cve-2018-11386-3.3:
  [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode
2018-05-23 15:38:57 +02:00
Nicolas Grekas e0c9884fa6 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode 2018-05-23 15:38:51 +02:00
Fabien Potencier b66afe62e8 bumped Symfony version to 3.3.17 2018-01-29 13:23:39 +01:00
Fabien Potencier 98e128ccee
Merge pull request #25955 from fabpot/release-3.3.16
released v3.3.16
2018-01-29 12:42:20 +01:00
Fabien Potencier 909c9ae5e7 updated VERSION for 3.3.16 2018-01-29 12:42:05 +01:00
Fabien Potencier 0c3890fe63 updated CHANGELOG for 3.3.16 2018-01-29 12:41:59 +01:00
47 changed files with 904 additions and 308 deletions

View File

@ -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
- |

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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()

View File

@ -37,4 +37,3 @@ Unsilenced deprecation notices (1)
Legacy deprecation notices (1)
Other deprecation notices (1)

View File

@ -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?

View File

@ -23,3 +23,5 @@ include 'phar://deprecation.phar/deprecation.php';
--EXPECTF--
Other deprecation notices (1)
1x: I come from… afar! :D

View File

@ -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

View File

@ -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));
}
}

View File

@ -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')));
}
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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(),
);

View File

@ -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

View File

@ -0,0 +1,5 @@
login:
path: /login
logout:
path: /logout

View File

@ -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"

View File

@ -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
-----

View File

@ -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) {

View File

@ -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.

View File

@ -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(

View File

@ -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']);

View File

@ -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))) {

View File

@ -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);
}
}
}

View File

@ -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';

View File

@ -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.

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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.');
}

View File

@ -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.

View File

@ -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']);
}
}

View File

@ -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'));
}
}

View File

@ -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();
}

View File

@ -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()) {

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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'));
}
}

View File

@ -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"