Pundit:如何为一项未经授权的操作处理多个错误代码?

时间:2019-06-12 10:26:12

标签: ruby-on-rails ruby pundit

我使用pundit处理我的API策略,有一个项目显示在某些情况下可以禁止用户使用,而在其他情况下则仅受限制。受限,我的意思是现在禁止这样做,但是如果他付费,则可以访问它。因此,我需要我的API以特定的代码(402 Payment Required进行响应,以便客户可以邀请用户付款以解锁节目。

这是我当前的代码,当pundit返回false时,它仅以403响应。

在哪里最好执行条件以返回403402以便变干和干净?

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 个答案:

答案 0 :(得分:2)

很遗憾,Pundit无法开箱即用地处理其他错误类型。它的构建始终使该策略的方法返回true或错误的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的一部分,在这种情况下,使用此全局数据存储区将避免覆盖专家内部。

您可能想阅读有关CurrentAttributes的API文档。