From a0a6243a21e0bcae2cbcfbe80fa9a65a080040fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4dlich?= Date: Tue, 17 Mar 2020 07:26:07 +0100 Subject: [PATCH 01/12] Fix deprecation messages --- src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php | 2 +- .../Security/Factory/FormLoginLdapFactory.php | 2 +- .../Security/Factory/HttpBasicLdapFactory.php | 2 +- .../Security/Factory/JsonLoginLdapFactory.php | 2 +- .../Authentication/Provider/LdapBindAuthenticationProvider.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php index e35d691fc3..b99a5fb13e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php @@ -42,7 +42,7 @@ abstract class WebTestCase extends KernelTestCase protected static function createClient(array $options = [], array $server = []) { if (static::$booted) { - @trigger_error(sprintf('Calling "%s()" while a kernel has been booted is deprecated since Symfony 4.4 and will throw in 5.0, ensure the kernel is shut down before calling the method.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Calling "%s()" while a kernel has been booted is deprecated since Symfony 4.4 and will throw an exception in 5.0, ensure the kernel is shut down before calling the method.', __METHOD__), E_USER_DEPRECATED); } $kernel = static::bootKernel($options); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php index d7b53e5cf4..a0cb0227ae 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php @@ -40,7 +40,7 @@ class FormLoginLdapFactory extends FormLoginFactory if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { - @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw in Symfony 5.0.', E_USER_DEPRECATED); + @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw an exception in Symfony 5.0.', E_USER_DEPRECATED); } $definition->addMethodCall('setQueryString', [$config['query_string']]); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php index 09933a9db9..b14a4f67ac 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php @@ -44,7 +44,7 @@ class HttpBasicLdapFactory extends HttpBasicFactory if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { - @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw in Symfony 5.0.', E_USER_DEPRECATED); + @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw an exception in Symfony 5.0.', E_USER_DEPRECATED); } $definition->addMethodCall('setQueryString', [$config['query_string']]); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php index 95f3f55858..17c60e12bd 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php @@ -42,7 +42,7 @@ class JsonLoginLdapFactory extends JsonLoginFactory if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { - @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw in Symfony 5.0.', E_USER_DEPRECATED); + @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw an exception in Symfony 5.0.', E_USER_DEPRECATED); } $definition->addMethodCall('setQueryString', [$config['query_string']]); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php index 79276847f6..2c9dc44a17 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php @@ -89,7 +89,7 @@ class LdapBindAuthenticationProvider extends UserAuthenticationProvider if ('' !== $this->searchDn && '' !== $this->searchPassword) { $this->ldap->bind($this->searchDn, $this->searchPassword); } else { - @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw in Symfony 5.0.', E_USER_DEPRECATED); + @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw an exception in Symfony 5.0.', E_USER_DEPRECATED); } $query = str_replace('{username}', $username, $this->queryString); $result = $this->ldap->query($this->dnString, $query)->execute(); From c6ace13e3439e26a5d7394f2aa0a873859b112c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20P=C3=A9delagrabe?= Date: Thu, 19 Mar 2020 14:13:39 +0100 Subject: [PATCH 02/12] [FrameworkBundle] Fix Router Cache --- .../Bundle/FrameworkBundle/Routing/Router.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 8971e5330b..cf2eadbd51 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -15,6 +15,8 @@ use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\DependencyInjection\CompatibilityServiceSubscriberInterface as ServiceSubscriberInterface; use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; @@ -71,6 +73,16 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']); $this->resolveParameters($this->collection); $this->collection->addResource(new ContainerParametersResource($this->collectedParameters)); + + try { + $containerFile = ($this->paramFetcher)('kernel.cache_dir').'/'.($this->paramFetcher)('kernel.container_class').'.php'; + if (file_exists($containerFile)) { + $this->collection->addResource(new FileResource($containerFile)); + } else { + $this->collection->addResource(new FileExistenceResource($containerFile)); + } + } catch (ParameterNotFoundException $exception) { + } } return $this->collection; From d43833a8218311b780e4da777a84aa5ea129eefe Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 19 Mar 2020 13:25:45 +0100 Subject: [PATCH 03/12] Prevent warning in proc_open() --- src/Symfony/Component/Process/Process.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 6672e61253..68d52512ed 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -336,7 +336,7 @@ class Process implements \IteratorAggregate @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED); } - $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); + $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); if (!\is_resource($this->process)) { throw new RuntimeException('Unable to launch a new process.'); From 677429479df3433f45e2bebcef80c0b465800318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 20 Mar 2020 16:02:37 +0100 Subject: [PATCH 04/12] [Security] Fixed hardcoded value of SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE --- .../Component/Security/Core/Encoder/SodiumPasswordEncoder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 0d552d165b..0aa39be80b 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -35,7 +35,7 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti } $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); - $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 2014); + $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); if (3 > $this->opsLimit) { throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); From f618b98b6cbf27ca604ffa7782e75ececee430a1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 22 Mar 2020 21:08:25 +0100 Subject: [PATCH 05/12] [HttpKernel] fix locking for PHP 7.4+ --- src/Symfony/Component/HttpKernel/Kernel.php | 54 +++++---------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index d6f5a462d6..958e049bf0 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -38,9 +38,6 @@ use Symfony\Component\HttpKernel\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; -// Help opcache.preload discover always-needed symbols -class_exists(ConfigCache::class); - /** * The Kernel is the heart of the Symfony system. * @@ -533,47 +530,20 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl try { is_dir($cacheDir) ?: mkdir($cacheDir, 0777, true); - if ($lock = fopen($cachePath, 'w')) { - chmod($cachePath, 0666 & ~umask()); + if ($lock = fopen($cachePath.'.lock', 'w')) { flock($lock, LOCK_EX | LOCK_NB, $wouldBlock); if (!flock($lock, $wouldBlock ? LOCK_SH : LOCK_EX)) { fclose($lock); - } else { - $cache = new class($cachePath, $this->debug) extends ConfigCache { - public $lock; + $lock = null; + } elseif (!\is_object($this->container = include $cachePath)) { + $this->container = null; + } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { + flock($lock, LOCK_UN); + fclose($lock); + $this->container->set('kernel', $this); - public function write($content, array $metadata = null) - { - rewind($this->lock); - ftruncate($this->lock, 0); - fwrite($this->lock, $content); - - if (null !== $metadata) { - file_put_contents($this->getPath().'.meta', serialize($metadata)); - @chmod($this->getPath().'.meta', 0666 & ~umask()); - } - - if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { - @opcache_invalidate($this->getPath(), true); - } - } - - public function release() - { - flock($this->lock, LOCK_UN); - fclose($this->lock); - } - }; - $cache->lock = $lock; - - if (!\is_object($this->container = include $cachePath)) { - $this->container = null; - } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { - $this->container->set('kernel', $this); - - return; - } + return; } } } catch (\Throwable $e) { @@ -637,8 +607,10 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); - if (method_exists($cache, 'release')) { - $cache->release(); + + if ($lock) { + flock($lock, LOCK_UN); + fclose($lock); } $this->container = require $cachePath; From 9c3951ed6956becee176248aac0bde09c81a0310 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 23 Mar 2020 09:19:50 +0100 Subject: [PATCH 06/12] [Mailer] Use %d instead of %s for error code in error messages --- .../Mailer/Bridge/Amazon/Transport/SesApiTransport.php | 2 +- .../Mailer/Bridge/Amazon/Transport/SesHttpTransport.php | 2 +- .../Bridge/Mailchimp/Transport/MandrillApiTransport.php | 4 ++-- .../Bridge/Mailchimp/Transport/MandrillHttpTransport.php | 4 ++-- .../Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php | 4 ++-- .../Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php | 4 ++-- .../Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php | 2 +- .../Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index 22ef23e84b..c9cd0ad8cc 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -65,7 +65,7 @@ class SesApiTransport extends AbstractApiTransport $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result->Error->Message, $result->Error->Code), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result->Error->Message, $result->Error->Code), $response); } $property = $payload['Action'].'Result'; diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index 1bb6c5486e..9cedbdf4c7 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -65,7 +65,7 @@ class SesHttpTransport extends AbstractHttpTransport $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result->Error->Message, $result->Error->Code), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result->Error->Message, $result->Error->Code), $response); } $message->setMessageId($result->SendRawEmailResult->MessageId); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php index af221ca66e..9b7c181b29 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php @@ -51,10 +51,10 @@ class MandrillApiTransport extends AbstractApiTransport $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('error' === ($result['status'] ?? false)) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $result['code']), $response); } - throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response); + throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response); } $firstRecipient = reset($result); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index 6ead154906..92f90b8563 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -58,10 +58,10 @@ class MandrillHttpTransport extends AbstractHttpTransport $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('error' === ($result['status'] ?? false)) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $result['code']), $response); } - throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response); + throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response); } $message->setMessageId($result[0]['_id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php index cf40a8cf1e..c8ed383fcc 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php @@ -65,10 +65,10 @@ class MailgunApiTransport extends AbstractApiTransport $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('application/json' === $response->getHeaders(false)['content-type'][0]) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $response->getStatusCode()), $response); } - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->getContent(false), $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $response->getContent(false), $response->getStatusCode()), $response); } $sentMessage->setMessageId($result['id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php index a42598a0b5..3ed6f73369 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php @@ -67,10 +67,10 @@ class MailgunHttpTransport extends AbstractHttpTransport $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('application/json' === $response->getHeaders(false)['content-type'][0]) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $response->getStatusCode()), $response); } - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $response->getContent(false), $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $response->getContent(false), $response->getStatusCode()), $response); } $message->setMessageId($result['id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index 610f858260..352b2eded1 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -54,7 +54,7 @@ class PostmarkApiTransport extends AbstractApiTransport $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', $result['Message'], $result['ErrorCode']), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['Message'], $result['ErrorCode']), $response); } $sentMessage->setMessageId($result['MessageID']); diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index df90d21472..550fd4bf33 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -53,7 +53,7 @@ class SendgridApiTransport extends AbstractApiTransport if (202 !== $response->getStatusCode()) { $errors = $response->toArray(false); - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %s).', implode('; ', array_column($errors['errors'], 'message')), $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', implode('; ', array_column($errors['errors'], 'message')), $response->getStatusCode()), $response); } $sentMessage->setMessageId($response->getHeaders(false)['x-message-id'][0]); From 69d03400665c98dbf5685b22556d486c2868e647 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 23 Mar 2020 09:29:43 +0100 Subject: [PATCH 07/12] [Validator] Backport translations --- .../Validator/Resources/translations/validators.en.xlf | 8 ++++++++ .../Validator/Resources/translations/validators.es.xlf | 8 ++++++++ .../Validator/Resources/translations/validators.pl.xlf | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 8f8d2d0a0f..674ccf5c30 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -374,6 +374,14 @@ The number of elements in this collection should be a multiple of {{ compared_value }}. The number of elements in this collection should be a multiple of {{ compared_value }}. + + This value should satisfy at least one of the following constraints: + This value should satisfy at least one of the following constraints: + + + Each element of this collection should satisfy its own set of constraints. + Each element of this collection should satisfy its own set of constraints. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index 24ef5548da..57b07b2b55 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -374,6 +374,14 @@ The number of elements in this collection should be a multiple of {{ compared_value }}. El número de elementos en esta colección debería ser múltiplo de {{ compared_value }}. + + This value should satisfy at least one of the following constraints: + Este valor debería satisfacer al menos una de las siguientes restricciones: + + + Each element of this collection should satisfy its own set of constraints. + Cada elemento de esta colección debería satisfacer su propio conjunto de restricciones. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf index df93abd0b9..afd3f73263 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf @@ -374,6 +374,14 @@ The number of elements in this collection should be a multiple of {{ compared_value }}. Liczba elementów w tym zbiorze powinna być wielokrotnością {{ compared_value }}. + + This value should satisfy at least one of the following constraints: + Ta wartość powinna spełniać co najmniej jedną z następujących reguł: + + + Each element of this collection should satisfy its own set of constraints. + Każdy element w tym zbiorze powinien spełniać własny zestaw reguł. + From b3d9a8ac300d6ade6697fbdb7cdd614832f3fef0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 23 Mar 2020 11:22:40 +0100 Subject: [PATCH 08/12] [Debug] fix for PHP 7.3.16+/7.4.4+ --- src/Symfony/Component/Debug/ErrorHandler.php | 4 ++-- src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index dfd63b85c8..40a4b1758c 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -521,7 +521,7 @@ class ErrorHandler $errorAsException ? ['exception' => $errorAsException] : [], ]; } else { - if (!\defined('HHVM_VERSION')) { + if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) { $currentErrorHandler = set_error_handler('var_dump'); restore_error_handler(); } @@ -533,7 +533,7 @@ class ErrorHandler } finally { $this->isRecursive = false; - if (!\defined('HHVM_VERSION')) { + if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) { set_error_handler($currentErrorHandler); } } diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index 2cf75a0e8e..01c63aea88 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -327,8 +327,6 @@ class ErrorHandlerTest extends TestCase $handler = new ErrorHandler(); $handler->setDefaultLogger($logger); @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, []); - - restore_error_handler(); } /** From f0ceb7339786f39edaae6429e435fc2f941d56a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 17 Jul 2018 14:51:24 +0200 Subject: [PATCH 09/12] [Security] Remember me: allow to set the samesite cookie flag --- .../Security/Factory/RememberMeFactory.php | 1 + .../Http/RememberMe/AbstractRememberMeServices.php | 3 ++- .../RememberMe/PersistentTokenBasedRememberMeServices.php | 8 ++++++-- .../Http/RememberMe/TokenBasedRememberMeServices.php | 4 +++- .../PersistentTokenBasedRememberMeServicesTest.php | 4 +++- .../Tests/RememberMe/TokenBasedRememberMeServicesTest.php | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index 89953502ba..624deb0283 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -25,6 +25,7 @@ class RememberMeFactory implements SecurityFactoryInterface 'domain' => null, 'secure' => false, 'httponly' => true, + 'samesite' => null, 'always_remember_me' => false, 'remember_me_parameter' => '_remember_me', ]; diff --git a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php index fba79fba87..53b6368973 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php @@ -38,6 +38,7 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface protected $options = [ 'secure' => false, 'httponly' => true, + 'samesite' => null, ]; private $providerKey; private $secret; @@ -281,7 +282,7 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface $this->logger->debug('Clearing remember-me cookie.', ['name' => $this->options['name']]); } - $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'], $this->options['secure'], $this->options['httponly'])); + $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'], $this->options['secure'], $this->options['httponly'], false, $this->options['samesite'])); } /** diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index 272a5cc2db..94ec0eae53 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -84,7 +84,9 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices $this->options['path'], $this->options['domain'], $this->options['secure'], - $this->options['httponly'] + $this->options['httponly'], + false, + $this->options['samesite'] ) ); @@ -117,7 +119,9 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices $this->options['path'], $this->options['domain'], $this->options['secure'], - $this->options['httponly'] + $this->options['httponly'], + false, + $this->options['samesite'] ) ); } diff --git a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php index 3df2ced622..32e65f3cf0 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php @@ -81,7 +81,9 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices $this->options['path'], $this->options['domain'], $this->options['secure'], - $this->options['httponly'] + $this->options['httponly'], + false, + $this->options['samesite'] ) ); } diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php index 599a7e81c3..7afa48edc9 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\Tests\RememberMe; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; @@ -268,7 +269,7 @@ class PersistentTokenBasedRememberMeServicesTest extends TestCase public function testLoginSuccessSetsCookieWhenLoggedInWithNonRememberMeTokenInterfaceImplementation() { - $service = $this->getService(null, ['name' => 'foo', 'domain' => 'myfoodomain.foo', 'path' => '/foo/path', 'secure' => true, 'httponly' => true, 'lifetime' => 3600, 'always_remember_me' => true]); + $service = $this->getService(null, ['name' => 'foo', 'domain' => 'myfoodomain.foo', 'path' => '/foo/path', 'secure' => true, 'httponly' => true, 'samesite' => Cookie::SAMESITE_STRICT, 'lifetime' => 3600, 'always_remember_me' => true]); $request = new Request(); $response = new Response(); @@ -305,6 +306,7 @@ class PersistentTokenBasedRememberMeServicesTest extends TestCase $this->assertTrue($cookie->getExpiresTime() > time() + 3590 && $cookie->getExpiresTime() < time() + 3610); $this->assertEquals('myfoodomain.foo', $cookie->getDomain()); $this->assertEquals('/foo/path', $cookie->getPath()); + $this->assertSame(Cookie::SAMESITE_STRICT, $cookie->getSameSite()); } protected function encodeCookie(array $parts) diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php index f24e4fff80..4a34d61421 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\Tests\RememberMe; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; @@ -205,7 +206,7 @@ class TokenBasedRememberMeServicesTest extends TestCase public function testLoginSuccess() { - $service = $this->getService(null, ['name' => 'foo', 'domain' => 'myfoodomain.foo', 'path' => '/foo/path', 'secure' => true, 'httponly' => true, 'lifetime' => 3600, 'always_remember_me' => true]); + $service = $this->getService(null, ['name' => 'foo', 'domain' => 'myfoodomain.foo', 'path' => '/foo/path', 'secure' => true, 'httponly' => true, 'samesite' => Cookie::SAMESITE_STRICT, 'lifetime' => 3600, 'always_remember_me' => true]); $request = new Request(); $response = new Response(); @@ -240,6 +241,7 @@ class TokenBasedRememberMeServicesTest extends TestCase $this->assertTrue($cookie->getExpiresTime() > time() + 3590 && $cookie->getExpiresTime() < time() + 3610); $this->assertEquals('myfoodomain.foo', $cookie->getDomain()); $this->assertEquals('/foo/path', $cookie->getPath()); + $this->assertSame(Cookie::SAMESITE_STRICT, $cookie->getSameSite()); } protected function getCookie($class, $username, $expires, $password) From 61025d1d1b7fa1675b15e20129ea9c7fa86981d7 Mon Sep 17 00:00:00 2001 From: Jorrit Schippers Date: Fri, 20 Mar 2020 13:10:52 +0100 Subject: [PATCH 10/12] [Form] Support customized intl php.ini settings `IntlDateParser->parse()` behaves differently when `intl.error_level` and/or `intl.use_exceptions` are not 0. This change makes sure `\IntlException` is caught when `intl.use_exceptions` is 1 and warnings thrown when `intl.error_level` is not 0 are ignored. --- .../DateTimeToLocalizedStringTransformer.php | 9 +++- ...teTimeToLocalizedStringTransformerTest.php | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index af2dad7c9e..e47b03d32d 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -117,11 +117,16 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer // date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due // to DST changes $dateOnly = $this->isPatternDateOnly(); + $dateFormatter = $this->getIntlDateFormatter($dateOnly); - $timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value); + try { + $timestamp = @$dateFormatter->parse($value); + } catch (\IntlException $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } if (0 != intl_get_error_code()) { - throw new TransformationFailedException(intl_get_error_message()); + throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code()); } elseif ($timestamp > 253402214400) { // This timestamp represents UTC midnight of 9999-12-31 to prevent 5+ digit years throw new TransformationFailedException('Years beyond 9999 are not supported.'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php index ce91d05ff4..ad9e099909 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -27,6 +27,12 @@ class DateTimeToLocalizedStringTransformerTest extends TestCase { parent::setUp(); + // Normalize intl. configuration settings. + if (\extension_loaded('intl')) { + $this->iniSet('intl.use_exceptions', 0); + $this->iniSet('intl.error_level', 0); + } + // Since we test against "de_AT", we need the full implementation IntlTestHelper::requireFullIntl($this, '57.1'); @@ -334,4 +340,44 @@ class DateTimeToLocalizedStringTransformerTest extends TestCase $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, null, \IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd HH:mm:ss'); $transformer->reverseTransform('20107-03-21 12:34:56'); } + + public function testReverseTransformWrapsIntlErrorsWithErrorLevel() + { + if (!\extension_loaded('intl')) { + $this->markTestSkipped('intl extension is not loaded'); + } + + $this->iniSet('intl.error_level', E_WARNING); + + $this->expectException('Symfony\Component\Form\Exception\TransformationFailedException'); + $transformer = new DateTimeToLocalizedStringTransformer(); + $transformer->reverseTransform('12345'); + } + + public function testReverseTransformWrapsIntlErrorsWithExceptions() + { + if (!\extension_loaded('intl')) { + $this->markTestSkipped('intl extension is not loaded'); + } + + $this->iniSet('intl.use_exceptions', 1); + + $this->expectException('Symfony\Component\Form\Exception\TransformationFailedException'); + $transformer = new DateTimeToLocalizedStringTransformer(); + $transformer->reverseTransform('12345'); + } + + public function testReverseTransformWrapsIntlErrorsWithExceptionsAndErrorLevel() + { + if (!\extension_loaded('intl')) { + $this->markTestSkipped('intl extension is not loaded'); + } + + $this->iniSet('intl.use_exceptions', 1); + $this->iniSet('intl.error_level', E_WARNING); + + $this->expectException('Symfony\Component\Form\Exception\TransformationFailedException'); + $transformer = new DateTimeToLocalizedStringTransformer(); + $transformer->reverseTransform('12345'); + } } From 9bb1230525b32fa20ed8abce429f931c90a1776c Mon Sep 17 00:00:00 2001 From: Koen Reiniers Date: Mon, 23 Mar 2020 11:51:31 +0100 Subject: [PATCH 11/12] [Security] Check if firewall is stateless before checking for session/previous session --- .../Guard/GuardAuthenticatorHandler.php | 2 +- .../Tests/GuardAuthenticatorHandlerTest.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 977442aa93..356547df2b 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -134,7 +134,7 @@ class GuardAuthenticatorHandler private function migrateSession(Request $request, TokenInterface $token, $providerKey) { - if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession() || \in_array($providerKey, $this->statelessProviderKeys, true)) { + if (\in_array($providerKey, $this->statelessProviderKeys, true) || !$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } diff --git a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php index 7fe29cacc0..74227e37c1 100644 --- a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php @@ -149,6 +149,25 @@ class GuardAuthenticatorHandlerTest extends TestCase $handler->authenticateWithToken($this->token, $this->request, 'some_provider_key'); } + /** + * @requires function \Symfony\Component\HttpFoundation\Request::setSessionFactory + */ + public function testSessionIsNotInstantiatedOnStatelessFirewall() + { + $sessionFactory = $this->getMockBuilder(\stdClass::class) + ->setMethods(['__invoke']) + ->getMock(); + + $sessionFactory->expects($this->never()) + ->method('__invoke'); + + $this->request->setSessionFactory($sessionFactory); + + $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher, ['stateless_provider_key']); + $handler->setSessionAuthenticationStrategy($this->sessionStrategy); + $handler->authenticateWithToken($this->token, $this->request, 'stateless_provider_key'); + } + protected function setUp() { $this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); From 4bdea1f2e77935c37362c31630accacf781cb0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20P=C3=A9delagrabe?= Date: Mon, 23 Mar 2020 11:02:50 +0100 Subject: [PATCH 12/12] [Http Foundation] Fix clear cookie samesite --- .../Component/HttpFoundation/ResponseHeaderBag.php | 7 +++++-- .../HttpFoundation/Tests/ResponseHeaderBagTest.php | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index e11b98a10f..9a6f87648f 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -244,10 +244,13 @@ class ResponseHeaderBag extends HeaderBag * @param string $domain * @param bool $secure * @param bool $httpOnly + * @param string $sameSite */ - public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true) + public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true/*, $sameSite = null*/) { - $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly)); + $sameSite = \func_num_args() > 5 ? func_get_arg(5) : null; + + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite)); } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index 4e3a6c82b5..632c96df52 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -128,6 +128,14 @@ class ResponseHeaderBagTest extends TestCase $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure', $bag); } + public function testClearCookieSamesite() + { + $bag = new ResponseHeaderBag([]); + + $bag->clearCookie('foo', '/', null, true, false, 'none'); + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure; samesite=none', $bag); + } + public function testReplace() { $bag = new ResponseHeaderBag([]);