Skip to content

Commit

Permalink
Build SQL with Arel instead of from strings (#2418)
Browse files Browse the repository at this point in the history
In Administrate::Order, there are several places where we are building
SQL expressions using string interpolation, e.g. order =
"#{relation.table_name}.#{attribute} #{direction}". Doing this can be
dangerous, because it's easy to introduce a SQL injection vulnerability.
Thankfully, it seems like this class is carefully designed in a way that
prevents such a vulnerability. Nevertheless, it feels precarious; if
someone makes changes, they have to be very careful to keep the code
safe.

This change replaces all string-interpolated SQL expressions with Arel
expressions. The database adapter will then automatically quote &
sanitize the components of these expressions.
  • Loading branch information
thoughtbot-summer authored Dec 12, 2023
1 parent 8e7bb22 commit 0f27027
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 22 deletions.
21 changes: 11 additions & 10 deletions lib/administrate/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ def apply(relation)
return order_by_association(relation) unless
reflect_association(relation).nil?

order = "#{relation.table_name}.#{attribute} #{direction}"
order = relation.arel_table[attribute].public_send(direction)

return relation.reorder(Arel.sql(order)) if
return relation.reorder(order) if
column_exist?(relation, attribute)

relation
Expand Down Expand Up @@ -66,11 +66,11 @@ def order_by_association(relation)

def order_by_count(relation)
klass = reflect_association(relation).klass
query = "COUNT(#{klass.table_name}.#{klass.primary_key}) #{direction}"
query = klass.arel_table[klass.primary_key].count.public_send(direction)
relation.
left_joins(attribute.to_sym).
group(:id).
reorder(Arel.sql(query))
reorder(query)
end

def order_by_belongs_to(relation)
Expand All @@ -92,15 +92,15 @@ def order_by_has_one(relation)
def order_by_attribute(relation)
relation.joins(
attribute.to_sym,
).reorder(Arel.sql(order_by_attribute_query))
).reorder(order_by_attribute_query)
end

def order_by_id(relation)
relation.reorder(Arel.sql(order_by_id_query(relation)))
relation.reorder(order_by_id_query(relation))
end

def order_by_association_id(relation)
relation.reorder(Arel.sql(order_by_association_id_query))
relation.reorder(order_by_association_id_query)
end

def ordering_by_association_column?(relation)
Expand All @@ -115,15 +115,16 @@ def column_exist?(table, column_name)
end

def order_by_id_query(relation)
"#{relation.table_name}.#{foreign_key(relation)} #{direction}"
relation.arel_table[foreign_key(relation)].public_send(direction)
end

def order_by_association_id_query
"#{association_table_name}.id #{direction}"
Arel::Table.new(association_table_name)[:id].public_send(direction)
end

def order_by_attribute_query
"#{association_table_name}.#{association_attribute} #{direction}"
table = Arel::Table.new(association_table_name)
table[association_attribute].public_send(direction)
end

def relation_type(relation)
Expand Down
38 changes: 26 additions & 12 deletions spec/lib/administrate/order_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "active_record"
require "rails_helper"
require "administrate/order"

describe Administrate::Order do
Expand Down Expand Up @@ -37,7 +37,9 @@

ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with("table_name.name asc")
expect(relation).to have_received(:reorder).with(
to_sql('"table_name"."name" ASC'),
)
expect(ordered).to eq(relation)
end

Expand All @@ -48,7 +50,9 @@

ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with("table_name.name desc")
expect(relation).to have_received(:reorder).with(
to_sql('"table_name"."name" DESC'),
)
expect(ordered).to eq(relation)
end

Expand All @@ -59,7 +63,9 @@

ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with("table_name.name asc")
expect(relation).to have_received(:reorder).with(
to_sql('"table_name"."name" ASC'),
)
expect(ordered).to eq(relation)
end
end
Expand All @@ -69,7 +75,11 @@
order = Administrate::Order.new(:name)
relation = relation_with_association(
:has_many,
klass: double(table_name: "users", primary_key: "uid"),
klass: double(
table_name: "users",
arel_table: Arel::Table.new("users"),
primary_key: "uid",
),
)
allow(relation).to receive(:reorder).and_return(relation)
allow(relation).to receive(:left_joins).and_return(relation)
Expand All @@ -79,7 +89,9 @@

expect(relation).to have_received(:left_joins).with(:name)
expect(relation).to have_received(:group).with(:id)
expect(relation).to have_received(:reorder).with("COUNT(users.uid) asc")
expect(relation).to have_received(:reorder).with(
to_sql('COUNT("users"."uid") ASC'),
)
expect(ordered).to eq(relation)
end
end
Expand All @@ -96,7 +108,7 @@
ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with(
"table_name.some_foreign_key asc",
to_sql('"table_name"."some_foreign_key" ASC'),
)
expect(ordered).to eq(relation)
end
Expand All @@ -120,7 +132,7 @@

ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
"users.name asc",
to_sql('"users"."name" ASC'),
)
expect(ordered).to eq(relation)
end
Expand All @@ -146,7 +158,7 @@
ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with(
"table_name.belongs_to_id asc",
to_sql('"table_name"."belongs_to_id" ASC'),
)
expect(ordered).to eq(relation)
end
Expand All @@ -164,7 +176,7 @@
ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with(
"users.id asc",
to_sql('"users"."id" ASC'),
)
expect(ordered).to eq(relation)
end
Expand All @@ -188,7 +200,7 @@

ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
"users.name asc",
to_sql('"users"."name" ASC'),
)
expect(ordered).to eq(relation)
end
Expand All @@ -214,7 +226,7 @@
ordered = order.apply(relation)

expect(relation).to have_received(:reorder).with(
"users.id asc",
to_sql('"users"."id" ASC'),
)
expect(ordered).to eq(relation)
end
Expand Down Expand Up @@ -311,6 +323,7 @@ def relation_with_column(column)
klass: double(reflect_on_association: nil),
columns_hash: { column.to_s => :column_info },
table_name: "table_name",
arel_table: Arel::Table.new("table_name"),
)
end

Expand All @@ -329,6 +342,7 @@ def relation_with_association(
),
),
table_name: "table_name",
arel_table: Arel::Table.new("table_name"),
)
end
end
5 changes: 5 additions & 0 deletions spec/support/matchers/to_sql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RSpec::Matchers.define :to_sql do |sql|
match do |actual|
actual.to_sql == sql
end
end

0 comments on commit 0f27027

Please sign in to comment.