diff --git a/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php index aa9ade7020..aaf3e5291c 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php @@ -70,24 +70,49 @@ class ChainUserProviderTest extends TestCase $provider1 = $this->getProvider(); $provider1 ->expects($this->once()) - ->method('refreshUser') - ->willThrowException(new UnsupportedUserException('unsupported')) + ->method('supportsClass') + ->willReturn(false) ; $provider2 = $this->getProvider(); $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + + $provider2 + ->expects($this->once()) + ->method('refreshUser') + ->willThrowException(new UnsupportedUserException('unsupported')) + ; + + $provider3 = $this->getProvider(); + $provider3 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + + $provider3 ->expects($this->once()) ->method('refreshUser') ->willReturn($account = $this->getAccount()) ; - $provider = new ChainUserProvider([$provider1, $provider2]); + $provider = new ChainUserProvider([$provider1, $provider2, $provider3]); $this->assertSame($account, $provider->refreshUser($this->getAccount())); } public function testRefreshUserAgain() { $provider1 = $this->getProvider(); + $provider1 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider1 ->expects($this->once()) ->method('refreshUser') @@ -95,6 +120,12 @@ class ChainUserProviderTest extends TestCase ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 ->expects($this->once()) ->method('refreshUser') @@ -109,6 +140,12 @@ class ChainUserProviderTest extends TestCase { $this->expectException('Symfony\Component\Security\Core\Exception\UnsupportedUserException'); $provider1 = $this->getProvider(); + $provider1 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider1 ->expects($this->once()) ->method('refreshUser') @@ -116,6 +153,12 @@ class ChainUserProviderTest extends TestCase ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 ->expects($this->once()) ->method('refreshUser') @@ -173,6 +216,12 @@ class ChainUserProviderTest extends TestCase public function testAcceptsTraversable() { $provider1 = $this->getProvider(); + $provider1 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider1 ->expects($this->once()) ->method('refreshUser') @@ -180,6 +229,12 @@ class ChainUserProviderTest extends TestCase ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 ->expects($this->once()) ->method('refreshUser') diff --git a/src/Symfony/Component/Security/Core/User/ChainUserProvider.php b/src/Symfony/Component/Security/Core/User/ChainUserProvider.php index 68bb91ec47..462a44e0cf 100644 --- a/src/Symfony/Component/Security/Core/User/ChainUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/ChainUserProvider.php @@ -73,6 +73,10 @@ class ChainUserProvider implements UserProviderInterface, PasswordUpgraderInterf foreach ($this->providers as $provider) { try { + if (!$provider->supportsClass(\get_class($user))) { + continue; + } + return $provider->refreshUser($user); } catch (UnsupportedUserException $e) { // try next one diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 69066552d7..d8a157e80b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -201,12 +201,17 @@ class ContextListener extends AbstractListener $userNotFoundByProvider = false; $userDeauthenticated = false; + $userClass = \get_class($user); foreach ($this->userProviders as $provider) { if (!$provider instanceof UserProviderInterface) { throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', \get_class($provider), UserProviderInterface::class)); } + if (!$provider->supportsClass($userClass)) { + continue; + } + try { $refreshedUser = $provider->refreshUser($user); $newToken = clone $token; @@ -263,7 +268,7 @@ class ContextListener extends AbstractListener return null; } - throw new \RuntimeException(sprintf('There is no user provider for user "%s".', \get_class($user))); + throw new \RuntimeException(sprintf('There is no user provider for user "%s".', $userClass)); } private function safelyUnserialize(string $serializedToken) diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index a6f118f649..13ec17289a 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -253,7 +253,7 @@ class ContextListenerTest extends TestCase public function testIfTokenIsDeauthenticated() { $refreshedUser = new User('foobar', 'baz'); - $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]); + $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)]); $this->assertNull($tokenStorage->getToken()); } @@ -275,7 +275,7 @@ class ContextListenerTest extends TestCase $rememberMeServices = $this->createMock(RememberMeServicesInterface::class); $rememberMeServices->expects($this->once())->method('loginFail'); - $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], null, $rememberMeServices); + $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], null, $rememberMeServices); $this->assertNull($tokenStorage->getToken()); } @@ -283,7 +283,7 @@ class ContextListenerTest extends TestCase public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() { $refreshedUser = new User('foobar', 'baz'); - $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser); + $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } @@ -291,14 +291,14 @@ class ContextListenerTest extends TestCase public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserProviderDidNotLoadTheUser() { $refreshedUser = new User('foobar', 'baz'); - $tokenStorage = $this->handleEventWithPreviousSession([new SupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser); + $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProviders() { - $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider()]); + $tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider()]); $this->assertNull($tokenStorage->getToken()); } @@ -306,13 +306,13 @@ class ContextListenerTest extends TestCase public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered() { $this->expectException('RuntimeException'); - $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new NotSupportingUserProvider()]); + $this->handleEventWithPreviousSession([new NotSupportingUserProvider(false), new NotSupportingUserProvider(true)]); } public function testAcceptsProvidersAsTraversable() { $refreshedUser = new User('foobar', 'baz'); - $tokenStorage = $this->handleEventWithPreviousSession(new \ArrayObject([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]), $refreshedUser); + $tokenStorage = $this->handleEventWithPreviousSession(new \ArrayObject([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)]), $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } @@ -338,7 +338,7 @@ class ContextListenerTest extends TestCase $this->assertNotEquals($event->getRefreshedToken()->getUser(), $user); }); - $listener = new ContextListener($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], 'context_key', null, $eventDispatcher); + $listener = new ContextListener($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], 'context_key', null, $eventDispatcher); $listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); $this->assertNull($tokenStorage->getToken()); @@ -422,6 +422,14 @@ class ContextListenerTest extends TestCase class NotSupportingUserProvider implements UserProviderInterface { + /** @var bool */ + private $throwsUnsupportedException; + + public function __construct($throwsUnsupportedException) + { + $this->throwsUnsupportedException = $throwsUnsupportedException; + } + public function loadUserByUsername($username): UserInterface { throw new UsernameNotFoundException(); @@ -429,7 +437,11 @@ class NotSupportingUserProvider implements UserProviderInterface public function refreshUser(UserInterface $user): UserInterface { - throw new UnsupportedUserException(); + if ($this->throwsUnsupportedException) { + throw new UnsupportedUserException(); + } + + return $user; } public function supportsClass($class): bool diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 40f2bfc8db..e5e0f2a4bd 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -679,6 +679,10 @@ class Inline $nextOffset = $i + $tagLength + 1; $nextOffset += strspn($value, ' ', $nextOffset); + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException(sprintf('Using the unquoted scalar value "!" is not supported. You must quote it.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + // Is followed by a scalar and is a built-in tag if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { // Manage in {@link self::evaluateScalar()} diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 0f8f8f8b94..0f46f75ee7 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -737,4 +737,69 @@ class InlineTest extends TestCase 'negative octal number' => [-28, '-034'], ]; } + + /** + * @dataProvider unquotedExclamationMarkThrowsProvider + */ + public function testUnquotedExclamationMarkThrows(string $value) + { + $this->expectException(ParseException::class); + $this->expectExceptionMessageRegExp('/^Using the unquoted scalar value "!" is not supported\. You must quote it at line 1 \(near "/'); + + Inline::parse($value); + } + + public function unquotedExclamationMarkThrowsProvider() + { + return [ + ['!'], + ['! '], + ['! '], + [' ! '], + ['[!]'], + ['[! ]'], + ['[! ]'], + ['[!, "foo"]'], + ['["foo", !, "ccc"]'], + ['{foo: !}'], + ['{foo: !}'], + ['{foo: !, bar: "ccc"}'], + ['{bar: "ccc", foo: ! }'], + ['!]]]'], + ['!}'], + ['!,}foo,]'], + ['! [!]'], + ]; + } + + /** + * @dataProvider quotedExclamationMarkProvider + */ + public function testQuotedExclamationMark($expected, string $value) + { + $this->assertSame($expected, Inline::parse($value)); + } + + // This provider should stay consistent with unquotedExclamationMarkThrowsProvider + public function quotedExclamationMarkProvider() + { + return [ + ['!', '"!"'], + ['! ', '"! "'], + [' !', '" !"'], + [' ! ', '" ! "'], + [['!'], '["!"]'], + [['! '], '["! "]'], + [['!', 'foo'], '["!", "foo"]'], + [['foo', '!', 'ccc'], '["foo", "!", "ccc"]'], + [['foo' => '!'], '{foo: "!"}'], + [['foo' => ' !'], '{foo: " !"}'], + [['foo' => '!', 'bar' => 'ccc'], '{foo: "!", bar: "ccc"}'], + [['bar' => 'ccc', 'foo' => '! '], '{bar: "ccc", foo: "! "}'], + ['!]]]', '"!]]]"'], + ['!}', '"!}"'], + ['!,}foo,]', '"!,}foo,]"'], + [['!'], '! ["!"]'], + ]; + } }