Pundit: как обрабатывать несколько кодов ошибок для одного несанкционированного действия? - PullRequest
0 голосов
/ 12 июня 2019

Я использую pundit для обработки своих политик API, у меня есть элемент шоу, который в некоторых случаях может быть запрещен пользователю, а в других - просто запрещен. Под ограничением я имею в виду, что теперь это запрещено, но если он заплатит, он сможет получить к нему доступ. Поэтому мне нужно, чтобы мой API отвечал определенным кодом (402 Payment Required), чтобы клиент мог пригласить пользователя заплатить, чтобы разблокировать шоу.

Это мой текущий код, он отвечает только 403, когда pundit возвращает false.

Где было бы лучше реализовать условие возврата 403 ИЛИ 402, чтобы быть СУХИМ и чистым?

class Api::V1::ItemController < Api::V1::BaseController
  def show
    @item = Item.find(params[:id])
    authorize @item
  end
end

class ItemPolicy < ApplicationPolicy
  def show?
    return true if record.public?

    # 403 will be generated, that's ok.
    return false if !record.band.members.include?(user)

    # If that condition is false I want to generate a 402 error at the end, not a 403.
    user.premium?
  end
end

class Api::V1::BaseController < ActionController::API
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  def user_not_authorized(_exception)
    # Here I've got the exception with :policy, :record and :query, 
    # also I can access :current_user so I could go for a condition, 
    # but that would include duplicated code from  ItemPolicy#show?.
    render json: { error: { message: "Access denied" } }, status: :forbidden
  end
end

1 Ответ

2 голосов
/ 12 июня 2019

К сожалению Pundit не может обрабатывать различные типы ошибок из коробки.И он создан так, чтобы всегда ожидать, что методы политики вернут true или false false.Поэтому вызов другой пользовательской ошибки и восстановление из нее в контроллере не будет работать, поскольку это также нарушит методы представления.

Я предлагаю обходной путь для введения различных типов ошибок.Нечто подобное может сработать:

# in the policy
class ItemPolicy < ApplicationPolicy
  def show?
    return true if record.public?
    return false unless record.band.members.include?(user)

    if user.premium?
      true
    else
      Current.specific_response_error_code = :payment_required
      false
    end
  end
end

# in the controller
class Api::V1::BaseController < ActionController::API
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  def user_not_authorized(_exception)
    case Current.specific_response_error_code
    when :payment_required
      render json: { error: { message: "Premium required" } }, status: :payment_required
    else
      render json: { error: { message: "Access denied" } }, status: :forbidden
    end
  end
end

Я бы не стал использовать глобальные CurrentAttributes в качестве хорошей практики, но они являются частью Rails, и в этом случае использование этого глобального хранилища данных позволяет избежать переопределения внутренних элементов pundit.

Возможно, вы захотите прочитать документы API по поводу CurrentAttributes.

...