【问题标题】:Safely pass a dynamic column name into an ActiveRecord query with a Postgres cast?使用 Postgres 强制转换将动态列名安全地传递到 ActiveRecord 查询中?
【发布时间】:2016-09-27 21:05:11
【问题描述】:

我在我的应用程序中做了很多时间没有日期查询,我想抽象一些查询。

假设我有一个带有DateTime starts_at 字段的模型:

Shift.where('starts_at::time > ?', '20:31:00.00')
-> SELECT "shifts".* FROM "shifts" WHERE (starts_at::time > '20:31:00.00')

这会正确返回所有大于时间 20:31 的 'starts_at' 值。

我想动态地将列名传递到查询中,所以我可以这样做:

Shift.where('? > ?', "#{column_name}::time", '20:31:00.00').
-> SELECT "shifts".* FROM "shifts" WHERE ('starts_at::time' > '20:31:00.00')

在此示例中,这不起作用,因为搜索将 starts_at::time 作为字符串执行,而不是作为具有 time 转换的列。

如何安全地将column_name 传递到带有::time 演员的查询中?虽然这不会接受用户输入,但我仍然希望确保考虑到 SQL 注入。

【问题讨论】:

  • 您尝试使用像query_text = " start_at > ?" 这样的临时变量,然后使用Shift.where( query_text , '20:31:00.00')
  • @inye 但问题是他不一定知道column_name就是'start_at',理论上列名可以是'pancakes house',所以需要引用/escaped,然后才能发送到数据库。
  • 当你知道column_name?也许你可以有一些if else 分配query_text

标签: ruby-on-rails postgresql activerecord rails-activerecord


【解决方案1】:

这比你最初想象的要复杂,因为标识符(列名、表名……)和值('pancakes'6、……)在 SQL 中是非常不同的东西,它们有不同的引用规则甚至引号字符(字符串的单引号,标准 SQL 中标识符的双引号,MySQL 中标识符的反引号,SQL-Server 中标识符的括号,......)。如果您想到像 Ruby 变量名称和值(如文字 Ruby 值)之类的标识符,那么您就会开始看到差异。

当你这样说时:

where('? > ?', ...)

两个占位符都将被视为值(而不是标识符)并被引用。为什么是这样? ActiveRecord 无法知道哪个? 应该是标识符(例如created_at 列名),哪个应该是值(例如20:31:00.00)。

数据库连接确实有一个专门用于引用列名的方法:

> puts ActiveRecord::Base.connection.quote_column_name('pancakes')
"pancakes"
=> nil

所以你可以这样说:

quoted_column = Shift.connection.quote_column_name(column_name)
Shift.where("#{quoted_name}::time > ?", '20:31:00.00')

这有点令人不快,因为我们在使用字符串插值来构建 SQL 时会退缩(或者至少我们应该这样做)。但是,quote_column_name 会处理 column_name 中的任何可疑或不安全的事情,所以这实际上并不危险。

你也可以说:

quoted_column = "#{Shift.connection.quote_column_name(column_name)}::time"
Shift.where("#{quoted_name} > ?", '20:31:00.00')

如果您并不总是需要将列名转换为 time。甚至:

clause = "#{Shift.connection.quote_column_name(column_name)}::time > ?"
Shift.where(clause, '20:31:00.00')

您也可以使用extract or one of the other date/time functions 代替类型转换,但您仍然会遇到引用问题和有点令人畏惧的quote_column_name 调用。

另一种选择是将column_name 列入白名单,以便只允许特定的有效值。然后您可以将保险箱column_name 直接放入查询中:

if(!in_the_whitelist(column_name))
  # Throw a tantrum, hissy fit, or complain in your preferred fashion
end
Shift.where("#{column_name} > ?", '20:31:00.00')

这应该没问题,只要您没有像 "gotta have some breakfast" 这样的时髦列名或类似的东西总是需要正确引用。您甚至可以使用Shift.column_namesShift.columns 来建立您的白名单。

同时使用白名单和quote_column_name 可能是最安全的,但quote_column_name 方法应该就足够了。

【讨论】:

  • 这就是我最终要做的。插值对我来说感觉有点不对劲,尽管它是安全的,但最好的解决方案!
  • 正是我想要的。谢谢
  • 很好的解决方案,正是我所需要的。谢谢!
【解决方案2】:

我决定使用这个小解决方案,利用 Rails 列命名约定:

  scope :field_before_and_on_date, -> (field, time) do
    column_name = field.to_s.parameterize.underscore
    where("#{column_name} <= ?", time.end_of_day)
  end

# Takes advantage of:
> "); and delete everything(); stuff(".parameterize.underscore
=> "and_delete_everything_stuff"

它是有限的,但这个概念也适用于类型转换。

【讨论】:

  • quote_column_name 把它变成一个字符串&gt; ActiveRecord::Base.connection.quote_column_name("); and delete everything(); stuff(") # =&gt; "\"); and delete everything(); stuff(\""
猜你喜欢
  • 2020-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多