Skip to content
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

[13.x] Fix skipping consent prompt #1777

Merged
merged 6 commits into from
Aug 7, 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
5 changes: 3 additions & 2 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laravel\Passport;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -163,9 +164,9 @@ public function firstParty()
/**
* Determine if the client should skip the authorization prompt.
*
* @return bool
* @param \Laravel\Passport\Scope[] $scopes
*/
public function skipsAuthorization()
public function skipsAuthorization(Authenticatable $user, array $scopes): bool
{
return false;
}
Expand Down
29 changes: 14 additions & 15 deletions src/Http/Controllers/AuthorizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use Laravel\Passport\Bridge\User;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\Contracts\AuthorizationViewResponse;
use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Passport;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use Nyholm\Psr7\Response as Psr7Response;
Expand Down Expand Up @@ -64,22 +64,18 @@ public function __construct(AuthorizationServer $server,
* @param \Psr\Http\Message\ServerRequestInterface $psrRequest
* @param \Illuminate\Http\Request $request
* @param \Laravel\Passport\ClientRepository $clients
* @param \Laravel\Passport\TokenRepository $tokens
* @return \Illuminate\Http\Response|\Laravel\Passport\Contracts\AuthorizationViewResponse
*/
public function authorize(ServerRequestInterface $psrRequest,
Request $request,
ClientRepository $clients,
TokenRepository $tokens)
public function authorize(ServerRequestInterface $psrRequest, Request $request, ClientRepository $clients)
{
$authRequest = $this->withErrorHandling(function () use ($psrRequest) {
return $this->server->validateAuthorizationRequest($psrRequest);
});

if ($this->guard->guest()) {
return $request->get('prompt') === 'none'
? $this->denyRequest($authRequest)
: $this->promptForLogin($request);
? $this->denyRequest($authRequest)
: $this->promptForLogin($request);
}

if ($request->get('prompt') === 'login' &&
Expand All @@ -98,7 +94,7 @@ public function authorize(ServerRequestInterface $psrRequest,
$client = $clients->find($authRequest->getClient()->getIdentifier());

if ($request->get('prompt') !== 'consent' &&
($client->skipsAuthorization() || $this->hasValidToken($tokens, $user, $client, $scopes))) {
($client->skipsAuthorization($user, $scopes) || $this->hasGrantedScopes($user, $client, $scopes))) {
return $this->approveRequest($authRequest, $user);
}

Expand Down Expand Up @@ -134,19 +130,22 @@ protected function parseScopes($authRequest)
}

/**
* Determine if a valid token exists for the given user, client, and scopes.
* Determine if the given user has already granted the client access to the scopes.
*
* @param \Laravel\Passport\TokenRepository $tokens
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Laravel\Passport\Client $client
* @param array $scopes
* @return bool
*/
protected function hasValidToken($tokens, $user, $client, $scopes)
protected function hasGrantedScopes($user, $client, $scopes)
{
$token = $tokens->findValidToken($user, $client);

return $token && $token->scopes === collect($scopes)->pluck('id')->all();
return collect($scopes)->pluck('id')->diff(
$client->tokens()->where([
['user_id', '=', $user->getAuthIdentifier()],
['revoked', '=', false],
['expires_at', '>', Date::now()],
])->pluck('scopes')->flatten()
)->isEmpty();
}

/**
Expand Down
60 changes: 20 additions & 40 deletions tests/Unit/AuthorizationControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laravel\Passport\Tests\Unit;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Laravel\Passport\Bridge\Scope;
Expand All @@ -12,8 +13,6 @@
use Laravel\Passport\Http\Controllers\AuthorizationController;
use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException as LeagueException;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
Expand Down Expand Up @@ -44,7 +43,7 @@ public function test_authorization_view_is_presented()
$controller = new AuthorizationController($server, $guard, $response);

$guard->shouldReceive('guest')->andReturn(false);
$guard->shouldReceive('user')->andReturn($user = m::mock());
$guard->shouldReceive('user')->andReturn($user = m::mock(Authenticatable::class));
$server->shouldReceive('validateAuthorizationRequest')->andReturn($authRequest = m::mock(AuthorizationRequestInterface::class));

$request = m::mock(Request::class);
Expand All @@ -60,9 +59,9 @@ public function test_authorization_view_is_presented()
$clients = m::mock(ClientRepository::class);
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
$client->shouldReceive('skipsAuthorization')->andReturn(false);
$client->shouldReceive('tokens->where->pluck->flatten')->andReturn(collect());

$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')->with($user, $client)->andReturnNull();
$user->shouldReceive('getAuthIdentifier')->andReturn(1);

$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$this->assertEquals($client, $data['client']);
Expand All @@ -74,7 +73,7 @@ public function test_authorization_view_is_presented()
});

$this->assertSame('view', $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
));
}

Expand All @@ -93,12 +92,11 @@ public function test_authorization_exceptions_are_handled()
$request->shouldReceive('session')->andReturn($session = m::mock());

$clients = m::mock(ClientRepository::class);
$tokens = m::mock(TokenRepository::class);

$this->expectException(OAuthServerException::class);

$controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
);
}

Expand All @@ -115,7 +113,7 @@ public function test_request_is_approved_if_valid_token_exists()
$controller = new AuthorizationController($server, $guard, $response);

$guard->shouldReceive('guest')->andReturn(false);
$guard->shouldReceive('user')->andReturn($user = m::mock());
$guard->shouldReceive('user')->andReturn($user = m::mock(Authenticatable::class));
$psrResponse = new Response();
$psrResponse->getBody()->write('approved');
$server->shouldReceive('validateAuthorizationRequest')
Expand All @@ -140,15 +138,11 @@ public function test_request_is_approved_if_valid_token_exists()
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));

$client->shouldReceive('skipsAuthorization')->andReturn(false);

$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')
->with($user, $client)
->andReturn($token = m::mock(Token::class));
$token->shouldReceive('getAttribute')->with('scopes')->andReturn(['scope-1']);
$client->shouldReceive('getKey')->andReturn(1);
$client->shouldReceive('tokens->where->pluck->flatten')->andReturn(collect(['scope-1']));

$this->assertSame('approved', $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
)->getContent());
}

Expand All @@ -165,7 +159,7 @@ public function test_request_is_approved_if_client_can_skip_authorization()
$controller = new AuthorizationController($server, $guard, $response);

$guard->shouldReceive('guest')->andReturn(false);
$guard->shouldReceive('user')->andReturn($user = m::mock());
$guard->shouldReceive('user')->andReturn($user = m::mock(Authenticatable::class));
$psrResponse = new Response();
$psrResponse->getBody()->write('approved');
$server->shouldReceive('validateAuthorizationRequest')
Expand All @@ -191,13 +185,8 @@ public function test_request_is_approved_if_client_can_skip_authorization()

$client->shouldReceive('skipsAuthorization')->andReturn(true);

$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')
->with($user, $client)
->andReturnNull();

$this->assertSame('approved', $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
)->getContent());
}

Expand Down Expand Up @@ -232,9 +221,6 @@ public function test_authorization_view_is_presented_if_request_has_prompt_equal
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
$client->shouldReceive('skipsAuthorization')->andReturn(false);

$tokens = m::mock(TokenRepository::class);
$tokens->shouldNotReceive('findValidToken');

$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$this->assertEquals($client, $data['client']);
$this->assertEquals($user, $data['user']);
Expand All @@ -245,7 +231,7 @@ public function test_authorization_view_is_presented_if_request_has_prompt_equal
});

$this->assertSame('view', $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
));
}

Expand All @@ -264,7 +250,7 @@ public function test_authorization_denied_if_request_has_prompt_equals_to_none()
$controller = new AuthorizationController($server, $guard, $response);

$guard->shouldReceive('guest')->andReturn(false);
$guard->shouldReceive('user')->andReturn($user = m::mock());
$guard->shouldReceive('user')->andReturn($user = m::mock(Authenticatable::class));
$server->shouldReceive('validateAuthorizationRequest')
->andReturn($authRequest = m::mock(AuthorizationRequest::class));
$server->shouldReceive('completeAuthorizationRequest')
Expand All @@ -288,14 +274,11 @@ public function test_authorization_denied_if_request_has_prompt_equals_to_none()
$clients = m::mock(ClientRepository::class);
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
$client->shouldReceive('skipsAuthorization')->andReturn(false);

$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')
->with($user, $client)
->andReturnNull();
$client->shouldReceive('getKey')->andReturn(1);
$client->shouldReceive('tokens->where->pluck->flatten')->andReturn(collect());

$controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
);
}

Expand Down Expand Up @@ -324,11 +307,10 @@ public function test_authorization_denied_if_unauthenticated_and_request_has_pro
$authRequest->shouldReceive('getGrantTypeId')->andReturn('authorization_code');

$clients = m::mock(ClientRepository::class);
$tokens = m::mock(TokenRepository::class);

try {
$controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
);
} catch (\Laravel\Passport\Exceptions\OAuthServerException $e) {
$this->assertStringStartsWith(
Expand Down Expand Up @@ -362,10 +344,9 @@ public function test_logout_and_prompt_login_if_request_has_prompt_equals_to_log
$request->shouldReceive('get')->with('prompt')->andReturn('login');

$clients = m::mock(ClientRepository::class);
$tokens = m::mock(TokenRepository::class);

$controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
);
}

Expand All @@ -390,10 +371,9 @@ public function test_user_should_be_authenticated()
$request->shouldReceive('get')->with('prompt')->andReturn(null);

$clients = m::mock(ClientRepository::class);
$tokens = m::mock(TokenRepository::class);

$controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
m::mock(ServerRequestInterface::class), $request, $clients
);
}
}