Как мы можем вернуть причину отказа в действиях из политики Laravel?

Допустим, у нас есть действие в политике для нашей модели, которое может возвращать 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 в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
0
758
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Если в контроллере вы используете политику, например:

<?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

Это противоречит тому, какие действия политики должны быть, потому что теперь вместо авторизации одного действия publish мне придется авторизовать несколько с разными именами. Все проверки должны быть инкапсулированы внутри одного действия в политике. В противном случае поддержка предоставленного вами кода станет ходячим кошмаром (представьте, что вы добавляете новую проверку авторизации публикации, когда вы авторизуете это действие во всем своем проекте).

d3jn 05.02.2019 17:23

Мотивом политики являются разрешения и контроль доступа на детальном уровне. Наличие одного метода, проверяющего 2 случая, само по себе неоднозначно. Цель политики — просто сказать, разрешено ли конкретное действие или нет. Ответственность за обработку возлагается на диспетчера. Но то, что у вас сейчас есть, по-прежнему будет очень жизнеспособным решением, если вы не хотите по-разному обрабатывать два случая проверки. В вашем случае мне удобно решение, которое я предоставил :)

Mihir Bhende 05.02.2019 17:29

Но что происходит, когда вводятся новые проверки авторизации? Или некоторые уже не нужны? Это приведет к изменению всех проверок авторизации во всем проекте для этого действия. Что, если один из них будет пропущен или пропущен? А может кто-то может напутать порядок (приоритет) на них и тд. Я благодарю вас за ваш вклад, но это не централизованный способ решения такой проблемы. Иметь такое бремя (знать, какие методы политики существуют и связаны с конкретным действием) каждый раз, когда действие должно быть авторизовано, просто пахнет неприятностями.

d3jn 05.02.2019 18:01

Полностью зависит от вашего решения. Политика Laravel предназначена для того, чтобы laravel мог обрабатывать запрещенный http-ответ 403. Политика не обязана объяснять, почему она была отклонена. Так что я вроде как понимаю ваше беспокойство о поддержке кода для более длительного использования. Вы можете сделать отдельную функцию для проверки и использовать ее на всем веб-сайте. Поэтому, если проверка для публикации изменяется, отвечает только эта функция. Я был бы рад узнать, есть ли лучшее решение для этого, но на данный момент я чувствую, что то, что я сказал, будет для вас самым простым.

Mihir Bhende 05.02.2019 18:11
Ответ принят как подходящий

В итоге я разделил некоторые обязанности между моделью и ее политикой.

Политика оказалась ответственной за обеспечение того, чтобы пользователь имел право выполнять определенные действия. Ни больше, ни меньше:

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.');
    }
}

Другие вопросы по теме