Допустим, у нас есть действие в политике для нашей модели, которое может возвращать false в нескольких разных сценариях:
class PostPolicy
{
public function publish(User $user, Post $post)
{
if ($post->user_id !== $user->id) {
return false;
}
return $post->show_at->lessThan(now());
}
}
Как видите, мы отказываем этому пользователю в праве на публикацию в двух случаях: если это не его пост или если этот пост был подготовлен заранее на какую-то будущую дату, которая еще не наступила.
Как я могу предоставить некоторый контекст относительно того, почему авторизация не удалась? Было ли это потому, что мы не являемся владельцем, или это было потому, что еще не пришло время для публикации этого поста?
$user->can('publish', $post); // if this returns false we don't know
// what caused authorization to fail.
Похоже, что политики Laravel по своему дизайну не имеют возможности сделать это. Но мне любопытно, какие обходные пути могут быть, чтобы мы могли иметь логику авторизации (независимо от того, насколько она сложна) в одном месте (политика модели), а также получать некоторый контекст (например, пользовательские коды ошибок) в случае сбоя авторизации.
Любые идеи?






Если в контроллере вы используете политику, например:
<?php
$this->authorize('publish', Post::class)
Тогда у laravel будет ошибка 403 HTTPResponse.
Что вы должны сделать, так это то, что один метод политики должен просто проверять один случай проверки.
Например, обновите свою политику:
<?php
// Check if post he is trying to publish is his own
public function publishOwnPost(){...}
// Check if post is for future purpose
public function publicFuturePost(){...}
Затем в контроллере выполните:
<?php
if (!$user->can('publishOwnPost', $post)){
// Return custom error view for case 1
return response()->view('errors.publishOwnPostError', $data, 403);
}
if (!$user->can('publishFuturePost', $post)){
// Return custom error view for case 2
return response()->view('errors.publishFuturePostError', $data, 403);
}
// Do further processing
Мотивом политики являются разрешения и контроль доступа на детальном уровне. Наличие одного метода, проверяющего 2 случая, само по себе неоднозначно. Цель политики — просто сказать, разрешено ли конкретное действие или нет. Ответственность за обработку возлагается на диспетчера. Но то, что у вас сейчас есть, по-прежнему будет очень жизнеспособным решением, если вы не хотите по-разному обрабатывать два случая проверки. В вашем случае мне удобно решение, которое я предоставил :)
Но что происходит, когда вводятся новые проверки авторизации? Или некоторые уже не нужны? Это приведет к изменению всех проверок авторизации во всем проекте для этого действия. Что, если один из них будет пропущен или пропущен? А может кто-то может напутать порядок (приоритет) на них и тд. Я благодарю вас за ваш вклад, но это не централизованный способ решения такой проблемы. Иметь такое бремя (знать, какие методы политики существуют и связаны с конкретным действием) каждый раз, когда действие должно быть авторизовано, просто пахнет неприятностями.
Полностью зависит от вашего решения. Политика Laravel предназначена для того, чтобы laravel мог обрабатывать запрещенный http-ответ 403. Политика не обязана объяснять, почему она была отклонена. Так что я вроде как понимаю ваше беспокойство о поддержке кода для более длительного использования. Вы можете сделать отдельную функцию для проверки и использовать ее на всем веб-сайте. Поэтому, если проверка для публикации изменяется, отвечает только эта функция. Я был бы рад узнать, есть ли лучшее решение для этого, но на данный момент я чувствую, что то, что я сказал, будет для вас самым простым.
В итоге я разделил некоторые обязанности между моделью и ее политикой.
Политика оказалась ответственной за обеспечение того, чтобы пользователь имел право выполнять определенные действия. Ни больше, ни меньше:
class PostPolicy
{
public function publish(User $user, Post $post)
{
return $post->user_id !== $user->id;
}
}
С другой стороны, модель должна иметь логику для проверки того, можно ли с ней выполнить определенное действие:
class Post extends Model
{
...
public function isPublishable()
{
return $this->show_at->lessThan(now());
}
...
}
Поэтому каждый экземпляр сообщения теперь может сказать нам, можно ли его опубликовать. Наконец, мое действие Post::publishBy(User $user) будет включать в себя сначала авторизацию пользователя для этого действия и проверку возможности публикации этого сообщения отдельно, чтобы мы могли определить конкретную причину, по которой публикация не удалась.
Я чувствую, что этот дизайн подходит лучше, оставляя политики Laravel делать только то, что они должны делать (разрешая действия пользователя), и требуя, чтобы модели несли ответственность за то, что касается только их.
В случае, если кому-то понадобится, помимо вышеприведенного принятого ответа, в Laravel 7+ Gate может указать причины отказа,
Ссылка: https://laravel.com/docs/7.x/authorization#gate-responses
Gate::authorize() будут вызывать причину вместе с исключением с сообщением, предоставленным в вызове Response::deny(<message>), само исключение будет Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException$user->can() или Gate::allows() дадут логическое значениеGate::inspect() даст полный ответУчитывая, что теперь вы будете возвращать объект Gate Response вместо логического значения, Laravel поможет вернуть подходящий ответ, указанный выше.
<?php
use Illuminate\Auth\Access\Response;
Первоначально вы возвращаете только логическое значение
<?php
class PostPolicy
{
public function publish(User $user, Post $post)
{
return $post->user_id !== $user->id;
}
}
Теперь, используя Gate Response, вы можете указать причину
<?php
use Illuminate\Auth\Access\Response;
class PostPolicy
{
public function publish(User $user, Post $post)
{
return $post->user_id === $user->id
? Response::allow()
: Response::deny('You are not the author of the post.');
}
}
Это противоречит тому, какие действия политики должны быть, потому что теперь вместо авторизации одного действия
publishмне придется авторизовать несколько с разными именами. Все проверки должны быть инкапсулированы внутри одного действия в политике. В противном случае поддержка предоставленного вами кода станет ходячим кошмаром (представьте, что вы добавляете новую проверку авторизации публикации, когда вы авторизуете это действие во всем своем проекте).