Merge branch '4.4'

* 4.4:
  [OptionsResolve] Revert change in tests for a not-merged change in code
  [HttpClient] fix handling of 3xx with no Location header - ignore Content-Length when no body is expected
  [Workflow] Made the configuration more robust for the 'property' key
  [Security/Core] make NativePasswordEncoder use sodium to validate passwords when possible
  [FrameworkBundle] make SodiumVault report bad decryption key accurately
  cs fix
  [Security] Allow to set a fixed algorithm
  [Security/Core] make encodedLength computation more generic
  [Security/Core] add fast path when encoded password cannot match anything
  #30432 fix an error message
  fix paths to detect code owners
  [HttpClient] ignore the body of responses to HEAD requests
  [Validator] Ensure numeric subpaths do not cause errors on PHP 7.4
  [SecurityBundle] Fix wrong assertion
  Remove unused local variables in tests
  [Yaml][Parser] Remove the getLastLineNumberBeforeDeprecation() internal unused method
  Make sure to collect child forms created on *_SET_DATA events
  [WebProfilerBundle] Improve display in Email panel for dark theme
  do not render errors for checkboxes twice
This commit is contained in:
Nicolas Grekas 2019-10-28 22:51:41 +01:00
commit 5a855408e1
99 changed files with 686 additions and 175 deletions

26
.github/CODEOWNERS vendored
View File

@ -6,7 +6,7 @@
/src/Symfony/Component/ErrorRenderer/* @yceruto /src/Symfony/Component/ErrorRenderer/* @yceruto
# Form # Form
/src/Symfony/Bridge/Twig/Extension/FormExtension.php @xabbuh /src/Symfony/Bridge/Twig/Extension/FormExtension.php @xabbuh
/src/Symfony/Bridge/Twig/Form/* @xabbuh /src/Symfony/Bridge/Twig/Form/ @xabbuh
/src/Symfony/Bridge/Twig/Node/FormThemeNode.php @xabbuh /src/Symfony/Bridge/Twig/Node/FormThemeNode.php @xabbuh
/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php @xabbuh /src/Symfony/Bridge/Twig/Node/RenderBlockNode.php @xabbuh
/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php @xabbuh /src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php @xabbuh
@ -15,36 +15,36 @@
/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php @xabbuh /src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php @xabbuh
/src/Symfony/Bridge/Twig/TokenParser/FormThemeTokenParser.php @xabbuh /src/Symfony/Bridge/Twig/TokenParser/FormThemeTokenParser.php @xabbuh
/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php @xabbuh /src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php @xabbuh
/src/Symfony/Bundle/FrameworkBundle/Resources/views/* @xabbuh /src/Symfony/Bundle/FrameworkBundle/Resources/views/ @xabbuh
/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @xabbuh /src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @xabbuh
/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php @xabbuh /src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php @xabbuh
/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @xabbuh /src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @xabbuh
/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @xabbuh /src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @xabbuh
/src/Symfony/Component/Form/* @xabbuh /src/Symfony/Component/Form/ @xabbuh
# HttpKernel # HttpKernel
/src/Symfony/Component/HttpKernel/Log/Logger.php @dunglas /src/Symfony/Component/HttpKernel/Log/Logger.php @dunglas
# LDAP # LDAP
/src/Symfony/Component/Ldap/* @csarrazi /src/Symfony/Component/Ldap/ @csarrazi
# Lock # Lock
/src/Symfony/Component/Lock/* @jderusse /src/Symfony/Component/Lock/ @jderusse
# Messenger # Messenger
/src/Symfony/Bridge/Doctrine/Messenger/* @sroze /src/Symfony/Bridge/Doctrine/Messenger/ @sroze
/src/Symfony/Component/Messenger/* @sroze /src/Symfony/Component/Messenger/ @sroze
# PropertyInfo # PropertyInfo
/src/Symfony/Component/PropertyInfo/* @dunglas /src/Symfony/Component/PropertyInfo/ @dunglas
/src/Symfony/Bridge/Doctrine/PropertyInfo/* @dunglas /src/Symfony/Bridge/Doctrine/PropertyInfo/ @dunglas
# Serializer # Serializer
/src/Symfony/Component/Serializer/* @dunglas /src/Symfony/Component/Serializer/ @dunglas
# TwigBundle # TwigBundle
/src/Symfony/Bundle/TwigBundle/ErrorRenderer/TwigHtmlErrorRenderer.php @yceruto /src/Symfony/Bundle/TwigBundle/ErrorRenderer/TwigHtmlErrorRenderer.php @yceruto
# WebLink # WebLink
/src/Symfony/Component/WebLink/* @dunglas /src/Symfony/Component/WebLink/ @dunglas
# Workflow # Workflow
/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @lyrixx /src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @lyrixx
/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php @lyrixx /src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php @lyrixx
/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @lyrixx /src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @lyrixx
/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php @lyrixx /src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php @lyrixx
/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @lyrixx /src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @lyrixx
/src/Symfony/Component/Workflow/* @lyrixx /src/Symfony/Component/Workflow/ @lyrixx
# Yaml # Yaml
/src/Symfony/Component/Yaml/* @xabbuh /src/Symfony/Component/Yaml/ @xabbuh

View File

@ -209,11 +209,6 @@ Security
* Not implementing the methods `__serialize` and `__unserialize` in classes implementing * Not implementing the methods `__serialize` and `__unserialize` in classes implementing
the `TokenInterface` is deprecated the `TokenInterface` is deprecated
SecurityBundle
--------------
* Configuring encoders using `argon2i` or `bcrypt` as algorithm has been deprecated, use `auto` instead.
TwigBridge TwigBridge
---------- ----------

View File

@ -509,7 +509,6 @@ SecurityBundle
changed to underscores. changed to underscores.
Before: `my-cookie` deleted the `my_cookie` cookie (with an underscore). Before: `my-cookie` deleted the `my_cookie` cookie (with an underscore).
After: `my-cookie` deletes the `my-cookie` cookie (with a dash). After: `my-cookie` deletes the `my-cookie` cookie (with a dash).
* Configuring encoders using `argon2i` or `bcrypt` as algorithm is not supported anymore, use `auto` instead.
Serializer Serializer
---------- ----------

View File

@ -187,7 +187,7 @@ class EntityTypeTest extends BaseTypeTest
public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ $this->factory->createNamed('name', static::TESTED_TYPE, null, [
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => new \stdClass(), 'query_builder' => new \stdClass(),

View File

@ -28,7 +28,6 @@ final class SearchAndRenderBlockNode extends FunctionExpression
preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches); preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches);
$label = null;
$arguments = iterator_to_array($this->getNode('arguments')); $arguments = iterator_to_array($this->getNode('arguments'));
$blockNameSuffix = $matches[1]; $blockNameSuffix = $matches[1];

View File

@ -82,7 +82,6 @@ col-sm-10
<div class="{{ block('form_group_class') }}"> <div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}} {{- form_widget(form) -}}
{{- form_help(form) -}} {{- form_help(form) -}}
{{- form_errors(form) -}}
</div>{#--#} </div>{#--#}
</div> </div>
{%- endblock checkbox_row %} {%- endblock checkbox_row %}

View File

@ -180,10 +180,10 @@ class AppVariableTest extends TestCase
$flashMessages = $this->setFlashMessages(); $flashMessages = $this->setFlashMessages();
$this->assertEquals($flashMessages, $this->appVariable->getFlashes([])); $this->assertEquals($flashMessages, $this->appVariable->getFlashes([]));
$flashMessages = $this->setFlashMessages(); $this->setFlashMessages();
$this->assertEquals([], $this->appVariable->getFlashes('this-does-not-exist')); $this->assertEquals([], $this->appVariable->getFlashes('this-does-not-exist'));
$flashMessages = $this->setFlashMessages(); $this->setFlashMessages();
$this->assertEquals( $this->assertEquals(
['this-does-not-exist' => []], ['this-does-not-exist' => []],
$this->appVariable->getFlashes(['this-does-not-exist']) $this->appVariable->getFlashes(['this-does-not-exist'])

View File

@ -52,7 +52,7 @@ class LintCommandTest extends TestCase
$filename = $this->createFile(''); $filename = $this->createFile('');
unlink($filename); unlink($filename);
$ret = $tester->execute(['filename' => [$filename]], ['decorated' => false]); $tester->execute(['filename' => [$filename]], ['decorated' => false]);
} }
public function testLintFileCompileTimeException() public function testLintFileCompileTimeException()

View File

@ -34,7 +34,7 @@ class StopwatchExtensionTest extends TestCase
$twig->addExtension(new StopwatchExtension($this->getStopwatch($events))); $twig->addExtension(new StopwatchExtension($this->getStopwatch($events)));
try { try {
$nodes = $twig->render('template'); $twig->render('template');
} catch (RuntimeError $e) { } catch (RuntimeError $e) {
throw $e->getPrevious(); throw $e->getPrevious();
} }

View File

@ -51,14 +51,14 @@ class TranslationExtensionTest extends TestCase
{ {
$this->expectException('Twig\Error\SyntaxError'); $this->expectException('Twig\Error\SyntaxError');
$this->expectExceptionMessage('Unexpected token. Twig was looking for the "with", "from", or "into" keyword in "index" at line 3.'); $this->expectExceptionMessage('Unexpected token. Twig was looking for the "with", "from", or "into" keyword in "index" at line 3.');
$output = $this->getTemplate("{% trans \n\nfoo %}{% endtrans %}")->render(); $this->getTemplate("{% trans \n\nfoo %}{% endtrans %}")->render();
} }
public function testTransComplexBody() public function testTransComplexBody()
{ {
$this->expectException('Twig\Error\SyntaxError'); $this->expectException('Twig\Error\SyntaxError');
$this->expectExceptionMessage('A message inside a trans tag must be a simple text in "index" at line 2.'); $this->expectExceptionMessage('A message inside a trans tag must be a simple text in "index" at line 2.');
$output = $this->getTemplate("{% trans %}\n{{ 1 + 2 }}{% endtrans %}")->render(); $this->getTemplate("{% trans %}\n{{ 1 + 2 }}{% endtrans %}")->render();
} }
public function getTransTests() public function getTransTests()

View File

@ -510,7 +510,6 @@ class TextDescriptor extends Descriptor
$tableHeaders = ['Order', 'Callable', 'Priority']; $tableHeaders = ['Order', 'Callable', 'Priority'];
$tableRows = []; $tableRows = [];
$order = 1;
foreach ($eventListeners as $order => $listener) { foreach ($eventListeners as $order => $listener) {
$tableRows[] = [sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener)]; $tableRows[] = [sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener)];
} }

View File

@ -115,7 +115,13 @@ class SodiumVault extends AbstractVault
return null; return null;
} }
return sodium_crypto_box_seal_open(include $file, $this->decryptionKey); if (false === $value = sodium_crypto_box_seal_open(include $file, $this->decryptionKey)) {
$this->lastMessage = sprintf('Secrets cannot be revealed as the wrong decryption key was provided for "%s".', $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
return null;
}
return $value;
} }
public function remove(string $name): bool public function remove(string $name): bool

View File

@ -336,8 +336,7 @@ class AbstractControllerTest extends TestCase
$controller = $this->createController(); $controller = $this->createController();
/* @var BinaryFileResponse $response */ $controller->file('some-file.txt', 'test.php');
$response = $controller->file('some-file.txt', 'test.php');
} }
public function testIsGranted() public function testIsGranted()

View File

@ -27,7 +27,7 @@ class TestExtension extends Extension implements PrependExtensionInterface
public function load(array $configs, ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container)
{ {
$configuration = $this->getConfiguration($configs, $container); $configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs); $this->processConfiguration($configuration, $configs);
$container->setAlias('test.annotation_reader', new Alias('annotation_reader', true)); $container->setAlias('test.annotation_reader', new Alias('annotation_reader', true));
} }

View File

@ -59,7 +59,7 @@ class SessionTest extends AbstractWebTestCase
} }
// set flash // set flash
$crawler = $client->request('GET', '/session_setflash/Hello%20world.'); $client->request('GET', '/session_setflash/Hello%20world.');
// check flash displays on redirect // check flash displays on redirect
$this->assertStringContainsString('Hello world.', $client->followRedirect()->text()); $this->assertStringContainsString('Hello world.', $client->followRedirect()->text());

View File

@ -19,7 +19,8 @@ CHANGELOG
4.4.0 4.4.0
----- -----
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories. * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories.
4.3.0 4.3.0
----- -----
@ -30,7 +31,6 @@ CHANGELOG
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies option is deprecated and will be disabled in Symfony 5.0. This affects to cookies
with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie` with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie`
name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore). name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore).
* Deprecated configuring encoders using `argon2i` as algorithm, use `auto` instead
4.2.0 4.2.0
----- -----

View File

@ -530,6 +530,41 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
]; ];
} }
// bcrypt encoder
if ('bcrypt' === $config['algorithm']) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_BCRYPT;
return $this->createEncoder($config);
}
// Argon2i encoder
if ('argon2i' === $config['algorithm']) {
if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2I')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_ARGON2I;
} else {
throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : ''));
}
return $this->createEncoder($config);
}
if ('argon2id' === $config['algorithm']) {
if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2ID')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_ARGON2ID;
} else {
throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : ''));
}
return $this->createEncoder($config);
}
if ('native' === $config['algorithm']) { if ('native' === $config['algorithm']) {
return [ return [
'class' => NativePasswordEncoder::class, 'class' => NativePasswordEncoder::class,
@ -537,7 +572,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$config['time_cost'], $config['time_cost'],
(($config['memory_cost'] ?? 0) << 10) ?: null, (($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'], $config['cost'],
], ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
]; ];
} }

View File

@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
abstract class CompleteConfigurationTest extends TestCase abstract class CompleteConfigurationTest extends TestCase
@ -370,6 +371,107 @@ abstract class CompleteConfigurationTest extends TestCase
]], $container->getDefinition('security.encoder_factory.generic')->getArguments()); ]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
} }
public function testEncodersWithArgon2i()
{
if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Argon2i algorithm is not supported.');
}
$container = $this->getContainer('argon2i_encoder');
$this->assertEquals([[
'JMS\FooBundle\Entity\User1' => [
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
'arguments' => [false],
],
'JMS\FooBundle\Entity\User2' => [
'algorithm' => 'sha1',
'encode_as_base64' => false,
'iterations' => 5,
'hash_algorithm' => 'sha512',
'key_length' => 40,
'ignore_case' => false,
'cost' => null,
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
],
'JMS\FooBundle\Entity\User3' => [
'algorithm' => 'md5',
'hash_algorithm' => 'sha512',
'key_length' => 40,
'ignore_case' => false,
'encode_as_base64' => true,
'iterations' => 5000,
'cost' => null,
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
],
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => [
'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
'arguments' => ['sha1', false, 5, 30],
],
'JMS\FooBundle\Entity\User6' => [
'class' => 'Symfony\Component\Security\Core\Encoder\NativePasswordEncoder',
'arguments' => [8, 102400, 15],
],
'JMS\FooBundle\Entity\User7' => [
'class' => $sodium ? SodiumPasswordEncoder::class : NativePasswordEncoder::class,
'arguments' => $sodium ? [256, 1] : [1, 262144, null, \PASSWORD_ARGON2I],
],
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
}
public function testEncodersWithBCrypt()
{
$container = $this->getContainer('bcrypt_encoder');
$this->assertEquals([[
'JMS\FooBundle\Entity\User1' => [
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
'arguments' => [false],
],
'JMS\FooBundle\Entity\User2' => [
'algorithm' => 'sha1',
'encode_as_base64' => false,
'iterations' => 5,
'hash_algorithm' => 'sha512',
'key_length' => 40,
'ignore_case' => false,
'cost' => null,
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
],
'JMS\FooBundle\Entity\User3' => [
'algorithm' => 'md5',
'hash_algorithm' => 'sha512',
'key_length' => 40,
'ignore_case' => false,
'encode_as_base64' => true,
'iterations' => 5000,
'cost' => null,
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
],
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => [
'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
'arguments' => ['sha1', false, 5, 30],
],
'JMS\FooBundle\Entity\User6' => [
'class' => 'Symfony\Component\Security\Core\Encoder\NativePasswordEncoder',
'arguments' => [8, 102400, 15],
],
'JMS\FooBundle\Entity\User7' => [
'class' => NativePasswordEncoder::class,
'arguments' => [null, null, 15, \PASSWORD_BCRYPT],
],
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
}
public function testRememberMeThrowExceptionsDefault() public function testRememberMeThrowExceptionsDefault()
{ {
$container = $this->getContainer('container1'); $container = $this->getContainer('container1');

View File

@ -0,0 +1,13 @@
<?php
$this->load('container1.php', $container);
$container->loadFromExtension('security', [
'encoders' => [
'JMS\FooBundle\Entity\User7' => [
'algorithm' => 'argon2i',
'memory_cost' => 256,
'time_cost' => 1,
],
],
]);

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://symfony.com/schema/dic/security"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<imports>
<import resource="container1.xml"/>
</imports>
<sec:config>
<sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1" />
</sec:config>
</container>

View File

@ -0,0 +1,9 @@
imports:
- { resource: container1.yml }
security:
encoders:
JMS\FooBundle\Entity\User7:
algorithm: argon2i
memory_cost: 256
time_cost: 1

View File

@ -61,7 +61,7 @@ class AbstractFactoryTest extends TestCase
$options['failure_handler'] = $serviceId; $options['failure_handler'] = $serviceId;
} }
list($container, $authProviderId, $listenerId, $entryPointId) = $this->callFactory('foo', $options, 'user_provider', 'entry_point'); list($container) = $this->callFactory('foo', $options, 'user_provider', 'entry_point');
$definition = $container->getDefinition('abstract_listener.foo'); $definition = $container->getDefinition('abstract_listener.foo');
$arguments = $definition->getArguments(); $arguments = $definition->getArguments();
@ -99,7 +99,7 @@ class AbstractFactoryTest extends TestCase
$options['success_handler'] = $serviceId; $options['success_handler'] = $serviceId;
} }
list($container, $authProviderId, $listenerId, $entryPointId) = $this->callFactory('foo', $options, 'user_provider', 'entry_point'); list($container) = $this->callFactory('foo', $options, 'user_provider', 'entry_point');
$definition = $container->getDefinition('abstract_listener.foo'); $definition = $container->getDefinition('abstract_listener.foo');
$arguments = $definition->getArguments(); $arguments = $definition->getArguments();

View File

@ -159,7 +159,7 @@ class GuardAuthenticationFactoryTest extends TestCase
'authenticators' => ['authenticator123', 'authenticatorABC'], 'authenticators' => ['authenticator123', 'authenticatorABC'],
'entry_point' => 'authenticatorABC', 'entry_point' => 'authenticatorABC',
]; ];
list($container, $entryPointId) = $this->executeCreate($config, null); list(, $entryPointId) = $this->executeCreate($config, null);
$this->assertEquals('authenticatorABC', $entryPointId); $this->assertEquals('authenticatorABC', $entryPointId);
} }
@ -172,7 +172,7 @@ class GuardAuthenticationFactoryTest extends TestCase
$userProviderId = 'my_user_provider'; $userProviderId = 'my_user_provider';
$factory = new GuardAuthenticationFactory(); $factory = new GuardAuthenticationFactory();
list($providerId, $listenerId, $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId); list(, , $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId);
return [$container, $entryPointId]; return [$container, $entryPointId];
} }

View File

@ -24,7 +24,7 @@ class MissingUserProviderTest extends AbstractWebTestCase
$response = $client->getResponse(); $response = $client->getResponse();
$this->assertSame(500, $response->getStatusCode()); $this->assertSame(500, $response->getStatusCode());
$this->stringContains('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException', $response->getContent()); $this->assertStringContainsString('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException', $response->getContent());
$this->stringContains('"default" firewall requires a user provider but none was defined.', $response->getContent()); $this->assertStringContainsString('"default" firewall requires a user provider but none was defined', html_entity_decode($response->getContent()));
} }
} }

View File

@ -53,6 +53,66 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$this->assertEquals($statusCode, 1); $this->assertEquals($statusCode, 1);
} }
public function testEncodePasswordBcrypt()
{
$this->setupBcrypt();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'password',
'user-class' => 'Custom\Class\Bcrypt\User',
], ['interactive' => false]);
$output = $this->passwordEncoderCommandTester->getDisplay();
$this->assertStringContainsString('Password encoding succeeded', $output);
$encoder = new NativePasswordEncoder(null, null, 17, PASSWORD_BCRYPT);
preg_match('# Encoded password\s{1,}([\w+\/$.]+={0,2})\s+#', $output, $matches);
$hash = $matches[1];
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
}
public function testEncodePasswordArgon2i()
{
if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Argon2i algorithm not available.');
}
$this->setupArgon2i();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'password',
'user-class' => 'Custom\Class\Argon2i\User',
], ['interactive' => false]);
$output = $this->passwordEncoderCommandTester->getDisplay();
$this->assertStringContainsString('Password encoding succeeded', $output);
$encoder = $sodium ? new SodiumPasswordEncoder() : new NativePasswordEncoder(null, null, null, PASSWORD_ARGON2I);
preg_match('# Encoded password\s+(\$argon2i?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches);
$hash = $matches[1];
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
}
public function testEncodePasswordArgon2id()
{
if (!($sodium = (SodiumPasswordEncoder::isSupported() && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13'))) && !\defined('PASSWORD_ARGON2ID')) {
$this->markTestSkipped('Argon2id algorithm not available.');
}
$this->setupArgon2id();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'password',
'user-class' => 'Custom\Class\Argon2id\User',
], ['interactive' => false]);
$output = $this->passwordEncoderCommandTester->getDisplay();
$this->assertStringContainsString('Password encoding succeeded', $output);
$encoder = $sodium ? new SodiumPasswordEncoder() : new NativePasswordEncoder(null, null, null, PASSWORD_ARGON2ID);
preg_match('# Encoded password\s+(\$argon2id?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches);
$hash = $matches[1];
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
}
public function testEncodePasswordNative() public function testEncodePasswordNative()
{ {
$this->passwordEncoderCommandTester->execute([ $this->passwordEncoderCommandTester->execute([
@ -148,6 +208,38 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay()); $this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
} }
public function testEncodePasswordArgon2iOutput()
{
if (!(SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Argon2i algorithm not available.');
}
$this->setupArgon2i();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'p@ssw0rd',
'user-class' => 'Custom\Class\Argon2i\User',
], ['interactive' => false]);
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
}
public function testEncodePasswordArgon2idOutput()
{
if (!(SodiumPasswordEncoder::isSupported() && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2ID')) {
$this->markTestSkipped('Argon2id algorithm not available.');
}
$this->setupArgon2id();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'p@ssw0rd',
'user-class' => 'Custom\Class\Argon2id\User',
], ['interactive' => false]);
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
}
public function testEncodePasswordSodiumOutput() public function testEncodePasswordSodiumOutput()
{ {
if (!SodiumPasswordEncoder::isSupported()) { if (!SodiumPasswordEncoder::isSupported()) {
@ -238,6 +330,45 @@ EOTXT
$this->passwordEncoderCommandTester = null; $this->passwordEncoderCommandTester = null;
} }
private function setupArgon2i()
{
putenv('COLUMNS='.(119 + \strlen(PHP_EOL)));
$kernel = $this->createKernel(['test_case' => 'PasswordEncode', 'root_config' => 'argon2i.yml']);
$kernel->boot();
$application = new Application($kernel);
$passwordEncoderCommand = $application->get('security:encode-password');
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
}
private function setupArgon2id()
{
putenv('COLUMNS='.(119 + \strlen(PHP_EOL)));
$kernel = $this->createKernel(['test_case' => 'PasswordEncode', 'root_config' => 'argon2id.yml']);
$kernel->boot();
$application = new Application($kernel);
$passwordEncoderCommand = $application->get('security:encode-password');
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
}
private function setupBcrypt()
{
putenv('COLUMNS='.(119 + \strlen(PHP_EOL)));
$kernel = $this->createKernel(['test_case' => 'PasswordEncode', 'root_config' => 'bcrypt.yml']);
$kernel->boot();
$application = new Application($kernel);
$passwordEncoderCommand = $application->get('security:encode-password');
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
}
private function setupSodium() private function setupSodium()
{ {
putenv('COLUMNS='.(119 + \strlen(PHP_EOL))); putenv('COLUMNS='.(119 + \strlen(PHP_EOL)));

View File

@ -0,0 +1,7 @@
imports:
- { resource: config.yml }
security:
encoders:
Custom\Class\Argon2id\User:
algorithm: argon2id

View File

@ -46,6 +46,8 @@
--base-4: #666; --base-4: #666;
--base-5: #444; --base-5: #444;
--base-6: #222; --base-6: #222;
--card-label-background: #eee;
--card-label-color: var(--base-6);
} }
.theme-dark { .theme-dark {
@ -85,6 +87,8 @@
--base-4: #666; --base-4: #666;
--base-5: #e0e0e0; --base-5: #e0e0e0;
--base-6: #f5f5f5; --base-6: #f5f5f5;
--card-label-background: var(--tab-active-background);
--card-label-color: var(--tab-active-color);
} }
{# Basic styles {# Basic styles
@ -436,8 +440,8 @@ table tbody td.num-col {
margin-top: 0; margin-top: 0;
} }
.card .label { .card .label {
background-color: #EEE; background-color: var(--card-label-background);
color: var(--base-6); color: var(--card-label-color);
} }
{# Status {# Status

View File

@ -201,7 +201,7 @@ class CookieTest extends TestCase
{ {
$this->expectException('UnexpectedValueException'); $this->expectException('UnexpectedValueException');
$this->expectExceptionMessage('The cookie expiration time "string" is not valid.'); $this->expectExceptionMessage('The cookie expiration time "string" is not valid.');
$cookie = new Cookie('foo', 'bar', 'string'); new Cookie('foo', 'bar', 'string');
} }
public function testSameSite() public function testSameSite()

View File

@ -70,7 +70,7 @@ class MaxIdLengthAdapterTest extends TestCase
{ {
$this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException'); $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Namespace must be 26 chars max, 40 given ("----------------------------------------")'); $this->expectExceptionMessage('Namespace must be 26 chars max, 40 given ("----------------------------------------")');
$cache = $this->getMockBuilder(MaxIdLengthAdapter::class) $this->getMockBuilder(MaxIdLengthAdapter::class)
->setConstructorArgs([str_repeat('-', 40)]) ->setConstructorArgs([str_repeat('-', 40)])
->getMock(); ->getMock();
} }

View File

@ -19,7 +19,7 @@ class DelegatingLoaderTest extends TestCase
{ {
public function testConstructor() public function testConstructor()
{ {
$loader = new DelegatingLoader($resolver = new LoaderResolver()); new DelegatingLoader($resolver = new LoaderResolver());
$this->assertTrue(true, '__construct() takes a loader resolver as its first argument'); $this->assertTrue(true, '__construct() takes a loader resolver as its first argument');
} }

View File

@ -67,7 +67,7 @@ EOF
$loadedClass = 123; $loadedClass = 123;
$res = new ClassExistenceResource('MissingFooClass', false); new ClassExistenceResource('MissingFooClass', false);
$this->assertSame(123, $loadedClass); $this->assertSame(123, $loadedClass);
} finally { } finally {

View File

@ -67,7 +67,7 @@ class DirectoryResourceTest extends TestCase
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$this->expectExceptionMessageRegExp('/The directory ".*" does not exist./'); $this->expectExceptionMessageRegExp('/The directory ".*" does not exist./');
$resource = new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999)); new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999));
} }
public function testIsFresh() public function testIsFresh()
@ -165,7 +165,7 @@ class DirectoryResourceTest extends TestCase
{ {
$resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/');
$unserialized = unserialize(serialize($resource)); unserialize(serialize($resource));
$this->assertSame(realpath($this->directory), $resource->getResource()); $this->assertSame(realpath($this->directory), $resource->getResource());
$this->assertSame('/\.(foo|xml)$/', $resource->getPattern()); $this->assertSame('/\.(foo|xml)$/', $resource->getPattern());

View File

@ -57,7 +57,7 @@ class FileResourceTest extends TestCase
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$this->expectExceptionMessageRegExp('/The file ".*" does not exist./'); $this->expectExceptionMessageRegExp('/The file ".*" does not exist./');
$resource = new FileResource('/____foo/foobar'.mt_rand(1, 999999)); new FileResource('/____foo/foobar'.mt_rand(1, 999999));
} }
public function testIsFresh() public function testIsFresh()
@ -76,7 +76,7 @@ class FileResourceTest extends TestCase
public function testSerializeUnserialize() public function testSerializeUnserialize()
{ {
$unserialized = unserialize(serialize($this->resource)); unserialize(serialize($this->resource));
$this->assertSame(realpath($this->file), $this->resource->getResource()); $this->assertSame(realpath($this->file), $this->resource->getResource());
} }

View File

@ -579,7 +579,7 @@ class ApplicationTest extends TestCase
// Subnamespace + plural // Subnamespace + plural
try { try {
$a = $application->find('foo3:'); $application->find('foo3:');
$this->fail('->find() should throw an Symfony\Component\Console\Exception\CommandNotFoundException if a command is ambiguous because of a subnamespace, with alternatives'); $this->fail('->find() should throw an Symfony\Component\Console\Exception\CommandNotFoundException if a command is ambiguous because of a subnamespace, with alternatives');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e); $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e);

View File

@ -104,7 +104,7 @@ class ProgressIndicatorTest extends TestCase
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Must have at least 2 indicator value characters.'); $this->expectExceptionMessage('Must have at least 2 indicator value characters.');
$bar = new ProgressIndicator($this->getOutputStream(), null, 100, ['1']); new ProgressIndicator($this->getOutputStream(), null, 100, ['1']);
} }
public function testCannotStartAlreadyStartedIndicator() public function testCannotStartAlreadyStartedIndicator()

View File

@ -269,7 +269,7 @@ EOF;
foreach ($ids as $id) { foreach ($ids as $id) {
$c .= ' '.$this->doExport($id)." => true,\n"; $c .= ' '.$this->doExport($id)." => true,\n";
} }
$files['removed-ids.php'] = $c .= "];\n"; $files['removed-ids.php'] = $c."];\n";
} }
if (!$this->inlineFactories) { if (!$this->inlineFactories) {

View File

@ -24,28 +24,28 @@ class AnalyzeServiceReferencesPassTest extends TestCase
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$a = $container $container
->register('a') ->register('a')
->addArgument($ref1 = new Reference('b')) ->addArgument($ref1 = new Reference('b'))
; ;
$b = $container $container
->register('b') ->register('b')
->addMethodCall('setA', [$ref2 = new Reference('a')]) ->addMethodCall('setA', [$ref2 = new Reference('a')])
; ;
$c = $container $container
->register('c') ->register('c')
->addArgument($ref3 = new Reference('a')) ->addArgument($ref3 = new Reference('a'))
->addArgument($ref4 = new Reference('b')) ->addArgument($ref4 = new Reference('b'))
; ;
$d = $container $container
->register('d') ->register('d')
->setProperty('foo', $ref5 = new Reference('b')) ->setProperty('foo', $ref5 = new Reference('b'))
; ;
$e = $container $container
->register('e') ->register('e')
->setConfigurator([$ref6 = new Reference('b'), 'methodName']) ->setConfigurator([$ref6 = new Reference('b'), 'methodName'])
; ;

View File

@ -51,7 +51,7 @@ class IntegrationTest extends TestCase
->setPublic(true) ->setPublic(true)
; ;
$b = $container $container
->register('b', '\stdClass') ->register('b', '\stdClass')
->addArgument(new Reference('c')) ->addArgument(new Reference('c'))
->setPublic(false) ->setPublic(false)

View File

@ -363,7 +363,7 @@ class ResolveChildDefinitionsPassTest extends TestCase
->setBindings(['a' => '1', 'b' => '2']) ->setBindings(['a' => '1', 'b' => '2'])
; ;
$child = $container->setDefinition('child', new ChildDefinition('parent')) $container->setDefinition('child', new ChildDefinition('parent'))
->setBindings(['b' => 'B', 'c' => 'C']) ->setBindings(['b' => 'B', 'c' => 'C'])
; ;

View File

@ -87,8 +87,8 @@ class ResolveClassPassTest extends TestCase
$this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Service definition "App\Foo\Child" has a parent but no class, and its name looks like a FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.'); $this->expectExceptionMessage('Service definition "App\Foo\Child" has a parent but no class, and its name looks like a FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.');
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$parent = $container->register('App\Foo', null); $container->register('App\Foo', null);
$child = $container->setDefinition('App\Foo\Child', new ChildDefinition('App\Foo')); $container->setDefinition('App\Foo\Child', new ChildDefinition('App\Foo'));
(new ResolveClassPass())->process($container); (new ResolveClassPass())->process($container);
} }

View File

@ -1272,7 +1272,7 @@ class ContainerBuilderTest extends TestCase
$this->expectExceptionMessage('The definition for "DateTime" has no class attribute, and appears to reference a class or interface in the global namespace.'); $this->expectExceptionMessage('The definition for "DateTime" has no class attribute, and appears to reference a class or interface in the global namespace.');
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$definition = $container->register(\DateTime::class); $container->register(\DateTime::class);
$container->compile(); $container->compile();
} }
@ -1302,7 +1302,7 @@ class ContainerBuilderTest extends TestCase
$this->expectExceptionMessage('The definition for "123_abc" has no class.'); $this->expectExceptionMessage('The definition for "123_abc" has no class.');
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$definition = $container->register('123_abc'); $container->register('123_abc');
$container->compile(); $container->compile();
} }
@ -1312,7 +1312,7 @@ class ContainerBuilderTest extends TestCase
$this->expectExceptionMessage('The definition for "\foo" has no class.'); $this->expectExceptionMessage('The definition for "\foo" has no class.');
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$definition = $container->register('\\foo'); $container->register('\\foo');
$container->compile(); $container->compile();
} }

View File

@ -1091,7 +1091,7 @@ class PhpDumperTest extends TestCase
->setPublic(true) ->setPublic(true)
->addArgument($baz); ->addArgument($baz);
$passConfig = $container->getCompiler()->getPassConfig(); $container->getCompiler()->getPassConfig();
$container->compile(); $container->compile();
$dumper = new PhpDumper($container); $dumper = new PhpDumper($container);
@ -1183,7 +1183,6 @@ class PhpDumperTest extends TestCase
$container->compile(); $container->compile();
$dumper = new PhpDumper($container); $dumper = new PhpDumper($container);
$dump = $dumper->dump();
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_adawson.php', $dumper->dump()); $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_adawson.php', $dumper->dump());
} }

View File

@ -884,7 +884,6 @@ class XmlFileLoaderTest extends TestCase
$container->compile(); $container->compile();
$dumper = new PhpDumper($container); $dumper = new PhpDumper($container);
$dump = $dumper->dump();
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_tsantos.php', $dumper->dump()); $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_tsantos.php', $dumper->dump());
} }

View File

@ -19,7 +19,7 @@ class ChoiceFormFieldTest extends FormFieldTestCase
{ {
$node = $this->createNode('textarea', ''); $node = $this->createNode('textarea', '');
try { try {
$field = new ChoiceFormField($node); new ChoiceFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not an input or a select'); $this->fail('->initialize() throws a \LogicException if the node is not an input or a select');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input or a select'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input or a select');
@ -27,7 +27,7 @@ class ChoiceFormFieldTest extends FormFieldTestCase
$node = $this->createNode('input', '', ['type' => 'text']); $node = $this->createNode('input', '', ['type' => 'text']);
try { try {
$field = new ChoiceFormField($node); new ChoiceFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is an input with a type different from checkbox or radio'); $this->fail('->initialize() throws a \LogicException if the node is an input with a type different from checkbox or radio');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is an input with a type different from checkbox or radio'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is an input with a type different from checkbox or radio');

View File

@ -24,7 +24,7 @@ class FileFormFieldTest extends FormFieldTestCase
$node = $this->createNode('textarea', ''); $node = $this->createNode('textarea', '');
try { try {
$field = new FileFormField($node); new FileFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not an input field'); $this->fail('->initialize() throws a \LogicException if the node is not an input field');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input field'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input field');
@ -32,7 +32,7 @@ class FileFormFieldTest extends FormFieldTestCase
$node = $this->createNode('input', '', ['type' => 'text']); $node = $this->createNode('input', '', ['type' => 'text']);
try { try {
$field = new FileFormField($node); new FileFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not a file input field'); $this->fail('->initialize() throws a \LogicException if the node is not a file input field');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not a file input field'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is not a file input field');

View File

@ -24,7 +24,7 @@ class InputFormFieldTest extends FormFieldTestCase
$node = $this->createNode('textarea', ''); $node = $this->createNode('textarea', '');
try { try {
$field = new InputFormField($node); new InputFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not an input'); $this->fail('->initialize() throws a \LogicException if the node is not an input');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input');
@ -32,7 +32,7 @@ class InputFormFieldTest extends FormFieldTestCase
$node = $this->createNode('input', '', ['type' => 'checkbox']); $node = $this->createNode('input', '', ['type' => 'checkbox']);
try { try {
$field = new InputFormField($node); new InputFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is a checkbox'); $this->fail('->initialize() throws a \LogicException if the node is a checkbox');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is a checkbox'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is a checkbox');
@ -40,7 +40,7 @@ class InputFormFieldTest extends FormFieldTestCase
$node = $this->createNode('input', '', ['type' => 'file']); $node = $this->createNode('input', '', ['type' => 'file']);
try { try {
$field = new InputFormField($node); new InputFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is a file'); $this->fail('->initialize() throws a \LogicException if the node is a file');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is a file'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is a file');

View File

@ -24,7 +24,7 @@ class TextareaFormFieldTest extends FormFieldTestCase
$node = $this->createNode('input', ''); $node = $this->createNode('input', '');
try { try {
$field = new TextareaFormField($node); new TextareaFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not a textarea'); $this->fail('->initialize() throws a \LogicException if the node is not a textarea');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not a textarea'); $this->assertTrue(true, '->initialize() throws a \LogicException if the node is not a textarea');

View File

@ -39,14 +39,14 @@ class FormTest extends TestCase
$nodes = $dom->getElementsByTagName('input'); $nodes = $dom->getElementsByTagName('input');
try { try {
$form = new Form($nodes->item(0), 'http://example.com'); new Form($nodes->item(0), 'http://example.com');
$this->fail('__construct() throws a \\LogicException if the node has no form ancestor'); $this->fail('__construct() throws a \\LogicException if the node has no form ancestor');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '__construct() throws a \\LogicException if the node has no form ancestor'); $this->assertTrue(true, '__construct() throws a \\LogicException if the node has no form ancestor');
} }
try { try {
$form = new Form($nodes->item(1), 'http://example.com'); new Form($nodes->item(1), 'http://example.com');
$this->fail('__construct() throws a \\LogicException if the input type is not submit, button, or image'); $this->fail('__construct() throws a \\LogicException if the input type is not submit, button, or image');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '__construct() throws a \\LogicException if the input type is not submit, button, or image'); $this->assertTrue(true, '__construct() throws a \\LogicException if the input type is not submit, button, or image');
@ -55,7 +55,7 @@ class FormTest extends TestCase
$nodes = $dom->getElementsByTagName('button'); $nodes = $dom->getElementsByTagName('button');
try { try {
$form = new Form($nodes->item(0), 'http://example.com'); new Form($nodes->item(0), 'http://example.com');
$this->fail('__construct() throws a \\LogicException if the node has no form ancestor'); $this->fail('__construct() throws a \\LogicException if the node has no form ancestor');
} catch (\LogicException $e) { } catch (\LogicException $e) {
$this->assertTrue(true, '__construct() throws a \\LogicException if the node has no form ancestor'); $this->assertTrue(true, '__construct() throws a \\LogicException if the node has no form ancestor');
@ -63,11 +63,19 @@ class FormTest extends TestCase
} }
/** /**
* __construct() should throw \\LogicException if the form attribute is invalid. * @dataProvider constructorThrowsExceptionIfNoRelatedFormProvider
*
* __construct() should throw a \LogicException if the form attribute is invalid.
*/ */
public function testConstructorThrowsExceptionIfNoRelatedForm() public function testConstructorThrowsExceptionIfNoRelatedForm(\DOMElement $node)
{ {
$this->expectException('LogicException'); $this->expectException('LogicException');
new Form($node, 'http://example.com');
}
public function constructorThrowsExceptionIfNoRelatedFormProvider()
{
$dom = new \DOMDocument(); $dom = new \DOMDocument();
$dom->loadHTML(' $dom->loadHTML('
<html> <html>
@ -81,8 +89,10 @@ class FormTest extends TestCase
$nodes = $dom->getElementsByTagName('input'); $nodes = $dom->getElementsByTagName('input');
$form = new Form($nodes->item(0), 'http://example.com'); return [
$form = new Form($nodes->item(1), 'http://example.com'); [$nodes->item(0)],
[$nodes->item(1)],
];
} }
public function testConstructorLoadsOnlyFieldsOfTheRightForm() public function testConstructorLoadsOnlyFieldsOfTheRightForm()

View File

@ -751,7 +751,7 @@ class DebugClassLoader
} }
if (isset($dirFiles[$file])) { if (isset($dirFiles[$file])) {
return $real .= $dirFiles[$file]; return $real.$dirFiles[$file];
} }
$kFile = strtolower($file); $kFile = strtolower($file);
@ -770,7 +770,7 @@ class DebugClassLoader
self::$darwinCache[$kDir][1] = $dirFiles; self::$darwinCache[$kDir][1] = $dirFiles;
} }
return $real .= $dirFiles[$kFile]; return $real.$dirFiles[$kFile];
} }
/** /**

View File

@ -294,7 +294,7 @@ class FlattenExceptionTest extends TestCase
// assertEquals() does not like NAN values. // assertEquals() does not like NAN values.
$this->assertEquals($array[$i][0], 'float'); $this->assertEquals($array[$i][0], 'float');
$this->assertNan($array[$i++][1]); $this->assertNan($array[$i][1]);
} }
public function testRecursionInArguments() public function testRecursionInArguments()

View File

@ -63,6 +63,14 @@ class ExpressionLanguageTest extends TestCase
$this->assertSame($savedParsedExpression, $parsedExpression); $this->assertSame($savedParsedExpression, $parsedExpression);
} }
public function testWrongCacheImplementation()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Cache argument has to implement Psr\Cache\CacheItemPoolInterface.');
$cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemSpoolInterface')->getMock();
new ExpressionLanguage($cacheMock);
}
public function testConstantFunction() public function testConstantFunction()
{ {
$expressionLanguage = new ExpressionLanguage(); $expressionLanguage = new ExpressionLanguage();
@ -158,7 +166,7 @@ class ExpressionLanguageTest extends TestCase
$cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock(); $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock();
$cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock(); $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock();
$expressionLanguage = new ExpressionLanguage($cacheMock); $expressionLanguage = new ExpressionLanguage($cacheMock);
$savedParsedExpressions = []; $savedParsedExpression = null;
$cacheMock $cacheMock
->expects($this->exactly(2)) ->expects($this->exactly(2))

View File

@ -131,7 +131,8 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf
$hash = spl_object_hash($form); $hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) { if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = []; // field was created by form event
$this->collectConfiguration($form);
} }
$this->dataByForm[$hash] = array_replace( $this->dataByForm[$hash] = array_replace(

View File

@ -181,10 +181,10 @@ class DateTimeToLocalizedStringTransformerTest extends TestCase
public function testTransformWrapsIntlErrors() public function testTransformWrapsIntlErrors()
{ {
$transformer = new DateTimeToLocalizedStringTransformer();
$this->markTestIncomplete('Checking for intl errors needs to be reimplemented'); $this->markTestIncomplete('Checking for intl errors needs to be reimplemented');
$transformer = new DateTimeToLocalizedStringTransformer();
// HOW TO REPRODUCE? // HOW TO REPRODUCE?
//$this->expectException('Symfony\Component\Form\Extension\Core\DataTransformer\TransformationFailedException'); //$this->expectException('Symfony\Component\Form\Extension\Core\DataTransformer\TransformationFailedException');

View File

@ -13,10 +13,20 @@ namespace Symfony\Component\Form\Tests\Extension\DataCollector;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Extension\Core\CoreExtension;
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollector; use Symfony\Component\Form\Extension\DataCollector\FormDataCollector;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\ResolvedFormTypeFactory;
class FormDataCollectorTest extends TestCase class FormDataCollectorTest extends TestCase
{ {
@ -69,9 +79,9 @@ class FormDataCollectorTest extends TestCase
{ {
$this->dataExtractor = $this->getMockBuilder('Symfony\Component\Form\Extension\DataCollector\FormDataExtractorInterface')->getMock(); $this->dataExtractor = $this->getMockBuilder('Symfony\Component\Form\Extension\DataCollector\FormDataExtractorInterface')->getMock();
$this->dataCollector = new FormDataCollector($this->dataExtractor); $this->dataCollector = new FormDataCollector($this->dataExtractor);
$this->dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); $this->dispatcher = new EventDispatcher();
$this->factory = $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface')->getMock(); $this->factory = new FormFactory(new FormRegistry([new CoreExtension()], new ResolvedFormTypeFactory()));
$this->dataMapper = $this->getMockBuilder('Symfony\Component\Form\DataMapperInterface')->getMock(); $this->dataMapper = new PropertyPathMapper();
$this->form = $this->createForm('name'); $this->form = $this->createForm('name');
$this->childForm = $this->createForm('child'); $this->childForm = $this->createForm('child');
$this->view = new FormView(); $this->view = new FormView();
@ -726,6 +736,56 @@ class FormDataCollectorTest extends TestCase
); );
} }
public function testCollectMissingDataFromChildFormAddedOnFormEvents()
{
$form = $this->factory->createNamedBuilder('root', FormType::class, ['items' => null])
->add('items', CollectionType::class, [
'entry_type' => TextType::class,
'allow_add' => true,
// data is locked and modelData (null) is different to the
// configured data, so modifications of the configured data
// won't be allowed at this point. It also means *_SET_DATA
// events won't dispatched either. Therefore, no child form
// is created during the mapping of data to the form.
'data' => ['foo'],
])
->getForm()
;
$this->dataExtractor->expects($extractConfiguration = $this->exactly(4))
->method('extractConfiguration')
->willReturn([])
;
$this->dataExtractor->expects($extractDefaultData = $this->exactly(4))
->method('extractDefaultData')
->willReturnCallback(static function (FormInterface $form) {
// this simulate the call in extractDefaultData() method
// where (if defaultDataSet is false) it fires *_SET_DATA
// events, adding the form related to the configured data
$form->getNormData();
return [];
})
;
$this->dataExtractor->expects($this->exactly(4))
->method('extractSubmittedData')
->willReturn([])
;
$this->dataCollector->collectConfiguration($form);
$this->assertSame(2, $extractConfiguration->getInvocationCount(), 'only "root" and "items" forms were collected, the "items" children do not exist yet.');
$this->dataCollector->collectDefaultData($form);
$this->assertSame(3, $extractConfiguration->getInvocationCount(), 'extracted missing configuration of the "items" children ["0" => foo].');
$this->assertSame(3, $extractDefaultData->getInvocationCount());
$this->assertSame(['foo'], $form->get('items')->getData());
$form->submit(['items' => ['foo', 'bar']]);
$this->dataCollector->collectSubmittedData($form);
$this->assertSame(4, $extractConfiguration->getInvocationCount(), 'extracted missing configuration of the "items" children ["1" => bar].');
$this->assertSame(4, $extractDefaultData->getInvocationCount(), 'extracted missing default data of the "items" children ["1" => bar].');
$this->assertSame(['foo', 'bar'], $form->get('items')->getData());
}
private function createForm($name) private function createForm($name)
{ {
$builder = new FormBuilder($name, null, $this->dispatcher, $this->factory); $builder = new FormBuilder($name, null, $this->dispatcher, $this->factory);

View File

@ -219,6 +219,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
if ('POST' === $method) { if ('POST' === $method) {
// Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303 // Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303
$curlopts[CURLOPT_POST] = true; $curlopts[CURLOPT_POST] = true;
} elseif ('HEAD' === $method) {
$curlopts[CURLOPT_NOBODY] = true;
} else { } else {
$curlopts[CURLOPT_CUSTOMREQUEST] = $method; $curlopts[CURLOPT_CUSTOMREQUEST] = $method;
} }

View File

@ -356,6 +356,7 @@ final class CurlResponse implements ResponseInterface
if (200 > $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) { if (200 > $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) {
$multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers); $multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers);
$location = null;
return \strlen($data); return \strlen($data);
} }
@ -379,9 +380,7 @@ final class CurlResponse implements ResponseInterface
} }
} }
$location = null; if ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
if ($statusCode < 300 || 400 <= $statusCode || curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
// Headers and redirects completed, time to get the response's body // Headers and redirects completed, time to get the response's body
$multi->handlesActivity[$id][] = new FirstChunk(); $multi->handlesActivity[$id][] = new FirstChunk();
@ -405,6 +404,8 @@ final class CurlResponse implements ResponseInterface
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));
} }
$location = null;
return \strlen($data); return \strlen($data);
} }
} }

View File

@ -183,7 +183,7 @@ class MockResponse implements ResponseInterface
try { try {
$offset = 0; $offset = 0;
$chunk[1]->getStatusCode(); $chunk[1]->getStatusCode();
$response->headers = $chunk[1]->getHeaders(false); $chunk[1]->getHeaders(false);
self::readResponse($response, $chunk[0], $chunk[1], $offset); self::readResponse($response, $chunk[0], $chunk[1], $offset);
$multi->handlesActivity[$id][] = new FirstChunk(); $multi->handlesActivity[$id][] = new FirstChunk();
$buffer = $response->requestOptions['buffer'] ?? null; $buffer = $response->requestOptions['buffer'] ?? null;
@ -269,7 +269,7 @@ class MockResponse implements ResponseInterface
$info = $mock->getInfo() ?: []; $info = $mock->getInfo() ?: [];
$response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200;
$response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers);
$dlSize = isset($response->headers['content-encoding']) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0);
$response->info = [ $response->info = [
'start_time' => $response->info['start_time'], 'start_time' => $response->info['start_time'],

View File

@ -195,8 +195,16 @@ final class NativeResponse implements ResponseInterface
return; return;
} }
$this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->inflate, $this->content, $this->onProgress, &$this->remaining, &$this->info];
$this->multi->handlesActivity[$this->id] = [new FirstChunk()]; $this->multi->handlesActivity[$this->id] = [new FirstChunk()];
if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) {
$this->multi->handlesActivity[$this->id][] = null;
$this->multi->handlesActivity[$this->id][] = null;
return;
}
$this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->inflate, $this->content, $this->onProgress, &$this->remaining, &$this->info];
} }
/** /**

View File

@ -152,7 +152,7 @@ class UploadedFileTest extends TestCase
UPLOAD_ERR_OK UPLOAD_ERR_OK
); );
$movedFile = $file->move(__DIR__.'/Fixtures/directory'); $file->move(__DIR__.'/Fixtures/directory');
} }
public function failedUploadedFile() public function failedUploadedFile()

View File

@ -59,7 +59,7 @@ class HeaderBagTest extends TestCase
{ {
$this->expectException('RuntimeException'); $this->expectException('RuntimeException');
$bag = new HeaderBag(['foo' => 'Tue']); $bag = new HeaderBag(['foo' => 'Tue']);
$headerDate = $bag->getDate('foo'); $bag->getDate('foo');
} }
public function testGetCacheControlHeader() public function testGetCacheControlHeader()

View File

@ -30,13 +30,13 @@ class RedirectResponseTest extends TestCase
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Cannot redirect to an empty URL.'); $this->expectExceptionMessage('Cannot redirect to an empty URL.');
$response = new RedirectResponse(''); new RedirectResponse('');
} }
public function testRedirectResponseConstructorWrongStatusCode() public function testRedirectResponseConstructorWrongStatusCode()
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$response = new RedirectResponse('foo.bar', 404); new RedirectResponse('foo.bar', 404);
} }
public function testGenerateLocationHeader() public function testGenerateLocationHeader()

View File

@ -40,7 +40,7 @@ class NativeFileSessionHandlerTest extends TestCase
*/ */
public function testConstructSavePath($savePath, $expectedSavePath, $path) public function testConstructSavePath($savePath, $expectedSavePath, $path)
{ {
$handler = new NativeFileSessionHandler($savePath); new NativeFileSessionHandler($savePath);
$this->assertEquals($expectedSavePath, ini_get('session.save_path')); $this->assertEquals($expectedSavePath, ini_get('session.save_path'));
$this->assertDirectoryExists(realpath($path)); $this->assertDirectoryExists(realpath($path));
@ -61,13 +61,13 @@ class NativeFileSessionHandlerTest extends TestCase
public function testConstructException() public function testConstructException()
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$handler = new NativeFileSessionHandler('something;invalid;with;too-many-args'); new NativeFileSessionHandler('something;invalid;with;too-many-args');
} }
public function testConstructDefault() public function testConstructDefault()
{ {
$path = ini_get('session.save_path'); $path = ini_get('session.save_path');
$storage = new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler()); new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler());
$this->assertEquals($path, ini_get('session.save_path')); $this->assertEquals($path, ini_get('session.save_path'));
} }

View File

@ -28,7 +28,7 @@ class NullSessionHandlerTest extends TestCase
{ {
public function testSaveHandlers() public function testSaveHandlers()
{ {
$storage = $this->getStorage(); $this->getStorage();
$this->assertEquals('user', ini_get('session.save_handler')); $this->assertEquals('user', ini_get('session.save_handler'));
} }

View File

@ -54,7 +54,7 @@ class PdoSessionHandlerTest extends TestCase
$pdo = $this->getMemorySqlitePdo(); $pdo = $this->getMemorySqlitePdo();
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
$storage = new PdoSessionHandler($pdo); new PdoSessionHandler($pdo);
} }
public function testInexistentTable() public function testInexistentTable()

View File

@ -142,7 +142,7 @@ class NativeSessionStorageTest extends TestCase
{ {
$this->iniSet('session.cache_limiter', 'nocache'); $this->iniSet('session.cache_limiter', 'nocache');
$storage = new NativeSessionStorage(); new NativeSessionStorage();
$this->assertEquals('', ini_get('session.cache_limiter')); $this->assertEquals('', ini_get('session.cache_limiter'));
} }
@ -150,7 +150,7 @@ class NativeSessionStorageTest extends TestCase
{ {
$this->iniSet('session.cache_limiter', 'nocache'); $this->iniSet('session.cache_limiter', 'nocache');
$storage = new NativeSessionStorage(['cache_limiter' => 'public']); new NativeSessionStorage(['cache_limiter' => 'public']);
$this->assertEquals('public', ini_get('session.cache_limiter')); $this->assertEquals('public', ini_get('session.cache_limiter'));
} }

View File

@ -49,7 +49,6 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
} }
} }
$content = null;
try { try {
$content = $request->getContent(); $content = $request->getContent();
} catch (\LogicException $e) { } catch (\LogicException $e) {
@ -59,7 +58,6 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
$sessionMetadata = []; $sessionMetadata = [];
$sessionAttributes = []; $sessionAttributes = [];
$session = null;
$flashes = []; $flashes = [];
if ($request->hasSession()) { if ($request->hasSession()) {
$session = $request->getSession(); $session = $request->getSession();

View File

@ -130,7 +130,6 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
$response->headers->set('Cache-Control', implode(', ', array_keys($flags))); $response->headers->set('Cache-Control', implode(', ', array_keys($flags)));
$maxAge = null; $maxAge = null;
$sMaxage = null;
if (is_numeric($this->ageDirectives['max-age'])) { if (is_numeric($this->ageDirectives['max-age'])) {
$maxAge = $this->ageDirectives['max-age'] + $this->age; $maxAge = $this->ageDirectives['max-age'] + $this->age;

View File

@ -256,7 +256,7 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase
public function testControllersAreMadePublic() public function testControllersAreMadePublic()
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$resolver = $container->register('argument_resolver.service')->addArgument([]); $container->register('argument_resolver.service')->addArgument([]);
$container->register('foo', ArgumentWithoutTypeController::class) $container->register('foo', ArgumentWithoutTypeController::class)
->setPublic(false) ->setPublic(false)

View File

@ -52,7 +52,7 @@ class StoreTest extends TestCase
public function testUnlockFileThatDoesExist() public function testUnlockFileThatDoesExist()
{ {
$cacheKey = $this->storeSimpleEntry(); $this->storeSimpleEntry();
$this->store->lock($this->request); $this->store->lock($this->request);
$this->assertTrue($this->store->unlock($this->request)); $this->assertTrue($this->store->unlock($this->request));
@ -92,7 +92,7 @@ class StoreTest extends TestCase
{ {
$cacheKey = $this->storeSimpleEntry(); $cacheKey = $this->storeSimpleEntry();
$entries = $this->getStoreMetadata($cacheKey); $entries = $this->getStoreMetadata($cacheKey);
list($req, $res) = $entries[0]; list(, $res) = $entries[0];
$this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]);
} }
@ -208,7 +208,7 @@ class StoreTest extends TestCase
{ {
$req1 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']); $req1 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']);
$res1 = new Response('test 1', 200, ['Vary' => 'Foo Bar']); $res1 = new Response('test 1', 200, ['Vary' => 'Foo Bar']);
$key = $this->store->write($req1, $res1); $this->store->write($req1, $res1);
$this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent());
$req2 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam']); $req2 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam']);
@ -229,7 +229,7 @@ class StoreTest extends TestCase
$req = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']); $req = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']);
$this->assertTrue($this->store->lock($req)); $this->assertTrue($this->store->lock($req));
$path = $this->store->lock($req); $this->store->lock($req);
$this->assertTrue($this->store->isLocked($req)); $this->assertTrue($this->store->isLocked($req));
$this->store->unlock($req); $this->store->unlock($req);

View File

@ -19,7 +19,7 @@ class RouteTest extends TestCase
public function testInvalidRouteParameter() public function testInvalidRouteParameter()
{ {
$this->expectException('BadMethodCallException'); $this->expectException('BadMethodCallException');
$route = new Route(['foo' => 'bar']); new Route(['foo' => 'bar']);
} }
public function testTryingToSetLocalesDirectly() public function testTryingToSetLocalesDirectly()

View File

@ -6,7 +6,7 @@ trait FooTrait
{ {
public function doBar() public function doBar()
{ {
$baz = self::class; self::class;
if (true) { if (true) {
} }
} }

View File

@ -194,7 +194,7 @@ class CompiledUrlGeneratorDumperTest extends TestCase
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump()); file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
$projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext()); $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
$url = $projectUrlGenerator->generate('NonExisting', []); $projectUrlGenerator->generate('NonExisting', []);
} }
public function testDumpForRouteWithDefaults() public function testDumpForRouteWithDefaults()

View File

@ -247,7 +247,7 @@ class RouteCompilerTest extends TestCase
$this->expectException('LogicException'); $this->expectException('LogicException');
$route = new Route('/{name}/{name}'); $route = new Route('/{name}/{name}');
$compiled = $route->compile(); $route->compile();
} }
public function testRouteCharsetMismatch() public function testRouteCharsetMismatch()
@ -255,7 +255,7 @@ class RouteCompilerTest extends TestCase
$this->expectException('LogicException'); $this->expectException('LogicException');
$route = new Route("/\xE9/{bar}", [], ['bar' => '.'], ['utf8' => true]); $route = new Route("/\xE9/{bar}", [], ['bar' => '.'], ['utf8' => true]);
$compiled = $route->compile(); $route->compile();
} }
public function testRequirementCharsetMismatch() public function testRequirementCharsetMismatch()
@ -263,7 +263,7 @@ class RouteCompilerTest extends TestCase
$this->expectException('LogicException'); $this->expectException('LogicException');
$route = new Route('/foo/{bar}', [], ['bar' => "\xE9"], ['utf8' => true]); $route = new Route('/foo/{bar}', [], ['bar' => "\xE9"], ['utf8' => true]);
$compiled = $route->compile(); $route->compile();
} }
public function testRouteWithFragmentAsPathParameter() public function testRouteWithFragmentAsPathParameter()
@ -271,7 +271,7 @@ class RouteCompilerTest extends TestCase
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
$route = new Route('/{_fragment}'); $route = new Route('/{_fragment}');
$compiled = $route->compile(); $route->compile();
} }
/** /**

View File

@ -61,6 +61,7 @@ CHANGELOG
* Marked all dispatched event classes as `@final` * Marked all dispatched event classes as `@final`
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`.
* Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()`
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
4.3.0 4.3.0
----- -----

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Core\Encoder; namespace Symfony\Component\Security\Core\Encoder;
use Symfony\Component\Security\Core\Exception\LogicException;
/** /**
* A generic encoder factory implementation. * A generic encoder factory implementation.
* *
@ -114,6 +116,12 @@ class EncoderFactory implements EncoderFactoryInterface
], ],
]; ];
case 'bcrypt':
$config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_BCRYPT;
return $this->getEncoderConfigFromAlgorithm($config);
case 'native': case 'native':
return [ return [
'class' => NativePasswordEncoder::class, 'class' => NativePasswordEncoder::class,
@ -121,7 +129,7 @@ class EncoderFactory implements EncoderFactoryInterface
$config['time_cost'] ?? null, $config['time_cost'] ?? null,
(($config['memory_cost'] ?? 0) << 10) ?: null, (($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'] ?? null, $config['cost'] ?? null,
], ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
]; ];
case 'sodium': case 'sodium':
@ -132,6 +140,30 @@ class EncoderFactory implements EncoderFactoryInterface
(($config['memory_cost'] ?? 0) << 10) ?: null, (($config['memory_cost'] ?? 0) << 10) ?: null,
], ],
]; ];
case 'argon2i':
if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2I')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_ARGON2I;
} else {
throw new LogicException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : ''));
}
return $this->getEncoderConfigFromAlgorithm($config);
case 'argon2id':
if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2ID')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_ARGON2ID;
} else {
throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : ''));
}
return $this->getEncoderConfigFromAlgorithm($config);
} }
return [ return [

View File

@ -22,7 +22,8 @@ class MessageDigestPasswordEncoder extends BasePasswordEncoder
{ {
private $algorithm; private $algorithm;
private $encodeHashAsBase64; private $encodeHashAsBase64;
private $iterations; private $iterations = 1;
private $encodedLength = -1;
/** /**
* @param string $algorithm The digest algorithm to use * @param string $algorithm The digest algorithm to use
@ -33,6 +34,13 @@ class MessageDigestPasswordEncoder extends BasePasswordEncoder
{ {
$this->algorithm = $algorithm; $this->algorithm = $algorithm;
$this->encodeHashAsBase64 = $encodeHashAsBase64; $this->encodeHashAsBase64 = $encodeHashAsBase64;
try {
$this->encodedLength = \strlen($this->encodePassword('', 'salt'));
} catch (\LogicException $e) {
// ignore algorithm not supported
}
$this->iterations = $iterations; $this->iterations = $iterations;
} }
@ -65,6 +73,10 @@ class MessageDigestPasswordEncoder extends BasePasswordEncoder
*/ */
public function isPasswordValid(string $encoded, string $raw, ?string $salt) public function isPasswordValid(string $encoded, string $raw, ?string $salt)
{ {
if (\strlen($encoded) !== $this->encodedLength || false !== strpos($encoded, '$')) {
return false;
}
return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
} }
} }

View File

@ -27,7 +27,10 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
private $algo; private $algo;
private $options; private $options;
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null) /**
* @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm
*/
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, string $algo = null)
{ {
$cost = $cost ?? 13; $cost = $cost ?? 13;
$opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4);
@ -45,7 +48,7 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); throw new \InvalidArgumentException('$cost must be in the range of 4-31.');
} }
$this->algo = \defined('PASSWORD_ARGON2I') ? max(PASSWORD_DEFAULT, \defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I) : PASSWORD_DEFAULT; $this->algo = (string) ($algo ?? \defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : (\defined('PASSWORD_ARGON2I') ? PASSWORD_ARGON2I : PASSWORD_BCRYPT));
$this->options = [ $this->options = [
'cost' => $cost, 'cost' => $cost,
'time_cost' => $opsLimit, 'time_cost' => $opsLimit,
@ -59,20 +62,13 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
*/ */
public function encodePassword(string $raw, ?string $salt): string public function encodePassword(string $raw, ?string $salt): string
{ {
if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { if (\strlen($raw) > self::MAX_PASSWORD_LENGTH || ((string) PASSWORD_BCRYPT === $this->algo && 72 < \strlen($raw))) {
throw new BadCredentialsException('Invalid password.'); throw new BadCredentialsException('Invalid password.');
} }
// Ignore $salt, the auto-generated one is always the best // Ignore $salt, the auto-generated one is always the best
$encoded = password_hash($raw, $this->algo, $this->options); return password_hash($raw, $this->algo, $this->options);
if (72 < \strlen($raw) && 0 === strpos($encoded, '$2')) {
// BCrypt encodes only the first 72 chars
throw new BadCredentialsException('Invalid password.');
}
return $encoded;
} }
/** /**
@ -80,12 +76,24 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
*/ */
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
{ {
if (72 < \strlen($raw) && 0 === strpos($encoded, '$2')) { if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
// BCrypt encodes only the first 72 chars
return false; return false;
} }
return \strlen($raw) <= self::MAX_PASSWORD_LENGTH && password_verify($raw, $encoded); if (0 === strpos($encoded, '$2')) {
// BCrypt encodes only the first 72 chars
return 72 >= \strlen($raw) && password_verify($raw, $encoded);
}
if (\extension_loaded('sodium') && version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) {
return sodium_crypto_pwhash_str_verify($encoded, $raw);
}
if (\extension_loaded('libsodium') && version_compare(phpversion('libsodium'), '1.0.14', '>=')) {
return \Sodium\crypto_pwhash_str_verify($encoded, $raw);
}
return password_verify($raw, $encoded);
} }
/** /**

View File

@ -30,8 +30,9 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder
{ {
private $algorithm; private $algorithm;
private $encodeHashAsBase64; private $encodeHashAsBase64;
private $iterations; private $iterations = 1;
private $length; private $length;
private $encodedLength = -1;
/** /**
* @param string $algorithm The digest algorithm to use * @param string $algorithm The digest algorithm to use
@ -43,8 +44,15 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder
{ {
$this->algorithm = $algorithm; $this->algorithm = $algorithm;
$this->encodeHashAsBase64 = $encodeHashAsBase64; $this->encodeHashAsBase64 = $encodeHashAsBase64;
$this->iterations = $iterations;
$this->length = $length; $this->length = $length;
try {
$this->encodedLength = \strlen($this->encodePassword('', 'salt'));
} catch (\LogicException $e) {
// ignore algorithm not supported
}
$this->iterations = $iterations;
} }
/** /**
@ -72,6 +80,10 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder
*/ */
public function isPasswordValid(string $encoded, string $raw, ?string $salt) public function isPasswordValid(string $encoded, string $raw, ?string $salt)
{ {
if (\strlen($encoded) !== $this->encodedLength || false !== strpos($encoded, '$')) {
return false;
}
return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
} }
} }

View File

@ -93,7 +93,7 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti
return \Sodium\crypto_pwhash_str_verify($encoded, $raw); return \Sodium\crypto_pwhash_str_verify($encoded, $raw);
} }
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.'); return false;
} }
/** /**

View File

@ -117,7 +117,7 @@ class EncoderFactoryTest extends TestCase
$user = new EncAwareUser('user', 'pass'); $user = new EncAwareUser('user', 'pass');
$user->encoderName = 'invalid_encoder_name'; $user->encoderName = 'invalid_encoder_name';
$encoder = $factory->getEncoder($user); $factory->getEncoder($user);
} }
public function testGetEncoderForEncoderAwareWithClassName() public function testGetEncoderForEncoderAwareWithClassName()

View File

@ -55,6 +55,14 @@ class NativePasswordEncoderTest extends TestCase
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
} }
public function testConfiguredAlgorithm()
{
$encoder = new NativePasswordEncoder(null, null, null, PASSWORD_BCRYPT);
$result = $encoder->encodePassword('password', null);
$this->assertTrue($encoder->isPasswordValid($result, 'password', null));
$this->assertStringStartsWith('$2', $result);
}
public function testCheckPasswordLength() public function testCheckPasswordLength()
{ {
$encoder = new NativePasswordEncoder(null, null, 4); $encoder = new NativePasswordEncoder(null, null, 4);

View File

@ -131,7 +131,7 @@ class GuardAuthenticationProviderTest extends TestCase
$token->setAuthenticated(false); $token->setAuthenticated(false);
$provider = new GuardAuthenticationProvider([], $this->userProvider, $providerKey, $this->userChecker); $provider = new GuardAuthenticationProvider([], $this->userProvider, $providerKey, $this->userChecker);
$actualToken = $provider->authenticate($token); $provider->authenticate($token);
} }
public function testSupportsChecksGuardAuthenticatorsTokenOrigin() public function testSupportsChecksGuardAuthenticatorsTokenOrigin()

View File

@ -21,7 +21,7 @@ class LogoutListenerTest extends TestCase
{ {
public function testHandleUnmatchedPath() public function testHandleUnmatchedPath()
{ {
list($listener, $tokenStorage, $httpUtils, $options) = $this->getListener(); list($listener, , $httpUtils, $options) = $this->getListener();
list($event, $request) = $this->getGetResponseEvent(); list($event, $request) = $this->getGetResponseEvent();
@ -131,7 +131,7 @@ class LogoutListenerTest extends TestCase
$this->expectException('RuntimeException'); $this->expectException('RuntimeException');
$successHandler = $this->getSuccessHandler(); $successHandler = $this->getSuccessHandler();
list($listener, $tokenStorage, $httpUtils, $options) = $this->getListener($successHandler); list($listener, , $httpUtils, $options) = $this->getListener($successHandler);
list($event, $request) = $this->getGetResponseEvent(); list($event, $request) = $this->getGetResponseEvent();
@ -153,7 +153,7 @@ class LogoutListenerTest extends TestCase
$this->expectException('Symfony\Component\Security\Core\Exception\LogoutException'); $this->expectException('Symfony\Component\Security\Core\Exception\LogoutException');
$tokenManager = $this->getTokenManager(); $tokenManager = $this->getTokenManager();
list($listener, $tokenStorage, $httpUtils, $options) = $this->getListener(null, $tokenManager); list($listener, , $httpUtils, $options) = $this->getListener(null, $tokenManager);
list($event, $request) = $this->getGetResponseEvent(); list($event, $request) = $this->getGetResponseEvent();

View File

@ -224,7 +224,7 @@ class RememberMeListenerTest extends TestCase
public function testSessionStrategy() public function testSessionStrategy()
{ {
list($listener, $tokenStorage, $service, $manager, , $dispatcher, $sessionStrategy) = $this->getListener(false, true, true); list($listener, $tokenStorage, $service, $manager, , , $sessionStrategy) = $this->getListener(false, true, true);
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->once())
@ -289,7 +289,7 @@ class RememberMeListenerTest extends TestCase
public function testSessionIsMigratedByDefault() public function testSessionIsMigratedByDefault()
{ {
list($listener, $tokenStorage, $service, $manager, , $dispatcher, $sessionStrategy) = $this->getListener(false, true, false); list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, true, false);
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->once())

View File

@ -60,7 +60,7 @@ class RemoteUserAuthenticationListenerTest extends TestCase
$method = new \ReflectionMethod($listener, 'getPreAuthenticatedData'); $method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
$method->setAccessible(true); $method->setAccessible(true);
$result = $method->invokeArgs($listener, [$request]); $method->invokeArgs($listener, [$request]);
} }
public function testGetPreAuthenticatedDataWithDifferentKeys() public function testGetPreAuthenticatedDataWithDifferentKeys()

View File

@ -98,7 +98,7 @@ class X509AuthenticationListenerTest extends TestCase
$method = new \ReflectionMethod($listener, 'getPreAuthenticatedData'); $method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
$method->setAccessible(true); $method->setAccessible(true);
$result = $method->invokeArgs($listener, [$request]); $method->invokeArgs($listener, [$request]);
} }
public function testGetPreAuthenticatedDataWithDifferentKeys() public function testGetPreAuthenticatedDataWithDifferentKeys()

View File

@ -66,8 +66,6 @@ class ResponseListenerTest extends TestCase
public function testItSubscribesToTheOnKernelResponseEvent() public function testItSubscribesToTheOnKernelResponseEvent()
{ {
$listener = new ResponseListener();
$this->assertSame([KernelEvents::RESPONSE => 'onKernelResponse'], ResponseListener::getSubscribedEvents()); $this->assertSame([KernelEvents::RESPONSE => 'onKernelResponse'], ResponseListener::getSubscribedEvents());
} }

View File

@ -27,7 +27,6 @@ class FilesystemLoaderTest extends TestCase
public function testConstructor() public function testConstructor()
{ {
$pathPattern = self::$fixturesPath.'/templates/%name%.%engine%'; $pathPattern = self::$fixturesPath.'/templates/%name%.%engine%';
$path = self::$fixturesPath.'/templates';
$loader = new ProjectTemplateLoader2($pathPattern); $loader = new ProjectTemplateLoader2($pathPattern);
$this->assertEquals([$pathPattern], $loader->getTemplatePathPatterns(), '__construct() takes a path as its second argument'); $this->assertEquals([$pathPattern], $loader->getTemplatePathPatterns(), '__construct() takes a path as its second argument');
$loader = new ProjectTemplateLoader2([$pathPattern]); $loader = new ProjectTemplateLoader2([$pathPattern]);

View File

@ -61,7 +61,7 @@ class FileValidator extends ConstraintValidator
$binaryFormat = null === $constraint->binaryFormat ? true : $constraint->binaryFormat; $binaryFormat = null === $constraint->binaryFormat ? true : $constraint->binaryFormat;
} }
list($sizeAsString, $limitAsString, $suffix) = $this->factorizeSizes(0, $limitInBytes, $binaryFormat); list(, $limitAsString, $suffix) = $this->factorizeSizes(0, $limitInBytes, $binaryFormat);
$this->context->buildViolation($constraint->uploadIniSizeErrorMessage) $this->context->buildViolation($constraint->uploadIniSizeErrorMessage)
->setParameter('{{ limit }}', $limitAsString) ->setParameter('{{ limit }}', $limitAsString)
->setParameter('{{ suffix }}', $suffix) ->setParameter('{{ suffix }}', $suffix)

View File

@ -456,7 +456,7 @@ abstract class FileValidatorTest extends ConstraintValidatorTestCase
$reflection = new \ReflectionClass(\get_class(new FileValidator())); $reflection = new \ReflectionClass(\get_class(new FileValidator()));
$method = $reflection->getMethod('factorizeSizes'); $method = $reflection->getMethod('factorizeSizes');
$method->setAccessible(true); $method->setAccessible(true);
list($sizeAsString, $limit, $suffix) = $method->invokeArgs(new FileValidator(), [0, UploadedFile::getMaxFilesize(), false]); list(, $limit, $suffix) = $method->invokeArgs(new FileValidator(), [0, UploadedFile::getMaxFilesize(), false]);
// it correctly parses the maxSize option and not only uses simple string comparison // it correctly parses the maxSize option and not only uses simple string comparison
// 1000M should be bigger than the ini value // 1000M should be bigger than the ini value

View File

@ -32,6 +32,7 @@ class PropertyPathTest extends TestCase
['foo', 'bar', 'foo.bar', 'It append the subPath to the basePath'], ['foo', 'bar', 'foo.bar', 'It append the subPath to the basePath'],
['foo', '[bar]', 'foo[bar]', 'It does not include the dot separator if subPath uses the array notation'], ['foo', '[bar]', 'foo[bar]', 'It does not include the dot separator if subPath uses the array notation'],
['0', 'bar', '0.bar', 'Leading zeros are kept.'], ['0', 'bar', '0.bar', 'Leading zeros are kept.'],
['0', 1, '0.1', 'Numeric subpaths do not cause PHP 7.4 errors.'],
]; ];
} }
} }

View File

@ -38,7 +38,7 @@ class DefinitionTest extends TestCase
{ {
$this->expectException('Symfony\Component\Workflow\Exception\LogicException'); $this->expectException('Symfony\Component\Workflow\Exception\LogicException');
$this->expectExceptionMessage('Place "d" cannot be the initial place as it does not exist.'); $this->expectExceptionMessage('Place "d" cannot be the initial place as it does not exist.');
$definition = new Definition([], [], 'd'); new Definition([], [], 'd');
} }
public function testAddTransition() public function testAddTransition()

View File

@ -7,6 +7,13 @@ use Symfony\Component\Workflow\Transition;
class TransitionTest extends TestCase class TransitionTest extends TestCase
{ {
public function testValidateName()
{
$this->expectException('Symfony\Component\Workflow\Exception\InvalidArgumentException');
$this->expectExceptionMessage('The transition "foo.bar" contains invalid characters.');
new Transition('foo.bar', 'a', 'b');
}
public function testConstructor() public function testConstructor()
{ {
$transition = new Transition('name', 'a', 'b'); $transition = new Transition('name', 'a', 'b');

View File

@ -314,7 +314,7 @@ class WorkflowTest extends TestCase
$this->assertFalse($marking->has('b')); $this->assertFalse($marking->has('b'));
$this->assertFalse($marking->has('c')); $this->assertFalse($marking->has('c'));
$marking = $workflow->apply($subject, 'a_to_bc'); $workflow->apply($subject, 'a_to_bc');
$marking = $workflow->apply($subject, 'b_to_c'); $marking = $workflow->apply($subject, 'b_to_c');
$this->assertFalse($marking->has('a')); $this->assertFalse($marking->has('a'));
@ -406,7 +406,7 @@ class WorkflowTest extends TestCase
'workflow.workflow_name.announce.t2', 'workflow.workflow_name.announce.t2',
]; ];
$marking = $workflow->apply($subject, 't1'); $workflow->apply($subject, 't1');
$this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents);
} }
@ -446,7 +446,7 @@ class WorkflowTest extends TestCase
'workflow.workflow_name.announce', 'workflow.workflow_name.announce',
]; ];
$marking = $workflow->apply($subject, 'a-b'); $workflow->apply($subject, 'a-b');
$this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents);
} }

View File

@ -108,14 +108,6 @@ class Parser
return $data; return $data;
} }
/**
* @internal
*/
public function getLastLineNumberBeforeDeprecation(): int
{
return $this->getRealCurrentLineNb();
}
private function doParse(string $value, int $flags) private function doParse(string $value, int $flags)
{ {
$this->currentLineNb = -1; $this->currentLineNb = -1;

View File

@ -97,7 +97,7 @@ YAML;
$filename = $this->createFile(''); $filename = $this->createFile('');
unlink($filename); unlink($filename);
$ret = $tester->execute(['filename' => $filename], ['decorated' => false]); $tester->execute(['filename' => $filename], ['decorated' => false]);
} }
private function createFile($content): string private function createFile($content): string

View File

@ -64,7 +64,7 @@ class ParserTest extends TestCase
foreach ($yamls as $yaml) { foreach ($yamls as $yaml) {
try { try {
$content = $this->parser->parse($yaml); $this->parser->parse($yaml);
$this->fail('YAML files must not contain tabs'); $this->fail('YAML files must not contain tabs');
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -82,6 +82,11 @@ switch ($vars['REQUEST_URI']) {
header('Location: ..', true, 302); header('Location: ..', true, 302);
break; break;
case '/304':
header('Content-Length: 10', true, 304);
echo '12345';
return;
case '/307': case '/307':
header('Location: http://localhost:8057/post', true, 307); header('Location: http://localhost:8057/post', true, 307);
break; break;

View File

@ -72,6 +72,33 @@ abstract class HttpClientTestCase extends TestCase
$response->getContent(); $response->getContent();
} }
public function testHeadRequest()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('HEAD', 'http://localhost:8057', [
'headers' => ['Foo' => 'baR'],
'user_data' => $data = new \stdClass(),
]);
$this->assertSame([], $response->getInfo('response_headers'));
$this->assertSame($data, $response->getInfo()['user_data']);
$this->assertSame(200, $response->getStatusCode());
$info = $response->getInfo();
$this->assertNull($info['error']);
$this->assertSame(0, $info['redirect_count']);
$this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]);
$this->assertSame('Host: localhost:8057', $info['response_headers'][1]);
$this->assertSame('http://localhost:8057/', $info['url']);
$headers = $response->getHeaders();
$this->assertSame('localhost:8057', $headers['host'][0]);
$this->assertSame(['application/json'], $headers['content-type']);
$this->assertSame('', $response->getContent());
}
public function testNonBufferedGetRequest() public function testNonBufferedGetRequest()
{ {
$client = $this->getHttpClient(__FUNCTION__); $client = $this->getHttpClient(__FUNCTION__);
@ -297,6 +324,17 @@ abstract class HttpClientTestCase extends TestCase
$response->getStatusCode(); $response->getStatusCode();
} }
public function test304()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/304', [
'headers' => ['If-Match' => '"abc"'],
]);
$this->assertSame(304, $response->getStatusCode());
$this->assertSame('', $response->getContent(false));
}
public function testRedirects() public function testRedirects()
{ {
$client = $this->getHttpClient(__FUNCTION__); $client = $this->getHttpClient(__FUNCTION__);