Skip to content

Commit 8dac38e

Browse files
Rename CheckScopes and CheckForAnyScope to CheckAbilities and CheckForAnyAbility (#312)
* Rename CheckScopes and CheckForAnyScope * add tests * remove unused imports
1 parent fff8fe9 commit 8dac38e

File tree

8 files changed

+307
-22
lines changed

8 files changed

+307
-22
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Laravel\Sanctum\Exceptions;
4+
5+
use Illuminate\Auth\Access\AuthorizationException;
6+
use Illuminate\Support\Arr;
7+
8+
class MissingAbilityException extends AuthorizationException
9+
{
10+
/**
11+
* The abilities that the user did not have.
12+
*
13+
* @var array
14+
*/
15+
protected $abilities;
16+
17+
/**
18+
* Create a new missing scope exception.
19+
*
20+
* @param array|string $abilities
21+
* @param string $message
22+
* @return void
23+
*/
24+
public function __construct($abilities = [], $message = 'Invalid ability provided.')
25+
{
26+
parent::__construct($message);
27+
28+
$this->abilities = Arr::wrap($abilities);
29+
}
30+
31+
/**
32+
* Get the abilities that the user did not have.
33+
*
34+
* @return array
35+
*/
36+
public function abilities()
37+
{
38+
return $this->abilities;
39+
}
40+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Laravel\Sanctum\Http\Middleware;
4+
5+
use Illuminate\Auth\AuthenticationException;
6+
use Laravel\Sanctum\Exceptions\MissingAbilityException;
7+
8+
class CheckAbilities
9+
{
10+
/**
11+
* Handle the incoming request.
12+
*
13+
* @param \Illuminate\Http\Request $request
14+
* @param \Closure $next
15+
* @param mixed ...$abilities
16+
* @return \Illuminate\Http\Response
17+
*
18+
* @throws \Illuminate\Auth\AuthenticationException|\Laravel\Sanctum\Exceptions\MissingAbilityException
19+
*/
20+
public function handle($request, $next, ...$abilities)
21+
{
22+
if (! $request->user() || ! $request->user()->currentAccessToken()) {
23+
throw new AuthenticationException;
24+
}
25+
26+
foreach ($abilities as $ability) {
27+
if (! $request->user()->tokenCan($ability)) {
28+
throw new MissingAbilityException($ability);
29+
}
30+
}
31+
32+
return $next($request);
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Laravel\Sanctum\Http\Middleware;
4+
5+
use Illuminate\Auth\AuthenticationException;
6+
use Laravel\Sanctum\Exceptions\MissingAbilityException;
7+
8+
class CheckForAnyAbility
9+
{
10+
/**
11+
* Handle the incoming request.
12+
*
13+
* @param \Illuminate\Http\Request $request
14+
* @param \Closure $next
15+
* @param mixed ...$abilities
16+
* @return \Illuminate\Http\Response
17+
*
18+
* @throws \Illuminate\Auth\AuthenticationException|\Laravel\Sanctum\Exceptions\MissingAbilityException
19+
*/
20+
public function handle($request, $next, ...$abilities)
21+
{
22+
if (! $request->user() || ! $request->user()->currentAccessToken()) {
23+
throw new AuthenticationException;
24+
}
25+
26+
foreach ($abilities as $ability) {
27+
if ($request->user()->tokenCan($ability)) {
28+
return $next($request);
29+
}
30+
}
31+
32+
throw new MissingAbilityException($abilities);
33+
}
34+
}

src/Http/Middleware/CheckForAnyScope.php

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace Laravel\Sanctum\Http\Middleware;
44

5-
use Illuminate\Auth\AuthenticationException;
65
use Laravel\Sanctum\Exceptions\MissingScopeException;
76

7+
/**
8+
* @deprecated
9+
* @see \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility
10+
*/
811
class CheckForAnyScope
912
{
1013
/**
@@ -19,16 +22,10 @@ class CheckForAnyScope
1922
*/
2023
public function handle($request, $next, ...$scopes)
2124
{
22-
if (! $request->user() || ! $request->user()->currentAccessToken()) {
23-
throw new AuthenticationException;
25+
try {
26+
return (new CheckForAnyAbility())->handle($request, $next, ...$scopes);
27+
} catch (\Laravel\Sanctum\Exceptions\MissingAbilityException $e) {
28+
throw new MissingScopeException($e->abilities());
2429
}
25-
26-
foreach ($scopes as $scope) {
27-
if ($request->user()->tokenCan($scope)) {
28-
return $next($request);
29-
}
30-
}
31-
32-
throw new MissingScopeException($scopes);
3330
}
3431
}

src/Http/Middleware/CheckScopes.php

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace Laravel\Sanctum\Http\Middleware;
44

5-
use Illuminate\Auth\AuthenticationException;
65
use Laravel\Sanctum\Exceptions\MissingScopeException;
76

7+
/**
8+
* @deprecated
9+
* @see \Laravel\Sanctum\Http\Middleware\CheckAbilities
10+
*/
811
class CheckScopes
912
{
1013
/**
@@ -19,16 +22,10 @@ class CheckScopes
1922
*/
2023
public function handle($request, $next, ...$scopes)
2124
{
22-
if (! $request->user() || ! $request->user()->currentAccessToken()) {
23-
throw new AuthenticationException;
25+
try {
26+
return (new CheckAbilities())->handle($request, $next, ...$scopes);
27+
} catch (\Laravel\Sanctum\Exceptions\MissingAbilityException $e) {
28+
throw new MissingScopeException($e->abilities());
2429
}
25-
26-
foreach ($scopes as $scope) {
27-
if (! $request->user()->tokenCan($scope)) {
28-
throw new MissingScopeException($scope);
29-
}
30-
}
31-
32-
return $next($request);
3330
}
3431
}

tests/ActingAsTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Illuminate\Support\Facades\Route;
88
use Laravel\Sanctum\Contracts\HasApiTokens as HasApiTokensContract;
99
use Laravel\Sanctum\HasApiTokens;
10+
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
11+
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
1012
use Laravel\Sanctum\Http\Middleware\CheckForAnyScope;
1113
use Laravel\Sanctum\Http\Middleware\CheckScopes;
1214
use Laravel\Sanctum\Sanctum;
@@ -73,6 +75,36 @@ public function testActingAsWhenTheRouteIsProtectedByCheckForAnyScopeMiddleware(
7375
$response->assertSee('bar');
7476
}
7577

78+
public function testActingAsWhenTheRouteIsProtectedByCheckAbilitiesMiddleware()
79+
{
80+
$this->withoutExceptionHandling();
81+
82+
Route::get('/foo', function () {
83+
return 'bar';
84+
})->middleware(CheckAbilities::class.':admin,footest');
85+
86+
Sanctum::actingAs(new SanctumUser(), ['admin', 'footest']);
87+
88+
$response = $this->get('/foo');
89+
$response->assertSuccessful();
90+
$response->assertSee('bar');
91+
}
92+
93+
public function testActingAsWhenTheRouteIsProtectedByCheckForAnyAbilityMiddleware()
94+
{
95+
$this->withoutExceptionHandling();
96+
97+
Route::get('/foo', function () {
98+
return 'bar';
99+
})->middleware(CheckForAnyAbility::class.':admin,footest');
100+
101+
Sanctum::actingAs(new SanctumUser(), ['footest']);
102+
103+
$response = $this->get('/foo');
104+
$response->assertSuccessful();
105+
$response->assertSee('bar');
106+
}
107+
76108
public function testActingAsWhenTheRouteIsProtectedUsingAbilities()
77109
{
78110
$this->artisan('migrate', ['--database' => 'testbench'])->run();

tests/CheckAbilitiesTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace Laravel\Sanctum\Tests;
4+
5+
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
6+
use Mockery;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class CheckAbilitiesTest extends TestCase
10+
{
11+
protected function tearDown(): void
12+
{
13+
parent::tearDown();
14+
15+
Mockery::close();
16+
}
17+
18+
public function test_request_is_passed_along_if_abilities_are_present_on_token()
19+
{
20+
$middleware = new CheckAbilities;
21+
$request = Mockery::mock();
22+
$request->shouldReceive('user')->andReturn($user = Mockery::mock());
23+
$user->shouldReceive('currentAccessToken')->andReturn($token = Mockery::mock());
24+
$user->shouldReceive('tokenCan')->with('foo')->andReturn(true);
25+
$user->shouldReceive('tokenCan')->with('bar')->andReturn(true);
26+
27+
$response = $middleware->handle($request, function () {
28+
return 'response';
29+
}, 'foo', 'bar');
30+
31+
$this->assertSame('response', $response);
32+
}
33+
34+
public function test_exception_is_thrown_if_token_doesnt_have_ability()
35+
{
36+
$this->expectException('Laravel\Sanctum\Exceptions\MissingAbilityException');
37+
38+
$middleware = new CheckAbilities;
39+
$request = Mockery::mock();
40+
$request->shouldReceive('user')->andReturn($user = Mockery::mock());
41+
$user->shouldReceive('currentAccessToken')->andReturn($token = Mockery::mock());
42+
$user->shouldReceive('tokenCan')->with('foo')->andReturn(false);
43+
44+
$middleware->handle($request, function () {
45+
return 'response';
46+
}, 'foo', 'bar');
47+
}
48+
49+
public function test_exception_is_thrown_if_no_authenticated_user()
50+
{
51+
$this->expectException('Illuminate\Auth\AuthenticationException');
52+
53+
$middleware = new CheckAbilities;
54+
$request = Mockery::mock();
55+
$request->shouldReceive('user')->once()->andReturn(null);
56+
57+
$middleware->handle($request, function () {
58+
return 'response';
59+
}, 'foo', 'bar');
60+
}
61+
62+
public function test_exception_is_thrown_if_no_token()
63+
{
64+
$this->expectException('Illuminate\Auth\AuthenticationException');
65+
66+
$middleware = new CheckAbilities;
67+
$request = Mockery::mock();
68+
$request->shouldReceive('user')->andReturn($user = Mockery::mock());
69+
$user->shouldReceive('currentAccessToken')->andReturn(null);
70+
71+
$middleware->handle($request, function () {
72+
return 'response';
73+
}, 'foo', 'bar');
74+
}
75+
}

tests/CheckForAnyAbilityTest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Laravel\Sanctum\Tests;
4+
5+
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
6+
use Mockery;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class CheckForAnyAbilityTest extends TestCase
10+
{
11+
protected function tearDown(): void
12+
{
13+
parent::tearDown();
14+
15+
Mockery::close();
16+
}
17+
18+
public function test_request_is_passed_along_if_abilities_are_present_on_token()
19+
{
20+
$middleware = new CheckForAnyAbility;
21+
$request = Mockery::mock();
22+
$request->shouldReceive('user')->andReturn($user = Mockery::mock());
23+
$user->shouldReceive('currentAccessToken')->andReturn($token = Mockery::mock());
24+
$user->shouldReceive('tokenCan')->with('foo')->andReturn(true);
25+
$user->shouldReceive('tokenCan')->with('bar')->andReturn(false);
26+
27+
$response = $middleware->handle($request, function () {
28+
return 'response';
29+
}, 'foo', 'bar');
30+
31+
$this->assertSame('response', $response);
32+
}
33+
34+
public function test_exception_is_thrown_if_token_doesnt_have_ability()
35+
{
36+
$this->expectException('Laravel\Sanctum\Exceptions\MissingAbilityException');
37+
38+
$middleware = new CheckForAnyAbility;
39+
$request = Mockery::mock();
40+
$request->shouldReceive('user')->andReturn($user = Mockery::mock());
41+
$user->shouldReceive('currentAccessToken')->andReturn($token = Mockery::mock());
42+
$user->shouldReceive('tokenCan')->with('foo')->andReturn(false);
43+
$user->shouldReceive('tokenCan')->with('bar')->andReturn(false);
44+
45+
$middleware->handle($request, function () {
46+
return 'response';
47+
}, 'foo', 'bar');
48+
}
49+
50+
public function test_exception_is_thrown_if_no_authenticated_user()
51+
{
52+
$this->expectException('Illuminate\Auth\AuthenticationException');
53+
54+
$middleware = new CheckForAnyAbility;
55+
$request = Mockery::mock();
56+
$request->shouldReceive('user')->once()->andReturn(null);
57+
58+
$middleware->handle($request, function () {
59+
return 'response';
60+
}, 'foo', 'bar');
61+
}
62+
63+
public function test_exception_is_thrown_if_no_token()
64+
{
65+
$this->expectException('Illuminate\Auth\AuthenticationException');
66+
67+
$middleware = new CheckForAnyAbility;
68+
$request = Mockery::mock();
69+
$request->shouldReceive('user')->andReturn($user = Mockery::mock());
70+
$user->shouldReceive('currentAccessToken')->andReturn(null);
71+
72+
$middleware->handle($request, function () {
73+
return 'response';
74+
}, 'foo', 'bar');
75+
}
76+
}

0 commit comments

Comments
 (0)