Skip to content

[13.x] Use the oauth_scopes property of the bearer token on TokenGuard #1755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Passport now always hashes client secrets using Laravel's `Hash` facade. If you

In light of this change, the `Passport::$hashesClientSecrets` property and `Passport::hashClientSecrets()` method has been removed.

### The User's Token Instance

PR: https://github.com/laravel/passport/pull/1755

When authenticating users via bearer tokens, the `User` model's `token` method now returns an instance of `Laravel\Passport\AccessToken` class instead of `Laravel\Passport\Token`.

### Personal Access Client Table and Model Removal

PR: https://github.com/laravel/passport/pull/1749
Expand Down
131 changes: 131 additions & 0 deletions src/AccessToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace Laravel\Passport;

use Illuminate\Support\Traits\ForwardsCalls;
use Psr\Http\Message\ServerRequestInterface;

/**
* @property string oauth_access_token_id
* @property string oauth_client_id
* @property string oauth_user_id
* @property string[] oauth_scopes
*/
class AccessToken
{
use ResolvesInheritedScopes, ForwardsCalls;

/**
* The token instance.
*/
protected ?Token $token;

/**
* All the attributes set on the access token instance.
*
* @var array<string, mixed>
*/
protected array $attributes = [];

/**
* Create a new access token instance.
*
* @param array<string, mixed> $attributes
*/
public function __construct(array $attributes = [])
{
foreach ($attributes as $key => $value) {
$this->attributes[$key] = $value;
}
}

/**
* Create a new access token instance from the incoming PSR-7 request.
*/
public static function fromPsrRequest(ServerRequestInterface $request): static
{
return new static($request->getAttributes());
}

/**
* Determine if the token has a given scope.
*/
public function can(string $scope): bool
{
if (in_array('*', $this->oauth_scopes)) {
return true;
}

$scopes = Passport::$withInheritedScopes
? $this->resolveInheritedScopes($scope)
: [$scope];

foreach ($scopes as $scope) {
if (array_key_exists($scope, array_flip($this->oauth_scopes))) {
return true;
}
}

return false;
}

/**
* Determine if the token is missing a given scope.
*/
public function cant(string $scope): bool
{
return ! $this->can($scope);
}

/**
* Determine if the token is a transient JWT token.
*/
public function transient(): bool
{
return false;
}

/**
* Revoke the token instance.
*/
public function revoke(): bool
{
return Passport::token()->whereKey($this->oauth_access_token_id)->forceFill(['revoked' => true])->save();
}

/**
* Get the token instance.
*/
protected function getToken(): ?Token
{
return $this->token ??= Passport::token()->find($this->oauth_access_token_id);
}

/**
* Dynamically determine if an attribute is set.
*/
public function __isset(string $key): bool
{
return isset($this->attributes[$key]) || isset($this->getToken()?->{$key});
}

/**
* Dynamically retrieve the value of an attribute.
*/
public function __get(string $key)
{
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}

return $this->getToken()?->{$key};
}

/**
* Pass dynamic methods onto the token instance.
*/
public function __call(string $method, array $parameters): mixed
{
return $this->forwardCallTo($this->getToken(), $method, $parameters);
}
}
5 changes: 2 additions & 3 deletions src/Guards/TokenGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Http\Request;
use Illuminate\Support\Traits\Macroable;
use Laravel\Passport\AccessToken;
use Laravel\Passport\Client;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\Passport;
Expand Down Expand Up @@ -203,9 +204,7 @@ protected function authenticateViaBearerToken($request)
// Next, we will assign a token instance to this user which the developers may use
// to determine if the token has a given scope, etc. This will be useful during
// authorization such as within the developer's Laravel model policy classes.
$token = $this->tokens->find(
$psr->getAttribute('oauth_access_token_id')
);
$token = AccessToken::fromPsrRequest($psr);

return $token ? $user->withAccessToken($token) : null;
}
Expand Down
6 changes: 3 additions & 3 deletions src/HasApiTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ trait HasApiTokens
/**
* The current access token for the authentication user.
*
* @var \Laravel\Passport\Token|\Laravel\Passport\TransientToken|null
* @var \Laravel\Passport\AccessToken|\Laravel\Passport\TransientToken|null
*/
protected $accessToken;

Expand All @@ -36,7 +36,7 @@ public function tokens()
/**
* Get the current access token being used by the user.
*
* @return \Laravel\Passport\Token|\Laravel\Passport\TransientToken|null
* @return \Laravel\Passport\AccessToken|\Laravel\Passport\TransientToken|null
*/
public function token()
{
Expand Down Expand Up @@ -71,7 +71,7 @@ public function createToken($name, array $scopes = [])
/**
* Set the current access token for the user.
*
* @param \Laravel\Passport\Token|\Laravel\Passport\TransientToken|null $accessToken
* @param \Laravel\Passport\AccessToken|\Laravel\Passport\TransientToken|null $accessToken
* @return $this
*/
public function withAccessToken($accessToken)
Expand Down
6 changes: 3 additions & 3 deletions src/Http/Middleware/CheckClientCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CheckClientCredentials extends CheckCredentials
/**
* Validate token credentials.
*
* @param \Laravel\Passport\Token $token
* @param \Laravel\Passport\AccessToken $token
* @return void
*
* @throws \Laravel\Passport\Exceptions\AuthenticationException
Expand All @@ -25,15 +25,15 @@ protected function validateCredentials($token)
/**
* Validate token credentials.
*
* @param \Laravel\Passport\Token $token
* @param \Laravel\Passport\AccessToken $token
* @param array $scopes
* @return void
*
* @throws \Laravel\Passport\Exceptions\MissingScopeException
*/
protected function validateScopes($token, $scopes)
{
if (in_array('*', $token->scopes)) {
if (in_array('*', $token->oauth_scopes)) {
return;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Http/Middleware/CheckClientCredentialsForAnyScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CheckClientCredentialsForAnyScope extends CheckCredentials
/**
* Validate token credentials.
*
* @param \Laravel\Passport\Token $token
* @param \Laravel\Passport\AccessToken $token
* @return void
*
* @throws \Laravel\Passport\Exceptions\AuthenticationException
Expand All @@ -25,15 +25,15 @@ protected function validateCredentials($token)
/**
* Validate token credentials.
*
* @param \Laravel\Passport\Token $token
* @param \Laravel\Passport\AccessToken $token
* @param array $scopes
* @return void
*
* @throws \Laravel\Passport\Exceptions\MissingScopeException
*/
protected function validateScopes($token, $scopes)
{
if (in_array('*', $token->scopes)) {
if (in_array('*', $token->oauth_scopes)) {
return;
}

Expand Down
7 changes: 4 additions & 3 deletions src/Http/Middleware/CheckCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Laravel\Passport\Http\Middleware;

use Closure;
use Laravel\Passport\AccessToken;
use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\Exception\OAuthServerException;
Expand Down Expand Up @@ -95,7 +96,7 @@ public function handle($request, Closure $next, ...$scopes)
*/
protected function validate($psr, $scopes)
{
$token = $this->repository->find($psr->getAttribute('oauth_access_token_id'));
$token = AccessToken::fromPsrRequest($psr);

$this->validateCredentials($token);

Expand All @@ -105,7 +106,7 @@ protected function validate($psr, $scopes)
/**
* Validate token credentials.
*
* @param \Laravel\Passport\Token $token
* @param \Laravel\Passport\AccessToken $token
* @return void
*
* @throws \Laravel\Passport\Exceptions\AuthenticationException
Expand All @@ -115,7 +116,7 @@ abstract protected function validateCredentials($token);
/**
* Validate token scopes.
*
* @param \Laravel\Passport\Token $token
* @param \Laravel\Passport\AccessToken $token
* @param array $scopes
* @return void
*
Expand Down
26 changes: 7 additions & 19 deletions src/Passport.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,10 @@ public static function ignoreCsrfToken($ignoreCsrfToken = true)
*/
public static function actingAs($user, $scopes = [], $guard = 'api')
{
$token = app(self::tokenModel());

$token->scopes = $scopes;
$token = new AccessToken([
'oauth_user_id' => $user->getKey(),
'oauth_scopes' => $scopes,
]);

$user->withAccessToken($token);

Expand All @@ -395,28 +396,15 @@ public static function actingAs($user, $scopes = [], $guard = 'api')
*/
public static function actingAsClient($client, $scopes = [], $guard = 'api')
{
$token = app(self::tokenModel());

$token->client_id = $client->getKey();
$token->setRelation('client', $client);

$token->scopes = $scopes;

$mock = Mockery::mock(ResourceServer::class);
$mock->shouldReceive('validateAuthenticatedRequest')
->andReturnUsing(function (ServerRequestInterface $request) use ($token) {
return $request->withAttribute('oauth_client_id', $token->client->id)
->withAttribute('oauth_access_token_id', $token->id)
->withAttribute('oauth_scopes', $token->scopes);
->andReturnUsing(function (ServerRequestInterface $request) use ($client, $scopes) {
return $request->withAttribute('oauth_client_id', $client->getKey())
->withAttribute('oauth_scopes', $scopes);
});

app()->instance(ResourceServer::class, $mock);

$mock = Mockery::mock(TokenRepository::class);
$mock->shouldReceive('find')->andReturn($token);

app()->instance(TokenRepository::class, $mock);

app('auth')->guard($guard)->setClient($client);

app('auth')->shouldUse($guard);
Expand Down
48 changes: 0 additions & 48 deletions src/Token.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

class Token extends Model
{
use ResolvesInheritedScopes;

/**
* The database table used by the model.
*
Expand Down Expand Up @@ -81,42 +79,6 @@ public function user()
return $this->belongsTo($model, 'user_id', (new $model)->getKeyName());
}

/**
* Determine if the token has a given scope.
*
* @param string $scope
* @return bool
*/
public function can($scope)
{
if (in_array('*', $this->scopes)) {
return true;
}

$scopes = Passport::$withInheritedScopes
? $this->resolveInheritedScopes($scope)
: [$scope];

foreach ($scopes as $scope) {
if (array_key_exists($scope, array_flip($this->scopes))) {
return true;
}
}

return false;
}

/**
* Determine if the token is missing a given scope.
*
* @param string $scope
* @return bool
*/
public function cant($scope)
{
return ! $this->can($scope);
}

/**
* Revoke the token instance.
*
Expand All @@ -127,16 +89,6 @@ public function revoke()
return $this->forceFill(['revoked' => true])->save();
}

/**
* Determine if the token is a transient JWT token.
*
* @return bool
*/
public function transient()
{
return false;
}

/**
* Get the current connection name for the model.
*
Expand Down
Loading