@@ -53,6 +53,7 @@ protected function setUp(): void
53
53
54
54
/**
55
55
* @group time-sensitive
56
+ *
56
57
* @dataProvider provideCreateLoginLinkData
57
58
*/
58
59
public function testCreateLoginLink ($ user , array $ extraProperties , Request $ request = null )
@@ -61,13 +62,14 @@ public function testCreateLoginLink($user, array $extraProperties, Request $requ
61
62
->method ('generate ' )
62
63
->with (
63
64
'app_check_login_link_route ' ,
64
- $ this ->callback (fn ($ parameters ) => 'weaverryan ' == $ parameters ['user ' ]
65
+ $ this ->callback (fn ($ parameters ) => 'weaverryan ' === $ parameters ['user ' ]
65
66
&& isset ($ parameters ['expires ' ])
66
67
&& isset ($ parameters ['hash ' ])
67
68
// allow a small expiration offset to avoid time-sensitivity
68
69
&& abs (time () + 600 - $ parameters ['expires ' ]) <= 1
69
70
// make sure hash is what we expect
70
- && $ parameters ['hash ' ] === $ this ->createSignatureHash ('weaverryan ' , $ parameters ['expires ' ], array_values ($ extraProperties ))),
71
+ && $ parameters ['hash ' ] === $ this ->createSignatureHash ('weaverryan ' , $ parameters ['expires ' ], $ extraProperties )
72
+ ),
71
73
UrlGeneratorInterface::ABSOLUTE_URL
72
74
)
73
75
->willReturn ('https://example.com/login/verify?user=weaverryan&hash=abchash&expires=1601235000 ' );
@@ -118,13 +120,14 @@ public function testCreateLoginLinkWithLifetime()
118
120
->method ('generate ' )
119
121
->with (
120
122
'app_check_login_link_route ' ,
121
- $ this ->callback (fn ($ parameters ) => 'weaverryan ' == $ parameters ['user ' ]
123
+ $ this ->callback (fn ($ parameters ) => 'weaverryan ' === $ parameters ['user ' ]
122
124
&& isset ($ parameters ['expires ' ])
123
125
// allow a small expiration offset to avoid time-sensitivity
124
126
&& abs (time () + 1000 - $ parameters ['expires ' ]) <= 1
125
127
&& isset ($ parameters ['hash ' ])
126
128
// make sure hash is what we expect
127
- && $ parameters ['hash ' ] === $ this ->createSignatureHash ('weaverryan ' , $ parameters ['expires ' ], array_values ($ extraProperties ))),
129
+ && $ parameters ['hash ' ] === $ this ->createSignatureHash ('weaverryan ' , $ parameters ['expires ' ], $ extraProperties )
130
+ ),
128
131
UrlGeneratorInterface::ABSOLUTE_URL
129
132
)
130
133
->willReturn ('https://example.com/login/verify?user=weaverryan&hash=abchash&expires=1654244256 ' );
@@ -143,7 +146,7 @@ public function testCreateLoginLinkWithLifetime()
143
146
public function testConsumeLoginLink ()
144
147
{
145
148
$ expires = time () + 500 ;
146
- $ signature =
$ this ->
createSignatureHash (
'weaverryan ' ,
$ expires, [ ' [email protected] ' , ' pwhash ' ] );
149
+ $ signature = $ this ->createSignatureHash ('weaverryan ' , $ expires );
147
150
$ request = Request::create (sprintf ('/login/verify?user=weaverryan&hash=%s&expires=%d ' , $ signature , $ expires ));
148
151
149
152
$ user =
new TestLoginLinkHandlerUser (
'weaverryan ' ,
'[email protected] ' ,
'pwhash ' );
@@ -159,44 +162,37 @@ public function testConsumeLoginLink()
159
162
160
163
public function testConsumeLoginLinkWithExpired ()
161
164
{
162
- $ this ->expectException (ExpiredLoginLinkException::class);
163
165
$ expires = time () - 500 ;
164
- $ signature =
$ this ->
createSignatureHash (
'weaverryan ' ,
$ expires, [ ' [email protected] ' , ' pwhash ' ] );
166
+ $ signature = $ this ->createSignatureHash ('weaverryan ' , $ expires );
165
167
$ request = Request::create (sprintf ('/login/verify?user=weaverryan&hash=%s&expires=%d ' , $ signature , $ expires ));
166
168
167
- $ user =
new TestLoginLinkHandlerUser (
'weaverryan ' ,
'[email protected] ' ,
'pwhash ' );
168
- $ this ->userProvider ->createUser ($ user );
169
-
170
169
$ linker = $ this ->createLinker (['max_uses ' => 3 ]);
170
+ $ this ->expectException (ExpiredLoginLinkException::class);
171
171
$ linker ->consumeLoginLink ($ request );
172
172
}
173
173
174
174
public function testConsumeLoginLinkWithUserNotFound ()
175
175
{
176
- $ this ->expectException (InvalidLoginLinkException::class);
177
- $ request = Request::create ('/login/verify?user=weaverryan&hash=thehash&expires=10000 ' );
176
+ $ request = Request::create ('/login/verify?user=weaverryan&hash=thehash&expires= ' .(time () + 500 ));
178
177
179
178
$ linker = $ this ->createLinker ();
179
+ $ this ->expectException (InvalidLoginLinkException::class);
180
180
$ linker ->consumeLoginLink ($ request );
181
181
}
182
182
183
183
public function testConsumeLoginLinkWithDifferentSignature ()
184
184
{
185
- $ this ->expectException (InvalidLoginLinkException::class);
186
185
$ request = Request::create (sprintf ('/login/verify?user=weaverryan&hash=fake_hash&expires=%d ' , time () + 500 ));
187
186
188
- $ user =
new TestLoginLinkHandlerUser (
'weaverryan ' ,
'[email protected] ' ,
'pwhash ' );
189
- $ this ->userProvider ->createUser ($ user );
190
-
191
187
$ linker = $ this ->createLinker ();
188
+ $ this ->expectException (InvalidLoginLinkException::class);
192
189
$ linker ->consumeLoginLink ($ request );
193
190
}
194
191
195
192
public function testConsumeLoginLinkExceedsMaxUsage ()
196
193
{
197
- $ this ->expectException (ExpiredLoginLinkException::class);
198
194
$ expires = time () + 500 ;
199
- $ signature =
$ this ->
createSignatureHash (
'weaverryan ' ,
$ expires, [ ' [email protected] ' , ' pwhash ' ] );
195
+ $ signature = $ this ->createSignatureHash ('weaverryan ' , $ expires );
200
196
$ request = Request::create (sprintf ('/login/verify?user=weaverryan&hash=%s&expires=%d ' , $ signature , $ expires ));
201
197
202
198
$ user =
new TestLoginLinkHandlerUser (
'weaverryan ' ,
'[email protected] ' ,
'pwhash ' );
@@ -207,6 +203,7 @@ public function testConsumeLoginLinkExceedsMaxUsage()
207
203
$ this ->expiredLinkCache ->save ($ item );
208
204
209
205
$ linker = $ this ->createLinker (['max_uses ' => 3 ]);
206
+ $ this ->expectException (ExpiredLoginLinkException::class);
210
207
$ linker ->consumeLoginLink ($ request );
211
208
}
212
209
@@ -234,15 +231,12 @@ public function testConsumeLoginLinkWithMissingExpiration()
234
231
$ linker ->consumeLoginLink ($ request );
235
232
}
236
233
237
- private function createSignatureHash (string $ username , int $ expires , array $ extraFields ): string
234
+ private function createSignatureHash (
string $ username,
int $ expires,
array $ extraFields = [ ' emailProperty ' => ' [email protected] ' , ' passwordProperty ' => ' pwhash ' ] ):
string
238
235
{
239
- $ fields = [base64_encode ($ username ), $ expires ];
240
- foreach ($ extraFields as $ extraField ) {
241
- $ fields [] = base64_encode ($ extraField );
242
- }
236
+ $ hasher = new SignatureHasher ($ this ->propertyAccessor , array_keys ($ extraFields ), 's3cret ' );
237
+ $ user = new TestLoginLinkHandlerUser ($ username , $ extraFields ['emailProperty ' ] ?? '' , $ extraFields ['passwordProperty ' ] ?? '' , $ extraFields ['lastAuthenticatedAt ' ] ?? null );
243
238
244
- // matches hash logic in the class
245
- return base64_encode (hash_hmac ('sha256 ' , implode (': ' , $ fields ), 's3cret ' ));
239
+ return $ hasher ->computeSignatureHash ($ user , $ expires );
246
240
}
247
241
248
242
private function createLinker (array $ options = [], array $ extraProperties = ['emailProperty ' , 'passwordProperty ' ]): LoginLinkHandler
@@ -326,7 +320,7 @@ public function loadUserByIdentifier(string $userIdentifier): TestLoginLinkHandl
326
320
327
321
public function refreshUser (UserInterface $ user ): TestLoginLinkHandlerUser
328
322
{
329
- return $ this ->users [$ username ];
323
+ return $ this ->users [$ user -> getUserIdentifier () ];
330
324
}
331
325
332
326
public function supportsClass (string $ class ): bool
0 commit comments