如何信任关联 ID 参数?

How to trust an association id parameter?

这是一个我们需要相信 conversation_id 没有被用户更改的例子:

# messages_controller.rb
def create
  @message = Message.new(
    body: message_params[:body], # trustworthy
    user_id: current_user.id, # trustworthy
    conversation_id: message_params[:conversation_id] # not trustworthy!
    )
  @message.save
end

所以我考虑将上面的代码包装在一个 if 语句中,像这样

# messages_controller.rb
def create
  if current_user.conversations.pluck(:id).include? message_params[:conversation_id]
    @message = Message.new(
      body: message_params[:body], 
      user_id: current_user.id, 
      conversation_id: message_params[:conversation_id] 
      )
    @message.save
  end
end

这是我能想到的确保对话实际上是用户所属的唯一方法(未能仔细检查可能会导致恶意用户成功将消息写入 other人们的谈话!)

由于这种类型的检查一定很常见,我只想知道我是否有效地完成了它,或者是否有更好的方法或更多'rails way'?

我还应该补充一点,当消息不属于涉及用户的对话时,我有 cancan 保护 create 方法(这应该完全防止恶作剧本身)并且我在 conversation_id(我知道这不是真正的保护,但它都有帮助)。但我仍然想知道如何在没有这些保护的情况下做到这一点以增加深度。

rails 方法是使用嵌套路由,而不是通过请求正文传递 conversation_id

resources :conversations do
  resources :messages, shallow: true
end
class MessagesController < ApplicationController
  # POST /conversations/:conversation_id/messages
  def create
    @conversation = current_user.conversations
                                .find(params[:conversation_id])
    @message = @conversation.messages.new(message_params) do |m|
      m.user = current_user
    end

    if @message.save
      redirect_to @conversation
    else
      render :new
    end
  end

  private
  def message_parameters
    params.require(:message)
          .permit(:body)
  end
end

这可行 - 但它远非完美。如果 @conversation = current_user.conversations.find(params[:conversation_id]) 没有找到记录,我们会收到 ActiveRecord::RecordNotFound 异常和 404 响应,而不是检查用户是否有权 post 进行该对话。

更好的解决方案是使用类似的东西:

@conversation = Conversation.find(params[:conversation_id])
unless conversations.users.exist?(id: current_user.id)
  raise SomeKindOfAuthenticationError
end

当然你真的应该使用像 Pundit 这样的东西,而不是在这里重新发明轮子。