Patryk
Patryk

Reputation: 197

Rspec is not catching error StatementInvalid

I'm trying to defend myself from SQL injection, I'm using variable as column name in #where:

class BuildSegment::FindUsers

  def initialize(params)
    @key = User.connection.quote_column_name(params[:key])
    @negative = params[:negative]
    @pattern = params[:pattern]
  end

  def call
    if @negative
      users = User.where.not("#{@key} LIKE ?", @pattern)
    else
      users = User.where("#{@key} LIKE ?", @pattern)
    end

    users.uniq
  end
end

Variable @key is taken from Hash which may be manipulated by user. So, for example if @key = "email LIKE '%' OR email" and I'm passing @key directly to Active Record query, I get:

SELECT "users".* FROM "users" WHERE (email LIKE '%' OR email LIKE '')

which returns all users. I found quote_column_name(params[:key]) as a solution which returns ActiveRecord::StatementInvalid error:

ActiveRecord::StatementInvalid:
       PG::UndefinedColumn: ERROR:  column "email LIKE '%com%' OR email" does not exist
       LINE 1: SELECT DISTINCT "users".* FROM "users" WHERE ("email LIKE '%...
                                                             ^
       : SELECT DISTINCT "users".* FROM "users" WHERE ("email LIKE '%com%' OR email" LIKE '')

This test, however, is failing:

expect(segment_builder.call).to raise_error(ActiveRecord::StatementError)

And full backtrace:

Failures:

  1) BuildSegment is protected from SQL injection
     Failure/Error: users_to_add = users.flatten

     ActiveRecord::StatementInvalid:
       PG::UndefinedColumn: ERROR:  column "email LIKE '%com%' OR email" does not exist
       LINE 1: SELECT DISTINCT "users".* FROM "users" WHERE ("email LIKE '%...
                                                             ^
       : SELECT DISTINCT "users".* FROM "users" WHERE ("email LIKE '%com%' OR email" LIKE '')
     # ./app/services/build_segment.rb:51:in `flatten'
     # ./app/services/build_segment.rb:51:in `users_passing_all_rules'
     # ./app/services/build_segment.rb:46:in `users_passing_filter'
     # ./app/services/build_segment.rb:32:in `block in users_meeting_requirements_for'
     # ./app/services/build_segment.rb:31:in `each'
     # ./app/services/build_segment.rb:31:in `users_meeting_requirements_for'
     # ./app/services/build_segment.rb:8:in `call'
     # ./spec/services/build_segment_spec.rb:107:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # PG::UndefinedColumn:
     #   ERROR:  column "email LIKE '%com%' OR email" does not exist
     #   LINE 1: SELECT DISTINCT "users".* FROM "users" WHERE ("email LIKE '%...
     #                                                         ^
     #   ./app/services/build_segment.rb:51:in `flatten'

Where could be my error?

Upvotes: 0

Views: 933

Answers (2)

sevenseacat
sevenseacat

Reputation: 25029

Two issues with your code:

1) The error is ActiveRecord::StatementInvalid, not ActiveRecord::StatementError, and

2) You need to use the block syntax of expect {} to catch exceptions. See https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/raise-error-matcher for more details.

Upvotes: 1

Dan Rubio
Dan Rubio

Reputation: 4907

It looks to me like you are not escaping your SQL correctly.

See if this works for you:

User.where.not("#{@key} LIKE ?, '#{@pattern}'")

User.where("#{@key} LIKE ?, '#{@pattern}'")

Upvotes: 0

Related Questions