roda 3.42.0 → 3.43.0

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: e2bdd27a69c76000c9c1d2d8f37f9919145fdae55224706c78adec42850c5d21
4
- data.tar.gz: 8b92e1dbc9ec83ebca5d2d5b068c67fa282dfee25fe241451c93228a406a4749
3
+ metadata.gz: 6bca86a88a2d0e6fc0952ec2b2a91aa2e4d6dc0374546d5ac9d74d5b39a30abc
4
+ data.tar.gz: 20044ea95c1b1efabc06d8cda365384193c33ddd8140c7c91f9977483975e004
5
5
  SHA512:
6
- metadata.gz: 5c9f2d3f0f021b7b0a16628cefe28c4d4001f2a80804f2bde9b48b641a70482a5d13d699252cb9f7c747de50710b91df30f893f83263e3e73c874621948b4cc6
7
- data.tar.gz: c060f123bfb7bd0f02a83d864169d7d2a021c14cd152b8f3e7641c4c7e0317affb84fce0812afcb0d382e0828e31e2fd97b46482338ae92574fbad97793c8996
6
+ metadata.gz: 2281aac1898e6a81c114023fefb15f77c14ad1ebcd37daad10a1f5c51815c0d175c68a052627bb5025e02c92821d6c64cf6eb3757d801b277b771e73daddc20f
7
+ data.tar.gz: a55217f87afb2989312e62804715b65c8213997699b187f52a78f8a96dfb592a62ee7adeb029a52663631887ef5cdd15c746930cf9dfba437b744df159b7090c
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ = 3.43.0 (2021-04-12)
2
+
3
+ * Add host_authorization plugin, for checking that requests are submitted using an approved host (jeremyevans)
4
+
1
5
  = 3.42.0 (2021-03-12)
2
6
 
3
7
  * Make Roda.plugin support plugins using keyword arguments in Ruby 3 (jeremyevans)
@@ -0,0 +1,34 @@
1
+ = New Features
2
+
3
+ * A host_authorization plugin has been added to verify the requested
4
+ Host header is authorized. Using it can prevent DNS rebinding
5
+ attacks in cases where the application can receive requests for
6
+ arbitrary hosts.
7
+
8
+ To check for authorized hosts in your routing tree, you call the
9
+ check_host_authorization! method. For example, if you want to
10
+ check for authorized hosts after serving requests for public
11
+ files, you could do:
12
+
13
+ plugin :public
14
+ plugin :host_authorization, 'my-domain-name.example.com'
15
+
16
+ route do |r|
17
+ r.public
18
+ check_host_authorized!
19
+
20
+ # ... rest of routing tree
21
+ end
22
+
23
+ In addition to handling single domain names via a string, you can
24
+ provide an array of domain names, a regexp to match again, or a
25
+ proc.
26
+
27
+ By default, requests using unauthorized hosts receive an empty 403
28
+ response. If you would like to customize the response, you can
29
+ pass a block when loading the plugin:
30
+
31
+ plugin :host_authorization, 'my-domain-name.example.com' do |r|
32
+ response.status = 403
33
+ "Response Body Here"
34
+ end
@@ -0,0 +1,156 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The host_authorization plugin allows configuring an authorized host or
7
+ # an array of authorized hosts. Then in the routing tree, you can check
8
+ # whether the request uses an authorized host via the +check_host_authorized!+
9
+ # method.
10
+ #
11
+ # If the request doesn't match one of the authorized hosts, the
12
+ # request processing stops at that point. Using this plugin can prevent
13
+ # DNS rebinding attacks if the application can receive requests for
14
+ # arbitrary hosts.
15
+ #
16
+ # By default, an empty response using status 403 will be returned for requests
17
+ # with unauthorized hosts.
18
+ #
19
+ # Because +check_host_authorized!+ is an instance method, you can easily choose
20
+ # to only check for authorization in certain routes, or to check it after
21
+ # other processing. For example, you could check for authorized hosts after
22
+ # serving static files, since the serving of static files should not be
23
+ # vulnerable to DNS rebinding attacks.
24
+ #
25
+ # = Usage
26
+ #
27
+ # In your routing tree, call the +check_host_authorized!+ method at the point you
28
+ # want to check for authorized hosts:
29
+ #
30
+ # plugin :host_authorization, 'www.example.com'
31
+ # plugin :public
32
+ #
33
+ # route do |r|
34
+ # r.public
35
+ # check_host_authorized!
36
+ #
37
+ # # ...
38
+ # end
39
+ #
40
+ # = Specifying authorized hosts
41
+ #
42
+ # For applications hosted on a single domain name, you can use a single string:
43
+ #
44
+ # plugin :host_authorization, 'www.example.com'
45
+ #
46
+ # For applications hosted on multiple domain names, you can use an array of strings:
47
+ #
48
+ # plugin :host_authorization, %w'www.example.com www.example2.com'
49
+ #
50
+ # For applications supporting arbitrary subdomains, you can use a regexp. If using
51
+ # a regexp, make sure you use <tt>\A<tt> and <tt>\z</tt> in your regexp, and restrict
52
+ # the allowed characters to the minimum required, otherwise you can potentionally
53
+ # introduce a security issue:
54
+ #
55
+ # plugin :host_authorization, /\A[-0-9a-f]+\.example\.com\z/
56
+ #
57
+ # For applications with more complex requirements, you can use a proc. Similarly
58
+ # to the regexp case, the proc should be aware the host contains user-submitted
59
+ # values, and not assume it is in any particular format:
60
+ #
61
+ # plugin :host_authorization, proc{|host| ExternalService.allowed_host?(host)}
62
+ #
63
+ # If an array of values is passed as the host argument, the host is authorized if
64
+ # it matches any value in the array. All host authorization checks use the
65
+ # <tt>===</tt> method, which is why it works for strings, regexps, and procs.
66
+ # It can also work with arbitrary objects that support <tt>===</tt>.
67
+ #
68
+ # For security reasons, only the +Host+ header is checked by default. If you are
69
+ # sure that your application is being run behind a forwarding proxy that sets the
70
+ # <tt>X-Forwarded-Host</tt> header, you should enable support for checking that
71
+ # header using the +:check_forwarded+ option:
72
+ #
73
+ # plugin :host_authorization, 'www.example.com', check_forwarded: true
74
+ #
75
+ # In this case, the trailing host in the <tt>X-Forwarded-Host</tt> header is checked,
76
+ # which should be the host set by the forwarding proxy closest to the application.
77
+ # In cases where multiple forwarding proxies are used that append to the
78
+ # <tt>X-Forwarded-Host</tt> header, you should not use this plugin.
79
+ #
80
+ # = Customizing behavior
81
+ #
82
+ # By default, an unauthorized host will receive an empty 403 response. You can
83
+ # customize this by passing a block when loading the plugin. For example, for
84
+ # sites using the render plugin, you could return a page that uses your default
85
+ # layout:
86
+ #
87
+ # plugin :render
88
+ # plugin :host_authorization, 'www.example.com' do |r|
89
+ # response.status = 403
90
+ # view(:content=>"<h1>Forbidden</h1>")
91
+ # end
92
+ #
93
+ # The block passed to this plugin is treated as a match block.
94
+ module HostAuthorization
95
+ def self.configure(app, host, opts=OPTS, &block)
96
+ app.opts[:host_authorization_host] = host
97
+ app.opts[:host_authorization_check_forwarded] = opts[:check_forwarded] if opts.key?(:check_forwarded)
98
+
99
+ if block
100
+ app.define_roda_method(:host_authorization_unauthorized, 1, &block)
101
+ end
102
+ end
103
+
104
+ module InstanceMethods
105
+ # Check whether the host is authorized. If not authorized, return a response
106
+ # immediately based on the plugin block.
107
+ def check_host_authorization!
108
+ r = @_request
109
+ return if host_authorized?(_convert_host_for_authorization(r.env["HTTP_HOST"].to_s.dup))
110
+
111
+ if opts[:host_authorization_check_forwarded] && (host = r.env["HTTP_X_FORWARDED_HOST"])
112
+ if i = host.rindex(',')
113
+ host = host[i+1, 10000000].to_s
114
+ end
115
+ host = _convert_host_for_authorization(host.strip)
116
+
117
+ if !host.empty? && host_authorized?(host)
118
+ return
119
+ end
120
+ end
121
+
122
+ r.on do
123
+ host_authorization_unauthorized(r)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # Remove the port information from the passed string (mutates the passed argument).
130
+ def _convert_host_for_authorization(host)
131
+ host.sub!(/:\d+\z/, "")
132
+ host
133
+ end
134
+
135
+ # Whether the host given is one of the authorized hosts for this application.
136
+ def host_authorized?(host, authorized_host = opts[:host_authorization_host])
137
+ case authorized_host
138
+ when Array
139
+ authorized_host.any?{|auth_host| host_authorized?(host, auth_host)}
140
+ else
141
+ authorized_host === host
142
+ end
143
+ end
144
+
145
+ # Action to take for unauthorized hosts. Sets a 403 status by default.
146
+ def host_authorization_unauthorized(_)
147
+ @_response.status = 403
148
+ nil
149
+ end
150
+ end
151
+ end
152
+
153
+ register_plugin(:host_authorization, HostAuthorization)
154
+ end
155
+ end
156
+
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 42
7
+ RodaMinorVersion = 43
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.42.0
4
+ version: 3.43.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-12 00:00:00.000000000 Z
11
+ date: 2021-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -213,6 +213,7 @@ extra_rdoc_files:
213
213
  - doc/release_notes/3.40.0.txt
214
214
  - doc/release_notes/3.41.0.txt
215
215
  - doc/release_notes/3.42.0.txt
216
+ - doc/release_notes/3.43.0.txt
216
217
  - doc/release_notes/3.5.0.txt
217
218
  - doc/release_notes/3.6.0.txt
218
219
  - doc/release_notes/3.7.0.txt
@@ -262,6 +263,7 @@ files:
262
263
  - doc/release_notes/3.40.0.txt
263
264
  - doc/release_notes/3.41.0.txt
264
265
  - doc/release_notes/3.42.0.txt
266
+ - doc/release_notes/3.43.0.txt
265
267
  - doc/release_notes/3.5.0.txt
266
268
  - doc/release_notes/3.6.0.txt
267
269
  - doc/release_notes/3.7.0.txt
@@ -312,6 +314,7 @@ files:
312
314
  - lib/roda/plugins/header_matchers.rb
313
315
  - lib/roda/plugins/heartbeat.rb
314
316
  - lib/roda/plugins/hooks.rb
317
+ - lib/roda/plugins/host_authorization.rb
315
318
  - lib/roda/plugins/indifferent_params.rb
316
319
  - lib/roda/plugins/json.rb
317
320
  - lib/roda/plugins/json_parser.rb
@@ -401,7 +404,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
401
404
  - !ruby/object:Gem::Version
402
405
  version: '0'
403
406
  requirements: []
404
- rubygems_version: 3.2.3
407
+ rubygems_version: 3.2.15
405
408
  signing_key:
406
409
  specification_version: 4
407
410
  summary: Routing tree web toolkit