Skip to content

Commit e8d4e19

Browse files
Gregory Haddowlindyhopchris
Gregory Haddow
authored andcommitted
feat: support auth responses from authorizer contract
1 parent d4a21ac commit e8d4e19

File tree

8 files changed

+99
-30
lines changed

8 files changed

+99
-30
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"require": {
2626
"php": "^8.2",
2727
"ext-json": "*",
28-
"laravel-json-api/core": "^4.3.2",
28+
"laravel-json-api/core": "^4.3.2|^5.0.1",
2929
"laravel-json-api/eloquent": "^4.4",
3030
"laravel-json-api/encoder-neomerx": "^4.1",
3131
"laravel-json-api/exceptions": "^3.1",

src/Http/Requests/FormRequest.php

+38-24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace LaravelJsonApi\Laravel\Http\Requests;
1313

14+
use Illuminate\Auth\Access\AuthorizationException;
15+
use Illuminate\Auth\Access\Response;
1416
use Illuminate\Auth\AuthenticationException;
1517
use Illuminate\Contracts\Auth\Guard;
1618
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
@@ -226,42 +228,54 @@ public function schema(): Schema
226228
*/
227229
protected function passesAuthorization()
228230
{
229-
/**
230-
* If the developer has implemented the `authorize` method, we
231-
* will return the result if it is a boolean. This allows
232-
* the developer to return a null value to indicate they want
233-
* the default authorization to run.
234-
*/
235-
if (method_exists($this, 'authorize')) {
236-
if (is_bool($passes = $this->container->call([$this, 'authorize']))) {
237-
return $passes;
231+
try {
232+
/**
233+
* If the developer has implemented the `authorize` method, we
234+
* will return the result if it is a boolean. This allows
235+
* the developer to return a null value to indicate they want
236+
* the default authorization to run.
237+
*/
238+
if (method_exists($this, 'authorize')) {
239+
$result = $this->container->call([$this, 'authorize']);
240+
if ($result !== null) {
241+
return $result instanceof Response ? $result->authorize() : $result;
242+
}
238243
}
239-
}
240244

241-
/**
242-
* If the developer has not authorized the request themselves,
243-
* we run our default authorization as long as authorization is
244-
* enabled for both the server and the schema (checked via the
245-
* `mustAuthorize()` method).
246-
*/
247-
if (method_exists($this, 'authorizeResource')) {
248-
return $this->container->call([$this, 'authorizeResource']);
249-
}
245+
/**
246+
* If the developer has not authorized the request themselves,
247+
* we run our default authorization as long as authorization is
248+
* enabled for both the server and the schema (checked via the
249+
* `mustAuthorize()` method).
250+
*/
251+
if (method_exists($this, 'authorizeResource')) {
252+
$result = $this->container->call([$this, 'authorizeResource']);
253+
return $result instanceof Response ? $result->authorize() : $result;
254+
}
250255

256+
} catch (AuthorizationException $ex) {
257+
$this->failIfUnauthenticated();
258+
throw $ex;
259+
}
251260
return true;
252261
}
253262

254-
/**
255-
* @inheritDoc
256-
*/
257-
protected function failedAuthorization()
263+
protected function failIfUnauthenticated()
258264
{
259-
/** @var Guard $auth */
265+
/** @var Guard $auth */
260266
$auth = $this->container->make(Guard::class);
261267

262268
if ($auth->guest()) {
263269
throw new AuthenticationException();
264270
}
271+
}
272+
273+
/**
274+
* @inheritDoc
275+
*/
276+
protected function failedAuthorization()
277+
{
278+
$this->failIfUnauthenticated();
265279

266280
parent::failedAuthorization();
267281
}

src/Http/Requests/ResourceQuery.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace LaravelJsonApi\Laravel\Http\Requests;
1313

14+
use Illuminate\Auth\Access\Response;
1415
use Illuminate\Contracts\Validation\Validator;
1516
use Illuminate\Database\Eloquent\Model;
1617
use LaravelJsonApi\Contracts\Auth\Authorizer;
@@ -104,9 +105,9 @@ public static function queryOne(string $resourceType): QueryParameters
104105
* Perform resource authorization.
105106
*
106107
* @param Authorizer $authorizer
107-
* @return bool
108+
* @return bool|Response
108109
*/
109-
public function authorizeResource(Authorizer $authorizer): bool
110+
public function authorizeResource(Authorizer $authorizer): bool|Response
110111
{
111112
if ($this->isViewingAny()) {
112113
return $authorizer->index(

src/Http/Requests/ResourceRequest.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace LaravelJsonApi\Laravel\Http\Requests;
1313

14+
use Illuminate\Auth\Access\Response;
1415
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
1516
use Illuminate\Contracts\Validation\Validator;
1617
use Illuminate\Database\Eloquent\Model;
@@ -150,9 +151,9 @@ public function toMany(): Collection
150151
* Perform resource authorization.
151152
*
152153
* @param Authorizer $authorizer
153-
* @return bool
154+
* @return bool|Response
154155
*/
155-
public function authorizeResource(Authorizer $authorizer): bool
156+
public function authorizeResource(Authorizer $authorizer): bool|Response
156157
{
157158
if ($this->isCreating()) {
158159
return $authorizer->store(

tests/dummy/app/Http/Controllers/Api/V1/UserController.php

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
class UserController extends Controller
2020
{
2121
use Actions\FetchOne;
22+
use Actions\Destroy;
2223
use Actions\FetchRelated;
2324
use Actions\FetchRelationship;
2425
use Actions\UpdateRelationship;

tests/dummy/app/Policies/UserPolicy.php

+14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace App\Policies;
1313

1414
use App\Models\User;
15+
use Illuminate\Auth\Access\Response;
1516

1617
class UserPolicy
1718
{
@@ -50,4 +51,17 @@ public function updatePhone(User $user, User $other): bool
5051
{
5152
return $user->is($other);
5253
}
54+
55+
/**
56+
* Determine if the user can delete the other user.
57+
*
58+
* @param User $user
59+
* @param User $other
60+
* @return bool|Response
61+
*/
62+
public function delete(User $user, User $other)
63+
{
64+
return $user->is($other) ? true : Response::denyAsNotFound('not found message');
65+
}
66+
5367
}

tests/dummy/routes/api.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
});
2626

2727
/** Users */
28-
$server->resource('users')->only('show')->relationships(function ($relationships) {
28+
$server->resource('users')->only('show','destroy')->relationships(function ($relationships) {
2929
$relationships->hasOne('phone');
3030
})->actions(function ($actions) {
3131
$actions->get('me');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
/*
3+
* Copyright 2024 Cloud Creativity Limited
4+
*
5+
* Use of this source code is governed by an MIT-style
6+
* license that can be found in the LICENSE file or at
7+
* https://opensource.org/licenses/MIT.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace App\Tests\Api\V1\Users;
13+
14+
use App\Models\User;
15+
use App\Tests\Api\V1\TestCase;
16+
17+
class DeleteTest extends TestCase
18+
{
19+
20+
public function test(): void
21+
{
22+
$user = User::factory()->createOne();
23+
24+
$expected = $this->serializer
25+
->user($user);
26+
$response = $this
27+
->actingAs(User::factory()->createOne())
28+
->jsonApi('users')
29+
->delete(url('/api/v1/users', $expected['id']));
30+
31+
$response->assertNotFound()
32+
->assertHasError(404, [
33+
'detail' => 'not found message',
34+
'status' => '404',
35+
'title' => 'Not Found',
36+
]);
37+
}
38+
}

0 commit comments

Comments
 (0)