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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9be24f058ca3ba9ce75b21d49af505eb2bd1dbfc40b2bfc1841bcce1c02e100
4
- data.tar.gz: 1ee920f5d61b7fee7d049e0849a3fee48b3d926a141d16c4fe7ce58171b60fe3
3
+ metadata.gz: 40eb39afa6860fdb8612711cf75ce0f5dfcb73fa3af2837fd2d2a0a0f2a2739f
4
+ data.tar.gz: 0f3b1d45dd9d78c0840e705d61344abcd11312b1d8d0e1cdf5c2cd249ba50bc9
5
5
  SHA512:
6
- metadata.gz: 3a1d64641747146ee096ecc99cb2407b75d0f1182cb732a174721354248a762a4bb362a4fb19337725430941da079c87944b16d7cc1f72f8a5aa072eae3abdad
7
- data.tar.gz: d5e8e3f3b720c33c41cd17d953b96851abe6032974e665856aa5896e2b1c6d436eb67372464ce4f44e30eb471e512bfa7c692d7cfaac31944d8bfed60eb72bbd
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 => App not dispatched to
26
+ # # GET /a/b/e => Not dispatched to application
27
27
  # # GET /a/b => App gets "" as PATH_INFO
28
28
  #
29
- # # with run_require_slash:
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
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 98
7
+ RodaMinorVersion = 100
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
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.98.0
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: 3.6.9
335
+ rubygems_version: 4.0.3
335
336
  specification_version: 4
336
337
  summary: Routing tree web toolkit
337
338
  test_files: []