roda 3.6.0 → 3.7.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 +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
|