Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
52 changes: 37 additions & 15 deletions src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,39 @@ 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 {
// TODO: put the good anchor to link to the new method (link in the string)
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 +101,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 +159,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 +178,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
60 changes: 60 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,65 @@ 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_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');
}
}