coercive 1.0.0 → 1.0.1

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
  SHA256:
3
- metadata.gz: d1b6a873b4a86ca962a22d60630a4de5355b278b529fccbd2615a794a7a94448
4
- data.tar.gz: 62f47d1f35c9d931935138872dace70a7c39d3eb090529f1f2886448dca2e4ad
3
+ metadata.gz: 867918b9a3ee09bdfc89a10e802032e229fdf4ab250f80727a68197f8144d615
4
+ data.tar.gz: 9d1dcfcc05db65d1669049c87f36bf9a252fe91ce68b9298a7578ce630aeae50
5
5
  SHA512:
6
- metadata.gz: d2d5f401306d119e14a761aa1937f6c5777f69458d9db1367d35bd2a02fcea8a2d41876db4331a0086af8ec49db00b3f892d3c76dbcbacd6d297edc836cfb3db
7
- data.tar.gz: e5e4ffc4e9e7874807a2b3af33fb3f846752acce0e9748d06c79247e54f7120ffb38ac5a764c33a629ee05009c62154864f41f975a7fa19985f77b96caa5d53e
6
+ metadata.gz: 2dc34142c2553b747290451db7ca631dd12b31e9e7488502e4a54b3fc1cf372a7dd498c951498f8c3b86d519172bb879fb7f650e97448260dd8fad2c5c5c8662
7
+ data.tar.gz: 513cb240285c58fbf784d3a61fae066bf7c768836cdc06281357b92f8c7a62ad72ea2c0114f3ba6645da29b4ed01fbabff616f10db91f93b8194c587d2f9c623
data/README.md CHANGED
@@ -1,10 +1,20 @@
1
- # coercive
1
+ # Coercive
2
+
3
+ # Install
4
+
5
+ ```
6
+ $ gem install coercive
7
+ ```
8
+
9
+ # Usage
2
10
 
3
11
  `Coercive` is a Ruby library to validate and coerce user input.
4
12
 
5
13
  Define your coercion modules like this:
6
14
 
7
15
  ```ruby
16
+ require "coercive"
17
+
8
18
  module CoerceFoo
9
19
  extend Coercive
10
20
 
@@ -184,7 +194,7 @@ CoerceFoo.call("foo" => [BasicObject.new])
184
194
 
185
195
  ### `hash`
186
196
 
187
- `hash` coercion let's you manipulate the key and values, similarly to how `array` does
197
+ `hash` coercion let's you manipulate the key and values, similarly to how `array` does.
188
198
 
189
199
  ```ruby
190
200
  module CoerceFoo
@@ -199,3 +209,7 @@ CoerceFoo.call("foo" => {"bar" => "0.1"})
199
209
  CoerceFoo.call("foo" => {"barrrr" => "0.1"})
200
210
  # => Coercive::Error: {"foo"=>{"barrrr"=>"too_long"}}
201
211
  ```
212
+
213
+ ### `uri`
214
+
215
+ The `uri` coercion validates
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "coercive"
3
+ s.version = "1.0.1"
4
+ s.summary = "Coercive is a library to validate and coerce user input"
5
+ s.description = s.summary
6
+ s.authors = ["Joe McIlvain", "Lucas Tolchinsky"]
7
+ s.email = ["joe.eli.mac@gmail.com", "tonchis@protonmail.com"]
8
+ s.homepage = "https://github.com/Theorem/coercive"
9
+ s.license = "MIT"
10
+
11
+ s.files = `git ls-files`.split("\n")
12
+ end
@@ -1,16 +1,12 @@
1
1
  require "ipaddr"
2
2
  require "uri"
3
3
 
4
- module Coercion
4
+ module Coercive
5
5
  module URI
6
- # Setting this `true` allows outbound connections to private IP addresses,
7
- # bypassing the security check that the IP address is public. This is designed
8
- # to be used in devlopment so that the tests can connect to local services.
9
- #
10
- # This SHOULD NOT be set in PRODUCTION.
11
- ALLOW_PRIVATE_IP_CONNECTIONS =
12
- ENV.fetch("ALLOW_PRIVATE_IP_CONNECTIONS", "").downcase == "true"
13
-
6
+ # The IP ranges below are considered private and by default not permitted by the `uri`
7
+ # coercion function. To allow connecting to local services (in development, for example)
8
+ # users can set the `allow_private_ip` option, which ignores if the URI resolves to a public
9
+ # address or not.
14
10
  PRIVATE_IP_RANGES = [
15
11
  IPAddr.new("0.0.0.0/8"), # Broadcasting to the current network. RFC 1700.
16
12
  IPAddr.new("10.0.0.0/8"), # Local private network. RFC 1918.
@@ -38,26 +34,27 @@ module Coercion
38
34
  # require_user - set true to make the URI user a required element
39
35
  # require_password - set true to make the URI password a required element
40
36
  def self.coerce_fn(string_coerce_fn, schema_fn: nil, require_path: false,
41
- require_port: false, require_user: false, require_password: false)
37
+ require_port: false, require_user: false, require_password: false,
38
+ allow_private_ip: false)
42
39
  ->(input) do
43
40
  uri = begin
44
41
  ::URI.parse(string_coerce_fn.call(input))
45
42
  rescue ::URI::InvalidURIError
46
- fail Coercion::Error.new("not_valid")
43
+ fail Coercive::Error.new("not_valid")
47
44
  end
48
45
 
49
- fail Coercion::Error.new("no_host") unless uri.host
50
- fail Coercion::Error.new("not_resolvable") unless resolvable_public_ip?(uri) || ALLOW_PRIVATE_IP_CONNECTIONS
51
- fail Coercion::Error.new("no_path") if require_path && uri.path.empty?
52
- fail Coercion::Error.new("no_port") if require_port && !uri.port
53
- fail Coercion::Error.new("no_user") if require_user && !uri.user
54
- fail Coercion::Error.new("no_password") if require_password && !uri.password
46
+ fail Coercive::Error.new("no_host") unless uri.host
47
+ fail Coercive::Error.new("not_resolvable") unless allow_private_ip || resolvable_public_ip?(uri)
48
+ fail Coercive::Error.new("no_path") if require_path && uri.path.empty?
49
+ fail Coercive::Error.new("no_port") if require_port && !uri.port
50
+ fail Coercive::Error.new("no_user") if require_user && !uri.user
51
+ fail Coercive::Error.new("no_password") if require_password && !uri.password
55
52
 
56
53
  if schema_fn
57
54
  begin
58
55
  schema_fn.call(uri.scheme)
59
- rescue Coercion::Error
60
- fail Coercion::Error.new("unsupported_schema")
56
+ rescue Coercive::Error
57
+ fail Coercive::Error.new("unsupported_schema")
61
58
  end
62
59
  end
63
60
 
@@ -113,7 +113,7 @@ describe "Coercive" do
113
113
  fixnum = 2
114
114
  rational = 2 ** -2
115
115
  bignum = 2 ** 64
116
- bigdecimal = BigDecimal.new("0.1")
116
+ bigdecimal = BigDecimal("0.1")
117
117
 
118
118
  [fixnum, rational, bignum, bigdecimal].each do |value|
119
119
  attributes = { "foo" => value }
@@ -11,7 +11,7 @@ describe "Coercive::URI" do
11
11
  assert_equal errors, e.errors
12
12
  end
13
13
 
14
- setup do
14
+ before do
15
15
  @coercion = Module.new do
16
16
  extend Coercive
17
17
 
@@ -39,10 +39,14 @@ describe "Coercive::URI" do
39
39
  attribute :require_password,
40
40
  uri(string(min: 1, max: 255), require_password: true),
41
41
  optional
42
+
43
+ attribute :allow_private_ip,
44
+ uri(string(min: 1, max: 255), allow_private_ip: true),
45
+ optional
42
46
  end
43
47
  end
44
48
 
45
- test "coerces a valid string to a URI" do
49
+ it "coerces a valid string to a URI" do
46
50
  attributes = {
47
51
  "any" => "http://user:pass@www.example.com:1234/path"
48
52
  }
@@ -50,7 +54,7 @@ describe "Coercive::URI" do
50
54
  assert_equal attributes, @coercion.call(attributes)
51
55
  end
52
56
 
53
- test "errors if input is an invalid URI" do
57
+ it "errors if input is an invalid URI" do
54
58
  attributes = { "any" => "%" }
55
59
 
56
60
  expected_errors = { "any" => "not_valid" }
@@ -58,7 +62,7 @@ describe "Coercive::URI" do
58
62
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
59
63
  end
60
64
 
61
- test "errors if the input is longer than the declared maximum size" do
65
+ it "errors if the input is longer than the declared maximum size" do
62
66
  attributes = {
63
67
  "min" => "http://foo.com",
64
68
  "max" => "http://long.url.com",
@@ -70,7 +74,7 @@ describe "Coercive::URI" do
70
74
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
71
75
  end
72
76
 
73
- test "errors if the input is shorter than the declared minimum size" do
77
+ it "errors if the input is shorter than the declared minimum size" do
74
78
  attributes = {
75
79
  "min" => "http://a.com",
76
80
  "max" => "http://bar.com",
@@ -82,14 +86,14 @@ describe "Coercive::URI" do
82
86
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
83
87
  end
84
88
 
85
- test "errors if the URI is an empty string" do
89
+ it "errors if the URI is an empty string" do
86
90
  attributes = { "schema" => "" }
87
91
  expected_errors = { "schema" => "is_empty" }
88
92
 
89
93
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
90
94
  end
91
95
 
92
- test "errors if no host" do
96
+ it "errors if no host" do
93
97
  attributes = { "any" => "http://" }
94
98
 
95
99
  expected_errors = { "any" => "no_host" }
@@ -97,7 +101,7 @@ describe "Coercive::URI" do
97
101
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
98
102
  end
99
103
 
100
- test "errors if schema is not supported" do
104
+ it "errors if schema is not supported" do
101
105
  attributes = { "schema" => "foo://example.com" }
102
106
 
103
107
  expected_errors = { "schema" => "unsupported_schema" }
@@ -105,7 +109,7 @@ describe "Coercive::URI" do
105
109
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
106
110
  end
107
111
 
108
- test "errors if required elements are not provided" do
112
+ it "errors if required elements are not provided" do
109
113
  attributes = {
110
114
  "require_path" => "foo://example.com",
111
115
  "require_port" => "foo://example.com",
@@ -130,7 +134,7 @@ describe "Coercive::URI" do
130
134
  first = first.ipv6? ? "[#{first}]" : first.to_s
131
135
  last = last.ipv6? ? "[#{last}]" : last.to_s
132
136
 
133
- test "errors when the URI host is an IP in the range #{first}..#{last}" do
137
+ it "errors when the URI host is an IP in the range #{first}..#{last}" do
134
138
  attributes_first = { "schema" => "http://#{first}/path" }
135
139
  attributes_last = { "schema" => "http://#{last}/path" }
136
140
  expected_errors = { "schema" => "not_resolvable" }
@@ -138,9 +142,17 @@ describe "Coercive::URI" do
138
142
  assert_coercion_error(expected_errors) { @coercion.call(attributes_first) }
139
143
  assert_coercion_error(expected_errors) { @coercion.call(attributes_last) }
140
144
  end
145
+
146
+ it "allows overriding private IP address checks" do
147
+ attributes_first = { "allow_private_ip" => "http://#{first}/path" }
148
+ attributes_last = { "allow_private_ip" => "http://#{last}/path" }
149
+
150
+ assert_equal attributes_first, @coercion.call(attributes_first)
151
+ assert_equal attributes_last, @coercion.call(attributes_last)
152
+ end
141
153
  end
142
154
 
143
- test "errors when the URI host is not resolvable" do
155
+ it "errors when the URI host is not resolvable" do
144
156
  attributes = {
145
157
  "schema" => "http://bogus-host-that-cant-possibly-exist-here/path"
146
158
  }
@@ -150,7 +162,7 @@ describe "Coercive::URI" do
150
162
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
151
163
  end
152
164
 
153
- test "errors when the URI host resolves to an IP in a private range" do
165
+ it "errors when the URI host resolves to an IP in a private range" do
154
166
  attributes = { "schema" => "http://localhost/path" }
155
167
 
156
168
  expected_errors = { "schema" => "not_resolvable" }
@@ -158,32 +170,32 @@ describe "Coercive::URI" do
158
170
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
159
171
  end
160
172
 
161
- test "allows a URI host to be IP that isn't in a private range" do
173
+ it "allows a URI host to be IP that isn't in a private range" do
162
174
  attributes = { "schema" => "http://8.8.8.8/path" }
163
175
 
164
176
  assert_equal attributes, @coercion.call(attributes)
165
177
  end
166
178
 
167
- test "allows a URI host that resolves to an IP not in a private range" do
179
+ it "allows a URI host that resolves to an IP not in a private range" do
168
180
  attributes = { "schema" => "http://www.example.com/path" }
169
181
 
170
182
  assert_equal attributes, @coercion.call(attributes)
171
183
  end
172
184
 
173
- test "allows a URI with no explicit path component" do
185
+ it "allows a URI with no explicit path component" do
174
186
  attributes = { "schema" => "http://www.example.com" }
175
187
 
176
188
  assert_equal attributes, @coercion.call(attributes)
177
189
  end
178
190
 
179
- test "errors for a string that does not pass URI.parse" do
191
+ it "errors for a string that does not pass URI.parse" do
180
192
  attributes = { "schema" => "\\" }
181
193
  expected_errors = { "schema" => "not_valid" }
182
194
 
183
195
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
184
196
  end
185
197
 
186
- test "errors for a URL that passes URI.parse, but is ill-formed" do
198
+ it "errors for a URL that passes URI.parse, but is ill-formed" do
187
199
  attributes = { "schema" => "http:example.com/path" }
188
200
 
189
201
  begin
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coercive
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe McIlvain
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-04 00:00:00.000000000 Z
12
+ date: 2020-08-05 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Coercive is a library to validate and coerce user input
15
15
  email:
@@ -21,6 +21,7 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - LICENSE
23
23
  - README.md
24
+ - coercive.gemspec
24
25
  - lib/coercive.rb
25
26
  - lib/coercive/uri.rb
26
27
  - test/coercive.rb