Merge branch '5.2' into 5.x

* 5.2:
  Dont allow unserializing classes with a destructor
  Dont allow unserializing classes with a destructor - 4.4
  [Cache] fix possible collision when writing tmp file in filesystem adapter
  a colon followed by spaces exclusively separates mapping keys and values
  Contracts: Remove ellipsis
  fix handling float-like key attribute values
  Fix transient test with HttpClient jitter
  Fix missing BCC recipients in SES bridge
  Move AuthenticationSuccessEvent outside try/catch block
  Dont allow unserializing classes with a destructor - 5.2
  Dont allow unserializing classes with a destructor - 5.1
This commit is contained in:
Nicolas Grekas 2021-01-12 15:29:08 +01:00
commit a176f8767c
39 changed files with 352 additions and 47 deletions

View File

@ -129,6 +129,16 @@ class ElasticsearchLogstashHandler extends AbstractHandler
$this->wait(false); $this->wait(false);
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->wait(true); $this->wait(true);

View File

@ -89,6 +89,12 @@ class AppKernel extends Kernel implements ExtensionInterface, ConfigurationInter
public function __wakeup() public function __wakeup()
{ {
foreach ($this as $k => $v) {
if (\is_object($v)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
}
$this->__construct($this->varDir, $this->testCase, $this->rootConfig, $this->environment, $this->debug); $this->__construct($this->varDir, $this->testCase, $this->rootConfig, $this->environment, $this->debug);
} }

View File

@ -93,9 +93,20 @@ trait FilesystemCommonTrait
set_error_handler(__CLASS__.'::throwError'); set_error_handler(__CLASS__.'::throwError');
try { try {
if (null === $this->tmp) { if (null === $this->tmp) {
$this->tmp = $this->directory.uniqid('', true); $this->tmp = $this->directory.bin2hex(random_bytes(6));
} }
file_put_contents($this->tmp, $data); try {
$h = fopen($this->tmp, 'x');
} catch (\ErrorException $e) {
if (false === strpos($e->getMessage(), 'File exists')) {
throw $e;
}
$this->tmp = $this->directory.bin2hex(random_bytes(6));
$h = fopen($this->tmp, 'x');
}
fwrite($h, $data);
fclose($h);
if (null !== $expiresAt) { if (null !== $expiresAt) {
touch($this->tmp, $expiresAt); touch($this->tmp, $expiresAt);

View File

@ -227,6 +227,10 @@ class PrototypedArrayNode extends ArrayNode
} elseif (isset($v[$this->keyAttribute])) { } elseif (isset($v[$this->keyAttribute])) {
$k = $v[$this->keyAttribute]; $k = $v[$this->keyAttribute];
if (\is_float($k)) {
$k = var_export($k, true);
}
// remove the key attribute when required // remove the key attribute when required
if ($this->removeKeyAttribute) { if ($this->removeKeyAttribute) {
unset($v[$this->keyAttribute]); unset($v[$this->keyAttribute]);

View File

@ -201,6 +201,32 @@ class NormalizationTest extends TestCase
$this->assertNormalized($tree, $data, $data); $this->assertNormalized($tree, $data, $data);
} }
public function testFloatLikeValueAsMapKeyAttribute()
{
$tree = (new TreeBuilder('root'))
->getRootNode()
->useAttributeAsKey('number')
->arrayPrototype()
->children()
->scalarNode('foo')->end()
->end()
->end()
->end()
->buildTree()
;
$this->assertNormalized($tree, [
[
'number' => 3.0,
'foo' => 'bar',
],
], [
'3.0' => [
'foo' => 'bar',
],
]);
}
public static function assertNormalized(NodeInterface $tree, $denormalized, $normalized) public static function assertNormalized(NodeInterface $tree, $denormalized, $normalized)
{ {
self::assertSame($normalized, $tree->normalize($denormalized)); self::assertSame($normalized, $tree->normalize($denormalized));

View File

@ -40,6 +40,16 @@ abstract class AbstractConfigurator
throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method)); throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method));
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
/** /**
* Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value. * Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value.
* *

View File

@ -35,6 +35,16 @@ class BufferingLogger extends AbstractLogger
return $logs; return $logs;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
foreach ($this->logs as [$level, $message, $context]) { foreach ($this->logs as [$level, $message, $context]) {

View File

@ -76,6 +76,16 @@ class OrderedHashMapIterator implements \Iterator
$this->managedCursors[$this->cursorId] = &$this->cursor; $this->managedCursors[$this->cursorId] = &$this->cursor;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
/** /**
* Removes the iterator's cursors from the managed cursors of the * Removes the iterator's cursors from the managed cursors of the
* corresponding {@link OrderedHashMap} instance. * corresponding {@link OrderedHashMap} instance.

View File

@ -120,6 +120,16 @@ class ErrorChunk implements ChunkInterface
return $this->didThrow; return $this->didThrow;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
if (!$this->didThrow) { if (!$this->didThrow) {

View File

@ -367,6 +367,16 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
} }
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->reset(); $this->reset();

View File

@ -218,6 +218,16 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->wait(); $this->wait();

View File

@ -142,6 +142,16 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
return null !== $type ? $this->info[$type] ?? null : $this->info; return null !== $type ? $this->info[$type] ?? null : $this->info;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
try { try {

View File

@ -127,6 +127,16 @@ trait CommonResponseTrait
return $stream; return $stream;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
/** /**
* Closes the response and all its network handles. * Closes the response and all its network handles.
*/ */

View File

@ -44,6 +44,16 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
$this->event = $event; $this->event = $event;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
try { try {

View File

@ -93,19 +93,16 @@ class GenericRetryStrategyTest extends TestCase
public function testJitter() public function testJitter()
{ {
$strategy = new GenericRetryStrategy([], 1000, 1, 0, 1); $strategy = new GenericRetryStrategy([], 1000, 1, 0, 1);
$belowHalf = 0; $min = 2000;
$aboveHalf = 0; $max = 0;
for ($i = 0; $i < 20; ++$i) { for ($i = 0; $i < 50; ++$i) {
$delay = $strategy->getDelay($this->getContext(0, 'GET', 'http://example.com/', 200), null, null); $delay = $strategy->getDelay($this->getContext(0, 'GET', 'http://example.com/', 200), null, null);
if ($delay < 500) { $min = min($min, $delay);
++$belowHalf; $max = max($max, $delay);
} elseif ($delay > 1500) {
++$aboveHalf;
}
} }
$this->assertGreaterThanOrEqual(1000, $max - $min);
$this->assertGreaterThanOrEqual(1, $belowHalf); $this->assertGreaterThanOrEqual(1000, $max);
$this->assertGreaterThanOrEqual(1, $aboveHalf); $this->assertLessThanOrEqual(1000, $min);
} }
private function getContext($retryCount, $method, $url, $statusCode): AsyncContext private function getContext($retryCount, $method, $url, $statusCode): AsyncContext

View File

@ -179,7 +179,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$fileLinkFormat = array_pop($this->data); $fileLinkFormat = array_pop($this->data);
$this->dataCount = \count($this->data); $this->dataCount = \count($this->data);
self::__construct($this->stopwatch, $fileLinkFormat, $charset); self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null);
} }
public function getDumpsCount(): int public function getDumpsCount(): int

View File

@ -865,6 +865,10 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
public function __wakeup() public function __wakeup()
{ {
if (\is_object($this->environment) || \is_object($this->debug)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
$this->__construct($this->environment, $this->debug); $this->__construct($this->environment, $this->debug);
} }
} }

View File

@ -35,6 +35,16 @@ class Connection extends AbstractConnection
/** @var resource */ /** @var resource */
private $connection; private $connection;
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->disconnect(); $this->disconnect();

View File

@ -38,6 +38,16 @@ class Query extends AbstractQuery
parent::__construct($connection, $dn, $query, $options); parent::__construct($connection, $dn, $query, $options);
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$con = $this->connection->getResource(); $con = $this->connection->getResource();

View File

@ -49,6 +49,16 @@ final class Lock implements SharedLockInterface, LoggerAwareInterface
$this->logger = new NullLogger(); $this->logger = new NullLogger();
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
/** /**
* Automatically releases the underlying lock when the object is destructed. * Automatically releases the underlying lock when the object is destructed.
*/ */

View File

@ -63,6 +63,12 @@ class SesHttpTransportTest extends TestCase
$this->assertStringContainsString('AWS3-HTTPS AWSAccessKeyId=ACCESS_KEY,Algorithm=HmacSHA256,Signature=', $options['headers'][0] ?? $options['request_headers'][0]); $this->assertStringContainsString('AWS3-HTTPS AWSAccessKeyId=ACCESS_KEY,Algorithm=HmacSHA256,Signature=', $options['headers'][0] ?? $options['request_headers'][0]);
parse_str($options['body'], $body); parse_str($options['body'], $body);
$this->assertArrayHasKey('Destinations_member_1', $body);
$this->assertSame('saif.gmati@symfony.com', $body['Destinations_member_1']);
$this->assertArrayHasKey('Destinations_member_2', $body);
$this->assertSame('jeremy@derusse.com', $body['Destinations_member_2']);
$content = base64_decode($body['RawMessage_Data']); $content = base64_decode($body['RawMessage_Data']);
$this->assertStringContainsString('Hello!', $content); $this->assertStringContainsString('Hello!', $content);
@ -88,6 +94,7 @@ class SesHttpTransportTest extends TestCase
$mail = new Email(); $mail = new Email();
$mail->subject('Hello!') $mail->subject('Hello!')
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
->bcc(new Address('jeremy@derusse.com', 'Jérémy Derussé'))
->from(new Address('fabpot@symfony.com', 'Fabien')) ->from(new Address('fabpot@symfony.com', 'Fabien'))
->text('Hello There!'); ->text('Hello There!');

View File

@ -66,8 +66,14 @@ class SesHttpTransport extends AbstractHttpTransport
], ],
]; ];
if (($message->getOriginalMessage() instanceof Message) $index = 1;
&& $configurationSetHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-CONFIGURATION-SET')) { foreach ($message->getEnvelope()->getRecipients() as $recipient) {
$request['body']['Destinations.member.'.$index++] = $recipient->getAddress();
}
if ($message->getOriginalMessage() instanceof Message
&& $configurationSetHeader = $message->getOriginalMessage()->getHeaders()->get('X-SES-CONFIGURATION-SET')
) {
$request['body']['ConfigurationSetName'] = $configurationSetHeader->getBodyAsString(); $request['body']['ConfigurationSetName'] = $configurationSetHeader->getBodyAsString();
} }

View File

@ -340,6 +340,16 @@ class SmtpTransport extends AbstractTransport
$this->restartCounter = 0; $this->restartCounter = 0;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->stop(); $this->stop();

View File

@ -66,6 +66,16 @@ class Connection
$this->queueUrl = $queueUrl; $this->queueUrl = $queueUrl;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->reset(); $this->reset();

View File

@ -155,7 +155,13 @@ class DataPart extends TextPart
$r->setValue($this, $this->_headers); $r->setValue($this, $this->_headers);
unset($this->_headers); unset($this->_headers);
if (!\is_array($this->_parent)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) { foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name])) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
$r = new \ReflectionProperty(TextPart::class, $name); $r = new \ReflectionProperty(TextPart::class, $name);
$r->setAccessible(true); $r->setAccessible(true);
$r->setValue($this, $this->_parent[$name]); $r->setValue($this, $this->_parent[$name]);

View File

@ -35,6 +35,16 @@ class UnixPipes extends AbstractPipes
parent::__construct($input); parent::__construct($input);
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->close(); $this->close();

View File

@ -88,6 +88,16 @@ class WindowsPipes extends AbstractPipes
parent::__construct($input); parent::__construct($input);
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->close(); $this->close();

View File

@ -195,6 +195,16 @@ class Process implements \IteratorAggregate
return $process; return $process;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
if ($this->options['create_new_console'] ?? false) { if ($this->options['create_new_console'] ?? false) {

View File

@ -104,6 +104,10 @@ final class TokenBucket implements LimiterStateInterface
*/ */
public function __wakeup(): void public function __wakeup(): void
{ {
if (!\is_string($this->stringRate)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
$this->rate = Rate::fromString($this->stringRate); $this->rate = Rate::fromString($this->stringRate);
unset($this->stringRate); unset($this->stringRate);
} }

View File

@ -38,6 +38,16 @@ class CollectionConfigurator
$this->parentPrefixes = $parentPrefixes; $this->parentPrefixes = $parentPrefixes;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
if (null === $this->prefixes) { if (null === $this->prefixes) {

View File

@ -30,6 +30,16 @@ class ImportConfigurator
$this->route = $route; $this->route = $route;
} }
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct() public function __destruct()
{ {
$this->parent->addCollection($this->route); $this->parent->addCollection($this->route);

View File

@ -185,18 +185,6 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
if (null !== $this->logger) { if (null !== $this->logger) {
$this->logger->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator)]); $this->logger->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator)]);
} }
// success! (sets the token on the token storage, etc)
$response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator);
if ($response instanceof Response) {
return $response;
}
if (null !== $this->logger) {
$this->logger->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator)]);
}
return null;
} catch (AuthenticationException $e) { } catch (AuthenticationException $e) {
// oh no! Authentication failed! // oh no! Authentication failed!
$response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport); $response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport);
@ -206,6 +194,18 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
return null; return null;
} }
// success! (sets the token on the token storage, etc)
$response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator);
if ($response instanceof Response) {
return $response;
}
if (null !== $this->logger) {
$this->logger->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator)]);
}
return null;
} }
private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator): ?Response private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator): ?Response

View File

@ -12,11 +12,12 @@
namespace Symfony\Component\Security\Http\EventListener; namespace Symfony\Component\Security\Http\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
/** /**
* @author Wouter de Jong <wouter@wouterj.nl> * @author Wouter de Jong <wouter@wouterj.nl>
@ -43,21 +44,21 @@ class UserCheckerListener implements EventSubscriberInterface
$this->userChecker->checkPreAuth($passport->getUser()); $this->userChecker->checkPreAuth($passport->getUser());
} }
public function postCheckCredentials(LoginSuccessEvent $event): void public function postCheckCredentials(AuthenticationSuccessEvent $event): void
{ {
$passport = $event->getPassport(); $user = $event->getAuthenticationToken()->getUser();
if (!$passport instanceof UserPassportInterface || null === $passport->getUser()) { if (!$user instanceof UserInterface) {
return; return;
} }
$this->userChecker->checkPostAuth($passport->getUser()); $this->userChecker->checkPostAuth($user);
} }
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
return [ return [
CheckPassportEvent::class => ['preCheckCredentials', 256], CheckPassportEvent::class => ['preCheckCredentials', 256],
LoginSuccessEvent::class => ['postCheckCredentials', 256], AuthenticationSuccessEvent::class => ['postCheckCredentials', 256],
]; ];
} }
} }

View File

@ -12,8 +12,8 @@
namespace Symfony\Component\Security\Http\Tests\EventListener; namespace Symfony\Component\Security\Http\Tests\EventListener;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
@ -21,8 +21,8 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticate
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\EventListener\UserCheckerListener; use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
class UserCheckerListenerTest extends TestCase class UserCheckerListenerTest extends TestCase
@ -63,14 +63,14 @@ class UserCheckerListenerTest extends TestCase
{ {
$this->userChecker->expects($this->once())->method('checkPostAuth')->with($this->user); $this->userChecker->expects($this->once())->method('checkPostAuth')->with($this->user);
$this->listener->postCheckCredentials($this->createLoginSuccessEvent()); $this->listener->postCheckCredentials(new AuthenticationSuccessEvent(new PostAuthenticationToken($this->user, 'main', [])));
} }
public function testPostAuthNoUser() public function testPostAuthNoUser()
{ {
$this->userChecker->expects($this->never())->method('checkPostAuth'); $this->userChecker->expects($this->never())->method('checkPostAuth');
$this->listener->postCheckCredentials($this->createLoginSuccessEvent($this->createMock(PassportInterface::class))); $this->listener->postCheckCredentials(new AuthenticationSuccessEvent(new PreAuthenticatedToken('nobody', null, 'main')));
} }
private function createCheckPassportEvent($passport = null) private function createCheckPassportEvent($passport = null)
@ -82,12 +82,8 @@ class UserCheckerListenerTest extends TestCase
return new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport); return new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport);
} }
private function createLoginSuccessEvent($passport = null) private function createAuthenticationSuccessEvent()
{ {
if (null === $passport) { return new AuthenticationSuccessEvent(new PostAuthenticationToken($this->user, 'main', []));
$passport = new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; }));
}
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main');
} }
} }

View File

@ -359,6 +359,10 @@ class UnicodeString extends AbstractUnicodeString
public function __wakeup() public function __wakeup()
{ {
if (!\is_string($this->string)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
} }

View File

@ -203,7 +203,7 @@ class Parser
array_pop($this->refsBeingParsed); array_pop($this->refsBeingParsed);
} }
} elseif ( } elseif (
self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values) self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:( ++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
&& (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
) { ) {
if ($context && 'sequence' == $context) { if ($context && 'sequence' == $context) {

View File

@ -915,4 +915,21 @@ class InlineTest extends TestCase
[['!'], '! ["!"]'], [['!'], '! ["!"]'],
]; ];
} }
/**
* @dataProvider ideographicSpaceProvider
*/
public function testParseIdeographicSpace(string $yaml, string $expected)
{
$this->assertSame($expected, Inline::parse($yaml));
}
public function ideographicSpaceProvider(): array
{
return [
["\u{3000}", ' '],
["'\u{3000}'", ' '],
["'a b'", 'a b'],
];
}
} }

View File

@ -2732,6 +2732,22 @@ YAML;
// (before, there was no \n after row2) // (before, there was no \n after row2)
$this->assertSame(['a' => ['b' => "row\nrow2\n"], 'c' => 'd'], $this->parser->parse($longDocument)); $this->assertSame(['a' => ['b' => "row\nrow2\n"], 'c' => 'd'], $this->parser->parse($longDocument));
} }
public function testParseIdeographicSpaces()
{
$expected = <<<YAML
unquoted: \u{3000}
quoted: '\u{3000}'
within_string: 'a b'
regular_space: 'a b'
YAML;
$this->assertSame([
'unquoted' => ' ',
'quoted' => ' ',
'within_string' => 'a b',
'regular_space' => 'a b',
], $this->parser->parse($expected));
}
} }
class B class B

View File

@ -11,7 +11,7 @@ Design Principles
* contracts are split by domain, each into their own sub-namespaces; * contracts are split by domain, each into their own sub-namespaces;
* contracts are small and consistent sets of PHP interfaces, traits, normative * contracts are small and consistent sets of PHP interfaces, traits, normative
docblocks and reference test suites when applicable, ...; docblocks and reference test suites when applicable;
* all contracts must have a proven implementation to enter this repository; * all contracts must have a proven implementation to enter this repository;
* they must be backward compatible with existing Symfony components. * they must be backward compatible with existing Symfony components.