Skip to content

Commit dd3f081

Browse files
axlontaylorotwell
andauthored
[11.x] Add the ability to limit scopes by client (#1682)
* Add the ability to limit scopes by client * Update Client.php --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent b566e15 commit dd3f081

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed

src/Bridge/ScopeRepository.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,31 @@
22

33
namespace Laravel\Passport\Bridge;
44

5+
use Laravel\Passport\ClientRepository;
56
use Laravel\Passport\Passport;
67
use League\OAuth2\Server\Entities\ClientEntityInterface;
78
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
89

910
class ScopeRepository implements ScopeRepositoryInterface
1011
{
12+
/**
13+
* The client repository.
14+
*
15+
* @var \Laravel\Passport\ClientRepository
16+
*/
17+
protected ClientRepository $clients;
18+
19+
/**
20+
* Create a new scope repository.
21+
*
22+
* @param \Laravel\Passport\ClientRepository $clients
23+
* @return void
24+
*/
25+
public function __construct(ClientRepository $clients)
26+
{
27+
$this->clients = $clients;
28+
}
29+
1130
/**
1231
* {@inheritdoc}
1332
*/
@@ -31,8 +50,11 @@ public function finalizeScopes(
3150
})->values()->all();
3251
}
3352

34-
return collect($scopes)->filter(function ($scope) {
35-
return Passport::hasScope($scope->getIdentifier());
53+
$client = $this->clients->findActive($clientEntity->getIdentifier());
54+
55+
return collect($scopes)->filter(function ($scope) use ($client) {
56+
return Passport::hasScope($scope->getIdentifier())
57+
&& $client->hasScope($scope->getIdentifier());
3658
})->values()->all();
3759
}
3860
}

src/Client.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Client extends Model
4141
*/
4242
protected $casts = [
4343
'grant_types' => 'array',
44+
'scopes' => 'array',
4445
'personal_access_client' => 'bool',
4546
'password_client' => 'bool',
4647
'revoked' => 'bool',
@@ -154,6 +155,17 @@ public function skipsAuthorization()
154155
return false;
155156
}
156157

158+
/**
159+
* Determine whether the client has the given scope.
160+
*
161+
* @param string $scope
162+
* @return bool
163+
*/
164+
public function hasScope($scope)
165+
{
166+
return ! is_array($this->scopes) || in_array($scope, $this->scopes);
167+
}
168+
157169
/**
158170
* Determine if the client is a confidential client.
159171
*

tests/Unit/BridgeScopeRepositoryTest.php

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
use Laravel\Passport\Bridge\Client;
66
use Laravel\Passport\Bridge\Scope;
77
use Laravel\Passport\Bridge\ScopeRepository;
8+
use Laravel\Passport\Client as ClientModel;
9+
use Laravel\Passport\ClientRepository;
810
use Laravel\Passport\Passport;
11+
use Mockery;
912
use PHPUnit\Framework\TestCase;
1013

1114
class BridgeScopeRepositoryTest extends TestCase
@@ -16,7 +19,56 @@ public function test_invalid_scopes_are_removed()
1619
'scope-1' => 'description',
1720
]);
1821

19-
$repository = new ScopeRepository;
22+
$client = Mockery::mock(ClientModel::class)->makePartial();
23+
24+
$clients = Mockery::mock(ClientRepository::class);
25+
$clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
26+
27+
$repository = new ScopeRepository($clients);
28+
29+
$scopes = $repository->finalizeScopes(
30+
[$scope1 = new Scope('scope-1'), new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
31+
);
32+
33+
$this->assertEquals([$scope1], $scopes);
34+
}
35+
36+
public function test_clients_do_not_restrict_scopes_by_default()
37+
{
38+
Passport::tokensCan([
39+
'scope-1' => 'description',
40+
'scope-2' => 'description',
41+
]);
42+
43+
$client = Mockery::mock(ClientModel::class)->makePartial();
44+
$client->scopes = null;
45+
46+
$clients = Mockery::mock(ClientRepository::class);
47+
$clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
48+
49+
$repository = new ScopeRepository($clients);
50+
51+
$scopes = $repository->finalizeScopes(
52+
[$scope1 = new Scope('scope-1'), $scope2 = new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
53+
);
54+
55+
$this->assertEquals([$scope1, $scope2], $scopes);
56+
}
57+
58+
public function test_scopes_disallowed_for_client_are_removed()
59+
{
60+
Passport::tokensCan([
61+
'scope-1' => 'description',
62+
'scope-2' => 'description',
63+
]);
64+
65+
$client = Mockery::mock(ClientModel::class)->makePartial();
66+
$client->scopes = ['scope-1'];
67+
68+
$clients = Mockery::mock(ClientRepository::class);
69+
$clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
70+
71+
$repository = new ScopeRepository($clients);
2072

2173
$scopes = $repository->finalizeScopes(
2274
[$scope1 = new Scope('scope-1'), new Scope('scope-2')], 'client_credentials', new Client('id', 'name', 'http://localhost'), 1
@@ -31,7 +83,12 @@ public function test_superuser_scope_cant_be_applied_if_wrong_grant()
3183
'scope-1' => 'description',
3284
]);
3385

34-
$repository = new ScopeRepository;
86+
$client = Mockery::mock(ClientModel::class)->makePartial();
87+
88+
$clients = Mockery::mock(ClientRepository::class);
89+
$clients->shouldReceive('findActive')->withAnyArgs()->andReturn($client);
90+
91+
$repository = new ScopeRepository($clients);
3592

3693
$scopes = $repository->finalizeScopes(
3794
[$scope1 = new Scope('*')], 'refresh_token', new Client('id', 'name', 'http://localhost'), 1

0 commit comments

Comments
 (0)