Skip to content

Commit 776653a

Browse files
valorinJurianArietaylorotwell
authored
Backporting Timebox fixes to 11.x (#55705)
* Shift Timebox up a level to encapsulate the query too * Add Timebox Duration into config * Add Timebox to Password Broker * Add early return inside reset Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com> * formatting --------- Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com> Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent abb1119 commit 776653a

File tree

4 files changed

+138
-74
lines changed

4 files changed

+138
-74
lines changed

src/Illuminate/Auth/AuthManager.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public function createSessionDriver($name, $config)
127127
$this->createUserProvider($config['provider'] ?? null),
128128
$this->app['session.store'],
129129
rehashOnLogin: $this->app['config']->get('hashing.rehash_on_login', true),
130+
timeboxDuration: $this->app['config']->get('auth.timebox_duration', 200000),
130131
);
131132

132133
// When using the remember me functionality of the authentication services we

src/Illuminate/Auth/Passwords/PasswordBroker.php

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Contracts\Auth\UserProvider;
1010
use Illuminate\Contracts\Events\Dispatcher;
1111
use Illuminate\Support\Arr;
12+
use Illuminate\Support\Timebox;
1213
use UnexpectedValueException;
1314

1415
class PasswordBroker implements PasswordBrokerContract
@@ -34,19 +35,42 @@ class PasswordBroker implements PasswordBrokerContract
3435
*/
3536
protected $events;
3637

38+
/**
39+
* The timebox instance.
40+
*
41+
* @var \Illuminate\Support\Timebox
42+
*/
43+
protected $timebox;
44+
45+
/**
46+
* The number of microseconds that the timebox should wait for.
47+
*
48+
* @var int
49+
*/
50+
protected $timeboxDuration;
51+
3752
/**
3853
* Create a new password broker instance.
3954
*
4055
* @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens
4156
* @param \Illuminate\Contracts\Auth\UserProvider $users
4257
* @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher
58+
* @param \Illuminate\Support\Timebox|null $timebox
59+
* @param int $timeboxDuration
4360
* @return void
4461
*/
45-
public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tokens, UserProvider $users, ?Dispatcher $dispatcher = null)
46-
{
62+
public function __construct(
63+
#[\SensitiveParameter] TokenRepositoryInterface $tokens,
64+
UserProvider $users,
65+
?Dispatcher $dispatcher = null,
66+
?Timebox $timebox = null,
67+
int $timeboxDuration = 200000,
68+
) {
4769
$this->users = $users;
4870
$this->tokens = $tokens;
4971
$this->events = $dispatcher;
72+
$this->timebox = $timebox ?: new Timebox;
73+
$this->timeboxDuration = $timeboxDuration;
5074
}
5175

5276
/**
@@ -58,33 +82,35 @@ public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tok
5882
*/
5983
public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closure $callback = null)
6084
{
61-
// First we will check to see if we found a user at the given credentials and
62-
// if we did not we will redirect back to this current URI with a piece of
63-
// "flash" data in the session to indicate to the developers the errors.
64-
$user = $this->getUser($credentials);
85+
return $this->timebox->call(function () use ($credentials, $callback) {
86+
// First we will check to see if we found a user at the given credentials and
87+
// if we did not we will redirect back to this current URI with a piece of
88+
// "flash" data in the session to indicate to the developers the errors.
89+
$user = $this->getUser($credentials);
6590

66-
if (is_null($user)) {
67-
return static::INVALID_USER;
68-
}
91+
if (is_null($user)) {
92+
return static::INVALID_USER;
93+
}
6994

70-
if ($this->tokens->recentlyCreatedToken($user)) {
71-
return static::RESET_THROTTLED;
72-
}
95+
if ($this->tokens->recentlyCreatedToken($user)) {
96+
return static::RESET_THROTTLED;
97+
}
7398

74-
$token = $this->tokens->create($user);
99+
$token = $this->tokens->create($user);
75100

76-
if ($callback) {
77-
return $callback($user, $token) ?? static::RESET_LINK_SENT;
78-
}
101+
if ($callback) {
102+
return $callback($user, $token) ?? static::RESET_LINK_SENT;
103+
}
79104

80-
// Once we have the reset token, we are ready to send the message out to this
81-
// user with a link to reset their password. We will then redirect back to
82-
// the current URI having nothing set in the session to indicate errors.
83-
$user->sendPasswordResetNotification($token);
105+
// Once we have the reset token, we are ready to send the message out to this
106+
// user with a link to reset their password. We will then redirect back to
107+
// the current URI having nothing set in the session to indicate errors.
108+
$user->sendPasswordResetNotification($token);
84109

85-
$this->events?->dispatch(new PasswordResetLinkSent($user));
110+
$this->events?->dispatch(new PasswordResetLinkSent($user));
86111

87-
return static::RESET_LINK_SENT;
112+
return static::RESET_LINK_SENT;
113+
}, $this->timeboxDuration);
88114
}
89115

90116
/**
@@ -96,25 +122,29 @@ public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closur
96122
*/
97123
public function reset(#[\SensitiveParameter] array $credentials, Closure $callback)
98124
{
99-
$user = $this->validateReset($credentials);
125+
return $this->timebox->call(function ($timebox) use ($credentials, $callback) {
126+
$user = $this->validateReset($credentials);
100127

101-
// If the responses from the validate method is not a user instance, we will
102-
// assume that it is a redirect and simply return it from this method and
103-
// the user is properly redirected having an error message on the post.
104-
if (! $user instanceof CanResetPasswordContract) {
105-
return $user;
106-
}
128+
// If the responses from the validate method is not a user instance, we will
129+
// assume that it is a redirect and simply return it from this method and
130+
// the user is properly redirected having an error message on the post.
131+
if (! $user instanceof CanResetPasswordContract) {
132+
return $user;
133+
}
107134

108-
$password = $credentials['password'];
135+
$password = $credentials['password'];
109136

110-
// Once the reset has been validated, we'll call the given callback with the
111-
// new password. This gives the user an opportunity to store the password
112-
// in their persistent storage. Then we'll delete the token and return.
113-
$callback($user, $password);
137+
// Once the reset has been validated, we'll call the given callback with the
138+
// new password. This gives the user an opportunity to store the password
139+
// in their persistent storage. Then we'll delete the token and return.
140+
$callback($user, $password);
114141

115-
$this->tokens->delete($user);
142+
$this->tokens->delete($user);
143+
144+
$timebox->returnEarly();
116145

117-
return static::PASSWORD_RESET;
146+
return static::PASSWORD_RESET;
147+
}, $this->timeboxDuration);
118148
}
119149

120150
/**
@@ -200,4 +230,14 @@ public function getRepository()
200230
{
201231
return $this->tokens;
202232
}
233+
234+
/**
235+
* Get the timebox instance used by the guard.
236+
*
237+
* @return \Illuminate\Support\Timebox
238+
*/
239+
public function getTimebox()
240+
{
241+
return $this->timebox;
242+
}
203243
}

src/Illuminate/Auth/Passwords/PasswordBrokerManager.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ protected function resolve($name)
7171
$this->createTokenRepository($config),
7272
$this->app['auth']->createUserProvider($config['provider'] ?? null),
7373
$this->app['events'] ?? null,
74+
timeboxDuration: $this->app['config']->get('auth.timebox_duration', 200000),
7475
);
7576
}
7677

src/Illuminate/Auth/SessionGuard.php

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
9696
*/
9797
protected $timebox;
9898

99+
/**
100+
* The number of microseconds that the timebox should wait for.
101+
*
102+
* @var int
103+
*/
104+
protected $timeboxDuration;
105+
99106
/**
100107
* Indicates if passwords should be rehashed on login if needed.
101108
*
@@ -126,6 +133,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
126133
* @param \Symfony\Component\HttpFoundation\Request|null $request
127134
* @param \Illuminate\Support\Timebox|null $timebox
128135
* @param bool $rehashOnLogin
136+
* @param int $timeboxDuration
129137
* @return void
130138
*/
131139
public function __construct(
@@ -135,13 +143,15 @@ public function __construct(
135143
?Request $request = null,
136144
?Timebox $timebox = null,
137145
bool $rehashOnLogin = true,
146+
int $timeboxDuration = 200000,
138147
) {
139148
$this->name = $name;
140149
$this->session = $session;
141150
$this->request = $request;
142151
$this->provider = $provider;
143152
$this->timebox = $timebox ?: new Timebox;
144153
$this->rehashOnLogin = $rehashOnLogin;
154+
$this->timeboxDuration = $timeboxDuration;
145155
}
146156

147157
/**
@@ -291,9 +301,17 @@ public function onceUsingId($id)
291301
*/
292302
public function validate(array $credentials = [])
293303
{
294-
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
304+
return $this->timebox->call(function ($timebox) use ($credentials) {
305+
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
295306

296-
return $this->hasValidCredentials($user, $credentials);
307+
$validated = $this->hasValidCredentials($user, $credentials);
308+
309+
if ($validated) {
310+
$timebox->returnEarly();
311+
}
312+
313+
return $validated;
314+
}, $this->timeboxDuration);
297315
}
298316

299317
/**
@@ -391,27 +409,31 @@ protected function failedBasicResponse()
391409
*/
392410
public function attempt(array $credentials = [], $remember = false)
393411
{
394-
$this->fireAttemptEvent($credentials, $remember);
412+
return $this->timebox->call(function ($timebox) use ($credentials, $remember) {
413+
$this->fireAttemptEvent($credentials, $remember);
395414

396-
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
415+
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
397416

398-
// If an implementation of UserInterface was returned, we'll ask the provider
399-
// to validate the user against the given credentials, and if they are in
400-
// fact valid we'll log the users into the application and return true.
401-
if ($this->hasValidCredentials($user, $credentials)) {
402-
$this->rehashPasswordIfRequired($user, $credentials);
417+
// If an implementation of UserInterface was returned, we'll ask the provider
418+
// to validate the user against the given credentials, and if they are in
419+
// fact valid we'll log the users into the application and return true.
420+
if ($this->hasValidCredentials($user, $credentials)) {
421+
$this->rehashPasswordIfRequired($user, $credentials);
403422

404-
$this->login($user, $remember);
423+
$this->login($user, $remember);
405424

406-
return true;
407-
}
425+
$timebox->returnEarly();
408426

409-
// If the authentication attempt fails we will fire an event so that the user
410-
// may be notified of any suspicious attempts to access their account from
411-
// an unrecognized user. A developer may listen to this event as needed.
412-
$this->fireFailedEvent($user, $credentials);
427+
return true;
428+
}
413429

414-
return false;
430+
// If the authentication attempt fails we will fire an event so that the user
431+
// may be notified of any suspicious attempts to access their account from
432+
// an unrecognized user. A developer may listen to this event as needed.
433+
$this->fireFailedEvent($user, $credentials);
434+
435+
return false;
436+
}, $this->timeboxDuration);
415437
}
416438

417439
/**
@@ -424,24 +446,28 @@ public function attempt(array $credentials = [], $remember = false)
424446
*/
425447
public function attemptWhen(array $credentials = [], $callbacks = null, $remember = false)
426448
{
427-
$this->fireAttemptEvent($credentials, $remember);
449+
return $this->timebox->call(function ($timebox) use ($credentials, $callbacks, $remember) {
450+
$this->fireAttemptEvent($credentials, $remember);
428451

429-
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
452+
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
430453

431-
// This method does the exact same thing as attempt, but also executes callbacks after
432-
// the user is retrieved and validated. If one of the callbacks returns falsy we do
433-
// not login the user. Instead, we will fail the specific authentication attempt.
434-
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
435-
$this->rehashPasswordIfRequired($user, $credentials);
454+
// This method does the exact same thing as attempt, but also executes callbacks after
455+
// the user is retrieved and validated. If one of the callbacks returns falsy we do
456+
// not login the user. Instead, we will fail the specific authentication attempt.
457+
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
458+
$this->rehashPasswordIfRequired($user, $credentials);
436459

437-
$this->login($user, $remember);
460+
$this->login($user, $remember);
438461

439-
return true;
440-
}
462+
$timebox->returnEarly();
441463

442-
$this->fireFailedEvent($user, $credentials);
464+
return true;
465+
}
443466

444-
return false;
467+
$this->fireFailedEvent($user, $credentials);
468+
469+
return false;
470+
}, $this->timeboxDuration);
445471
}
446472

447473
/**
@@ -453,17 +479,13 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe
453479
*/
454480
protected function hasValidCredentials($user, $credentials)
455481
{
456-
return $this->timebox->call(function ($timebox) use ($user, $credentials) {
457-
$validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
458-
459-
if ($validated) {
460-
$timebox->returnEarly();
482+
$validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
461483

462-
$this->fireValidatedEvent($user);
463-
}
484+
if ($validated) {
485+
$this->fireValidatedEvent($user);
486+
}
464487

465-
return $validated;
466-
}, 200 * 1000);
488+
return $validated;
467489
}
468490

469491
/**

0 commit comments

Comments
 (0)