Merge branch '4.3' into 4.4

* 4.3:
  [Yaml] Throw on unquoted exclamation mark
  Use supportsClass where possible
This commit is contained in:
Nicolas Grekas 2020-01-21 12:12:16 +01:00
commit 9d33550945
6 changed files with 158 additions and 13 deletions

View File

@ -70,24 +70,49 @@ class ChainUserProviderTest extends TestCase
$provider1 = $this->getProvider(); $provider1 = $this->getProvider();
$provider1 $provider1
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('supportsClass')
->willThrowException(new UnsupportedUserException('unsupported')) ->willReturn(false)
; ;
$provider2 = $this->getProvider(); $provider2 = $this->getProvider();
$provider2 $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()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')
->willReturn($account = $this->getAccount()) ->willReturn($account = $this->getAccount())
; ;
$provider = new ChainUserProvider([$provider1, $provider2]); $provider = new ChainUserProvider([$provider1, $provider2, $provider3]);
$this->assertSame($account, $provider->refreshUser($this->getAccount())); $this->assertSame($account, $provider->refreshUser($this->getAccount()));
} }
public function testRefreshUserAgain() public function testRefreshUserAgain()
{ {
$provider1 = $this->getProvider(); $provider1 = $this->getProvider();
$provider1
->expects($this->once())
->method('supportsClass')
->willReturn(true)
;
$provider1 $provider1
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')
@ -95,6 +120,12 @@ class ChainUserProviderTest extends TestCase
; ;
$provider2 = $this->getProvider(); $provider2 = $this->getProvider();
$provider2
->expects($this->once())
->method('supportsClass')
->willReturn(true)
;
$provider2 $provider2
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')
@ -109,6 +140,12 @@ class ChainUserProviderTest extends TestCase
{ {
$this->expectException('Symfony\Component\Security\Core\Exception\UnsupportedUserException'); $this->expectException('Symfony\Component\Security\Core\Exception\UnsupportedUserException');
$provider1 = $this->getProvider(); $provider1 = $this->getProvider();
$provider1
->expects($this->once())
->method('supportsClass')
->willReturn(true)
;
$provider1 $provider1
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')
@ -116,6 +153,12 @@ class ChainUserProviderTest extends TestCase
; ;
$provider2 = $this->getProvider(); $provider2 = $this->getProvider();
$provider2
->expects($this->once())
->method('supportsClass')
->willReturn(true)
;
$provider2 $provider2
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')
@ -173,6 +216,12 @@ class ChainUserProviderTest extends TestCase
public function testAcceptsTraversable() public function testAcceptsTraversable()
{ {
$provider1 = $this->getProvider(); $provider1 = $this->getProvider();
$provider1
->expects($this->once())
->method('supportsClass')
->willReturn(true)
;
$provider1 $provider1
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')
@ -180,6 +229,12 @@ class ChainUserProviderTest extends TestCase
; ;
$provider2 = $this->getProvider(); $provider2 = $this->getProvider();
$provider2
->expects($this->once())
->method('supportsClass')
->willReturn(true)
;
$provider2 $provider2
->expects($this->once()) ->expects($this->once())
->method('refreshUser') ->method('refreshUser')

View File

@ -73,6 +73,10 @@ class ChainUserProvider implements UserProviderInterface, PasswordUpgraderInterf
foreach ($this->providers as $provider) { foreach ($this->providers as $provider) {
try { try {
if (!$provider->supportsClass(\get_class($user))) {
continue;
}
return $provider->refreshUser($user); return $provider->refreshUser($user);
} catch (UnsupportedUserException $e) { } catch (UnsupportedUserException $e) {
// try next one // try next one

View File

@ -218,12 +218,17 @@ class ContextListener extends AbstractListener implements ListenerInterface
$userNotFoundByProvider = false; $userNotFoundByProvider = false;
$userDeauthenticated = false; $userDeauthenticated = false;
$userClass = \get_class($user);
foreach ($this->userProviders as $provider) { foreach ($this->userProviders as $provider) {
if (!$provider instanceof UserProviderInterface) { if (!$provider instanceof UserProviderInterface) {
throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', \get_class($provider), UserProviderInterface::class)); throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', \get_class($provider), UserProviderInterface::class));
} }
if (!$provider->supportsClass($userClass)) {
continue;
}
try { try {
$refreshedUser = $provider->refreshUser($user); $refreshedUser = $provider->refreshUser($user);
$newToken = clone $token; $newToken = clone $token;
@ -287,7 +292,7 @@ class ContextListener extends AbstractListener implements ListenerInterface
return null; 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) private function safelyUnserialize(string $serializedToken)

View File

@ -253,7 +253,7 @@ class ContextListenerTest extends TestCase
public function testIfTokenIsDeauthenticated() public function testIfTokenIsDeauthenticated()
{ {
$refreshedUser = new User('foobar', 'baz'); $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()); $this->assertNull($tokenStorage->getToken());
} }
@ -275,7 +275,7 @@ class ContextListenerTest extends TestCase
$rememberMeServices = $this->createMock(RememberMeServicesInterface::class); $rememberMeServices = $this->createMock(RememberMeServicesInterface::class);
$rememberMeServices->expects($this->once())->method('loginFail'); $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()); $this->assertNull($tokenStorage->getToken());
} }
@ -283,7 +283,7 @@ class ContextListenerTest extends TestCase
public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() public function testTryAllUserProvidersUntilASupportingUserProviderIsFound()
{ {
$refreshedUser = new User('foobar', 'baz'); $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()); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
} }
@ -291,14 +291,14 @@ class ContextListenerTest extends TestCase
public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserProviderDidNotLoadTheUser() public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserProviderDidNotLoadTheUser()
{ {
$refreshedUser = new User('foobar', 'baz'); $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()); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
} }
public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProviders() 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()); $this->assertNull($tokenStorage->getToken());
} }
@ -306,13 +306,13 @@ class ContextListenerTest extends TestCase
public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered() public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered()
{ {
$this->expectException('RuntimeException'); $this->expectException('RuntimeException');
$this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new NotSupportingUserProvider()]); $this->handleEventWithPreviousSession([new NotSupportingUserProvider(false), new NotSupportingUserProvider(true)]);
} }
public function testAcceptsProvidersAsTraversable() public function testAcceptsProvidersAsTraversable()
{ {
$refreshedUser = new User('foobar', 'baz'); $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()); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
} }
@ -338,7 +338,7 @@ class ContextListenerTest extends TestCase
$this->assertNotEquals($event->getRefreshedToken()->getUser(), $user); $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)); $listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST));
$this->assertNull($tokenStorage->getToken()); $this->assertNull($tokenStorage->getToken());
@ -431,6 +431,14 @@ class ContextListenerTest extends TestCase
class NotSupportingUserProvider implements UserProviderInterface class NotSupportingUserProvider implements UserProviderInterface
{ {
/** @var bool */
private $throwsUnsupportedException;
public function __construct($throwsUnsupportedException)
{
$this->throwsUnsupportedException = $throwsUnsupportedException;
}
public function loadUserByUsername($username): UserInterface public function loadUserByUsername($username): UserInterface
{ {
throw new UsernameNotFoundException(); throw new UsernameNotFoundException();
@ -438,7 +446,11 @@ class NotSupportingUserProvider implements UserProviderInterface
public function refreshUser(UserInterface $user): UserInterface public function refreshUser(UserInterface $user): UserInterface
{ {
throw new UnsupportedUserException(); if ($this->throwsUnsupportedException) {
throw new UnsupportedUserException();
}
return $user;
} }
public function supportsClass($class): bool public function supportsClass($class): bool

View File

@ -679,6 +679,10 @@ class Inline
$nextOffset = $i + $tagLength + 1; $nextOffset = $i + $tagLength + 1;
$nextOffset += strspn($value, ' ', $nextOffset); $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 // 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)) { 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()} // Manage in {@link self::evaluateScalar()}

View File

@ -737,4 +737,69 @@ class InlineTest extends TestCase
'negative octal number' => [-28, '-034'], '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,]"'],
[['!'], '! ["!"]'],
];
}
} }