首页 > 解决方案 > Pundit:如何为一项未经授权的操作处理多个错误代码?

问题描述

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

这是我当前的代码,它仅403在权威返回 false 时响应。

为了干燥和清洁,最好在哪里实施返回403OR的条件?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

标签: ruby-on-railsrubypundit

解决方案


不幸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 的一部分,在这种情况下,使用这个全局数据存储可以避免覆盖权威内部。

您可能想阅读有关CurrentAttributes.


推荐阅读