diff --git a/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub b/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub index 0ceed5eab268..0bd61b85dae3 100644 --- a/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub +++ b/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub @@ -54,7 +54,7 @@ Login - + Forgot Your Password? diff --git a/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub b/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub index 582b7586b2ec..e61738616f5c 100644 --- a/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub +++ b/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub @@ -14,60 +14,55 @@ @endif -
- {{ csrf_field() }} - - - -
- + @if (session('warning')) +
+ {{ session('warning') }} +
+ @else -
- + + {{ csrf_field() }} - @if ($errors->has('email')) - - {{ $errors->first('email') }} - - @endif -
-
+ + + -
- +
+ -
- +
+ - @if ($errors->has('password')) - - {{ $errors->first('password') }} - - @endif + @if ($errors->has('password')) + + {{ $errors->first('password') }} + + @endif +
-
-
- -
- +
+ +
+ - @if ($errors->has('password_confirmation')) - - {{ $errors->first('password_confirmation') }} - - @endif + @if ($errors->has('password_confirmation')) + + {{ $errors->first('password_confirmation') }} + + @endif +
-
-
-
- +
+
+ +
-
- + + @endif
diff --git a/src/Illuminate/Auth/Notifications/ResetPassword.php b/src/Illuminate/Auth/Notifications/ResetPassword.php index 7e4f0fbeae7a..2c3ae7dac1eb 100644 --- a/src/Illuminate/Auth/Notifications/ResetPassword.php +++ b/src/Illuminate/Auth/Notifications/ResetPassword.php @@ -14,15 +14,24 @@ class ResetPassword extends Notification */ public $token; + /** + * The password reset expiration date. + * + * @var int + */ + public $expiration; + /** * Create a notification instance. * * @param string $token + * @param int $expiration * @return void */ - public function __construct($token) + public function __construct($token, $expiration) { $this->token = $token; + $this->expiration = $expiration; } /** @@ -44,9 +53,12 @@ public function via($notifiable) */ public function toMail($notifiable) { + $email = $notifiable->getEmailForPasswordReset(); + $link = url("password/reset?email={$email}&expiration={$this->expiration}&token={$this->token}"); + return (new MailMessage) ->line('You are receiving this email because we received a password reset request for your account.') - ->action('Reset Password', url('password/reset', $this->token)) + ->action('Reset Password', $link) ->line('If you did not request a password reset, no further action is required.'); } } diff --git a/src/Illuminate/Auth/Passwords/CanResetPassword.php b/src/Illuminate/Auth/Passwords/CanResetPassword.php index 918a288fec66..83c81ee39cdc 100644 --- a/src/Illuminate/Auth/Passwords/CanResetPassword.php +++ b/src/Illuminate/Auth/Passwords/CanResetPassword.php @@ -20,10 +20,11 @@ public function getEmailForPasswordReset() * Send the password reset notification. * * @param string $token + * @param int $expiration * @return void */ - public function sendPasswordResetNotification($token) + public function sendPasswordResetNotification($token, $expiration) { - $this->notify(new ResetPasswordNotification($token)); + $this->notify(new ResetPasswordNotification($token, $expiration)); } } diff --git a/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php b/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php deleted file mode 100755 index aff32ca17b04..000000000000 --- a/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php +++ /dev/null @@ -1,204 +0,0 @@ -table = $table; - $this->hasher = $hasher; - $this->hashKey = $hashKey; - $this->expires = $expires * 60; - $this->connection = $connection; - } - - /** - * Create a new token record. - * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @return string - */ - public function create(CanResetPasswordContract $user) - { - $email = $user->getEmailForPasswordReset(); - - $this->deleteExisting($user); - - // We will create a new, random token for the user so that we can e-mail them - // a safe link to the password reset form. Then we will insert a record in - // the database so that we can verify the token within the actual reset. - $token = $this->createNewToken(); - - $this->getTable()->insert($this->getPayload($email, $token)); - - return $token; - } - - /** - * Delete all existing reset tokens from the database. - * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @return int - */ - protected function deleteExisting(CanResetPasswordContract $user) - { - return $this->getTable()->where('email', $user->getEmailForPasswordReset())->delete(); - } - - /** - * Build the record payload for the table. - * - * @param string $email - * @param string $token - * @return array - */ - protected function getPayload($email, $token) - { - return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon]; - } - - /** - * Determine if a token record exists and is valid. - * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @param string $token - * @return bool - */ - public function exists(CanResetPasswordContract $user, $token) - { - $record = (array) $this->getTable()->where( - 'email', $user->getEmailForPasswordReset() - )->first(); - - return $record && - ! $this->tokenExpired($record['created_at']) && - $this->hasher->check($token, $record['token']); - } - - /** - * Determine if the token has expired. - * - * @param string $createdAt - * @return bool - */ - protected function tokenExpired($createdAt) - { - return Carbon::parse($createdAt)->addSeconds($this->expires)->isPast(); - } - - /** - * Delete a token record by user. - * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @return void - */ - public function delete(CanResetPasswordContract $user) - { - $this->deleteExisting($user); - } - - /** - * Delete expired tokens. - * - * @return void - */ - public function deleteExpired() - { - $expiredAt = Carbon::now()->subSeconds($this->expires); - - $this->getTable()->where('created_at', '<', $expiredAt)->delete(); - } - - /** - * Create a new token for the user. - * - * @return string - */ - public function createNewToken() - { - return hash_hmac('sha256', Str::random(40), $this->hashKey); - } - - /** - * Get the database connection instance. - * - * @return \Illuminate\Database\ConnectionInterface - */ - public function getConnection() - { - return $this->connection; - } - - /** - * Begin a new database query against the table. - * - * @return \Illuminate\Database\Query\Builder - */ - protected function getTable() - { - return $this->connection->table($this->table); - } - - /** - * Get the hasher instance. - * - * @return \Illuminate\Contracts\Hashing\Hasher - */ - public function getHasher() - { - return $this->hasher; - } -} diff --git a/src/Illuminate/Auth/Passwords/PasswordBroker.php b/src/Illuminate/Auth/Passwords/PasswordBroker.php index e72ad0189c40..b423ab08b58b 100755 --- a/src/Illuminate/Auth/Passwords/PasswordBroker.php +++ b/src/Illuminate/Auth/Passwords/PasswordBroker.php @@ -3,20 +3,23 @@ namespace Illuminate\Auth\Passwords; use Closure; +use Carbon\Carbon; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use UnexpectedValueException; use Illuminate\Contracts\Auth\UserProvider; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; class PasswordBroker implements PasswordBrokerContract { /** - * The password token repository. + * The application instance. * - * @var \Illuminate\Auth\Passwords\TokenRepositoryInterface + * @var \Illuminate\Foundation\Application */ - protected $tokens; + protected $app; /** * The user provider implementation. @@ -25,6 +28,13 @@ class PasswordBroker implements PasswordBrokerContract */ protected $users; + /** + * The number of minutes that the reset token should be considered valid. + * + * @var int + */ + protected $expiration; + /** * The custom password validator callback. * @@ -35,15 +45,15 @@ class PasswordBroker implements PasswordBrokerContract /** * Create a new password broker instance. * - * @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens + * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Contracts\Auth\UserProvider $users * @return void */ - public function __construct(TokenRepositoryInterface $tokens, - UserProvider $users) + public function __construct(Application $app, UserProvider $users, $expiration) { + $this->app = $app; $this->users = $users; - $this->tokens = $tokens; + $this->expiration = $expiration; } /** @@ -63,11 +73,14 @@ public function sendResetLink(array $credentials) return static::INVALID_USER; } + $expiration = Carbon::now()->addMinutes($this->expiration)->timestamp; + // Once we have the reset token, we are ready to send the message out to this // user with a link to reset their password. We will then redirect back to // the current URI having nothing set in the session to indicate errors. $user->sendPasswordResetNotification( - $this->tokens->create($user) + $this->createToken($user, $expiration), + $expiration ); return static::RESET_LINK_SENT; @@ -95,11 +108,9 @@ public function reset(array $credentials, Closure $callback) // Once the reset has been validated, we'll call the given callback with the // new password. This gives the user an opportunity to store the password - // in their persistent storage. Then we'll delete the token and return. + // in their persistent storage. $callback($user, $password); - $this->tokens->delete($user); - return static::PASSWORD_RESET; } @@ -119,10 +130,14 @@ protected function validateReset(array $credentials) return static::INVALID_PASSWORD; } - if (! $this->tokens->exists($user, $credentials['token'])) { + if (! $this->validateToken($user, $credentials)) { return static::INVALID_TOKEN; } + if (! $this->validateTimestamp($credentials['expiration'])) { + return static::EXPIRED_TOKEN; + } + return $user; } @@ -185,9 +200,7 @@ protected function validatePasswordWithDefaults(array $credentials) */ public function getUser(array $credentials) { - $credentials = Arr::except($credentials, ['token']); - - $user = $this->users->retrieveByCredentials($credentials); + $user = $this->users->retrieveByCredentials(Arr::only($credentials, ['email'])); if ($user && ! $user instanceof CanResetPasswordContract) { throw new UnexpectedValueException('User must implement CanResetPassword interface.'); @@ -200,43 +213,72 @@ public function getUser(array $credentials) * Create a new password reset token for the given user. * * @param CanResetPasswordContract $user + * @param int $expiration * @return string */ - public function createToken(CanResetPasswordContract $user) + public function createToken(CanResetPasswordContract $user, $expiration) { - return $this->tokens->create($user); + $payload = $this->buildPayload($user, $user->getEmailForPasswordReset(), $expiration); + + return hash_hmac('sha256', $payload, $this->getKey()); } /** - * Delete the given password reset token. + * Validate the given password reset token. * - * @param string $token - * @return void + * @param CanResetPasswordContract $user + * @param array $credentials + * @return bool */ - public function deleteToken($token) + public function validateToken(CanResetPasswordContract $user, array $credentials) { - $this->tokens->delete($token); + $payload = $this->buildPayload($user, $credentials['email'], $credentials['expiration']); + + return hash_equals($credentials['token'], hash_hmac('sha256', $payload, $this->getKey())); } /** - * Validate the given password reset token. + * Validate the given expiration timestamp. * - * @param CanResetPasswordContract $user - * @param string $token + * @param int $expiration * @return bool */ - public function tokenExists(CanResetPasswordContract $user, $token) + public function validateTimestamp($expiration) + { + return Carbon::createFromTimestamp($expiration)->isFuture(); + } + + /** + * Return the application key. + * + * @return string + */ + public function getKey() { - return $this->tokens->exists($user, $token); + $key = $this->app['config']['app.key']; + + if (Str::startsWith($key, 'base64:')) { + $key = base64_decode(substr($key, 7)); + } + + return $key; } /** - * Get the password reset token repository implementation. + * Returns the payload string containing. * - * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface + * @param CanResetPasswordContract $user + * @param string $email + * @param int $expiration + * @return string */ - public function getRepository() + protected function buildPayload(CanResetPasswordContract $user, $email, $expiration) { - return $this->tokens; + return implode(';', [ + $email, + $expiration, + $user->getKey(), + $user->password, + ]); } } diff --git a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php index 0f3f26283bfc..0e3e4929cfae 100644 --- a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php +++ b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php @@ -2,7 +2,6 @@ namespace Illuminate\Auth\Passwords; -use Illuminate\Support\Str; use InvalidArgumentException; use Illuminate\Contracts\Auth\PasswordBrokerFactory as FactoryContract; @@ -64,36 +63,9 @@ protected function resolve($name) throw new InvalidArgumentException("Password resetter [{$name}] is not defined."); } - // The password broker uses a token repository to validate tokens and send user - // password e-mails, as well as validating that password reset process as an - // aggregate service of sorts providing a convenient interface for resets. return new PasswordBroker( - $this->createTokenRepository($config), - $this->app['auth']->createUserProvider($config['provider']) - ); - } - - /** - * Create a token repository instance based on the given configuration. - * - * @param array $config - * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface - */ - protected function createTokenRepository(array $config) - { - $key = $this->app['config']['app.key']; - - if (Str::startsWith($key, 'base64:')) { - $key = base64_decode(substr($key, 7)); - } - - $connection = isset($config['connection']) ? $config['connection'] : null; - - return new DatabaseTokenRepository( - $this->app['db']->connection($connection), - $this->app['hash'], - $config['table'], - $key, + $this->app, + $this->app['auth']->createUserProvider($config['provider']), $config['expire'] ); } diff --git a/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php b/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php deleted file mode 100755 index dcd06e8d6516..000000000000 --- a/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php +++ /dev/null @@ -1,40 +0,0 @@ -with( - ['token' => $token, 'email' => $request->email] - ); + $credentials = $request->only('email', 'expiration', 'token'); + + if (! ($user = $this->broker()->getUser($credentials))) { + $request->session()->flash('warning', trans(Password::INVALID_USER)); + } + + if (! $this->broker()->validateToken($user, $credentials)) { + $request->session()->flash('warning', trans(Password::INVALID_TOKEN)); + } + + if (! $this->broker()->validateTimestamp($credentials['expiration'])) { + $request->session()->flash('warning', trans(Password::EXPIRED_TOKEN)); + } + + return view('auth.passwords.reset')->with($credentials); } /** @@ -64,6 +73,7 @@ protected function rules() return [ 'token' => 'required', 'email' => 'required|email', + 'expiration' => 'required|date_format:U', 'password' => 'required|confirmed|min:6', ]; } @@ -87,7 +97,7 @@ protected function validationErrorMessages() protected function credentials(Request $request) { return $request->only( - 'email', 'password', 'password_confirmation', 'token' + 'email', 'expiration', 'token', 'password', 'password_confirmation' ); } @@ -116,8 +126,7 @@ protected function resetPassword($user, $password) */ protected function sendResetResponse($response) { - return redirect($this->redirectPath()) - ->with('status', trans($response)); + return redirect($this->redirectPath())->with('status', trans($response)); } /** @@ -129,9 +138,7 @@ protected function sendResetResponse($response) */ protected function sendResetFailedResponse(Request $request, $response) { - return redirect()->back() - ->withInput($request->only('email')) - ->withErrors(['email' => trans($response)]); + return redirect()->back()->with('warning', trans($response)); } /** diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index ef0958a8b5d3..578ccc847014 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -1003,9 +1003,9 @@ public function auth() $this->post('register', 'Auth\RegisterController@register'); // Password Reset Routes... - $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); + $this->get('password/request', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail'); - $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); + $this->get('password/reset', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); $this->post('password/reset', 'Auth\ResetPasswordController@reset'); } diff --git a/src/Illuminate/Support/Facades/Password.php b/src/Illuminate/Support/Facades/Password.php index 6ebea841f99d..2ca3c28fcf29 100755 --- a/src/Illuminate/Support/Facades/Password.php +++ b/src/Illuminate/Support/Facades/Password.php @@ -42,6 +42,13 @@ class Password extends Facade */ const INVALID_TOKEN = 'passwords.token'; + /** + * Constant representing an expired token. + * + * @var string + */ + const EXPIRED_TOKEN = 'passwords.expired'; + /** * Get the registered name of the component. * diff --git a/tests/Auth/AuthDatabaseTokenRepositoryTest.php b/tests/Auth/AuthDatabaseTokenRepositoryTest.php deleted file mode 100755 index 13ff8fe8c4f9..000000000000 --- a/tests/Auth/AuthDatabaseTokenRepositoryTest.php +++ /dev/null @@ -1,116 +0,0 @@ -getRepo(); - $repo->getHasher()->shouldReceive('make')->andReturn('hashed-token'); - $repo->getConnection()->shouldReceive('table')->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->with('email', 'email')->andReturn($query); - $query->shouldReceive('delete')->once(); - $query->shouldReceive('insert')->once(); - $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); - $user->shouldReceive('getEmailForPasswordReset')->andReturn('email'); - - $results = $repo->create($user); - - $this->assertInternalType('string', $results); - $this->assertGreaterThan(1, strlen($results)); - } - - public function testExistReturnsFalseIfNoRowFoundForUser() - { - $repo = $this->getRepo(); - $repo->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->once()->with('email', 'email')->andReturn($query); - $query->shouldReceive('first')->andReturn(null); - $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); - $user->shouldReceive('getEmailForPasswordReset')->andReturn('email'); - - $this->assertFalse($repo->exists($user, 'token')); - } - - public function testExistReturnsFalseIfRecordIsExpired() - { - $repo = $this->getRepo(); - $repo->getHasher()->shouldReceive('check')->with('token', 'hashed-token')->andReturn(true); - $repo->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->once()->with('email', 'email')->andReturn($query); - $date = date('Y-m-d H:i:s', time() - 300000); - $query->shouldReceive('first')->andReturn((object) ['created_at' => $date, 'token' => 'hashed-token']); - $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); - $user->shouldReceive('getEmailForPasswordReset')->andReturn('email'); - - $this->assertFalse($repo->exists($user, 'token')); - } - - public function testExistReturnsTrueIfValidRecordExists() - { - $repo = $this->getRepo(); - $repo->getHasher()->shouldReceive('check')->with('token', 'hashed-token')->andReturn(true); - $repo->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->once()->with('email', 'email')->andReturn($query); - $date = date('Y-m-d H:i:s', time() - 600); - $query->shouldReceive('first')->andReturn((object) ['created_at' => $date, 'token' => 'hashed-token']); - $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); - $user->shouldReceive('getEmailForPasswordReset')->andReturn('email'); - - $this->assertTrue($repo->exists($user, 'token')); - } - - public function testExistReturnsFalseIfInvalidToken() - { - $repo = $this->getRepo(); - $repo->getHasher()->shouldReceive('check')->with('wrong-token', 'hashed-token')->andReturn(false); - $repo->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->once()->with('email', 'email')->andReturn($query); - $date = date('Y-m-d H:i:s', time() - 600); - $query->shouldReceive('first')->andReturn((object) ['created_at' => $date, 'token' => 'hashed-token']); - $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); - $user->shouldReceive('getEmailForPasswordReset')->andReturn('email'); - - $this->assertFalse($repo->exists($user, 'wrong-token')); - } - - public function testDeleteMethodDeletesByToken() - { - $repo = $this->getRepo(); - $repo->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->once()->with('email', 'email')->andReturn($query); - $query->shouldReceive('delete')->once(); - $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); - $user->shouldReceive('getEmailForPasswordReset')->andReturn('email'); - - $repo->delete($user); - } - - public function testDeleteExpiredMethodDeletesExpiredTokens() - { - $repo = $this->getRepo(); - $repo->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($query = m::mock('StdClass')); - $query->shouldReceive('where')->once()->with('created_at', '<', m::any())->andReturn($query); - $query->shouldReceive('delete')->once(); - - $repo->deleteExpired(); - } - - protected function getRepo() - { - return new DatabaseTokenRepository( - m::mock('Illuminate\Database\Connection'), - m::mock('Illuminate\Contracts\Hashing\Hasher'), - 'table', 'key'); - } -} diff --git a/tests/Auth/AuthPasswordBrokerTest.php b/tests/Auth/AuthPasswordBrokerTest.php index dc53c389d71e..8757bb714c42 100755 --- a/tests/Auth/AuthPasswordBrokerTest.php +++ b/tests/Auth/AuthPasswordBrokerTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Auth; use Mockery as m; +use Carbon\Carbon; use PHPUnit\Framework\TestCase; use Illuminate\Contracts\Auth\PasswordBroker; @@ -27,125 +28,113 @@ public function testIfUserIsNotFoundErrorRedirectIsReturned() */ public function testGetUserThrowsExceptionIfUserDoesntImplementCanResetPassword() { + $creds = ['email' => 'taylor@laravel.com']; $broker = $this->getBroker($mocks = $this->getMocks()); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn('bar'); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn('foo'); - $broker->getUser(['foo']); + $broker->getUser($creds); } public function testUserIsRetrievedByCredentials() { + $creds = ['email' => 'taylor@laravel.com']; $broker = $this->getBroker($mocks = $this->getMocks()); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); - $this->assertEquals($user, $broker->getUser(['foo'])); + $this->assertEquals($user, $broker->getUser($creds)); } public function testBrokerCreatesTokenAndRedirectsWithoutError() { + $creds = ['email' => 'taylor@laravel.com']; $mocks = $this->getMocks(); - $broker = $this->getMockBuilder('Illuminate\Auth\Passwords\PasswordBroker')->setMethods(['emailResetLink', 'getUri'])->setConstructorArgs(array_values($mocks))->getMock(); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); - $mocks['tokens']->shouldReceive('create')->once()->with($user)->andReturn('token'); + $broker = $this->getMockBuilder('Illuminate\Auth\Passwords\PasswordBroker')->setMethods(['emailResetLink', 'getKey'])->setConstructorArgs(array_values($mocks))->getMock(); + + $user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'); + $user->password = 'foo'; + $user->updated_at = Carbon::now(); + $user->shouldReceive('getEmailForPasswordReset')->once(); + $user->shouldReceive('getKey')->once(); + + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user); + $callback = function () { + // }; - $user->shouldReceive('sendPasswordResetNotification')->with('token'); + $user->shouldReceive('sendPasswordResetNotification'); - $this->assertEquals(PasswordBroker::RESET_LINK_SENT, $broker->sendResetLink(['foo'], $callback)); + $this->assertEquals(PasswordBroker::RESET_LINK_SENT, $broker->sendResetLink($creds, $callback)); } public function testRedirectIsReturnedByResetWhenUserCredentialsInvalid() { + $creds = ['email' => 'taylor@laravel.com']; $broker = $this->getBroker($mocks = $this->getMocks()); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['creds'])->andReturn(null); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn(null); - $this->assertEquals(PasswordBroker::INVALID_USER, $broker->reset(['creds'], function () { + $this->assertEquals(PasswordBroker::INVALID_USER, $broker->reset($creds, function () { + // })); } public function testRedirectReturnedByRemindWhenPasswordsDontMatch() { - $creds = ['password' => 'foo', 'password_confirmation' => 'bar']; + $creds = ['email' => 'taylor@laravel.com', 'password' => 'foo', 'password_confirmation' => 'bar']; $broker = $this->getBroker($mocks = $this->getMocks()); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'taylor@laravel.com'])->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); $this->assertEquals(PasswordBroker::INVALID_PASSWORD, $broker->reset($creds, function () { + // })); } public function testRedirectReturnedByRemindWhenPasswordNotSet() { - $creds = ['password' => null, 'password_confirmation' => null]; + $creds = ['email' => 'taylor@laravel.com', 'password' => null, 'password_confirmation' => null]; $broker = $this->getBroker($mocks = $this->getMocks()); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'taylor@laravel.com'])->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); $this->assertEquals(PasswordBroker::INVALID_PASSWORD, $broker->reset($creds, function () { + // })); } public function testRedirectReturnedByRemindWhenPasswordsLessThanSixCharacters() { - $creds = ['password' => 'abc', 'password_confirmation' => 'abc']; + $creds = ['email' => 'taylor@laravel.com', 'password' => 'abc', 'password_confirmation' => 'abc']; $broker = $this->getBroker($mocks = $this->getMocks()); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'taylor@laravel.com'])->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); $this->assertEquals(PasswordBroker::INVALID_PASSWORD, $broker->reset($creds, function () { + // })); } public function testRedirectReturnedByRemindWhenPasswordDoesntPassValidator() { - $creds = ['password' => 'abcdef', 'password_confirmation' => 'abcdef']; + $creds = ['email' => 'taylor@laravel.com', 'password' => 'abcdef', 'password_confirmation' => 'abcdef']; $broker = $this->getBroker($mocks = $this->getMocks()); $broker->validator(function ($credentials) { return strlen($credentials['password']) >= 7; }); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); + $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'taylor@laravel.com'])->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); $this->assertEquals(PasswordBroker::INVALID_PASSWORD, $broker->reset($creds, function () { + // })); } - public function testRedirectReturnedByRemindWhenRecordDoesntExistInTable() - { - $creds = ['token' => 'token']; - $broker = $this->getMockBuilder('Illuminate\Auth\Passwords\PasswordBroker')->setMethods(['validateNewPassword'])->setConstructorArgs(array_values($mocks = $this->getMocks()))->getMock(); - $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(array_except($creds, ['token']))->andReturn($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword')); - $broker->expects($this->once())->method('validateNewPassword')->will($this->returnValue(true)); - $mocks['tokens']->shouldReceive('exists')->with($user, 'token')->andReturn(false); - - $this->assertEquals(PasswordBroker::INVALID_TOKEN, $broker->reset($creds, function () { - })); - } - - public function testResetRemovesRecordOnReminderTableAndCallsCallback() - { - unset($_SERVER['__password.reset.test']); - $broker = $this->getMockBuilder('Illuminate\Auth\Passwords\PasswordBroker')->setMethods(['validateReset', 'getPassword', 'getToken'])->setConstructorArgs(array_values($mocks = $this->getMocks()))->getMock(); - $broker->expects($this->once())->method('validateReset')->will($this->returnValue($user = m::mock('Illuminate\Contracts\Auth\CanResetPassword'))); - $mocks['tokens']->shouldReceive('delete')->once()->with($user); - $callback = function ($user, $password) { - $_SERVER['__password.reset.test'] = compact('user', 'password'); - - return 'foo'; - }; - - $this->assertEquals(PasswordBroker::PASSWORD_RESET, $broker->reset(['password' => 'password', 'token' => 'token'], $callback)); - $this->assertEquals(['user' => $user, 'password' => 'password'], $_SERVER['__password.reset.test']); - } - protected function getBroker($mocks) { - return new \Illuminate\Auth\Passwords\PasswordBroker($mocks['tokens'], $mocks['users'], $mocks['mailer'], $mocks['view']); + return new \Illuminate\Auth\Passwords\PasswordBroker($mocks['app'], $mocks['users'], $mocks['expiration']); } protected function getMocks() { $mocks = [ - 'tokens' => m::mock('Illuminate\Auth\Passwords\TokenRepositoryInterface'), - 'users' => m::mock('Illuminate\Contracts\Auth\UserProvider'), - 'mailer' => m::mock('Illuminate\Contracts\Mail\Mailer'), - 'view' => 'resetLinkView', + 'app' => m::mock('Illuminate\Contracts\Foundation\Application'), + 'users' => m::mock('Illuminate\Contracts\Auth\UserProvider'), + 'expiration' => time(), ]; return $mocks;