roda 3.99.0 → 3.101.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/MIT-LICENSE +1 -1
- data/lib/roda/plugins/bearer_token.rb +28 -0
- data/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/lib/roda/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7b1458c27a93cf829302766fa6299abadc1b52a7cc45375c23e588d0a28bbf7
|
|
4
|
+
data.tar.gz: d9312bd50698b9eba385272903d3c12a04f5d699185f299b5ddc250fdcbac2ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1483575934392739f5ee8c79cd74ca78f434ba9d798d9c2066f24f737f39fbfd0c0ad818acbd28b113ffaedf7751b3c45302ee17f8590c1dd47d6072de785453
|
|
7
|
+
data.tar.gz: 20f6bc4308bcebd17abc063c4303487d80fd2ac63a1a35b35b99ae0ed2a2a82cf2c5effb480464817a3c1fbed343006a74547743022b1b2e2717c16164f06740
|
data/MIT-LICENSE
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The bearer_token plugin adds an +r.bearer_token+ method for retrieving
|
|
7
|
+
# a bearer token from the +Authorization+ HTTP header. Bearer tokens will
|
|
8
|
+
# in the authorization header will be recognized as long as they start
|
|
9
|
+
# with the case insensitive string "bearer ".
|
|
10
|
+
module BearerToken
|
|
11
|
+
# :nocov:
|
|
12
|
+
METHOD = RUBY_VERSION >= "2.4" ? :match? : :match
|
|
13
|
+
# :nocov:
|
|
14
|
+
|
|
15
|
+
module RequestMethods
|
|
16
|
+
# Return the bearer token for the request if there is one in the
|
|
17
|
+
# authorization HTTP header.
|
|
18
|
+
def bearer_token
|
|
19
|
+
if (auth = @env["HTTP_AUTHORIZATION"]) && auth.send(METHOD, /\Abearer /i)
|
|
20
|
+
auth[7, 100000000]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
register_plugin(:bearer_token, BearerToken)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
class Roda
|
|
4
|
+
module RodaPlugins
|
|
5
|
+
# The sec_fetch_site plugin allows for CSRF protection using the
|
|
6
|
+
# Sec-Fetch-Site header added in modern browsers. It allows for CSRF
|
|
7
|
+
# protection without the use of CSRF tokens, which can simplify
|
|
8
|
+
# form creation.
|
|
9
|
+
#
|
|
10
|
+
# The protection offered by the sec_fetch_site plugin is weaker than
|
|
11
|
+
# the protection offered by the route_csrf plugin with default settings,
|
|
12
|
+
# since it doesn't support per-request tokens. Be aware you are trading
|
|
13
|
+
# security for simplicity when using the sec_fetch_site plugin instead
|
|
14
|
+
# of the route_csrf plugin. Other caveats in using the sec_fetch_site
|
|
15
|
+
# plugin:
|
|
16
|
+
#
|
|
17
|
+
# * Not all browsers set the Sec-Fetch-Site header. Some browsers
|
|
18
|
+
# didn't start setting the header until 2023. In these cases, you
|
|
19
|
+
# need to decide how to handle the request. The default is to deny
|
|
20
|
+
# the request, though you can use the :allow_missing option to allow
|
|
21
|
+
# it.
|
|
22
|
+
#
|
|
23
|
+
# * Sec-Fetch-Site headers are not set for http requests, only https
|
|
24
|
+
# requests, so this doesn't offer protection for http requests.
|
|
25
|
+
#
|
|
26
|
+
# * It isn't possible to share a CSRF secret between applications in
|
|
27
|
+
# different origins to allow cross-site requests between the
|
|
28
|
+
# applications.
|
|
29
|
+
#
|
|
30
|
+
# This plugin adds the +check_sec_fetch_site!+ method to the routing
|
|
31
|
+
# block scope. You should call this method at the appropriate place
|
|
32
|
+
# in the routing tree to enforce the CSRF protection. The method can
|
|
33
|
+
# accept a block to override the :csrf_failure plugin option behavior
|
|
34
|
+
# on a per-call basis.
|
|
35
|
+
#
|
|
36
|
+
# When loading the plugin with no options:
|
|
37
|
+
#
|
|
38
|
+
# plugin :sec_fetch_site_csrf
|
|
39
|
+
#
|
|
40
|
+
# Only same-origin requests are allowed by default.
|
|
41
|
+
#
|
|
42
|
+
# This plugin supports the following options:
|
|
43
|
+
#
|
|
44
|
+
# :allow_missing :: Whether to allow requests lacking the Sec-Fetch-Site
|
|
45
|
+
# header (false by default).
|
|
46
|
+
# :allow_none :: Whether to allow requests where Sec-Fetch-Value is none
|
|
47
|
+
# (false by default).
|
|
48
|
+
# :allow_same_site :: Whether to allow requests where Sec-Fetch-Value is
|
|
49
|
+
# same-site (false by default)
|
|
50
|
+
# :check_request_methods :: Which request methods require CSRF protection
|
|
51
|
+
# (default: <tt>['POST', 'DELETE', 'PATCH', 'PUT']</tt>)
|
|
52
|
+
# :csrf_failure :: The action to taken if a request does not have a valid header
|
|
53
|
+
# (default: :raise). Options:
|
|
54
|
+
# :raise :: raise a Roda::RodaPlugins::SecFetchSiteCsrf::CsrfFailure
|
|
55
|
+
# exception
|
|
56
|
+
# :empty_403 :: return a blank 403 page
|
|
57
|
+
# :clear_session :: clear the current session
|
|
58
|
+
#
|
|
59
|
+
# The plugin also supports a block, in which case failures will call the block
|
|
60
|
+
# as a routing block (the block should accept the request object).
|
|
61
|
+
module SecFetchSiteCsrf
|
|
62
|
+
DEFAULTS = {
|
|
63
|
+
:csrf_failure => :raise,
|
|
64
|
+
:check_request_methods => %w'POST DELETE PATCH PUT'.freeze.each(&:freeze)
|
|
65
|
+
}.freeze
|
|
66
|
+
|
|
67
|
+
# Exception class raised when :csrf_failure option is :raise and
|
|
68
|
+
# the Sec-Fetch-Site header is not considered valid.
|
|
69
|
+
class CsrfFailure < RodaError; end
|
|
70
|
+
|
|
71
|
+
def self.configure(app, opts=OPTS, &block)
|
|
72
|
+
options = app.opts[:sec_fetch_site_csrf] = (app.opts[:sec_fetch_site_csrf] || DEFAULTS).merge(opts)
|
|
73
|
+
|
|
74
|
+
allowed_values = options[:allowed_values] = ["same-origin"]
|
|
75
|
+
allowed_values << "same-site" if opts[:allow_same_site]
|
|
76
|
+
allowed_values << "none" if opts[:allow_none]
|
|
77
|
+
allowed_values << nil if opts[:allow_missing]
|
|
78
|
+
allowed_values.freeze
|
|
79
|
+
|
|
80
|
+
if block
|
|
81
|
+
options[:csrf_failure] = :method
|
|
82
|
+
app.define_roda_method(:_roda_sec_fetch_site_csrf_failure, 1, &app.send(:convert_route_block, block))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
case options[:csrf_failure]
|
|
86
|
+
when :raise, :empty_403, :clear_session, :method
|
|
87
|
+
# nothing
|
|
88
|
+
else
|
|
89
|
+
raise RodaError, "Unsupported :csrf_failure plugin option: #{options[:csrf_failure].inspect}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
options.freeze
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
module InstanceMethods
|
|
96
|
+
# Check that the Sec-Fetch-Site header is valid, if the request requires it.
|
|
97
|
+
# If the header is valid or the request does not require the header, return nil.
|
|
98
|
+
# Otherwise, if a block is given, treat it as a routing block and yield to it, and
|
|
99
|
+
# if a block is not given, use the plugin :csrf_failure option to determine how to
|
|
100
|
+
# handle it.
|
|
101
|
+
def check_sec_fetch_site!(&block)
|
|
102
|
+
plugin_opts = self.class.opts[:sec_fetch_site_csrf]
|
|
103
|
+
return unless plugin_opts[:check_request_methods].include?(request.request_method)
|
|
104
|
+
|
|
105
|
+
sec_fetch_site = env["HTTP_SEC_FETCH_SITE"]
|
|
106
|
+
return if plugin_opts[:allowed_values].include?(sec_fetch_site)
|
|
107
|
+
|
|
108
|
+
@_request.on(&block) if block
|
|
109
|
+
|
|
110
|
+
case failure_action = plugin_opts[:csrf_failure]
|
|
111
|
+
when :raise
|
|
112
|
+
raise CsrfFailure, "potential cross-site request, Sec-Fetch-Site value: #{sec_fetch_site.inspect}"
|
|
113
|
+
when :empty_403
|
|
114
|
+
@_response.status = 403
|
|
115
|
+
headers = @_response.headers
|
|
116
|
+
headers.clear
|
|
117
|
+
headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html'
|
|
118
|
+
headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
|
|
119
|
+
throw :halt, @_response.finish_with_body([])
|
|
120
|
+
when :clear_session
|
|
121
|
+
session.clear
|
|
122
|
+
else # when :method
|
|
123
|
+
@_request.on{_roda_sec_fetch_site_csrf_failure(@_request)}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
register_plugin(:sec_fetch_site_csrf, SecFetchSiteCsrf)
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/roda/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: roda
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.101.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Evans
|
|
@@ -176,6 +176,7 @@ files:
|
|
|
176
176
|
- lib/roda/plugins/autoload_hash_branches.rb
|
|
177
177
|
- lib/roda/plugins/autoload_named_routes.rb
|
|
178
178
|
- lib/roda/plugins/backtracking_array.rb
|
|
179
|
+
- lib/roda/plugins/bearer_token.rb
|
|
179
180
|
- lib/roda/plugins/branch_locals.rb
|
|
180
181
|
- lib/roda/plugins/break.rb
|
|
181
182
|
- lib/roda/plugins/caching.rb
|
|
@@ -284,6 +285,7 @@ files:
|
|
|
284
285
|
- lib/roda/plugins/run_append_slash.rb
|
|
285
286
|
- lib/roda/plugins/run_handler.rb
|
|
286
287
|
- lib/roda/plugins/run_require_slash.rb
|
|
288
|
+
- lib/roda/plugins/sec_fetch_site_csrf.rb
|
|
287
289
|
- lib/roda/plugins/sessions.rb
|
|
288
290
|
- lib/roda/plugins/shared_vars.rb
|
|
289
291
|
- lib/roda/plugins/sinatra_helpers.rb
|
|
@@ -331,7 +333,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
331
333
|
- !ruby/object:Gem::Version
|
|
332
334
|
version: '0'
|
|
333
335
|
requirements: []
|
|
334
|
-
rubygems_version:
|
|
336
|
+
rubygems_version: 4.0.3
|
|
335
337
|
specification_version: 4
|
|
336
338
|
summary: Routing tree web toolkit
|
|
337
339
|
test_files: []
|