Skip to content
This repository was archived by the owner on Dec 2, 2021. It is now read-only.

Commit 5c2cbc4

Browse files
committed
Reset prepared provider on each login, to fix preparation issue when invalidate_session option is false #296
1 parent 0f757ff commit 5c2cbc4

File tree

4 files changed

+84
-14
lines changed

4 files changed

+84
-14
lines changed

Security/TwoFactor/Provider/TwoFactorProviderPreparationListener.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ public function __construct(
6868
public function onLogin(AuthenticationEvent $event): void
6969
{
7070
$token = $event->getAuthenticationToken();
71+
72+
if ($token instanceof TwoFactorTokenInterface) {
73+
$firewallName = $token->getProviderKey();
74+
$this->preparationRecorder->startRecording($firewallName);
75+
}
76+
7177
if ($this->prepareOnLogin && $this->supports($token)) {
7278
/** @var TwoFactorTokenInterface $token */
7379
// After login, when the token is a TwoFactorTokenInterface, execute preparation

Security/TwoFactor/Provider/TwoFactorProviderPreparationRecorder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ public function isProviderPrepared(string $firewallName, string $providerName):
2828
return \in_array($providerName, $firewallCalledProviders, true);
2929
}
3030

31+
public function startRecording(string $firewallName): void
32+
{
33+
$calledProviders = $this->session->get(self::CALLED_PROVIDERS_SESSION_KEY, []);
34+
$calledProviders[$firewallName] = [];
35+
$this->session->set(self::CALLED_PROVIDERS_SESSION_KEY, $calledProviders);
36+
}
37+
3138
public function recordProviderIsPrepared(string $firewallName, string $providerName): void
3239
{
3340
$calledProviders = $this->session->get(self::CALLED_PROVIDERS_SESSION_KEY, []);

Tests/Security/TwoFactor/Provider/TwoFactorProviderPreparationListenerTest.php

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry;
1515
use Scheb\TwoFactorBundle\Tests\TestCase;
1616
use Symfony\Component\HttpFoundation\Request;
17-
use Symfony\Component\HttpFoundation\Session\SessionInterface;
1817
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
1918
use Symfony\Component\HttpKernel\HttpKernelInterface;
19+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2020
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
2121

2222
class TwoFactorProviderPreparationListenerTest extends TestCase
@@ -35,17 +35,17 @@ class TwoFactorProviderPreparationListenerTest extends TestCase
3535
private $request;
3636

3737
/**
38-
* @var MockObject|SessionInterface
38+
* @var MockObject|TwoFactorProviderPreparationRecorder
3939
*/
4040
private $preparationRecorder;
4141

4242
/**
4343
* @var MockObject|TwoFactorToken
4444
*/
45-
private $token;
45+
private $twoFactorToken;
4646

4747
/**
48-
* @var
48+
* @var \stdClass
4949
*/
5050
private $user;
5151

@@ -58,16 +58,16 @@ protected function setUp(): void
5858
{
5959
$this->request = $this->createMock(Request::class);
6060
$this->user = new \stdClass();
61-
$this->token = $this->createMock(TwoFactorToken::class);
62-
$this->token
61+
$this->twoFactorToken = $this->createMock(TwoFactorToken::class);
62+
$this->twoFactorToken
6363
->expects($this->any())
6464
->method('getProviderKey')
6565
->willReturn(self::FIREWALL_NAME);
66-
$this->token
66+
$this->twoFactorToken
6767
->expects($this->any())
6868
->method('getCurrentTwoFactorProvider')
6969
->willReturn(self::CURRENT_PROVIDER_NAME);
70-
$this->token
70+
$this->twoFactorToken
7171
->expects($this->any())
7272
->method('getUser')
7373
->willReturn($this->user);
@@ -91,12 +91,12 @@ private function initTwoFactorProviderPreparationListener($prepareOnLogin, $prep
9191

9292
private function createTwoFactorAuthenticationEvent(): TwoFactorAuthenticationEvent
9393
{
94-
return new TwoFactorAuthenticationEvent($this->request, $this->token);
94+
return new TwoFactorAuthenticationEvent($this->request, $this->twoFactorToken);
9595
}
9696

97-
private function createAuthenticationEvent(): AuthenticationEvent
97+
private function createAuthenticationEvent(TokenInterface $token): AuthenticationEvent
9898
{
99-
return new AuthenticationEvent($this->token);
99+
return new AuthenticationEvent($token);
100100
}
101101

102102
private function createFinishRequestEvent(): FinishRequestEvent
@@ -141,7 +141,7 @@ private function expectNotPrepareCurrentProvider(): void
141141
{
142142
$this->preparationRecorder
143143
->expects($this->never())
144-
->method($this->anything());
144+
->method('recordProviderIsPrepared');
145145

146146
$this->providerRegistry
147147
->expects($this->never())
@@ -154,7 +154,7 @@ private function expectNotPrepareCurrentProvider(): void
154154
public function onLogin_optionPrepareOnLoginTrue_twoFactorProviderIsPrepared(): void
155155
{
156156
$this->initTwoFactorProviderPreparationListener(true, false);
157-
$event = $this->createAuthenticationEvent();
157+
$event = $this->createAuthenticationEvent($this->twoFactorToken);
158158

159159
$this->expectPrepareCurrentProvider();
160160

@@ -168,14 +168,46 @@ public function onLogin_optionPrepareOnLoginTrue_twoFactorProviderIsPrepared():
168168
public function onLogin_optionPrepareOnLoginFalse_twoFactorProviderIsNotPrepared(): void
169169
{
170170
$this->initTwoFactorProviderPreparationListener(false, false);
171-
$event = $this->createAuthenticationEvent();
171+
$event = $this->createAuthenticationEvent($this->twoFactorToken);
172172

173173
$this->expectNotPrepareCurrentProvider();
174174

175175
$this->listener->onLogin($event);
176176
$this->listener->onKernelFinishRequest($this->createFinishRequestEvent());
177177
}
178178

179+
/**
180+
* @test
181+
*/
182+
public function onLogin_twoFactorToken_startRecording(): void
183+
{
184+
$this->initTwoFactorProviderPreparationListener(false, false);
185+
$event = $this->createAuthenticationEvent($this->twoFactorToken);
186+
187+
$this->preparationRecorder
188+
->expects($this->once())
189+
->method('startRecording')
190+
->with(self::FIREWALL_NAME);
191+
192+
$this->listener->onLogin($event);
193+
}
194+
195+
/**
196+
* @test
197+
*/
198+
public function onLogin_otherToken_doNothing(): void
199+
{
200+
$this->initTwoFactorProviderPreparationListener(false, false);
201+
$token = $this->createMock(TokenInterface::class);
202+
$event = $this->createAuthenticationEvent($token);
203+
204+
$this->preparationRecorder
205+
->expects($this->never())
206+
->method('startRecording');
207+
208+
$this->listener->onLogin($event);
209+
}
210+
179211
/**
180212
* @test
181213
*/

Tests/Security/TwoFactor/Provider/TwoFactorProviderPreparationRecorderTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,31 @@ protected function setUp(): void
3030
$this->recorder = new TwoFactorProviderPreparationRecorder($this->session);
3131
}
3232

33+
/**
34+
* @test
35+
*/
36+
public function startRecording_wasPreparedBefore_noLongerPrepared(): void
37+
{
38+
$this->session
39+
->expects($this->any())
40+
->method('get')
41+
->with('2fa_called_providers')
42+
->willReturn([
43+
'otherFirewallName' => [self::CURRENT_PROVIDER_NAME],
44+
self::FIREWALL_NAME => [self::CURRENT_PROVIDER_NAME],
45+
]);
46+
47+
$this->session
48+
->expects($this->once())
49+
->method('set')
50+
->with('2fa_called_providers', [
51+
'otherFirewallName' => [self::CURRENT_PROVIDER_NAME],
52+
self::FIREWALL_NAME => [],
53+
]);
54+
55+
$this->recorder->startRecording(self::FIREWALL_NAME);
56+
}
57+
3358
/**
3459
* @test
3560
*/

0 commit comments

Comments
 (0)