Pundit:如何处理一个未经授权的操作的多个错误代码?
Pundit: how to handle multiple error codes for one unauthorized action?
我使用专家来处理我的 API 政策,我有一个项目展示可以在某些情况下禁止用户使用,而在其他情况下只是限制。受限是指现在禁止使用,但如果他付费,他就可以访问它。所以我需要我的 API 以特定代码 (402 Payment Required
) 响应,以便客户可以邀请用户付款以解锁节目。
这是我当前的代码,它仅在专家 return 为假时以 403
响应。
最好在什么地方实施 return 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
不幸的是,Pundit
无法开箱即用地处理不同的错误类型。它被构建为始终期望策略的方法为 return 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
的 API 文档。
在app/controllers/concerns/response.rb
中创建响应模块
module Response
def json_response(object, status = :ok)
render json: object, status: status
end
end
在 app/controllers/concerns/exception_handler.rb
中创建异常处理程序
module ExceptionHandler
extend ActiveSupport::Concern
included do
rescue_from Pundit::NotAuthorizedError, with: :unauthorized_request
end
private
# JSON response with message; Status code 401 - Unauthorized
def unauthorized_request(e)
json_response({ message: e.message }, :unauthorized)
end
end
在app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include Response
include ExceptionHandler
end
就是这样
我使用专家来处理我的 API 政策,我有一个项目展示可以在某些情况下禁止用户使用,而在其他情况下只是限制。受限是指现在禁止使用,但如果他付费,他就可以访问它。所以我需要我的 API 以特定代码 (402 Payment Required
) 响应,以便客户可以邀请用户付款以解锁节目。
这是我当前的代码,它仅在专家 return 为假时以 403
响应。
最好在什么地方实施 return 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
不幸的是,Pundit
无法开箱即用地处理不同的错误类型。它被构建为始终期望策略的方法为 return 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
的 API 文档。
在app/controllers/concerns/response.rb
中创建响应模块module Response
def json_response(object, status = :ok)
render json: object, status: status
end
end
在 app/controllers/concerns/exception_handler.rb
中创建异常处理程序module ExceptionHandler
extend ActiveSupport::Concern
included do
rescue_from Pundit::NotAuthorizedError, with: :unauthorized_request
end
private
# JSON response with message; Status code 401 - Unauthorized
def unauthorized_request(e)
json_response({ message: e.message }, :unauthorized)
end
end
在app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include Response
include ExceptionHandler
end
就是这样