order_as_specified 1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -1
- data/README.md +36 -0
- data/lib/order_as_specified/version.rb +1 -1
- data/lib/order_as_specified.rb +4 -3
- data/order_as_specified.gemspec +1 -1
- data/spec/postgresql_spec.rb +26 -0
- data/spec/shared/order_as_specified_examples.rb +50 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69bf1783284bb4738f7c5a3d189e122d3f5f00a2
|
4
|
+
data.tar.gz: 6c9f53fe8658d25a1d44dc9cd120d8eda8d62537
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
data/lib/order_as_specified.rb
CHANGED
@@ -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
|
-
|
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(", "))
|
data/order_as_specified.gemspec
CHANGED
@@ -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"
|
data/spec/postgresql_spec.rb
CHANGED
@@ -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.
|
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-
|
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:
|
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:
|
26
|
+
version: 4.0.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|