roda 3.6.0 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -0
- data/doc/release_notes/3.7.0.txt +123 -0
- data/lib/roda/plugins/class_level_routing.rb +1 -1
- data/lib/roda/plugins/content_security_policy.rb +317 -0
- data/lib/roda/plugins/error_handler.rb +2 -2
- data/lib/roda/version.rb +1 -1
- data/spec/plugin/content_security_policy_spec.rb +175 -0
- data/spec/plugin/response_request_spec.rb +31 -0
- metadata +16 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7fe20c8d9a31f311d69cac5c073690374859342d995f97183abeda25e84c2e8
|
4
|
+
data.tar.gz: d8b584c5768ef36fc60cd495d51285446c9cdd7e94a56668758b9c38253413a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90e1c33010e0cd7d21fbf434c88bbad41dc069c746bb7eb58b1ae8b982f48320316eabf7f95d8af9376d76cc6d5d88fcb216c3859df5199f6a98bcf86830b5f3
|
7
|
+
data.tar.gz: 1a2d6dda37a5a49871af93894e5b32e553cdac5baa34d5c0a441481101bbc88353e3b8120deeaf7afcfeaae48c1c7cb48ea525a27c8db8e3d212e7945e75fb8d
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
= 3.7.0 (2018-04-20)
|
2
|
+
|
3
|
+
* Make response_request plugin work with error_handler and class_level_routing plugins (jeremyevans)
|
4
|
+
|
5
|
+
* Add content_security_policy plugin for setting an appropriate Content-Security-Policy header (jeremyevans)
|
6
|
+
|
1
7
|
= 3.6.0 (2018-03-26)
|
2
8
|
|
3
9
|
* Add :wrap option to json_parser plugin, for whether/how to wrap the uploaded JSON object (jeremyevans) (#142)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A content_security_policy plugin has been added for setting up an
|
4
|
+
appropriate Content-Security-Policy header. To configure the
|
5
|
+
default policy, load the plugin with a block:
|
6
|
+
|
7
|
+
plugin :content_security_policy do |csp|
|
8
|
+
csp.default_src :none
|
9
|
+
csp.img_src :self
|
10
|
+
csp.style_src :self, 'fonts.googleapis.com'
|
11
|
+
csp.script_src :self
|
12
|
+
csp.font_src :self, 'fonts.gstatic.com'
|
13
|
+
csp.form_action :self
|
14
|
+
csp.base_uri :none
|
15
|
+
csp.frame_ancestors :none
|
16
|
+
csp.block_all_mixed_content
|
17
|
+
end
|
18
|
+
|
19
|
+
It's recommended that use use a default_src of :none at the top
|
20
|
+
of the policy, then explicitly change other settings (e.g. img_src)
|
21
|
+
when you want to allow content.
|
22
|
+
|
23
|
+
Anywhere in the routing tree, you can use the content_security_policy
|
24
|
+
method to override the default policy. You can pass this method a
|
25
|
+
block:
|
26
|
+
|
27
|
+
r.get 'foo' do
|
28
|
+
content_security_policy do |csp|
|
29
|
+
csp.object_src :self
|
30
|
+
csp.add_style_src 'bar.com'
|
31
|
+
end
|
32
|
+
# ...
|
33
|
+
end
|
34
|
+
|
35
|
+
Or just call a method on it:
|
36
|
+
|
37
|
+
r.get 'foo' do
|
38
|
+
content_security_policy.script_src :self, 'example.com', [:nonce, 'foobarbaz']
|
39
|
+
# ...
|
40
|
+
end
|
41
|
+
|
42
|
+
The following methods exist for configuring the content security policy, they set
|
43
|
+
the appropriate directive, with the underscores replaced by a dash.
|
44
|
+
|
45
|
+
* base_uri
|
46
|
+
* child_src
|
47
|
+
* connect_src
|
48
|
+
* default_src
|
49
|
+
* font_src
|
50
|
+
* form_action
|
51
|
+
* frame_ancestors
|
52
|
+
* frame_src
|
53
|
+
* img_src
|
54
|
+
* manifest_src
|
55
|
+
* media_src
|
56
|
+
* object_src
|
57
|
+
* plugin_types
|
58
|
+
* report_uri
|
59
|
+
* require_sri_for
|
60
|
+
* sandbox
|
61
|
+
* script_src
|
62
|
+
* style_src
|
63
|
+
* worker_src
|
64
|
+
|
65
|
+
All of these methods support any number of arguments, and each argument
|
66
|
+
should be one of the following types:
|
67
|
+
|
68
|
+
String :: used verbatim
|
69
|
+
Symbol :: Substitutes underscore with dash and surrounds with single
|
70
|
+
quotes
|
71
|
+
Array :: only accepts 2 element arrays, joins elements with a dash
|
72
|
+
and surrounds them with single quotes
|
73
|
+
|
74
|
+
Example:
|
75
|
+
|
76
|
+
content_security_policy.script_src :self, :unsafe_eval,
|
77
|
+
'example.com', [:nonce, 'foobarbaz']
|
78
|
+
# script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
|
79
|
+
|
80
|
+
When calling a method with no arguments, the setting is removed from
|
81
|
+
the policy instead of being left empty, since all of these setting
|
82
|
+
require at least one value. Likewise, if the policy does not have
|
83
|
+
any settings, the header will not be added.
|
84
|
+
|
85
|
+
Calling the method overrides any previous setting. Each of the
|
86
|
+
methods has a add_* method (e.g. add_script_src) for appending to the
|
87
|
+
current setting, and a get_* method (e.g. get_script_src) for
|
88
|
+
retrieving the current value of the setting, or nil if it is not
|
89
|
+
defined.
|
90
|
+
|
91
|
+
content_security_policy.script_src :self, :unsafe_eval
|
92
|
+
# script-src 'self' 'unsafe-eval';
|
93
|
+
content_security_policy.add_script_src 'example.com', [:nonce, 'foobarbaz']
|
94
|
+
# script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
|
95
|
+
|
96
|
+
content_security_policy.get_script_src 'example.com', [:nonce, 'foobarbaz']
|
97
|
+
# => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
|
98
|
+
|
99
|
+
The clear method can be used to remove all settings from the policy.
|
100
|
+
|
101
|
+
The following methods to set boolean directives are also defined:
|
102
|
+
|
103
|
+
* block_all_mixed_content
|
104
|
+
* upgrade_insecure_requests
|
105
|
+
|
106
|
+
Calling these methods will turn on the related setting. To turn the
|
107
|
+
setting off again, you can call them with a false argument (e.g.
|
108
|
+
block_all_mixed_content(false)). Each method also an *? method
|
109
|
+
(e.g. block_all_mixed_content?) for returning whether the setting is
|
110
|
+
currently enabled.
|
111
|
+
|
112
|
+
Likewise there is also a report_only method for turning on report
|
113
|
+
only mode (the default is enforcement mode), or turning off report
|
114
|
+
only mode if a false argument is given. Also, there is a
|
115
|
+
report_only? method for returning whether report only mode is
|
116
|
+
enabled. In report only mode, the Content-Security-Policy-Report-Only
|
117
|
+
header is used.
|
118
|
+
|
119
|
+
= Other Improvements
|
120
|
+
|
121
|
+
* The response_request plugin now integrates with the error_handler and
|
122
|
+
class_level_routing plugins. Those plugins now reinitialize the
|
123
|
+
current response object instead of creating a new response object.
|
@@ -86,7 +86,7 @@ class Roda
|
|
86
86
|
if result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
|
87
87
|
# Reset the response so it doesn't inherit the status or any headers from
|
88
88
|
# the original response.
|
89
|
-
@_response
|
89
|
+
@_response.send(:initialize)
|
90
90
|
super do |r|
|
91
91
|
opts[:class_level_routes].each do |meth, args, blk|
|
92
92
|
req.instance_variable_set(:@remaining_path, rp)
|
@@ -0,0 +1,317 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The content_security_policy plugin allows you to easily set a Content-Security-Policy
|
7
|
+
# header for the application, which modern browsers will use to control access to specific
|
8
|
+
# types of page content.
|
9
|
+
#
|
10
|
+
# You would generally call the plugin with a block to set the default policy:
|
11
|
+
#
|
12
|
+
# plugin :content_security_policy do |csp|
|
13
|
+
# csp.default_src :none
|
14
|
+
# csp.img_src :self
|
15
|
+
# csp.style_src :self
|
16
|
+
# csp.script_src :self
|
17
|
+
# csp.font_src :self
|
18
|
+
# csp.form_action :self
|
19
|
+
# csp.base_uri :none
|
20
|
+
# csp.frame_ancestors :none
|
21
|
+
# csp.block_all_mixed_content
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Then, anywhere in the routing tree, you can customize the policy for just that
|
25
|
+
# branch or action using the same block syntax:
|
26
|
+
#
|
27
|
+
# r.get 'foo' do
|
28
|
+
# content_security_policy do |csp|
|
29
|
+
# csp.object_src :self
|
30
|
+
# csp.add_style_src 'bar.com'
|
31
|
+
# end
|
32
|
+
# # ...
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# In addition to using a block, you can also call methods on the object returned
|
36
|
+
# by the method:
|
37
|
+
#
|
38
|
+
# r.get 'foo' do
|
39
|
+
# content_security_policy.script_src :self, 'example.com', [:nonce, 'foobarbaz']
|
40
|
+
# # ...
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# The following methods are available for configuring the content security policy,
|
44
|
+
# which specify the setting (substituting _ with -):
|
45
|
+
#
|
46
|
+
# * base_uri
|
47
|
+
# * child_src
|
48
|
+
# * connect_src
|
49
|
+
# * default_src
|
50
|
+
# * font_src
|
51
|
+
# * form_action
|
52
|
+
# * frame_ancestors
|
53
|
+
# * frame_src
|
54
|
+
# * img_src
|
55
|
+
# * manifest_src
|
56
|
+
# * media_src
|
57
|
+
# * object_src
|
58
|
+
# * plugin_types
|
59
|
+
# * report_uri
|
60
|
+
# * require_sri_for
|
61
|
+
# * sandbox
|
62
|
+
# * script_src
|
63
|
+
# * style_src
|
64
|
+
# * worker_src
|
65
|
+
#
|
66
|
+
# All of these methods support any number of arguments, and each argument should
|
67
|
+
# be one of the following types:
|
68
|
+
#
|
69
|
+
# String :: used verbatim
|
70
|
+
# Symbol :: Substitutes +_+ with +-+ and surrounds with <tt>'</tt>
|
71
|
+
# Array :: only accepts 2 element arrays, joins elements with +-+ and
|
72
|
+
# surrounds the result with <tt>'</tt>
|
73
|
+
#
|
74
|
+
# Example:
|
75
|
+
#
|
76
|
+
# content_security_policy.script_src :self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']
|
77
|
+
# # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
|
78
|
+
#
|
79
|
+
# When calling a method with no arguments, the setting is removed from the policy instead
|
80
|
+
# of being left empty, since all of these setting require at least one value. Likewise,
|
81
|
+
# if the policy does not have any settings, the header will not be added.
|
82
|
+
#
|
83
|
+
# Calling the method overrides any previous setting. Each of the methods has +add_*+ and
|
84
|
+
# +get_*+ methods defined. The +add_*+ method appends to any existing setting, and the +get_*+ method
|
85
|
+
# returns the current value for the setting.
|
86
|
+
#
|
87
|
+
# content_security_policy.script_src :self, :unsafe_eval
|
88
|
+
# content_security_policy.add_script_src 'example.com', [:nonce, 'foobarbaz']
|
89
|
+
# # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
|
90
|
+
#
|
91
|
+
# content_security_policy.get_script_src 'example.com', [:nonce, 'foobarbaz']
|
92
|
+
# # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
|
93
|
+
#
|
94
|
+
# The clear method can be used to remove all settings from the policy.
|
95
|
+
#
|
96
|
+
# The following methods to set boolean settings are also defined:
|
97
|
+
#
|
98
|
+
# * block_all_mixed_content
|
99
|
+
# * upgrade_insecure_requests
|
100
|
+
#
|
101
|
+
# Calling these methods will turn on the related setting. To turn the setting
|
102
|
+
# off again, you can call them with a +false+ argument. There is also a <tt>*?</tt> method
|
103
|
+
# for each setting for returning whether the setting is currently enabled.
|
104
|
+
#
|
105
|
+
# Likewise there is also a +report_only+ method for turning on report only mode (the
|
106
|
+
# default is enforcement mode), or turning off report only mode if a false argument
|
107
|
+
# is given. Also, there is a +report_only?+ method for returning whether report only
|
108
|
+
# mode is enabled.
|
109
|
+
module ContentSecurityPolicy
|
110
|
+
# Represents a content security policy.
|
111
|
+
class Policy
|
112
|
+
'
|
113
|
+
base-uri
|
114
|
+
child-src
|
115
|
+
connect-src
|
116
|
+
default-src
|
117
|
+
font-src
|
118
|
+
form-action
|
119
|
+
frame-ancestors
|
120
|
+
frame-src
|
121
|
+
img-src
|
122
|
+
manifest-src
|
123
|
+
media-src
|
124
|
+
object-src
|
125
|
+
plugin-types
|
126
|
+
report-uri
|
127
|
+
require-sri-for
|
128
|
+
sandbox
|
129
|
+
script-src
|
130
|
+
style-src
|
131
|
+
worker-src
|
132
|
+
'.split.each(&:freeze).each do |setting|
|
133
|
+
meth = setting.gsub('-', '_').freeze
|
134
|
+
|
135
|
+
# Setting method name sets the setting value, or removes it if no args are given.
|
136
|
+
define_method(meth) do |*args|
|
137
|
+
if args.empty?
|
138
|
+
@opts.delete(setting)
|
139
|
+
else
|
140
|
+
@opts[setting] = args.freeze
|
141
|
+
end
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
# add_* method name adds to the setting value, or clears setting if no values
|
146
|
+
# are given.
|
147
|
+
define_method("add_#{meth}") do |*args|
|
148
|
+
if args.empty?
|
149
|
+
@opts[setting]
|
150
|
+
else
|
151
|
+
@opts[setting] ||= EMPTY_ARRAY
|
152
|
+
@opts[setting] += args
|
153
|
+
@opts[setting].freeze
|
154
|
+
end
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
|
158
|
+
# get_* method always returns current setting value.
|
159
|
+
define_method("get_#{meth}") do
|
160
|
+
@opts[setting]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
%w'block-all-mixed-content upgrade-insecure-requests'.each(&:freeze).each do |setting|
|
165
|
+
meth = setting.gsub('-', '_').freeze
|
166
|
+
|
167
|
+
# Setting method name turns on setting if true or no argument given,
|
168
|
+
# or removes setting if false is given.
|
169
|
+
define_method(meth) do |arg=true|
|
170
|
+
if arg
|
171
|
+
@opts[setting] = true
|
172
|
+
else
|
173
|
+
@opts.delete(setting)
|
174
|
+
end
|
175
|
+
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
# *? method returns true or false depending on whether setting is enabled.
|
180
|
+
define_method("#{meth}?") do
|
181
|
+
!!@opts[setting]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def initialize
|
186
|
+
clear
|
187
|
+
end
|
188
|
+
|
189
|
+
# Clear all settings, useful to remove any inherited settings.
|
190
|
+
def clear
|
191
|
+
@opts = {}
|
192
|
+
end
|
193
|
+
|
194
|
+
# Do not allow future modifications to any settings.
|
195
|
+
def freeze
|
196
|
+
@opts.freeze
|
197
|
+
header_value.freeze
|
198
|
+
super
|
199
|
+
end
|
200
|
+
|
201
|
+
# The header name to use, depends on whether report only mode has been enabled.
|
202
|
+
def header_key
|
203
|
+
@report_only ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
|
204
|
+
end
|
205
|
+
|
206
|
+
# The header value to use.
|
207
|
+
def header_value
|
208
|
+
return @header_value if @header_value
|
209
|
+
|
210
|
+
s = String.new
|
211
|
+
@opts.each do |k, vs|
|
212
|
+
s << k
|
213
|
+
unless vs == true
|
214
|
+
vs.each{|v| append_formatted_value(s, v)}
|
215
|
+
end
|
216
|
+
s << '; '
|
217
|
+
end
|
218
|
+
@header_value = s
|
219
|
+
end
|
220
|
+
|
221
|
+
# Set whether the Content-Security-Policy-Report-Only header instead of the
|
222
|
+
# default Content-Security-Policy header.
|
223
|
+
def report_only(report=true)
|
224
|
+
@report_only = report
|
225
|
+
end
|
226
|
+
|
227
|
+
# Whether this policy uses report only mode.
|
228
|
+
def report_only?
|
229
|
+
!!@report_only
|
230
|
+
end
|
231
|
+
|
232
|
+
# Set the current policy in the headers hash. If no settings have been made
|
233
|
+
# in the policy, does not set a header.
|
234
|
+
def set_header(headers)
|
235
|
+
return if @opts.empty?
|
236
|
+
headers[header_key] ||= header_value
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# Handle three types of values when formatting the header:
|
242
|
+
# String :: used verbatim
|
243
|
+
# Symbol :: Substitutes _ with - and surrounds with '
|
244
|
+
# Array :: only accepts 2 element arrays, joins them with - and
|
245
|
+
# surrounds them with '
|
246
|
+
def append_formatted_value(s, v)
|
247
|
+
case v
|
248
|
+
when String
|
249
|
+
s << ' ' << v
|
250
|
+
when Array
|
251
|
+
case v.length
|
252
|
+
when 2
|
253
|
+
s << " '" << v.join('-') << "'"
|
254
|
+
else
|
255
|
+
raise RodaError, "unsupported CSP value used: #{v.inspect}"
|
256
|
+
end
|
257
|
+
when Symbol
|
258
|
+
s << " '" << v.to_s.gsub('_', '-') << "'"
|
259
|
+
else
|
260
|
+
raise RodaError, "unsupported CSP value used: #{v.inspect}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Make object copy use copy of settings, and remove cached header value.
|
265
|
+
def initialize_copy(_)
|
266
|
+
super
|
267
|
+
@opts = @opts.dup
|
268
|
+
@header_value = nil
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
# Yield the current Content Security Policy to the block.
|
274
|
+
def self.configure(app)
|
275
|
+
policy = app.opts[:content_security_policy] = if policy = app.opts[:content_security_policy]
|
276
|
+
policy.dup
|
277
|
+
else
|
278
|
+
Policy.new
|
279
|
+
end
|
280
|
+
|
281
|
+
yield policy if block_given?
|
282
|
+
policy.freeze
|
283
|
+
end
|
284
|
+
|
285
|
+
module InstanceMethods
|
286
|
+
# If a block is given, yield the current content security policy. Returns the
|
287
|
+
# current content security policy.
|
288
|
+
def content_security_policy
|
289
|
+
policy = @_response.content_security_policy
|
290
|
+
yield policy if block_given?
|
291
|
+
policy
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
module ResponseMethods
|
296
|
+
# Unset any content security policy when reinitializing
|
297
|
+
def initialize
|
298
|
+
super
|
299
|
+
@content_security_policy &&= nil
|
300
|
+
end
|
301
|
+
|
302
|
+
# The current content security policy to be used for this response.
|
303
|
+
def content_security_policy
|
304
|
+
@content_security_policy ||= roda_class.opts[:content_security_policy].dup
|
305
|
+
end
|
306
|
+
|
307
|
+
# Set the appropriate content security policy header.
|
308
|
+
def set_default_headers
|
309
|
+
super
|
310
|
+
(@content_security_policy || roda_class.opts[:content_security_policy]).set_header(@headers)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
register_plugin(:content_security_policy, ContentSecurityPolicy)
|
316
|
+
end
|
317
|
+
end
|
data/lib/roda/version.rb
CHANGED
@@ -0,0 +1,175 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
describe "content_security_policy plugin" do
|
4
|
+
it "does not add header if no options are set" do
|
5
|
+
app(:content_security_policy){'a'}
|
6
|
+
header('Content-Security-Policy', "/a").must_be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "sets Content-Security-Policy header" do
|
10
|
+
app(:bare) do
|
11
|
+
plugin :content_security_policy do |csp|
|
12
|
+
csp.default_src :self
|
13
|
+
csp.img_src :self, 'example.com'
|
14
|
+
csp.style_src [:sha256, 'abc']
|
15
|
+
end
|
16
|
+
|
17
|
+
route do |r|
|
18
|
+
r.get 'ro' do
|
19
|
+
content_security_policy.report_only
|
20
|
+
''
|
21
|
+
end
|
22
|
+
|
23
|
+
r.get 'nro' do
|
24
|
+
content_security_policy.report_only
|
25
|
+
content_security_policy.report_only(false)
|
26
|
+
content_security_policy.report_only?.inspect
|
27
|
+
end
|
28
|
+
|
29
|
+
r.get 'get' do
|
30
|
+
content_security_policy.get_default_src.inspect
|
31
|
+
end
|
32
|
+
|
33
|
+
r.get 'add' do
|
34
|
+
content_security_policy.add_default_src('foo.com', 'bar.com')
|
35
|
+
''
|
36
|
+
end
|
37
|
+
|
38
|
+
r.get 'empty' do
|
39
|
+
content_security_policy.add_default_src
|
40
|
+
''
|
41
|
+
end
|
42
|
+
|
43
|
+
r.get 'set' do
|
44
|
+
content_security_policy.default_src('foo.com', 'bar.com')
|
45
|
+
''
|
46
|
+
end
|
47
|
+
|
48
|
+
r.get 'bool' do
|
49
|
+
content_security_policy.block_all_mixed_content
|
50
|
+
content_security_policy.upgrade_insecure_requests(false)
|
51
|
+
content_security_policy.block_all_mixed_content?.inspect
|
52
|
+
end
|
53
|
+
|
54
|
+
r.get 'block' do
|
55
|
+
content_security_policy do |csp|
|
56
|
+
csp.block_all_mixed_content
|
57
|
+
csp.add_default_src('foo.com', 'bar.com')
|
58
|
+
csp.img_src :none
|
59
|
+
csp.style_src
|
60
|
+
csp.report_only
|
61
|
+
end
|
62
|
+
''
|
63
|
+
end
|
64
|
+
|
65
|
+
r.get 'clear' do
|
66
|
+
content_security_policy do |csp|
|
67
|
+
csp.clear
|
68
|
+
csp.add_default_src('foo.com', 'bar.com')
|
69
|
+
end
|
70
|
+
''
|
71
|
+
end
|
72
|
+
|
73
|
+
'a'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
v = "default-src 'self'; img-src 'self' example.com; style-src 'sha256-abc'; "
|
78
|
+
|
79
|
+
header('Content-Security-Policy', "/a").must_equal v
|
80
|
+
|
81
|
+
header('Content-Security-Policy', "/nro").must_equal v
|
82
|
+
header('Content-Security-Policy-Report-Only', "/nro").must_be_nil
|
83
|
+
body("/nro").must_equal 'false'
|
84
|
+
|
85
|
+
header('Content-Security-Policy-Report-Only', "/ro").must_equal v
|
86
|
+
header('Content-Security-Policy', "/ro").must_be_nil
|
87
|
+
|
88
|
+
body('/get').must_equal '[:self]'
|
89
|
+
|
90
|
+
header('Content-Security-Policy', "/add").must_equal "default-src 'self' foo.com bar.com; img-src 'self' example.com; style-src 'sha256-abc'; "
|
91
|
+
|
92
|
+
header('Content-Security-Policy', "/empty").must_equal "default-src 'self'; img-src 'self' example.com; style-src 'sha256-abc'; "
|
93
|
+
|
94
|
+
header('Content-Security-Policy', "/set").must_equal "default-src foo.com bar.com; img-src 'self' example.com; style-src 'sha256-abc'; "
|
95
|
+
|
96
|
+
body('/bool').must_equal 'true'
|
97
|
+
header('Content-Security-Policy', "/bool").must_equal "default-src 'self'; img-src 'self' example.com; style-src 'sha256-abc'; block-all-mixed-content; "
|
98
|
+
|
99
|
+
header('Content-Security-Policy-Report-Only', "/block").must_equal "default-src 'self' foo.com bar.com; img-src 'none'; block-all-mixed-content; "
|
100
|
+
|
101
|
+
header('Content-Security-Policy', "/clear").must_equal "default-src foo.com bar.com; "
|
102
|
+
end
|
103
|
+
|
104
|
+
it "raises error for unsupported CSP values" do
|
105
|
+
app{}
|
106
|
+
proc{app.plugin(:content_security_policy){|csp| csp.default_src Object.new}}.must_raise Roda::RodaError
|
107
|
+
proc{app.plugin(:content_security_policy){|csp| csp.default_src []}}.must_raise Roda::RodaError
|
108
|
+
proc{app.plugin(:content_security_policy){|csp| csp.default_src [:a]}}.must_raise Roda::RodaError
|
109
|
+
proc{app.plugin(:content_security_policy){|csp| csp.default_src [:a, :b, :c]}}.must_raise Roda::RodaError
|
110
|
+
end
|
111
|
+
|
112
|
+
it "supports all documented settings" do
|
113
|
+
app(:content_security_policy) do |r|
|
114
|
+
content_security_policy.send(r.path[1..-1], :self)
|
115
|
+
end
|
116
|
+
|
117
|
+
'
|
118
|
+
base_uri
|
119
|
+
child_src
|
120
|
+
connect_src
|
121
|
+
default_src
|
122
|
+
font_src
|
123
|
+
form_action
|
124
|
+
frame_ancestors
|
125
|
+
frame_src
|
126
|
+
img_src
|
127
|
+
manifest_src
|
128
|
+
media_src
|
129
|
+
object_src
|
130
|
+
plugin_types
|
131
|
+
report_uri
|
132
|
+
require_sri_for
|
133
|
+
sandbox
|
134
|
+
script_src
|
135
|
+
style_src
|
136
|
+
worker_src
|
137
|
+
'.split.each do |setting|
|
138
|
+
header('Content-Security-Policy', "/#{setting}").must_equal "#{setting.gsub('_', '-')} 'self'; "
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "does not override existing heading" do
|
143
|
+
app(:content_security_policy) do |r|
|
144
|
+
content_security_policy.default_src :self
|
145
|
+
response['Content-Security-Policy'] = "default_src 'none';"
|
146
|
+
''
|
147
|
+
end
|
148
|
+
header('Content-Security-Policy').must_equal "default_src 'none';"
|
149
|
+
end
|
150
|
+
|
151
|
+
it "works with error_handler" do
|
152
|
+
app(:bare) do
|
153
|
+
plugin(:error_handler){|_| ''}
|
154
|
+
plugin :content_security_policy do |csp|
|
155
|
+
csp.default_src :self
|
156
|
+
csp.img_src :self, 'example.com'
|
157
|
+
csp.style_src [:sha256, 'abc']
|
158
|
+
end
|
159
|
+
|
160
|
+
route do |r|
|
161
|
+
r.get 'a' do
|
162
|
+
content_security_policy.default_src 'foo.com'
|
163
|
+
raise
|
164
|
+
end
|
165
|
+
|
166
|
+
raise
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
header('Content-Security-Policy').must_equal "default-src 'self'; img-src 'self' example.com; style-src 'sha256-abc'; "
|
171
|
+
|
172
|
+
# Don't include updates before the error
|
173
|
+
header('Content-Security-Policy', '/a').must_equal "default-src 'self'; img-src 'self' example.com; style-src 'sha256-abc'; "
|
174
|
+
end
|
175
|
+
end
|
@@ -9,4 +9,35 @@ describe "response_request plugin" do
|
|
9
9
|
body.must_equal "a"
|
10
10
|
body('REQUEST_METHOD'=>'POST').must_equal "b"
|
11
11
|
end
|
12
|
+
|
13
|
+
it "should work with error_handler plugin" do
|
14
|
+
app(:bare) do
|
15
|
+
plugin :response_request
|
16
|
+
|
17
|
+
plugin :error_handler do |_|
|
18
|
+
response.request.post? ? "b" : "a"
|
19
|
+
end
|
20
|
+
|
21
|
+
route{raise}
|
22
|
+
end
|
23
|
+
|
24
|
+
body.must_equal "a"
|
25
|
+
body('REQUEST_METHOD'=>'POST').must_equal "b"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should work with class_level_routing plugin" do
|
29
|
+
app(:bare) do
|
30
|
+
plugin :response_request
|
31
|
+
plugin :class_level_routing
|
32
|
+
|
33
|
+
is '' do |_|
|
34
|
+
response.request.post? ? "b" : "a"
|
35
|
+
end
|
36
|
+
|
37
|
+
route{}
|
38
|
+
end
|
39
|
+
|
40
|
+
body.must_equal "a"
|
41
|
+
body('REQUEST_METHOD'=>'POST').must_equal "b"
|
42
|
+
end
|
12
43
|
end
|
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.
|
4
|
+
version: 3.7.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: 2018-
|
11
|
+
date: 2018-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -160,21 +160,13 @@ extra_rdoc_files:
|
|
160
160
|
- MIT-LICENSE
|
161
161
|
- CHANGELOG
|
162
162
|
- doc/conventions.rdoc
|
163
|
+
- doc/release_notes/3.7.0.txt
|
163
164
|
- doc/release_notes/1.0.0.txt
|
164
165
|
- doc/release_notes/1.1.0.txt
|
165
166
|
- doc/release_notes/1.2.0.txt
|
166
167
|
- doc/release_notes/1.3.0.txt
|
167
168
|
- doc/release_notes/2.0.0.txt
|
168
169
|
- doc/release_notes/2.1.0.txt
|
169
|
-
- doc/release_notes/2.2.0.txt
|
170
|
-
- doc/release_notes/2.3.0.txt
|
171
|
-
- doc/release_notes/2.4.0.txt
|
172
|
-
- doc/release_notes/2.5.0.txt
|
173
|
-
- doc/release_notes/2.5.1.txt
|
174
|
-
- doc/release_notes/2.6.0.txt
|
175
|
-
- doc/release_notes/2.7.0.txt
|
176
|
-
- doc/release_notes/2.8.0.txt
|
177
|
-
- doc/release_notes/2.9.0.txt
|
178
170
|
- doc/release_notes/2.10.0.txt
|
179
171
|
- doc/release_notes/2.11.0.txt
|
180
172
|
- doc/release_notes/2.12.0.txt
|
@@ -185,6 +177,7 @@ extra_rdoc_files:
|
|
185
177
|
- doc/release_notes/2.17.0.txt
|
186
178
|
- doc/release_notes/2.18.0.txt
|
187
179
|
- doc/release_notes/2.19.0.txt
|
180
|
+
- doc/release_notes/2.2.0.txt
|
188
181
|
- doc/release_notes/2.20.0.txt
|
189
182
|
- doc/release_notes/2.21.0.txt
|
190
183
|
- doc/release_notes/2.22.0.txt
|
@@ -195,6 +188,14 @@ extra_rdoc_files:
|
|
195
188
|
- doc/release_notes/2.27.0.txt
|
196
189
|
- doc/release_notes/2.28.0.txt
|
197
190
|
- doc/release_notes/2.29.0.txt
|
191
|
+
- doc/release_notes/2.3.0.txt
|
192
|
+
- doc/release_notes/2.4.0.txt
|
193
|
+
- doc/release_notes/2.5.0.txt
|
194
|
+
- doc/release_notes/2.5.1.txt
|
195
|
+
- doc/release_notes/2.6.0.txt
|
196
|
+
- doc/release_notes/2.7.0.txt
|
197
|
+
- doc/release_notes/2.8.0.txt
|
198
|
+
- doc/release_notes/2.9.0.txt
|
198
199
|
- doc/release_notes/3.0.0.txt
|
199
200
|
- doc/release_notes/3.1.0.txt
|
200
201
|
- doc/release_notes/3.2.0.txt
|
@@ -250,6 +251,7 @@ files:
|
|
250
251
|
- doc/release_notes/3.4.0.txt
|
251
252
|
- doc/release_notes/3.5.0.txt
|
252
253
|
- doc/release_notes/3.6.0.txt
|
254
|
+
- doc/release_notes/3.7.0.txt
|
253
255
|
- lib/roda.rb
|
254
256
|
- lib/roda/plugins/_symbol_regexp_matchers.rb
|
255
257
|
- lib/roda/plugins/all_verbs.rb
|
@@ -262,6 +264,7 @@ files:
|
|
262
264
|
- lib/roda/plugins/class_level_routing.rb
|
263
265
|
- lib/roda/plugins/class_matchers.rb
|
264
266
|
- lib/roda/plugins/content_for.rb
|
267
|
+
- lib/roda/plugins/content_security_policy.rb
|
265
268
|
- lib/roda/plugins/cookies.rb
|
266
269
|
- lib/roda/plugins/csrf.rb
|
267
270
|
- lib/roda/plugins/default_headers.rb
|
@@ -358,6 +361,7 @@ files:
|
|
358
361
|
- spec/plugin/class_level_routing_spec.rb
|
359
362
|
- spec/plugin/class_matchers_spec.rb
|
360
363
|
- spec/plugin/content_for_spec.rb
|
364
|
+
- spec/plugin/content_security_policy_spec.rb
|
361
365
|
- spec/plugin/cookies_spec.rb
|
362
366
|
- spec/plugin/csrf_spec.rb
|
363
367
|
- spec/plugin/default_headers_spec.rb
|
@@ -478,7 +482,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
478
482
|
version: '0'
|
479
483
|
requirements: []
|
480
484
|
rubyforge_project:
|
481
|
-
rubygems_version: 2.7.
|
485
|
+
rubygems_version: 2.7.6
|
482
486
|
signing_key:
|
483
487
|
specification_version: 4
|
484
488
|
summary: Routing tree web toolkit
|