activesupport 7.0.0 → 7.2.2.1
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/CHANGELOG.md +156 -255
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +3 -1
- data/lib/active_support/backtrace_cleaner.rb +41 -9
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/broadcast_logger.rb +251 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +49 -17
- data/lib/active_support/cache/mem_cache_store.rb +111 -129
- data/lib/active_support/cache/memory_store.rb +81 -26
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +175 -154
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +31 -13
- data/lib/active_support/cache.rb +457 -377
- data/lib/active_support/callbacks.rb +123 -139
- data/lib/active_support/code_generator.rb +15 -10
- data/lib/active_support/concern.rb +4 -2
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/configurable.rb +12 -2
- data/lib/active_support/core_ext/array/conversions.rb +7 -9
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +4 -15
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/calculations.rb +20 -5
- data/lib/active_support/core_ext/date/conversions.rb +15 -16
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +29 -2
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +15 -15
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +7 -10
- data/lib/active_support/core_ext/enumerable.rb +51 -101
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/file/atomic.rb +2 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -2
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +7 -7
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +38 -20
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -119
- data/lib/active_support/core_ext/module/deprecation.rb +12 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +77 -75
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/duplicable.rb +25 -16
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
- data/lib/active_support/core_ext/object/json.rb +17 -7
- data/lib/active_support/core_ext/object/to_query.rb +0 -2
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +9 -9
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -0
- data/lib/active_support/core_ext/pathname/existence.rb +2 -0
- data/lib/active_support/core_ext/pathname.rb +1 -0
- data/lib/active_support/core_ext/range/conversions.rb +32 -11
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +2 -6
- data/lib/active_support/core_ext/string/conversions.rb +3 -3
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +16 -9
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +39 -150
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +42 -32
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +13 -15
- data/lib/active_support/core_ext/time/zones.rb +8 -9
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/core_ext.rb +0 -1
- data/lib/active_support/current_attributes.rb +53 -46
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +202 -0
- data/lib/active_support/dependencies/autoload.rb +9 -16
- data/lib/active_support/deprecation/behaviors.rb +65 -42
- data/lib/active_support/deprecation/constant_accessor.rb +47 -25
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +6 -8
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
- data/lib/active_support/deprecation/reporting.rb +49 -27
- data/lib/active_support/deprecation.rb +39 -9
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +66 -175
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -4
- data/lib/active_support/duration.rb +13 -7
- data/lib/active_support/encrypted_configuration.rb +63 -10
- data/lib/active_support/encrypted_file.rb +29 -13
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +160 -36
- data/lib/active_support/evented_file_update_checker.rb +19 -7
- data/lib/active_support/execution_wrapper.rb +23 -28
- data/lib/active_support/file_update_checker.rb +5 -3
- data/lib/active_support/fork_tracker.rb +4 -32
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +41 -25
- data/lib/active_support/html_safe_translation.rb +19 -6
- data/lib/active_support/i18n.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +20 -13
- data/lib/active_support/inflector/inflections.rb +2 -0
- data/lib/active_support/inflector/methods.rb +28 -18
- data/lib/active_support/inflector/transliterate.rb +4 -2
- data/lib/active_support/isolated_execution_state.rb +39 -19
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +25 -43
- data/lib/active_support/key_generator.rb +13 -5
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +76 -36
- data/lib/active_support/logger.rb +22 -60
- data/lib/active_support/logger_thread_safe_level.rb +10 -32
- data/lib/active_support/message_encryptor.rb +200 -55
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +305 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +220 -89
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +111 -45
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +4 -2
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +248 -87
- data/lib/active_support/notifications/instrumenter.rb +93 -25
- data/lib/active_support/notifications.rb +38 -31
- data/lib/active_support/number_helper/number_converter.rb +16 -7
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/number_helper.rb +379 -317
- data/lib/active_support/option_merger.rb +4 -4
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +68 -16
- data/lib/active_support/parameter_filter.rb +103 -84
- data/lib/active_support/proxy_object.rb +8 -3
- data/lib/active_support/railtie.rb +30 -25
- data/lib/active_support/reloader.rb +13 -5
- data/lib/active_support/rescuable.rb +12 -10
- data/lib/active_support/secure_compare_rotator.rb +17 -10
- data/lib/active_support/string_inquirer.rb +4 -2
- data/lib/active_support/subscriber.rb +10 -27
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +64 -25
- data/lib/active_support/test_case.rb +160 -7
- data/lib/active_support/testing/assertions.rb +29 -13
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +54 -0
- data/lib/active_support/testing/deprecation.rb +20 -27
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +46 -33
- data/lib/active_support/testing/method_call_assertions.rb +7 -8
- data/lib/active_support/testing/parallelization/server.rb +3 -0
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/strict_warnings.rb +43 -0
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +38 -16
- data/lib/active_support/time_with_zone.rb +28 -54
- data/lib/active_support/values/time_zone.rb +26 -15
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -10
- data/lib/active_support/xml_mini/nokogiri.rb +1 -1
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +1 -1
- data/lib/active_support/xml_mini.rb +13 -4
- data/lib/active_support.rb +15 -3
- metadata +142 -21
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/deprecation/instance_delegator.rb +0 -38
- data/lib/active_support/per_thread_registry.rb +0 -65
- data/lib/active_support/ruby_features.rb +0 -7
@@ -1,24 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
|
-
require "
|
5
|
-
require "concurrent"
|
6
|
-
require "fiber"
|
4
|
+
require "logger"
|
7
5
|
|
8
6
|
module ActiveSupport
|
9
7
|
module LoggerThreadSafeLevel # :nodoc:
|
10
8
|
extend ActiveSupport::Concern
|
11
9
|
|
12
|
-
Logger::Severity.constants.each do |severity|
|
13
|
-
class_eval(<<-EOT, __FILE__, __LINE__ + 1)
|
14
|
-
def #{severity.downcase}? # def debug?
|
15
|
-
Logger::#{severity} >= level # DEBUG >= level
|
16
|
-
end # end
|
17
|
-
EOT
|
18
|
-
end
|
19
|
-
|
20
10
|
def local_level
|
21
|
-
IsolatedExecutionState[
|
11
|
+
IsolatedExecutionState[local_level_key]
|
22
12
|
end
|
23
13
|
|
24
14
|
def local_level=(level)
|
@@ -30,7 +20,11 @@ module ActiveSupport
|
|
30
20
|
else
|
31
21
|
raise ArgumentError, "Invalid log level: #{level.inspect}"
|
32
22
|
end
|
33
|
-
|
23
|
+
if level.nil?
|
24
|
+
IsolatedExecutionState.delete(local_level_key)
|
25
|
+
else
|
26
|
+
IsolatedExecutionState[local_level_key] = level
|
27
|
+
end
|
34
28
|
end
|
35
29
|
|
36
30
|
def level
|
@@ -45,25 +39,9 @@ module ActiveSupport
|
|
45
39
|
self.local_level = old_local_level
|
46
40
|
end
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
severity ||= UNKNOWN
|
52
|
-
progname ||= @progname
|
53
|
-
|
54
|
-
return true if @logdev.nil? || severity < level
|
55
|
-
|
56
|
-
if message.nil?
|
57
|
-
if block_given?
|
58
|
-
message = yield
|
59
|
-
else
|
60
|
-
message = progname
|
61
|
-
progname = @progname
|
62
|
-
end
|
42
|
+
private
|
43
|
+
def local_level_key
|
44
|
+
@local_level_key ||= :"logger_thread_safe_level_#{object_id}"
|
63
45
|
end
|
64
|
-
|
65
|
-
@logdev.write \
|
66
|
-
format_message(format_severity(severity), Time.now, progname, message)
|
67
|
-
end
|
68
46
|
end
|
69
47
|
end
|
@@ -3,17 +3,20 @@
|
|
3
3
|
require "openssl"
|
4
4
|
require "base64"
|
5
5
|
require "active_support/core_ext/module/attribute_accessors"
|
6
|
+
require "active_support/messages/codec"
|
7
|
+
require "active_support/messages/rotator"
|
6
8
|
require "active_support/message_verifier"
|
7
|
-
require "active_support/messages/metadata"
|
8
9
|
|
9
10
|
module ActiveSupport
|
11
|
+
# = Active Support Message Encryptor
|
12
|
+
#
|
10
13
|
# MessageEncryptor is a simple way to encrypt values which get stored
|
11
14
|
# somewhere you don't trust.
|
12
15
|
#
|
13
16
|
# The cipher text and initialization vector are base64 encoded and returned
|
14
17
|
# to you.
|
15
18
|
#
|
16
|
-
# This can be used in situations similar to the
|
19
|
+
# This can be used in situations similar to the MessageVerifier, but
|
17
20
|
# where you don't want users to be able to determine the value of the payload.
|
18
21
|
#
|
19
22
|
# len = ActiveSupport::MessageEncryptor.key_len
|
@@ -22,8 +25,9 @@ module ActiveSupport
|
|
22
25
|
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
|
23
26
|
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
|
24
27
|
# crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
|
28
|
+
#
|
25
29
|
# The +decrypt_and_verify+ method will raise an
|
26
|
-
#
|
30
|
+
# +ActiveSupport::MessageEncryptor::InvalidMessage+ exception if the data
|
27
31
|
# provided cannot be decrypted or verified.
|
28
32
|
#
|
29
33
|
# crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
|
@@ -83,8 +87,8 @@ module ActiveSupport
|
|
83
87
|
# the above should be combined into:
|
84
88
|
#
|
85
89
|
# crypt.rotate old_secret, cipher: "aes-256-cbc"
|
86
|
-
class MessageEncryptor
|
87
|
-
prepend Messages::Rotator
|
90
|
+
class MessageEncryptor < Messages::Codec
|
91
|
+
prepend Messages::Rotator
|
88
92
|
|
89
93
|
cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
|
90
94
|
|
@@ -108,55 +112,140 @@ module ActiveSupport
|
|
108
112
|
end
|
109
113
|
end
|
110
114
|
|
111
|
-
module NullVerifier # :nodoc:
|
112
|
-
def self.verify(value)
|
113
|
-
value
|
114
|
-
end
|
115
|
-
|
116
|
-
def self.generate(value)
|
117
|
-
value
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
115
|
class InvalidMessage < StandardError; end
|
122
116
|
OpenSSLCipherError = OpenSSL::Cipher::CipherError
|
123
117
|
|
118
|
+
AUTH_TAG_LENGTH = 16 # :nodoc:
|
119
|
+
SEPARATOR = "--" # :nodoc:
|
120
|
+
|
124
121
|
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
|
125
122
|
# the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
|
126
123
|
# bits. If you are using a user-entered secret, you can generate a suitable
|
127
|
-
# key by using
|
124
|
+
# key by using ActiveSupport::KeyGenerator or a similar key
|
128
125
|
# derivation function.
|
129
126
|
#
|
130
|
-
#
|
131
|
-
# This allows you to specify keys to encrypt and sign
|
127
|
+
# The first additional parameter is used as the signature key for
|
128
|
+
# MessageVerifier. This allows you to specify keys to encrypt and sign
|
129
|
+
# data. Ignored when using an AEAD cipher like 'aes-256-gcm'.
|
132
130
|
#
|
133
131
|
# ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
|
134
132
|
#
|
135
|
-
# Options
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
|
133
|
+
# ==== Options
|
134
|
+
#
|
135
|
+
# [+:cipher+]
|
136
|
+
# Cipher to use. Can be any cipher returned by +OpenSSL::Cipher.ciphers+.
|
137
|
+
# Default is 'aes-256-gcm'.
|
138
|
+
#
|
139
|
+
# [+:digest+]
|
140
|
+
# Digest used for signing. Ignored when using an AEAD cipher like
|
141
|
+
# 'aes-256-gcm'.
|
142
|
+
#
|
143
|
+
# [+:serializer+]
|
144
|
+
# The serializer used to serialize message data. You can specify any
|
145
|
+
# object that responds to +dump+ and +load+, or you can choose from
|
146
|
+
# several preconfigured serializers: +:marshal+, +:json_allow_marshal+,
|
147
|
+
# +:json+, +:message_pack_allow_marshal+, +:message_pack+.
|
148
|
+
#
|
149
|
+
# The preconfigured serializers include a fallback mechanism to support
|
150
|
+
# multiple deserialization formats. For example, the +:marshal+ serializer
|
151
|
+
# will serialize using +Marshal+, but can deserialize using +Marshal+,
|
152
|
+
# ActiveSupport::JSON, or ActiveSupport::MessagePack. This makes it easy
|
153
|
+
# to migrate between serializers.
|
154
|
+
#
|
155
|
+
# The +:marshal+, +:json_allow_marshal+, and +:message_pack_allow_marshal+
|
156
|
+
# serializers support deserializing using +Marshal+, but the others do
|
157
|
+
# not. Beware that +Marshal+ is a potential vector for deserialization
|
158
|
+
# attacks in cases where a message signing secret has been leaked. <em>If
|
159
|
+
# possible, choose a serializer that does not support +Marshal+.</em>
|
160
|
+
#
|
161
|
+
# The +:message_pack+ and +:message_pack_allow_marshal+ serializers use
|
162
|
+
# ActiveSupport::MessagePack, which can roundtrip some Ruby types that are
|
163
|
+
# not supported by JSON, and may provide improved performance. However,
|
164
|
+
# these require the +msgpack+ gem.
|
165
|
+
#
|
166
|
+
# When using \Rails, the default depends on +config.active_support.message_serializer+.
|
167
|
+
# Otherwise, the default is +:marshal+.
|
168
|
+
#
|
169
|
+
# [+:url_safe+]
|
170
|
+
# By default, MessageEncryptor generates RFC 4648 compliant strings
|
171
|
+
# which are not URL-safe. In other words, they can contain "+" and "/".
|
172
|
+
# If you want to generate URL-safe strings (in compliance with "Base 64
|
173
|
+
# Encoding with URL and Filename Safe Alphabet" in RFC 4648), you can
|
174
|
+
# pass +true+.
|
175
|
+
#
|
176
|
+
# [+:force_legacy_metadata_serializer+]
|
177
|
+
# Whether to use the legacy metadata serializer, which serializes the
|
178
|
+
# message first, then wraps it in an envelope which is also serialized. This
|
179
|
+
# was the default in \Rails 7.0 and below.
|
180
|
+
#
|
181
|
+
# If you don't pass a truthy value, the default is set using
|
182
|
+
# +config.active_support.use_message_serializer_for_metadata+.
|
183
|
+
def initialize(secret, sign_secret = nil, **options)
|
184
|
+
super(**options)
|
142
185
|
@secret = secret
|
143
|
-
@
|
144
|
-
@
|
145
|
-
@
|
146
|
-
|
147
|
-
|
186
|
+
@cipher = options[:cipher] || self.class.default_cipher
|
187
|
+
@aead_mode = new_cipher.authenticated?
|
188
|
+
@verifier = if !@aead_mode
|
189
|
+
MessageVerifier.new(sign_secret || secret, **options, serializer: NullSerializer)
|
190
|
+
end
|
148
191
|
end
|
149
192
|
|
150
193
|
# Encrypt and sign a message. We need to sign the message in order to avoid
|
151
194
|
# padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
|
152
|
-
|
153
|
-
|
195
|
+
#
|
196
|
+
# ==== Options
|
197
|
+
#
|
198
|
+
# [+:expires_at+]
|
199
|
+
# The datetime at which the message expires. After this datetime,
|
200
|
+
# verification of the message will fail.
|
201
|
+
#
|
202
|
+
# message = encryptor.encrypt_and_sign("hello", expires_at: Time.now.tomorrow)
|
203
|
+
# encryptor.decrypt_and_verify(message) # => "hello"
|
204
|
+
# # 24 hours later...
|
205
|
+
# encryptor.decrypt_and_verify(message) # => nil
|
206
|
+
#
|
207
|
+
# [+:expires_in+]
|
208
|
+
# The duration for which the message is valid. After this duration has
|
209
|
+
# elapsed, verification of the message will fail.
|
210
|
+
#
|
211
|
+
# message = encryptor.encrypt_and_sign("hello", expires_in: 24.hours)
|
212
|
+
# encryptor.decrypt_and_verify(message) # => "hello"
|
213
|
+
# # 24 hours later...
|
214
|
+
# encryptor.decrypt_and_verify(message) # => nil
|
215
|
+
#
|
216
|
+
# [+:purpose+]
|
217
|
+
# The purpose of the message. If specified, the same purpose must be
|
218
|
+
# specified when verifying the message; otherwise, verification will fail.
|
219
|
+
# (See #decrypt_and_verify.)
|
220
|
+
def encrypt_and_sign(value, **options)
|
221
|
+
create_message(value, **options)
|
154
222
|
end
|
155
223
|
|
156
224
|
# Decrypt and verify a message. We need to verify the message in order to
|
157
225
|
# avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
|
158
|
-
|
159
|
-
|
226
|
+
#
|
227
|
+
# ==== Options
|
228
|
+
#
|
229
|
+
# [+:purpose+]
|
230
|
+
# The purpose that the message was generated with. If the purpose does not
|
231
|
+
# match, +decrypt_and_verify+ will return +nil+.
|
232
|
+
#
|
233
|
+
# message = encryptor.encrypt_and_sign("hello", purpose: "greeting")
|
234
|
+
# encryptor.decrypt_and_verify(message, purpose: "greeting") # => "hello"
|
235
|
+
# encryptor.decrypt_and_verify(message) # => nil
|
236
|
+
#
|
237
|
+
# message = encryptor.encrypt_and_sign("bye")
|
238
|
+
# encryptor.decrypt_and_verify(message) # => "bye"
|
239
|
+
# encryptor.decrypt_and_verify(message, purpose: "greeting") # => nil
|
240
|
+
#
|
241
|
+
def decrypt_and_verify(message, **options)
|
242
|
+
catch_and_raise :invalid_message_format, as: InvalidMessage do
|
243
|
+
catch_and_raise :invalid_message_serialization, as: InvalidMessage do
|
244
|
+
catch_and_ignore :invalid_message_content do
|
245
|
+
read_message(message, **options)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
160
249
|
end
|
161
250
|
|
162
251
|
# Given a cipher, returns the key length of the cipher to help generate the key of desired size
|
@@ -164,8 +253,28 @@ module ActiveSupport
|
|
164
253
|
OpenSSL::Cipher.new(cipher).key_len
|
165
254
|
end
|
166
255
|
|
256
|
+
def create_message(value, **options) # :nodoc:
|
257
|
+
sign(encrypt(serialize_with_metadata(value, **options)))
|
258
|
+
end
|
259
|
+
|
260
|
+
def read_message(message, **options) # :nodoc:
|
261
|
+
deserialize_with_metadata(decrypt(verify(message)), **options)
|
262
|
+
end
|
263
|
+
|
264
|
+
def inspect # :nodoc:
|
265
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
266
|
+
end
|
267
|
+
|
167
268
|
private
|
168
|
-
def
|
269
|
+
def sign(data)
|
270
|
+
@verifier ? @verifier.create_message(data) : data
|
271
|
+
end
|
272
|
+
|
273
|
+
def verify(data)
|
274
|
+
@verifier ? @verifier.read_message(data) : data
|
275
|
+
end
|
276
|
+
|
277
|
+
def encrypt(data)
|
169
278
|
cipher = new_cipher
|
170
279
|
cipher.encrypt
|
171
280
|
cipher.key = @secret
|
@@ -174,22 +283,25 @@ module ActiveSupport
|
|
174
283
|
iv = cipher.random_iv
|
175
284
|
cipher.auth_data = "" if aead_mode?
|
176
285
|
|
177
|
-
encrypted_data = cipher.update(
|
286
|
+
encrypted_data = cipher.update(data)
|
178
287
|
encrypted_data << cipher.final
|
179
288
|
|
180
|
-
|
181
|
-
|
182
|
-
|
289
|
+
parts = [encrypted_data, iv]
|
290
|
+
parts << cipher.auth_tag(AUTH_TAG_LENGTH) if aead_mode?
|
291
|
+
|
292
|
+
join_parts(parts)
|
183
293
|
end
|
184
294
|
|
185
|
-
def
|
295
|
+
def decrypt(encrypted_message)
|
186
296
|
cipher = new_cipher
|
187
|
-
encrypted_data, iv, auth_tag = encrypted_message
|
297
|
+
encrypted_data, iv, auth_tag = extract_parts(encrypted_message)
|
188
298
|
|
189
299
|
# Currently the OpenSSL bindings do not raise an error if auth_tag is
|
190
300
|
# truncated, which would allow an attacker to easily forge it. See
|
191
301
|
# https://github.com/ruby/openssl/issues/63
|
192
|
-
|
302
|
+
if aead_mode? && auth_tag.bytesize != AUTH_TAG_LENGTH
|
303
|
+
throw :invalid_message_format, "truncated auth_tag"
|
304
|
+
end
|
193
305
|
|
194
306
|
cipher.decrypt
|
195
307
|
cipher.key = @secret
|
@@ -201,29 +313,62 @@ module ActiveSupport
|
|
201
313
|
|
202
314
|
decrypted_data = cipher.update(encrypted_data)
|
203
315
|
decrypted_data << cipher.final
|
316
|
+
rescue OpenSSLCipherError => error
|
317
|
+
throw :invalid_message_format, error
|
318
|
+
end
|
204
319
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
320
|
+
def length_after_encode(length_before_encode)
|
321
|
+
if @url_safe
|
322
|
+
(4 * length_before_encode / 3.0).ceil # length without padding
|
323
|
+
else
|
324
|
+
4 * (length_before_encode / 3.0).ceil # length with padding
|
325
|
+
end
|
209
326
|
end
|
210
327
|
|
211
|
-
def
|
212
|
-
|
328
|
+
def length_of_encoded_iv
|
329
|
+
@length_of_encoded_iv ||= length_after_encode(new_cipher.iv_len)
|
213
330
|
end
|
214
331
|
|
215
|
-
|
332
|
+
def length_of_encoded_auth_tag
|
333
|
+
@length_of_encoded_auth_tag ||= length_after_encode(AUTH_TAG_LENGTH)
|
334
|
+
end
|
216
335
|
|
217
|
-
def
|
218
|
-
|
336
|
+
def join_parts(parts)
|
337
|
+
parts.map! { |part| encode(part) }.join(SEPARATOR)
|
219
338
|
end
|
220
339
|
|
221
|
-
def
|
222
|
-
|
223
|
-
|
340
|
+
def extract_part(encrypted_message, rindex, length)
|
341
|
+
index = rindex - length
|
342
|
+
|
343
|
+
if encrypted_message[index - SEPARATOR.length, SEPARATOR.length] == SEPARATOR
|
344
|
+
encrypted_message[index, length]
|
224
345
|
else
|
225
|
-
|
346
|
+
throw :invalid_message_format, "missing separator"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def extract_parts(encrypted_message)
|
351
|
+
parts = []
|
352
|
+
rindex = encrypted_message.length
|
353
|
+
|
354
|
+
if aead_mode?
|
355
|
+
parts << extract_part(encrypted_message, rindex, length_of_encoded_auth_tag)
|
356
|
+
rindex -= SEPARATOR.length + length_of_encoded_auth_tag
|
226
357
|
end
|
358
|
+
|
359
|
+
parts << extract_part(encrypted_message, rindex, length_of_encoded_iv)
|
360
|
+
rindex -= SEPARATOR.length + length_of_encoded_iv
|
361
|
+
|
362
|
+
parts << encrypted_message[0, rindex]
|
363
|
+
|
364
|
+
parts.reverse!.map! { |part| decode(part) }
|
365
|
+
end
|
366
|
+
|
367
|
+
def new_cipher
|
368
|
+
OpenSSL::Cipher.new(@cipher)
|
227
369
|
end
|
370
|
+
|
371
|
+
attr_reader :aead_mode
|
372
|
+
alias :aead_mode? :aead_mode
|
228
373
|
end
|
229
374
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/messages/rotation_coordinator"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
class MessageEncryptors < Messages::RotationCoordinator
|
7
|
+
##
|
8
|
+
# :attr_accessor: transitional
|
9
|
+
#
|
10
|
+
# If true, the first two rotation option sets are swapped when building
|
11
|
+
# message encryptors. For example, with the following configuration, message
|
12
|
+
# encryptors will encrypt messages using <tt>serializer: Marshal, url_safe: true</tt>,
|
13
|
+
# and will able to decrypt messages that were encrypted using any of the
|
14
|
+
# three option sets:
|
15
|
+
#
|
16
|
+
# encryptors = ActiveSupport::MessageEncryptors.new { ... }
|
17
|
+
# encryptors.rotate(serializer: JSON, url_safe: true)
|
18
|
+
# encryptors.rotate(serializer: Marshal, url_safe: true)
|
19
|
+
# encryptors.rotate(serializer: Marshal, url_safe: false)
|
20
|
+
# encryptors.transitional = true
|
21
|
+
#
|
22
|
+
# This can be useful when performing a rolling deploy of an application,
|
23
|
+
# wherein servers that have not yet been updated must still be able to
|
24
|
+
# decrypt messages from updated servers. In such a scenario, first perform a
|
25
|
+
# rolling deploy with the new rotation (e.g. <tt>serializer: JSON, url_safe: true</tt>)
|
26
|
+
# as the first rotation and <tt>transitional = true</tt>. Then, after all
|
27
|
+
# servers have been updated, perform a second rolling deploy with
|
28
|
+
# <tt>transitional = false</tt>.
|
29
|
+
|
30
|
+
##
|
31
|
+
# :method: initialize
|
32
|
+
# :call-seq: initialize(&secret_generator)
|
33
|
+
#
|
34
|
+
# Initializes a new instance. +secret_generator+ must accept a salt and a
|
35
|
+
# +secret_length+ kwarg, and return a suitable secret (string) or secrets
|
36
|
+
# (array of strings). +secret_generator+ may also accept other arbitrary
|
37
|
+
# kwargs. If #rotate is called with any options matching those kwargs, those
|
38
|
+
# options will be passed to +secret_generator+ instead of to the message
|
39
|
+
# encryptor.
|
40
|
+
#
|
41
|
+
# encryptors = ActiveSupport::MessageEncryptors.new do |salt, secret_length:, base:|
|
42
|
+
# MySecretGenerator.new(base).generate(salt, secret_length)
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# encryptors.rotate(base: "...")
|
46
|
+
|
47
|
+
##
|
48
|
+
# :method: []
|
49
|
+
# :call-seq: [](salt)
|
50
|
+
#
|
51
|
+
# Returns a MessageEncryptor configured with a secret derived from the
|
52
|
+
# given +salt+, and options from #rotate. MessageEncryptor instances will
|
53
|
+
# be memoized, so the same +salt+ will return the same instance.
|
54
|
+
|
55
|
+
##
|
56
|
+
# :method: []=
|
57
|
+
# :call-seq: []=(salt, encryptor)
|
58
|
+
#
|
59
|
+
# Overrides a MessageEncryptor instance associated with a given +salt+.
|
60
|
+
|
61
|
+
##
|
62
|
+
# :method: rotate
|
63
|
+
# :call-seq:
|
64
|
+
# rotate(**options)
|
65
|
+
# rotate(&block)
|
66
|
+
#
|
67
|
+
# Adds +options+ to the list of option sets. Messages will be encrypted
|
68
|
+
# using the first set in the list. When decrypting, however, each set will
|
69
|
+
# be tried, in order, until one succeeds.
|
70
|
+
#
|
71
|
+
# Notably, the +:secret_generator+ option can specify a different secret
|
72
|
+
# generator than the one initially specified. The secret generator must
|
73
|
+
# respond to +call+, accept a salt and a +secret_length+ kwarg, and return
|
74
|
+
# a suitable secret (string) or secrets (array of strings). The secret
|
75
|
+
# generator may also accept other arbitrary kwargs.
|
76
|
+
#
|
77
|
+
# If any options match the kwargs of the operative secret generator, those
|
78
|
+
# options will be passed to the secret generator instead of to the message
|
79
|
+
# encryptor.
|
80
|
+
#
|
81
|
+
# For fine-grained per-salt rotations, a block form is supported. The block
|
82
|
+
# will receive the salt, and should return an appropriate options Hash. The
|
83
|
+
# block may also return +nil+ to indicate that the rotation does not apply
|
84
|
+
# to the given salt. For example:
|
85
|
+
#
|
86
|
+
# encryptors = ActiveSupport::MessageEncryptors.new { ... }
|
87
|
+
#
|
88
|
+
# encryptors.rotate do |salt|
|
89
|
+
# case salt
|
90
|
+
# when :foo
|
91
|
+
# { serializer: JSON, url_safe: true }
|
92
|
+
# when :bar
|
93
|
+
# { serializer: Marshal, url_safe: true }
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# encryptors.rotate(serializer: Marshal, url_safe: false)
|
98
|
+
#
|
99
|
+
# # Uses `serializer: JSON, url_safe: true`.
|
100
|
+
# # Falls back to `serializer: Marshal, url_safe: false`.
|
101
|
+
# encryptors[:foo]
|
102
|
+
#
|
103
|
+
# # Uses `serializer: Marshal, url_safe: true`.
|
104
|
+
# # Falls back to `serializer: Marshal, url_safe: false`.
|
105
|
+
# encryptors[:bar]
|
106
|
+
#
|
107
|
+
# # Uses `serializer: Marshal, url_safe: false`.
|
108
|
+
# encryptors[:baz]
|
109
|
+
|
110
|
+
##
|
111
|
+
# :method: rotate_defaults
|
112
|
+
# :call-seq: rotate_defaults
|
113
|
+
#
|
114
|
+
# Invokes #rotate with the default options.
|
115
|
+
|
116
|
+
##
|
117
|
+
# :method: clear_rotations
|
118
|
+
# :call-seq: clear_rotations
|
119
|
+
#
|
120
|
+
# Clears the list of option sets.
|
121
|
+
|
122
|
+
##
|
123
|
+
# :method: on_rotation
|
124
|
+
# :call-seq: on_rotation(&callback)
|
125
|
+
#
|
126
|
+
# Sets a callback to invoke when a message is decrypted using an option set
|
127
|
+
# other than the first.
|
128
|
+
#
|
129
|
+
# For example, this callback could log each time it is called, and thus
|
130
|
+
# indicate whether old option sets are still in use or can be removed from
|
131
|
+
# rotation.
|
132
|
+
|
133
|
+
##
|
134
|
+
private
|
135
|
+
def build(salt, secret_generator:, secret_generator_options:, **options)
|
136
|
+
secret_length = MessageEncryptor.key_len(*options[:cipher])
|
137
|
+
secret = secret_generator.call(salt, secret_length: secret_length, **secret_generator_options)
|
138
|
+
MessageEncryptor.new(*Array(secret), **options)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "serializer"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
module MessagePack
|
7
|
+
module CacheSerializer
|
8
|
+
include Serializer
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def load(dumped)
|
12
|
+
super
|
13
|
+
rescue ActiveSupport::MessagePack::MissingClassError
|
14
|
+
# Treat missing class as cache miss => return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def install_unregistered_type_handler
|
19
|
+
Extensions.install_unregistered_type_fallback(message_pack_factory)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|