ultra-lite-hub 0.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 +7 -0
- data/roda-3.105.0/MIT-LICENSE +21 -0
- data/roda-3.105.0/lib/roda/cache.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
- data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
- data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
- data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
- data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
- data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
- data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
- data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
- data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
- data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
- data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
- data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
- data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
- data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
- data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
- data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
- data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
- data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
- data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
- data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
- data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
- data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
- data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
- data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
- data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
- data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
- data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
- data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
- data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
- data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
- data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
- data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
- data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
- data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
- data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
- data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
- data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
- data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
- data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
- data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
- data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
- data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
- data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
- data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
- data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
- data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
- data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
- data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
- data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
- data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
- data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
- data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
- data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
- data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
- data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
- data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
- data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
- data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins.rb +68 -0
- data/roda-3.105.0/lib/roda/request.rb +722 -0
- data/roda-3.105.0/lib/roda/response.rb +221 -0
- data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
- data/roda-3.105.0/lib/roda/version.rb +18 -0
- data/roda-3.105.0/lib/roda.rb +586 -0
- data/ultra-lite-hub.gemspec +12 -0
- metadata +201 -0
|
@@ -0,0 +1,329 @@
|
|
|
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_to
|
|
60
|
+
# * report_uri
|
|
61
|
+
# * require_sri_for
|
|
62
|
+
# * sandbox
|
|
63
|
+
# * script_src
|
|
64
|
+
# * style_src
|
|
65
|
+
# * worker_src
|
|
66
|
+
#
|
|
67
|
+
# All of these methods support any number of arguments, and each argument should
|
|
68
|
+
# be one of the following types:
|
|
69
|
+
#
|
|
70
|
+
# String :: used verbatim
|
|
71
|
+
# Symbol :: Substitutes +_+ with +-+ and surrounds with <tt>'</tt>
|
|
72
|
+
# Array :: only accepts 2 element arrays, joins elements with +-+ and
|
|
73
|
+
# surrounds the result with <tt>'</tt>
|
|
74
|
+
#
|
|
75
|
+
# Example:
|
|
76
|
+
#
|
|
77
|
+
# content_security_policy.script_src :self, :unsafe_eval, '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 the policy instead
|
|
81
|
+
# of being left empty, since all of these setting require at least one value. Likewise,
|
|
82
|
+
# if the policy does not have any settings, the header will not be added.
|
|
83
|
+
#
|
|
84
|
+
# Calling the method overrides any previous setting. Each of the methods has +add_*+ and
|
|
85
|
+
# +get_*+ methods defined. The +add_*+ method appends to any existing setting, and the +get_*+ method
|
|
86
|
+
# returns the current value for the setting.
|
|
87
|
+
#
|
|
88
|
+
# content_security_policy.script_src :self, :unsafe_eval
|
|
89
|
+
# content_security_policy.add_script_src 'example.com', [:nonce, 'foobarbaz']
|
|
90
|
+
# # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
|
|
91
|
+
#
|
|
92
|
+
# content_security_policy.get_script_src
|
|
93
|
+
# # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
|
|
94
|
+
#
|
|
95
|
+
# The clear method can be used to remove all settings from the policy. Empty policies
|
|
96
|
+
# do not set any headers. You can use +response.skip_content_security_policy!+ to skip
|
|
97
|
+
# setting a policy. This is faster than calling +content_security_policy.clear+, since
|
|
98
|
+
# it does not duplicate the default policy.
|
|
99
|
+
#
|
|
100
|
+
# The following methods to set boolean settings are also defined:
|
|
101
|
+
#
|
|
102
|
+
# * block_all_mixed_content
|
|
103
|
+
# * upgrade_insecure_requests
|
|
104
|
+
#
|
|
105
|
+
# Calling these methods will turn on the related setting. To turn the setting
|
|
106
|
+
# off again, you can call them with a +false+ argument. There is also a <tt>*?</tt> method
|
|
107
|
+
# for each setting for returning whether the setting is currently enabled.
|
|
108
|
+
#
|
|
109
|
+
# Likewise there is also a +report_only+ method for turning on report only mode (the
|
|
110
|
+
# default is enforcement mode), or turning off report only mode if a false argument
|
|
111
|
+
# is given. Also, there is a +report_only?+ method for returning whether report only
|
|
112
|
+
# mode is enabled.
|
|
113
|
+
module ContentSecurityPolicy
|
|
114
|
+
# Represents a content security policy.
|
|
115
|
+
class Policy
|
|
116
|
+
'
|
|
117
|
+
base-uri
|
|
118
|
+
child-src
|
|
119
|
+
connect-src
|
|
120
|
+
default-src
|
|
121
|
+
font-src
|
|
122
|
+
form-action
|
|
123
|
+
frame-ancestors
|
|
124
|
+
frame-src
|
|
125
|
+
img-src
|
|
126
|
+
manifest-src
|
|
127
|
+
media-src
|
|
128
|
+
object-src
|
|
129
|
+
plugin-types
|
|
130
|
+
report-to
|
|
131
|
+
report-uri
|
|
132
|
+
require-sri-for
|
|
133
|
+
sandbox
|
|
134
|
+
script-src
|
|
135
|
+
style-src
|
|
136
|
+
worker-src
|
|
137
|
+
'.split.each(&:freeze).each do |setting|
|
|
138
|
+
meth = setting.tr('-', '_').freeze
|
|
139
|
+
|
|
140
|
+
# Setting method name sets the setting value, or removes it if no args are given.
|
|
141
|
+
define_method(meth) do |*args|
|
|
142
|
+
if args.empty?
|
|
143
|
+
@opts.delete(setting)
|
|
144
|
+
else
|
|
145
|
+
@opts[setting] = args.freeze
|
|
146
|
+
end
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# add_* method name adds to the setting value, or clears setting if no values
|
|
151
|
+
# are given.
|
|
152
|
+
define_method("add_#{meth}") do |*args|
|
|
153
|
+
unless args.empty?
|
|
154
|
+
@opts[setting] ||= EMPTY_ARRAY
|
|
155
|
+
@opts[setting] += args
|
|
156
|
+
@opts[setting].freeze
|
|
157
|
+
end
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# get_* method always returns current setting value.
|
|
162
|
+
define_method("get_#{meth}") do
|
|
163
|
+
@opts[setting]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
%w'block-all-mixed-content upgrade-insecure-requests'.each(&:freeze).each do |setting|
|
|
168
|
+
meth = setting.tr('-', '_').freeze
|
|
169
|
+
|
|
170
|
+
# Setting method name turns on setting if true or no argument given,
|
|
171
|
+
# or removes setting if false is given.
|
|
172
|
+
define_method(meth) do |arg=true|
|
|
173
|
+
if arg
|
|
174
|
+
@opts[setting] = true
|
|
175
|
+
else
|
|
176
|
+
@opts.delete(setting)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# *? method returns true or false depending on whether setting is enabled.
|
|
183
|
+
define_method("#{meth}?") do
|
|
184
|
+
!!@opts[setting]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def initialize
|
|
189
|
+
clear
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Clear all settings, useful to remove any inherited settings.
|
|
193
|
+
def clear
|
|
194
|
+
@opts = {}
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Do not allow future modifications to any settings.
|
|
198
|
+
def freeze
|
|
199
|
+
@opts.freeze
|
|
200
|
+
header_value.freeze
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# The header name to use, depends on whether report only mode has been enabled.
|
|
205
|
+
def header_key
|
|
206
|
+
@report_only ? RodaResponseHeaders::CONTENT_SECURITY_POLICY_REPORT_ONLY : RodaResponseHeaders::CONTENT_SECURITY_POLICY
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# The header value to use.
|
|
210
|
+
def header_value
|
|
211
|
+
return @header_value if @header_value
|
|
212
|
+
|
|
213
|
+
s = String.new
|
|
214
|
+
@opts.each do |k, vs|
|
|
215
|
+
s << k
|
|
216
|
+
unless vs == true
|
|
217
|
+
vs.each{|v| append_formatted_value(s, v)}
|
|
218
|
+
end
|
|
219
|
+
s << '; '
|
|
220
|
+
end
|
|
221
|
+
@header_value = s
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Set whether the Content-Security-Policy-Report-Only header instead of the
|
|
225
|
+
# default Content-Security-Policy header.
|
|
226
|
+
def report_only(report=true)
|
|
227
|
+
@report_only = report
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Whether this policy uses report only mode.
|
|
231
|
+
def report_only?
|
|
232
|
+
!!@report_only
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Set the current policy in the headers hash. If no settings have been made
|
|
236
|
+
# in the policy, does not set a header.
|
|
237
|
+
def set_header(headers)
|
|
238
|
+
return if @opts.empty?
|
|
239
|
+
headers[header_key] ||= header_value
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
# Handle three types of values when formatting the header:
|
|
245
|
+
# String :: used verbatim
|
|
246
|
+
# Symbol :: Substitutes _ with - and surrounds with '
|
|
247
|
+
# Array :: only accepts 2 element arrays, joins them with - and
|
|
248
|
+
# surrounds them with '
|
|
249
|
+
def append_formatted_value(s, v)
|
|
250
|
+
case v
|
|
251
|
+
when String
|
|
252
|
+
s << ' ' << v
|
|
253
|
+
when Array
|
|
254
|
+
case v.length
|
|
255
|
+
when 2
|
|
256
|
+
s << " '" << v.join('-') << "'"
|
|
257
|
+
else
|
|
258
|
+
raise RodaError, "unsupported CSP value used: #{v.inspect}"
|
|
259
|
+
end
|
|
260
|
+
when Symbol
|
|
261
|
+
s << " '" << v.to_s.tr('_', '-') << "'"
|
|
262
|
+
else
|
|
263
|
+
raise RodaError, "unsupported CSP value used: #{v.inspect}"
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Make object copy use copy of settings, and remove cached header value.
|
|
268
|
+
def initialize_copy(_)
|
|
269
|
+
super
|
|
270
|
+
@opts = @opts.dup
|
|
271
|
+
@header_value = nil
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# Yield the current Content Security Policy to the block.
|
|
277
|
+
def self.configure(app)
|
|
278
|
+
policy = app.opts[:content_security_policy] = if policy = app.opts[:content_security_policy]
|
|
279
|
+
policy.dup
|
|
280
|
+
else
|
|
281
|
+
Policy.new
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
yield policy if defined?(yield)
|
|
285
|
+
policy.freeze
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
module InstanceMethods
|
|
289
|
+
# If a block is given, yield the current content security policy. Returns the
|
|
290
|
+
# current content security policy.
|
|
291
|
+
def content_security_policy
|
|
292
|
+
policy = @_response.content_security_policy
|
|
293
|
+
yield policy if defined?(yield)
|
|
294
|
+
policy
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
module ResponseMethods
|
|
299
|
+
# Unset any content security policy when reinitializing
|
|
300
|
+
def initialize
|
|
301
|
+
super
|
|
302
|
+
@content_security_policy &&= nil
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# The current content security policy to be used for this response.
|
|
306
|
+
def content_security_policy
|
|
307
|
+
@content_security_policy ||= roda_class.opts[:content_security_policy].dup
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Do not set a content security policy header for this response.
|
|
311
|
+
def skip_content_security_policy!
|
|
312
|
+
@skip_content_security_policy = true
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
# Set the appropriate content security policy header.
|
|
318
|
+
def set_default_headers
|
|
319
|
+
super
|
|
320
|
+
unless @skip_content_security_policy
|
|
321
|
+
(@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
register_plugin(:content_security_policy, ContentSecurityPolicy)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The cookie_flags plugin allows users to force specific cookie flags for
|
|
7
|
+
# all cookies set by the application. It can also be used to warn or
|
|
8
|
+
# raise for unexpected cookie flags.
|
|
9
|
+
#
|
|
10
|
+
# The cookie_flags plugin deals with the following cookie flags:
|
|
11
|
+
#
|
|
12
|
+
# httponly :: Disallows access to the cookie from client-side scripts.
|
|
13
|
+
# samesite :: Restricts to which domains the cookie is sent.
|
|
14
|
+
# secure :: Instructs the browser to only transmit the cookie over HTTPS.
|
|
15
|
+
#
|
|
16
|
+
# This plugin ships in secure-by-default mode, where it enforces
|
|
17
|
+
# secure, httponly, samesite=strict cookies. You can disable enforcing
|
|
18
|
+
# specific flags using the following options:
|
|
19
|
+
#
|
|
20
|
+
# :httponly :: Set to false to not enforce httponly flag.
|
|
21
|
+
# :same_site :: Set to symbol or string to enforce a different samesite
|
|
22
|
+
# setting, or false to not enforce a specific samesite setting.
|
|
23
|
+
# :secure :: Set to false to not enforce secure flag.
|
|
24
|
+
#
|
|
25
|
+
# For example, to enforce secure cookies and enforce samesite=lax, but not enforce
|
|
26
|
+
# an httponly flag:
|
|
27
|
+
#
|
|
28
|
+
# plugin :cookie_flags, httponly: false, same_site: 'lax'
|
|
29
|
+
#
|
|
30
|
+
# In general, overriding cookie flags using this plugin should be considered a
|
|
31
|
+
# stop-gap solution. Instead of overriding cookie flags, it's better to fix
|
|
32
|
+
# whatever is setting the cookie flags incorrectly. You can use the :action
|
|
33
|
+
# option to modify the behavior:
|
|
34
|
+
#
|
|
35
|
+
# # Issue warnings when modifying cookie flags
|
|
36
|
+
# plugin :cookie_flags, action: :warn_and_modify
|
|
37
|
+
#
|
|
38
|
+
# # Issue warnings for incorrect cookie flags without modifying cookie flags
|
|
39
|
+
# plugin :cookie_flags, action: :warn
|
|
40
|
+
#
|
|
41
|
+
# # Raise errors for incorrect cookie flags
|
|
42
|
+
# plugin :cookie_flags, action: :raise
|
|
43
|
+
#
|
|
44
|
+
# The recommended way to use the plugin is to use it only during testing with
|
|
45
|
+
# <tt>action: :raise</tt>. Then as long as you have fully covering tests, you
|
|
46
|
+
# can be sure the cookies set by your application use the correct flags.
|
|
47
|
+
#
|
|
48
|
+
# Note that this plugin only affects cookies set by the application, and does not
|
|
49
|
+
# affect cookies set by middleware the application is using.
|
|
50
|
+
module CookieFlags
|
|
51
|
+
# :nocov:
|
|
52
|
+
MATCH_METH = RUBY_VERSION >= '2.4' ? :match? : :match
|
|
53
|
+
# :nocov:
|
|
54
|
+
private_constant :MATCH_METH
|
|
55
|
+
|
|
56
|
+
DEFAULTS = {:secure=>true, :httponly=>true, :same_site=>'strict', :action=>:modify}.freeze
|
|
57
|
+
private_constant :DEFAULTS
|
|
58
|
+
|
|
59
|
+
# Error class raised for action: :raise when incorrect cookie flags are used.
|
|
60
|
+
class Error < RodaError
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.configure(app, opts=OPTS)
|
|
64
|
+
previous = app.opts[:cookie_flags] || DEFAULTS
|
|
65
|
+
opts = app.opts[:cookie_flags] = previous.merge(opts)
|
|
66
|
+
|
|
67
|
+
case opts[:same_site]
|
|
68
|
+
when String, Symbol
|
|
69
|
+
opts[:same_site] = opts[:same_site].to_s.downcase.freeze
|
|
70
|
+
opts[:same_site_string] = "; samesite=#{opts[:same_site]}".freeze
|
|
71
|
+
opts[:secure] = true if opts[:same_site] == 'none'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
opts.freeze
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module InstanceMethods
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def _handle_cookie_flags_array(cookies)
|
|
81
|
+
opts = self.class.opts[:cookie_flags]
|
|
82
|
+
needs_secure = opts[:secure]
|
|
83
|
+
needs_httponly = opts[:httponly]
|
|
84
|
+
if needs_same_site = opts[:same_site]
|
|
85
|
+
same_site_string = opts[:same_site_string]
|
|
86
|
+
same_site_regexp = /;\s*samesite\s*=\s*(\S+)\s*(?:\z|;)/i
|
|
87
|
+
end
|
|
88
|
+
action = opts[:action]
|
|
89
|
+
|
|
90
|
+
cookies.map do |cookie|
|
|
91
|
+
if needs_secure
|
|
92
|
+
add_secure = !/;\s*secure\s*(?:\z|;)/i.send(MATCH_METH, cookie)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if needs_httponly
|
|
96
|
+
add_httponly = !/;\s*httponly\s*(?:\z|;)/i.send(MATCH_METH, cookie)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if needs_same_site
|
|
100
|
+
has_same_site = same_site_regexp.match(cookie)
|
|
101
|
+
unless add_same_site = !has_same_site
|
|
102
|
+
update_same_site = needs_same_site != has_same_site[1].downcase
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
next cookie unless add_secure || add_httponly || add_same_site || update_same_site
|
|
107
|
+
|
|
108
|
+
case action
|
|
109
|
+
when :raise, :warn, :warn_and_modify
|
|
110
|
+
message = "Response contains cookie with unexpected flags: #{cookie.inspect}." \
|
|
111
|
+
"Expecting the following cookie flags: "\
|
|
112
|
+
"#{'secure ' if add_secure}#{'httponly ' if add_httponly}#{same_site_string[2..-1] if add_same_site || update_same_site}"
|
|
113
|
+
|
|
114
|
+
if action == :raise
|
|
115
|
+
raise Error, message
|
|
116
|
+
else
|
|
117
|
+
warn(message)
|
|
118
|
+
next cookie if action == :warn
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if update_same_site
|
|
123
|
+
cookie = cookie.gsub(same_site_regexp, same_site_string)
|
|
124
|
+
else
|
|
125
|
+
cookie = cookie.dup
|
|
126
|
+
cookie << same_site_string if add_same_site
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
cookie << '; secure' if add_secure
|
|
130
|
+
cookie << '; httponly' if add_httponly
|
|
131
|
+
|
|
132
|
+
cookie
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if Rack.release >= '3'
|
|
137
|
+
def _handle_cookie_flags(cookies)
|
|
138
|
+
cookies = [cookies] if cookies.is_a?(String)
|
|
139
|
+
_handle_cookie_flags_array(cookies)
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
def _handle_cookie_flags(cookie_string)
|
|
143
|
+
_handle_cookie_flags_array(cookie_string.split("\n")).join("\n")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Handle cookie flags in response
|
|
148
|
+
def _roda_after_85__cookie_flags(res)
|
|
149
|
+
return unless res && (headers = res[1]) && (value = headers[RodaResponseHeaders::SET_COOKIE])
|
|
150
|
+
headers[RodaResponseHeaders::SET_COOKIE] = _handle_cookie_flags(value)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
register_plugin(:cookie_flags, CookieFlags)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack/utils'
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
class Roda
|
|
7
|
+
module RodaPlugins
|
|
8
|
+
# The cookies plugin adds response methods for handling cookies.
|
|
9
|
+
# Currently, you can set cookies with +set_cookie+ and delete cookies
|
|
10
|
+
# with +delete_cookie+:
|
|
11
|
+
#
|
|
12
|
+
# response.set_cookie('foo', 'bar')
|
|
13
|
+
# response.delete_cookie('foo')
|
|
14
|
+
#
|
|
15
|
+
# Pass a hash of cookie options when loading the plugin to set some
|
|
16
|
+
# defaults for all cookies upon setting and deleting. This is particularly
|
|
17
|
+
# useful for configuring the +domain+ and +path+ of all cookies.
|
|
18
|
+
#
|
|
19
|
+
# plugin :cookies, domain: 'example.com', path: '/api'
|
|
20
|
+
module Cookies
|
|
21
|
+
# Allow setting default cookie options when loading the cookies plugin.
|
|
22
|
+
def self.configure(app, opts={})
|
|
23
|
+
app.opts[:cookies_opts] = (app.opts[:cookies_opts]||{}).merge(opts).freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module ResponseMethods
|
|
27
|
+
# Modify the headers to include a Set-Cookie value that
|
|
28
|
+
# deletes the cookie. A value hash can be provided to
|
|
29
|
+
# override the default one used to delete the cookie.
|
|
30
|
+
# Example:
|
|
31
|
+
#
|
|
32
|
+
# response.delete_cookie('foo')
|
|
33
|
+
# response.delete_cookie('foo', domain: 'example.org')
|
|
34
|
+
def delete_cookie(key, value = {})
|
|
35
|
+
::Rack::Utils.delete_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set the cookie with the given key in the headers.
|
|
39
|
+
#
|
|
40
|
+
# response.set_cookie('foo', 'bar')
|
|
41
|
+
# response.set_cookie('foo', value: 'bar', domain: 'example.org')
|
|
42
|
+
def set_cookie(key, value)
|
|
43
|
+
value = { :value=>value } unless value.respond_to?(:keys)
|
|
44
|
+
::Rack::Utils.set_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
register_plugin(:cookies, Cookies)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack/csrf'
|
|
4
|
+
|
|
5
|
+
class Roda
|
|
6
|
+
module RodaPlugins
|
|
7
|
+
# This plugin is no longer recommended for use, it exists only for
|
|
8
|
+
# backwards compatibility. Consider using the route_csrf plugin
|
|
9
|
+
# instead, as that provides stronger CSRF protection.
|
|
10
|
+
#
|
|
11
|
+
# The csrf plugin adds CSRF protection using rack_csrf, along with
|
|
12
|
+
# some csrf helper methods to use in your views. To use it, load
|
|
13
|
+
# the plugin, with the options hash passed to Rack::Csrf:
|
|
14
|
+
#
|
|
15
|
+
# plugin :csrf, raise: true
|
|
16
|
+
#
|
|
17
|
+
# Optionally you can choose not to setup rack_csrf middleware on the
|
|
18
|
+
# roda app if you already have one configured:
|
|
19
|
+
#
|
|
20
|
+
# plugin :csrf, skip_middleware: true
|
|
21
|
+
#
|
|
22
|
+
# This adds the following instance methods:
|
|
23
|
+
#
|
|
24
|
+
# csrf_field :: The field name to use for the hidden/meta csrf tag.
|
|
25
|
+
# csrf_header :: The http header name to use for submitting csrf token via
|
|
26
|
+
# headers (useful for javascript).
|
|
27
|
+
# csrf_metatag :: An html meta tag string containing the token, suitable
|
|
28
|
+
# for placing in the page header
|
|
29
|
+
# csrf_tag :: An html hidden input tag string containing the token, suitable
|
|
30
|
+
# for placing in an html form.
|
|
31
|
+
# csrf_token :: The value of the csrf token, in case it needs to be accessed
|
|
32
|
+
# directly.
|
|
33
|
+
module Csrf
|
|
34
|
+
CSRF = ::Rack::Csrf
|
|
35
|
+
|
|
36
|
+
# Load the Rack::Csrf middleware into the app with the given options.
|
|
37
|
+
def self.configure(app, opts={})
|
|
38
|
+
return if opts[:skip_middleware]
|
|
39
|
+
app.instance_exec do
|
|
40
|
+
@middleware.each do |(mid, *rest), _|
|
|
41
|
+
if mid.equal?(CSRF)
|
|
42
|
+
rest[0].merge!(opts)
|
|
43
|
+
build_rack_app
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
use CSRF, opts
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module InstanceMethods
|
|
52
|
+
# The name of the hidden/meta csrf tag.
|
|
53
|
+
def csrf_field
|
|
54
|
+
CSRF.field
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The http header name to use for submitting csrf token via headers.
|
|
58
|
+
def csrf_header
|
|
59
|
+
CSRF.header
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# An html meta tag string containing the token.
|
|
63
|
+
def csrf_metatag(opts={})
|
|
64
|
+
CSRF.metatag(env, opts)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# An html hidden input tag string containing the token.
|
|
68
|
+
def csrf_tag
|
|
69
|
+
CSRF.tag(env)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The value of the csrf token.
|
|
73
|
+
def csrf_token
|
|
74
|
+
CSRF.token(env)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
register_plugin(:csrf, Csrf)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The custom_block_results plugin allows you to specify handling
|
|
7
|
+
# for different block results. By default, Roda only supports
|
|
8
|
+
# nil, false, and string block results, but using this plugin,
|
|
9
|
+
# you can support other block results.
|
|
10
|
+
#
|
|
11
|
+
# For example, if you wanted to support returning Integer
|
|
12
|
+
# block results, and have them set the response status code,
|
|
13
|
+
# you could do:
|
|
14
|
+
#
|
|
15
|
+
# plugin :custom_block_results
|
|
16
|
+
#
|
|
17
|
+
# handle_block_result Integer do |result|
|
|
18
|
+
# response.status_code = result
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# route do |r|
|
|
22
|
+
# 200
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# The expected use case for this is to customize behavior by
|
|
26
|
+
# class, but matching uses ===, so it is possible to use non-class
|
|
27
|
+
# objects that respond to === appropriately.
|
|
28
|
+
#
|
|
29
|
+
# Note that custom block result handling only occurs if the types
|
|
30
|
+
# are not handled by Roda itself. You cannot use this to modify
|
|
31
|
+
# the handling of nil, false, or string results. Additionally,
|
|
32
|
+
# if the response body has already been written to before the the
|
|
33
|
+
# route block exits, then the result of the block is ignored,
|
|
34
|
+
# and the related +handle_block_result+ block will not be called
|
|
35
|
+
# (this is standard Roda behavior).
|
|
36
|
+
#
|
|
37
|
+
# The return value of the +handle_block_result+ block is written
|
|
38
|
+
# to the body if the block return value is a String, similar to
|
|
39
|
+
# standard Roda handling of block results. Non-String return
|
|
40
|
+
# values are ignored.
|
|
41
|
+
module CustomBlockResults
|
|
42
|
+
def self.configure(app)
|
|
43
|
+
app.opts[:custom_block_results] ||= {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ClassMethods
|
|
47
|
+
# Freeze the configured custom block results when freezing the app.
|
|
48
|
+
def freeze
|
|
49
|
+
opts[:custom_block_results].freeze
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Specify a block that will be called when an instance of klass
|
|
54
|
+
# is returned as a block result. The block defines a method.
|
|
55
|
+
def handle_block_result(klass, &block)
|
|
56
|
+
opts[:custom_block_results][klass] = define_roda_method(opts[:custom_block_results][klass] || "custom_block_result_#{klass}", 1, &block)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module RequestMethods
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Try each configured custom block result, and call the related method
|
|
64
|
+
# to get the block result.
|
|
65
|
+
def unsupported_block_result(result)
|
|
66
|
+
roda_class.opts[:custom_block_results].each do |klass, meth|
|
|
67
|
+
if klass === result
|
|
68
|
+
result = scope.send(meth, result)
|
|
69
|
+
|
|
70
|
+
if String === result
|
|
71
|
+
return result
|
|
72
|
+
else
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
register_plugin(:custom_block_results, CustomBlockResults)
|
|
84
|
+
end
|
|
85
|
+
end
|