roda 3.98.0 → 3.100.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/lib/roda/plugins/run_require_slash.rb +3 -3
- data/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/lib/roda/request.rb +18 -0
- data/lib/roda/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40eb39afa6860fdb8612711cf75ce0f5dfcb73fa3af2837fd2d2a0a0f2a2739f
|
|
4
|
+
data.tar.gz: 0f3b1d45dd9d78c0840e705d61344abcd11312b1d8d0e1cdf5c2cd249ba50bc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3640eeb3784285b94ef078c79eccdd69feebfcab19ee0ec0d6858e7959343511af1c173d590043221484ea99b3dd7ef7a2345a1dc26808e683b5781f6f6a4626
|
|
7
|
+
data.tar.gz: 4c739d613fcff7aa56f5dff3c6d83c26ace1a040aebb2f6ed4808086814f93b4f7858b4a968188fecc7e09eb0b7224ff62e44dcd32db6d1e9f22a09fa74ae029
|
|
@@ -9,7 +9,7 @@ class Roda
|
|
|
9
9
|
# dispatching to the application with an environment that would violate the
|
|
10
10
|
# Rack SPEC.
|
|
11
11
|
#
|
|
12
|
-
# You are unlikely to want to use this plugin unless are consuming partial
|
|
12
|
+
# You are unlikely to want to use this plugin unless you are consuming partial
|
|
13
13
|
# segments of the request path, or using the match_affix plugin to change
|
|
14
14
|
# how routing is done:
|
|
15
15
|
#
|
|
@@ -23,10 +23,10 @@ class Roda
|
|
|
23
23
|
# end
|
|
24
24
|
#
|
|
25
25
|
# # with run_require_slash:
|
|
26
|
-
# # GET /a/b/e =>
|
|
26
|
+
# # GET /a/b/e => Not dispatched to application
|
|
27
27
|
# # GET /a/b => App gets "" as PATH_INFO
|
|
28
28
|
#
|
|
29
|
-
# #
|
|
29
|
+
# # without run_require_slash:
|
|
30
30
|
# # GET /a/b/e => App gets "e" as PATH_INFO, violating rack SPEC
|
|
31
31
|
# # GET /a/b => App gets "" as PATH_INFO
|
|
32
32
|
module RunRequireSlash
|
|
@@ -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/request.rb
CHANGED
|
@@ -12,6 +12,7 @@ else
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
require 'set'
|
|
15
16
|
require_relative "cache"
|
|
16
17
|
|
|
17
18
|
class Roda
|
|
@@ -489,6 +490,21 @@ class Roda
|
|
|
489
490
|
end
|
|
490
491
|
end
|
|
491
492
|
|
|
493
|
+
# Match only if the next segment in the path is one of the
|
|
494
|
+
# set's elements, and yield the next segment.
|
|
495
|
+
def _match_set(set)
|
|
496
|
+
rp = @remaining_path
|
|
497
|
+
if key = _match_class_String
|
|
498
|
+
if set.include?(@captures[-1])
|
|
499
|
+
true
|
|
500
|
+
else
|
|
501
|
+
@remaining_path = rp
|
|
502
|
+
@captures.pop
|
|
503
|
+
false
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
492
508
|
# Match the given symbol if any segment matches.
|
|
493
509
|
def _match_symbol(sym=nil)
|
|
494
510
|
rp = @remaining_path
|
|
@@ -629,6 +645,8 @@ class Roda
|
|
|
629
645
|
_match_array(matcher)
|
|
630
646
|
when Hash
|
|
631
647
|
_match_hash(matcher)
|
|
648
|
+
when Set
|
|
649
|
+
_match_set(matcher)
|
|
632
650
|
when Symbol
|
|
633
651
|
_match_symbol(matcher)
|
|
634
652
|
when false, nil
|
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.100.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Evans
|
|
@@ -284,6 +284,7 @@ files:
|
|
|
284
284
|
- lib/roda/plugins/run_append_slash.rb
|
|
285
285
|
- lib/roda/plugins/run_handler.rb
|
|
286
286
|
- lib/roda/plugins/run_require_slash.rb
|
|
287
|
+
- lib/roda/plugins/sec_fetch_site_csrf.rb
|
|
287
288
|
- lib/roda/plugins/sessions.rb
|
|
288
289
|
- lib/roda/plugins/shared_vars.rb
|
|
289
290
|
- lib/roda/plugins/sinatra_helpers.rb
|
|
@@ -331,7 +332,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
331
332
|
- !ruby/object:Gem::Version
|
|
332
333
|
version: '0'
|
|
333
334
|
requirements: []
|
|
334
|
-
rubygems_version:
|
|
335
|
+
rubygems_version: 4.0.3
|
|
335
336
|
specification_version: 4
|
|
336
337
|
summary: Routing tree web toolkit
|
|
337
338
|
test_files: []
|