123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775 |
- <?php
-
- namespace Illuminate\Auth;
-
- use RuntimeException;
- use Illuminate\Support\Str;
- use Illuminate\Support\Facades\Hash;
- use Illuminate\Support\Traits\Macroable;
- use Illuminate\Contracts\Session\Session;
- use Illuminate\Contracts\Auth\UserProvider;
- use Illuminate\Contracts\Events\Dispatcher;
- use Illuminate\Contracts\Auth\StatefulGuard;
- use Symfony\Component\HttpFoundation\Request;
- use Illuminate\Contracts\Auth\SupportsBasicAuth;
- use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
- use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
- use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
-
- class SessionGuard implements StatefulGuard, SupportsBasicAuth
- {
- use GuardHelpers, Macroable;
-
- /**
- * The name of the Guard. Typically "session".
- *
- * Corresponds to guard name in authentication configuration.
- *
- * @var string
- */
- protected $name;
-
- /**
- * The user we last attempted to retrieve.
- *
- * @var \Illuminate\Contracts\Auth\Authenticatable
- */
- protected $lastAttempted;
-
- /**
- * Indicates if the user was authenticated via a recaller cookie.
- *
- * @var bool
- */
- protected $viaRemember = false;
-
- /**
- * The session used by the guard.
- *
- * @var \Illuminate\Contracts\Session\Session
- */
- protected $session;
-
- /**
- * The Illuminate cookie creator service.
- *
- * @var \Illuminate\Contracts\Cookie\QueueingFactory
- */
- protected $cookie;
-
- /**
- * The request instance.
- *
- * @var \Symfony\Component\HttpFoundation\Request
- */
- protected $request;
-
- /**
- * The event dispatcher instance.
- *
- * @var \Illuminate\Contracts\Events\Dispatcher
- */
- protected $events;
-
- /**
- * Indicates if the logout method has been called.
- *
- * @var bool
- */
- protected $loggedOut = false;
-
- /**
- * Indicates if a token user retrieval has been attempted.
- *
- * @var bool
- */
- protected $recallAttempted = false;
-
- /**
- * Create a new authentication guard.
- *
- * @param string $name
- * @param \Illuminate\Contracts\Auth\UserProvider $provider
- * @param \Illuminate\Contracts\Session\Session $session
- * @param \Symfony\Component\HttpFoundation\Request|null $request
- * @return void
- */
- public function __construct($name,
- UserProvider $provider,
- Session $session,
- Request $request = null)
- {
- $this->name = $name;
- $this->session = $session;
- $this->request = $request;
- $this->provider = $provider;
- }
-
- /**
- * Get the currently authenticated user.
- *
- * @return \Illuminate\Contracts\Auth\Authenticatable|null
- */
- public function user()
- {
- if ($this->loggedOut) {
- return;
- }
-
- // If we've already retrieved the user for the current request we can just
- // return it back immediately. We do not want to fetch the user data on
- // every call to this method because that would be tremendously slow.
- if (! is_null($this->user)) {
- return $this->user;
- }
-
- $id = $this->session->get($this->getName());
-
- // First we will try to load the user using the identifier in the session if
- // one exists. Otherwise we will check for a "remember me" cookie in this
- // request, and if one exists, attempt to retrieve the user using that.
- if (! is_null($id)) {
- if ($this->user = $this->provider->retrieveById($id)) {
- $this->fireAuthenticatedEvent($this->user);
- }
- }
-
- // If the user is null, but we decrypt a "recaller" cookie we can attempt to
- // pull the user data on that cookie which serves as a remember cookie on
- // the application. Once we have a user we can return it to the caller.
- $recaller = $this->recaller();
-
- if (is_null($this->user) && ! is_null($recaller)) {
- $this->user = $this->userFromRecaller($recaller);
-
- if ($this->user) {
- $this->updateSession($this->user->getAuthIdentifier());
-
- $this->fireLoginEvent($this->user, true);
- }
- }
-
- return $this->user;
- }
-
- /**
- * Pull a user from the repository by its "remember me" cookie token.
- *
- * @param \Illuminate\Auth\Recaller $recaller
- * @return mixed
- */
- protected function userFromRecaller($recaller)
- {
- if (! $recaller->valid() || $this->recallAttempted) {
- return;
- }
-
- // If the user is null, but we decrypt a "recaller" cookie we can attempt to
- // pull the user data on that cookie which serves as a remember cookie on
- // the application. Once we have a user we can return it to the caller.
- $this->recallAttempted = true;
-
- $this->viaRemember = ! is_null($user = $this->provider->retrieveByToken(
- $recaller->id(), $recaller->token()
- ));
-
- return $user;
- }
-
- /**
- * Get the decrypted recaller cookie for the request.
- *
- * @return \Illuminate\Auth\Recaller|null
- */
- protected function recaller()
- {
- if (is_null($this->request)) {
- return;
- }
-
- if ($recaller = $this->request->cookies->get($this->getRecallerName())) {
- return new Recaller($recaller);
- }
- }
-
- /**
- * Get the ID for the currently authenticated user.
- *
- * @return int|null
- */
- public function id()
- {
- if ($this->loggedOut) {
- return;
- }
-
- return $this->user()
- ? $this->user()->getAuthIdentifier()
- : $this->session->get($this->getName());
- }
-
- /**
- * Log a user into the application without sessions or cookies.
- *
- * @param array $credentials
- * @return bool
- */
- public function once(array $credentials = [])
- {
- $this->fireAttemptEvent($credentials);
-
- if ($this->validate($credentials)) {
- $this->setUser($this->lastAttempted);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Log the given user ID into the application without sessions or cookies.
- *
- * @param mixed $id
- * @return \Illuminate\Contracts\Auth\Authenticatable|false
- */
- public function onceUsingId($id)
- {
- if (! is_null($user = $this->provider->retrieveById($id))) {
- $this->setUser($user);
-
- return $user;
- }
-
- return false;
- }
-
- /**
- * Validate a user's credentials.
- *
- * @param array $credentials
- * @return bool
- */
- public function validate(array $credentials = [])
- {
- $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
-
- return $this->hasValidCredentials($user, $credentials);
- }
-
- /**
- * Attempt to authenticate using HTTP Basic Auth.
- *
- * @param string $field
- * @param array $extraConditions
- * @return \Symfony\Component\HttpFoundation\Response|null
- */
- public function basic($field = 'email', $extraConditions = [])
- {
- if ($this->check()) {
- return;
- }
-
- // If a username is set on the HTTP basic request, we will return out without
- // interrupting the request lifecycle. Otherwise, we'll need to generate a
- // request indicating that the given credentials were invalid for login.
- if ($this->attemptBasic($this->getRequest(), $field, $extraConditions)) {
- return;
- }
-
- return $this->failedBasicResponse();
- }
-
- /**
- * Perform a stateless HTTP Basic login attempt.
- *
- * @param string $field
- * @param array $extraConditions
- * @return \Symfony\Component\HttpFoundation\Response|null
- */
- public function onceBasic($field = 'email', $extraConditions = [])
- {
- $credentials = $this->basicCredentials($this->getRequest(), $field);
-
- if (! $this->once(array_merge($credentials, $extraConditions))) {
- return $this->failedBasicResponse();
- }
- }
-
- /**
- * Attempt to authenticate using basic authentication.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- * @param string $field
- * @param array $extraConditions
- * @return bool
- */
- protected function attemptBasic(Request $request, $field, $extraConditions = [])
- {
- if (! $request->getUser()) {
- return false;
- }
-
- return $this->attempt(array_merge(
- $this->basicCredentials($request, $field), $extraConditions
- ));
- }
-
- /**
- * Get the credential array for a HTTP Basic request.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- * @param string $field
- * @return array
- */
- protected function basicCredentials(Request $request, $field)
- {
- return [$field => $request->getUser(), 'password' => $request->getPassword()];
- }
-
- /**
- * Get the response for basic authentication.
- *
- * @return void
- * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
- */
- protected function failedBasicResponse()
- {
- throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
- }
-
- /**
- * Attempt to authenticate a user using the given credentials.
- *
- * @param array $credentials
- * @param bool $remember
- * @return bool
- */
- public function attempt(array $credentials = [], $remember = false)
- {
- $this->fireAttemptEvent($credentials, $remember);
-
- $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
-
- // If an implementation of UserInterface was returned, we'll ask the provider
- // to validate the user against the given credentials, and if they are in
- // fact valid we'll log the users into the application and return true.
- if ($this->hasValidCredentials($user, $credentials)) {
- $this->login($user, $remember);
-
- return true;
- }
-
- // If the authentication attempt fails we will fire an event so that the user
- // may be notified of any suspicious attempts to access their account from
- // an unrecognized user. A developer may listen to this event as needed.
- $this->fireFailedEvent($user, $credentials);
-
- return false;
- }
-
- /**
- * Determine if the user matches the credentials.
- *
- * @param mixed $user
- * @param array $credentials
- * @return bool
- */
- protected function hasValidCredentials($user, $credentials)
- {
- return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
- }
-
- /**
- * Log the given user ID into the application.
- *
- * @param mixed $id
- * @param bool $remember
- * @return \Illuminate\Contracts\Auth\Authenticatable|false
- */
- public function loginUsingId($id, $remember = false)
- {
- if (! is_null($user = $this->provider->retrieveById($id))) {
- $this->login($user, $remember);
-
- return $user;
- }
-
- return false;
- }
-
- /**
- * Log a user into the application.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @param bool $remember
- * @return void
- */
- public function login(AuthenticatableContract $user, $remember = false)
- {
- $this->updateSession($user->getAuthIdentifier());
-
- // If the user should be permanently "remembered" by the application we will
- // queue a permanent cookie that contains the encrypted copy of the user
- // identifier. We will then decrypt this later to retrieve the users.
- if ($remember) {
- $this->ensureRememberTokenIsSet($user);
-
- $this->queueRecallerCookie($user);
- }
-
- // If we have an event dispatcher instance set we will fire an event so that
- // any listeners will hook into the authentication events and run actions
- // based on the login and logout events fired from the guard instances.
- $this->fireLoginEvent($user, $remember);
-
- $this->setUser($user);
- }
-
- /**
- * Update the session with the given ID.
- *
- * @param string $id
- * @return void
- */
- protected function updateSession($id)
- {
- $this->session->put($this->getName(), $id);
-
- $this->session->migrate(true);
- }
-
- /**
- * Create a new "remember me" token for the user if one doesn't already exist.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @return void
- */
- protected function ensureRememberTokenIsSet(AuthenticatableContract $user)
- {
- if (empty($user->getRememberToken())) {
- $this->cycleRememberToken($user);
- }
- }
-
- /**
- * Queue the recaller cookie into the cookie jar.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @return void
- */
- protected function queueRecallerCookie(AuthenticatableContract $user)
- {
- $this->getCookieJar()->queue($this->createRecaller(
- $user->getAuthIdentifier().'|'.$user->getRememberToken().'|'.$user->getAuthPassword()
- ));
- }
-
- /**
- * Create a "remember me" cookie for a given ID.
- *
- * @param string $value
- * @return \Symfony\Component\HttpFoundation\Cookie
- */
- protected function createRecaller($value)
- {
- return $this->getCookieJar()->forever($this->getRecallerName(), $value);
- }
-
- /**
- * Log the user out of the application.
- *
- * @return void
- */
- public function logout()
- {
- $user = $this->user();
-
- // If we have an event dispatcher instance, we can fire off the logout event
- // so any further processing can be done. This allows the developer to be
- // listening for anytime a user signs out of this application manually.
- $this->clearUserDataFromStorage();
-
- if (! is_null($this->user)) {
- $this->cycleRememberToken($user);
- }
-
- if (isset($this->events)) {
- $this->events->dispatch(new Events\Logout($user));
- }
-
- // Once we have fired the logout event we will clear the users out of memory
- // so they are no longer available as the user is no longer considered as
- // being signed into this application and should not be available here.
- $this->user = null;
-
- $this->loggedOut = true;
- }
-
- /**
- * Remove the user data from the session and cookies.
- *
- * @return void
- */
- protected function clearUserDataFromStorage()
- {
- $this->session->remove($this->getName());
-
- if (! is_null($this->recaller())) {
- $this->getCookieJar()->queue($this->getCookieJar()
- ->forget($this->getRecallerName()));
- }
- }
-
- /**
- * Refresh the "remember me" token for the user.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @return void
- */
- protected function cycleRememberToken(AuthenticatableContract $user)
- {
- $user->setRememberToken($token = Str::random(60));
-
- $this->provider->updateRememberToken($user, $token);
- }
-
- /**
- * Invalidate other sessions for the current user.
- *
- * The application must be using the AuthenticateSession middleware.
- *
- * @param string $password
- * @param string $attribute
- * @return null|bool
- */
- public function logoutOtherDevices($password, $attribute = 'password')
- {
- if (! $this->user()) {
- return;
- }
-
- return tap($this->user()->forceFill([
- $attribute => Hash::make($password),
- ]))->save();
- }
-
- /**
- * Register an authentication attempt event listener.
- *
- * @param mixed $callback
- * @return void
- */
- public function attempting($callback)
- {
- if (isset($this->events)) {
- $this->events->listen(Events\Attempting::class, $callback);
- }
- }
-
- /**
- * Fire the attempt event with the arguments.
- *
- * @param array $credentials
- * @param bool $remember
- * @return void
- */
- protected function fireAttemptEvent(array $credentials, $remember = false)
- {
- if (isset($this->events)) {
- $this->events->dispatch(new Events\Attempting(
- $credentials, $remember
- ));
- }
- }
-
- /**
- * Fire the login event if the dispatcher is set.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @param bool $remember
- * @return void
- */
- protected function fireLoginEvent($user, $remember = false)
- {
- if (isset($this->events)) {
- $this->events->dispatch(new Events\Login($user, $remember));
- }
- }
-
- /**
- * Fire the authenticated event if the dispatcher is set.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @return void
- */
- protected function fireAuthenticatedEvent($user)
- {
- if (isset($this->events)) {
- $this->events->dispatch(new Events\Authenticated($user));
- }
- }
-
- /**
- * Fire the failed authentication attempt event with the given arguments.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable|null $user
- * @param array $credentials
- * @return void
- */
- protected function fireFailedEvent($user, array $credentials)
- {
- if (isset($this->events)) {
- $this->events->dispatch(new Events\Failed($user, $credentials));
- }
- }
-
- /**
- * Get the last user we attempted to authenticate.
- *
- * @return \Illuminate\Contracts\Auth\Authenticatable
- */
- public function getLastAttempted()
- {
- return $this->lastAttempted;
- }
-
- /**
- * Get a unique identifier for the auth session value.
- *
- * @return string
- */
- public function getName()
- {
- return 'login_'.$this->name.'_'.sha1(static::class);
- }
-
- /**
- * Get the name of the cookie used to store the "recaller".
- *
- * @return string
- */
- public function getRecallerName()
- {
- return 'remember_'.$this->name.'_'.sha1(static::class);
- }
-
- /**
- * Determine if the user was authenticated via "remember me" cookie.
- *
- * @return bool
- */
- public function viaRemember()
- {
- return $this->viaRemember;
- }
-
- /**
- * Get the cookie creator instance used by the guard.
- *
- * @return \Illuminate\Contracts\Cookie\QueueingFactory
- *
- * @throws \RuntimeException
- */
- public function getCookieJar()
- {
- if (! isset($this->cookie)) {
- throw new RuntimeException('Cookie jar has not been set.');
- }
-
- return $this->cookie;
- }
-
- /**
- * Set the cookie creator instance used by the guard.
- *
- * @param \Illuminate\Contracts\Cookie\QueueingFactory $cookie
- * @return void
- */
- public function setCookieJar(CookieJar $cookie)
- {
- $this->cookie = $cookie;
- }
-
- /**
- * Get the event dispatcher instance.
- *
- * @return \Illuminate\Contracts\Events\Dispatcher
- */
- public function getDispatcher()
- {
- return $this->events;
- }
-
- /**
- * Set the event dispatcher instance.
- *
- * @param \Illuminate\Contracts\Events\Dispatcher $events
- * @return void
- */
- public function setDispatcher(Dispatcher $events)
- {
- $this->events = $events;
- }
-
- /**
- * Get the session store used by the guard.
- *
- * @return \Illuminate\Contracts\Session\Session
- */
- public function getSession()
- {
- return $this->session;
- }
-
- /**
- * Return the currently cached user.
- *
- * @return \Illuminate\Contracts\Auth\Authenticatable|null
- */
- public function getUser()
- {
- return $this->user;
- }
-
- /**
- * Set the current user.
- *
- * @param \Illuminate\Contracts\Auth\Authenticatable $user
- * @return $this
- */
- public function setUser(AuthenticatableContract $user)
- {
- $this->user = $user;
-
- $this->loggedOut = false;
-
- $this->fireAuthenticatedEvent($user);
-
- return $this;
- }
-
- /**
- * Get the current request instance.
- *
- * @return \Symfony\Component\HttpFoundation\Request
- */
- public function getRequest()
- {
- return $this->request ?: Request::createFromGlobals();
- }
-
- /**
- * Set the current request instance.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- * @return $this
- */
- public function setRequest(Request $request)
- {
- $this->request = $request;
-
- return $this;
- }
- }
|