Skip to content

Commit fda3f97

Browse files
baumerdevnicolas-grekas
authored andcommitted
[Security] Add remember me option for JSON logins
1 parent c0a1e5f commit fda3f97

File tree

4 files changed

+86
-9
lines changed

4 files changed

+86
-9
lines changed

Authenticator/JsonLoginAuthenticator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
2727
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
2828
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
29+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
2930
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
3031
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
3132
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
@@ -88,7 +89,7 @@ public function authenticate(Request $request): Passport
8889
}
8990

9091
$userBadge = new UserBadge($credentials['username'], $this->userProvider->loadUserByIdentifier(...));
91-
$passport = new Passport($userBadge, new PasswordCredentials($credentials['password']));
92+
$passport = new Passport($userBadge, new PasswordCredentials($credentials['password']), [new RememberMeBadge()]);
9293

9394
if ($this->userProvider instanceof PasswordUpgraderInterface) {
9495
$passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.3
5+
---
6+
7+
* Add `RememberMeBadge` to `JsonLoginAuthenticator` and enable reading parameter in JSON request body
8+
49
6.2
510
---
611

EventListener/CheckRememberMeConditionsListener.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpFoundation\Request;
1617
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
1718
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
1819
use Symfony\Component\Security\Http\ParameterBagUtils;
@@ -54,7 +55,7 @@ public function onSuccessfulLogin(LoginSuccessEvent $event): void
5455
/** @var RememberMeBadge $badge */
5556
$badge = $passport->getBadge(RememberMeBadge::class);
5657
if (!$this->options['always_remember_me']) {
57-
$parameter = ParameterBagUtils::getRequestParameterValue($event->getRequest(), $this->options['remember_me_parameter']);
58+
$parameter = $this->getParameter($event->getRequest(), $this->options['remember_me_parameter']);
5859
if (!('true' === $parameter || 'on' === $parameter || '1' === $parameter || 'yes' === $parameter || true === $parameter)) {
5960
$this->logger?->debug('Remember me disabled; request does not contain remember me parameter ("{parameter}").', ['parameter' => $this->options['remember_me_parameter']]);
6061

@@ -69,4 +70,23 @@ public static function getSubscribedEvents(): array
6970
{
7071
return [LoginSuccessEvent::class => ['onSuccessfulLogin', -32]];
7172
}
73+
74+
private function getParameter(Request $request, string $parameterName): mixed
75+
{
76+
$parameter = ParameterBagUtils::getRequestParameterValue($request, $parameterName);
77+
if (null !== $parameter) {
78+
return $parameter;
79+
}
80+
81+
if ('application/json' === $request->headers->get('Content-Type')) {
82+
$data = json_decode($request->getContent());
83+
if (!$data instanceof \stdClass) {
84+
return null;
85+
}
86+
87+
return $data->{$parameterName} ?? null;
88+
}
89+
90+
return null;
91+
}
7292
}

Tests/EventListener/CheckRememberMeConditionsListenerTest.php

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,29 @@ class CheckRememberMeConditionsListenerTest extends TestCase
3333
protected function setUp(): void
3434
{
3535
$this->listener = new CheckRememberMeConditionsListener();
36-
$this->request = Request::create('/login');
37-
$this->request->request->set('_remember_me', true);
38-
$this->response = new Response();
3936
}
4037

41-
public function testSuccessfulLoginWithoutSupportingAuthenticator()
38+
public function testSuccessfulHttpLoginWithoutSupportingAuthenticator()
4239
{
40+
$this->createHttpRequest();
41+
4342
$passport = $this->createPassport([]);
4443

4544
$this->listener->onSuccessfulLogin($this->createLoginSuccessfulEvent($passport));
4645

4746
$this->assertFalse($passport->hasBadge(RememberMeBadge::class));
4847
}
4948

49+
public function testSuccessfulJsonLoginWithoutSupportingAuthenticator()
50+
{
51+
$this->createJsonRequest();
52+
53+
$passport = $this->createPassport([]);
54+
$this->listener->onSuccessfulLogin($this->createLoginSuccessfulEvent($passport));
55+
56+
$this->assertFalse($passport->hasBadge(RememberMeBadge::class));
57+
}
58+
5059
public function testSuccessfulLoginWithoutRequestParameter()
5160
{
5261
$this->request = Request::create('/login');
@@ -57,10 +66,22 @@ public function testSuccessfulLoginWithoutRequestParameter()
5766
$this->assertFalse($passport->getBadge(RememberMeBadge::class)->isEnabled());
5867
}
5968

60-
public function testSuccessfulLoginWhenRememberMeAlwaysIsTrue()
69+
public function testSuccessfulHttpLoginWhenRememberMeAlwaysIsTrue()
6170
{
71+
$this->createHttpRequest();
72+
73+
$passport = $this->createPassport();
74+
75+
$this->listener->onSuccessfulLogin($this->createLoginSuccessfulEvent($passport));
76+
77+
$this->assertTrue($passport->getBadge(RememberMeBadge::class)->isEnabled());
78+
}
79+
80+
public function testSuccessfulJsonLoginWhenRememberMeAlwaysIsTrue()
81+
{
82+
$this->createJsonRequest();
83+
6284
$passport = $this->createPassport();
63-
$listener = new CheckRememberMeConditionsListener(['always_remember_me' => true]);
6485

6586
$this->listener->onSuccessfulLogin($this->createLoginSuccessfulEvent($passport));
6687

@@ -70,8 +91,10 @@ public function testSuccessfulLoginWhenRememberMeAlwaysIsTrue()
7091
/**
7192
* @dataProvider provideRememberMeOptInValues
7293
*/
73-
public function testSuccessfulLoginWithOptInRequestParameter($optInValue)
94+
public function testSuccessfulHttpLoginWithOptInRequestParameter($optInValue)
7495
{
96+
$this->createHttpRequest();
97+
7598
$this->request->request->set('_remember_me', $optInValue);
7699
$passport = $this->createPassport();
77100

@@ -80,6 +103,20 @@ public function testSuccessfulLoginWithOptInRequestParameter($optInValue)
80103
$this->assertTrue($passport->getBadge(RememberMeBadge::class)->isEnabled());
81104
}
82105

106+
/**
107+
* @dataProvider provideRememberMeOptInValues
108+
*/
109+
public function testSuccessfulJsonLoginWithOptInRequestParameter($optInValue)
110+
{
111+
$this->createJsonRequest(['_remember_me' => $optInValue]);
112+
113+
$passport = $this->createPassport();
114+
115+
$this->listener->onSuccessfulLogin($this->createLoginSuccessfulEvent($passport));
116+
117+
$this->assertTrue($passport->getBadge(RememberMeBadge::class)->isEnabled());
118+
}
119+
83120
public static function provideRememberMeOptInValues()
84121
{
85122
yield ['true'];
@@ -89,6 +126,20 @@ public static function provideRememberMeOptInValues()
89126
yield [true];
90127
}
91128

129+
private function createHttpRequest(): void
130+
{
131+
$this->request = Request::create('/login');
132+
$this->request->request->set('_remember_me', true);
133+
$this->response = new Response();
134+
}
135+
136+
private function createJsonRequest(mixed $content = ['_remember_me' => true]): void
137+
{
138+
$this->request = Request::create('/login', 'POST', [], [], [], [], json_encode($content));
139+
$this->request->headers->add(['Content-Type' => 'application/json']);
140+
$this->response = new Response();
141+
}
142+
92143
private function createLoginSuccessfulEvent(Passport $passport)
93144
{
94145
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), $this->request, $this->response, 'main_firewall');

0 commit comments

Comments
 (0)