Skip to content

Commit 0f947b2

Browse files
committed
Allow null and DateTime objects to be used as signatureProperties
Returning DateTime objects seems like a common use-case to automatically expire all login links when one is used or to only allow the login link to be used once.
1 parent 0000dfe commit 0f947b2

File tree

2 files changed

+35
-10
lines changed

2 files changed

+35
-10
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ private function computeSignatureHash(UserInterface $user, int $expires): string
106106
$signatureFields = [base64_encode($user->getUsername()), $expires];
107107

108108
foreach ($this->signatureProperties as $property) {
109-
$value = $this->propertyAccessor->getValue($user, $property);
109+
$value = $this->propertyAccessor->getValue($user, $property) ?? '';
110+
if ($value instanceof \DateTimeInterface) {
111+
$value = $value->format('c');
112+
}
113+
110114
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
111115
throw new \InvalidArgumentException(sprintf('The property path "%s" on the user object "%s" must return a value that can be cast to a string, but "%s" was returned.', $property, \get_class($user), get_debug_type($value)));
112116
}

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,48 @@ protected function setUp(): void
4444
$this->expiredLinkStorage = $this->createMock(ExpiredLoginLinkStorage::class);
4545
}
4646

47-
public function testCreateLoginLink()
47+
/**
48+
* @dataProvider provideCreateLoginLinkData
49+
*/
50+
public function testCreateLoginLink($user, array $extraProperties)
4851
{
49-
$user = new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash');
50-
5152
$this->router->expects($this->once())
5253
->method('generate')
5354
->with(
5455
'app_check_login_link_route',
55-
$this->callback(function ($parameters) {
56+
$this->callback(function ($parameters) use ($extraProperties) {
5657
return 'weaverryan' == $parameters['user']
5758
&& isset($parameters['expires'])
5859
&& isset($parameters['hash'])
5960
// make sure hash is what we expect
60-
&& $parameters['hash'] === $this->createSignatureHash('weaverryan', time() + 600, ['ryan@symfonycasts.com', 'pwhash']);
61+
&& $parameters['hash'] === $this->createSignatureHash('weaverryan', time() + 600, array_values($extraProperties));
6162
}),
6263
UrlGeneratorInterface::ABSOLUTE_URL
6364
)
6465
->willReturn('https://example.com/login/verify?user=weaverryan&hash=abchash&expires=1601235000');
6566

66-
$loginLink = $this->createLinker()->createLoginLink($user);
67+
$loginLink = $this->createLinker([], array_keys($extraProperties))->createLoginLink($user);
6768
$this->assertSame('https://example.com/login/verify?user=weaverryan&hash=abchash&expires=1601235000', $loginLink->getUrl());
6869
}
6970

71+
public function provideCreateLoginLinkData()
72+
{
73+
yield [
74+
new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash'),
75+
['emailProperty' => 'ryan@symfonycasts.com', 'passwordProperty' => 'pwhash'],
76+
];
77+
78+
yield [
79+
new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash'),
80+
['lastAuthenticatedAt' => ''],
81+
];
82+
83+
yield [
84+
new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash', new \DateTime('2020-06-01 00:00:00', new \DateTimeZone('+0000'))),
85+
['lastAuthenticatedAt' => '2020-06-01T00:00:00+00:00'],
86+
];
87+
}
88+
7089
public function testConsumeLoginLink()
7190
{
7291
$expires = time() + 500;
@@ -167,14 +186,14 @@ private function createSignatureHash(string $username, int $expires, array $extr
167186
return base64_encode(hash_hmac('sha256', implode(':', $fields), 's3cret'));
168187
}
169188

170-
private function createLinker(array $options = []): LoginLinkHandler
189+
private function createLinker(array $options = [], array $extraProperties = ['emailProperty', 'passwordProperty']): LoginLinkHandler
171190
{
172191
$options = array_merge([
173192
'lifetime' => 600,
174193
'route_name' => 'app_check_login_link_route',
175194
], $options);
176195

177-
return new LoginLinkHandler($this->router, $this->userProvider, $this->propertyAccessor, ['emailProperty', 'passwordProperty'], 's3cret', $options, $this->expiredLinkStorage);
196+
return new LoginLinkHandler($this->router, $this->userProvider, $this->propertyAccessor, $extraProperties, 's3cret', $options, $this->expiredLinkStorage);
178197
}
179198
}
180199

@@ -183,12 +202,14 @@ class TestLoginLinkHandlerUser implements UserInterface
183202
public $username;
184203
public $emailProperty;
185204
public $passwordProperty;
205+
public $lastAuthenticatedAt;
186206

187-
public function __construct($username, $emailProperty, $passwordProperty)
207+
public function __construct($username, $emailProperty, $passwordProperty, $lastAuthenticatedAt = null)
188208
{
189209
$this->username = $username;
190210
$this->emailProperty = $emailProperty;
191211
$this->passwordProperty = $passwordProperty;
212+
$this->lastAuthenticatedAt = $lastAuthenticatedAt;
192213
}
193214

194215
public function getRoles()

0 commit comments

Comments
 (0)