Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cce60ba

Browse files
committed
Shift rehashing into SessionGuard
The Session guard's attempt() method is a better place to apply rehashing than the validateCredentials() method on the provider. The latter shouldn't have side-effects, as per it's name.
1 parent d6e06b5 commit cce60ba

File tree

7 files changed

+116
-55
lines changed

7 files changed

+116
-55
lines changed

src/Illuminate/Auth/DatabaseUserProvider.php

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Auth;
44

55
use Closure;
6+
use Illuminate\Contracts\Auth\Authenticatable;
67
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
78
use Illuminate\Contracts\Auth\UserProvider;
89
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
@@ -154,32 +155,26 @@ protected function getGenericUser($user)
154155
*/
155156
public function validateCredentials(UserContract $user, array $credentials)
156157
{
157-
if (is_null($plain = $credentials['password'])) {
158-
return false;
159-
}
160-
161-
if (! $this->hasher->check($plain, $hash = $user->getAuthPassword())) {
162-
return false;
163-
}
164-
165-
if ($this->hasher->needsRehash($hash)) {
166-
$this->rehashUserPassword($user, $plain);
167-
}
168-
169-
return true;
158+
return $this->hasher->check(
159+
$credentials['password'], $user->getAuthPassword()
160+
);
170161
}
171162

172163
/**
173-
* Rehash the user's password.
164+
* Rehash the user's password if required and supported.
174165
*
175166
* @param \Illuminate\Contracts\Auth\Authenticatable $user
176-
* @param string $plain
177-
* @return void
167+
* @param array $credentials
168+
* @return string|null
178169
*/
179-
public function rehashUserPassword(UserContract $user, string $plain): void
170+
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials)
180171
{
172+
if (! $this->hasher->needsRehash($user->getAuthPassword())) {
173+
return;
174+
}
175+
181176
$this->connection->table($this->table)
182177
->where($user->getAuthIdentifierName(), $user->getAuthIdentifier())
183-
->update([$user->getAuthPasswordName() => $this->hasher->make($plain)]);
178+
->update([$user->getAuthPasswordName() => $this->hasher->make($credentials['password'])]);
184179
}
185180
}

src/Illuminate/Auth/EloquentUserProvider.php

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -152,28 +152,24 @@ public function validateCredentials(UserContract $user, array $credentials)
152152
return false;
153153
}
154154

155-
if (! $this->hasher->check($plain, $hash = $user->getAuthPassword())) {
156-
return false;
157-
}
158-
159-
if ($this->hasher->needsRehash($hash)) {
160-
$this->rehashUserPassword($user, $plain);
161-
}
162-
163-
return true;
155+
return $this->hasher->check($plain, $user->getAuthPassword());
164156
}
165157

166158
/**
167-
* Rehash the user's password.
159+
* Rehash the user's password if required and supported.
168160
*
169161
* @param \Illuminate\Contracts\Auth\Authenticatable $user
170-
* @param string $plain
171-
* @return void
162+
* @param array $credentials
163+
* @return string|null
172164
*/
173-
public function rehashUserPassword(UserContract $user, string $plain): void
165+
public function rehashPasswordIfRequired(UserContract $user, array $credentials)
174166
{
167+
if (! $this->hasher->needsRehash($user->getAuthPassword())) {
168+
return;
169+
}
170+
175171
$user->forceFill([
176-
$user->getAuthPasswordName() => $this->hasher->make($plain),
172+
$user->getAuthPasswordName() => $this->hasher->make($credentials['password']),
177173
])->save();
178174
}
179175

src/Illuminate/Auth/SessionGuard.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ public function attempt(array $credentials = [], $remember = false)
384384
// to validate the user against the given credentials, and if they are in
385385
// fact valid we'll log the users into the application and return true.
386386
if ($this->hasValidCredentials($user, $credentials)) {
387+
$this->provider->rehashPasswordIfRequired($user, $credentials);
387388
$this->login($user, $remember);
388389

389390
return true;
@@ -415,6 +416,7 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe
415416
// the user is retrieved and validated. If one of the callbacks returns falsy we do
416417
// not login the user. Instead, we will fail the specific authentication attempt.
417418
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
419+
$this->provider->rehashPasswordIfRequired($user, $credentials);
418420
$this->login($user, $remember);
419421

420422
return true;

src/Illuminate/Contracts/Auth/UserProvider.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,13 @@ public function retrieveByCredentials(array $credentials);
4646
* @return bool
4747
*/
4848
public function validateCredentials(Authenticatable $user, array $credentials);
49+
50+
/**
51+
* Rehash the user's password if required and supported.
52+
*
53+
* @param \Illuminate\Contracts\Auth\Authenticatable $user
54+
* @param array $credentials
55+
* @return void
56+
*/
57+
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials);
4958
}

tests/Auth/AuthDatabaseUserProviderTest.php

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Tests\Auth;
44

55
use Illuminate\Auth\DatabaseUserProvider;
6+
use Illuminate\Auth\EloquentUserProvider;
67
use Illuminate\Auth\GenericUser;
78
use Illuminate\Contracts\Auth\Authenticatable;
89
use Illuminate\Contracts\Hashing\Hasher;
@@ -154,7 +155,6 @@ public function testCredentialValidation()
154155
$conn = m::mock(Connection::class);
155156
$hasher = m::mock(Hasher::class);
156157
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(true);
157-
$hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(false);
158158
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
159159
$user = m::mock(Authenticatable::class);
160160
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
@@ -163,38 +163,60 @@ public function testCredentialValidation()
163163
$this->assertTrue($result);
164164
}
165165

166-
public function testCredentialValidationRequiresRehash()
166+
public function testCredentialValidationFailed()
167+
{
168+
$conn = m::mock(Connection::class);
169+
$hasher = m::mock(Hasher::class);
170+
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(false);
171+
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
172+
$user = m::mock(Authenticatable::class);
173+
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
174+
$result = $provider->validateCredentials($user, ['password' => 'plain']);
175+
176+
$this->assertFalse($result);
177+
}
178+
179+
public function testRehashPasswordIfRequired()
167180
{
168181
$hasher = m::mock(Hasher::class);
169-
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(true);
170182
$hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(true);
171183
$hasher->shouldReceive('make')->once()->with('plain')->andReturn('rehashed');
184+
172185
$conn = m::mock(Connection::class);
173186
$table = m::mock(ConnectionInterface::class);
174187
$conn->shouldReceive('table')->once()->with('foo')->andReturn($table);
175188
$table->shouldReceive('where')->once()->with('id', 1)->andReturnSelf();
176189
$table->shouldReceive('update')->once()->with(['password_attribute' => 'rehashed']);
177-
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
190+
178191
$user = m::mock(Authenticatable::class);
179192
$user->shouldReceive('getAuthIdentifierName')->once()->andReturn('id');
180193
$user->shouldReceive('getAuthIdentifier')->once()->andReturn(1);
181194
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
182195
$user->shouldReceive('getAuthPasswordName')->once()->andReturn('password_attribute');
183-
$result = $provider->validateCredentials($user, ['password' => 'plain']);
184196

185-
$this->assertTrue($result);
197+
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
198+
$provider->rehashPasswordIfRequired($user, ['password' => 'plain']);
186199
}
187200

188-
public function testCredentialValidationFailed()
201+
public function testDontRehashPasswordIfNotRequired()
189202
{
190-
$conn = m::mock(Connection::class);
191203
$hasher = m::mock(Hasher::class);
192-
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(false);
193-
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
204+
$hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(false);
205+
$hasher->shouldNotReceive('make');
206+
207+
$conn = m::mock(Connection::class);
208+
$table = m::mock(ConnectionInterface::class);
209+
$conn->shouldNotReceive('table');
210+
$table->shouldNotReceive('where');
211+
$table->shouldNotReceive('update');
212+
194213
$user = m::mock(Authenticatable::class);
195214
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
196-
$result = $provider->validateCredentials($user, ['password' => 'plain']);
215+
$user->shouldNotReceive('getAuthIdentifierName');
216+
$user->shouldNotReceive('getAuthIdentifier');
217+
$user->shouldNotReceive('getAuthPasswordName');
197218

198-
$this->assertFalse($result);
219+
$provider = new DatabaseUserProvider($conn, $hasher, 'foo');
220+
$provider->rehashPasswordIfRequired($user, ['password' => 'plain']);
199221
}
200222
}

tests/Auth/AuthEloquentUserProviderTest.php

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ public function testCredentialValidation()
132132
{
133133
$hasher = m::mock(Hasher::class);
134134
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(true);
135-
$hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(false);
136135
$provider = new EloquentUserProvider($hasher, 'foo');
137136
$user = m::mock(Authenticatable::class);
138137
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
@@ -141,33 +140,48 @@ public function testCredentialValidation()
141140
$this->assertTrue($result);
142141
}
143142

144-
public function testCredentialValidationRequiresRehash()
143+
public function testCredentialValidationFailed()
144+
{
145+
$hasher = m::mock(Hasher::class);
146+
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(false);
147+
$provider = new EloquentUserProvider($hasher, 'foo');
148+
$user = m::mock(Authenticatable::class);
149+
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
150+
$result = $provider->validateCredentials($user, ['password' => 'plain']);
151+
152+
$this->assertFalse($result);
153+
}
154+
155+
public function testRehashPasswordIfRequired()
145156
{
146157
$hasher = m::mock(Hasher::class);
147-
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(true);
148158
$hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(true);
149159
$hasher->shouldReceive('make')->once()->with('plain')->andReturn('rehashed');
150-
$provider = new EloquentUserProvider($hasher, 'foo');
160+
151161
$user = m::mock(Authenticatable::class);
152162
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
153163
$user->shouldReceive('getAuthPasswordName')->once()->andReturn('password_attribute');
154164
$user->shouldReceive('forceFill')->once()->with(['password_attribute' => 'rehashed'])->andReturnSelf();
155165
$user->shouldReceive('save')->once();
156-
$result = $provider->validateCredentials($user, ['password' => 'plain']);
157166

158-
$this->assertTrue($result);
167+
$provider = new EloquentUserProvider($hasher, 'foo');
168+
$provider->rehashPasswordIfRequired($user, ['password' => 'plain']);
159169
}
160170

161-
public function testCredentialValidationFailed()
171+
public function testDontRehashPasswordIfNotRequired()
162172
{
163173
$hasher = m::mock(Hasher::class);
164-
$hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(false);
165-
$provider = new EloquentUserProvider($hasher, 'foo');
174+
$hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(false);
175+
$hasher->shouldNotReceive('make');
176+
166177
$user = m::mock(Authenticatable::class);
167178
$user->shouldReceive('getAuthPassword')->once()->andReturn('hash');
168-
$result = $provider->validateCredentials($user, ['password' => 'plain']);
179+
$user->shouldNotReceive('getAuthPasswordName');
180+
$user->shouldNotReceive('forceFill');
181+
$user->shouldNotReceive('save');
169182

170-
$this->assertFalse($result);
183+
$provider = new EloquentUserProvider($hasher, 'foo');
184+
$provider->rehashPasswordIfRequired($user, ['password' => 'plain']);
171185
}
172186

173187
public function testModelsCanBeCreated()

tests/Auth/AuthGuardTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public function testAttemptCallsRetrieveByCredentials()
103103
$events->shouldReceive('dispatch')->once()->with(m::type(Failed::class));
104104
$events->shouldNotReceive('dispatch')->with(m::type(Validated::class));
105105
$guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo']);
106+
$guard->getProvider()->shouldNotReceive('rehashPasswordIfRequired');
106107
$guard->attempt(['foo']);
107108
}
108109

@@ -119,6 +120,7 @@ public function testAttemptReturnsUserInterface()
119120
$user = $this->createMock(Authenticatable::class);
120121
$guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn($user);
121122
$guard->getProvider()->shouldReceive('validateCredentials')->with($user, ['foo'])->andReturn(true);
123+
$guard->getProvider()->shouldReceive('rehashPasswordIfRequired')->with($user, ['foo'])->once();
122124
$guard->expects($this->once())->method('login')->with($this->equalTo($user));
123125
$this->assertTrue($guard->attempt(['foo']));
124126
}
@@ -135,6 +137,7 @@ public function testAttemptReturnsFalseIfUserNotGiven()
135137
$events->shouldReceive('dispatch')->once()->with(m::type(Failed::class));
136138
$events->shouldNotReceive('dispatch')->with(m::type(Validated::class));
137139
$mock->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn(null);
140+
$mock->getProvider()->shouldNotReceive('rehashPasswordIfRequired');
138141
$this->assertFalse($mock->attempt(['foo']));
139142
}
140143

@@ -159,6 +162,7 @@ public function testAttemptAndWithCallbacks()
159162
$mock->getProvider()->shouldReceive('retrieveByCredentials')->times(3)->with(['foo'])->andReturn($user);
160163
$mock->getProvider()->shouldReceive('validateCredentials')->twice()->andReturnTrue();
161164
$mock->getProvider()->shouldReceive('validateCredentials')->once()->andReturnFalse();
165+
$mock->getProvider()->shouldReceive('rehashPasswordIfRequired')->with($user, ['foo'])->once();
162166

163167
$this->assertTrue($mock->attemptWhen(['foo'], function ($user, $guard) {
164168
static::assertInstanceOf(Authenticatable::class, $user);
@@ -183,6 +187,24 @@ public function testAttemptAndWithCallbacks()
183187
$this->assertFalse($executed);
184188
}
185189

190+
public function testAttemptRehashesPasswordWhenRequired()
191+
{
192+
[$session, $provider, $request, $cookie, $timebox] = $this->getMocks();
193+
$guard = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['login'])->setConstructorArgs(['default', $provider, $session, $request, $timebox])->getMock();
194+
$guard->setDispatcher($events = m::mock(Dispatcher::class));
195+
$timebox->shouldReceive('call')->once()->andReturnUsing(function ($callback, $microseconds) use ($timebox) {
196+
return $callback($timebox->shouldReceive('returnEarly')->once()->getMock());
197+
});
198+
$events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class));
199+
$events->shouldReceive('dispatch')->once()->with(m::type(Validated::class));
200+
$user = $this->createMock(Authenticatable::class);
201+
$guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn($user);
202+
$guard->getProvider()->shouldReceive('validateCredentials')->with($user, ['foo'])->andReturn(true);
203+
$guard->getProvider()->shouldReceive('rehashPasswordIfRequired')->with($user, ['foo'])->once();
204+
$guard->expects($this->once())->method('login')->with($this->equalTo($user));
205+
$this->assertTrue($guard->attempt(['foo']));
206+
}
207+
186208
public function testLoginStoresIdentifierInSession()
187209
{
188210
[$session, $provider, $request, $cookie] = $this->getMocks();
@@ -235,6 +257,7 @@ public function testFailedAttemptFiresFailedEvent()
235257
$events->shouldReceive('dispatch')->once()->with(m::type(Failed::class));
236258
$events->shouldNotReceive('dispatch')->with(m::type(Validated::class));
237259
$guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn(null);
260+
$guard->getProvider()->shouldNotReceive('rehashPasswordIfRequired');
238261
$guard->attempt(['foo']);
239262
}
240263

0 commit comments

Comments
 (0)