sidekiq 8.0.9 → 8.1.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/Changes.md +29 -0
- data/README.md +15 -0
- data/bin/lint-herb +13 -0
- data/lib/sidekiq/api.rb +17 -7
- data/lib/sidekiq/cli.rb +16 -4
- data/lib/sidekiq/config.rb +4 -4
- data/lib/sidekiq/job.rb +2 -0
- data/lib/sidekiq/job_retry.rb +7 -3
- data/lib/sidekiq/launcher.rb +8 -8
- data/lib/sidekiq/redis_connection.rb +2 -2
- data/lib/sidekiq/scheduled.rb +7 -5
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +1 -1
- data/lib/sidekiq/web/config.rb +3 -6
- data/lib/sidekiq/web/helpers.rb +3 -3
- data/lib/sidekiq/web.rb +23 -4
- data/sidekiq.gemspec +5 -5
- data/web/assets/javascripts/application.js +1 -1
- data/web/assets/stylesheets/style.css +0 -2
- data/web/locales/fr.yml +1 -1
- data/web/views/{filtering.erb → filtering.html.erb} +2 -2
- data/web/views/{metrics.erb → metrics.html.erb} +3 -2
- data/web/views/{morgue.erb → morgue.html.erb} +1 -1
- data/web/views/{retries.erb → retries.html.erb} +1 -1
- data/web/views/{scheduled.erb → scheduled.html.erb} +1 -1
- metadata +34 -34
- data/lib/sidekiq/web/csrf_protection.rb +0 -183
- /data/web/views/{_footer.erb → _footer.html.erb} +0 -0
- /data/web/views/{_job_info.erb → _job_info.html.erb} +0 -0
- /data/web/views/{_metrics_period_select.erb → _metrics_period_select.html.erb} +0 -0
- /data/web/views/{_nav.erb → _nav.html.erb} +0 -0
- /data/web/views/{_paging.erb → _paging.html.erb} +0 -0
- /data/web/views/{_poll_link.erb → _poll_link.html.erb} +0 -0
- /data/web/views/{_summary.erb → _summary.html.erb} +0 -0
- /data/web/views/{busy.erb → busy.html.erb} +0 -0
- /data/web/views/{dashboard.erb → dashboard.html.erb} +0 -0
- /data/web/views/{dead.erb → dead.html.erb} +0 -0
- /data/web/views/{layout.erb → layout.html.erb} +0 -0
- /data/web/views/{metrics_for_job.erb → metrics_for_job.html.erb} +0 -0
- /data/web/views/{profiles.erb → profiles.html.erb} +0 -0
- /data/web/views/{queue.erb → queue.html.erb} +0 -0
- /data/web/views/{queues.erb → queues.html.erb} +0 -0
- /data/web/views/{retry.erb → retry.html.erb} +0 -0
- /data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 63658318932ac6b7045211590b07f84ed9e1e6398f908ace790a0df20d5ac1f0
|
|
4
|
+
data.tar.gz: 0c3f5e69ada7529bdac7392acb1c56ef2a19b8ed13210be8c36aac19858c6df4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a5e95a2faccbbf42f8e651c4882c9495ad72b0500bb8fe77fa2a93fb4aa6f8cb96fc820621cd1910fb4b00accf35d75a99d548b3cc8ccdd41a18b5620a9ad73
|
|
7
|
+
data.tar.gz: 1bc66490ee07c548c71f680fb280e876efa7eeb8c6f234277df73211ce05c5ae29f5c9bc13064972da945568d1df6634252a6c22a7c5298bdd436b7266246c53
|
data/Changes.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
[Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
|
|
4
4
|
|
|
5
|
+
8.1.0
|
|
6
|
+
----------
|
|
7
|
+
|
|
8
|
+
- `retry_for` and `retry` are now mutually exclusive [#6878, Saidbek]
|
|
9
|
+
- `perform_inline` now enforces `strict_args!` [#6718, Saidbek]
|
|
10
|
+
- Integrate Herb linting for ERB templates [#6760, Saidbek]
|
|
11
|
+
- Remove CSRF code, use `Sec-Fetch-Site` header [#6874, deve1212]
|
|
12
|
+
- Allow custom Web UI `assets_path` for CDN purposes [#6865, stanhu]
|
|
13
|
+
- Upgrade to connection_pool 3.0
|
|
14
|
+
- Allow idle connection reaping after N seconds.
|
|
15
|
+
You can activate this **beta** feature like below.
|
|
16
|
+
Feedback requested: is this feature stable and useful for you in production?
|
|
17
|
+
This feature may or may not be enabled by default in Sidekiq 9.0.
|
|
18
|
+
```ruby
|
|
19
|
+
Sidekiq.configure_server do |cfg|
|
|
20
|
+
cfg.reap_idle_redis_connections(60)
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
8.0.10
|
|
25
|
+
----------
|
|
26
|
+
|
|
27
|
+
- Add confirm dialog for Delete All buttons in Web UI [#6853]
|
|
28
|
+
- Adjust scheduler to run closer to poll average [#6866]
|
|
29
|
+
- Forward compatibility changes for connection_pool 3.0.0
|
|
30
|
+
- Backwards compatibility fix for <8.0.9 process data in Redis [#6870]
|
|
31
|
+
- Backtrace dump can now be triggered with the INFO signal, since Puma uses the
|
|
32
|
+
same signal [#6857]
|
|
33
|
+
|
|
5
34
|
8.0.9
|
|
6
35
|
----------
|
|
7
36
|
|
data/README.md
CHANGED
|
@@ -97,6 +97,21 @@ Contributing
|
|
|
97
97
|
|
|
98
98
|
See [the contributing guidelines](https://github.com/sidekiq/sidekiq/blob/main/.github/contributing.md).
|
|
99
99
|
|
|
100
|
+
### ERB Linting with HERB
|
|
101
|
+
|
|
102
|
+
This project uses [HERB](https://herb-tools.dev/) for ERB file linting and formatting. All ERB files have been renamed to use the `.html.erb` extension for better tooling support.
|
|
103
|
+
|
|
104
|
+
**Local Development:**
|
|
105
|
+
```bash
|
|
106
|
+
# Run HERB linting
|
|
107
|
+
bundle exec rake lint:herb
|
|
108
|
+
# or
|
|
109
|
+
bin/lint-herb
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**CI Integration:**
|
|
113
|
+
HERB linting is automatically run in CI to ensure all ERB files are properly formatted and free of parse errors.
|
|
114
|
+
|
|
100
115
|
License
|
|
101
116
|
-----------------
|
|
102
117
|
|
data/bin/lint-herb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# HERB Linting Script
|
|
5
|
+
# Run this script to lint all ERB files in the project
|
|
6
|
+
# Usage: bin/lint-herb
|
|
7
|
+
|
|
8
|
+
require "bundler/setup"
|
|
9
|
+
|
|
10
|
+
puts "🔍 Running HERB linting on ERB files..."
|
|
11
|
+
puts
|
|
12
|
+
|
|
13
|
+
exec("bundle exec herb analyze web/views -n --no-log-file")
|
data/lib/sidekiq/api.rb
CHANGED
|
@@ -1091,19 +1091,29 @@ module Sidekiq
|
|
|
1091
1091
|
|
|
1092
1092
|
# deprecated, use capsules below
|
|
1093
1093
|
def queues
|
|
1094
|
-
|
|
1094
|
+
# Backwards compatibility with <8.0.8
|
|
1095
|
+
if !self["capsules"]
|
|
1096
|
+
self["queues"]
|
|
1097
|
+
else
|
|
1098
|
+
capsules.values.flat_map { |x| x["weights"].keys }.uniq
|
|
1099
|
+
end
|
|
1095
1100
|
end
|
|
1096
1101
|
|
|
1097
1102
|
# deprecated, use capsules below
|
|
1098
1103
|
def weights
|
|
1099
|
-
|
|
1100
|
-
capsules
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
+
# Backwards compatibility with <8.0.8
|
|
1105
|
+
if !self["capsules"]
|
|
1106
|
+
self["weights"]
|
|
1107
|
+
else
|
|
1108
|
+
hash = {}
|
|
1109
|
+
capsules.values.each do |cap|
|
|
1110
|
+
# Note: will lose data if two capsules are processing the same named queue
|
|
1111
|
+
cap["weights"].each_pair do |queue, weight|
|
|
1112
|
+
hash[queue] = weight
|
|
1113
|
+
end
|
|
1104
1114
|
end
|
|
1115
|
+
hash
|
|
1105
1116
|
end
|
|
1106
|
-
hash
|
|
1107
1117
|
end
|
|
1108
1118
|
|
|
1109
1119
|
def capsules
|
data/lib/sidekiq/cli.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Sidekiq # :nodoc:
|
|
|
49
49
|
logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
|
|
50
50
|
|
|
51
51
|
self_read, self_write = IO.pipe
|
|
52
|
-
sigs = %w[INT TERM TTIN TSTP]
|
|
52
|
+
sigs = %w[INT TERM INFO TTIN TSTP]
|
|
53
53
|
# USR1 and USR2 don't work on the JVM
|
|
54
54
|
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
|
55
55
|
sigs.each do |sig|
|
|
@@ -201,7 +201,19 @@ module Sidekiq # :nodoc:
|
|
|
201
201
|
cli.logger.info "Received TSTP, no longer accepting new work"
|
|
202
202
|
cli.launcher.quiet
|
|
203
203
|
},
|
|
204
|
+
# deprecated, use INFO
|
|
204
205
|
"TTIN" => ->(cli) {
|
|
206
|
+
cli.logger.error { "DEPRECATED: Please use the INFO signal for backtraces, support for TTIN will be removed in Sidekiq 9.0." }
|
|
207
|
+
Thread.list.each do |thread|
|
|
208
|
+
cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
|
|
209
|
+
if thread.backtrace
|
|
210
|
+
cli.logger.warn thread.backtrace.join("\n")
|
|
211
|
+
else
|
|
212
|
+
cli.logger.warn "<no backtrace available>"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
},
|
|
216
|
+
"INFO" => ->(cli) {
|
|
205
217
|
Thread.list.each do |thread|
|
|
206
218
|
cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
|
|
207
219
|
if thread.backtrace
|
|
@@ -212,12 +224,12 @@ module Sidekiq # :nodoc:
|
|
|
212
224
|
end
|
|
213
225
|
}
|
|
214
226
|
}
|
|
215
|
-
UNHANDLED_SIGNAL_HANDLER = ->(cli) { cli.logger.info "No signal handler registered, ignoring" }
|
|
216
|
-
SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
|
|
217
227
|
|
|
218
228
|
def handle_signal(sig)
|
|
219
229
|
logger.debug "Got #{sig} signal"
|
|
220
|
-
SIGNAL_HANDLERS[sig]
|
|
230
|
+
hndlr = SIGNAL_HANDLERS[sig]
|
|
231
|
+
hndlr ? hndlr.call(self) :
|
|
232
|
+
logger.warn("No #{sig} signal handler registered, ignoring")
|
|
221
233
|
end
|
|
222
234
|
|
|
223
235
|
private
|
data/lib/sidekiq/config.rb
CHANGED
|
@@ -36,7 +36,8 @@ module Sidekiq
|
|
|
36
36
|
dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
|
|
37
37
|
reloader: proc { |&block| block.call },
|
|
38
38
|
backtrace_cleaner: ->(backtrace) { backtrace },
|
|
39
|
-
logged_job_attributes: ["bid", "tags"]
|
|
39
|
+
logged_job_attributes: ["bid", "tags"],
|
|
40
|
+
redis_idle_timeout: nil
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
|
|
@@ -145,10 +146,9 @@ module Sidekiq
|
|
|
145
146
|
@redis_config = @redis_config.merge(hash)
|
|
146
147
|
end
|
|
147
148
|
|
|
148
|
-
def reap_idle_redis_connections(timeout =
|
|
149
|
-
self[:
|
|
149
|
+
def reap_idle_redis_connections(timeout = 60)
|
|
150
|
+
self[:redis_idle_timeout] = timeout
|
|
150
151
|
end
|
|
151
|
-
alias_method :reap, :reap_idle_redis_connections
|
|
152
152
|
|
|
153
153
|
def redis_pool
|
|
154
154
|
Thread.current[:sidekiq_redis_pool] || Thread.current[:sidekiq_capsule]&.redis_pool || local_redis_pool
|
data/lib/sidekiq/job.rb
CHANGED
data/lib/sidekiq/job_retry.rb
CHANGED
|
@@ -178,10 +178,14 @@ module Sidekiq
|
|
|
178
178
|
msg["error_backtrace"] = compress_backtrace(lines)
|
|
179
179
|
end
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
# retry_for and retry are mutually exclusive - if retry_for is set,
|
|
182
|
+
# we exclusively use duration-based retry logic and ignore count-based logic
|
|
183
183
|
rf = msg["retry_for"]
|
|
184
|
-
|
|
184
|
+
if rf
|
|
185
|
+
return retries_exhausted(jobinst, msg, exception) if (time_for(msg["failed_at"]) + rf) < Time.now
|
|
186
|
+
elsif count >= max_retry_attempts
|
|
187
|
+
return retries_exhausted(jobinst, msg, exception)
|
|
188
|
+
end
|
|
185
189
|
|
|
186
190
|
strategy, delay = delay_for(jobinst, count, exception, msg)
|
|
187
191
|
case strategy
|
data/lib/sidekiq/launcher.rb
CHANGED
|
@@ -142,10 +142,10 @@ module Sidekiq
|
|
|
142
142
|
key = identity
|
|
143
143
|
fails = procd = 0
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
if
|
|
147
|
-
config.capsules.each_value { |cap| cap.local_redis_pool.reap(
|
|
148
|
-
config.local_redis_pool.reap(
|
|
145
|
+
idle_timeout = config[:redis_idle_timeout]
|
|
146
|
+
if idle_timeout
|
|
147
|
+
config.capsules.each_value { |cap| cap.local_redis_pool.reap(idle_seconds: idle_timeout, &:close) }
|
|
148
|
+
config.local_redis_pool.reap(idle_seconds: idle_timeout, &:close)
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
begin
|
|
@@ -220,11 +220,11 @@ module Sidekiq
|
|
|
220
220
|
# Log a warning if it's a disaster.
|
|
221
221
|
if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
|
|
222
222
|
logger.warn <<~EOM
|
|
223
|
-
Your Redis network connection
|
|
223
|
+
Your Redis network connection appears to be performing poorly.
|
|
224
224
|
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
|
225
|
-
Ensure Redis is running in the same AZ or datacenter as Sidekiq
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
Ensure Redis is running in the same AZ or datacenter as Sidekiq and that
|
|
226
|
+
your Sidekiq process is not CPU-saturated; reduce your concurrency and/or
|
|
227
|
+
see https://github.com/sidekiq/sidekiq/discussions/5039
|
|
228
228
|
EOM
|
|
229
229
|
RTT_READINGS.reset
|
|
230
230
|
end
|
|
@@ -23,7 +23,7 @@ module Sidekiq
|
|
|
23
23
|
|
|
24
24
|
size = symbolized_options.delete(:size) || 5
|
|
25
25
|
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
|
26
|
-
|
|
26
|
+
symbolized_options.delete(:pool_name)
|
|
27
27
|
|
|
28
28
|
# Default timeout in redis-client is 1 second, which can be too aggressive
|
|
29
29
|
# if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
|
|
@@ -33,7 +33,7 @@ module Sidekiq
|
|
|
33
33
|
symbolized_options[:timeout] ||= 3
|
|
34
34
|
|
|
35
35
|
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
|
36
|
-
ConnectionPool.new(timeout: pool_timeout, size: size
|
|
36
|
+
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
|
37
37
|
redis_config.new_client
|
|
38
38
|
end
|
|
39
39
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
|
@@ -72,6 +72,7 @@ module Sidekiq
|
|
|
72
72
|
include Sidekiq::Component
|
|
73
73
|
|
|
74
74
|
INITIAL_WAIT = 10
|
|
75
|
+
attr_accessor :rnd
|
|
75
76
|
|
|
76
77
|
def initialize(config)
|
|
77
78
|
@config = config
|
|
@@ -80,6 +81,7 @@ module Sidekiq
|
|
|
80
81
|
@done = false
|
|
81
82
|
@thread = nil
|
|
82
83
|
@count_calls = 0
|
|
84
|
+
@rnd = Random.new
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
# Shut down this instance, will pause until the thread is dead.
|
|
@@ -115,9 +117,9 @@ module Sidekiq
|
|
|
115
117
|
private
|
|
116
118
|
|
|
117
119
|
def wait
|
|
118
|
-
@sleeper.pop(random_poll_interval)
|
|
120
|
+
@sleeper.pop(timeout: random_poll_interval)
|
|
119
121
|
rescue Timeout::Error
|
|
120
|
-
#
|
|
122
|
+
# TODO move to exception: false
|
|
121
123
|
rescue => ex
|
|
122
124
|
# if poll_interval_average hasn't been calculated yet, we can
|
|
123
125
|
# raise an error trying to reach Redis.
|
|
@@ -151,11 +153,11 @@ module Sidekiq
|
|
|
151
153
|
|
|
152
154
|
if count < 10
|
|
153
155
|
# For small clusters, calculate a random interval that is ±50% the desired average.
|
|
154
|
-
interval * rand + interval.to_f / 2
|
|
156
|
+
interval * @rnd.rand + interval.to_f / 2
|
|
155
157
|
else
|
|
156
158
|
# With 10+ processes, we should have enough randomness to get decent polling
|
|
157
159
|
# across the entire timespan
|
|
158
|
-
interval * rand
|
|
160
|
+
interval * @rnd.rand * 2
|
|
159
161
|
end
|
|
160
162
|
end
|
|
161
163
|
|
|
@@ -223,7 +225,7 @@ module Sidekiq
|
|
|
223
225
|
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
|
224
226
|
total += (5 * rand)
|
|
225
227
|
|
|
226
|
-
@sleeper.pop(total)
|
|
228
|
+
@sleeper.pop(timeout: total)
|
|
227
229
|
rescue Timeout::Error
|
|
228
230
|
ensure
|
|
229
231
|
# periodically clean out the `processes` set in Redis which can collect
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
|
@@ -117,7 +117,7 @@ module Sidekiq
|
|
|
117
117
|
if content.is_a? Symbol
|
|
118
118
|
unless respond_to?(:"_erb_#{content}")
|
|
119
119
|
views = options[:views] || Web.views
|
|
120
|
-
filename = "#{views}/#{content}.erb"
|
|
120
|
+
filename = "#{views}/#{content}.html.erb"
|
|
121
121
|
src = ERB.new(File.read(filename)).src
|
|
122
122
|
|
|
123
123
|
# Need to use lineno less by 1 because erb generates a
|
data/lib/sidekiq/web/config.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq/web/csrf_protection"
|
|
4
|
-
|
|
5
3
|
module Sidekiq
|
|
6
4
|
class Web
|
|
7
5
|
##
|
|
@@ -24,10 +22,7 @@ module Sidekiq
|
|
|
24
22
|
# and very difficult for us to vendor or provide ourselves. If you are worried
|
|
25
23
|
# about data security and wish to self-host, you can change these URLs.
|
|
26
24
|
profile_view_url: "https://profiler.firefox.com/public/%s",
|
|
27
|
-
profile_store_url: "https://api.profiler.firefox.com/compressed-store"
|
|
28
|
-
# Will be false in Sidekiq 9.0.
|
|
29
|
-
# CSRF is unnecessary if you are using SameSite=(Strict|Lax) cookies.
|
|
30
|
-
csrf: true
|
|
25
|
+
profile_store_url: "https://api.profiler.firefox.com/compressed-store"
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
##
|
|
@@ -54,11 +49,13 @@ module Sidekiq
|
|
|
54
49
|
|
|
55
50
|
# Adds the "Back to App" link in the header
|
|
56
51
|
attr_accessor :app_url
|
|
52
|
+
attr_accessor :assets_path
|
|
57
53
|
|
|
58
54
|
def initialize
|
|
59
55
|
@options = OPTIONS.dup
|
|
60
56
|
@locales = LOCALES
|
|
61
57
|
@views = VIEWS
|
|
58
|
+
@assets_path = ASSETS
|
|
62
59
|
@tabs = DEFAULT_TABS.dup
|
|
63
60
|
@middlewares = []
|
|
64
61
|
@custom_job_info_rows = []
|
data/lib/sidekiq/web/helpers.rb
CHANGED
|
@@ -122,8 +122,8 @@ module Sidekiq
|
|
|
122
122
|
resultset
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
def filtering(which)
|
|
126
|
-
erb(:filtering, locals: {which:
|
|
125
|
+
def filtering(which, placeholder_key: "AnyJobContent", label_key: "Filter")
|
|
126
|
+
erb(:filtering, locals: {which:, placeholder_key:, label_key:})
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def filter_link(jid, within = "retries")
|
|
@@ -329,7 +329,7 @@ module Sidekiq
|
|
|
329
329
|
end
|
|
330
330
|
|
|
331
331
|
def csrf_tag
|
|
332
|
-
"
|
|
332
|
+
""
|
|
333
333
|
end
|
|
334
334
|
|
|
335
335
|
def csp_nonce
|
data/lib/sidekiq/web.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Sidekiq
|
|
|
13
13
|
ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
|
|
14
14
|
VIEWS = "#{ROOT}/views"
|
|
15
15
|
LOCALES = ["#{ROOT}/locales"]
|
|
16
|
-
LAYOUT = "#{VIEWS}/layout.erb"
|
|
16
|
+
LAYOUT = "#{VIEWS}/layout.html.erb"
|
|
17
17
|
ASSETS = "#{ROOT}/assets"
|
|
18
18
|
|
|
19
19
|
DEFAULT_TABS = {
|
|
@@ -42,6 +42,12 @@ module Sidekiq
|
|
|
42
42
|
@@config.app_url = url
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def assets_path=(path)
|
|
46
|
+
@@config.assets_path = path
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def assets_path = @@config.assets_path
|
|
50
|
+
|
|
45
51
|
def tabs = @@config.tabs
|
|
46
52
|
|
|
47
53
|
def locales = @@config.locales
|
|
@@ -87,13 +93,27 @@ module Sidekiq
|
|
|
87
93
|
env[:web_config] = Sidekiq::Web.configure
|
|
88
94
|
env[:csp_nonce] = SecureRandom.hex(8)
|
|
89
95
|
env[:redis_pool] = self.class.redis_pool
|
|
90
|
-
app.call(env)
|
|
96
|
+
safe_request?(env) ? app.call(env) : deny(env)
|
|
91
97
|
end
|
|
92
98
|
|
|
93
99
|
def app
|
|
94
100
|
@app ||= build(@@config)
|
|
95
101
|
end
|
|
96
102
|
|
|
103
|
+
def safe_methods?(env)
|
|
104
|
+
%w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def safe_request?(env)
|
|
108
|
+
return true if safe_methods?(env)
|
|
109
|
+
env["HTTP_SEC_FETCH_SITE"] == "same-origin"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def deny(env)
|
|
113
|
+
Sidekiq.logger.warn "attack prevented by #{self.class}"
|
|
114
|
+
[403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
|
|
115
|
+
end
|
|
116
|
+
|
|
97
117
|
private
|
|
98
118
|
|
|
99
119
|
def build(cfg)
|
|
@@ -105,11 +125,10 @@ module Sidekiq
|
|
|
105
125
|
|
|
106
126
|
::Rack::Builder.new do
|
|
107
127
|
use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
|
|
108
|
-
root:
|
|
128
|
+
root: cfg.assets_path,
|
|
109
129
|
cascade: true,
|
|
110
130
|
header_rules: rules
|
|
111
131
|
m.each { |middleware, block| use(*middleware, &block) }
|
|
112
|
-
use CsrfProtection if cfg[:csrf]
|
|
113
132
|
run Sidekiq::Web::Application.new(self.class)
|
|
114
133
|
end
|
|
115
134
|
end
|
data/sidekiq.gemspec
CHANGED
|
@@ -23,9 +23,9 @@ Gem::Specification.new do |gem|
|
|
|
23
23
|
"rubygems_mfa_required" => "true"
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
gem.add_dependency "redis-client", ">= 0.
|
|
27
|
-
gem.add_dependency "connection_pool", ">=
|
|
28
|
-
gem.add_dependency "rack", ">= 3.
|
|
29
|
-
gem.add_dependency "json", ">= 2.
|
|
30
|
-
gem.add_dependency "logger", ">= 1.
|
|
26
|
+
gem.add_dependency "redis-client", ">= 0.26.0"
|
|
27
|
+
gem.add_dependency "connection_pool", ">= 3.0.0"
|
|
28
|
+
gem.add_dependency "rack", ">= 3.2.0"
|
|
29
|
+
gem.add_dependency "json", ">= 2.16.0"
|
|
30
|
+
gem.add_dependency "logger", ">= 1.7.0"
|
|
31
31
|
end
|
|
@@ -190,7 +190,7 @@ function updateProgressBars() {
|
|
|
190
190
|
function handleConfirmDialog (event) {
|
|
191
191
|
const target = event.target
|
|
192
192
|
|
|
193
|
-
if (target.localName !== "input") { return }
|
|
193
|
+
if (target.localName !== "input" && target.localName !== "button" ) { return }
|
|
194
194
|
const confirmMessage = target.dataset.confirm
|
|
195
195
|
|
|
196
196
|
if (confirmMessage === undefined) { return }
|
data/web/locales/fr.yml
CHANGED
|
@@ -8,7 +8,7 @@ fr:
|
|
|
8
8
|
AreYouSureDeleteJob: Êtes-vous certain de vouloir supprimer cette tâche ?
|
|
9
9
|
AreYouSureDeleteQueue: Êtes-vous certain de vouloir supprimer la queue %{queue} ?
|
|
10
10
|
Arguments: Arguments
|
|
11
|
-
|
|
11
|
+
BackToApp: Retour à l'application
|
|
12
12
|
Busy: En cours
|
|
13
13
|
Class: Classe
|
|
14
14
|
Connections: Connexions
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="filter">
|
|
2
2
|
<form role="search" method="get" class="form-inline" action="<%= root_path %><%= which %>">
|
|
3
|
-
<label for="substr"><%= t(
|
|
4
|
-
<input class="form-control" type="search" name="substr" value="<%= h url_params("substr") %>" placeholder="<%= t(
|
|
3
|
+
<label for="substr"><%= t(label_key) %></label>
|
|
4
|
+
<input class="form-control" type="search" name="substr" value="<%= h url_params("substr") %>" placeholder="<%= t(placeholder_key) %>" autocomplete="off">
|
|
5
5
|
</form>
|
|
6
6
|
</div>
|
|
@@ -58,8 +58,9 @@
|
|
|
58
58
|
id="<%= id %>"
|
|
59
59
|
class="metrics-swatch"
|
|
60
60
|
value="<%= kls %>"
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
<% if visible_kls.include?(kls) %>
|
|
62
|
+
checked
|
|
63
|
+
<% end %>>
|
|
63
64
|
<code><a href="<%= root_path %>metrics/<%= kls %>?period=<%= @period %>"><%= kls %></a></code>
|
|
64
65
|
</div>
|
|
65
66
|
</td>
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<input class="btn btn-primary pull-left flip" type="submit" name="retry" value="<%= t('RetryNow') %>">
|
|
60
60
|
<input class="btn btn-danger pull-left flip" type="submit" name="delete" value="<%= t('Delete') %>">
|
|
61
61
|
<button class="btn btn-primary bulk-action-buttons bulk-lead-button" type="submit" name="action" value="retry_all" formaction="<%= "#{root_path}morgue/all/retry" %>"><%= t('RetryAll') %></button>
|
|
62
|
-
<button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}morgue/all/delete" %>"><%= t('DeleteAll') %></button>
|
|
62
|
+
<button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}morgue/all/delete" %>" data-confirm="<%= t('AreYouSure') %>"><%= t('DeleteAll') %></button>
|
|
63
63
|
</div>
|
|
64
64
|
</form>
|
|
65
65
|
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
<input class="btn btn-danger" type="submit" name="kill" value="<%= t('Kill') %>">
|
|
64
64
|
<!-- Retry all -->
|
|
65
65
|
<button class="btn btn-primary bulk-action-buttons bulk-lead-button" type="submit" name="action" value="retry_all" formaction="<%= "#{root_path}retries/all/retry" %>"><%= t('RetryAll') %></button>
|
|
66
|
-
<button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}retries/all/delete" %>"><%= t('DeleteAll') %></button>
|
|
66
|
+
<button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}retries/all/delete" %>" data-confirm="<%= t('AreYouSure') %>"><%= t('DeleteAll') %></button>
|
|
67
67
|
<button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="kill_all" formaction="<%= "#{root_path}retries/all/kill" %>"><%= t('KillAll') %></button>
|
|
68
68
|
</div>
|
|
69
69
|
</form>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
<div class="buttons-row">
|
|
55
55
|
<input class="btn btn-danger" type="submit" name="delete" value="<%= t('Delete') %>">
|
|
56
56
|
<input class="btn btn-danger" type="submit" name="add_to_queue" value="<%= t('AddToQueue') %>">
|
|
57
|
-
<button class="btn btn-danger bulk-action-buttons bulk-lead-button" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}scheduled/all/delete" %>"><%= t('DeleteAll') %></button>
|
|
57
|
+
<button class="btn btn-danger bulk-action-buttons bulk-lead-button" type="submit" name="action" value="delete_all" formaction="<%= "#{root_path}scheduled/all/delete" %>" data-confirm="<%= t('AreYouSure') %>"><%= t('DeleteAll') %></button>
|
|
58
58
|
<button class="btn btn-danger bulk-action-buttons" type="submit" name="action" value="add_all_to_queue" formaction="<%= "#{root_path}scheduled/all/add_to_queue" %>"><%= t('AddAllToQueue') %></button>
|
|
59
59
|
</div>
|
|
60
60
|
</form>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sidekiq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.0
|
|
4
|
+
version: 8.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Perham
|
|
@@ -15,70 +15,70 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.26.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.26.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: connection_pool
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: 3.0.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
39
|
+
version: 3.0.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: rack
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 3.
|
|
46
|
+
version: 3.2.0
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 3.
|
|
53
|
+
version: 3.2.0
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: json
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: 2.
|
|
60
|
+
version: 2.16.0
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: 2.
|
|
67
|
+
version: 2.16.0
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: logger
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 1.
|
|
74
|
+
version: 1.7.0
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 1.
|
|
81
|
+
version: 1.7.0
|
|
82
82
|
description: Simple, efficient background processing for Ruby.
|
|
83
83
|
email:
|
|
84
84
|
- info@contribsys.com
|
|
@@ -91,6 +91,7 @@ files:
|
|
|
91
91
|
- Changes.md
|
|
92
92
|
- LICENSE.txt
|
|
93
93
|
- README.md
|
|
94
|
+
- bin/lint-herb
|
|
94
95
|
- bin/multi_queue_bench
|
|
95
96
|
- bin/sidekiq
|
|
96
97
|
- bin/sidekiqload
|
|
@@ -151,7 +152,6 @@ files:
|
|
|
151
152
|
- lib/sidekiq/web/action.rb
|
|
152
153
|
- lib/sidekiq/web/application.rb
|
|
153
154
|
- lib/sidekiq/web/config.rb
|
|
154
|
-
- lib/sidekiq/web/csrf_protection.rb
|
|
155
155
|
- lib/sidekiq/web/helpers.rb
|
|
156
156
|
- lib/sidekiq/web/router.rb
|
|
157
157
|
- lib/sidekiq/worker_compatibility_alias.rb
|
|
@@ -199,28 +199,28 @@ files:
|
|
|
199
199
|
- web/locales/vi.yml
|
|
200
200
|
- web/locales/zh-CN.yml
|
|
201
201
|
- web/locales/zh-TW.yml
|
|
202
|
-
- web/views/_footer.erb
|
|
203
|
-
- web/views/_job_info.erb
|
|
204
|
-
- web/views/_metrics_period_select.erb
|
|
205
|
-
- web/views/_nav.erb
|
|
206
|
-
- web/views/_paging.erb
|
|
207
|
-
- web/views/_poll_link.erb
|
|
208
|
-
- web/views/_summary.erb
|
|
209
|
-
- web/views/busy.erb
|
|
210
|
-
- web/views/dashboard.erb
|
|
211
|
-
- web/views/dead.erb
|
|
212
|
-
- web/views/filtering.erb
|
|
213
|
-
- web/views/layout.erb
|
|
214
|
-
- web/views/metrics.erb
|
|
215
|
-
- web/views/metrics_for_job.erb
|
|
216
|
-
- web/views/morgue.erb
|
|
217
|
-
- web/views/profiles.erb
|
|
218
|
-
- web/views/queue.erb
|
|
219
|
-
- web/views/queues.erb
|
|
220
|
-
- web/views/retries.erb
|
|
221
|
-
- web/views/retry.erb
|
|
222
|
-
- web/views/scheduled.erb
|
|
223
|
-
- web/views/scheduled_job_info.erb
|
|
202
|
+
- web/views/_footer.html.erb
|
|
203
|
+
- web/views/_job_info.html.erb
|
|
204
|
+
- web/views/_metrics_period_select.html.erb
|
|
205
|
+
- web/views/_nav.html.erb
|
|
206
|
+
- web/views/_paging.html.erb
|
|
207
|
+
- web/views/_poll_link.html.erb
|
|
208
|
+
- web/views/_summary.html.erb
|
|
209
|
+
- web/views/busy.html.erb
|
|
210
|
+
- web/views/dashboard.html.erb
|
|
211
|
+
- web/views/dead.html.erb
|
|
212
|
+
- web/views/filtering.html.erb
|
|
213
|
+
- web/views/layout.html.erb
|
|
214
|
+
- web/views/metrics.html.erb
|
|
215
|
+
- web/views/metrics_for_job.html.erb
|
|
216
|
+
- web/views/morgue.html.erb
|
|
217
|
+
- web/views/profiles.html.erb
|
|
218
|
+
- web/views/queue.html.erb
|
|
219
|
+
- web/views/queues.html.erb
|
|
220
|
+
- web/views/retries.html.erb
|
|
221
|
+
- web/views/retry.html.erb
|
|
222
|
+
- web/views/scheduled.html.erb
|
|
223
|
+
- web/views/scheduled_job_info.html.erb
|
|
224
224
|
homepage: https://sidekiq.org
|
|
225
225
|
licenses:
|
|
226
226
|
- LGPL-3.0
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# this file originally based on authenticity_token.rb from the sinatra/rack-protection project
|
|
4
|
-
#
|
|
5
|
-
# The MIT License (MIT)
|
|
6
|
-
#
|
|
7
|
-
# Copyright (c) 2011-2017 Konstantin Haase
|
|
8
|
-
# Copyright (c) 2015-2017 Zachary Scott
|
|
9
|
-
#
|
|
10
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
|
11
|
-
# a copy of this software and associated documentation files (the
|
|
12
|
-
# 'Software'), to deal in the Software without restriction, including
|
|
13
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
|
14
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
15
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
|
16
|
-
# the following conditions:
|
|
17
|
-
#
|
|
18
|
-
# The above copyright notice and this permission notice shall be
|
|
19
|
-
# included in all copies or substantial portions of the Software.
|
|
20
|
-
#
|
|
21
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
22
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
23
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
24
|
-
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
25
|
-
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
26
|
-
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
27
|
-
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
28
|
-
|
|
29
|
-
require "securerandom"
|
|
30
|
-
require "rack/request"
|
|
31
|
-
|
|
32
|
-
module Sidekiq
|
|
33
|
-
class Web
|
|
34
|
-
class CsrfProtection
|
|
35
|
-
def initialize(app, options = nil)
|
|
36
|
-
@app = app
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def call(env)
|
|
40
|
-
accept?(env) ? admit(env) : deny(env)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def admit(env)
|
|
46
|
-
# On each successful request, we create a fresh masked token
|
|
47
|
-
# which will be used in any forms rendered for this request.
|
|
48
|
-
s = session(env)
|
|
49
|
-
s[:csrf] ||= SecureRandom.base64(TOKEN_LENGTH)
|
|
50
|
-
env[:csrf_token] = mask_token(s[:csrf])
|
|
51
|
-
@app.call(env)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def safe?(env)
|
|
55
|
-
%w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def logger(env)
|
|
59
|
-
@logger ||= env["rack.logger"] || ::Logger.new(env["rack.errors"])
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def deny(env)
|
|
63
|
-
logger(env).warn "attack prevented by #{self.class}"
|
|
64
|
-
[403, {Rack::CONTENT_TYPE => "text/plain"}, ["Forbidden"]]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def session(env)
|
|
68
|
-
env["rack.session"] || fail(<<~EOM)
|
|
69
|
-
Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
|
|
70
|
-
make sure you mount Sidekiq::Web *inside* your application routes:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Rails.application.routes.draw do
|
|
74
|
-
mount Sidekiq::Web => "/sidekiq"
|
|
75
|
-
....
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
If this is a Rails app in API mode, you need to enable sessions.
|
|
80
|
-
|
|
81
|
-
https://guides.rubyonrails.org/api_app.html#using-session-middlewares
|
|
82
|
-
|
|
83
|
-
If this is a bare Rack app, use a session middleware before Sidekiq::Web:
|
|
84
|
-
|
|
85
|
-
# first, use IRB to create a shared secret key for sessions and commit it
|
|
86
|
-
require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
|
|
87
|
-
|
|
88
|
-
# now use the secret with a session cookie middleware
|
|
89
|
-
use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
|
|
90
|
-
run Sidekiq::Web
|
|
91
|
-
|
|
92
|
-
EOM
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def accept?(env)
|
|
96
|
-
return true if safe?(env)
|
|
97
|
-
|
|
98
|
-
giventoken = ::Rack::Request.new(env).params["authenticity_token"]
|
|
99
|
-
valid_token?(env, giventoken)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
TOKEN_LENGTH = 32
|
|
103
|
-
|
|
104
|
-
# Checks that the token given to us as a parameter matches
|
|
105
|
-
# the token stored in the session.
|
|
106
|
-
def valid_token?(env, giventoken)
|
|
107
|
-
return false if giventoken.nil? || giventoken.empty?
|
|
108
|
-
|
|
109
|
-
begin
|
|
110
|
-
token = decode_token(giventoken)
|
|
111
|
-
rescue ArgumentError # client input is invalid
|
|
112
|
-
return false
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
sess = session(env)
|
|
116
|
-
localtoken = sess[:csrf]
|
|
117
|
-
|
|
118
|
-
# Checks that Rack::Session::Cookie actually contains the csrf token
|
|
119
|
-
return false if localtoken.nil?
|
|
120
|
-
|
|
121
|
-
# Rotate the session token after every use
|
|
122
|
-
sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH)
|
|
123
|
-
|
|
124
|
-
# See if it's actually a masked token or not. We should be able
|
|
125
|
-
# to handle any unmasked tokens that we've issued without error.
|
|
126
|
-
|
|
127
|
-
if unmasked_token?(token)
|
|
128
|
-
compare_with_real_token token, localtoken
|
|
129
|
-
elsif masked_token?(token)
|
|
130
|
-
unmasked = unmask_token(token)
|
|
131
|
-
compare_with_real_token unmasked, localtoken
|
|
132
|
-
else
|
|
133
|
-
false # Token is malformed
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Creates a masked version of the authenticity token that varies
|
|
138
|
-
# on each request. The masking is used to mitigate SSL attacks
|
|
139
|
-
# like BREACH.
|
|
140
|
-
def mask_token(token)
|
|
141
|
-
token = decode_token(token)
|
|
142
|
-
one_time_pad = SecureRandom.random_bytes(token.length)
|
|
143
|
-
encrypted_token = xor_byte_strings(one_time_pad, token)
|
|
144
|
-
masked_token = one_time_pad + encrypted_token
|
|
145
|
-
encode_token(masked_token)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Essentially the inverse of +mask_token+.
|
|
149
|
-
def unmask_token(masked_token)
|
|
150
|
-
# Split the token into the one-time pad and the encrypted
|
|
151
|
-
# value and decrypt it
|
|
152
|
-
token_length = masked_token.length / 2
|
|
153
|
-
one_time_pad = masked_token[0...token_length]
|
|
154
|
-
encrypted_token = masked_token[token_length..]
|
|
155
|
-
xor_byte_strings(one_time_pad, encrypted_token)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def unmasked_token?(token)
|
|
159
|
-
token.length == TOKEN_LENGTH
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def masked_token?(token)
|
|
163
|
-
token.length == TOKEN_LENGTH * 2
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def compare_with_real_token(token, local)
|
|
167
|
-
::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def encode_token(token)
|
|
171
|
-
[token].pack("m0").tr("+/", "-_")
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def decode_token(token)
|
|
175
|
-
token.tr("-_", "+/").unpack1("m0")
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def xor_byte_strings(s1, s2)
|
|
179
|
-
s1.bytes.zip(s2.bytes).map { |(c1, c2)| c1 ^ c2 }.pack("c*")
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|