order_as_specified 1.1 → 1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3348c26ab12edd7c9a4066c710b0cc914974946
4
- data.tar.gz: 10b274073cfa67a6237afe207f3df7ae7141fddf
3
+ metadata.gz: 69bf1783284bb4738f7c5a3d189e122d3f5f00a2
4
+ data.tar.gz: 6c9f53fe8658d25a1d44dc9cd120d8eda8d62537
5
5
  SHA512:
6
- metadata.gz: 4a8cb8f25341c51ede2a157a997c41bafe0f4c1ba2174e727f7c9f4b35ffb39f86c5d229cd614a25967eda34e731ae9704c2f372f12d77a9bda5a2261b67ea45
7
- data.tar.gz: 92fce24b220e4a658da7eb7c6be5520f532935806793f2d145b190e4db7b85190d7b39bc14f4c6782e79b708ff1433c616623d760a56cd7d46f78438dd8c7a1e
6
+ metadata.gz: 9f971220e0601e197de6ca6d16e5275db283f9d4c895953801220c527725242dd6a6401e94d73346555b4e0c7548893403dfc6538a91d8db9baf796db6aa13fa
7
+ data.tar.gz: 8f9dba4933b6c521c4a7dbfdbac6c7a23e2bd374151862ee1166ef2c916ebfa2dcf1a02975f3d51ec0abff5ca27002ec7ea5eb143546480cc3c21bdf50166777
data/CHANGELOG.md CHANGED
@@ -1,6 +1,24 @@
1
+ # 1.2
2
+
3
+ This release contains an important change: `order_as_specified` is no longer
4
+ vulnerable to SQL injection attacks. Big thanks to
5
+ [Anthony Mangano](https://github.com/wangteji) for finding and fixing this
6
+ issue.
7
+
8
+ In addition, this release corrects an error in the `.gemspec`—this gem relies on
9
+ ActiveRecord >= 4.0.1, not 4.0.0.
10
+
11
+ # 1.1
12
+
13
+ This release contains several minor changes:
14
+
15
+ - Tests now run against Ruby 2.2.5 and 2.3.1 and Rails 4.2 and 5.0.
16
+ - A code change was made to fix a Rails 5 deprecation warning.
17
+
1
18
  # 1.0
2
19
 
3
- We've hit our first major release! This release contains **no** breaking changes. The changes:
20
+ We've hit our first major release! This release contains **no** breaking
21
+ changes. The changes:
4
22
 
5
23
  - Added `:distinct_on` option. [[#3](https://github.com/panorama-ed/order_as_specified/issues/3)]
6
24
  - Improved error message for `nil` arguments. [[#8](https://github.com/panorama-ed/order_as_specified/pull/8)]
data/README.md CHANGED
@@ -80,6 +80,42 @@ TestObject.
80
80
  ]>
81
81
  ```
82
82
 
83
+ We can use chaining in this way to order by multiple attributes as well:
84
+
85
+ ```ruby
86
+ TestObject.
87
+ order_as_specified(language: ["es", "en"]).
88
+ order_as_specified(id: [4, 3, 5]).
89
+ order(:updated_at)
90
+ => #<ActiveRecord::Relation [
91
+
92
+ # First is language "es"...
93
+ #<TestObject id: 1, language: "es", updated_at: "2016-08-01 02:22:00">,
94
+
95
+ # Within the language, we order by :updated_at...
96
+ #<TestObject id: 2, language: "es", updated_at: "2016-08-01 07:29:07">,
97
+
98
+ # Then language "en"...
99
+ #<TestObject id: 9, language: "en", updated_at: "2016-08-03 04:11:26">,
100
+
101
+ # Within the language, we order by :updated_at...
102
+ #<TestObject id: 8, language: "en", updated_at: "2016-08-04 18:52:14">,
103
+
104
+ # Then id 4...
105
+ #<TestObject id: 4, language: "fr", updated_at: "2016-08-01 12:59:33">,
106
+
107
+ # Then id 3...
108
+ #<TestObject id: 3, language: "ar", updated_at: "2016-08-02 19:41:44">,
109
+
110
+ # Then id 5...
111
+ #<TestObject id: 5, language: "ar", updated_at: "2016-08-02 22:12:52">,
112
+
113
+ # Then we order by :updated_at...
114
+ #<TestObject id: 7, language: "fr", updated_at: "2016-08-02 14:27:16">,
115
+ #<TestObject id: 6, language: "ar", updated_at: "2016-08-03 14:26:06">,
116
+ ]>
117
+ ```
118
+
83
119
  We can also use this when we want to sort by an attribute in another model:
84
120
 
85
121
  ```ruby
@@ -1,3 +1,3 @@
1
1
  module OrderAsSpecified
2
- VERSION = "1.1"
2
+ VERSION = "1.2"
3
3
  end
@@ -13,8 +13,8 @@ module OrderAsSpecified
13
13
  distinct_on = hash.delete(:distinct_on)
14
14
  params = extract_params(hash)
15
15
 
16
- table = params[:table]
17
- attribute = params[:attribute]
16
+ table = connection.quote_table_name(params[:table])
17
+ attribute = connection.quote_column_name(params[:attribute])
18
18
 
19
19
  # We have to explicitly quote for now because SQL sanitization for ORDER BY
20
20
  # queries isn't in less current versions of Rails.
@@ -22,7 +22,8 @@ module OrderAsSpecified
22
22
  conditions = params[:values].map do |value|
23
23
  raise OrderAsSpecified::Error, "Cannot order by `nil`" if value.nil?
24
24
 
25
- "#{table}.#{attribute}='#{value}'"
25
+ # Sanitize each value to reduce the risk of SQL injection.
26
+ "#{table}.#{attribute}=#{sanitize(value)}"
26
27
  end
27
28
 
28
29
  scope = order(conditions.map { |cond| "#{cond} DESC" }.join(", "))
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "activerecord", ">= 4.0"
22
+ spec.add_dependency "activerecord", ">= 4.0.1"
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.7"
25
25
  spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4"
@@ -28,5 +28,31 @@ RSpec.describe "PostgreSQL" do
28
28
  it "returns distinct objects" do
29
29
  expect(subject.length).to eq 3
30
30
  end
31
+
32
+ context "input safety" do
33
+ before(:each) { TestClass.create(field: "foo") }
34
+
35
+ it "sanitizes column values" do
36
+ # Attempt to inject code to add a 'hi' field to each record. If the SQL inputs
37
+ # are properly sanitized, the code will be ignored and the returned model
38
+ # instances will not respond to #hi. If not, the code will execute and
39
+ # each of the returned model instances will have a #hi method to access the
40
+ # new field.
41
+ bad_value = "foo') field, 'hi'::varchar AS hi FROM test_classes --"
42
+ record = TestClass.order_as_specified(distinct_on: true, field: [bad_value]).to_a.first
43
+ expect(record).to_not respond_to(:hi)
44
+ end
45
+
46
+ it "quotes column and table names" do
47
+ table = "association_test_classes"
48
+ quoted_table = AssociationTestClass.connection.quote_table_name(table)
49
+
50
+ column = "id"
51
+ quoted_column = AssociationTestClass.connection.quote_column_name(column)
52
+
53
+ sql = TestClass.order_as_specified(distinct_on: true, table => { column => ["foo"] }).to_sql
54
+ expect(sql).to include("DISTINCT ON (#{quoted_table}.#{quoted_column}")
55
+ end
56
+ end
31
57
  end
32
58
  end
@@ -30,6 +30,24 @@ RSpec.shared_examples ".order_as_specified" do
30
30
  to eq [*shuffled_object_ids, omitted_object.id]
31
31
  end
32
32
 
33
+ context "when the order is chained with other orderings" do
34
+ subject do
35
+ TestClass.
36
+ order_as_specified(field: shuffled_object_fields.take(3)).
37
+ order(:id)
38
+ end
39
+
40
+ it "returns results in the given order by multiple fields" do
41
+ shuffled_objects # Build these objects first.
42
+ omitted_object # Build an object that isn't sorted in this list.
43
+ expect(subject.map(&:id)).to eq [
44
+ *shuffled_object_ids.take(3),
45
+ *shuffled_object_ids.drop(3).sort,
46
+ omitted_object.id
47
+ ]
48
+ end
49
+ end
50
+
33
51
  context "when the order includes nil" do
34
52
  let(:shuffled_objects) do
35
53
  5.times.map do |i|
@@ -77,4 +95,36 @@ RSpec.shared_examples ".order_as_specified" do
77
95
  to eq [*shuffled_object_ids, omitted_object.id]
78
96
  end
79
97
  end
98
+
99
+ context "input safety" do
100
+ before(:each) do
101
+ 2.times { |i| TestClass.create(field: "foo#{i}") }
102
+ end
103
+
104
+ it "sanitizes column values" do
105
+ # Verify that the result set includes two records when using good column value.
106
+ good_value = "foo"
107
+ records = TestClass.order_as_specified(field: [good_value]).to_a
108
+ expect(records.count).to eq(2)
109
+
110
+ # Attempt to inject a LIMIT clause into the query. If the SQL inputs are
111
+ # properly sanitized, it will be ignored and the returned result set will
112
+ # include two records. If not, the LIMIT clause will execute and the result
113
+ # set will include just one record.
114
+ bad_value = "' LIMIT 1 --"
115
+ records = TestClass.order_as_specified(field: [bad_value]).to_a
116
+ expect(records.count).to eq(2)
117
+ end
118
+
119
+ it "quotes column and table names" do
120
+ table = "association_test_classes"
121
+ quoted_table = AssociationTestClass.connection.quote_table_name(table)
122
+
123
+ column = "id"
124
+ quoted_column = AssociationTestClass.connection.quote_column_name(column)
125
+
126
+ sql = TestClass.order_as_specified(table => { column => ["foo"] }).to_sql
127
+ expect(sql).to include("ORDER BY #{quoted_table}.#{quoted_column}")
128
+ end
129
+ end
80
130
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: order_as_specified
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Evelyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-13 00:00:00.000000000 Z
11
+ date: 2016-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: 4.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
- version: '4.0'
26
+ version: 4.0.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement