8000 Merge branch '3.4' into 4.3 · symfony/symfony@cad1417 · GitHub
[go: up one dir, main page]

Skip to content

Commit cad1417

Browse files
Merge branch '3.4' into 4.3
* 3.4: [Security] Fix clearing remember-me cookie after deauthentication more robust initialization from request
2 parents 2a9c31e + 9b3cc04 commit cad1417

File tree

10 files changed

+180
-9
lines changed

10 files changed

+180
-9
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
8383
throw new \RuntimeException('Each "security.remember_me_aware" tag must have a provider attribute.');
8484
}
8585

86-
$userProviders[] = new Reference($attribute['provider']);
86+
// context listeners don't need a provider
87+
if ('none' !== $attribute['provider']) {
88+
$userProviders[] = new Reference($attribute['provider']);
89+
}
90+
8791
$container
8892
->getDefinition($serviceId)
8993
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,11 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
321321
$listeners[] = new Reference('security.channel_listener');
322322

323323
$contextKey = null;
324+
$contextListenerId = null;
324325
// Context serializer listener
325326
if (false === $firewall['stateless']) {
326327
$contextKey = $firewall['context'] ?? $id;
327-
$listeners[] = new Reference($this->createContextListener($container, $contextKey));
328+
$listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey));
328329
$sessionStrategyId = 'security.authentication.session_strategy';
329330
} else {
330331
$this->statelessFirewallKeys[] = $id;
@@ -397,7 +398,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
397398
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
398399

399400
// Authentication listeners
400-
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint);
401+
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId);
401402

402403
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);
403404

@@ -452,7 +453,7 @@ private function createContextListener($container, $contextKey)
452453
return $this->contextListeners[$contextKey] = $listenerId;
453454
}
454455

455-
private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider = null, array $providerIds, $defaultEntryPoint)
456+
private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider = null, array $providerIds, $defaultEntryPoint, $contextListenerId = null)
456457
{
457458
$listeners = [];
458459
$hasListeners = false;
@@ -470,6 +471,10 @@ private function createAuthenticationListeners($container, $id, $firewall, &$aut
470471
} elseif ('remember_me' === $key) {
471472
// RememberMeFactory will use the firewall secret when created
472473
$userProvider = null;
474+
475+
if ($contextListenerId) {
476+
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
477+
}
473478
} elseif ($defaultProvider) {
474479
$userProvider = $defaultProvider;
475480
} elseif (empty($providerIds)) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
16+
use Symfony\Component\Security\Core\User\User;
17+
use Symfony\Component\Security\Core\User\UserInterface;
18+
use Symfony\Component\Security\Core\User\UserProviderInterface;
19+
20+
class ClearRememberMeTest extends AbstractWebTestCase
21+
{
22+
public function testUserChangeClearsCookie()
23+
{
24+
$client = $this->createClient(['test_case' => 'ClearRememberMe', 'root_config' => 'config.yml']);
25+
26+
$client->request('POST', '/login', [
27+
'_username' => 'johannes',
28+
'_password' => 'test',
29+
]);
30+
31+
$this->assertSame(302, $client->getResponse()->getStatusCode());
32+
$cookieJar = $client->getCookieJar();
33+
$this->assertNotNull($cookieJar->get('REMEMBERME'));
34+
35+
$client->request('GET', '/foo');
36+
$this->assertSame(200, $client->getResponse()->getStatusCode());
37+
$this->assertNull($cookieJar->get('REMEMBERME'));
38+
}
39+
}
40+
41+
class RememberMeFooController
42+
{
43+
public function __invoke(UserInterface $user)
44+
{
45+
return new Response($user->getUsername());
46+
}
47+
}
48+
49+
class RememberMeUserProvider implements UserProviderInterface
50+
{
51+
private $inner;
52+
53+
public function __construct(InMemoryUserProvider $inner)
54+
{
55+
$this->inner = $inner;
56+
}
57+
58+
public function loadUserByUsername($username)
59+
{
60+
return $this->inner->loadUserByUsername($username);
61+
}
62+
63+
public function refreshUser(UserInterface $user)
64+
{
65+
$user = $this->inner->refreshUser($user);
66+
67+
$alterUser = \Closure::bind(function (User $user) { $user->password = 'foo'; }, null, User::class);
68+
$alterUser($user);
69+
70+
return $user;
71+
}
72+
73+
public function supportsClass($class)
74+
{
75+
return $this->inner->supportsClass($class);
76+
}
77+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
13+
use Symfony\Bundle\SecurityBundle\SecurityBundle;
14+
15+
return [
16+
new FrameworkBundle(),
17+
new SecurityBundle(),
18+
];
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
imports:
2+
- { resource: ./../config/framework.yml }
3+
4+
security:
5+
encoders:
6+
Symfony\Component\Security\Core\User\User: plaintext
7+
8+
providers:
9+
in_memory:
10+
memory:
11+
users:
12+
johannes: { password: test, roles: [ROLE_USER] }
13+
14+
firewalls:
15+
default:
16+
form_login:
17+
check_path: login
18+
remember_me: true
19+
remember_me:
20+
always_remember_me: true
21+
secret: key
22+
anonymous: ~
23+
24+
access_control:
25+
- { path: ^/foo, roles: ROLE_USER }
26+
27+
services:
28+
Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeUserProvider:
29+
public: true
30+
decorates: security.user.provider.concrete.in_memory
31+
arguments: ['@Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeUserProvider.inner']
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
login:
2+
path: /login
3+
4+
foo:
5+
path: /foo
6+
defaults:
7+
_controller: Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeFooController

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"symfony/security-core": "~4.3",
2525
"symfony/security-csrf": "~4.2",
2626
"symfony/security-guard": "~4.2",
27-
"symfony/security-http": "^4.3.8"
27+
"symfony/security-http": "~4.3.9|^4.4.1"
2828
},
2929
"require-dev": {
3030
"symfony/asset": "~3.4|~4.0",

src/Symfony/Component/Routing/RequestContext.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ public function fromRequest(Request $request)
5757
$this->setMethod($request->getMethod());
5858
$this->setHost($request->getHost());
5959
$this->setScheme($request->getScheme());
60-
$this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort());
61-
$this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort);
60+
$this->setHttpPort($request->isSecure() || null === $request->getPort() ? $this->httpPort : $request->getPort());
61+
$this->setHttpsPort($request->isSecure() && null !== $request->getPort() ? $request->getPort() : $this->httpsPort);
6262
$this->setQueryString($request->server->get('QUERY_STRING', ''));
6363

6464
return $this;

src/Symfony/Component/Security/Http/Firewall/ContextListener.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Symfony\Component\Security\Core\User\UserInterface;
3131
use Symfony\Component\Security\Core\User\UserProviderInterface;
3232
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
33+
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
3334

3435
/**
3536
* ContextListener manages the SecurityContext persistence through a session.
@@ -50,6 +51,7 @@ class ContextListener implements ListenerInterface
5051
private $dispatcher;
5152
private $registered;
5253
private $trustResolver;
54+
private $rememberMeServices;
5355

5456
/**
5557
* @param iterable|UserProviderInterface[] $userProviders
@@ -110,6 +112,10 @@ public function __invoke(RequestEvent $event)
110112

111113
if ($token instanceof TokenInterface) {
112114
$token = $this->refreshUser($token);
115+
116+
if (!$token && $this->rememberMeServices) {
117+
$this->rememberMeServices->loginFail($request);
118+
}
113119
} elseif (null !== $token) {
114120
if (null !== $this->logger) {
115121
$this->logger->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]);
@@ -278,4 +284,9 @@ public static function handleUnserializeCallback($class)
278284
{
279285
throw new \ErrorException('Class not found: '.$class, 0x37313bc);
280286
}
287+
288+
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
289+
{
290+
$this->rememberMeServices = $rememberMeServices;
291+
}
281292
}

src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Symfony\Component\Security\Core\User\UserProviderInterface;
3434
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
3535
use Symfony\Component\Security\Http\Firewall\ContextListener;
36+
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
3637

3738
class ContextListenerTest extends TestCase
3839
{
@@ -263,10 +264,23 @@ public function testIfTokenIsNotDeauthenticated()
263264
$tokenStorage = new TokenStorage();
264265
$badRefreshedUser = new User('foobar', 'baz');
265266
$goodRefreshedUser = new User('foobar', 'bar');
266-
$this->handleEventWithPreviousSession($tokenStorage, [new SupportingUserProvider($badRefreshedUser), new SupportingUserProvider($goodRefreshedUser)], $goodRefreshedUser, true);
267+
$this->handleEventWithPreviousSession($tokenStorage, [new SupportingUserProvider($badRefreshedUser), new SupportingUserProvider($goodRefreshedUser)], $goodRefreshedUser);
267268
$this->assertSame($goodRefreshedUser, $tokenStorage->getToken()->getUser());
268269
}
269270

271+
public function testRememberMeGetsCanceledIfTokenIsDeauthenticated()
272+
{
273+
$tokenStorage = new TokenStorage();
274+
$refreshedUser = new User('foobar', 'baz');
275+
276+
$rememberMeServices = $this->createMock(RememberMeServicesInterface::class);
277+
$rememberMeServices->expects($this->once())->method('loginFail');
278+
279+
$this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], null, $rememberMeServices);
280+
281+
$this->assertNull($tokenStorage->getToken());
282+
}
283+
270284
public function testTryAllUserProvidersUntilASupportingUserProviderIsFound()
271285
{
272286
$tokenStorage = new TokenStorage();
@@ -363,7 +377,7 @@ protected function runSessionOnKernelResponse($newToken, $original = null)
363377
return $session;
364378
}
365379

366-
private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, $userProviders, UserInterface $user = null)
380+
private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, $userProviders, UserInterface $user = null, RememberMeServicesInterface $rememberMeServices = null)
367381
{
368382
$user = $user ?: new User('foo', 'bar');
369383
$session = new Session(new MockArraySessionStorage());
374388
$request->cookies->set('MOCKSESSID', true);
375389

376390
$listener = new ContextListener($tokenStorage, $userProviders, 'context_key');
391+
392+
if ($rememberMeServices) {
393+
$listener->setRememberMeServices($rememberMeServices);
394+
}
377395
$listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST));
378396
}
379397
}

0 commit comments

Comments
 (0)
0