Skip to content

Commit

Permalink
Update UserProvider API (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
evansims authored Mar 9, 2022
1 parent 557b93b commit 58ce97f
Show file tree
Hide file tree
Showing 15 changed files with 487 additions and 714 deletions.
252 changes: 121 additions & 131 deletions src/Auth/Guard.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Auth0\Laravel\Auth;

final class Guard implements \Illuminate\Contracts\Auth\Guard, \Auth0\Laravel\Contract\Auth\Guard
final class Guard implements \Auth0\Laravel\Contract\Auth\Guard, \Illuminate\Contracts\Auth\Guard
{
/**
* The user provider implementation.
Expand All @@ -16,36 +16,15 @@ final class Guard implements \Illuminate\Contracts\Auth\Guard, \Auth0\Laravel\Co
*/
private \Illuminate\Http\Request $request;

/**
* The name of the query string item from the request containing the API token.
*/
private string $inputKey;

/**
* The name of the token "column" in persistent storage.
*/
private string $storageKey;

/**
* Indicates if the API token is hashed in storage.
*/
private bool $hash = false;

/**
* @inheritdoc
*/
public function __construct(
\Illuminate\Contracts\Auth\UserProvider $provider,
\Illuminate\Http\Request $request,
$inputKey = 'api_token',
$storageKey = 'api_token',
$hash = false
\Illuminate\Http\Request $request
) {
$this->provider = $provider;
$this->request = $request;
$this->inputKey = $inputKey;
$this->storageKey = $storageKey;
$this->hash = $hash;
}

/**
Expand All @@ -54,7 +33,7 @@ public function __construct(
public function login(
\Illuminate\Contracts\Auth\Authenticatable $user
): self {
$this->getInstance()->setUser($user);
$this->getState()->setUser($user);
return $this;
}

Expand All @@ -63,7 +42,7 @@ public function login(
*/
public function logout(): self
{
$this->getInstance()->setUser(null);
$this->getState()->setUser(null);
app('auth0')->getSdk()->clear();
return $this;
}
Expand All @@ -84,6 +63,14 @@ public function guest(): bool
return ! $this->check();
}

/**
* @inheritdoc
*/
public function user(): ?\Illuminate\Contracts\Auth\Authenticatable
{
return $this->getState()->getUser() ?? $this->getUserFromToken() ?? $this->getUserFromSession() ?? null;
}

/**
* @inheritdoc
*/
Expand All @@ -104,25 +91,31 @@ public function id()

/**
* @inheritdoc
*
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*/
public function validate(
array $credentials = []
): bool {
if (! isset($credentials[$this->inputKey])) {
return false;
}

$credentials = [$this->storageKey => $credentials[$this->inputKey]];
return false;
}

return $this->provider->retrieveByCredentials($credentials) !== null;
/**
* @inheritdoc
*/
public function setUser(
\Illuminate\Contracts\Auth\Authenticatable $user
): self {
$user = $this->getState()->setUser($user);
return $this;
}

/**
* @inheritdoc
*/
public function hasUser(): bool
{
return ! is_null($this->getInstance()->getUser());
return ! is_null($this->getState()->getUser());
}

/**
Expand All @@ -131,160 +124,157 @@ public function hasUser(): bool
public function hasScope(
string $scope
): bool {
$user = $this->getInstance()->getUser();
$state = $this->getState();

if ($user !== null && in_array($scope, $user->getAccessTokenScope(), true)) {
if (in_array($scope, $state->getAccessTokenScope() ?? [], true)) {
return true;
}

return false;
}

/**
* @inheritdoc
*/
public function setUser(
\Illuminate\Contracts\Auth\Authenticatable $user
): self {
$user = $this->getInstance()->setUser($user);
return $this;
}

/**
* @inheritdoc
*/
public function setRequest(
\Illuminate\Http\Request $request
): self {
$this->request = $request;
return $this;
}

/**
* @inheritdoc
* Get the user context from a provided access token.
*/
public function user(): ?\Illuminate\Contracts\Auth\Authenticatable
private function getUserFromToken(): ?\Illuminate\Contracts\Auth\Authenticatable
{
$instance = $this->getInstance();
$user = null;
$token = $this->getTokenForRequest();
// Retrieve an available bearer token from the request.
$token = $this->request->bearerToken();

if ($token !== null) {
$user = $this->provider->retrieveByToken([], $token);
// If a session is not available, return null.
if ($token === null) {
return null;
}

if ($user === null && $token === null) {
$user = $this->getUserFromSession();
$user = $this->handleTokenExpiration($user);
try {
// Attempt to decode the bearer token.
$decoded = app('auth0')->getSdk()->decode($token, null, null, null, null, null, null, \Auth0\SDK\Token::TYPE_TOKEN)->toArray();
} catch (\Auth0\SDK\Exception\InvalidTokenException $invalidToken) {
// Invalid bearer token.
return null;
}

// Query the UserProvider to retrieve tue user for the token.
$user = $this->getProvider()->getRepository()->fromAccessToken($decoded);

// Was a user retrieved successfully?
if ($user !== null) {
$instance->setUser($user);
if (! $user instanceof \Illuminate\Contracts\Auth\Authenticatable) {
die('User model returned fromAccessToken must implement \Illuminate\Contracts\Auth\Authenticatable.');
}

if (! $user instanceof \Auth0\Laravel\Contract\Model\Stateless\User) {
die('User model returned fromAccessToken must implement \Auth0\Laravel\Contract\Model\Stateless\User.');
}

$this->getState()
->clear()
->setDecoded($decoded)
->setAccessToken($token)
->setAccessTokenScope(explode(' ', $decoded['scope'] ?? ''))
->setAccessTokenExpiration($decoded['exp'] ?? null);
}

return $user;
}

/**
* @inheritdoc
* Get the user context from an Auth0-PHP SDK session..
*/
public function getTokenForRequest(): ?string
private function getUserFromSession(): ?\Illuminate\Contracts\Auth\Authenticatable
{
$token = $this->request->query($this->inputKey);

if ($token === null) {
$token = $this->request->input($this->inputKey);
}
// Retrieve an available session from the Auth0-PHP SDK.
$session = app('auth0')->getSdk()->getCredentials();

if ($token === null) {
$token = $this->request->bearerToken();
// If a session is not available, return null.
if ($session === null) {
return null;
}

if ($token === null) {
$token = $this->request->getPassword();
}
// Query the UserProvider to retrieve tue user for the session.
$user = $this->getProvider()->getRepository()->fromSession($session->user);

if ($token !== null && is_string($token)) {
return $token;
}
// Was a user retrieved successfully?
if ($user !== null) {
if (! $user instanceof \Illuminate\Contracts\Auth\Authenticatable) {
die('User model returned fromSession must implement \Illuminate\Contracts\Auth\Authenticatable.');
}

return null;
}
if (! $user instanceof \Auth0\Laravel\Contract\Model\Stateful\User) {
die('User model returned fromSession must implement \Auth0\Laravel\Contract\Model\Stateful\User.');
}

/**
* Return the current request's StateInstance singleton.
*/
private function getInstance(): \Auth0\Laravel\StateInstance
{
return app()->make(\Auth0\Laravel\StateInstance::class);
}
$this->getState()
->clear()
->setDecoded($session->user)
->setIdToken($session->idToken)
->setAccessToken($session->accessToken)
->setAccessTokenScope($session->accessTokenScope)
->setAccessTokenExpiration($session->accessTokenExpiration)
->setRefreshToken($session->refreshToken);

/**
* Get the local user session.
*/
private function getUserFromSession(): ?\Illuminate\Contracts\Auth\Authenticatable
{
$session = app('auth0')->getSdk()->getCredentials();

if ($session !== null) {
return $this->provider->retrieveByCredentials((array) $session);
$user = $this->handleSessionExpiration($user);
}

return null;
return $user;
}

/**
* Handle instances of access token expiration.
* Handle instances of session token expiration.
*/
private function handleTokenExpiration(
private function handleSessionExpiration(
?\Illuminate\Contracts\Auth\Authenticatable $user
): ?\Illuminate\Contracts\Auth\Authenticatable {
if ($user === null) {
return null;
}
$state = $this->getState();

if ($user->getAccessTokenExpired() === false) {
// Unless our token expired, we have nothing to do here.
if ($state->getAccessTokenExpired() !== true) {
return $user;
}

// Did we scope with 'offline_access' (and does the API allow) for a refresh token?
if ($user->getRefreshToken() !== null) {
// Do we have a refresh token?
if ($state->getRefreshToken() !== null) {
try {
// Try to renew our token.
app('auth0')->getSdk()->renew();
} catch (\Auth0\SDK\Exception\StateException $tokenRefreshFailed) {
// Inform the host application token refresh failed, to enable custom handling behavior
// Renew failed. Inform application.
event(new \Auth0\Laravel\Event\Stateful\TokenRefreshFailed());
}

// Retrieve any potentially updated state data
$refreshed = $this->getUserFromSession();

// Was refreshed successfully?
if ($refreshed !== null && $refreshed->getAccessTokenExpired() === false) {
// Inform the host application to enable custom handling behavior
$event = new \Auth0\Laravel\Event\Stateful\TokenRefreshSucceeded($refreshed);
event($event);
// Retrieve updated state data
$refreshed = app('auth0')->getSdk()->getCredentials();

return $event->getUser();
if ($refreshed !== null && $refreshed->accessTokenExpired === false) {
event(new \Auth0\Laravel\Event\Stateful\TokenRefreshSucceeded());
return $user;
}
}

// We didn't have a refresh token, or the refresh failed. Inform host application.
$event = new \Auth0\Laravel\Event\Stateful\TokenExpired($user);
event($event);

// Did the host application override default expiration handling?
if ($event->getUser()->getAccessTokenExpired() === false) {
// Unless the host application expressly opted into not clearing the local user session, do so:
return $event->getUser();
}
// We didn't have a refresh token, or the refresh failed.
// Clear session.
$state->clear();
app('auth0')->getSdk()->clear();

if ($event->getClearSession() === true) {
// Clear the local user session:
$this->getInstance()->setUser(null);
app('auth0')->getSdk()->clear();
}
// Inform host application.
event(new \Auth0\Laravel\Event\Stateful\TokenExpired());

return null;
}

/**
* Return the current request's StateInstance singleton.
*/
private function getState(): \Auth0\Laravel\StateInstance
{
return app()->make(\Auth0\Laravel\StateInstance::class);
}

/**
* Return the current request's StateInstance singleton.
*/
private function getProvider(): \Illuminate\Contracts\Auth\UserProvider
{
return $this->provider;
}
}
Loading

0 comments on commit 58ce97f

Please sign in to comment.