Dangerous query method called with non-attribute argument(s)

踩坑 query method。

问题描述

现有model issue,需要对issues进行排序,根据指定的ID集合来决定记录的位置,比如id包含在(4, 6, 9)中的纪录就排在前面,剩下的排在后面。

使用scope进行处理:

class Issue < ApplicationRecord
  .........
  IDLIST = [4, 6, 9]
  scope :order_by_custom_id, -> {
     order_by = ['CASE']
    order_by << "WHEN id in (#{IDLIST.join(', ')}) THEN 0"
    order_by << 'ELSE 1 END'
    order(order_by.join(' '))
  }
end

在controller中调用了该scope。刷新页面后,发现服务器报出warning message:

DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "CASE WHEN id in (4,6,9) THEN 0 ELSE 1 END". Non-attribute arguments will be disallowed in Rails 6.0. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql().

解决方法

其实,报错信息里面已经告诉我解决方法了,但是当时一看懵逼,Dangerous query method?!直接Google了一堆,后面在slack上跟同事求助了下,搞定!

很简单,就是使用warning message里面提到的Arel.sql()方法,将CASE WHEN id in (4,6,9) THEN 0 ELSE 1 END 用Arel.sql() 包起来即可。

修改scope:

class Issue < ApplicationRecord
  .........
  IDLIST = [4, 6, 9]
  scope :order_by_custom_id, -> {
     order_by = ['CASE']
    order_by << "WHEN id in (#{IDLIST.join(', ')}) THEN 0"
    order_by << 'ELSE 1 END'
    order(Arel.sql(order_by.join(' ')))
  }
end

OK!

但是,这个Arel.sql(raw_sql)是什么?

先看看Arel是什么。

官方文档的解释:

Arel is a SQL AST manager for Ruby. It

  • Simplifies the generation of complex SQL queries
  • Adapts to various RDBMSes

文档里面还列出了很多例子,从简单的select * from users 到复杂点的joins table,我猜是因为我接触Rails的时间不长,反正是从来没用过这东西。

Arel.sql(raw_sql) 这个类方法的源代码如下:

def self.sql raw_sql
  Arel::Nodes::SqlLiteral.new raw_sql
end

也就是新建了一个Arel::Nodes::SqlLiteral实例,而Arel::Nodes::SqlLiteral为什么是安全的呢?用Arel::Nodes::SqlLiteral 把raw_sql包起来的用意是什么?估计也能猜到,防攻击啥的。果然,Google了下,发现说是可以防止SQL注入的情况。

有些复杂了,脑子暂时消化不了。

此外,文档中有关sanitize_limit 部分也有提到SQL injection的问题。

参考

Arel

A “strict Arel” mode for ActiveRecord to prevent SQL injection vulnerabilities