Merge branch '4.3' into 4.4

* 4.3:
  [Debug] fix ClassNotFoundFatalErrorHandler
  [Routing] Fix using a custom matcher & generator dumper class
  [Dotenv] Fixed infinite loop with missing quote followed by quoted value
  [HttpClient] Added missing sprintf
  [TwigBridge] button_widget now has its title attr translated even if its label = null or false
  [PhpUnitBridge] When using phpenv + phpenv-composer plugin, composer executable is wrapped into a bash script
  [Messenger] Added check if json_encode succeeded
  [Security] Prevent canceled remember-me cookie from being accepted
  [FrameworkBundle][TranslationUpdateCommand] Do not output positive feedback on stderr
  [Security\Guard] Fix missing typehints
This commit is contained in:
Nicolas Grekas 2020-01-08 18:29:02 +01:00
commit 80cd480254
18 changed files with 61 additions and 37 deletions

View File

@ -243,6 +243,7 @@ FrameworkBundle
* Removed `routing.loader.service`.
* Added support for PHPUnit 8. A `void` return-type was added to the `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` method.
* Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services.
* Removed the `router.cache_class_prefix` parameter.
HttpClient
----------

View File

@ -106,7 +106,7 @@ $COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar')
|| ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`))
|| ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer`) : `which composer 2> /dev/null`))
|| file_exists($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `git rev-parse --show-toplevel 2> NUL` : `git rev-parse --show-toplevel 2> /dev/null`).DIRECTORY_SEPARATOR.'composer.phar')
? $PHP.' '.escapeshellarg($COMPOSER)
? (file_get_contents($COMPOSER, null, 0, 18) === '#!/usr/bin/env php' ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang
: 'composer';
$SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml': ''));

View File

@ -228,13 +228,11 @@
'%name%': name,
'%id%': id,
}) %}
{%- elseif label is same as(false) -%}
{% set translation_domain = false %}
{%- else -%}
{%- elseif label is not same as(false) -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ translation_domain is same as(false) ? label : label|trans(label_translation_parameters, translation_domain) }}</button>
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ translation_domain is same as(false) or label is same as(false) ? label : label|trans(label_translation_parameters, translation_domain) }}</button>
{%- endblock button_widget -%}
{%- block submit_widget -%}

View File

@ -213,12 +213,12 @@ EOF
}
}
$errorIo->title('Translation Messages Extractor and Dumper');
$errorIo->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
$io->title('Translation Messages Extractor and Dumper');
$io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
// load any messages from templates
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
$errorIo->comment('Parsing templates...');
$io->comment('Parsing templates...');
$this->extractor->setPrefix($input->getOption('prefix'));
foreach ($viewsPaths as $path) {
if (is_dir($path) || is_file($path)) {
@ -228,7 +228,7 @@ EOF
// load any existing messages from the translation files
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$errorIo->comment('Loading translation files...');
$io->comment('Loading translation files...');
foreach ($transPaths as $path) {
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
@ -296,7 +296,7 @@ EOF
}
if ('xlf' === $input->getOption('output-format')) {
$errorIo->comment(sprintf('Xliff output version is <info>%s</info>', $input->getOption('xliff-version')));
$io->comment(sprintf('Xliff output version is <info>%s</info>', $input->getOption('xliff-version')));
}
$resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was');
@ -308,7 +308,7 @@ EOF
// save the files
if (true === $input->getOption('force')) {
$errorIo->comment('Writing files...');
$io->comment('Writing files...');
$bundleTransPath = false;
foreach ($transPaths as $path) {
@ -328,7 +328,7 @@ EOF
}
}
$errorIo->success($resultMessage.'.');
$io->success($resultMessage.'.');
return 0;
}

View File

@ -33,7 +33,7 @@ class ClearRememberMeTest extends AbstractWebTestCase
$this->assertNotNull($cookieJar->get('REMEMBERME'));
$client->request('GET', '/foo');
$this->assertSame(200, $client->getResponse()->getStatusCode());
$this->assertRedirect($client->getResponse(), '/login');
$this->assertNull($cookieJar->get('REMEMBERME'));
}
}

View File

@ -24,7 +24,7 @@
"symfony/security-core": "^4.4",
"symfony/security-csrf": "^4.2|^5.0",
"symfony/security-guard": "^4.2|^5.0",
"symfony/security-http": "^4.4.1"
"symfony/security-http": "^4.4.3"
},
"require-dev": {
"doctrine/doctrine-bundle": "^1.5|^2.0",

View File

@ -32,6 +32,10 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
if (!\is_array($function)) {
continue;
}
}
if ($function[0] instanceof ComposerClassLoader) {

View File

@ -277,7 +277,10 @@ final class Dotenv
$this->cursor += 1 + $len;
} elseif ('"' === $this->data[$this->cursor]) {
$value = '';
++$this->cursor;
if (++$this->cursor === $this->end) {
throw $this->createFormatException('Missing quote to end the value');
}
while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) {
$value .= $this->data[$this->cursor];

View File

@ -40,6 +40,7 @@ class DotenvTest extends TestCase
['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"],
['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"],
['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"],
["FOO=\"foo\nBAR=\"bar\"", "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo\\nBAR=\"bar\"...\n ^ line 1 offset 18"],
['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 9"],
['export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"],
['FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"],

View File

@ -30,6 +30,10 @@ class ClassNotFoundErrorEnhancerTest extends TestCase
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
if (!\is_array($function)) {
continue;
}
}
if ($function[0] instanceof ComposerClassLoader) {

View File

@ -353,7 +353,7 @@ trait ResponseTrait
}
if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) {
$multi->handlesActivity[$j] = [null, new TransportException('Failed writing %d bytes to the response buffer.', \strlen($chunk))];
$multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))];
continue;
}

View File

@ -189,9 +189,7 @@ class ConnectionTest extends TestCase
public function testJsonError()
{
$redis = new \Redis();
$connection = Connection::fromDsn('redis://localhost/json-error', [], $redis);
try {
$connection->add("\xB1\x31", []);
} catch (TransportException $e) {

View File

@ -285,7 +285,7 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->matcher;
}
$compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && (UrlMatcher::class === $this->options['matcher_base_class'] || RedirectableUrlMatcher::class === $this->options['matcher_base_class']);
$compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && (UrlMatcher::class === $this->options['matcher_base_class'] || RedirectableUrlMatcher::class === $this->options['matcher_base_class']) && is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true);
if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) {
$routes = $this->getRouteCollection();
@ -342,7 +342,7 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->generator;
}
$compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && UrlGenerator::class === $this->options['generator_base_class'];
$compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && UrlGenerator::class === $this->options['generator_base_class'] && is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true);
if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) {
$routes = $this->getRouteCollection();
@ -392,8 +392,8 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
protected function getGeneratorDumperInstance()
{
// For BC, fallback to PhpGeneratorDumper if the UrlGenerator and UrlGeneratorDumper are not consistent with each other
if (is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) !== is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true)) {
// For BC, fallback to PhpGeneratorDumper (which is the old default value) if the old UrlGenerator is used with the new default CompiledUrlGeneratorDumper
if (!is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true)) {
return new PhpGeneratorDumper($this->getRouteCollection());
}
@ -405,8 +405,8 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
protected function getMatcherDumperInstance()
{
// For BC, fallback to PhpMatcherDumper if the UrlMatcher and UrlMatcherDumper are not consistent with each other
if (is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) !== is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true)) {
// For BC, fallback to PhpMatcherDumper (which is the old default value) if the old UrlMatcher is used with the new default CompiledUrlMatcherDumper
if (!is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true)) {
return new PhpMatcherDumper($this->getRouteCollection());
}

View File

@ -49,7 +49,7 @@ class GuardAuthenticationListener extends AbstractListener implements ListenerIn
* @param string $providerKey The provider (i.e. firewall) key
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
*/
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, $guardAuthenticators, LoggerInterface $logger = null)
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, LoggerInterface $logger = null)
{
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');

View File

@ -48,7 +48,7 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener
* @param string $providerKey The provider (i.e. firewall) key
*/
public function __construct($guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, UserPasswordEncoderInterface $passwordEncoder = null)
public function __construct(iterable $guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, UserPasswordEncoderInterface $passwordEncoder = null)
{
$this->guardAuthenticators = $guardAuthenticators;
$this->userProvider = $userProvider;

View File

@ -94,6 +94,10 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface
*/
final public function autoLogin(Request $request): ?TokenInterface
{
if (($cookie = $request->attributes->get(self::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) {
return null;
}
if (null === $cookie = $request->cookies->get($this->options['name'])) {
return null;
}

View File

@ -39,6 +39,17 @@ class AbstractRememberMeServicesTest extends TestCase
$this->assertNull($service->autoLogin(new Request()));
}
public function testAutoLoginReturnsNullAfterLoginFail()
{
$service = $this->getService(null, ['name' => 'foo', 'path' => null, 'domain' => null]);
$request = new Request();
$request->cookies->set('foo', 'foo');
$service->loginFail($request);
$this->assertNull($service->autoLogin($request));
}
public function testAutoLoginThrowsExceptionWhenImplementationDoesNotReturnUserInterface()
{
$this->expectException('RuntimeException');