activesupport 5.1.7 → 5.2.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +424 -512
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_support/all.rb +2 -0
  6. data/lib/active_support/array_inquirer.rb +2 -0
  7. data/lib/active_support/backtrace_cleaner.rb +2 -0
  8. data/lib/active_support/benchmarkable.rb +2 -0
  9. data/lib/active_support/builder.rb +2 -0
  10. data/lib/active_support/cache/file_store.rb +5 -4
  11. data/lib/active_support/cache/mem_cache_store.rb +39 -38
  12. data/lib/active_support/cache/memory_store.rb +2 -0
  13. data/lib/active_support/cache/null_store.rb +2 -0
  14. data/lib/active_support/cache/redis_cache_store.rb +466 -0
  15. data/lib/active_support/cache/strategy/local_cache.rb +33 -2
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
  17. data/lib/active_support/cache.rb +197 -83
  18. data/lib/active_support/callbacks.rb +28 -39
  19. data/lib/active_support/concern.rb +10 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -0
  21. data/lib/active_support/configurable.rb +2 -0
  22. data/lib/active_support/core_ext/array/access.rb +4 -2
  23. data/lib/active_support/core_ext/array/conversions.rb +2 -0
  24. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  25. data/lib/active_support/core_ext/array/grouping.rb +2 -0
  26. data/lib/active_support/core_ext/array/inquiry.rb +2 -0
  27. data/lib/active_support/core_ext/array/prepend_and_append.rb +4 -2
  28. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  29. data/lib/active_support/core_ext/array.rb +2 -0
  30. data/lib/active_support/core_ext/benchmark.rb +2 -0
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +2 -0
  32. data/lib/active_support/core_ext/big_decimal.rb +2 -0
  33. data/lib/active_support/core_ext/class/attribute.rb +34 -16
  34. data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -0
  35. data/lib/active_support/core_ext/class/subclasses.rb +1 -2
  36. data/lib/active_support/core_ext/class.rb +2 -0
  37. data/lib/active_support/core_ext/date/acts_like.rb +2 -0
  38. data/lib/active_support/core_ext/date/blank.rb +2 -0
  39. data/lib/active_support/core_ext/date/calculations.rb +2 -0
  40. data/lib/active_support/core_ext/date/conversions.rb +10 -9
  41. data/lib/active_support/core_ext/date/zones.rb +2 -0
  42. data/lib/active_support/core_ext/date.rb +2 -0
  43. data/lib/active_support/core_ext/date_and_time/calculations.rb +50 -16
  44. data/lib/active_support/core_ext/date_and_time/compatibility.rb +3 -1
  45. data/lib/active_support/core_ext/date_and_time/zones.rb +2 -0
  46. data/lib/active_support/core_ext/date_time/acts_like.rb +2 -0
  47. data/lib/active_support/core_ext/date_time/blank.rb +2 -0
  48. data/lib/active_support/core_ext/date_time/calculations.rb +2 -0
  49. data/lib/active_support/core_ext/date_time/compatibility.rb +7 -5
  50. data/lib/active_support/core_ext/date_time/conversions.rb +2 -0
  51. data/lib/active_support/core_ext/date_time.rb +2 -0
  52. data/lib/active_support/core_ext/digest/uuid.rb +3 -1
  53. data/lib/active_support/core_ext/digest.rb +3 -0
  54. data/lib/active_support/core_ext/enumerable.rb +8 -1
  55. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  56. data/lib/active_support/core_ext/file.rb +2 -0
  57. data/lib/active_support/core_ext/hash/compact.rb +2 -0
  58. data/lib/active_support/core_ext/hash/conversions.rb +4 -2
  59. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  60. data/lib/active_support/core_ext/hash/except.rb +2 -0
  61. data/lib/active_support/core_ext/hash/indifferent_access.rb +2 -0
  62. data/lib/active_support/core_ext/hash/keys.rb +2 -0
  63. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  64. data/lib/active_support/core_ext/hash/slice.rb +4 -4
  65. data/lib/active_support/core_ext/hash/transform_values.rb +2 -0
  66. data/lib/active_support/core_ext/hash.rb +2 -0
  67. data/lib/active_support/core_ext/integer/inflections.rb +2 -0
  68. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  69. data/lib/active_support/core_ext/integer/time.rb +7 -14
  70. data/lib/active_support/core_ext/integer.rb +2 -0
  71. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  72. data/lib/active_support/core_ext/kernel/concern.rb +2 -0
  73. data/lib/active_support/core_ext/kernel/reporting.rb +2 -0
  74. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  75. data/lib/active_support/core_ext/kernel.rb +2 -0
  76. data/lib/active_support/core_ext/load_error.rb +2 -7
  77. data/lib/active_support/core_ext/marshal.rb +2 -0
  78. data/lib/active_support/core_ext/module/aliasing.rb +2 -0
  79. data/lib/active_support/core_ext/module/anonymous.rb +2 -0
  80. data/lib/active_support/core_ext/module/attr_internal.rb +2 -0
  81. data/lib/active_support/core_ext/module/attribute_accessors.rb +21 -24
  82. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +2 -0
  83. data/lib/active_support/core_ext/module/concerning.rb +7 -8
  84. data/lib/active_support/core_ext/module/delegation.rb +31 -29
  85. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  86. data/lib/active_support/core_ext/module/introspection.rb +2 -0
  87. data/lib/active_support/core_ext/module/reachable.rb +3 -0
  88. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  89. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  90. data/lib/active_support/core_ext/module.rb +3 -0
  91. data/lib/active_support/core_ext/name_error.rb +7 -0
  92. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  93. data/lib/active_support/core_ext/numeric/conversions.rb +9 -7
  94. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -0
  95. data/lib/active_support/core_ext/numeric/time.rb +7 -15
  96. data/lib/active_support/core_ext/numeric.rb +2 -0
  97. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  98. data/lib/active_support/core_ext/object/blank.rb +12 -1
  99. data/lib/active_support/core_ext/object/conversions.rb +2 -0
  100. data/lib/active_support/core_ext/object/deep_dup.rb +2 -0
  101. data/lib/active_support/core_ext/object/duplicable.rb +10 -8
  102. data/lib/active_support/core_ext/object/inclusion.rb +2 -0
  103. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  104. data/lib/active_support/core_ext/object/json.rb +8 -0
  105. data/lib/active_support/core_ext/object/to_param.rb +2 -0
  106. data/lib/active_support/core_ext/object/to_query.rb +2 -0
  107. data/lib/active_support/core_ext/object/try.rb +2 -0
  108. data/lib/active_support/core_ext/object/with_options.rb +3 -1
  109. data/lib/active_support/core_ext/object.rb +2 -0
  110. data/lib/active_support/core_ext/range/compare_range.rb +61 -0
  111. data/lib/active_support/core_ext/range/conversions.rb +9 -1
  112. data/lib/active_support/core_ext/range/each.rb +5 -1
  113. data/lib/active_support/core_ext/range/include_range.rb +2 -22
  114. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  115. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  116. data/lib/active_support/core_ext/range.rb +4 -1
  117. data/lib/active_support/core_ext/regexp.rb +2 -0
  118. data/lib/active_support/core_ext/securerandom.rb +2 -0
  119. data/lib/active_support/core_ext/string/access.rb +2 -0
  120. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  121. data/lib/active_support/core_ext/string/conversions.rb +2 -0
  122. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  123. data/lib/active_support/core_ext/string/filters.rb +2 -0
  124. data/lib/active_support/core_ext/string/indent.rb +2 -0
  125. data/lib/active_support/core_ext/string/inflections.rb +26 -12
  126. data/lib/active_support/core_ext/string/inquiry.rb +2 -0
  127. data/lib/active_support/core_ext/string/multibyte.rb +4 -0
  128. data/lib/active_support/core_ext/string/output_safety.rb +6 -7
  129. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  130. data/lib/active_support/core_ext/string/strip.rb +2 -0
  131. data/lib/active_support/core_ext/string/zones.rb +2 -0
  132. data/lib/active_support/core_ext/string.rb +2 -0
  133. data/lib/active_support/core_ext/time/acts_like.rb +2 -0
  134. data/lib/active_support/core_ext/time/calculations.rb +23 -15
  135. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  136. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  137. data/lib/active_support/core_ext/time/zones.rb +6 -4
  138. data/lib/active_support/core_ext/time.rb +2 -0
  139. data/lib/active_support/core_ext/uri.rb +6 -6
  140. data/lib/active_support/core_ext.rb +3 -1
  141. data/lib/active_support/current_attributes.rb +195 -0
  142. data/lib/active_support/dependencies/autoload.rb +2 -0
  143. data/lib/active_support/dependencies/interlock.rb +2 -0
  144. data/lib/active_support/dependencies.rb +25 -26
  145. data/lib/active_support/deprecation/behaviors.rb +28 -9
  146. data/lib/active_support/deprecation/constant_accessor.rb +4 -2
  147. data/lib/active_support/deprecation/instance_delegator.rb +2 -0
  148. data/lib/active_support/deprecation/method_wrappers.rb +30 -17
  149. data/lib/active_support/deprecation/proxy_wrappers.rb +5 -2
  150. data/lib/active_support/deprecation/reporting.rb +5 -3
  151. data/lib/active_support/deprecation.rb +4 -2
  152. data/lib/active_support/descendants_tracker.rb +2 -0
  153. data/lib/active_support/digest.rb +20 -0
  154. data/lib/active_support/duration/iso8601_parser.rb +4 -2
  155. data/lib/active_support/duration/iso8601_serializer.rb +4 -2
  156. data/lib/active_support/duration.rb +22 -14
  157. data/lib/active_support/encrypted_configuration.rb +49 -0
  158. data/lib/active_support/encrypted_file.rb +99 -0
  159. data/lib/active_support/evented_file_update_checker.rb +2 -0
  160. data/lib/active_support/execution_wrapper.rb +18 -13
  161. data/lib/active_support/executor.rb +2 -0
  162. data/lib/active_support/file_update_checker.rb +2 -0
  163. data/lib/active_support/gem_version.rb +3 -1
  164. data/lib/active_support/gzip.rb +2 -0
  165. data/lib/active_support/hash_with_indifferent_access.rb +55 -1
  166. data/lib/active_support/i18n.rb +3 -1
  167. data/lib/active_support/i18n_railtie.rb +4 -6
  168. data/lib/active_support/inflections.rb +2 -0
  169. data/lib/active_support/inflector/inflections.rb +20 -4
  170. data/lib/active_support/inflector/methods.rb +43 -24
  171. data/lib/active_support/inflector/transliterate.rb +17 -8
  172. data/lib/active_support/inflector.rb +2 -0
  173. data/lib/active_support/json/decoding.rb +2 -0
  174. data/lib/active_support/json/encoding.rb +2 -0
  175. data/lib/active_support/json.rb +2 -0
  176. data/lib/active_support/key_generator.rb +3 -1
  177. data/lib/active_support/lazy_load_hooks.rb +2 -0
  178. data/lib/active_support/log_subscriber/test_helper.rb +2 -0
  179. data/lib/active_support/log_subscriber.rb +3 -2
  180. data/lib/active_support/logger.rb +2 -0
  181. data/lib/active_support/logger_silence.rb +3 -2
  182. data/lib/active_support/logger_thread_safe_level.rb +4 -1
  183. data/lib/active_support/message_encryptor.rb +95 -22
  184. data/lib/active_support/message_verifier.rb +78 -7
  185. data/lib/active_support/messages/metadata.rb +71 -0
  186. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  187. data/lib/active_support/messages/rotator.rb +56 -0
  188. data/lib/active_support/multibyte/chars.rb +2 -0
  189. data/lib/active_support/multibyte/unicode.rb +4 -2
  190. data/lib/active_support/multibyte.rb +2 -0
  191. data/lib/active_support/notifications/fanout.rb +4 -2
  192. data/lib/active_support/notifications/instrumenter.rb +2 -0
  193. data/lib/active_support/notifications.rb +2 -0
  194. data/lib/active_support/number_helper/number_converter.rb +2 -0
  195. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -0
  196. data/lib/active_support/number_helper/number_to_delimited_converter.rb +2 -0
  197. data/lib/active_support/number_helper/number_to_human_converter.rb +2 -0
  198. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -0
  199. data/lib/active_support/number_helper/number_to_percentage_converter.rb +2 -0
  200. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -1
  201. data/lib/active_support/number_helper/number_to_rounded_converter.rb +2 -20
  202. data/lib/active_support/number_helper/rounding_helper.rb +6 -4
  203. data/lib/active_support/number_helper.rb +2 -0
  204. data/lib/active_support/option_merger.rb +2 -0
  205. data/lib/active_support/ordered_hash.rb +2 -0
  206. data/lib/active_support/ordered_options.rb +5 -3
  207. data/lib/active_support/per_thread_registry.rb +2 -0
  208. data/lib/active_support/proxy_object.rb +2 -0
  209. data/lib/active_support/rails.rb +2 -0
  210. data/lib/active_support/railtie.rb +37 -8
  211. data/lib/active_support/reloader.rb +8 -6
  212. data/lib/active_support/rescuable.rb +3 -2
  213. data/lib/active_support/security_utils.rb +15 -11
  214. data/lib/active_support/string_inquirer.rb +2 -0
  215. data/lib/active_support/subscriber.rb +8 -2
  216. data/lib/active_support/tagged_logging.rb +2 -0
  217. data/lib/active_support/test_case.rb +3 -2
  218. data/lib/active_support/testing/assertions.rb +31 -14
  219. data/lib/active_support/testing/autorun.rb +2 -0
  220. data/lib/active_support/testing/constant_lookup.rb +2 -0
  221. data/lib/active_support/testing/declarative.rb +2 -0
  222. data/lib/active_support/testing/deprecation.rb +2 -0
  223. data/lib/active_support/testing/file_fixtures.rb +2 -0
  224. data/lib/active_support/testing/isolation.rb +3 -1
  225. data/lib/active_support/testing/method_call_assertions.rb +2 -0
  226. data/lib/active_support/testing/setup_and_teardown.rb +12 -7
  227. data/lib/active_support/testing/stream.rb +2 -0
  228. data/lib/active_support/testing/tagged_logging.rb +2 -0
  229. data/lib/active_support/testing/time_helpers.rb +33 -3
  230. data/lib/active_support/time.rb +2 -0
  231. data/lib/active_support/time_with_zone.rb +38 -0
  232. data/lib/active_support/values/time_zone.rb +20 -8
  233. data/lib/active_support/version.rb +2 -0
  234. data/lib/active_support/xml_mini/jdom.rb +4 -2
  235. data/lib/active_support/xml_mini/libxml.rb +3 -1
  236. data/lib/active_support/xml_mini/libxmlsax.rb +4 -2
  237. data/lib/active_support/xml_mini/nokogiri.rb +3 -1
  238. data/lib/active_support/xml_mini/nokogirisax.rb +3 -1
  239. data/lib/active_support/xml_mini/rexml.rb +3 -1
  240. data/lib/active_support/xml_mini.rb +4 -2
  241. data/lib/active_support.rb +5 -13
  242. metadata +17 -5
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "openssl"
2
4
  require "base64"
3
5
  require "active_support/core_ext/array/extract_options"
6
+ require "active_support/core_ext/module/attribute_accessors"
4
7
  require "active_support/message_verifier"
8
+ require "active_support/messages/metadata"
5
9
 
6
10
  module ActiveSupport
7
11
  # MessageEncryptor is a simple way to encrypt values which get stored
@@ -13,13 +17,82 @@ module ActiveSupport
13
17
  # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
14
18
  # where you don't want users to be able to determine the value of the payload.
15
19
  #
16
- # salt = SecureRandom.random_bytes(64)
17
- # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..."
18
- # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
19
- # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
20
- # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
20
+ # len = ActiveSupport::MessageEncryptor.key_len
21
+ # salt = SecureRandom.random_bytes(len)
22
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."
23
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
24
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
25
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
26
+ #
27
+ # === Confining messages to a specific purpose
28
+ #
29
+ # By default any message can be used throughout your app. But they can also be
30
+ # confined to a specific +:purpose+.
31
+ #
32
+ # token = crypt.encrypt_and_sign("this is the chair", purpose: :login)
33
+ #
34
+ # Then that same purpose must be passed when verifying to get the data back out:
35
+ #
36
+ # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair"
37
+ # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil
38
+ # crypt.decrypt_and_verify(token) # => nil
39
+ #
40
+ # Likewise, if a message has no purpose it won't be returned when verifying with
41
+ # a specific purpose.
42
+ #
43
+ # token = crypt.encrypt_and_sign("the conversation is lively")
44
+ # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil
45
+ # crypt.decrypt_and_verify(token) # => "the conversation is lively"
46
+ #
47
+ # === Making messages expire
48
+ #
49
+ # By default messages last forever and verifying one year from now will still
50
+ # return the original value. But messages can be set to expire at a given
51
+ # time with +:expires_in+ or +:expires_at+.
52
+ #
53
+ # crypt.encrypt_and_sign(parcel, expires_in: 1.month)
54
+ # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
55
+ #
56
+ # Then the messages can be verified and returned upto the expire time.
57
+ # Thereafter, verifying returns +nil+.
58
+ #
59
+ # === Rotating keys
60
+ #
61
+ # MessageEncryptor also supports rotating out old configurations by falling
62
+ # back to a stack of encryptors. Call +rotate+ to build and add an encryptor
63
+ # so +decrypt_and_verify+ will also try the fallback.
64
+ #
65
+ # By default any rotated encryptors use the values of the primary
66
+ # encryptor unless specified otherwise.
67
+ #
68
+ # You'd give your encryptor the new defaults:
69
+ #
70
+ # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
71
+ #
72
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
73
+ # generated with the old values will then work until the rotation is removed.
74
+ #
75
+ # crypt.rotate old_secret # Fallback to an old secret instead of @secret.
76
+ # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
77
+ #
78
+ # Though if both the secret and the cipher was changed at the same time,
79
+ # the above should be combined into:
80
+ #
81
+ # crypt.rotate old_secret, cipher: "aes-256-cbc"
21
82
  class MessageEncryptor
22
- DEFAULT_CIPHER = "aes-256-cbc"
83
+ prepend Messages::Rotator::Encryptor
84
+
85
+ cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
86
+
87
+ class << self
88
+ def default_cipher #:nodoc:
89
+ if use_authenticated_message_encryption
90
+ "aes-256-gcm"
91
+ else
92
+ "aes-256-cbc"
93
+ end
94
+ end
95
+ end
23
96
 
24
97
  module NullSerializer #:nodoc:
25
98
  def self.load(value)
@@ -45,7 +118,7 @@ module ActiveSupport
45
118
  OpenSSLCipherError = OpenSSL::Cipher::CipherError
46
119
 
47
120
  # Initialize a new MessageEncryptor. +secret+ must be at least as long as
48
- # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
121
+ # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
49
122
  # bits. If you are using a user-entered secret, you can generate a suitable
50
123
  # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
51
124
  # derivation function.
@@ -57,7 +130,7 @@ module ActiveSupport
57
130
  #
58
131
  # Options:
59
132
  # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
60
- # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
133
+ # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-gcm'.
61
134
  # * <tt>:digest</tt> - String of digest to use for signing. Default is
62
135
  # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
63
136
  # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
@@ -66,32 +139,31 @@ module ActiveSupport
66
139
  sign_secret = signature_key_or_options.first
67
140
  @secret = secret
68
141
  @sign_secret = sign_secret
69
- @cipher = options[:cipher] || DEFAULT_CIPHER
142
+ @cipher = options[:cipher] || self.class.default_cipher
70
143
  @digest = options[:digest] || "SHA1" unless aead_mode?
71
144
  @verifier = resolve_verifier
72
145
  @serializer = options[:serializer] || Marshal
73
146
  end
74
147
 
75
148
  # Encrypt and sign a message. We need to sign the message in order to avoid
76
- # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
77
- def encrypt_and_sign(value)
78
- verifier.generate(_encrypt(value))
149
+ # 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))
79
152
  end
80
153
 
81
154
  # Decrypt and verify a message. We need to verify the message in order to
82
- # avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
83
- def decrypt_and_verify(value)
84
- _decrypt(verifier.verify(value))
155
+ # 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)
85
158
  end
86
159
 
87
160
  # Given a cipher, returns the key length of the cipher to help generate the key of desired size
88
- def self.key_len(cipher = DEFAULT_CIPHER)
161
+ def self.key_len(cipher = default_cipher)
89
162
  OpenSSL::Cipher.new(cipher).key_len
90
163
  end
91
164
 
92
165
  private
93
-
94
- def _encrypt(value)
166
+ def _encrypt(value, **metadata_options)
95
167
  cipher = new_cipher
96
168
  cipher.encrypt
97
169
  cipher.key = @secret
@@ -100,15 +172,15 @@ module ActiveSupport
100
172
  iv = cipher.random_iv
101
173
  cipher.auth_data = "" if aead_mode?
102
174
 
103
- encrypted_data = cipher.update(@serializer.dump(value))
175
+ encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), metadata_options))
104
176
  encrypted_data << cipher.final
105
177
 
106
178
  blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
107
- blob << "--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
179
+ blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
108
180
  blob
109
181
  end
110
182
 
111
- def _decrypt(encrypted_message)
183
+ def _decrypt(encrypted_message, purpose)
112
184
  cipher = new_cipher
113
185
  encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map { |v| ::Base64.strict_decode64(v) }
114
186
 
@@ -128,7 +200,8 @@ module ActiveSupport
128
200
  decrypted_data = cipher.update(encrypted_data)
129
201
  decrypted_data << cipher.final
130
202
 
131
- @serializer.load(decrypted_data)
203
+ message = Messages::Metadata.verify(decrypted_data, purpose)
204
+ @serializer.load(message) if message
132
205
  rescue OpenSSLCipherError, TypeError, ArgumentError
133
206
  raise InvalidMessage
134
207
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "base64"
2
4
  require "active_support/core_ext/object/blank"
3
5
  require "active_support/security_utils"
6
+ require "active_support/messages/metadata"
7
+ require "active_support/messages/rotator"
4
8
 
5
9
  module ActiveSupport
6
10
  # +MessageVerifier+ makes it easy to generate and verify messages which are
@@ -27,10 +31,76 @@ module ActiveSupport
27
31
  #
28
32
  # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
29
33
  # If you want to use a different hash algorithm, you can change it by providing
30
- # `:digest` key as an option while initializing the verifier:
34
+ # +:digest+ key as an option while initializing the verifier:
31
35
  #
32
36
  # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
37
+ #
38
+ # === Confining messages to a specific purpose
39
+ #
40
+ # By default any message can be used throughout your app. But they can also be
41
+ # confined to a specific +:purpose+.
42
+ #
43
+ # token = @verifier.generate("this is the chair", purpose: :login)
44
+ #
45
+ # Then that same purpose must be passed when verifying to get the data back out:
46
+ #
47
+ # @verifier.verified(token, purpose: :login) # => "this is the chair"
48
+ # @verifier.verified(token, purpose: :shipping) # => nil
49
+ # @verifier.verified(token) # => nil
50
+ #
51
+ # @verifier.verify(token, purpose: :login) # => "this is the chair"
52
+ # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature
53
+ # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature
54
+ #
55
+ # Likewise, if a message has no purpose it won't be returned when verifying with
56
+ # a specific purpose.
57
+ #
58
+ # token = @verifier.generate("the conversation is lively")
59
+ # @verifier.verified(token, purpose: :scare_tactics) # => nil
60
+ # @verifier.verified(token) # => "the conversation is lively"
61
+ #
62
+ # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature
63
+ # @verifier.verify(token) # => "the conversation is lively"
64
+ #
65
+ # === Making messages expire
66
+ #
67
+ # By default messages last forever and verifying one year from now will still
68
+ # return the original value. But messages can be set to expire at a given
69
+ # time with +:expires_in+ or +:expires_at+.
70
+ #
71
+ # @verifier.generate(parcel, expires_in: 1.month)
72
+ # @verifier.generate(doowad, expires_at: Time.now.end_of_year)
73
+ #
74
+ # Then the messages can be verified and returned upto the expire time.
75
+ # Thereafter, the +verified+ method returns +nil+ while +verify+ raises
76
+ # <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
77
+ #
78
+ # === Rotating keys
79
+ #
80
+ # MessageVerifier also supports rotating out old configurations by falling
81
+ # back to a stack of verifiers. Call +rotate+ to build and add a verifier to
82
+ # so either +verified+ or +verify+ will also try verifying with the fallback.
83
+ #
84
+ # By default any rotated verifiers use the values of the primary
85
+ # verifier unless specified otherwise.
86
+ #
87
+ # You'd give your verifier the new defaults:
88
+ #
89
+ # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
90
+ #
91
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
92
+ # generated with the old values will then work until the rotation is removed.
93
+ #
94
+ # verifier.rotate old_secret # Fallback to an old secret instead of @secret.
95
+ # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512.
96
+ # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON.
97
+ #
98
+ # Though the above would most likely be combined into one rotation:
99
+ #
100
+ # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal
33
101
  class MessageVerifier
102
+ prepend Messages::Rotator::Verifier
103
+
34
104
  class InvalidSignature < StandardError; end
35
105
 
36
106
  def initialize(secret, options = {})
@@ -77,11 +147,12 @@ module ActiveSupport
77
147
  #
78
148
  # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
79
149
  # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
80
- def verified(signed_message)
150
+ def verified(signed_message, purpose: nil, **)
81
151
  if valid_message?(signed_message)
82
152
  begin
83
153
  data = signed_message.split("--".freeze)[0]
84
- @serializer.load(decode(data))
154
+ message = Messages::Metadata.verify(decode(data), purpose)
155
+ @serializer.load(message) if message
85
156
  rescue ArgumentError => argument_error
86
157
  return if argument_error.message.include?("invalid base64")
87
158
  raise
@@ -101,8 +172,8 @@ module ActiveSupport
101
172
  #
102
173
  # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
103
174
  # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
104
- def verify(signed_message)
105
- verified(signed_message) || raise(InvalidSignature)
175
+ def verify(*args)
176
+ verified(*args) || raise(InvalidSignature)
106
177
  end
107
178
 
108
179
  # Generates a signed message for the provided value.
@@ -112,8 +183,8 @@ module ActiveSupport
112
183
  #
113
184
  # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
114
185
  # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
115
- def generate(value)
116
- data = encode(@serializer.dump(value))
186
+ def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
187
+ data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
117
188
  "#{data}--#{generate_digest(data)}"
118
189
  end
119
190
 
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module ActiveSupport
6
+ module Messages #:nodoc:
7
+ class Metadata #:nodoc:
8
+ def initialize(message, expires_at = nil, purpose = nil)
9
+ @message, @expires_at, @purpose = message, expires_at, purpose
10
+ end
11
+
12
+ def as_json(options = {})
13
+ { _rails: { message: @message, exp: @expires_at, pur: @purpose } }
14
+ end
15
+
16
+ class << self
17
+ def wrap(message, expires_at: nil, expires_in: nil, purpose: nil)
18
+ if expires_at || expires_in || purpose
19
+ JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose)
20
+ else
21
+ message
22
+ end
23
+ end
24
+
25
+ def verify(message, purpose)
26
+ extract_metadata(message).verify(purpose)
27
+ end
28
+
29
+ private
30
+ def pick_expiry(expires_at, expires_in)
31
+ if expires_at
32
+ expires_at.utc.iso8601(3)
33
+ elsif expires_in
34
+ Time.now.utc.advance(seconds: expires_in).iso8601(3)
35
+ end
36
+ end
37
+
38
+ def extract_metadata(message)
39
+ data = JSON.decode(message) rescue nil
40
+
41
+ if data.is_a?(Hash) && data.key?("_rails")
42
+ new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"])
43
+ else
44
+ new(message)
45
+ end
46
+ end
47
+
48
+ def encode(message)
49
+ ::Base64.strict_encode64(message)
50
+ end
51
+
52
+ def decode(message)
53
+ ::Base64.strict_decode64(message)
54
+ end
55
+ end
56
+
57
+ def verify(purpose)
58
+ @message if match?(purpose) && fresh?
59
+ end
60
+
61
+ private
62
+ def match?(purpose)
63
+ @purpose.to_s == purpose.to_s
64
+ end
65
+
66
+ def fresh?
67
+ @expires_at.nil? || Time.now.utc < Time.iso8601(@expires_at)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Messages
5
+ class RotationConfiguration # :nodoc:
6
+ attr_reader :signed, :encrypted
7
+
8
+ def initialize
9
+ @signed, @encrypted = [], []
10
+ end
11
+
12
+ def rotate(kind, *args)
13
+ case kind
14
+ when :signed
15
+ @signed << args
16
+ when :encrypted
17
+ @encrypted << args
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Messages
5
+ module Rotator # :nodoc:
6
+ def initialize(*, **options)
7
+ super
8
+
9
+ @options = options
10
+ @rotations = []
11
+ end
12
+
13
+ def rotate(*secrets, **options)
14
+ @rotations << build_rotation(*secrets, @options.merge(options))
15
+ end
16
+
17
+ module Encryptor
18
+ include Rotator
19
+
20
+ def decrypt_and_verify(*args, on_rotation: nil, **options)
21
+ super
22
+ rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
23
+ run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise
24
+ end
25
+
26
+ private
27
+ def build_rotation(secret = @secret, sign_secret = @sign_secret, options)
28
+ self.class.new(secret, sign_secret, options)
29
+ end
30
+ end
31
+
32
+ module Verifier
33
+ include Rotator
34
+
35
+ def verified(*args, on_rotation: nil, **options)
36
+ super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) }
37
+ end
38
+
39
+ private
40
+ def build_rotation(secret = @secret, options)
41
+ self.class.new(secret, options)
42
+ end
43
+ end
44
+
45
+ private
46
+ def run_rotations(on_rotation)
47
+ @rotations.find do |rotation|
48
+ if message = yield(rotation) rescue next
49
+ on_rotation.call if on_rotation
50
+ return message
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/json"
2
4
  require "active_support/core_ext/string/access"
3
5
  require "active_support/core_ext/string/behavior"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module Multibyte
3
5
  module Unicode
@@ -274,7 +276,7 @@ module ActiveSupport
274
276
  reorder_characters(decompose(:compatibility, codepoints))
275
277
  when :kc
276
278
  compose(reorder_characters(decompose(:compatibility, codepoints)))
277
- else
279
+ else
278
280
  raise ArgumentError, "#{form} is not a valid normalization variant", caller
279
281
  end.pack("U*".freeze)
280
282
  end
@@ -357,7 +359,7 @@ module ActiveSupport
357
359
 
358
360
  # Returns the directory in which the data files are stored.
359
361
  def self.dirname
360
- File.dirname(__FILE__) + "/../values/"
362
+ File.expand_path("../values", __dir__)
361
363
  end
362
364
 
363
365
  # Returns the filename for the data file for this version.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport #:nodoc:
2
4
  module Multibyte
3
5
  autoload :Chars, "active_support/multibyte/chars"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "mutex_m"
2
4
  require "concurrent/map"
3
5
 
@@ -16,8 +18,8 @@ module ActiveSupport
16
18
  super
17
19
  end
18
20
 
19
- def subscribe(pattern = nil, block = Proc.new)
20
- subscriber = Subscribers.new pattern, block
21
+ def subscribe(pattern = nil, callable = nil, &block)
22
+ subscriber = Subscribers.new(pattern, callable || block)
21
23
  synchronize do
22
24
  @subscribers << subscriber
23
25
  @listeners_for.clear
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "securerandom"
2
4
 
3
5
  module ActiveSupport
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/notifications/instrumenter"
2
4
  require "active_support/notifications/fanout"
3
5
  require "active_support/per_thread_registry"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/big_decimal/conversions"
2
4
  require "active_support/core_ext/object/blank"
3
5
  require "active_support/core_ext/hash/keys"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/numeric/inquiry"
2
4
 
3
5
  module ActiveSupport
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class NumberToDelimitedConverter < NumberConverter #:nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class NumberToHumanConverter < NumberConverter # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class NumberToHumanSizeConverter < NumberConverter #:nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class NumberToPercentageConverter < NumberConverter # :nodoc:
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class NumberToPhoneConverter < NumberConverter #:nodoc:
4
6
  def convert
5
- str = country_code(opts[:country_code])
7
+ str = country_code(opts[:country_code]).dup
6
8
  str << convert_to_phone_number(number.to_s.strip)
7
9
  str << phone_ext(opts[:extension])
8
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class NumberToRoundedConverter < NumberConverter # :nodoc:
@@ -35,26 +37,6 @@ module ActiveSupport
35
37
 
36
38
  private
37
39
 
38
- def digits_and_rounded_number(precision)
39
- if zero?
40
- [1, 0]
41
- else
42
- digits = digit_count(number)
43
- multiplier = 10**(digits - precision)
44
- rounded_number = calculate_rounded_number(multiplier)
45
- digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
46
- [digits, rounded_number]
47
- end
48
- end
49
-
50
- def calculate_rounded_number(multiplier)
51
- (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
52
- end
53
-
54
- def digit_count(number)
55
- number.zero? ? 1 : (Math.log10(absolute_number(number)) + 1).floor
56
- end
57
-
58
40
  def strip_insignificant_zeros
59
41
  options[:strip_insignificant_zeros]
60
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  class RoundingHelper # :nodoc:
@@ -34,17 +36,17 @@ module ActiveSupport
34
36
  return 0 if number.zero?
35
37
  digits = digit_count(number)
36
38
  multiplier = 10**(digits - precision)
37
- (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
39
+ (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier
38
40
  end
39
41
 
40
42
  def convert_to_decimal(number)
41
43
  case number
42
44
  when Float, String
43
- number = BigDecimal(number.to_s)
45
+ BigDecimal(number.to_s)
44
46
  when Rational
45
- number = BigDecimal(number, digit_count(number.to_i) + precision)
47
+ BigDecimal(number, digit_count(number.to_i) + precision)
46
48
  else
47
- number = number.to_d
49
+ number.to_d
48
50
  end
49
51
  end
50
52
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module NumberHelper
3
5
  extend ActiveSupport::Autoload
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/deep_merge"
2
4
 
3
5
  module ActiveSupport
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
 
3
5
  YAML.add_builtin_type("omap") do |type, val|