activesupport 6.0.6.1 → 7.1.3.2

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.
Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +865 -438
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/active_support/actionable_error.rb +4 -2
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +30 -10
  8. data/lib/active_support/benchmarkable.rb +4 -3
  9. data/lib/active_support/broadcast_logger.rb +250 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +53 -20
  14. data/lib/active_support/cache/mem_cache_store.rb +208 -63
  15. data/lib/active_support/cache/memory_store.rb +120 -38
  16. data/lib/active_support/cache/null_store.rb +16 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +201 -208
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +73 -66
  20. data/lib/active_support/cache.rb +539 -261
  21. data/lib/active_support/callbacks.rb +273 -142
  22. data/lib/active_support/code_generator.rb +65 -0
  23. data/lib/active_support/concern.rb +53 -7
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/concurrency/share_lock.rb +2 -2
  27. data/lib/active_support/configurable.rb +19 -6
  28. data/lib/active_support/configuration_file.rb +51 -0
  29. data/lib/active_support/core_ext/array/access.rb +1 -5
  30. data/lib/active_support/core_ext/array/conversions.rb +15 -13
  31. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  32. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  33. data/lib/active_support/core_ext/benchmark.rb +2 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  35. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  36. data/lib/active_support/core_ext/class/subclasses.rb +19 -29
  37. data/lib/active_support/core_ext/date/blank.rb +1 -1
  38. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  39. data/lib/active_support/core_ext/date/conversions.rb +18 -16
  40. data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
  41. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  44. data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
  45. data/lib/active_support/core_ext/digest/uuid.rb +30 -13
  46. data/lib/active_support/core_ext/enumerable.rb +146 -72
  47. data/lib/active_support/core_ext/erb/util.rb +196 -0
  48. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  49. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  50. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
  52. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  53. data/lib/active_support/core_ext/hash/keys.rb +5 -5
  54. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  55. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  56. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  57. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  58. data/lib/active_support/core_ext/load_error.rb +1 -1
  59. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  60. data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
  61. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
  62. data/lib/active_support/core_ext/module/concerning.rb +14 -8
  63. data/lib/active_support/core_ext/module/delegation.rb +75 -42
  64. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  65. data/lib/active_support/core_ext/module/introspection.rb +1 -26
  66. data/lib/active_support/core_ext/name_error.rb +23 -2
  67. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  68. data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
  69. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  70. data/lib/active_support/core_ext/object/blank.rb +2 -2
  71. data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
  72. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  73. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  74. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  75. data/lib/active_support/core_ext/object/json.rb +52 -28
  76. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  77. data/lib/active_support/core_ext/object/try.rb +20 -20
  78. data/lib/active_support/core_ext/object/with.rb +44 -0
  79. data/lib/active_support/core_ext/object/with_options.rb +25 -6
  80. data/lib/active_support/core_ext/object.rb +1 -0
  81. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  82. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  83. data/lib/active_support/core_ext/pathname.rb +4 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  85. data/lib/active_support/core_ext/range/conversions.rb +34 -13
  86. data/lib/active_support/core_ext/range/each.rb +1 -1
  87. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  88. data/lib/active_support/core_ext/range.rb +1 -2
  89. data/lib/active_support/core_ext/regexp.rb +8 -1
  90. data/lib/active_support/core_ext/securerandom.rb +25 -13
  91. data/lib/active_support/core_ext/string/access.rb +5 -24
  92. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  93. data/lib/active_support/core_ext/string/filters.rb +21 -15
  94. data/lib/active_support/core_ext/string/indent.rb +1 -1
  95. data/lib/active_support/core_ext/string/inflections.rb +51 -10
  96. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  97. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  98. data/lib/active_support/core_ext/string/output_safety.rb +85 -194
  99. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  100. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  101. data/lib/active_support/core_ext/symbol.rb +3 -0
  102. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  103. data/lib/active_support/core_ext/time/calculations.rb +46 -8
  104. data/lib/active_support/core_ext/time/conversions.rb +16 -13
  105. data/lib/active_support/core_ext/time/zones.rb +12 -28
  106. data/lib/active_support/core_ext.rb +2 -1
  107. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  108. data/lib/active_support/current_attributes.rb +54 -22
  109. data/lib/active_support/deep_mergeable.rb +53 -0
  110. data/lib/active_support/dependencies/autoload.rb +17 -12
  111. data/lib/active_support/dependencies/interlock.rb +10 -18
  112. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  113. data/lib/active_support/dependencies.rb +58 -769
  114. data/lib/active_support/deprecation/behaviors.rb +77 -38
  115. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  116. data/lib/active_support/deprecation/deprecators.rb +104 -0
  117. data/lib/active_support/deprecation/disallowed.rb +54 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +31 -5
  119. data/lib/active_support/deprecation/method_wrappers.rb +12 -28
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
  121. data/lib/active_support/deprecation/reporting.rb +76 -16
  122. data/lib/active_support/deprecation.rb +36 -4
  123. data/lib/active_support/deprecator.rb +7 -0
  124. data/lib/active_support/descendants_tracker.rb +150 -68
  125. data/lib/active_support/digest.rb +5 -3
  126. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  127. data/lib/active_support/duration/iso8601_serializer.rb +24 -12
  128. data/lib/active_support/duration.rb +136 -56
  129. data/lib/active_support/encrypted_configuration.rb +72 -9
  130. data/lib/active_support/encrypted_file.rb +46 -13
  131. data/lib/active_support/environment_inquirer.rb +40 -0
  132. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  133. data/lib/active_support/error_reporter.rb +203 -0
  134. data/lib/active_support/evented_file_update_checker.rb +86 -137
  135. data/lib/active_support/execution_context/test_helper.rb +13 -0
  136. data/lib/active_support/execution_context.rb +53 -0
  137. data/lib/active_support/execution_wrapper.rb +31 -12
  138. data/lib/active_support/executor/test_helper.rb +7 -0
  139. data/lib/active_support/file_update_checker.rb +4 -2
  140. data/lib/active_support/fork_tracker.rb +79 -0
  141. data/lib/active_support/gem_version.rb +5 -5
  142. data/lib/active_support/gzip.rb +2 -0
  143. data/lib/active_support/hash_with_indifferent_access.rb +86 -42
  144. data/lib/active_support/html_safe_translation.rb +53 -0
  145. data/lib/active_support/i18n.rb +2 -1
  146. data/lib/active_support/i18n_railtie.rb +29 -27
  147. data/lib/active_support/inflector/inflections.rb +26 -9
  148. data/lib/active_support/inflector/methods.rb +54 -64
  149. data/lib/active_support/inflector/transliterate.rb +7 -5
  150. data/lib/active_support/isolated_execution_state.rb +76 -0
  151. data/lib/active_support/json/decoding.rb +6 -5
  152. data/lib/active_support/json/encoding.rb +31 -45
  153. data/lib/active_support/key_generator.rb +32 -7
  154. data/lib/active_support/lazy_load_hooks.rb +33 -7
  155. data/lib/active_support/locale/en.yml +10 -4
  156. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  157. data/lib/active_support/log_subscriber.rb +101 -32
  158. data/lib/active_support/logger.rb +9 -60
  159. data/lib/active_support/logger_silence.rb +2 -26
  160. data/lib/active_support/logger_thread_safe_level.rb +24 -25
  161. data/lib/active_support/message_encryptor.rb +205 -58
  162. data/lib/active_support/message_encryptors.rb +141 -0
  163. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  164. data/lib/active_support/message_pack/extensions.rb +292 -0
  165. data/lib/active_support/message_pack/serializer.rb +63 -0
  166. data/lib/active_support/message_pack.rb +50 -0
  167. data/lib/active_support/message_verifier.rb +237 -86
  168. data/lib/active_support/message_verifiers.rb +135 -0
  169. data/lib/active_support/messages/codec.rb +65 -0
  170. data/lib/active_support/messages/metadata.rb +112 -46
  171. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  172. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  173. data/lib/active_support/messages/rotator.rb +35 -32
  174. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  175. data/lib/active_support/multibyte/chars.rb +15 -52
  176. data/lib/active_support/multibyte/unicode.rb +8 -122
  177. data/lib/active_support/multibyte.rb +1 -1
  178. data/lib/active_support/notifications/fanout.rb +310 -105
  179. data/lib/active_support/notifications/instrumenter.rb +113 -48
  180. data/lib/active_support/notifications.rb +56 -29
  181. data/lib/active_support/number_helper/number_converter.rb +15 -8
  182. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  183. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  184. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  185. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
  186. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  187. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  188. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  189. data/lib/active_support/number_helper.rb +379 -304
  190. data/lib/active_support/option_merger.rb +11 -18
  191. data/lib/active_support/ordered_hash.rb +4 -4
  192. data/lib/active_support/ordered_options.rb +23 -3
  193. data/lib/active_support/parameter_filter.rb +104 -75
  194. data/lib/active_support/proxy_object.rb +2 -0
  195. data/lib/active_support/rails.rb +1 -4
  196. data/lib/active_support/railtie.rb +90 -6
  197. data/lib/active_support/reloader.rb +12 -4
  198. data/lib/active_support/rescuable.rb +18 -16
  199. data/lib/active_support/ruby_features.rb +7 -0
  200. data/lib/active_support/secure_compare_rotator.rb +58 -0
  201. data/lib/active_support/security_utils.rb +19 -12
  202. data/lib/active_support/string_inquirer.rb +5 -3
  203. data/lib/active_support/subscriber.rb +23 -47
  204. data/lib/active_support/syntax_error_proxy.rb +70 -0
  205. data/lib/active_support/tagged_logging.rb +84 -23
  206. data/lib/active_support/test_case.rb +166 -27
  207. data/lib/active_support/testing/assertions.rb +73 -20
  208. data/lib/active_support/testing/autorun.rb +0 -2
  209. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  210. data/lib/active_support/testing/deprecation.rb +53 -2
  211. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  212. data/lib/active_support/testing/isolation.rb +30 -29
  213. data/lib/active_support/testing/method_call_assertions.rb +24 -11
  214. data/lib/active_support/testing/parallelization/server.rb +82 -0
  215. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  216. data/lib/active_support/testing/parallelization.rb +16 -95
  217. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  218. data/lib/active_support/testing/stream.rb +4 -6
  219. data/lib/active_support/testing/strict_warnings.rb +39 -0
  220. data/lib/active_support/testing/tagged_logging.rb +1 -1
  221. data/lib/active_support/testing/time_helpers.rb +89 -19
  222. data/lib/active_support/time_with_zone.rb +105 -70
  223. data/lib/active_support/values/time_zone.rb +59 -26
  224. data/lib/active_support/version.rb +1 -1
  225. data/lib/active_support/xml_mini/jdom.rb +4 -11
  226. data/lib/active_support/xml_mini/libxml.rb +5 -5
  227. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  228. data/lib/active_support/xml_mini/nokogiri.rb +5 -5
  229. data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
  230. data/lib/active_support/xml_mini/rexml.rb +9 -2
  231. data/lib/active_support/xml_mini.rb +7 -6
  232. data/lib/active_support.rb +40 -1
  233. metadata +127 -40
  234. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  235. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  236. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  237. data/lib/active_support/core_ext/marshal.rb +0 -24
  238. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  239. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  240. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  241. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
  242. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  243. data/lib/active_support/core_ext/uri.rb +0 -25
  244. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
  245. data/lib/active_support/per_thread_registry.rb +0 -60
@@ -2,19 +2,21 @@
2
2
 
3
3
  require "openssl"
4
4
  require "base64"
5
- require "active_support/core_ext/array/extract_options"
6
5
  require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/messages/codec"
7
+ require "active_support/messages/rotator"
7
8
  require "active_support/message_verifier"
8
- require "active_support/messages/metadata"
9
9
 
10
10
  module ActiveSupport
11
+ # = Active Support Message Encryptor
12
+ #
11
13
  # MessageEncryptor is a simple way to encrypt values which get stored
12
14
  # somewhere you don't trust.
13
15
  #
14
16
  # The cipher text and initialization vector are base64 encoded and returned
15
17
  # to you.
16
18
  #
17
- # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
19
+ # This can be used in situations similar to the MessageVerifier, but
18
20
  # where you don't want users to be able to determine the value of the payload.
19
21
  #
20
22
  # len = ActiveSupport::MessageEncryptor.key_len
@@ -24,6 +26,12 @@ module ActiveSupport
24
26
  # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
25
27
  # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
26
28
  #
29
+ # The +decrypt_and_verify+ method will raise an
30
+ # +ActiveSupport::MessageEncryptor::InvalidMessage+ exception if the data
31
+ # provided cannot be decrypted or verified.
32
+ #
33
+ # crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
34
+ #
27
35
  # === Confining messages to a specific purpose
28
36
  #
29
37
  # By default any message can be used throughout your app. But they can also be
@@ -79,13 +87,13 @@ module ActiveSupport
79
87
  # the above should be combined into:
80
88
  #
81
89
  # crypt.rotate old_secret, cipher: "aes-256-cbc"
82
- class MessageEncryptor
83
- prepend Messages::Rotator::Encryptor
90
+ class MessageEncryptor < Messages::Codec
91
+ prepend Messages::Rotator
84
92
 
85
93
  cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
86
94
 
87
95
  class << self
88
- def default_cipher #:nodoc:
96
+ def default_cipher # :nodoc:
89
97
  if use_authenticated_message_encryption
90
98
  "aes-256-gcm"
91
99
  else
@@ -94,7 +102,7 @@ module ActiveSupport
94
102
  end
95
103
  end
96
104
 
97
- module NullSerializer #:nodoc:
105
+ module NullSerializer # :nodoc:
98
106
  def self.load(value)
99
107
  value
100
108
  end
@@ -104,57 +112,140 @@ module ActiveSupport
104
112
  end
105
113
  end
106
114
 
107
- module NullVerifier #:nodoc:
108
- def self.verify(value)
109
- value
110
- end
111
-
112
- def self.generate(value)
113
- value
114
- end
115
- end
116
-
117
115
  class InvalidMessage < StandardError; end
118
116
  OpenSSLCipherError = OpenSSL::Cipher::CipherError
119
117
 
118
+ AUTH_TAG_LENGTH = 16 # :nodoc:
119
+ SEPARATOR = "--" # :nodoc:
120
+
120
121
  # Initialize a new MessageEncryptor. +secret+ must be at least as long as
121
122
  # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
122
123
  # bits. If you are using a user-entered secret, you can generate a suitable
123
- # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
124
+ # key by using ActiveSupport::KeyGenerator or a similar key
124
125
  # derivation function.
125
126
  #
126
- # First additional parameter is used as the signature key for +MessageVerifier+.
127
- # This allows you to specify keys to encrypt and sign data.
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'.
128
130
  #
129
131
  # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
130
132
  #
131
- # Options:
132
- # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
133
- # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-gcm'.
134
- # * <tt>:digest</tt> - String of digest to use for signing. Default is
135
- # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
136
- # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
137
- def initialize(secret, *signature_key_or_options)
138
- options = signature_key_or_options.extract_options!
139
- sign_secret = signature_key_or_options.first
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)
140
185
  @secret = secret
141
- @sign_secret = sign_secret
142
186
  @cipher = options[:cipher] || self.class.default_cipher
143
- @digest = options[:digest] || "SHA1" unless aead_mode?
144
- @verifier = resolve_verifier
145
- @serializer = options[:serializer] || Marshal
187
+ @aead_mode = new_cipher.authenticated?
188
+ @verifier = if !@aead_mode
189
+ MessageVerifier.new(sign_secret || secret, **options, serializer: NullSerializer)
190
+ end
146
191
  end
147
192
 
148
193
  # Encrypt and sign a message. We need to sign the message in order to avoid
149
194
  # padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
150
- def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil)
151
- verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose))
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)
152
222
  end
153
223
 
154
224
  # Decrypt and verify a message. We need to verify the message in order to
155
225
  # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
156
- def decrypt_and_verify(data, purpose: nil, **)
157
- _decrypt(verifier.verify(data), purpose)
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
158
249
  end
159
250
 
160
251
  # Given a cipher, returns the key length of the cipher to help generate the key of desired size
@@ -162,8 +253,28 @@ module ActiveSupport
162
253
  OpenSSL::Cipher.new(cipher).key_len
163
254
  end
164
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
+
165
268
  private
166
- def _encrypt(value, **metadata_options)
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)
167
278
  cipher = new_cipher
168
279
  cipher.encrypt
169
280
  cipher.key = @secret
@@ -172,22 +283,25 @@ module ActiveSupport
172
283
  iv = cipher.random_iv
173
284
  cipher.auth_data = "" if aead_mode?
174
285
 
175
- encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), **metadata_options))
286
+ encrypted_data = cipher.update(data)
176
287
  encrypted_data << cipher.final
177
288
 
178
- blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
179
- blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
180
- blob
289
+ parts = [encrypted_data, iv]
290
+ parts << cipher.auth_tag(AUTH_TAG_LENGTH) if aead_mode?
291
+
292
+ join_parts(parts)
181
293
  end
182
294
 
183
- def _decrypt(encrypted_message, purpose)
295
+ def decrypt(encrypted_message)
184
296
  cipher = new_cipher
185
- encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) }
297
+ encrypted_data, iv, auth_tag = extract_parts(encrypted_message)
186
298
 
187
299
  # Currently the OpenSSL bindings do not raise an error if auth_tag is
188
300
  # truncated, which would allow an attacker to easily forge it. See
189
301
  # https://github.com/ruby/openssl/issues/63
190
- raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)
302
+ if aead_mode? && auth_tag.bytesize != AUTH_TAG_LENGTH
303
+ throw :invalid_message_format, "truncated auth_tag"
304
+ end
191
305
 
192
306
  cipher.decrypt
193
307
  cipher.key = @secret
@@ -199,29 +313,62 @@ module ActiveSupport
199
313
 
200
314
  decrypted_data = cipher.update(encrypted_data)
201
315
  decrypted_data << cipher.final
316
+ rescue OpenSSLCipherError => error
317
+ throw :invalid_message_format, error
318
+ end
202
319
 
203
- message = Messages::Metadata.verify(decrypted_data, purpose)
204
- @serializer.load(message) if message
205
- rescue OpenSSLCipherError, TypeError, ArgumentError
206
- raise InvalidMessage
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
207
326
  end
208
327
 
209
- def new_cipher
210
- OpenSSL::Cipher.new(@cipher)
328
+ def length_of_encoded_iv
329
+ @length_of_encoded_iv ||= length_after_encode(new_cipher.iv_len)
211
330
  end
212
331
 
213
- attr_reader :verifier
332
+ def length_of_encoded_auth_tag
333
+ @length_of_encoded_auth_tag ||= length_after_encode(AUTH_TAG_LENGTH)
334
+ end
214
335
 
215
- def aead_mode?
216
- @aead_mode ||= new_cipher.authenticated?
336
+ def join_parts(parts)
337
+ parts.map! { |part| encode(part) }.join(SEPARATOR)
217
338
  end
218
339
 
219
- def resolve_verifier
220
- if aead_mode?
221
- NullVerifier
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]
222
345
  else
223
- MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer)
346
+ throw :invalid_message_format, "missing separator"
224
347
  end
225
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
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)
369
+ end
370
+
371
+ attr_reader :aead_mode
372
+ alias :aead_mode? :aead_mode
226
373
  end
227
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