sidekiq 7.0.9 → 7.3.10
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/Changes.md +263 -0
- data/README.md +5 -5
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiqload +41 -14
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +170 -52
- data/lib/sidekiq/capsule.rb +8 -3
- data/lib/sidekiq/cli.rb +6 -2
- data/lib/sidekiq/client.rb +58 -22
- data/lib/sidekiq/component.rb +23 -1
- data/lib/sidekiq/config.rb +52 -11
- data/lib/sidekiq/deploy.rb +4 -2
- data/lib/sidekiq/embedded.rb +2 -0
- data/lib/sidekiq/fetch.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +15 -8
- data/lib/sidekiq/job_logger.rb +7 -6
- data/lib/sidekiq/job_retry.rb +30 -8
- data/lib/sidekiq/job_util.rb +6 -2
- data/lib/sidekiq/launcher.rb +8 -6
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +6 -1
- data/lib/sidekiq/metrics/shared.rb +16 -5
- data/lib/sidekiq/metrics/tracking.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +88 -16
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +8 -2
- data/lib/sidekiq/processor.rb +44 -33
- data/lib/sidekiq/rails.rb +28 -7
- data/lib/sidekiq/redis_client_adapter.rb +30 -31
- data/lib/sidekiq/redis_connection.rb +49 -9
- data/lib/sidekiq/ring_buffer.rb +3 -0
- data/lib/sidekiq/scheduled.rb +3 -3
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +32 -13
- data/lib/sidekiq/transaction_aware_client.rb +20 -5
- data/lib/sidekiq/version.rb +5 -1
- data/lib/sidekiq/web/action.rb +29 -7
- data/lib/sidekiq/web/application.rb +64 -25
- data/lib/sidekiq/web/csrf_protection.rb +9 -6
- data/lib/sidekiq/web/helpers.rb +95 -35
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +67 -3
- data/lib/sidekiq.rb +5 -3
- data/sidekiq.gemspec +5 -13
- data/web/assets/javascripts/application.js +27 -0
- data/web/assets/javascripts/dashboard-charts.js +40 -12
- data/web/assets/javascripts/dashboard.js +14 -10
- data/web/assets/javascripts/metrics.js +34 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +38 -3
- data/web/locales/en.yml +5 -1
- data/web/locales/fr.yml +13 -0
- data/web/locales/gd.yml +0 -1
- data/web/locales/it.yml +32 -1
- data/web/locales/ja.yml +0 -1
- data/web/locales/pt-br.yml +20 -1
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +24 -1
- data/web/locales/zh-cn.yml +0 -1
- data/web/locales/zh-tw.yml +0 -1
- data/web/views/_footer.erb +12 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_metrics_period_select.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +7 -7
- data/web/views/dashboard.erb +29 -36
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +38 -30
- data/web/views/metrics_for_job.erb +30 -39
- data/web/views/morgue.erb +2 -2
- data/web/views/queue.erb +1 -1
- data/web/views/queues.erb +6 -2
- metadata +52 -21
data/lib/sidekiq/scheduled.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Sidekiq
|
|
|
12
12
|
|
|
13
13
|
LUA_ZPOPBYSCORE = <<~LUA
|
|
14
14
|
local key, now = KEYS[1], ARGV[1]
|
|
15
|
-
local jobs = redis.call("
|
|
15
|
+
local jobs = redis.call("zrange", key, "-inf", now, "byscore", "limit", 0, 1)
|
|
16
16
|
if jobs[1] then
|
|
17
17
|
redis.call("zrem", key, jobs[1])
|
|
18
18
|
return jobs[1]
|
|
@@ -144,7 +144,7 @@ module Sidekiq
|
|
|
144
144
|
# In the example above, each process should schedule every 10 seconds on average. We special
|
|
145
145
|
# case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
|
|
146
146
|
# As we run more processes, the scheduling interval average will approach an even spread
|
|
147
|
-
# between 0 and poll interval so we don't need this
|
|
147
|
+
# between 0 and poll interval so we don't need this artificial boost.
|
|
148
148
|
#
|
|
149
149
|
count = process_count
|
|
150
150
|
interval = poll_interval_average(count)
|
|
@@ -193,7 +193,7 @@ module Sidekiq
|
|
|
193
193
|
# should never depend on sidekiq/api.
|
|
194
194
|
def cleanup
|
|
195
195
|
# dont run cleanup more than once per minute
|
|
196
|
-
return 0 unless redis { |conn| conn.set("process_cleanup", "1",
|
|
196
|
+
return 0 unless redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
|
|
197
197
|
|
|
198
198
|
count = 0
|
|
199
199
|
redis do |conn|
|
data/lib/sidekiq/systemd.rb
CHANGED
data/lib/sidekiq/testing.rb
CHANGED
|
@@ -5,23 +5,42 @@ require "sidekiq"
|
|
|
5
5
|
|
|
6
6
|
module Sidekiq
|
|
7
7
|
class Testing
|
|
8
|
+
class TestModeAlreadySetError < RuntimeError; end
|
|
8
9
|
class << self
|
|
9
|
-
attr_accessor :
|
|
10
|
+
attr_accessor :__global_test_mode
|
|
10
11
|
|
|
12
|
+
# Calling without a block sets the global test mode, affecting
|
|
13
|
+
# all threads. Calling with a block only affects the current Thread.
|
|
11
14
|
def __set_test_mode(mode)
|
|
12
15
|
if block_given?
|
|
13
|
-
|
|
16
|
+
# Reentrant testing modes will lead to a rat's nest of code which is
|
|
17
|
+
# hard to reason about. You can set the testing mode once globally and
|
|
18
|
+
# you can override that global setting once per-thread.
|
|
19
|
+
raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
|
|
20
|
+
|
|
21
|
+
self.__local_test_mode = mode
|
|
14
22
|
begin
|
|
15
|
-
self.__test_mode = mode
|
|
16
23
|
yield
|
|
17
24
|
ensure
|
|
18
|
-
self.
|
|
25
|
+
self.__local_test_mode = nil
|
|
19
26
|
end
|
|
20
27
|
else
|
|
21
|
-
self.
|
|
28
|
+
self.__global_test_mode = mode
|
|
22
29
|
end
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
def __test_mode
|
|
33
|
+
__local_test_mode || __global_test_mode
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def __local_test_mode
|
|
37
|
+
Thread.current[:__sidekiq_test_mode]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def __local_test_mode=(value)
|
|
41
|
+
Thread.current[:__sidekiq_test_mode] = value
|
|
42
|
+
end
|
|
43
|
+
|
|
25
44
|
def disable!(&block)
|
|
26
45
|
__set_test_mode(:disable, &block)
|
|
27
46
|
end
|
|
@@ -64,7 +83,7 @@ module Sidekiq
|
|
|
64
83
|
class EmptyQueueError < RuntimeError; end
|
|
65
84
|
|
|
66
85
|
module TestingClient
|
|
67
|
-
def
|
|
86
|
+
def atomic_push(conn, payloads)
|
|
68
87
|
if Sidekiq::Testing.fake?
|
|
69
88
|
payloads.each do |job|
|
|
70
89
|
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
@@ -93,7 +112,7 @@ module Sidekiq
|
|
|
93
112
|
# The Queues class is only for testing the fake queue implementation.
|
|
94
113
|
# There are 2 data structures involved in tandem. This is due to the
|
|
95
114
|
# Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
|
|
96
|
-
# to the array. Because the array was
|
|
115
|
+
# to the array. Because the array was derived from a filter of the total
|
|
97
116
|
# jobs enqueued, it appeared as though the array didn't change.
|
|
98
117
|
#
|
|
99
118
|
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
|
@@ -259,16 +278,16 @@ module Sidekiq
|
|
|
259
278
|
def perform_one
|
|
260
279
|
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
|
261
280
|
next_job = jobs.first
|
|
262
|
-
Queues.delete_for(next_job["jid"], queue, to_s)
|
|
281
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
|
263
282
|
process_job(next_job)
|
|
264
283
|
end
|
|
265
284
|
|
|
266
285
|
def process_job(job)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Sidekiq::Testing.server_middleware.invoke(
|
|
271
|
-
execute_job(
|
|
286
|
+
instance = new
|
|
287
|
+
instance.jid = job["jid"]
|
|
288
|
+
instance.bid = job["bid"] if instance.respond_to?(:bid=)
|
|
289
|
+
Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
|
|
290
|
+
execute_job(instance, job["args"])
|
|
272
291
|
end
|
|
273
292
|
end
|
|
274
293
|
|
|
@@ -7,13 +7,26 @@ module Sidekiq
|
|
|
7
7
|
class TransactionAwareClient
|
|
8
8
|
def initialize(pool: nil, config: nil)
|
|
9
9
|
@redis_client = Client.new(pool: pool, config: config)
|
|
10
|
+
@transaction_backend =
|
|
11
|
+
if ActiveRecord.version >= Gem::Version.new("7.2")
|
|
12
|
+
ActiveRecord.method(:after_all_transactions_commit)
|
|
13
|
+
else
|
|
14
|
+
AfterCommitEverywhere.method(:after_commit)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def batching?
|
|
19
|
+
Thread.current[:sidekiq_batch]
|
|
10
20
|
end
|
|
11
21
|
|
|
12
22
|
def push(item)
|
|
23
|
+
# 6160 we can't support both Sidekiq::Batch and transactions.
|
|
24
|
+
return @redis_client.push(item) if batching?
|
|
25
|
+
|
|
13
26
|
# pre-allocate the JID so we can return it immediately and
|
|
14
27
|
# save it to the database as part of the transaction.
|
|
15
28
|
item["jid"] ||= SecureRandom.hex(12)
|
|
16
|
-
|
|
29
|
+
@transaction_backend.call { @redis_client.push(item) }
|
|
17
30
|
item["jid"]
|
|
18
31
|
end
|
|
19
32
|
|
|
@@ -31,10 +44,12 @@ end
|
|
|
31
44
|
# Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer
|
|
32
45
|
module Sidekiq
|
|
33
46
|
def self.transactional_push!
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
if ActiveRecord.version < Gem::Version.new("7.2")
|
|
48
|
+
begin
|
|
49
|
+
require "after_commit_everywhere"
|
|
50
|
+
rescue LoadError
|
|
51
|
+
raise %q(You need ActiveRecord >= 7.2 or to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
|
|
52
|
+
end
|
|
38
53
|
end
|
|
39
54
|
|
|
40
55
|
Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
|
@@ -15,13 +15,19 @@ module Sidekiq
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def halt(res)
|
|
18
|
-
throw :halt, [res, {
|
|
18
|
+
throw :halt, [res, {Rack::CONTENT_TYPE => "text/plain"}, [res.to_s]]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def redirect(location)
|
|
22
|
-
throw :halt, [302, {
|
|
22
|
+
throw :halt, [302, {Web::LOCATION => "#{request.base_url}#{location}"}, []]
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def reload_page
|
|
26
|
+
current_location = request.referer.gsub(request.base_url, "")
|
|
27
|
+
redirect current_location
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# deprecated, will warn in 8.0
|
|
25
31
|
def params
|
|
26
32
|
indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
|
|
27
33
|
|
|
@@ -31,8 +37,19 @@ module Sidekiq
|
|
|
31
37
|
indifferent_hash
|
|
32
38
|
end
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
# Use like `url_params("page")` within your action blocks
|
|
41
|
+
def url_params(key)
|
|
42
|
+
request.params[key]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Use like `route_params(:name)` within your action blocks
|
|
46
|
+
# key is required in 8.0, nil is only used for backwards compatibility
|
|
47
|
+
def route_params(key = nil)
|
|
48
|
+
if key
|
|
49
|
+
env[WebRouter::ROUTE_PARAMS][key]
|
|
50
|
+
else
|
|
51
|
+
env[WebRouter::ROUTE_PARAMS]
|
|
52
|
+
end
|
|
36
53
|
end
|
|
37
54
|
|
|
38
55
|
def session
|
|
@@ -42,8 +59,13 @@ module Sidekiq
|
|
|
42
59
|
def erb(content, options = {})
|
|
43
60
|
if content.is_a? Symbol
|
|
44
61
|
unless respond_to?(:"_erb_#{content}")
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
views = options[:views] || Web.settings.views
|
|
63
|
+
filename = "#{views}/#{content}.erb"
|
|
64
|
+
src = ERB.new(File.read(filename)).src
|
|
65
|
+
|
|
66
|
+
# Need to use lineno less by 1 because erb generates a
|
|
67
|
+
# comment before the source code.
|
|
68
|
+
WebAction.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
|
|
47
69
|
def _erb_#{content}
|
|
48
70
|
#{src}
|
|
49
71
|
end
|
|
@@ -68,7 +90,7 @@ module Sidekiq
|
|
|
68
90
|
end
|
|
69
91
|
|
|
70
92
|
def json(payload)
|
|
71
|
-
[200, {
|
|
93
|
+
[200, {Rack::CONTENT_TYPE => "application/json", Rack::CACHE_CONTROL => "private, no-store"}, [Sidekiq.dump_json(payload)]]
|
|
72
94
|
end
|
|
73
95
|
|
|
74
96
|
def initialize(env, block)
|
|
@@ -5,7 +5,7 @@ module Sidekiq
|
|
|
5
5
|
extend WebRouter
|
|
6
6
|
|
|
7
7
|
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
|
8
|
-
|
|
8
|
+
CSP_HEADER_TEMPLATE = [
|
|
9
9
|
"default-src 'self' https: http:",
|
|
10
10
|
"child-src 'self'",
|
|
11
11
|
"connect-src 'self' https: http: wss: ws:",
|
|
@@ -15,8 +15,8 @@ module Sidekiq
|
|
|
15
15
|
"manifest-src 'self'",
|
|
16
16
|
"media-src 'self'",
|
|
17
17
|
"object-src 'none'",
|
|
18
|
-
"script-src 'self'
|
|
19
|
-
"style-src 'self' https: http: 'unsafe-inline'",
|
|
18
|
+
"script-src 'self' 'nonce-!placeholder!'",
|
|
19
|
+
"style-src 'self' https: http: 'unsafe-inline'", # TODO Nonce in 8.0
|
|
20
20
|
"worker-src 'self'",
|
|
21
21
|
"base-uri 'self'"
|
|
22
22
|
].join("; ").freeze
|
|
@@ -49,13 +49,13 @@ module Sidekiq
|
|
|
49
49
|
|
|
50
50
|
head "/" do
|
|
51
51
|
# HEAD / is the cheapest heartbeat possible,
|
|
52
|
-
# it hits Redis to ensure connectivity
|
|
53
|
-
|
|
54
|
-
""
|
|
52
|
+
# it hits Redis to ensure connectivity and returns
|
|
53
|
+
# the size of the default queue
|
|
54
|
+
Sidekiq.redis { |c| c.llen("queue:default") }.to_s
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
get "/" do
|
|
58
|
-
@redis_info = redis_info.
|
|
58
|
+
@redis_info = redis_info.slice(*REDIS_KEYS)
|
|
59
59
|
days = (params["days"] || 30).to_i
|
|
60
60
|
return halt(401) if days < 1 || days > 180
|
|
61
61
|
|
|
@@ -67,11 +67,15 @@ module Sidekiq
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
get "/metrics" do
|
|
70
|
+
x = params[:substr]
|
|
71
|
+
class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
|
|
72
|
+
|
|
70
73
|
q = Sidekiq::Metrics::Query.new
|
|
71
74
|
@period = h((params[:period] || "")[0..1])
|
|
72
75
|
@periods = METRICS_PERIODS
|
|
73
76
|
minutes = @periods.fetch(@period, @periods.values.first)
|
|
74
|
-
@query_result = q.top_jobs(minutes: minutes)
|
|
77
|
+
@query_result = q.top_jobs(minutes: minutes, class_filter: class_filter)
|
|
78
|
+
|
|
75
79
|
erb(:metrics)
|
|
76
80
|
end
|
|
77
81
|
|
|
@@ -153,9 +157,15 @@ module Sidekiq
|
|
|
153
157
|
end
|
|
154
158
|
|
|
155
159
|
get "/morgue" do
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
x = params[:substr]
|
|
161
|
+
|
|
162
|
+
if x && x != ""
|
|
163
|
+
@dead = search(Sidekiq::DeadSet.new, x)
|
|
164
|
+
else
|
|
165
|
+
@count = (params["count"] || 25).to_i
|
|
166
|
+
(@current_page, @total_size, @dead) = page("dead", params["page"], @count, reverse: true)
|
|
167
|
+
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
|
168
|
+
end
|
|
159
169
|
|
|
160
170
|
erb(:morgue)
|
|
161
171
|
end
|
|
@@ -174,7 +184,7 @@ module Sidekiq
|
|
|
174
184
|
end
|
|
175
185
|
|
|
176
186
|
post "/morgue" do
|
|
177
|
-
redirect(request.path) unless
|
|
187
|
+
redirect(request.path) unless url_params("key")
|
|
178
188
|
|
|
179
189
|
params["key"].each do |key|
|
|
180
190
|
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
|
@@ -197,7 +207,7 @@ module Sidekiq
|
|
|
197
207
|
end
|
|
198
208
|
|
|
199
209
|
post "/morgue/:key" do
|
|
200
|
-
key = route_params
|
|
210
|
+
key = route_params(:key)
|
|
201
211
|
halt(404) unless key
|
|
202
212
|
|
|
203
213
|
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
|
@@ -207,9 +217,15 @@ module Sidekiq
|
|
|
207
217
|
end
|
|
208
218
|
|
|
209
219
|
get "/retries" do
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
220
|
+
x = url_params("substr")
|
|
221
|
+
|
|
222
|
+
if x && x != ""
|
|
223
|
+
@retries = search(Sidekiq::RetrySet.new, x)
|
|
224
|
+
else
|
|
225
|
+
@count = (params["count"] || 25).to_i
|
|
226
|
+
(@current_page, @total_size, @retries) = page("retry", params["page"], @count)
|
|
227
|
+
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
|
228
|
+
end
|
|
213
229
|
|
|
214
230
|
erb(:retries)
|
|
215
231
|
end
|
|
@@ -262,9 +278,15 @@ module Sidekiq
|
|
|
262
278
|
end
|
|
263
279
|
|
|
264
280
|
get "/scheduled" do
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
x = params[:substr]
|
|
282
|
+
|
|
283
|
+
if x && x != ""
|
|
284
|
+
@scheduled = search(Sidekiq::ScheduledSet.new, x)
|
|
285
|
+
else
|
|
286
|
+
@count = (params["count"] || 25).to_i
|
|
287
|
+
(@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
|
|
288
|
+
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
|
289
|
+
end
|
|
268
290
|
|
|
269
291
|
erb(:scheduled)
|
|
270
292
|
end
|
|
@@ -306,7 +328,7 @@ module Sidekiq
|
|
|
306
328
|
|
|
307
329
|
get "/stats" do
|
|
308
330
|
sidekiq_stats = Sidekiq::Stats.new
|
|
309
|
-
redis_stats = redis_info.
|
|
331
|
+
redis_stats = redis_info.slice(*REDIS_KEYS)
|
|
310
332
|
json(
|
|
311
333
|
sidekiq: {
|
|
312
334
|
processed: sidekiq_stats.processed,
|
|
@@ -328,9 +350,21 @@ module Sidekiq
|
|
|
328
350
|
json Sidekiq::Stats.new.queues
|
|
329
351
|
end
|
|
330
352
|
|
|
353
|
+
post "/change_locale" do
|
|
354
|
+
locale = params["locale"]
|
|
355
|
+
|
|
356
|
+
match = available_locales.find { |available|
|
|
357
|
+
locale == available
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
session[:locale] = match if match
|
|
361
|
+
|
|
362
|
+
reload_page
|
|
363
|
+
end
|
|
364
|
+
|
|
331
365
|
def call(env)
|
|
332
366
|
action = self.class.match(env)
|
|
333
|
-
return [404, {
|
|
367
|
+
return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
|
|
334
368
|
|
|
335
369
|
app = @klass
|
|
336
370
|
resp = catch(:halt) do
|
|
@@ -347,16 +381,21 @@ module Sidekiq
|
|
|
347
381
|
else
|
|
348
382
|
# rendered content goes here
|
|
349
383
|
headers = {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
384
|
+
Rack::CONTENT_TYPE => "text/html",
|
|
385
|
+
Rack::CACHE_CONTROL => "private, no-store",
|
|
386
|
+
Web::CONTENT_LANGUAGE => action.locale,
|
|
387
|
+
Web::CONTENT_SECURITY_POLICY => process_csp(env, CSP_HEADER_TEMPLATE),
|
|
388
|
+
Web::X_CONTENT_TYPE_OPTIONS => "nosniff"
|
|
354
389
|
}
|
|
355
390
|
# we'll let Rack calculate Content-Length for us.
|
|
356
391
|
[200, headers, [resp]]
|
|
357
392
|
end
|
|
358
393
|
end
|
|
359
394
|
|
|
395
|
+
def process_csp(env, input)
|
|
396
|
+
input.gsub("!placeholder!", env[:csp_nonce])
|
|
397
|
+
end
|
|
398
|
+
|
|
360
399
|
def self.helpers(mod = nil, &block)
|
|
361
400
|
if block
|
|
362
401
|
WebAction.class_eval(&block)
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
28
28
|
|
|
29
29
|
require "securerandom"
|
|
30
|
-
require "base64"
|
|
31
30
|
require "rack/request"
|
|
32
31
|
|
|
33
32
|
module Sidekiq
|
|
@@ -57,12 +56,12 @@ module Sidekiq
|
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
def logger(env)
|
|
60
|
-
@logger ||=
|
|
59
|
+
@logger ||= env["rack.logger"] || ::Logger.new(env["rack.errors"])
|
|
61
60
|
end
|
|
62
61
|
|
|
63
62
|
def deny(env)
|
|
64
63
|
logger(env).warn "attack prevented by #{self.class}"
|
|
65
|
-
[403, {
|
|
64
|
+
[403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
def session(env)
|
|
@@ -116,7 +115,7 @@ module Sidekiq
|
|
|
116
115
|
sess = session(env)
|
|
117
116
|
localtoken = sess[:csrf]
|
|
118
117
|
|
|
119
|
-
# Checks that Rack::Session::Cookie
|
|
118
|
+
# Checks that Rack::Session::Cookie actually contains the csrf token
|
|
120
119
|
return false if localtoken.nil?
|
|
121
120
|
|
|
122
121
|
# Rotate the session token after every use
|
|
@@ -143,7 +142,7 @@ module Sidekiq
|
|
|
143
142
|
one_time_pad = SecureRandom.random_bytes(token.length)
|
|
144
143
|
encrypted_token = xor_byte_strings(one_time_pad, token)
|
|
145
144
|
masked_token = one_time_pad + encrypted_token
|
|
146
|
-
|
|
145
|
+
encode_token(masked_token)
|
|
147
146
|
end
|
|
148
147
|
|
|
149
148
|
# Essentially the inverse of +mask_token+.
|
|
@@ -168,8 +167,12 @@ module Sidekiq
|
|
|
168
167
|
::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
|
|
169
168
|
end
|
|
170
169
|
|
|
170
|
+
def encode_token(token)
|
|
171
|
+
[token].pack("m0").tr("+/", "-_")
|
|
172
|
+
end
|
|
173
|
+
|
|
171
174
|
def decode_token(token)
|
|
172
|
-
|
|
175
|
+
token.tr("-_", "+/").unpack1("m0")
|
|
173
176
|
end
|
|
174
177
|
|
|
175
178
|
def xor_byte_strings(s1, s2)
|