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 () {
set -e
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
echo -e "\\n\\e[33;1mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m"
return
@ -312,10 +317,6 @@ install:
PHPUNIT_X="$PHPUNIT_X,legacy"
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 {}"
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.
* 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.
* 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)
**Before**

View File

@ -437,7 +437,7 @@ Security
* `SimpleAuthenticatorInterface`, `SimpleFormAuthenticatorInterface`, `SimplePreAuthenticatorInterface`,
`SimpleAuthenticationProvider`, `SimpleAuthenticationHandler`, `SimpleFormAuthenticationListener` and
`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.
* `\Serializable` interface has been removed from `AbstractToken` and `AuthenticationException`,
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\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
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
* 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
* they can't be determined
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public function getQueryBuilderPartsForCachingHash(QueryBuilder $queryBuilder): ?array
public function getQueryBuilderPartsForCachingHash($queryBuilder): ?array
{
return null;
}

View File

@ -50,6 +50,10 @@ class EntityType extends DoctrineType
*/
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);
}
@ -65,11 +69,17 @@ class EntityType extends DoctrineType
* We consider two query builders with an equal SQL string and
* equal parameters to be equal.
*
* @param QueryBuilder $queryBuilder
*
* @internal This method is public to be usable as callback. It should not
* 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 [
$queryBuilder->getQuery()->getSQL(),
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\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Loader\ArrayLoader;
class TwigExtractorTest extends TestCase
@ -74,31 +73,23 @@ class TwigExtractorTest extends TestCase
/**
* @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->addExtension(new TranslationExtension($this->getMockBuilder(TranslatorInterface::class)->getMock()));
$extractor = new TwigExtractor($twig);
try {
$extractor->extract($resources, new MessageCatalogue('en'));
} 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;
}
$catalogue = new MessageCatalogue('en');
$extractor->extract($resources, $catalogue);
$this->assertSame($messages, $catalogue->all());
}
public function resourcesWithSyntaxErrorsProvider(): array
{
return [
[__DIR__.'/../Fixtures'],
[__DIR__.'/../Fixtures/extractor/syntax_error.twig'],
[new \SplFileInfo(__DIR__.'/../Fixtures/extractor/syntax_error.twig')],
[__DIR__.'/../Fixtures', ['messages' => ['Hi!' => 'Hi!']]],
[__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;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Translation\Extractor\AbstractFileExtractor;
use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue;
@ -58,13 +57,7 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
try {
$this->extractTemplate(file_get_contents($file->getPathname()), $catalogue);
} catch (Error $e) {
if ($file instanceof \SplFileInfo) {
$path = $file->getRealPath() ?: $file->getPathname();
$name = $file instanceof SplFileInfo ? $file->getRelativePathname() : $path;
$e->setSourceContext(new Source('', $name, $path));
}
throw $e;
// ignore errors, these should be fixed by using the linter
}
}
}

View File

@ -83,7 +83,11 @@ class RememberMeFactory implements SecurityFactoryInterface
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
->getDefinition($serviceId)
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])

View File

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

View File

@ -151,9 +151,7 @@
<argument type="service" id="security.exception_listener" />
<argument /> <!-- LogoutListener -->
<argument /> <!-- FirewallConfig -->
<argument type="service" id="security.access_listener" />
<argument type="service" id="security.untracked_token_storage" />
<argument type="service" id="security.access_map" />
</service>
<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\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\Firewall\AccessListener;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
@ -28,17 +25,13 @@ use Symfony\Component\Security\Http\Firewall\LogoutListener;
*/
class LazyFirewallContext extends FirewallContext
{
private $accessListener;
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);
$this->accessListener = $accessListener;
$this->tokenStorage = $tokenStorage;
$this->map = $map;
}
public function getListeners(): iterable
@ -48,21 +41,37 @@ class LazyFirewallContext extends FirewallContext
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);
foreach (parent::getListeners() as $listener) {
foreach ($listeners as $listener) {
$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-csrf": "^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": {
"doctrine/doctrine-bundle": "^1.5|^2.0",

View File

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

View File

@ -183,15 +183,17 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
}
$props = [
'file' => $trace[$i]['file'],
'line' => $trace[$i]['line'],
'file' => isset($trace[$i]['file']) ? $trace[$i]['file'] : null,
'line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : null,
'trace' => \array_slice($trace, 1 + $i),
];
foreach ($props as $p => $v) {
$r = new \ReflectionProperty('Exception', $p);
$r->setAccessible(true);
$r->setValue($e, $v);
if (null !== $v) {
$r = new \ReflectionProperty('Exception', $p);
$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) {
yield preg_replace('/^ @@.*/m', '', $m);
$defaults = [];
$parametersWithUndefinedConstants = [];
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);
}

View File

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

View File

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

View File

@ -189,19 +189,20 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
// Acm<NEWLINE>
// Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
// <NEWLINE>
// <UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <DOWN ARROW><NEWLINE>
// S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><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();
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$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('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('AsseticBundle', $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()

View File

@ -316,6 +316,11 @@ class XmlDumper extends Dumper
if (\in_array($value, ['null', 'true', 'false'], true)) {
$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));
$element->appendChild($text);
}

View File

@ -36,6 +36,7 @@ abstract class FileLoader extends BaseFileLoader
protected $instanceof = [];
protected $interfaces = [];
protected $singlyImplemented = [];
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
{
@ -114,12 +115,16 @@ abstract class FileLoader extends BaseFileLoader
}
}
}
if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
public function registerAliasesForSinglyImplementedInterfaces()
{
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);
}
}

View File

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

View File

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

View File

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

View File

@ -11,6 +11,17 @@ $container = new ContainerBuilder(new ParameterBag([
'values' => [true, false, null, 0, 1000.3, 'true', 'false', 'null'],
'binary' => "\xf0\xf0\xf0\xf0",
'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;

View File

@ -108,6 +108,17 @@ class ProjectServiceContainer extends Container
],
'binary' => 'ðððð',
'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 key="binary" type="binary">8PDw8A==</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>
<services>
<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']
binary: !!binary 8PDw8A==
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:
service_container:

View File

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

View File

@ -37,9 +37,6 @@
"symfony/service-contracts": "^1.0|^2",
"symfony/var-dumper": "^4.4|^5.0"
},
"conflict": {
"symfony/http-kernel": "<4.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\HttpClient\\": "" },
"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\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
/**
* @group time-sensitive
@ -94,4 +95,101 @@ EOF
$redeliveryStamp2->getRedeliveredAt()->format('Y-m-d H:i:s')),
$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
{
$values = [];
array_walk_recursive($fields, function ($item, $key) use (&$values) {
if (!\is_array($item)) {
$values[] = $this->preparePart($key, $item);
$prepare = function ($item, $key, $root = null) use (&$values, &$prepare) {
$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;
}

View File

@ -47,6 +47,34 @@ class FormDataPartTest extends TestCase
$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()
{
$p = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif');

View File

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

View File

@ -62,6 +62,7 @@ CHANGELOG
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`.
* 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 `AbstractListener` which replaces the deprecated `ListenerInterface`
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\GuardAuthenticatorHandler;
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
/**
@ -31,7 +32,7 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
*
* @final
*/
class GuardAuthenticationListener
class GuardAuthenticationListener extends AbstractListener
{
private $guardHandler;
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) {
$context = ['firewall_key' => $this->providerKey];
@ -72,7 +73,39 @@ class GuardAuthenticationListener
$this->logger->debug('Checking for guard authentication credentials.', $context);
}
$guardAuthenticators = [];
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
// this MUST be the same as GuardAuthenticationProvider
$uniqueGuardKey = $this->providerKey.'_'.$key;
@ -93,19 +126,6 @@ class GuardAuthenticationListener
{
$request = $event->getRequest();
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) {
$this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}

View File

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

View File

@ -48,7 +48,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class AbstractAuthenticationListener
abstract class AbstractAuthenticationListener extends AbstractListener
{
protected $options;
protected $logger;
@ -102,20 +102,24 @@ abstract class AbstractAuthenticationListener
$this->rememberMeServices = $rememberMeServices;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return $this->requiresAuthentication($request);
}
/**
* Handles form based authentication.
*
* @throws \RuntimeException
* @throws SessionUnavailableException
*/
public function __invoke(RequestEvent $event)
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
if (!$this->requiresAuthentication($request)) {
return;
}
if (!$request->hasSession()) {
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
*/
abstract class AbstractPreAuthenticatedListener
{
abstract class AbstractPreAuthenticatedListener extends AbstractListener
protected $logger;
private $tokenStorage;
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 {
list($user, $credentials) = $this->getPreAuthenticatedData($request);
$request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request));
} catch (BadCredentialsException $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) {
$this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]);
}

View File

@ -11,10 +11,12 @@
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
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\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Http\AccessMapInterface;
@ -27,7 +29,7 @@ use Symfony\Component\Security\Http\Event\LazyResponseEvent;
*
* @final
*/
class AccessListener
class AccessListener extends AbstractListener
{
private $tokenStorage;
private $accessDecisionManager;
@ -42,13 +44,24 @@ class AccessListener
$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.
*
* @throws AccessDeniedException
* @throws AuthenticationCredentialsNotFoundException
*/
public function __invoke(RequestEvent $event)
public function authenticate(RequestEvent $event)
{
if (!$event instanceof LazyResponseEvent && null === $token = $this->tokenStorage->getToken()) {
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
@ -56,9 +69,10 @@ class AccessListener
$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;
}

View File

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

View File

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

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
@ -24,7 +25,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface
*
* @final
*/
class ChannelListener
class ChannelListener extends AbstractListener
{
private $map;
private $authenticationEntryPoint;
@ -40,10 +41,8 @@ class ChannelListener
/**
* Handles channel management.
*/
public function __invoke(RequestEvent $event)
public function supports(Request $request): ?bool
{
$request = $event->getRequest();
list(, $channel) = $this->map->getPatterns($request);
if ('https' === $channel && !$request->isSecure()) {
@ -57,11 +56,7 @@ class ChannelListener
}
}
$response = $this->authenticationEntryPoint->start($request);
$event->setResponse($response);
return;
return true;
}
if ('http' === $channel && $request->isSecure()) {
@ -69,9 +64,18 @@ class ChannelListener
$this->logger->info('Redirecting to HTTP.');
}
$response = $this->authenticationEntryPoint->start($request);
$event->setResponse($response);
return true;
}
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\UserProviderInterface;
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
@ -39,7 +40,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
*
* @final
*/
class ContextListener
class ContextListener extends AbstractListener
{
private $tokenStorage;
private $sessionKey;
@ -48,6 +49,7 @@ class ContextListener
private $dispatcher;
private $registered;
private $trustResolver;
private $rememberMeServices;
private $sessionTrackerEnabler;
/**
@ -68,10 +70,18 @@ class ContextListener
$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.
*/
public function __invoke(RequestEvent $event)
public function authenticate(RequestEvent $event)
{
if (!$this->registered && null !== $this->dispatcher && $event->isMasterRequest()) {
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
@ -112,6 +122,10 @@ class ContextListener
if ($token instanceof TokenInterface) {
$token = $this->refreshUser($token);
if (!$token && $this->rememberMeServices) {
$this->rememberMeServices->loginFail($request);
}
} elseif (null !== $token) {
if (null !== $this->logger) {
$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);
}
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
{
$this->rememberMeServices = $rememberMeServices;
}
}

View File

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

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
@ -30,7 +31,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
*
* @final
*/
class RememberMeListener
class RememberMeListener extends AbstractListener
{
private $tokenStorage;
private $rememberMeServices;
@ -51,10 +52,18 @@ class RememberMeListener
$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.
*/
public function __invoke(RequestEvent $event)
public function authenticate(RequestEvent $event)
{
if (null !== $this->tokenStorage->getToken()) {
return;

View File

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

View File

@ -43,7 +43,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
*
* @final
*/
class UsernamePasswordJsonAuthenticationListener
class UsernamePasswordJsonAuthenticationListener extends AbstractListener
{
private $tokenStorage;
private $authenticationManager;
@ -71,19 +71,27 @@ class UsernamePasswordJsonAuthenticationListener
$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')
&& false === strpos($request->getContentType(), 'json')
) {
return;
return false;
}
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());
try {

View File

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

View File

@ -12,7 +12,9 @@
namespace Symfony\Component\Security\Http\Tests\Firewall;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener;
@ -38,7 +40,7 @@ class AnonymousAuthenticationListenerTest extends TestCase
;
$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()
@ -69,7 +71,7 @@ class AnonymousAuthenticationListenerTest extends TestCase
;
$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()
@ -84,6 +86,6 @@ class AnonymousAuthenticationListenerTest extends TestCase
$authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock();
$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\Http\Event\DeauthenticatedEvent;
use Symfony\Component\Security\Http\Firewall\ContextListener;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;
class ContextListenerTest extends TestCase
@ -262,10 +263,23 @@ class ContextListenerTest extends TestCase
$tokenStorage = new TokenStorage();
$badRefreshedUser = new User('foobar', 'baz');
$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());
}
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()
{
$refreshedUser = new User('foobar', 'baz');
@ -372,7 +386,7 @@ class ContextListenerTest extends TestCase
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');
$session = new Session(new MockArraySessionStorage());
@ -392,6 +406,10 @@ class ContextListenerTest extends TestCase
$sessionTrackerEnabler = [$tokenStorage, 'enableUsageTracking'];
$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));
$this->assertSame($usageIndex, $session->getUsageIndex());

View File

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

View File

@ -318,6 +318,54 @@
<source>Error</source>
<target>Napaka</target>
</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>
</file>
</xliff>