activesupport 4.2.0 → 5.2.0

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 (254) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +366 -232
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +4 -5
  5. data/lib/active_support.rb +17 -7
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +7 -5
  9. data/lib/active_support/benchmarkable.rb +6 -4
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache.rb +271 -177
  12. data/lib/active_support/cache/file_store.rb +41 -35
  13. data/lib/active_support/cache/mem_cache_store.rb +97 -88
  14. data/lib/active_support/cache/memory_store.rb +27 -30
  15. data/lib/active_support/cache/null_store.rb +7 -8
  16. data/lib/active_support/cache/redis_cache_store.rb +454 -0
  17. data/lib/active_support/cache/strategy/local_cache.rb +67 -34
  18. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  19. data/lib/active_support/callbacks.rb +654 -560
  20. data/lib/active_support/concern.rb +5 -3
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  22. data/lib/active_support/concurrency/share_lock.rb +227 -0
  23. data/lib/active_support/configurable.rb +8 -5
  24. data/lib/active_support/core_ext.rb +3 -1
  25. data/lib/active_support/core_ext/array.rb +9 -6
  26. data/lib/active_support/core_ext/array/access.rb +29 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +22 -18
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  30. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  31. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
  32. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  33. data/lib/active_support/core_ext/benchmark.rb +3 -1
  34. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  35. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  36. data/lib/active_support/core_ext/class.rb +4 -3
  37. data/lib/active_support/core_ext/class/attribute.rb +41 -22
  38. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  39. data/lib/active_support/core_ext/class/subclasses.rb +20 -8
  40. data/lib/active_support/core_ext/date.rb +6 -4
  41. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  42. data/lib/active_support/core_ext/date/blank.rb +14 -0
  43. data/lib/active_support/core_ext/date/calculations.rb +11 -9
  44. data/lib/active_support/core_ext/date/conversions.rb +31 -23
  45. data/lib/active_support/core_ext/date/zones.rb +4 -2
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +179 -56
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
  49. data/lib/active_support/core_ext/date_time.rb +7 -4
  50. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  51. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  52. data/lib/active_support/core_ext/date_time/calculations.rb +58 -20
  53. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  54. data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/enumerable.rb +107 -28
  57. data/lib/active_support/core_ext/file.rb +3 -1
  58. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  59. data/lib/active_support/core_ext/hash.rb +11 -9
  60. data/lib/active_support/core_ext/hash/compact.rb +24 -15
  61. data/lib/active_support/core_ext/hash/conversions.rb +63 -43
  62. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  63. data/lib/active_support/core_ext/hash/except.rb +11 -8
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  65. data/lib/active_support/core_ext/hash/keys.rb +33 -27
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -8
  68. data/lib/active_support/core_ext/hash/transform_values.rb +16 -7
  69. data/lib/active_support/core_ext/integer.rb +5 -3
  70. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  71. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  72. data/lib/active_support/core_ext/integer/time.rb +11 -33
  73. data/lib/active_support/core_ext/kernel.rb +6 -5
  74. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  75. data/lib/active_support/core_ext/kernel/concern.rb +5 -1
  76. data/lib/active_support/core_ext/kernel/reporting.rb +4 -83
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  78. data/lib/active_support/core_ext/load_error.rb +3 -22
  79. data/lib/active_support/core_ext/marshal.rb +13 -10
  80. data/lib/active_support/core_ext/module.rb +14 -11
  81. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  82. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  83. data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +11 -12
  87. data/lib/active_support/core_ext/module/delegation.rb +121 -39
  88. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  89. data/lib/active_support/core_ext/module/introspection.rb +9 -9
  90. data/lib/active_support/core_ext/module/reachable.rb +5 -2
  91. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/name_error.rb +22 -2
  94. data/lib/active_support/core_ext/numeric.rb +6 -3
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
  97. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +35 -38
  99. data/lib/active_support/core_ext/object.rb +14 -13
  100. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  101. data/lib/active_support/core_ext/object/blank.rb +29 -4
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  104. data/lib/active_support/core_ext/object/duplicable.rb +98 -45
  105. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +49 -19
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +6 -4
  110. data/lib/active_support/core_ext/object/try.rb +70 -22
  111. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  112. data/lib/active_support/core_ext/range.rb +7 -4
  113. data/lib/active_support/core_ext/range/conversions.rb +27 -7
  114. data/lib/active_support/core_ext/range/each.rb +19 -17
  115. data/lib/active_support/core_ext/range/include_range.rb +21 -19
  116. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  118. data/lib/active_support/core_ext/regexp.rb +6 -0
  119. data/lib/active_support/core_ext/securerandom.rb +25 -0
  120. data/lib/active_support/core_ext/string.rb +15 -13
  121. data/lib/active_support/core_ext/string/access.rb +9 -7
  122. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  123. data/lib/active_support/core_ext/string/conversions.rb +8 -5
  124. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  125. data/lib/active_support/core_ext/string/filters.rb +10 -8
  126. data/lib/active_support/core_ext/string/indent.rb +6 -4
  127. data/lib/active_support/core_ext/string/inflections.rb +61 -24
  128. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  129. data/lib/active_support/core_ext/string/multibyte.rb +15 -7
  130. data/lib/active_support/core_ext/string/output_safety.rb +35 -35
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  132. data/lib/active_support/core_ext/string/strip.rb +4 -5
  133. data/lib/active_support/core_ext/string/zones.rb +4 -2
  134. data/lib/active_support/core_ext/time.rb +7 -5
  135. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  136. data/lib/active_support/core_ext/time/calculations.rb +101 -51
  137. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  138. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  139. data/lib/active_support/core_ext/time/zones.rb +41 -7
  140. data/lib/active_support/core_ext/uri.rb +5 -4
  141. data/lib/active_support/current_attributes.rb +195 -0
  142. data/lib/active_support/dependencies.rb +143 -160
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/deprecation.rb +12 -9
  146. data/lib/active_support/deprecation/behaviors.rb +41 -12
  147. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +17 -2
  149. data/lib/active_support/deprecation/method_wrappers.rb +54 -21
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
  151. data/lib/active_support/deprecation/reporting.rb +32 -12
  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.rb +326 -30
  155. data/lib/active_support/duration/iso8601_parser.rb +125 -0
  156. data/lib/active_support/duration/iso8601_serializer.rb +55 -0
  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 +205 -0
  160. data/lib/active_support/execution_wrapper.rb +128 -0
  161. data/lib/active_support/executor.rb +8 -0
  162. data/lib/active_support/file_update_checker.rb +63 -37
  163. data/lib/active_support/gem_version.rb +4 -2
  164. data/lib/active_support/gzip.rb +7 -5
  165. data/lib/active_support/hash_with_indifferent_access.rb +130 -30
  166. data/lib/active_support/i18n.rb +8 -6
  167. data/lib/active_support/i18n_railtie.rb +34 -14
  168. data/lib/active_support/inflections.rb +13 -11
  169. data/lib/active_support/inflector.rb +7 -5
  170. data/lib/active_support/inflector/inflections.rb +61 -12
  171. data/lib/active_support/inflector/methods.rb +161 -136
  172. data/lib/active_support/inflector/transliterate.rb +48 -27
  173. data/lib/active_support/json.rb +4 -2
  174. data/lib/active_support/json/decoding.rb +16 -13
  175. data/lib/active_support/json/encoding.rb +15 -57
  176. data/lib/active_support/key_generator.rb +25 -25
  177. data/lib/active_support/lazy_load_hooks.rb +50 -20
  178. data/lib/active_support/locale/en.yml +2 -0
  179. data/lib/active_support/log_subscriber.rb +13 -10
  180. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  181. data/lib/active_support/logger.rb +54 -3
  182. data/lib/active_support/logger_silence.rb +12 -7
  183. data/lib/active_support/logger_thread_safe_level.rb +33 -0
  184. data/lib/active_support/message_encryptor.rb +173 -51
  185. data/lib/active_support/message_verifier.rb +150 -17
  186. data/lib/active_support/messages/metadata.rb +71 -0
  187. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  188. data/lib/active_support/messages/rotator.rb +56 -0
  189. data/lib/active_support/multibyte.rb +4 -2
  190. data/lib/active_support/multibyte/chars.rb +37 -24
  191. data/lib/active_support/multibyte/unicode.rb +100 -96
  192. data/lib/active_support/notifications.rb +11 -7
  193. data/lib/active_support/notifications/fanout.rb +10 -8
  194. data/lib/active_support/notifications/instrumenter.rb +27 -7
  195. data/lib/active_support/number_helper.rb +94 -68
  196. data/lib/active_support/number_helper/number_converter.rb +13 -11
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/option_merger.rb +3 -1
  206. data/lib/active_support/ordered_hash.rb +6 -4
  207. data/lib/active_support/ordered_options.rb +22 -4
  208. data/lib/active_support/per_thread_registry.rb +13 -6
  209. data/lib/active_support/proxy_object.rb +2 -0
  210. data/lib/active_support/rails.rb +16 -8
  211. data/lib/active_support/railtie.rb +43 -9
  212. data/lib/active_support/reloader.rb +131 -0
  213. data/lib/active_support/rescuable.rb +108 -53
  214. data/lib/active_support/security_utils.rb +17 -6
  215. data/lib/active_support/string_inquirer.rb +11 -3
  216. data/lib/active_support/subscriber.rb +15 -14
  217. data/lib/active_support/tagged_logging.rb +14 -11
  218. data/lib/active_support/test_case.rb +18 -46
  219. data/lib/active_support/testing/assertions.rb +137 -20
  220. data/lib/active_support/testing/autorun.rb +4 -2
  221. data/lib/active_support/testing/constant_lookup.rb +2 -1
  222. data/lib/active_support/testing/declarative.rb +3 -1
  223. data/lib/active_support/testing/deprecation.rb +14 -10
  224. data/lib/active_support/testing/file_fixtures.rb +36 -0
  225. data/lib/active_support/testing/isolation.rb +34 -25
  226. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  227. data/lib/active_support/testing/setup_and_teardown.rb +12 -3
  228. data/lib/active_support/testing/stream.rb +44 -0
  229. data/lib/active_support/testing/tagged_logging.rb +3 -1
  230. data/lib/active_support/testing/time_helpers.rb +96 -27
  231. data/lib/active_support/time.rb +14 -12
  232. data/lib/active_support/time_with_zone.rb +195 -53
  233. data/lib/active_support/values/time_zone.rb +200 -61
  234. data/lib/active_support/values/unicode_tables.dat +0 -0
  235. data/lib/active_support/version.rb +3 -1
  236. data/lib/active_support/xml_mini.rb +69 -51
  237. data/lib/active_support/xml_mini/jdom.rb +116 -113
  238. data/lib/active_support/xml_mini/libxml.rb +17 -16
  239. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  240. data/lib/active_support/xml_mini/nokogiri.rb +15 -15
  241. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  242. data/lib/active_support/xml_mini/rexml.rb +17 -16
  243. metadata +55 -43
  244. data/lib/active_support/concurrency/latch.rb +0 -27
  245. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
  246. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  247. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  248. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  249. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -11
  250. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  251. data/lib/active_support/core_ext/object/itself.rb +0 -15
  252. data/lib/active_support/core_ext/struct.rb +0 -6
  253. data/lib/active_support/core_ext/thread.rb +0 -86
  254. data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,6 +1,10 @@
1
- require 'base64'
2
- require 'active_support/core_ext/object/blank'
3
- require 'active_support/security_utils'
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "active_support/core_ext/object/blank"
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
@@ -15,7 +19,7 @@ module ActiveSupport
15
19
  # In the authentication filter:
16
20
  #
17
21
  # id, time = @verifier.verify(cookies[:remember_me])
18
- # if time < Time.now
22
+ # if Time.now < time
19
23
  # self.current_user = User.find(id)
20
24
  # end
21
25
  #
@@ -24,34 +28,163 @@ module ActiveSupport
24
28
  # hash upon initialization:
25
29
  #
26
30
  # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
31
+ #
32
+ # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
33
+ # If you want to use a different hash algorithm, you can change it by providing
34
+ # +:digest+ key as an option while initializing the verifier:
35
+ #
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
27
101
  class MessageVerifier
102
+ prepend Messages::Rotator::Verifier
103
+
28
104
  class InvalidSignature < StandardError; end
29
105
 
30
106
  def initialize(secret, options = {})
31
- raise ArgumentError, 'Secret should not be nil.' unless secret
107
+ raise ArgumentError, "Secret should not be nil." unless secret
32
108
  @secret = secret
33
- @digest = options[:digest] || 'SHA1'
109
+ @digest = options[:digest] || "SHA1"
34
110
  @serializer = options[:serializer] || Marshal
35
111
  end
36
112
 
37
- def verify(signed_message)
38
- raise InvalidSignature if signed_message.blank?
113
+ # Checks if a signed message could have been generated by signing an object
114
+ # with the +MessageVerifier+'s secret.
115
+ #
116
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
117
+ # signed_message = verifier.generate 'a private message'
118
+ # verifier.valid_message?(signed_message) # => true
119
+ #
120
+ # tampered_message = signed_message.chop # editing the message invalidates the signature
121
+ # verifier.valid_message?(tampered_message) # => false
122
+ def valid_message?(signed_message)
123
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
39
124
 
40
- data, digest = signed_message.split("--")
41
- if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
125
+ data, digest = signed_message.split("--".freeze)
126
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
127
+ end
128
+
129
+ # Decodes the signed message using the +MessageVerifier+'s secret.
130
+ #
131
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
132
+ #
133
+ # signed_message = verifier.generate 'a private message'
134
+ # verifier.verified(signed_message) # => 'a private message'
135
+ #
136
+ # Returns +nil+ if the message was not signed with the same secret.
137
+ #
138
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
139
+ # other_verifier.verified(signed_message) # => nil
140
+ #
141
+ # Returns +nil+ if the message is not Base64-encoded.
142
+ #
143
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
144
+ # verifier.verified(invalid_message) # => nil
145
+ #
146
+ # Raises any error raised while decoding the signed message.
147
+ #
148
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
149
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
150
+ def verified(signed_message, purpose: nil, **)
151
+ if valid_message?(signed_message)
42
152
  begin
43
- @serializer.load(decode(data))
153
+ data = signed_message.split("--".freeze)[0]
154
+ message = Messages::Metadata.verify(decode(data), purpose)
155
+ @serializer.load(message) if message
44
156
  rescue ArgumentError => argument_error
45
- raise InvalidSignature if argument_error.message =~ %r{invalid base64}
157
+ return if argument_error.message.include?("invalid base64")
46
158
  raise
47
159
  end
48
- else
49
- raise InvalidSignature
50
160
  end
51
161
  end
52
162
 
53
- def generate(value)
54
- data = encode(@serializer.dump(value))
163
+ # Decodes the signed message using the +MessageVerifier+'s secret.
164
+ #
165
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
166
+ # signed_message = verifier.generate 'a private message'
167
+ #
168
+ # verifier.verify(signed_message) # => 'a private message'
169
+ #
170
+ # Raises +InvalidSignature+ if the message was not signed with the same
171
+ # secret or was not Base64-encoded.
172
+ #
173
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
174
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
175
+ def verify(*args)
176
+ verified(*args) || raise(InvalidSignature)
177
+ end
178
+
179
+ # Generates a signed message for the provided value.
180
+ #
181
+ # The message is signed with the +MessageVerifier+'s secret. Without knowing
182
+ # the secret, the original value cannot be extracted from the message.
183
+ #
184
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
185
+ # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
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))
55
188
  "#{data}--#{generate_digest(data)}"
56
189
  end
57
190
 
@@ -65,7 +198,7 @@ module ActiveSupport
65
198
  end
66
199
 
67
200
  def generate_digest(data)
68
- require 'openssl' unless defined?(OpenSSL)
201
+ require "openssl" unless defined?(OpenSSL)
69
202
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
70
203
  end
71
204
  end
@@ -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,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport #:nodoc:
2
4
  module Multibyte
3
- autoload :Chars, 'active_support/multibyte/chars'
4
- autoload :Unicode, 'active_support/multibyte/unicode'
5
+ autoload :Chars, "active_support/multibyte/chars"
6
+ autoload :Unicode, "active_support/multibyte/unicode"
5
7
 
6
8
  # The proxy class returned when calling mb_chars. You can use this accessor
7
9
  # to configure your own proxy class so you can support other encodings. See
@@ -1,8 +1,10 @@
1
- # encoding: utf-8
2
- require 'active_support/json'
3
- require 'active_support/core_ext/string/access'
4
- require 'active_support/core_ext/string/behavior'
5
- require 'active_support/core_ext/module/delegation'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/json"
4
+ require "active_support/core_ext/string/access"
5
+ require "active_support/core_ext/string/behavior"
6
+ require "active_support/core_ext/module/delegation"
7
+ require "active_support/core_ext/regexp"
6
8
 
7
9
  module ActiveSupport #:nodoc:
8
10
  module Multibyte #:nodoc:
@@ -16,7 +18,8 @@ module ActiveSupport #:nodoc:
16
18
  # through the +mb_chars+ method. Methods which would normally return a
17
19
  # String object now return a Chars object so methods can be chained.
18
20
  #
19
- # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
21
+ # 'The Perfect String '.mb_chars.downcase.strip.normalize
22
+ # # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
20
23
  #
21
24
  # Chars objects are perfectly interchangeable with String objects as long as
22
25
  # no explicit class checks are made. If certain methods do explicitly check
@@ -46,7 +49,7 @@ module ActiveSupport #:nodoc:
46
49
  alias to_s wrapped_string
47
50
  alias to_str wrapped_string
48
51
 
49
- delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
52
+ delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string
50
53
 
51
54
  # Creates a new Chars instance by wrapping _string_.
52
55
  def initialize(string)
@@ -57,7 +60,7 @@ module ActiveSupport #:nodoc:
57
60
  # Forward all undefined methods to the wrapped string.
58
61
  def method_missing(method, *args, &block)
59
62
  result = @wrapped_string.__send__(method, *args, &block)
60
- if method.to_s =~ /!$/
63
+ if /!$/.match?(method)
61
64
  self if result
62
65
  else
63
66
  result.kind_of?(String) ? chars(result) : result
@@ -86,17 +89,27 @@ module ActiveSupport #:nodoc:
86
89
  @wrapped_string.split(*args).map { |i| self.class.new(i) }
87
90
  end
88
91
 
89
- # Works like like <tt>String#slice!</tt>, but returns an instance of
90
- # Chars, or nil if the string was not modified.
92
+ # Works like <tt>String#slice!</tt>, but returns an instance of
93
+ # Chars, or +nil+ if the string was not modified. The string will not be
94
+ # modified if the range given is out of bounds
95
+ #
96
+ # string = 'Welcome'
97
+ # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
98
+ # string # => 'Welome'
99
+ # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
100
+ # string # => 'me'
91
101
  def slice!(*args)
92
- chars(@wrapped_string.slice!(*args))
102
+ string_sliced = @wrapped_string.slice!(*args)
103
+ if string_sliced
104
+ chars(string_sliced)
105
+ end
93
106
  end
94
107
 
95
108
  # Reverses all characters in the string.
96
109
  #
97
110
  # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
98
111
  def reverse
99
- chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
112
+ chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
100
113
  end
101
114
 
102
115
  # Limits the byte size of the string to a number of bytes without breaking
@@ -124,7 +137,7 @@ module ActiveSupport #:nodoc:
124
137
 
125
138
  # Converts characters in the string to the opposite case.
126
139
  #
127
- # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
140
+ # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
128
141
  def swapcase
129
142
  chars Unicode.swapcase(@wrapped_string)
130
143
  end
@@ -133,15 +146,15 @@ module ActiveSupport #:nodoc:
133
146
  #
134
147
  # 'über'.mb_chars.capitalize.to_s # => "Über"
135
148
  def capitalize
136
- (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
149
+ (slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase
137
150
  end
138
151
 
139
152
  # Capitalizes the first letter of every word, when possible.
140
153
  #
141
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
142
- # "日本語".mb_chars.titleize # => "日本語"
154
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
155
+ # "日本語".mb_chars.titleize.to_s # => "日本語"
143
156
  def titleize
144
- chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
157
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
145
158
  end
146
159
  alias_method :titlecase, :titleize
147
160
 
@@ -161,7 +174,7 @@ module ActiveSupport #:nodoc:
161
174
  # 'é'.length # => 2
162
175
  # 'é'.mb_chars.decompose.to_s.length # => 3
163
176
  def decompose
164
- chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
177
+ chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
165
178
  end
166
179
 
167
180
  # Performs composition on all the characters.
@@ -169,7 +182,7 @@ module ActiveSupport #:nodoc:
169
182
  # 'é'.length # => 3
170
183
  # 'é'.mb_chars.compose.to_s.length # => 2
171
184
  def compose
172
- chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
185
+ chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
173
186
  end
174
187
 
175
188
  # Returns the number of grapheme clusters in the string.
@@ -200,21 +213,21 @@ module ActiveSupport #:nodoc:
200
213
  end
201
214
  end
202
215
 
203
- protected
216
+ private
204
217
 
205
- def translate_offset(byte_offset) #:nodoc:
218
+ def translate_offset(byte_offset)
206
219
  return nil if byte_offset.nil?
207
- return 0 if @wrapped_string == ''
220
+ return 0 if @wrapped_string == ""
208
221
 
209
222
  begin
210
- @wrapped_string.byteslice(0...byte_offset).unpack('U*').length
223
+ @wrapped_string.byteslice(0...byte_offset).unpack("U*").length
211
224
  rescue ArgumentError
212
225
  byte_offset -= 1
213
226
  retry
214
227
  end
215
228
  end
216
229
 
217
- def chars(string) #:nodoc:
230
+ def chars(string)
218
231
  self.class.new(string)
219
232
  end
220
233
  end