Merge branch '4.4' into 5.0

* 4.4:
  [DI] auto-register singly implemented interfaces by default
  [DI] fix overriding existing services with aliases for singly-implemented interfaces
  remove service when base class is missing
  do not depend on the QueryBuilder from the ORM
  [Security/Http] call auth listeners/guards eagerly when they "support" the request
  [Messenger] add tests to FailedMessagesShowCommand
  Fix the translation commands when a template contains a syntax error
  [Security] Fix clearing remember-me cookie after deauthentication
  [Validator] Update Slovenian translations
  [HttpClient] remove conflict rule with HttpKernel that prevents using the component in Symfony 3.4
  [Config][ReflectionClassResource] Handle parameters with undefined constant as their default values
  fix dumping number-like string parameters
  Fix CI
  [Console] Fix autocomplete multibyte input support
  [Config] don't break on virtual stack frames in ClassExistenceResource
  more robust initialization from request
  Changing the multipart form-data behavior to use the form name as an array, which makes it recognizable as an array by PHP on the $_POST globals once it is coming from the HttpClient component
This commit is contained in:
Nicolas Grekas 2019-11-30 15:12:50 +01:00
commit bb11cac33e
62 changed files with 971 additions and 298 deletions

View File

@ -266,6 +266,11 @@ install:
run_tests () { run_tests () {
set -e set -e
export PHP=$1 export PHP=$1
if [[ !$deps && $PHP = 7.2 ]]; then
tfold src/Symfony/Component/HttpClient.h2push "$COMPOSER_UP symfony/contracts && docker run -it --rm -v $(pwd):/app -v $(phpenv which composer):/usr/local/bin/composer -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push"
fi
if [[ $PHP != 7.4* && $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then if [[ $PHP != 7.4* && $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then
echo -e "\\n\\e[33;1mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m" echo -e "\\n\\e[33;1mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m"
return return
@ -312,10 +317,6 @@ install:
PHPUNIT_X="$PHPUNIT_X,legacy" PHPUNIT_X="$PHPUNIT_X,legacy"
fi fi
if [[ $PHP = ${MIN_PHP%.*} ]]; then
tfold src/Symfony/Component/HttpClient.h2push docker run -it --rm -v $(pwd):/app -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push
fi
echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}"
tfold src/Symfony/Component/Console.tty $PHPUNIT --group tty tfold src/Symfony/Component/Console.tty $PHPUNIT --group tty

View File

@ -222,6 +222,7 @@ Security
* The `LdapUserProvider` class has been deprecated, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead. * The `LdapUserProvider` class has been deprecated, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead.
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method * Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. Please explicitly return `false` to indicate invalid credentials. * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. Please explicitly return `false` to indicate invalid credentials.
* The `ListenerInterface` is deprecated, extend `AbstractListener` instead.
* Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` (and indirectly the `is_granted()` Twig and ExpressionLanguage function) * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` (and indirectly the `is_granted()` Twig and ExpressionLanguage function)
**Before** **Before**

View File

@ -437,7 +437,7 @@ Security
* `SimpleAuthenticatorInterface`, `SimpleFormAuthenticatorInterface`, `SimplePreAuthenticatorInterface`, * `SimpleAuthenticatorInterface`, `SimpleFormAuthenticatorInterface`, `SimplePreAuthenticatorInterface`,
`SimpleAuthenticationProvider`, `SimpleAuthenticationHandler`, `SimpleFormAuthenticationListener` and `SimpleAuthenticationProvider`, `SimpleAuthenticationHandler`, `SimpleFormAuthenticationListener` and
`SimplePreAuthenticationListener` have been removed. Use Guard instead. `SimplePreAuthenticationListener` have been removed. Use Guard instead.
* The `ListenerInterface` has been removed, turn your listeners into callables instead. * The `ListenerInterface` has been removed, extend `AbstractListener` instead.
* The `Firewall::handleRequest()` method has been removed, use `Firewall::callListeners()` instead. * The `Firewall::handleRequest()` method has been removed, use `Firewall::callListeners()` instead.
* `\Serializable` interface has been removed from `AbstractToken` and `AuthenticationException`, * `\Serializable` interface has been removed from `AbstractToken` and `AuthenticationException`,
thus `serialize()` and `unserialize()` aren't available. thus `serialize()` and `unserialize()` aren't available.

View File

@ -14,7 +14,6 @@ namespace Symfony\Bridge\Doctrine\Form\Type;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader; use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
@ -82,13 +81,16 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
* For instance in ORM two query builders with an equal SQL string and * For instance in ORM two query builders with an equal SQL string and
* equal parameters are considered to be equal. * equal parameters are considered to be equal.
* *
* @param object $queryBuilder A query builder, type declaration is not present here as there
* is no common base class for the different implementations
*
* @return array|null Array with important QueryBuilder parts or null if * @return array|null Array with important QueryBuilder parts or null if
* they can't be determined * they can't be determined
* *
* @internal This method is public to be usable as callback. It should not * @internal This method is public to be usable as callback. It should not
* be used in user code. * be used in user code.
*/ */
public function getQueryBuilderPartsForCachingHash(QueryBuilder $queryBuilder): ?array public function getQueryBuilderPartsForCachingHash($queryBuilder): ?array
{ {
return null; return null;
} }

View File

@ -50,6 +50,10 @@ class EntityType extends DoctrineType
*/ */
public function getLoader(ObjectManager $manager, QueryBuilder $queryBuilder, string $class) public function getLoader(ObjectManager $manager, QueryBuilder $queryBuilder, string $class)
{ {
if (!$queryBuilder instanceof QueryBuilder) {
throw new \TypeError(sprintf('Expected an instance of %s, but got %s.', QueryBuilder::class, \is_object($queryBuilder) ? \get_class($queryBuilder) : \gettype($queryBuilder)));
}
return new ORMQueryBuilderLoader($queryBuilder); return new ORMQueryBuilderLoader($queryBuilder);
} }
@ -65,11 +69,17 @@ class EntityType extends DoctrineType
* We consider two query builders with an equal SQL string and * We consider two query builders with an equal SQL string and
* equal parameters to be equal. * equal parameters to be equal.
* *
* @param QueryBuilder $queryBuilder
*
* @internal This method is public to be usable as callback. It should not * @internal This method is public to be usable as callback. It should not
* be used in user code. * be used in user code.
*/ */
public function getQueryBuilderPartsForCachingHash(QueryBuilder $queryBuilder): ?array public function getQueryBuilderPartsForCachingHash($queryBuilder): ?array
{ {
if (!$queryBuilder instanceof QueryBuilder) {
throw new \TypeError(sprintf('Expected an instance of %s, but got %s.', QueryBuilder::class, \is_object($queryBuilder) ? \get_class($queryBuilder) : \gettype($queryBuilder)));
}
return [ return [
$queryBuilder->getQuery()->getSQL(), $queryBuilder->getQuery()->getSQL(),
array_map([$this, 'parameterToArray'], $queryBuilder->getParameters()->toArray()), array_map([$this, 'parameterToArray'], $queryBuilder->getParameters()->toArray()),

View File

@ -17,7 +17,6 @@ use Symfony\Bridge\Twig\Translation\TwigExtractor;
use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment; use Twig\Environment;
use Twig\Error\Error;
use Twig\Loader\ArrayLoader; use Twig\Loader\ArrayLoader;
class TwigExtractorTest extends TestCase class TwigExtractorTest extends TestCase
@ -74,31 +73,23 @@ class TwigExtractorTest extends TestCase
/** /**
* @dataProvider resourcesWithSyntaxErrorsProvider * @dataProvider resourcesWithSyntaxErrorsProvider
*/ */
public function testExtractSyntaxError($resources) public function testExtractSyntaxError($resources, array $messages)
{ {
$this->expectException('Twig\Error\Error');
$twig = new Environment($this->getMockBuilder('Twig\Loader\LoaderInterface')->getMock()); $twig = new Environment($this->getMockBuilder('Twig\Loader\LoaderInterface')->getMock());
$twig->addExtension(new TranslationExtension($this->getMockBuilder(TranslatorInterface::class)->getMock())); $twig->addExtension(new TranslationExtension($this->getMockBuilder(TranslatorInterface::class)->getMock()));
$extractor = new TwigExtractor($twig); $extractor = new TwigExtractor($twig);
$catalogue = new MessageCatalogue('en');
try { $extractor->extract($resources, $catalogue);
$extractor->extract($resources, new MessageCatalogue('en')); $this->assertSame($messages, $catalogue->all());
} catch (Error $e) {
$this->assertSame(\dirname(__DIR__).strtr('/Fixtures/extractor/syntax_error.twig', '/', \DIRECTORY_SEPARATOR), $e->getFile());
$this->assertSame(1, $e->getLine());
$this->assertSame('Unclosed "block".', $e->getMessage());
throw $e;
}
} }
public function resourcesWithSyntaxErrorsProvider(): array public function resourcesWithSyntaxErrorsProvider(): array
{ {
return [ return [
[__DIR__.'/../Fixtures'], [__DIR__.'/../Fixtures', ['messages' => ['Hi!' => 'Hi!']]],
[__DIR__.'/../Fixtures/extractor/syntax_error.twig'], [__DIR__.'/../Fixtures/extractor/syntax_error.twig', []],
[new \SplFileInfo(__DIR__.'/../Fixtures/extractor/syntax_error.twig')], [new \SplFileInfo(__DIR__.'/../Fixtures/extractor/syntax_error.twig'), []],
]; ];
} }

View File

@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Translation; namespace Symfony\Bridge\Twig\Translation;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Translation\Extractor\AbstractFileExtractor; use Symfony\Component\Translation\Extractor\AbstractFileExtractor;
use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogue;
@ -58,13 +57,7 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
try { try {
$this->extractTemplate(file_get_contents($file->getPathname()), $catalogue); $this->extractTemplate(file_get_contents($file->getPathname()), $catalogue);
} catch (Error $e) { } catch (Error $e) {
if ($file instanceof \SplFileInfo) { // ignore errors, these should be fixed by using the linter
$path = $file->getRealPath() ?: $file->getPathname();
$name = $file instanceof SplFileInfo ? $file->getRelativePathname() : $path;
$e->setSourceContext(new Source('', $name, $path));
}
throw $e;
} }
} }
} }

View File

@ -83,7 +83,11 @@ class RememberMeFactory implements SecurityFactoryInterface
throw new \RuntimeException('Each "security.remember_me_aware" tag must have a provider attribute.'); throw new \RuntimeException('Each "security.remember_me_aware" tag must have a provider attribute.');
} }
$userProviders[] = new Reference($attribute['provider']); // context listeners don't need a provider
if ('none' !== $attribute['provider']) {
$userProviders[] = new Reference($attribute['provider']);
}
$container $container
->getDefinition($serviceId) ->getDefinition($serviceId)
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)]) ->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])

View File

@ -315,10 +315,11 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$listeners[] = new Reference('security.channel_listener'); $listeners[] = new Reference('security.channel_listener');
$contextKey = null; $contextKey = null;
$contextListenerId = null;
// Context serializer listener // Context serializer listener
if (false === $firewall['stateless']) { if (false === $firewall['stateless']) {
$contextKey = $firewall['context'] ?? $id; $contextKey = $firewall['context'] ?? $id;
$listeners[] = new Reference($this->createContextListener($container, $contextKey)); $listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey));
$sessionStrategyId = 'security.authentication.session_strategy'; $sessionStrategyId = 'security.authentication.session_strategy';
} else { } else {
$this->statelessFirewallKeys[] = $id; $this->statelessFirewallKeys[] = $id;
@ -391,7 +392,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null; $configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
// Authentication listeners // Authentication listeners
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint); list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId);
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint); $config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);
@ -404,9 +405,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
} }
// Access listener // Access listener
if ($firewall['stateless'] || empty($firewall['anonymous']['lazy'])) { $listeners[] = new Reference('security.access_listener');
$listeners[] = new Reference('security.access_listener');
}
// Exception listener // Exception listener
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless'])); $exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
@ -444,7 +443,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $this->contextListeners[$contextKey] = $listenerId; return $this->contextListeners[$contextKey] = $listenerId;
} }
private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint) private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, string $contextListenerId = null)
{ {
$listeners = []; $listeners = [];
$hasListeners = false; $hasListeners = false;
@ -462,6 +461,10 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
} elseif ('remember_me' === $key || 'anonymous' === $key) { } elseif ('remember_me' === $key || 'anonymous' === $key) {
// RememberMeFactory will use the firewall secret when created, AnonymousAuthenticationListener does not load users. // RememberMeFactory will use the firewall secret when created, AnonymousAuthenticationListener does not load users.
$userProvider = null; $userProvider = null;
if ('remember_me' === $key && $contextListenerId) {
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
}
} elseif ($defaultProvider) { } elseif ($defaultProvider) {
$userProvider = $defaultProvider; $userProvider = $defaultProvider;
} elseif (empty($providerIds)) { } elseif (empty($providerIds)) {

View File

@ -151,9 +151,7 @@
<argument type="service" id="security.exception_listener" /> <argument type="service" id="security.exception_listener" />
<argument /> <!-- LogoutListener --> <argument /> <!-- LogoutListener -->
<argument /> <!-- FirewallConfig --> <argument /> <!-- FirewallConfig -->
<argument type="service" id="security.access_listener" />
<argument type="service" id="security.untracked_token_storage" /> <argument type="service" id="security.untracked_token_storage" />
<argument type="service" id="security.access_map" />
</service> </service>
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true"> <service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">

View File

@ -13,11 +13,8 @@ namespace Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\Event\LazyResponseEvent; use Symfony\Component\Security\Http\Event\LazyResponseEvent;
use Symfony\Component\Security\Http\Firewall\AccessListener; use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener; use Symfony\Component\Security\Http\Firewall\LogoutListener;
@ -28,17 +25,13 @@ use Symfony\Component\Security\Http\Firewall\LogoutListener;
*/ */
class LazyFirewallContext extends FirewallContext class LazyFirewallContext extends FirewallContext
{ {
private $accessListener;
private $tokenStorage; private $tokenStorage;
private $map;
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, AccessListener $accessListener, TokenStorage $tokenStorage, AccessMapInterface $map) public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage)
{ {
parent::__construct($listeners, $exceptionListener, $logoutListener, $config); parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
$this->accessListener = $accessListener;
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->map = $map;
} }
public function getListeners(): iterable public function getListeners(): iterable
@ -48,21 +41,37 @@ class LazyFirewallContext extends FirewallContext
public function __invoke(RequestEvent $event) public function __invoke(RequestEvent $event)
{ {
$this->tokenStorage->setInitializer(function () use ($event) { $listeners = [];
$request = $event->getRequest();
$lazy = $request->isMethodCacheable();
foreach (parent::getListeners() as $listener) {
if (!$lazy || !$listener instanceof AbstractListener) {
$listeners[] = $listener;
$lazy = $lazy && $listener instanceof AbstractListener;
} elseif (false !== $supports = $listener->supports($request)) {
$listeners[] = [$listener, 'authenticate'];
$lazy = null === $supports;
}
}
if (!$lazy) {
foreach ($listeners as $listener) {
$listener($event);
if ($event->hasResponse()) {
return;
}
}
return;
}
$this->tokenStorage->setInitializer(function () use ($event, $listeners) {
$event = new LazyResponseEvent($event); $event = new LazyResponseEvent($event);
foreach (parent::getListeners() as $listener) { foreach ($listeners as $listener) {
$listener($event); $listener($event);
} }
}); });
try {
[$attributes] = $this->map->getPatterns($event->getRequest());
if ($attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes) {
($this->accessListener)($event);
}
} catch (LazyResponseException $e) {
$event->setResponse($e->getResponse());
}
} }
} }

View File

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class AppCustomAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request)
{
return true;
}
public function getCredentials(Request $request)
{
throw new AuthenticationException('This should be hit');
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
}
public function checkCredentials($credentials, UserInterface $user)
{
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new Response('', 418);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new Response($authException->getMessage(), Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class ClearRememberMeTest extends AbstractWebTestCase
{
public function testUserChangeClearsCookie()
{
$client = $this->createClient(['test_case' => 'ClearRememberMe', 'root_config' => 'config.yml']);
$client->request('POST', '/login', [
'_username' => 'johannes',
'_password' => 'test',
]);
$this->assertSame(302, $client->getResponse()->getStatusCode());
$cookieJar = $client->getCookieJar();
$this->assertNotNull($cookieJar->get('REMEMBERME'));
$client->request('GET', '/foo');
$this->assertSame(200, $client->getResponse()->getStatusCode());
$this->assertNull($cookieJar->get('REMEMBERME'));
}
}
class RememberMeFooController
{
public function __invoke(UserInterface $user)
{
return new Response($user->getUsername());
}
}
class RememberMeUserProvider implements UserProviderInterface
{
private $inner;
public function __construct(InMemoryUserProvider $inner)
{
$this->inner = $inner;
}
public function loadUserByUsername($username)
{
return $this->inner->loadUserByUsername($username);
}
public function refreshUser(UserInterface $user)
{
$user = $this->inner->refreshUser($user);
$alterUser = \Closure::bind(function (User $user) { $user->password = 'foo'; }, null, User::class);
$alterUser($user);
return $user;
}
public function supportsClass($class)
{
return $this->inner->supportsClass($class);
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
class GuardedTest extends AbstractWebTestCase
{
public function testGuarded()
{
$client = $this->createClient(['test_case' => 'Guarded', 'root_config' => 'config.yml']);
$client->request('GET', '/');
$this->assertSame(418, $client->getResponse()->getStatusCode());
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
];

View File

@ -0,0 +1,31 @@
imports:
- { resource: ./../config/framework.yml }
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
providers:
in_memory:
memory:
users:
johannes: { password: test, roles: [ROLE_USER] }
firewalls:
default:
form_login:
check_path: login
remember_me: true
remember_me:
always_remember_me: true
secret: key
anonymous: ~
access_control:
- { path: ^/foo, roles: ROLE_USER }
services:
Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeUserProvider:
public: true
decorates: security.user.provider.concrete.in_memory
arguments: ['@Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeUserProvider.inner']

View File

@ -0,0 +1,7 @@
login:
path: /login
foo:
path: /foo
defaults:
_controller: Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeFooController

View File

@ -0,0 +1,15 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
];

View File

@ -0,0 +1,22 @@
framework:
secret: test
router: { resource: "%kernel.project_dir%/%kernel.test_case%/routing.yml" }
test: ~
default_locale: en
profiler: false
session:
storage_id: session.storage.mock_file
services:
logger: { class: Psr\Log\NullLogger }
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator: ~
security:
firewalls:
secure:
pattern: ^/
anonymous: lazy
stateless: false
guard:
authenticators:
- Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator

View File

@ -0,0 +1,5 @@
main:
path: /
defaults:
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction
path: /app

View File

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

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
use Symfony\Bridge\Twig\Extension\AssetExtension;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;

View File

@ -183,15 +183,17 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
} }
$props = [ $props = [
'file' => $trace[$i]['file'], 'file' => isset($trace[$i]['file']) ? $trace[$i]['file'] : null,
'line' => $trace[$i]['line'], 'line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : null,
'trace' => \array_slice($trace, 1 + $i), 'trace' => \array_slice($trace, 1 + $i),
]; ];
foreach ($props as $p => $v) { foreach ($props as $p => $v) {
$r = new \ReflectionProperty('Exception', $p); if (null !== $v) {
$r->setAccessible(true); $r = new \ReflectionProperty('Exception', $p);
$r->setValue($e, $v); $r->setAccessible(true);
$r->setValue($e, $v);
}
} }
} }

View File

@ -143,12 +143,56 @@ class ReflectionClassResource implements SelfCheckingResourceInterface
} }
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
yield preg_replace('/^ @@.*/m', '', $m);
$defaults = []; $defaults = [];
$parametersWithUndefinedConstants = [];
foreach ($m->getParameters() as $p) { foreach ($m->getParameters() as $p) {
$defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null; if (!$p->isDefaultValueAvailable()) {
$defaults[$p->name] = null;
continue;
}
if (!$p->isDefaultValueConstant() || \defined($p->getDefaultValueConstantName())) {
$defaults[$p->name] = $p->getDefaultValue();
continue;
}
$defaults[$p->name] = $p->getDefaultValueConstantName();
$parametersWithUndefinedConstants[$p->name] = true;
} }
if (!$parametersWithUndefinedConstants) {
yield preg_replace('/^ @@.*/m', '', $m);
} else {
$stack = [
$m->getDocComment(),
$m->getName(),
$m->isAbstract(),
$m->isFinal(),
$m->isStatic(),
$m->isPublic(),
$m->isPrivate(),
$m->isProtected(),
$m->returnsReference(),
$m->hasReturnType() ? $m->getReturnType()->getName() : '',
];
foreach ($m->getParameters() as $p) {
if (!isset($parametersWithUndefinedConstants[$p->name])) {
$stack[] = (string) $p;
} else {
$stack[] = $p->isOptional();
$stack[] = $p->hasType() ? $p->getType()->getName() : '';
$stack[] = $p->isPassedByReference();
$stack[] = $p->isVariadic();
$stack[] = $p->getName();
}
}
yield implode(',', $stack);
}
yield print_r($defaults, true); yield print_r($defaults, true);
} }

View File

@ -64,8 +64,12 @@ class ReflectionClassResourceTest extends TestCase
/** /**
* @dataProvider provideHashedSignature * @dataProvider provideHashedSignature
*/ */
public function testHashedSignature($changeExpected, $changedLine, $changedCode) public function testHashedSignature($changeExpected, $changedLine, $changedCode, $setContext = null)
{ {
if ($setContext) {
$setContext();
}
$code = <<<'EOPHP' $code = <<<'EOPHP'
/* 0*/ /* 0*/
/* 1*/ class %s extends ErrorException /* 1*/ class %s extends ErrorException
@ -83,7 +87,9 @@ class ReflectionClassResourceTest extends TestCase
/*13*/ protected function prot($a = []) {} /*13*/ protected function prot($a = []) {}
/*14*/ /*14*/
/*15*/ private function priv() {} /*15*/ private function priv() {}
/*16*/ } /*16*/
/*17*/ public function ccc($bar = A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC) {}
/*18*/ }
EOPHP; EOPHP;
static $expectedSignature, $generateSignature; static $expectedSignature, $generateSignature;
@ -98,7 +104,9 @@ EOPHP;
} }
$code = explode("\n", $code); $code = explode("\n", $code);
$code[$changedLine] = $changedCode; if (null !== $changedCode) {
$code[$changedLine] = $changedCode;
}
eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true))));
$signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class))));
@ -142,6 +150,10 @@ EOPHP;
yield [0, 7, 'protected int $prot;']; yield [0, 7, 'protected int $prot;'];
yield [0, 9, 'private string $priv;']; yield [0, 9, 'private string $priv;'];
} }
yield [1, 17, 'public function ccc($bar = 187) {}'];
yield [1, 17, 'public function ccc($bar = ANOTHER_ONE_THAT_WILL_NEVER_BE_DEFINED_CCCCCCCCC) {}'];
yield [1, 17, null, static function () { \define('A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC', 'foo'); }];
} }
public function testEventSubscriber() public function testEventSubscriber()

View File

@ -230,7 +230,7 @@ class QuestionHelper extends Helper
} elseif ("\177" === $c) { // Backspace Character } elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) { if (0 === $numMatches && 0 !== $i) {
--$i; --$i;
$fullChoice = substr($fullChoice, 0, -1); $fullChoice = self::substr($fullChoice, 0, -1);
// Move cursor backwards // Move cursor backwards
$output->write("\033[1D"); $output->write("\033[1D");
} }
@ -244,7 +244,7 @@ class QuestionHelper extends Helper
} }
// Pop the last character off the end of our string // Pop the last character off the end of our string
$ret = substr($ret, 0, $i); $ret = self::substr($ret, 0, $i);
} elseif ("\033" === $c) { } elseif ("\033" === $c) {
// Did we read an escape sequence? // Did we read an escape sequence?
$c .= fread($inputStream, 2); $c .= fread($inputStream, 2);
@ -270,7 +270,7 @@ class QuestionHelper extends Helper
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters); $output->write($remainingCharacters);
$fullChoice .= $remainingCharacters; $fullChoice .= $remainingCharacters;
$i = \strlen($fullChoice); $i = self::strlen($fullChoice);
$matches = array_filter( $matches = array_filter(
$autocomplete($ret), $autocomplete($ret),

View File

@ -189,19 +189,20 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
// Acm<NEWLINE> // Acm<NEWLINE>
// Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE> // Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
// <NEWLINE> // <NEWLINE>
// <UP ARROW><UP ARROW><NEWLINE> // <UP ARROW><UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE> // <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <DOWN ARROW><NEWLINE> // <DOWN ARROW><NEWLINE>
// S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE> // S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
// F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE> // F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
$inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); // F⭐<TAB><BACKSPACE><BACKSPACE>⭐<TAB><NEWLINE>
$inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\nF⭐\t\177\177\t\n");
$dialog = new QuestionHelper(); $dialog = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]); $helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet); $dialog->setHelperSet($helperSet);
$question = new Question('Please select a bundle', 'FrameworkBundle'); $question = new Question('Please select a bundle', 'FrameworkBundle');
$question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle']); $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']);
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
@ -211,6 +212,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
} }
public function testAskWithAutocompleteTrimmable() public function testAskWithAutocompleteTrimmable()

View File

@ -316,6 +316,11 @@ class XmlDumper extends Dumper
if (\in_array($value, ['null', 'true', 'false'], true)) { if (\in_array($value, ['null', 'true', 'false'], true)) {
$element->setAttribute('type', 'string'); $element->setAttribute('type', 'string');
} }
if (\is_string($value) && (is_numeric($value) || preg_match('/^0b[01]*$/', $value) || preg_match('/^0x[0-9a-f]++$/i', $value))) {
$element->setAttribute('type', 'string');
}
$text = $this->document->createTextNode(self::phpToXml($value)); $text = $this->document->createTextNode(self::phpToXml($value));
$element->appendChild($text); $element->appendChild($text);
} }

View File

@ -36,6 +36,7 @@ abstract class FileLoader extends BaseFileLoader
protected $instanceof = []; protected $instanceof = [];
protected $interfaces = []; protected $interfaces = [];
protected $singlyImplemented = []; protected $singlyImplemented = [];
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator) public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
{ {
@ -114,12 +115,16 @@ abstract class FileLoader extends BaseFileLoader
} }
} }
} }
if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
$this->registerAliasesForSinglyImplementedInterfaces();
}
} }
public function registerAliasesForSinglyImplementedInterfaces() public function registerAliasesForSinglyImplementedInterfaces()
{ {
foreach ($this->interfaces as $interface) { foreach ($this->interfaces as $interface) {
if (!empty($this->singlyImplemented[$interface]) && !$this->container->hasAlias($interface)) { if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) {
$this->container->setAlias($interface, $this->singlyImplemented[$interface])->setPublic(false); $this->container->setAlias($interface, $this->singlyImplemented[$interface])->setPublic(false);
} }
} }

View File

@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
*/ */
class PhpFileLoader extends FileLoader class PhpFileLoader extends FileLoader
{ {
protected $autoRegisterAliasesForSinglyImplementedInterfaces = false;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -36,6 +36,8 @@ class XmlFileLoader extends FileLoader
{ {
const NS = 'http://symfony.com/schema/dic/services'; const NS = 'http://symfony.com/schema/dic/services';
protected $autoRegisterAliasesForSinglyImplementedInterfaces = false;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -110,6 +110,8 @@ class YamlFileLoader extends FileLoader
private $anonymousServicesCount; private $anonymousServicesCount;
private $anonymousServicesSuffix; private $anonymousServicesSuffix;
protected $autoRegisterAliasesForSinglyImplementedInterfaces = false;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -11,6 +11,17 @@ $container = new ContainerBuilder(new ParameterBag([
'values' => [true, false, null, 0, 1000.3, 'true', 'false', 'null'], 'values' => [true, false, null, 0, 1000.3, 'true', 'false', 'null'],
'binary' => "\xf0\xf0\xf0\xf0", 'binary' => "\xf0\xf0\xf0\xf0",
'binary-control-char' => "This is a Bell char \x07", 'binary-control-char' => "This is a Bell char \x07",
'null string' => 'null',
'string of digits' => '123',
'string of digits prefixed with minus character' => '-123',
'true string' => 'true',
'false string' => 'false',
'binary number string' => '0b0110',
'numeric string' => '-1.2E2',
'hexadecimal number string' => '0xFF',
'float string' => '10100.1',
'positive float string' => '+10100.1',
'negative float string' => '-10100.1',
])); ]));
return $container; return $container;

View File

@ -108,6 +108,17 @@ class ProjectServiceContainer extends Container
], ],
'binary' => 'ðððð', 'binary' => 'ðððð',
'binary-control-char' => 'This is a Bell char ', 'binary-control-char' => 'This is a Bell char ',
'null string' => 'null',
'string of digits' => '123',
'string of digits prefixed with minus character' => '-123',
'true string' => 'true',
'false string' => 'false',
'binary number string' => '0b0110',
'numeric string' => '-1.2E2',
'hexadecimal number string' => '0xFF',
'float string' => '10100.1',
'positive float string' => '+10100.1',
'negative float string' => '-10100.1',
]; ];
} }
} }

View File

@ -20,6 +20,17 @@
</parameter> </parameter>
<parameter key="binary" type="binary">8PDw8A==</parameter> <parameter key="binary" type="binary">8PDw8A==</parameter>
<parameter key="binary-control-char" type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</parameter> <parameter key="binary-control-char" type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</parameter>
<parameter key="null string" type="string">null</parameter>
<parameter key="string of digits" type="string">123</parameter>
<parameter key="string of digits prefixed with minus character" type="string">-123</parameter>
<parameter key="true string" type="string">true</parameter>
<parameter key="false string" type="string">false</parameter>
<parameter key="binary number string" type="string">0b0110</parameter>
<parameter key="numeric string" type="string">-1.2E2</parameter>
<parameter key="hexadecimal number string" type="string">0xFF</parameter>
<parameter key="float string" type="string">10100.1</parameter>
<parameter key="positive float string" type="string">+10100.1</parameter>
<parameter key="negative float string" type="string">-10100.1</parameter>
</parameters> </parameters>
<services> <services>
<service id="service_container" class="Symfony\Component\DependencyInjection\ContainerInterface" public="true" synthetic="true"/> <service id="service_container" class="Symfony\Component\DependencyInjection\ContainerInterface" public="true" synthetic="true"/>

View File

@ -6,6 +6,17 @@ parameters:
values: [true, false, null, 0, 1000.3, 'true', 'false', 'null'] values: [true, false, null, 0, 1000.3, 'true', 'false', 'null']
binary: !!binary 8PDw8A== binary: !!binary 8PDw8A==
binary-control-char: !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH binary-control-char: !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH
null string: 'null'
string of digits: '123'
string of digits prefixed with minus character: '-123'
true string: 'true'
false string: 'false'
binary number string: '0b0110'
numeric string: '-1.2E2'
hexadecimal number string: '0xFF'
float string: '10100.1'
positive float string: '+10100.1'
negative float string: '-10100.1'
services: services:
service_container: service_container:

View File

@ -89,6 +89,7 @@ class FileLoaderTest extends TestCase
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$container->setParameter('sub_dir', 'Sub'); $container->setParameter('sub_dir', 'Sub');
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'));
$loader->autoRegisterAliasesForSinglyImplementedInterfaces = false;
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); $loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*');
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); // loading twice should not be an issue $loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); // loading twice should not be an issue
@ -121,7 +122,6 @@ class FileLoaderTest extends TestCase
// load everything, except OtherDir/AnotherSub & Foo.php // load everything, except OtherDir/AnotherSub & Foo.php
'Prototype/{%other_dir%/AnotherSub,Foo.php}' 'Prototype/{%other_dir%/AnotherSub,Foo.php}'
); );
$loader->registerAliasesForSinglyImplementedInterfaces();
$this->assertTrue($container->has(Bar::class)); $this->assertTrue($container->has(Bar::class));
$this->assertTrue($container->has(Baz::class)); $this->assertTrue($container->has(Baz::class));
@ -151,7 +151,6 @@ class FileLoaderTest extends TestCase
'Prototype/OtherDir/AnotherSub/DeeperBaz.php', 'Prototype/OtherDir/AnotherSub/DeeperBaz.php',
] ]
); );
$loader->registerAliasesForSinglyImplementedInterfaces();
$this->assertTrue($container->has(Foo::class)); $this->assertTrue($container->has(Foo::class));
$this->assertTrue($container->has(Baz::class)); $this->assertTrue($container->has(Baz::class));
@ -167,7 +166,6 @@ class FileLoaderTest extends TestCase
$prototype = new Definition(); $prototype = new Definition();
$prototype->setPublic(true)->setPrivate(true); $prototype->setPublic(true)->setPrivate(true);
$loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*'); $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*');
$loader->registerAliasesForSinglyImplementedInterfaces();
$this->assertTrue($container->has(Bar::class)); $this->assertTrue($container->has(Bar::class));
$this->assertTrue($container->has(Baz::class)); $this->assertTrue($container->has(Baz::class));
@ -199,7 +197,6 @@ class FileLoaderTest extends TestCase
'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\\', 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\\',
'Prototype/%bad_classes_dir%/*' 'Prototype/%bad_classes_dir%/*'
); );
$loader->registerAliasesForSinglyImplementedInterfaces();
$this->assertTrue($container->has(MissingParent::class)); $this->assertTrue($container->has(MissingParent::class));
@ -218,7 +215,6 @@ class FileLoaderTest extends TestCase
// the Sub is missing from namespace prefix // the Sub is missing from namespace prefix
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/Sub/*'); $loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/Sub/*');
$loader->registerAliasesForSinglyImplementedInterfaces();
} }
public function testRegisterClassesWithIncompatibleExclude() public function testRegisterClassesWithIncompatibleExclude()
@ -234,12 +230,13 @@ class FileLoaderTest extends TestCase
'Prototype/*', 'Prototype/*',
'yaml/*' 'yaml/*'
); );
$loader->registerAliasesForSinglyImplementedInterfaces();
} }
} }
class TestFileLoader extends FileLoader class TestFileLoader extends FileLoader
{ {
public $autoRegisterAliasesForSinglyImplementedInterfaces = true;
public function load($resource, string $type = null) public function load($resource, string $type = null)
{ {
return $resource; return $resource;

View File

@ -37,9 +37,6 @@
"symfony/service-contracts": "^1.0|^2", "symfony/service-contracts": "^1.0|^2",
"symfony/var-dumper": "^4.4|^5.0" "symfony/var-dumper": "^4.4|^5.0"
}, },
"conflict": {
"symfony/http-kernel": "<4.4"
},
"autoload": { "autoload": {
"psr-4": { "Symfony\\Component\\HttpClient\\": "" }, "psr-4": { "Symfony\\Component\\HttpClient\\": "" },
"exclude-from-classmap": [ "exclude-from-classmap": [

View File

@ -19,6 +19,7 @@ use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp; use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface; use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
/** /**
* @group time-sensitive * @group time-sensitive
@ -94,4 +95,101 @@ EOF
$redeliveryStamp2->getRedeliveredAt()->format('Y-m-d H:i:s')), $redeliveryStamp2->getRedeliveredAt()->format('Y-m-d H:i:s')),
$tester->getDisplay(true)); $tester->getDisplay(true));
} }
public function testReceiverShouldBeListable()
{
$receiver = $this->createMock(ReceiverInterface::class);
$command = new FailedMessagesShowCommand(
'failure_receiver',
$receiver
);
$this->expectExceptionMessage('The "failure_receiver" receiver does not support listing or showing specific messages.');
$tester = new CommandTester($command);
$tester->execute(['id' => 15]);
}
public function testListMessages()
{
$sentToFailureStamp = new SentToFailureTransportStamp('async');
$redeliveryStamp = new RedeliveryStamp(0, 'failure_receiver', 'Things are bad!');
$envelope = new Envelope(new \stdClass(), [
new TransportMessageIdStamp(15),
$sentToFailureStamp,
$redeliveryStamp,
]);
$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with()->willReturn([$envelope]);
$command = new FailedMessagesShowCommand(
'failure_receiver',
$receiver
);
$tester = new CommandTester($command);
$tester->execute([]);
$this->assertStringContainsString(sprintf(<<<EOF
15 stdClass %s Things are bad!
EOF
,
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
$tester->getDisplay(true));
}
public function testListMessagesReturnsNoMessagesFound()
{
$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with()->willReturn([]);
$command = new FailedMessagesShowCommand(
'failure_receiver',
$receiver
);
$tester = new CommandTester($command);
$tester->execute([]);
$this->assertStringContainsString('[OK] No failed messages were found.', $tester->getDisplay(true));
}
public function testListMessagesReturnsPaginatedMessages()
{
$sentToFailureStamp = new SentToFailureTransportStamp('async');
$envelope = new Envelope(new \stdClass(), [
new TransportMessageIdStamp(15),
$sentToFailureStamp,
new RedeliveryStamp(0, 'failure_receiver', 'Things are bad!'),
]);
$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with()->willReturn([$envelope]);
$command = new FailedMessagesShowCommand(
'failure_receiver',
$receiver
);
$tester = new CommandTester($command);
$tester->execute(['--max' => 1]);
$this->assertStringContainsString('Showing first 1 messages.', $tester->getDisplay(true));
}
public function testInvalidMessagesThrowsException()
{
$sentToFailureStamp = new SentToFailureTransportStamp('async');
$envelope = new Envelope(new \stdClass(), [
new TransportMessageIdStamp(15),
$sentToFailureStamp,
]);
$receiver = $this->createMock(ListableReceiverInterface::class);
$command = new FailedMessagesShowCommand(
'failure_receiver',
$receiver
);
$this->expectExceptionMessage('The message "15" was not found.');
$tester = new CommandTester($command);
$tester->execute(['id' => 15]);
}
} }

View File

@ -56,11 +56,20 @@ final class FormDataPart extends AbstractMultipartPart
private function prepareFields(array $fields): array private function prepareFields(array $fields): array
{ {
$values = []; $values = [];
array_walk_recursive($fields, function ($item, $key) use (&$values) {
if (!\is_array($item)) { $prepare = function ($item, $key, $root = null) use (&$values, &$prepare) {
$values[] = $this->preparePart($key, $item); $fieldName = $root ? sprintf('%s[%s]', $root, $key) : $key;
if (\is_array($item)) {
array_walk($item, $prepare, $fieldName);
return;
} }
});
$values[] = $this->preparePart($fieldName, $item);
};
array_walk($fields, $prepare);
return $values; return $values;
} }

View File

@ -47,6 +47,34 @@ class FormDataPartTest extends TestCase
$this->assertEquals([$t, $b, $c], $f->getParts()); $this->assertEquals([$t, $b, $c], $f->getParts());
} }
public function testNestedArrayParts()
{
$p1 = new TextPart('content', 'utf-8', 'plain', '8bit');
$f = new FormDataPart([
'foo' => clone $p1,
'bar' => [
'baz' => [
clone $p1,
'qux' => clone $p1,
],
],
]);
$this->assertEquals('multipart', $f->getMediaType());
$this->assertEquals('form-data', $f->getMediaSubtype());
$p1->setName('foo');
$p1->setDisposition('form-data');
$p2 = clone $p1;
$p2->setName('bar[baz][0]');
$p3 = clone $p1;
$p3->setName('bar[baz][qux]');
$this->assertEquals([$p1, $p2, $p3], $f->getParts());
}
public function testToString() public function testToString()
{ {
$p = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif'); $p = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif');

View File

@ -57,8 +57,8 @@ class RequestContext
$this->setMethod($request->getMethod()); $this->setMethod($request->getMethod());
$this->setHost($request->getHost()); $this->setHost($request->getHost());
$this->setScheme($request->getScheme()); $this->setScheme($request->getScheme());
$this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); $this->setHttpPort($request->isSecure() || null === $request->getPort() ? $this->httpPort : $request->getPort());
$this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); $this->setHttpsPort($request->isSecure() && null !== $request->getPort() ? $request->getPort() : $this->httpsPort);
$this->setQueryString($request->server->get('QUERY_STRING', '')); $this->setQueryString($request->server->get('QUERY_STRING', ''));
return $this; return $this;

View File

@ -62,6 +62,7 @@ CHANGELOG
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`.
* Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()`
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.) * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
* Added `AbstractListener` which replaces the deprecated `ListenerInterface`
4.3.0 4.3.0
----- -----

View File

@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
/** /**
@ -31,7 +32,7 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
* *
* @final * @final
*/ */
class GuardAuthenticationListener class GuardAuthenticationListener extends AbstractListener
{ {
private $guardHandler; private $guardHandler;
private $authenticationManager; private $authenticationManager;
@ -58,9 +59,9 @@ class GuardAuthenticationListener
} }
/** /**
* Iterates over each authenticator to see if each wants to authenticate the request. * {@inheritdoc}
*/ */
public function __invoke(RequestEvent $event) public function supports(Request $request): ?bool
{ {
if (null !== $this->logger) { if (null !== $this->logger) {
$context = ['firewall_key' => $this->providerKey]; $context = ['firewall_key' => $this->providerKey];
@ -72,7 +73,39 @@ class GuardAuthenticationListener
$this->logger->debug('Checking for guard authentication credentials.', $context); $this->logger->debug('Checking for guard authentication credentials.', $context);
} }
$guardAuthenticators = [];
foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
if (null !== $this->logger) {
$this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
if ($guardAuthenticator->supports($request)) {
$guardAuthenticators[$key] = $guardAuthenticator;
} elseif (null !== $this->logger) {
$this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
}
if (!$guardAuthenticators) {
return false;
}
$request->attributes->set('_guard_authenticators', $guardAuthenticators);
return true;
}
/**
* Iterates over each authenticator to see if each wants to authenticate the request.
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$guardAuthenticators = $request->attributes->get('_guard_authenticators');
$request->attributes->remove('_guard_authenticators');
foreach ($guardAuthenticators as $key => $guardAuthenticator) {
// get a key that's unique to *this* guard authenticator // get a key that's unique to *this* guard authenticator
// this MUST be the same as GuardAuthenticationProvider // this MUST be the same as GuardAuthenticationProvider
$uniqueGuardKey = $this->providerKey.'_'.$key; $uniqueGuardKey = $this->providerKey.'_'.$key;
@ -93,19 +126,6 @@ class GuardAuthenticationListener
{ {
$request = $event->getRequest(); $request = $event->getRequest();
try { try {
if (null !== $this->logger) {
$this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
// abort the execution of the authenticator if it doesn't support the request
if (!$guardAuthenticator->supports($request)) {
if (null !== $this->logger) {
$this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
return;
}
if (null !== $this->logger) { if (null !== $this->logger) {
$this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); $this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
} }

View File

@ -18,7 +18,7 @@
"require": { "require": {
"php": "^7.2.5", "php": "^7.2.5",
"symfony/security-core": "^5.0", "symfony/security-core": "^5.0",
"symfony/security-http": "^4.4|^5.0" "symfony/security-http": "^4.4.1|^5.0.1"
}, },
"require-dev": { "require-dev": {
"psr/log": "~1.0" "psr/log": "~1.0"

View File

@ -48,7 +48,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com> * @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/ */
abstract class AbstractAuthenticationListener abstract class AbstractAuthenticationListener extends AbstractListener
{ {
protected $options; protected $options;
protected $logger; protected $logger;
@ -102,20 +102,24 @@ abstract class AbstractAuthenticationListener
$this->rememberMeServices = $rememberMeServices; $this->rememberMeServices = $rememberMeServices;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return $this->requiresAuthentication($request);
}
/** /**
* Handles form based authentication. * Handles form based authentication.
* *
* @throws \RuntimeException * @throws \RuntimeException
* @throws SessionUnavailableException * @throws SessionUnavailableException
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
$request = $event->getRequest(); $request = $event->getRequest();
if (!$this->requiresAuthentication($request)) {
return;
}
if (!$request->hasSession()) { if (!$request->hasSession()) {
throw new \RuntimeException('This authentication method requires a session.'); throw new \RuntimeException('This authentication method requires a session.');
} }

View File

@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
/**
* A base class for listeners that can tell whether they should authenticate incoming requests.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractListener
{
final public function __invoke(RequestEvent $event)
{
if (false !== $this->supports($event->getRequest())) {
$this->authenticate($event);
}
}
/**
* Tells whether the authenticate() method should be called or not depending on the incoming request.
*
* Returning null means authenticate() can be called lazily when accessing the token storage.
*/
abstract public function supports(Request $request): ?bool;
/**
* Does whatever is required to authenticate the request, typically calling $event->setResponse() internally.
*/
abstract public function authenticate(RequestEvent $event);
}

View File

@ -34,8 +34,8 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* *
* @internal * @internal
*/ */
abstract class AbstractPreAuthenticatedListener abstract class AbstractPreAuthenticatedListener extends AbstractListener
{
protected $logger; protected $logger;
private $tokenStorage; private $tokenStorage;
private $authenticationManager; private $authenticationManager;
@ -53,20 +53,31 @@ abstract class AbstractPreAuthenticatedListener
} }
/** /**
* Handles pre-authentication. * {@inheritdoc}
*/ */
public function __invoke(RequestEvent $event) public function supports(Request $request): ?bool
{ {
$request = $event->getRequest();
try { try {
list($user, $credentials) = $this->getPreAuthenticatedData($request); $request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request));
} catch (BadCredentialsException $e) { } catch (BadCredentialsException $e) {
$this->clearToken($e); $this->clearToken($e);
return; return false;
} }
return true;
}
/**
* Handles pre-authentication.
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
[$user, $credentials] = $request->attributes->get('_pre_authenticated_data');
$request->attributes->remove('_pre_authenticated_data');
if (null !== $this->logger) { if (null !== $this->logger) {
$this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]); $this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]);
} }

View File

@ -11,10 +11,12 @@
namespace Symfony\Component\Security\Http\Firewall; namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Http\AccessMapInterface;
@ -27,7 +29,7 @@ use Symfony\Component\Security\Http\Event\LazyResponseEvent;
* *
* @final * @final
*/ */
class AccessListener class AccessListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $accessDecisionManager; private $accessDecisionManager;
@ -42,13 +44,24 @@ class AccessListener
$this->authManager = $authManager; $this->authManager = $authManager;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
[$attributes] = $this->map->getPatterns($request);
$request->attributes->set('_access_control_attributes', $attributes);
return $attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes ? true : null;
}
/** /**
* Handles access authorization. * Handles access authorization.
* *
* @throws AccessDeniedException * @throws AccessDeniedException
* @throws AuthenticationCredentialsNotFoundException * @throws AuthenticationCredentialsNotFoundException
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
if (!$event instanceof LazyResponseEvent && null === $token = $this->tokenStorage->getToken()) { if (!$event instanceof LazyResponseEvent && null === $token = $this->tokenStorage->getToken()) {
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
@ -56,9 +69,10 @@ class AccessListener
$request = $event->getRequest(); $request = $event->getRequest();
list($attributes) = $this->map->getPatterns($request); $attributes = $request->attributes->get('_access_control_attributes');
$request->attributes->remove('_access_control_attributes');
if (!$attributes) { if (!$attributes || ([AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes && $event instanceof LazyResponseEvent)) {
return; return;
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\Firewall; namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
@ -26,7 +27,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
* *
* @final * @final
*/ */
class AnonymousAuthenticationListener class AnonymousAuthenticationListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $secret; private $secret;
@ -41,10 +42,18 @@ class AnonymousAuthenticationListener
$this->logger = $logger; $this->logger = $logger;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null; // always run authenticate() lazily with lazy firewalls
}
/** /**
* Handles anonymous authentication. * Handles anonymous authentication.
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
if (null !== $this->tokenStorage->getToken()) { if (null !== $this->tokenStorage->getToken()) {
return; return;

View File

@ -29,7 +29,7 @@ use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterfa
* *
* @final * @final
*/ */
class BasicAuthenticationListener class BasicAuthenticationListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $authenticationManager; private $authenticationManager;
@ -53,10 +53,18 @@ class BasicAuthenticationListener
$this->ignoreFailure = false; $this->ignoreFailure = false;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null !== $request->headers->get('PHP_AUTH_USER');
}
/** /**
* Handles basic authentication. * Handles basic authentication.
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
$request = $event->getRequest(); $request = $event->getRequest();

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\Firewall; namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
@ -24,7 +25,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface
* *
* @final * @final
*/ */
class ChannelListener class ChannelListener extends AbstractListener
{ {
private $map; private $map;
private $authenticationEntryPoint; private $authenticationEntryPoint;
@ -40,10 +41,8 @@ class ChannelListener
/** /**
* Handles channel management. * Handles channel management.
*/ */
public function __invoke(RequestEvent $event) public function supports(Request $request): ?bool
{ {
$request = $event->getRequest();
list(, $channel) = $this->map->getPatterns($request); list(, $channel) = $this->map->getPatterns($request);
if ('https' === $channel && !$request->isSecure()) { if ('https' === $channel && !$request->isSecure()) {
@ -57,11 +56,7 @@ class ChannelListener
} }
} }
$response = $this->authenticationEntryPoint->start($request); return true;
$event->setResponse($response);
return;
} }
if ('http' === $channel && $request->isSecure()) { if ('http' === $channel && $request->isSecure()) {
@ -69,9 +64,18 @@ class ChannelListener
$this->logger->info('Redirecting to HTTP.'); $this->logger->info('Redirecting to HTTP.');
} }
$response = $this->authenticationEntryPoint->start($request); return true;
$event->setResponse($response);
} }
return false;
}
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$response = $this->authenticationEntryPoint->start($request);
$event->setResponse($response);
} }
} }

View File

@ -29,6 +29,7 @@ use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent; use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/** /**
@ -39,7 +40,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* *
* @final * @final
*/ */
class ContextListener class ContextListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $sessionKey; private $sessionKey;
@ -48,6 +49,7 @@ class ContextListener
private $dispatcher; private $dispatcher;
private $registered; private $registered;
private $trustResolver; private $trustResolver;
private $rememberMeServices;
private $sessionTrackerEnabler; private $sessionTrackerEnabler;
/** /**
@ -68,10 +70,18 @@ class ContextListener
$this->sessionTrackerEnabler = $sessionTrackerEnabler; $this->sessionTrackerEnabler = $sessionTrackerEnabler;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null; // always run authenticate() lazily with lazy firewalls
}
/** /**
* Reads the Security Token from the session. * Reads the Security Token from the session.
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
if (!$this->registered && null !== $this->dispatcher && $event->isMasterRequest()) { if (!$this->registered && null !== $this->dispatcher && $event->isMasterRequest()) {
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']); $this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
@ -112,6 +122,10 @@ class ContextListener
if ($token instanceof TokenInterface) { if ($token instanceof TokenInterface) {
$token = $this->refreshUser($token); $token = $this->refreshUser($token);
if (!$token && $this->rememberMeServices) {
$this->rememberMeServices->loginFail($request);
}
} elseif (null !== $token) { } elseif (null !== $token) {
if (null !== $this->logger) { if (null !== $this->logger) {
$this->logger->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]); $this->logger->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]);
@ -282,4 +296,9 @@ class ContextListener
{ {
throw new \ErrorException('Class not found: '.$class, 0x37313bc); throw new \ErrorException('Class not found: '.$class, 0x37313bc);
} }
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
{
$this->rememberMeServices = $rememberMeServices;
}
} }

View File

@ -30,7 +30,7 @@ use Symfony\Component\Security\Http\ParameterBagUtils;
* *
* @final * @final
*/ */
class LogoutListener class LogoutListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $options; private $options;
@ -61,6 +61,14 @@ class LogoutListener
$this->handlers[] = $handler; $this->handlers[] = $handler;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return $this->requiresLogout($request);
}
/** /**
* Performs the logout if requested. * Performs the logout if requested.
* *
@ -70,14 +78,10 @@ class LogoutListener
* @throws LogoutException if the CSRF token is invalid * @throws LogoutException if the CSRF token is invalid
* @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response * @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
$request = $event->getRequest(); $request = $event->getRequest();
if (!$this->requiresLogout($request)) {
return;
}
if (null !== $this->csrfTokenManager) { if (null !== $this->csrfTokenManager) {
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\Firewall; namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
@ -30,7 +31,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* *
* @final * @final
*/ */
class RememberMeListener class RememberMeListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $rememberMeServices; private $rememberMeServices;
@ -51,10 +52,18 @@ class RememberMeListener
$this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy; $this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy;
} }
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null; // always run authenticate() lazily with lazy firewalls
}
/** /**
* Handles remember-me cookie based authentication. * Handles remember-me cookie based authentication.
*/ */
public function __invoke(RequestEvent $event) public function authenticate(RequestEvent $event)
{ {
if (null !== $this->tokenStorage->getToken()) { if (null !== $this->tokenStorage->getToken()) {
return; return;

View File

@ -37,7 +37,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* *
* @final * @final
*/ */
class SwitchUserListener class SwitchUserListener extends AbstractListener
{ {
const EXIT_VALUE = '_exit'; const EXIT_VALUE = '_exit';
@ -71,14 +71,10 @@ class SwitchUserListener
} }
/** /**
* Handles the switch to another user. * {@inheritdoc}
*
* @throws \LogicException if switching to a user failed
*/ */
public function __invoke(RequestEvent $event) public function supports(Request $request): ?bool
{ {
$request = $event->getRequest();
// usernames can be falsy // usernames can be falsy
$username = $request->get($this->usernameParameter); $username = $request->get($this->usernameParameter);
@ -88,9 +84,26 @@ class SwitchUserListener
// if it's still "empty", nothing to do. // if it's still "empty", nothing to do.
if (null === $username || '' === $username) { if (null === $username || '' === $username) {
return; return false;
} }
$request->attributes->set('_switch_user_username', $username);
return true;
}
/**
* Handles the switch to another user.
*
* @throws \LogicException if switching to a user failed
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$username = $request->attributes->get('_switch_user_username');
$request->attributes->remove('_switch_user_username');
if (null === $this->tokenStorage->getToken()) { if (null === $this->tokenStorage->getToken()) {
throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.');
} }

View File

@ -43,7 +43,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* *
* @final * @final
*/ */
class UsernamePasswordJsonAuthenticationListener class UsernamePasswordJsonAuthenticationListener extends AbstractListener
{ {
private $tokenStorage; private $tokenStorage;
private $authenticationManager; private $authenticationManager;
@ -71,19 +71,27 @@ class UsernamePasswordJsonAuthenticationListener
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
} }
public function __invoke(RequestEvent $event) public function supports(Request $request): ?bool
{ {
$request = $event->getRequest();
if (false === strpos($request->getRequestFormat(), 'json') if (false === strpos($request->getRequestFormat(), 'json')
&& false === strpos($request->getContentType(), 'json') && false === strpos($request->getContentType(), 'json')
) { ) {
return; return false;
} }
if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) {
return; return false;
} }
return true;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$data = json_decode($request->getContent()); $data = json_decode($request->getContent());
try { try {

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http\Tests\Firewall;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
@ -26,7 +27,7 @@ class AccessListenerTest extends TestCase
public function testHandleWhenTheAccessDecisionManagerDecidesToRefuseAccess() public function testHandleWhenTheAccessDecisionManagerDecidesToRefuseAccess()
{ {
$this->expectException('Symfony\Component\Security\Core\Exception\AccessDeniedException'); $this->expectException('Symfony\Component\Security\Core\Exception\AccessDeniedException');
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); $request = new Request();
$accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock();
$accessMap $accessMap
@ -65,19 +66,12 @@ class AccessListenerTest extends TestCase
$this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock()
); );
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST));
$event
->expects($this->any())
->method('getRequest')
->willReturn($request)
;
$listener($event);
} }
public function testHandleWhenTheTokenIsNotAuthenticated() public function testHandleWhenTheTokenIsNotAuthenticated()
{ {
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); $request = new Request();
$accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock();
$accessMap $accessMap
@ -136,19 +130,12 @@ class AccessListenerTest extends TestCase
$authManager $authManager
); );
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST));
$event
->expects($this->any())
->method('getRequest')
->willReturn($request)
;
$listener($event);
} }
public function testHandleWhenThereIsNoAccessMapEntryMatchingTheRequest() public function testHandleWhenThereIsNoAccessMapEntryMatchingTheRequest()
{ {
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); $request = new Request();
$accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock();
$accessMap $accessMap
@ -178,19 +165,12 @@ class AccessListenerTest extends TestCase
$this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock()
); );
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST));
$event
->expects($this->any())
->method('getRequest')
->willReturn($request)
;
$listener($event);
} }
public function testHandleWhenAccessMapReturnsEmptyAttributes() public function testHandleWhenAccessMapReturnsEmptyAttributes()
{ {
$request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock(); $request = new Request();
$accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock(); $accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock();
$accessMap $accessMap
@ -213,12 +193,7 @@ class AccessListenerTest extends TestCase
$this->getMockBuilder(AuthenticationManagerInterface::class)->getMock() $this->getMockBuilder(AuthenticationManagerInterface::class)->getMock()
); );
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST);
$event
->expects($this->any())
->method('getRequest')
->willReturn($request)
;
$listener(new LazyResponseEvent($event)); $listener(new LazyResponseEvent($event));
} }
@ -233,7 +208,7 @@ class AccessListenerTest extends TestCase
->willReturn(null) ->willReturn(null)
; ;
$request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock(); $request = new Request();
$accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock(); $accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock();
$accessMap $accessMap
@ -250,13 +225,6 @@ class AccessListenerTest extends TestCase
$this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock()
); );
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST));
$event
->expects($this->any())
->method('getRequest')
->willReturn($request)
;
$listener($event);
} }
} }

View File

@ -12,7 +12,9 @@
namespace Symfony\Component\Security\Http\Tests\Firewall; namespace Symfony\Component\Security\Http\Tests\Firewall;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener;
@ -38,7 +40,7 @@ class AnonymousAuthenticationListenerTest extends TestCase
; ;
$listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager); $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager);
$listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST));
} }
public function testHandleWithTokenStorageHavingNoToken() public function testHandleWithTokenStorageHavingNoToken()
@ -69,7 +71,7 @@ class AnonymousAuthenticationListenerTest extends TestCase
; ;
$listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager); $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager);
$listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST));
} }
public function testHandledEventIsLogged() public function testHandledEventIsLogged()
@ -84,6 +86,6 @@ class AnonymousAuthenticationListenerTest extends TestCase
$authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(); $authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock();
$listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', $logger, $authenticationManager); $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', $logger, $authenticationManager);
$listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST));
} }
} }

View File

@ -36,6 +36,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent; use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
use Symfony\Component\Security\Http\Firewall\ContextListener; use Symfony\Component\Security\Http\Firewall\ContextListener;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait; use Symfony\Contracts\Service\ServiceLocatorTrait;
class ContextListenerTest extends TestCase class ContextListenerTest extends TestCase
@ -262,10 +263,23 @@ class ContextListenerTest extends TestCase
$tokenStorage = new TokenStorage(); $tokenStorage = new TokenStorage();
$badRefreshedUser = new User('foobar', 'baz'); $badRefreshedUser = new User('foobar', 'baz');
$goodRefreshedUser = new User('foobar', 'bar'); $goodRefreshedUser = new User('foobar', 'bar');
$tokenStorage = $this->handleEventWithPreviousSession([new SupportingUserProvider($badRefreshedUser), new SupportingUserProvider($goodRefreshedUser)], $goodRefreshedUser, true); $tokenStorage = $this->handleEventWithPreviousSession([new SupportingUserProvider($badRefreshedUser), new SupportingUserProvider($goodRefreshedUser)], $goodRefreshedUser);
$this->assertSame($goodRefreshedUser, $tokenStorage->getToken()->getUser()); $this->assertSame($goodRefreshedUser, $tokenStorage->getToken()->getUser());
} }
public function testRememberMeGetsCanceledIfTokenIsDeauthenticated()
{
$tokenStorage = new TokenStorage();
$refreshedUser = new User('foobar', 'baz');
$rememberMeServices = $this->createMock(RememberMeServicesInterface::class);
$rememberMeServices->expects($this->once())->method('loginFail');
$tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], null, $rememberMeServices);
$this->assertNull($tokenStorage->getToken());
}
public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() public function testTryAllUserProvidersUntilASupportingUserProviderIsFound()
{ {
$refreshedUser = new User('foobar', 'baz'); $refreshedUser = new User('foobar', 'baz');
@ -372,7 +386,7 @@ class ContextListenerTest extends TestCase
return $session; return $session;
} }
private function handleEventWithPreviousSession($userProviders, UserInterface $user = null) private function handleEventWithPreviousSession($userProviders, UserInterface $user = null, RememberMeServicesInterface $rememberMeServices = null)
{ {
$user = $user ?: new User('foo', 'bar'); $user = $user ?: new User('foo', 'bar');
$session = new Session(new MockArraySessionStorage()); $session = new Session(new MockArraySessionStorage());
@ -392,6 +406,10 @@ class ContextListenerTest extends TestCase
$sessionTrackerEnabler = [$tokenStorage, 'enableUsageTracking']; $sessionTrackerEnabler = [$tokenStorage, 'enableUsageTracking'];
$listener = new ContextListener($tokenStorage, $userProviders, 'context_key', null, null, null, $sessionTrackerEnabler); $listener = new ContextListener($tokenStorage, $userProviders, 'context_key', null, null, null, $sessionTrackerEnabler);
if ($rememberMeServices) {
$listener->setRememberMeServices($rememberMeServices);
}
$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->assertSame($usageIndex, $session->getUsageIndex()); $this->assertSame($usageIndex, $session->getUsageIndex());

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\RememberMeListener; use Symfony\Component\Security\Http\Firewall\RememberMeListener;
use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\Security\Http\SecurityEvents;
@ -27,7 +28,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage) = $this->getListener(); list($listener, $tokenStorage) = $this->getListener();
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()) ->willReturn($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock())
; ;
@ -45,7 +46,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service) = $this->getListener(); list($listener, $tokenStorage, $service) = $this->getListener();
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -57,11 +58,6 @@ class RememberMeListenerTest extends TestCase
; ;
$event = $this->getGetResponseEvent(); $event = $this->getGetResponseEvent();
$event
->expects($this->once())
->method('getRequest')
->willReturn(new Request())
;
$this->assertNull($listener($event)); $this->assertNull($listener($event));
} }
@ -73,7 +69,7 @@ class RememberMeListenerTest extends TestCase
$exception = new AuthenticationException('Authentication failed.'); $exception = new AuthenticationException('Authentication failed.');
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -96,12 +92,7 @@ class RememberMeListenerTest extends TestCase
->willThrowException($exception) ->willThrowException($exception)
; ;
$event = $this->getGetResponseEvent(); $event = $this->getGetResponseEvent($request);
$event
->expects($this->once())
->method('getRequest')
->willReturn($request)
;
$listener($event); $listener($event);
} }
@ -113,7 +104,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, false); list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, false);
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -137,11 +128,6 @@ class RememberMeListenerTest extends TestCase
; ;
$event = $this->getGetResponseEvent(); $event = $this->getGetResponseEvent();
$event
->expects($this->once())
->method('getRequest')
->willReturn(new Request())
;
$listener($event); $listener($event);
} }
@ -151,7 +137,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service, $manager) = $this->getListener(); list($listener, $tokenStorage, $service, $manager) = $this->getListener();
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -174,11 +160,6 @@ class RememberMeListenerTest extends TestCase
; ;
$event = $this->getGetResponseEvent(); $event = $this->getGetResponseEvent();
$event
->expects($this->once())
->method('getRequest')
->willReturn(new Request())
;
$listener($event); $listener($event);
} }
@ -188,7 +169,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service, $manager) = $this->getListener(); list($listener, $tokenStorage, $service, $manager) = $this->getListener();
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -213,11 +194,6 @@ class RememberMeListenerTest extends TestCase
; ;
$event = $this->getGetResponseEvent(); $event = $this->getGetResponseEvent();
$event
->expects($this->once())
->method('getRequest')
->willReturn(new Request())
;
$listener($event); $listener($event);
} }
@ -227,7 +203,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service, $manager, , , $sessionStrategy) = $this->getListener(false, true, true); list($listener, $tokenStorage, $service, $manager, , , $sessionStrategy) = $this->getListener(false, true, true);
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -258,25 +234,10 @@ class RememberMeListenerTest extends TestCase
->willReturn(true) ->willReturn(true)
; ;
$request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->getMock(); $request = new Request();
$request $request->setSession($session);
->expects($this->once())
->method('hasSession')
->willReturn(true)
;
$request $event = $this->getGetResponseEvent($request);
->expects($this->once())
->method('getSession')
->willReturn($session)
;
$event = $this->getGetResponseEvent();
$event
->expects($this->once())
->method('getRequest')
->willReturn($request)
;
$sessionStrategy $sessionStrategy
->expects($this->once()) ->expects($this->once())
@ -292,7 +253,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, true, false); list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, true, false);
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -327,25 +288,10 @@ class RememberMeListenerTest extends TestCase
->method('migrate') ->method('migrate')
; ;
$request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->getMock(); $request = new Request();
$request $request->setSession($session);
->expects($this->any())
->method('hasSession')
->willReturn(true)
;
$request $event = $this->getGetResponseEvent($request);
->expects($this->any())
->method('getSession')
->willReturn($session)
;
$event = $this->getGetResponseEvent();
$event
->expects($this->once())
->method('getRequest')
->willReturn($request)
;
$listener($event); $listener($event);
} }
@ -355,7 +301,7 @@ class RememberMeListenerTest extends TestCase
list($listener, $tokenStorage, $service, $manager, , $dispatcher) = $this->getListener(true); list($listener, $tokenStorage, $service, $manager, , $dispatcher) = $this->getListener(true);
$tokenStorage $tokenStorage
->expects($this->once()) ->expects($this->any())
->method('getToken') ->method('getToken')
->willReturn(null) ->willReturn(null)
; ;
@ -380,12 +326,6 @@ class RememberMeListenerTest extends TestCase
; ;
$event = $this->getGetResponseEvent(); $event = $this->getGetResponseEvent();
$request = new Request();
$event
->expects($this->once())
->method('getRequest')
->willReturn($request)
;
$dispatcher $dispatcher
->expects($this->once()) ->expects($this->once())
@ -399,9 +339,20 @@ class RememberMeListenerTest extends TestCase
$listener($event); $listener($event);
} }
protected function getGetResponseEvent() protected function getGetResponseEvent(Request $request = null): RequestEvent
{ {
return $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $request = $request ?? new Request();
$event = $this->getMockBuilder(RequestEvent::class)
->setConstructorArgs([$this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST])
->getMock();
$event
->expects($this->any())
->method('getRequest')
->willReturn($request)
;
return $event;
} }
protected function getResponseEvent(): ResponseEvent protected function getResponseEvent(): ResponseEvent

View File

@ -318,6 +318,54 @@
<source>Error</source> <source>Error</source>
<target>Napaka</target> <target>Napaka</target>
</trans-unit> </trans-unit>
<trans-unit id="83">
<source>This is not a valid UUID.</source>
<target>To ni veljaven UUID.</target>
</trans-unit>
<trans-unit id="84">
<source>This value should be a multiple of {{ compared_value }}.</source>
<target>Ta vrednost bi morala biti večkratnik od {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="85">
<source>This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.</source>
<target>Ta poslovna identifikacijska koda (BIC) ni povezana z IBAN {{ iban }}.</target>
</trans-unit>
<trans-unit id="86">
<source>This value should be valid JSON.</source>
<target>Ta vrednost bi morala biti veljaven JSON.</target>
</trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>Ta zbirka bi morala vsebovati samo edinstvene elemente.</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>Ta vrednost bi morala biti pozitivna.</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>Ta vrednost bi morala biti pozitivna ali enaka nič.</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>Ta vrednost bi morala biti negativna.</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Ta vrednost bi morala biti negativna ali enaka nič.</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>Ta vrednost ni veljaven časovni pas.</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>To geslo je ušlo pri kršitvi varnosti podatkov in ga ne smete uporabljati. Prosimo, uporabite drugo geslo.</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Ta vrednost bi morala biti med {{ min }} in {{ max }}.</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>