Skip to content
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
3 changes: 3 additions & 0 deletions config/rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
'gates' => [
'enabled' => true,
'key' => 'gates',
'message' => [
'enabled' => false,
],
// Here you can customize the keys for each gate
'names' => [
'authorized_to_view' => 'authorized_to_view',
Expand Down
10 changes: 5 additions & 5 deletions src/Concerns/Authorizable.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ public function authorizeTo($ability, $model)
* @param string $ability
* @param Model|string $model
*
* @return bool
* @return Response
*/
public function authorizedTo($ability, $model)
{
if ($this->isAuthorizingEnabled()) {
$resolver = function () use ($ability, $model) {
return Gate::check($ability, $model);
return Gate::inspect($ability, $model);
};

if ($this->isAuthorizationCacheEnabled()) {
Expand All @@ -86,15 +86,15 @@ public function authorizedTo($ability, $model)
return $resolver();
}

return true;
return Response::allow();
}

/**
* Determine if the current user has a given ability.
*
* @param string $ability
* * @param Model $model
* * @param string $toActionModel
* @param Model $model
* @param string $toActionModel
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*
Expand Down
51 changes: 36 additions & 15 deletions src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,38 @@ public function resource(Resource $resource)
});
}

protected function buildGatesForModel(Model $model, Resource $resource, array $gates)
protected function buildGatesForModel(Model|string $model, Resource $resource, array $gates)
{
return array_merge(
in_array('view', $gates) ? [config('rest.gates.names.authorized_to_view') => $resource->authorizedTo('view', $model)] : [],
in_array('update', $gates) ? [config('rest.gates.names.authorized_to_update') => $resource->authorizedTo('update', $model)] : [],
in_array('delete', $gates) ? [config('rest.gates.names.authorized_to_delete') => $resource->authorizedTo('delete', $model)] : [],
in_array('restore', $gates) ? [config('rest.gates.names.authorized_to_restore') => $resource->authorizedTo('restore', $model)] : [],
in_array('forceDelete', $gates) ? [config('rest.gates.names.authorized_to_force_delete') => $resource->authorizedTo('forceDelete', $model)] : [],
);
$nameMap = [
'create' => config('rest.gates.names.authorized_to_create'),
'view' => config('rest.gates.names.authorized_to_view'),
'update' => config('rest.gates.names.authorized_to_update'),
'delete' => config('rest.gates.names.authorized_to_delete'),
'restore' => config('rest.gates.names.authorized_to_restore'),
'forceDelete' => config('rest.gates.names.authorized_to_force_delete'),
];

$result = [];

if (config('rest.gates.message.enabled', false)) {
foreach ($gates as $gate) {
if (isset($nameMap[$gate])) {
$authorizedTo = $resource->authorizedTo($gate, $model);
$result[$nameMap[$gate]]['allowed'] = $authorizedTo->allowed();
$result[$nameMap[$gate]]['message'] = $authorizedTo->message();
}
}
} else {
trigger_deprecation('lomkit/laravel-rest-api', '2.17.0', 'In Laravel Rest Api 3 it won\'t be possible to use the old gate schema, please upgrade as quickly as possible. See: https://laravel-rest-api.lomkit.com/digging-deeper/gates#policy-message-in-gates');
foreach ($gates as $gate) {
if (isset($nameMap[$gate])) {
$authorizedTo = $resource->authorizedTo($gate, $model);
$result[$nameMap[$gate]] = $authorizedTo->allowed();
}
}
}

return $result;
}

/**
Expand Down Expand Up @@ -77,9 +100,11 @@ public function modelToResponse(Model $model, Resource $resource, array $request
)
)
->when($resource->isGatingEnabled() && isset($currentRequestArray['gates']), function ($attributes) use ($currentRequestArray, $resource, $model) {
$currentRequestArrayWithoutCreate = collect($currentRequestArray['gates'])->reject(fn ($value) => $value === 'create')->toArray();

return $attributes->put(
config('rest.gates.key'),
$this->buildGatesForModel($model, $resource, $currentRequestArray['gates'])
$this->buildGatesForModel($model, $resource, $currentRequestArrayWithoutCreate)
);
})
->toArray(),
Expand Down Expand Up @@ -133,9 +158,7 @@ public function toResponse($request)
$this->responsable->currentPage(),
$this->responsable->getOptions(),
$this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', [])) ? [
config('rest.gates.key') => [
config('rest.gates.names.authorized_to_create') => $this->resource->authorizedTo('create', $this->resource::newModel()::class),
],
config('rest.gates.key') => $this->buildGatesForModel($this->resource::newModel()::class, $this->resource, ['create']),
] : []
);

Expand All @@ -154,9 +177,7 @@ public function toResponse($request)
'data' => $data ?? $this->map($this->responsable, $this->modelToResponse($this->responsable, $this->resource, $request->input('search', []))),
'meta' => array_merge(
$this->resource->isGatingEnabled() && in_array('create', $request->input('search.gates', [])) ? [
config('rest.gates.key') => [
config('rest.gates.names.authorized_to_create') => $this->resource->authorizedTo('create', $this->resource::newModel()::class),
],
config('rest.gates.key') => $this->buildGatesForModel($this->resource::newModel()::class, $this->resource, ['create']),
] : []
),
];
Expand Down
119 changes: 119 additions & 0 deletions tests/Feature/Controllers/AutomaticGatingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Lomkit\Rest\Tests\Support\Policies\DeletePolicy;
use Lomkit\Rest\Tests\Support\Policies\ForceDeletePolicy;
use Lomkit\Rest\Tests\Support\Policies\GreenPolicy;
use Lomkit\Rest\Tests\Support\Policies\RedPolicyWithMessage;
use Lomkit\Rest\Tests\Support\Policies\RestorePolicy;
use Lomkit\Rest\Tests\Support\Policies\UpdatePolicy;
use Lomkit\Rest\Tests\Support\Policies\ViewPolicy;
Expand Down Expand Up @@ -62,6 +63,124 @@ public function test_searching_automatic_gated_resource(): void
);
}

public function test_searching_automatic_gated_resource_and_custom_message(): void
{
$model = ModelFactory::new()
->create();

Gate::policy(Model::class, RedPolicyWithMessage::class);

config(['rest.gates.message.enabled' => true]);

$response = $this->post(
'/api/automatic-gating/search',
[
'search' => [
'gates' => ['create', 'view', 'update', 'delete', 'forceDelete', 'restore'],
],
],
['Accept' => 'application/json']
);

$this->assertResourcePaginated(
$response,
[$model],
new AutomaticGatingResource(),
[
[
'gates' => [
'authorized_to_view' => [
'allowed' => false,
'message' => 'You don\'t have permission to view user',
],
'authorized_to_update' => [
'allowed' => false,
'message' => 'You don\'t have permission to update user',
],
'authorized_to_delete' => [
'allowed' => false,
'message' => 'You don\'t have permission to delete user',
],
'authorized_to_restore' => [
'allowed' => false,
'message' => 'You don\'t have permission to restore user',
],
'authorized_to_force_delete' => [
'allowed' => false,
'message' => 'You don\'t have permission to force delete user',
],
],
],
]
);
$response->assertJsonPath(
'meta.gates.authorized_to_create',
[
'allowed' => false,
'message' => 'You don\'t have permission to create user',
]
);
}

public function test_searching_automatic_gated_resource_with_allowed_gates_and_custom_message(): void
{
$model = ModelFactory::new()
->create();

Gate::policy(Model::class, GreenPolicy::class);

config(['rest.gates.message.enabled' => true]);

$response = $this->post(
'/api/automatic-gating/search',
[
'search' => [
'gates' => ['create', 'view', 'update', 'delete', 'forceDelete', 'restore'],
],
],
['Accept' => 'application/json']
);

$this->assertResourcePaginated(
$response,
[$model],
new AutomaticGatingResource(),
[
[
'gates' => [
'authorized_to_view' => [
'allowed' => true,
'message' => null,
],
'authorized_to_update' => [
'allowed' => true,
'message' => null,
],
'authorized_to_delete' => [
'allowed' => true,
'message' => null,
],
'authorized_to_restore' => [
'allowed' => true,
'message' => null,
],
'authorized_to_force_delete' => [
'allowed' => true,
'message' => null,
],
],
],
]
);
$response->assertJsonPath(
'meta.gates.authorized_to_create',
[
'allowed' => true,
'message' => null,
]
);
}

public function test_searching_automatic_gated_resource_with_global_config_disabled(): void
{
$model = ModelFactory::new()
Expand Down
87 changes: 87 additions & 0 deletions tests/Support/Policies/RedPolicyWithMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Lomkit\Rest\Tests\Support\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
use Illuminate\Database\Eloquent\Model;

class RedPolicyWithMessage
{
use HandlesAuthorization;

/**
* Determine whether the user can view the list of models.
*
* @param $user
*/
public function viewAny($user)
{
return true;
}

/**
* Determine whether the user can view the model.
*
* @param $user
* @param Model $model
*/
public function view($user, Model $model)
{
return Response::deny('You don\'t have permission to view user');
}

/**
* Determine whether the user can create models.
*
* @param $user
*/
public function create($user)
{
return Response::deny('You don\'t have permission to create user');
}

/**
* Determine whether the user can update the model.
*
* @param $user
* @param Model $model
*/
public function update($user, Model $model)
{
return Response::deny('You don\'t have permission to update user');
}

/**
* Determine whether the user can delete the model.
*
* @param $user
* @param Model $model
*/
public function delete($user, Model $model)
{
return Response::deny('You don\'t have permission to delete user');
}

/**
* Determine whether the user can restore the model.
*
* @param $user
* @param Model $model
*/
public function restore($user, Model $model)
{
return Response::deny('You don\'t have permission to restore user');
}

/**
* Determine whether the user can permanently delete the model.
*
* @param $user
* @param Model $model
*/
public function forceDelete($user, Model $model)
{
return Response::deny('You don\'t have permission to force delete user');
}
}