roda 3.79.0 → 3.80.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +4 -0
- data/doc/release_notes/3.80.0.txt +31 -0
- data/lib/roda/plugins/hmac_paths.rb +117 -10
- data/lib/roda/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cebe935d536e1f903b212075108ba9cbcade4aa2e8d2abf1c5e0d6e6f539ce8
|
4
|
+
data.tar.gz: 9b0c576aaa36c5a05596c1bc1bec23d3ab0f37c56dc72342bfa962b10442928f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e83d0fe8e1f70bb196ea5f285f7a00590ab1669e4b1d686f859d1e5c65a8e760d1320b345306740d00c7d063f0f6bada1af88433b3b04c6b429f439bb7e2521
|
7
|
+
data.tar.gz: e9b0ffd5b5fb7e976a971f11fbed4f0beb35868dcb7aa70c41c005046a9053cb6c62caf29f669c02cd53e67cd5db51a28076f058824c90a1c1b917a9b7f0f4fb
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
= 3.80.0 (2024-05-10)
|
2
|
+
|
3
|
+
* Support :namespace option in hmac_paths plugin, allowing for easy per-user/per-group HMAC paths (jeremyevans)
|
4
|
+
|
1
5
|
= 3.79.0 (2024-04-12)
|
2
6
|
|
3
7
|
* Do not update template mtime when there is an error reloading templates in the render plugin (jeremyevans)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* The hmac_paths plugin now supports a :namespace option for both hmac_path and
|
4
|
+
r.hmac_path. The :namespace option makes the generated HMAC values unique
|
5
|
+
per namespace, allowing easy use of per user/group HMAC paths. This can
|
6
|
+
be useful if the same path will show different information to different
|
7
|
+
users/groups, and you want to prevent path enumeration for each user/group
|
8
|
+
(not allow paths enumerated by one user/group to be valid for a different
|
9
|
+
user/group). Example:
|
10
|
+
|
11
|
+
hmac_path('/widget/1', namespace: '1')
|
12
|
+
# => "/3793ac2a72ea399c40cbd63f154d19f0fe34cdf8d347772134c506a0b756d590/n/widget/1"
|
13
|
+
|
14
|
+
hmac_path('/widget/1', namespace: '2')
|
15
|
+
# => "/0e1e748860d4fd17fe9b7c8259b1e26996502c38e465f802c2c9a0a13000087c/n/widget/1"
|
16
|
+
|
17
|
+
The HMAC path created with namespace: '1' will only be valid when calling
|
18
|
+
r.hmac_path with namespace: '1' (similar for namespace: '2').
|
19
|
+
|
20
|
+
It is expected that the most common use of the :namespace option is to
|
21
|
+
reference session values, so the value of each path depends on the logged in
|
22
|
+
user. You can use the :namespace_session_key plugin option to set the
|
23
|
+
default namespace for both hmac_path and r.hmac_path:
|
24
|
+
|
25
|
+
plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes',
|
26
|
+
namespace_session_key: 'account_id'
|
27
|
+
|
28
|
+
This will use <tt>session['account_id']</tt> (converted to a string) as the namespace
|
29
|
+
for both hmac_path and r.hmac_path, unless a specific :namespace option is
|
30
|
+
given, making it simple to implement per user/group HMAC paths across an
|
31
|
+
application.
|
@@ -112,13 +112,43 @@ class Roda
|
|
112
112
|
# this for POST requests (or other HTTP verbs that can have request bodies), use +r.GET+
|
113
113
|
# instead of +r.params+ to specifically check query string parameters.
|
114
114
|
#
|
115
|
-
#
|
115
|
+
# The :namespace option, if provided, should be a string, and it modifies the generated HMACs
|
116
|
+
# to only match those in the same namespace. This can be used to provide different paths to
|
117
|
+
# different users or groups of users.
|
116
118
|
#
|
117
|
-
# hmac_path('/1',
|
118
|
-
# # => "/
|
119
|
+
# hmac_path('/widget/1', namespace: '1')
|
120
|
+
# # => "/3793ac2a72ea399c40cbd63f154d19f0fe34cdf8d347772134c506a0b756d590/n/widget/1"
|
121
|
+
#
|
122
|
+
# hmac_path('/widget/1', namespace: '2')
|
123
|
+
# # => "/0e1e748860d4fd17fe9b7c8259b1e26996502c38e465f802c2c9a0a13000087c/n/widget/1"
|
124
|
+
#
|
125
|
+
# The +r.hmac_path+ method accepts a :namespace option, and if a :namespace option is
|
126
|
+
# provided, it will only match an hmac path if the namespace given matches the one used
|
127
|
+
# when the hmac path was created.
|
128
|
+
#
|
129
|
+
# r.hmac_path(namespace: '1'){}
|
130
|
+
# # will match "/3793ac2a72ea399c40cbd63f154d19f0fe34cdf8d347772134c506a0b756d590/n/widget/1"
|
131
|
+
# # will not match "/0e1e748860d4fd17fe9b7c8259b1e26996502c38e465f802c2c9a0a13000087c/n/widget/1"
|
132
|
+
#
|
133
|
+
# The most common use of the :namespace option is to reference session values, so the value of
|
134
|
+
# each path depends on the logged in user. You can use the +:namespace_session_key+ plugin
|
135
|
+
# option to set the default namespace for both +hmac_path+ and +r.hmac_path+:
|
136
|
+
#
|
137
|
+
# plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes',
|
138
|
+
# namespace_session_key: 'account_id'
|
139
|
+
#
|
140
|
+
# This will use <tt>session['account_id']</tt> as the default namespace for both +hmac_path+
|
141
|
+
# and +r.hmac_path+ (if the session value is not nil, it is converted to a string using +to_s+).
|
142
|
+
# You can override the default namespace by passing a +:namespace+ option when calling +hmac_path+
|
143
|
+
# and +r.hmac_path+.
|
144
|
+
#
|
145
|
+
# You can use +:root+, +:method+, +:params+, and +:namespace+ at the same time:
|
146
|
+
#
|
147
|
+
# hmac_path('/1', root: '/widget', method: :get, params: {foo: 'bar'}, namespace: '1')
|
148
|
+
# # => "/widget/c14c78a81d34d766cf334a3ddbb7a6b231bc2092ef50a77ded0028586027b14e/mpn/1?foo=bar"
|
119
149
|
#
|
120
150
|
# This gives you a path only valid for a GET request with a root of <tt>/widget</tt> and
|
121
|
-
# a query string of <tt>foo=bar</tt
|
151
|
+
# a query string of <tt>foo=bar</tt>, using namespace +1+.
|
122
152
|
#
|
123
153
|
# To handle secret rotation, you can provide an +:old_secret+ option when loading the
|
124
154
|
# plugin.
|
@@ -128,6 +158,43 @@ class Roda
|
|
128
158
|
#
|
129
159
|
# This will use +:secret+ for constructing new paths, but will respect paths generated by
|
130
160
|
# +:old_secret+.
|
161
|
+
#
|
162
|
+
# = HMAC Construction
|
163
|
+
#
|
164
|
+
# This describes the internals for how HMACs are constructed based on the options provided
|
165
|
+
# to +hmac_path+. In the examples below:
|
166
|
+
#
|
167
|
+
# * +HMAC+ is the raw HMAC-SHA256 output (first argument is secret, second is data)
|
168
|
+
# * +HMAC_hex+ is the hexidecimal version of +HMAC+
|
169
|
+
# * +secret+ is the plugin :secret option
|
170
|
+
#
|
171
|
+
# The +:secret+ plugin option is never used directly as the HMAC secret. All HMACs are
|
172
|
+
# generated with a root-specific secret. The root will be the empty if no +:root+ option
|
173
|
+
# is given. The hmac path flags are always included in the hmac calculation, prepended to the
|
174
|
+
# path:
|
175
|
+
#
|
176
|
+
# r.hmac_path('/1')
|
177
|
+
# HMAC_hex(HMAC_hex(secret, ''), '/0/1')
|
178
|
+
#
|
179
|
+
# r.hmac_path('/1', root: '/2')
|
180
|
+
# HMAC_hex(HMAC_hex(secret, '/2'), '/0/1')
|
181
|
+
#
|
182
|
+
# The +:method+ option uses an uppercase version of the method prepended to the path. This
|
183
|
+
# cannot conflict with the path itself, since paths must start with a slash.
|
184
|
+
#
|
185
|
+
# r.hmac_path('/1', method: :get)
|
186
|
+
# HMAC_hex(HMAC_hex(secret, ''), 'GET:/m/1')
|
187
|
+
#
|
188
|
+
# The +:params+ option includes the query string for the params in the HMAC:
|
189
|
+
#
|
190
|
+
# r.hmac_path('/1', params: {k: 2})
|
191
|
+
# HMAC_hex(HMAC_hex(secret, ''), '/p/1?k=2')
|
192
|
+
#
|
193
|
+
# If a +:namespace+ option is provided, the original secret used before the +:root+ option is
|
194
|
+
# an HMAC of the +:secret+ plugin option and the given namespace.
|
195
|
+
#
|
196
|
+
# r.hmac_path('/1', namespace: '2')
|
197
|
+
# HMAC_hex(HMAC_hex(HMAC(secret, '2'), ''), '/n/1')
|
131
198
|
module HmacPaths
|
132
199
|
def self.configure(app, opts=OPTS)
|
133
200
|
hmac_secret = opts[:secret]
|
@@ -143,6 +210,10 @@ class Roda
|
|
143
210
|
|
144
211
|
app.opts[:hmac_paths_secret] = hmac_secret
|
145
212
|
app.opts[:hmac_paths_old_secret] = hmac_old_secret
|
213
|
+
|
214
|
+
if opts[:namespace_session_key]
|
215
|
+
app.opts[:hmac_paths_namespace_session_key] = opts[:namespace_session_key]
|
216
|
+
end
|
146
217
|
end
|
147
218
|
|
148
219
|
module InstanceMethods
|
@@ -152,6 +223,9 @@ class Roda
|
|
152
223
|
# valid paths. The given path should be a string starting with +/+. Options:
|
153
224
|
#
|
154
225
|
# :method :: Limits the returned path to only be valid for the given request method.
|
226
|
+
# :namespace :: Make the HMAC value depend on the given namespace. If this is not
|
227
|
+
# provided, the default namespace is used. To explicitly not use a
|
228
|
+
# namespace when there is a default namespace, pass a nil value.
|
155
229
|
# :params :: Includes parameters in the query string of the returned path, and
|
156
230
|
# limits the returned path to only be valid for that exact query string.
|
157
231
|
# :root :: Should be an empty string or string starting with +/+. This will be
|
@@ -180,6 +254,10 @@ class Roda
|
|
180
254
|
path << '?' << Rack::Utils.build_query(params)
|
181
255
|
end
|
182
256
|
|
257
|
+
if hmac_path_namespace(opts)
|
258
|
+
flags << 'n'
|
259
|
+
end
|
260
|
+
|
183
261
|
flags << '0' if flags.empty?
|
184
262
|
|
185
263
|
hmac_path = if method
|
@@ -188,7 +266,7 @@ class Roda
|
|
188
266
|
"/#{flags}#{path}"
|
189
267
|
end
|
190
268
|
|
191
|
-
"#{root}/#{hmac_path_hmac(root, hmac_path)}/#{flags}#{path}"
|
269
|
+
"#{root}/#{hmac_path_hmac(root, hmac_path, opts)}/#{flags}#{path}"
|
192
270
|
end
|
193
271
|
|
194
272
|
# The HMAC to use in hmac_path, for the given root, path, and options.
|
@@ -196,14 +274,34 @@ class Roda
|
|
196
274
|
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_path_hmac_secret(root, opts), path)
|
197
275
|
end
|
198
276
|
|
277
|
+
# The namespace to use for the hmac path. If a :namespace option is not
|
278
|
+
# provided, and a :namespace_session_key option was provided, this will
|
279
|
+
# use the value of the related session key, if present.
|
280
|
+
def hmac_path_namespace(opts=OPTS)
|
281
|
+
opts.fetch(:namespace){hmac_path_default_namespace}
|
282
|
+
end
|
283
|
+
|
199
284
|
private
|
200
285
|
|
201
286
|
# The secret used to calculate the HMAC in hmac_path. This is itself an HMAC, created
|
202
|
-
# using the secret given in the plugin, for the given root and options.
|
287
|
+
# using the secret given in the plugin, for the given root and options.
|
288
|
+
# This always returns a hexidecimal string.
|
203
289
|
def hmac_path_hmac_secret(root, opts=OPTS)
|
204
290
|
secret = opts[:secret] || self.opts[:hmac_paths_secret]
|
291
|
+
|
292
|
+
if namespace = hmac_path_namespace(opts)
|
293
|
+
secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, namespace)
|
294
|
+
end
|
295
|
+
|
205
296
|
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, secret, root)
|
206
297
|
end
|
298
|
+
|
299
|
+
# The default namespace to use for hmac_path, if a :namespace option is not provided.
|
300
|
+
def hmac_path_default_namespace
|
301
|
+
if (key = opts[:hmac_paths_namespace_session_key]) && (value = session[key])
|
302
|
+
value.to_s
|
303
|
+
end
|
304
|
+
end
|
207
305
|
end
|
208
306
|
|
209
307
|
module RequestMethods
|
@@ -221,6 +319,13 @@ class Roda
|
|
221
319
|
if submitted_hmac.bytesize == 64
|
222
320
|
on String do |flags|
|
223
321
|
if flags.bytesize >= 1
|
322
|
+
if flags.include?('n') ^ !scope.hmac_path_namespace(opts).nil?
|
323
|
+
# Namespace required and not provided, or provided and not required.
|
324
|
+
# Bail early to avoid unnecessary HMAC calculation.
|
325
|
+
@remaining_path = orig_path
|
326
|
+
return
|
327
|
+
end
|
328
|
+
|
224
329
|
if flags.include?('m')
|
225
330
|
rpath = "#{env['REQUEST_METHOD'].to_s.upcase}:#{rpath}"
|
226
331
|
end
|
@@ -229,7 +334,7 @@ class Roda
|
|
229
334
|
rpath = "#{rpath}?#{env["QUERY_STRING"]}"
|
230
335
|
end
|
231
336
|
|
232
|
-
if hmac_path_valid?(mpath, rpath, submitted_hmac)
|
337
|
+
if hmac_path_valid?(mpath, rpath, submitted_hmac, opts)
|
233
338
|
always(&block)
|
234
339
|
end
|
235
340
|
end
|
@@ -249,11 +354,13 @@ class Roda
|
|
249
354
|
private
|
250
355
|
|
251
356
|
# Determine whether the provided hmac matches.
|
252
|
-
def hmac_path_valid?(root, path, hmac)
|
253
|
-
if Rack::Utils.secure_compare(scope.hmac_path_hmac(root, path), hmac)
|
357
|
+
def hmac_path_valid?(root, path, hmac, opts=OPTS)
|
358
|
+
if Rack::Utils.secure_compare(scope.hmac_path_hmac(root, path, opts), hmac)
|
254
359
|
true
|
255
360
|
elsif old_secret = roda_class.opts[:hmac_paths_old_secret]
|
256
|
-
|
361
|
+
opts = opts.dup
|
362
|
+
opts[:secret] = old_secret
|
363
|
+
Rack::Utils.secure_compare(scope.hmac_path_hmac(root, path, opts), hmac)
|
257
364
|
else
|
258
365
|
false
|
259
366
|
end
|
data/lib/roda/version.rb
CHANGED
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.80.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: 2024-
|
11
|
+
date: 2024-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -254,6 +254,7 @@ extra_rdoc_files:
|
|
254
254
|
- doc/release_notes/3.78.0.txt
|
255
255
|
- doc/release_notes/3.79.0.txt
|
256
256
|
- doc/release_notes/3.8.0.txt
|
257
|
+
- doc/release_notes/3.80.0.txt
|
257
258
|
- doc/release_notes/3.9.0.txt
|
258
259
|
files:
|
259
260
|
- CHANGELOG
|
@@ -340,6 +341,7 @@ files:
|
|
340
341
|
- doc/release_notes/3.78.0.txt
|
341
342
|
- doc/release_notes/3.79.0.txt
|
342
343
|
- doc/release_notes/3.8.0.txt
|
344
|
+
- doc/release_notes/3.80.0.txt
|
343
345
|
- doc/release_notes/3.9.0.txt
|
344
346
|
- lib/roda.rb
|
345
347
|
- lib/roda/cache.rb
|
@@ -505,7 +507,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
505
507
|
- !ruby/object:Gem::Version
|
506
508
|
version: '0'
|
507
509
|
requirements: []
|
508
|
-
rubygems_version: 3.5.
|
510
|
+
rubygems_version: 3.5.9
|
509
511
|
signing_key:
|
510
512
|
specification_version: 4
|
511
513
|
summary: Routing tree web toolkit
|