【问题标题】:How can I ensure that parent_id is set correctly when creating new item based on existing item?在基于现有项目创建新项目时,如何确保正确设置 parent_id?
【发布时间】:2010-08-12 05:29:06
【问题描述】:

问题

有趣的问题。有趣,因为它看起来很随意,直到你开始考虑它。假设我允许人们根据其他项目创建项目。您可以打开 /items/new?id=3,与常规的新操作不同,您将看到一个预先填充了 item-3 值的表单,而不是一个空表单。因此,与您的平均默认新操作不同,我的操作如下所示:

def new
  @item = params[:id] ? Item.find(params[:id]) : Item.new
end

因此,您可以使用 /items/new?id=1 之类的 url 访问操作,并且表单将预先填充来自 item-1 的数据。

到目前为止一切顺利。

现在您提交此数据。我的创建动作非常标准。

def create
  @item = Item.new(params[:item])

  if @item.save
    # happy
  else
    # sad
  end
end

这里只有一个问题。刚刚创建的新项目(我们称之为 item-2)需要知道爸爸是谁。在我们的例子中,爸爸是 item-1(因为原始表单预先填充了来自 item-1 的数据),因此 item-2 必须将 parent_id 设置为 1。只是,这不仅仅是一个不错的 -有。它必须坚如磐石,不可篡改。您不能欺骗 parent_id。

所以我的问题是 — 在基于现有项目创建新项目时,如何确保正确设置 parent_id?

我们将不胜感激一些帮助,但只是为了让您不认为这是一个简单的偶然问题,这里有一些您可能正在考虑的非解决方案。

非解决方案#1

因此,保存 parent_id 的常用解决方案是将其作为表单中的隐藏字段。

<% form_for @item do |f| %>
  ...
  <%= f.hidden_field :parent_id, :value => params[:id] %>
  ...
<% end %>

但是在这里我可以很容易地在我的 html 中切换它。提交表单时,服务器不会有任何线索。服务器将简单地“相信”该项目是使用父级 [whatever] 创建的,而它应该是父级 1。

非解决方案 #2

在服务器上,我可以使用 session 来存储您正在编辑的项目。然后我的新动作将如下所示。

def new
  if params[:id]
    @item = Item.find(params[:id])
    session[:creating_item] = params[:id].to_i
  else
    @item = Item.new
    session[:creating_item] = :mine # this prevents loophole if session is cleared
  end
end

我的创建操作将采用此值并将其设置为 parent_id。

def create
  @item = Item.new(params[:item])

  if session[:creating_item] == :mine
    @item.parent_id = nil
  elsif session[:creating_item].is_a?(Fixnum)
    @item.parent_id = session[:creating_item]
  else
    # session was probably cleared
    redirect_to root_url and return
  end

  if @item.save
    # happy
  else
    # sad
  end
end

如果您多次打开/items/new(例如在浏览器的多个选项卡中),这将不起作用。会话值每次都会被覆盖。因此,如果您提交了在/items/new?id=1 打开的表单,但您也打开了/items/new?id=2,那么会话中的 parent_id 将为 2。失败。

非解决方案#3

如果您在会话中将所有打开的父级保存在数组中,而不是仅仅覆盖单个值。好吧,我不会写很长的例子,我只想说,当任何打开的表单被提交时——你会知道它一定是你在会话中保存的父母之一(例如 1、2 或 3 ),但你仍然不知道是哪一个。然后,您可以将来自父级 1 的数据保存为来自父级 3 的数据。

【问题讨论】:

    标签: ruby-on-rails security validation forms token


    【解决方案1】:

    如何将父级存储在会话中,但使用哈希而不是数组。您可以使用真实性令牌作为散列的键,以便在提交表单时根据此值查找父级。多个表单都有自己的令牌,因此您可以在会话中同时跟踪多个父 ID。

    另一种方法是稍微改变工作流程。 让动作实际克隆并创建新元素之前允许用户编辑。这样整个问题就变得没有意义了。您可能需要在用户创建之前与他们确认,以确保您没有在整个系统中克隆对象,或者如果他们改变主意,则提供简单的删除功能。

    【讨论】:

    • 不同的工作流程方法绝对不是我想做的事情。以这种方式创建大量对象太容易了,而在我的领域中,这正是将要发生的事情。 :) 根据使用 auth-token,它几乎是有帮助的,但是......您仍然可以将身份验证令牌从一种形式复制到另一种形式,不是吗?它可以防止 CSRF,但是如果您打开 2 个表单并切换令牌 - rails 不会介意,或者会吗?
    • 您可以根据 parent_id 本身创建某种散列,并根据预期值检查它,类似于 CSRF 的工作方式。这些东西实际上会有多大的问题?您是否可以检查其他数据作为验证步骤?所以交叉引用父母与新孩子?
    • 任何哈希解决方案都可以通过从 2 个开放表单交换令牌来轻松破解,每个表单都意味着不同的 parent_id。其他数据都可以改变,所以不是真的。 :( 与 x-reference 无关。一旦有人找到欺骗父母的方法并在某处发布脚本(在我的目标市场中,这是一个合理的预期),这将是一个丑陋且无法解决的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-03
    • 2018-11-29
    • 1970-01-01
    • 2011-02-16
    • 2016-07-22
    相关资源
    最近更新 更多