Skip to content

Commit c28d322

Browse files
committed
[Security] [LoginLink] Set custom lifetime for login link
1 parent 2894680 commit c28d322

File tree

6 files changed

+42
-5
lines changed

6 files changed

+42
-5
lines changed

UPGRADE-6.2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Security
1414
* Add maximum username length enforcement of 4096 characters in `UserBadge` to
1515
prevent [session storage flooding](https://symfony.com/blog/cve-2016-4423-large-username-storage-in-session)
1616
* Deprecate the `Symfony\Component\Security\Core\Security` class and service, use `Symfony\Bundle\SecurityBundle\Security\Security` instead
17+
* Add `$lifetime` parameter to `LoginLinkHandlerInterface::createLoginLink()`
1718

1819
Validator
1920
---------

src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ public function __construct(FirewallMap $firewallMap, ContainerInterface $loginL
3838
$this->requestStack = $requestStack;
3939
}
4040

41-
public function createLoginLink(UserInterface $user, Request $request = null): LoginLinkDetails
41+
public function createLoginLink(UserInterface $user, Request $request = null, int $lifetime = null): LoginLinkDetails
4242
{
43-
return $this->getForFirewall()->createLoginLink($user, $request);
43+
return $this->getForFirewall()->createLoginLink($user, $request, $lifetime);
4444
}
4545

4646
public function consumeLoginLink(Request $request): UserInterface

src/Symfony/Component/Security/Http/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Add maximum username length enforcement of 4096 characters in `UserBadge`
8+
* Set custom lifetime for login link
9+
* Add `$lifetime` parameter to `LoginLinkHandlerInterface::createLoginLink()`
810

911
6.0
1012
---

src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ public function __construct(UrlGeneratorInterface $urlGenerator, UserProviderInt
4444
], $options);
4545
}
4646

47-
public function createLoginLink(UserInterface $user, Request $request = null): LoginLinkDetails
47+
public function createLoginLink(UserInterface $user, Request $request = null, int $lifetime = null): LoginLinkDetails
4848
{
49-
$expires = time() + $this->options['lifetime'];
49+
$expires = time() + ($lifetime ?: $this->options['lifetime']);
5050
$expiresAt = new \DateTimeImmutable('@'.$expires);
5151

5252
$parameters = [

src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ interface LoginLinkHandlerInterface
2323
{
2424
/**
2525
* Generate a link that can be used to authenticate as the given user.
26+
*
27+
* @param int $lifetime
2628
*/
27-
public function createLoginLink(UserInterface $user, Request $request = null): LoginLinkDetails;
29+
public function createLoginLink(UserInterface $user, Request $request = null /*, int $lifetime = null */): LoginLinkDetails;
2830

2931
/**
3032
* Validates if this request contains a login link and returns the associated User.

src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,38 @@ public function provideCreateLoginLinkData()
112112
];
113113
}
114114

115+
public function testCreateLoginLinkWithLifetime()
116+
{
117+
$extraProperties = ['emailProperty' => 'ryan@symfonycasts.com', 'passwordProperty' => 'pwhash'];
118+
119+
$this->router->expects($this->once())
120+
->method('generate')
121+
->with(
122+
'app_check_login_link_route',
123+
$this->callback(function ($parameters) use ($extraProperties) {
124+
return 'weaverryan' == $parameters['user']
125+
&& isset($parameters['expires'])
126+
// allow a small expiration offset to avoid time-sensitivity
127+
&& abs(time() + 1000 - $parameters['expires']) <= 1
128+
&& isset($parameters['hash'])
129+
// make sure hash is what we expect
130+
&& $parameters['hash'] === $this->createSignatureHash('weaverryan', $parameters['expires'], array_values($extraProperties));
131+
}),
132+
UrlGeneratorInterface::ABSOLUTE_URL
133+
)
134+
->willReturn('https://example.com/login/verify?user=weaverryan&hash=abchash&expires=1654244256');
135+
136+
$user = new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash');
137+
$lifetime = 1000;
138+
139+
$loginLink = $this->createLinker([], array_keys($extraProperties))->createLoginLink(
140+
user: $user,
141+
lifetime: $lifetime,
142+
);
143+
144+
$this->assertSame('https://example.com/login/verify?user=weaverryan&hash=abchash&expires=1654244256', $loginLink->getUrl());
145+
}
146+
115147
public function testConsumeLoginLink()
116148
{
117149
$expires = time() + 500;

0 commit comments

Comments
 (0)