【问题标题】:Returning a 403 when a 404 occurs in Rails API在 Rails API 中发生 404 时返回 403
【发布时间】:2020-05-02 17:11:00
【问题描述】:

我有一个带有控制器的 Rails API,该控制器在 create 函数上有一个 before_action,用于检查创建“粘贴”的 current_user 是否也是 paste_params 中指定 room_id 的所有者。

如果未找到 current_user.rooms.find(params[:paste][:room_id]).user_id 或找到但不等于 current_user.id,则服务器始终返回 404。

如何让它返回 403 而不是 404?因为此检查旨在确定创建粘贴的用户是否也是粘贴将链接到的房间的所有者,如果用户不是该房间的所有者,则意味着他们无权在下创建粘贴那个房间。

这是控制器的相关部分:

class Api::V1::PastesController < ApplicationController
  before_action :check_room_owner, only: %i[create update destroy]

  def create
    paste = current_user.pastes.build(paste_params)
    if paste.save
      render json: PasteSerializer.new(paste).serializable_hash, status: :created
    else
      render json: { errors: paste.errors }, status: :unprocessable_entity
    end
  end

  private

  # Convert to 403 forbidden if not found
  def check_room_owner
    head :forbidden unless current_user.rooms.find(params[:paste][:room_id]).user_id === current_user.id
  end

end

干杯!

【问题讨论】:

  • 这个问题的实际minimal reproducible example 将是 11 行,而不是 48 行。我建议花点时间阅读那篇文章。很多时候,自己创建 MRE 会引导您找到解决方案。

标签: ruby-on-rails ruby api controller


【解决方案1】:

这就是所谓的授权,如果你真的要重新发明轮子,至少要做好:

# app/errors/authentication_error.rb
class AuthenticationError < StandardError
end
class ApplicationController
  rescue_from 'AuthenticationError', with: :deny_access

  def deny_access
    head :forbidden
  end
end
# Do not use :: when declaring classes!
module API
  module V1
    class PastesController < ApplicationController
      before_action :find_and_authenticate_room!

      def create
        paste = current_user.pastes.build(paste_params)
        if paste.save
          render json: PasteSerializer.new(paste).serializable_hash, status: :created
        else
          render json: { errors: paste.errors }, status: :unprocessable_entity
        end
      end

      private
      def find_and_authenticate_room!
        # This smells really bad - use a nested route instead!
        @room = Room.find(params[:paste][:room_id])
        raise AuthenticationError unless @room.user == current_user
      end
    end
  end
end

这将响应的逻辑与确定允许的内容分开,并使用继承来干燥整个过程。更好的是不要重新发明轮子并使用 Pundit 或 CanCanCan 将授权规则与控制器分开,从而使其保持精简。

【讨论】:

    【解决方案2】:

    我认为您正在寻找这样的东西:

    class Api::V1::PastesController < ApplicationController
    
      def create
        paste = current_user.pastes.build(paste_params)
        return head :forbidden unless paste.room.user_id === current_user.id
        if paste.save
          render json: PasteSerializer.new(paste).serializable_hash, status: :created
        else
          render json: { errors: paste.errors }, status: :unprocessable_entity
        end
      end
    end
    

    【讨论】:

      【解决方案3】:

      马克的回答很完美,但我也找到了另一个效果很好的解决方案。

        def check_room_owner
          selected_room = current_user.rooms.find(params[:paste][:room_id]).user_id
          rescue ActiveRecord::RecordNotFound
           head :forbidden
        end
      

      如果没有找到selected_room,rails 将引发ActiveRecord::RecordNotFound 异常,而不是使变量未定义或为零。所以我们可以使用rescue来处理异常并返回403,而不是让rails默认返回404。

      【讨论】:

      • 这并不理想,因为您实际上并不知道错误是否因 id 无效或用户未被授权而实际引发,并且您在两种情况下都返回 :forbidden。跨度>
      猜你喜欢
      • 1970-01-01
      • 2012-05-17
      • 1970-01-01
      • 1970-01-01
      • 2017-07-20
      • 2018-12-05
      • 1970-01-01
      • 2015-02-24
      • 2021-06-27
      相关资源
      最近更新 更多