activesupport 4.0.13 → 5.2.5

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 (264) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +412 -444
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +8 -4
  5. data/lib/active_support/all.rb +5 -3
  6. data/lib/active_support/array_inquirer.rb +48 -0
  7. data/lib/active_support/backtrace_cleaner.rb +14 -12
  8. data/lib/active_support/benchmarkable.rb +6 -14
  9. data/lib/active_support/builder.rb +3 -1
  10. data/lib/active_support/cache/file_store.rb +67 -51
  11. data/lib/active_support/cache/mem_cache_store.rb +95 -97
  12. data/lib/active_support/cache/memory_store.rb +28 -30
  13. data/lib/active_support/cache/null_store.rb +7 -8
  14. data/lib/active_support/cache/redis_cache_store.rb +466 -0
  15. data/lib/active_support/cache/strategy/local_cache.rb +70 -56
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  17. data/lib/active_support/cache.rb +331 -206
  18. data/lib/active_support/callbacks.rb +697 -426
  19. data/lib/active_support/concern.rb +32 -10
  20. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  21. data/lib/active_support/concurrency/share_lock.rb +227 -0
  22. data/lib/active_support/configurable.rb +8 -5
  23. data/lib/active_support/core_ext/array/access.rb +39 -1
  24. data/lib/active_support/core_ext/array/conversions.rb +24 -35
  25. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +23 -13
  27. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  28. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -5
  29. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  30. data/lib/active_support/core_ext/array.rb +9 -7
  31. data/lib/active_support/core_ext/benchmark.rb +3 -1
  32. data/lib/active_support/core_ext/big_decimal/conversions.rb +9 -26
  33. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  34. data/lib/active_support/core_ext/class/attribute.rb +41 -23
  35. data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
  36. data/lib/active_support/core_ext/class/subclasses.rb +20 -8
  37. data/lib/active_support/core_ext/class.rb +4 -4
  38. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  39. data/lib/active_support/core_ext/date/blank.rb +14 -0
  40. data/lib/active_support/core_ext/date/calculations.rb +21 -9
  41. data/lib/active_support/core_ext/date/conversions.rb +32 -22
  42. data/lib/active_support/core_ext/date/zones.rb +5 -34
  43. data/lib/active_support/core_ext/date.rb +6 -4
  44. data/lib/active_support/core_ext/date_and_time/calculations.rb +199 -57
  45. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  46. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  47. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  48. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  49. data/lib/active_support/core_ext/date_time/calculations.rb +78 -37
  50. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  51. data/lib/active_support/core_ext/date_time/conversions.rb +19 -13
  52. data/lib/active_support/core_ext/date_time.rb +7 -4
  53. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  54. data/lib/active_support/core_ext/digest.rb +3 -0
  55. data/lib/active_support/core_ext/enumerable.rb +113 -29
  56. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  57. data/lib/active_support/core_ext/file.rb +3 -1
  58. data/lib/active_support/core_ext/hash/compact.rb +29 -0
  59. data/lib/active_support/core_ext/hash/conversions.rb +71 -49
  60. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  61. data/lib/active_support/core_ext/hash/except.rb +12 -3
  62. data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
  63. data/lib/active_support/core_ext/hash/keys.rb +50 -38
  64. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  65. data/lib/active_support/core_ext/hash/slice.rb +12 -6
  66. data/lib/active_support/core_ext/hash/transform_values.rb +32 -0
  67. data/lib/active_support/core_ext/hash.rb +11 -8
  68. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  69. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  70. data/lib/active_support/core_ext/integer/time.rb +11 -33
  71. data/lib/active_support/core_ext/integer.rb +5 -3
  72. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  73. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  74. data/lib/active_support/core_ext/kernel/reporting.rb +5 -74
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +6 -4
  77. data/lib/active_support/core_ext/load_error.rb +5 -21
  78. data/lib/active_support/core_ext/marshal.rb +13 -10
  79. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  80. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  81. data/lib/active_support/core_ext/module/attr_internal.rb +8 -8
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +170 -21
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
  84. data/lib/active_support/core_ext/module/concerning.rb +134 -0
  85. data/lib/active_support/core_ext/module/delegation.rb +135 -45
  86. data/lib/active_support/core_ext/module/deprecation.rb +3 -3
  87. data/lib/active_support/core_ext/module/introspection.rb +9 -25
  88. data/lib/active_support/core_ext/module/reachable.rb +5 -2
  89. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  90. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  91. data/lib/active_support/core_ext/module.rb +14 -10
  92. data/lib/active_support/core_ext/name_error.rb +22 -2
  93. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  94. data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
  95. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  96. data/lib/active_support/core_ext/numeric/time.rb +37 -50
  97. data/lib/active_support/core_ext/numeric.rb +6 -3
  98. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  99. data/lib/active_support/core_ext/object/blank.rb +70 -19
  100. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  101. data/lib/active_support/core_ext/object/deep_dup.rb +19 -10
  102. data/lib/active_support/core_ext/object/duplicable.rb +100 -34
  103. data/lib/active_support/core_ext/object/inclusion.rb +18 -15
  104. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  105. data/lib/active_support/core_ext/object/json.rb +227 -0
  106. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  107. data/lib/active_support/core_ext/object/to_query.rb +21 -8
  108. data/lib/active_support/core_ext/object/try.rb +94 -24
  109. data/lib/active_support/core_ext/object/with_options.rb +45 -5
  110. data/lib/active_support/core_ext/object.rb +14 -12
  111. data/lib/active_support/core_ext/range/compare_range.rb +61 -0
  112. data/lib/active_support/core_ext/range/conversions.rb +27 -7
  113. data/lib/active_support/core_ext/range/each.rb +19 -17
  114. data/lib/active_support/core_ext/range/include_range.rb +2 -22
  115. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  116. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  117. data/lib/active_support/core_ext/range.rb +7 -4
  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/access.rb +41 -39
  121. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  122. data/lib/active_support/core_ext/string/conversions.rb +17 -13
  123. data/lib/active_support/core_ext/string/exclude.rb +5 -3
  124. data/lib/active_support/core_ext/string/filters.rb +55 -6
  125. data/lib/active_support/core_ext/string/indent.rb +6 -4
  126. data/lib/active_support/core_ext/string/inflections.rb +66 -24
  127. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  128. data/lib/active_support/core_ext/string/multibyte.rb +15 -7
  129. data/lib/active_support/core_ext/string/output_safety.rb +114 -54
  130. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  131. data/lib/active_support/core_ext/string/strip.rb +4 -5
  132. data/lib/active_support/core_ext/string/zones.rb +4 -1
  133. data/lib/active_support/core_ext/string.rb +15 -13
  134. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  135. data/lib/active_support/core_ext/time/calculations.rb +123 -110
  136. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  137. data/lib/active_support/core_ext/time/conversions.rb +23 -14
  138. data/lib/active_support/core_ext/time/zones.rb +42 -26
  139. data/lib/active_support/core_ext/time.rb +7 -5
  140. data/lib/active_support/core_ext/uri.rb +6 -8
  141. data/lib/active_support/core_ext.rb +3 -2
  142. data/lib/active_support/current_attributes.rb +195 -0
  143. data/lib/active_support/dependencies/autoload.rb +3 -1
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/dependencies.rb +196 -166
  146. data/lib/active_support/deprecation/behaviors.rb +48 -15
  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 +66 -20
  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/deprecation.rb +14 -11
  153. data/lib/active_support/descendants_tracker.rb +2 -0
  154. data/lib/active_support/digest.rb +20 -0
  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/duration.rb +354 -34
  158. data/lib/active_support/encrypted_configuration.rb +49 -0
  159. data/lib/active_support/encrypted_file.rb +99 -0
  160. data/lib/active_support/evented_file_update_checker.rb +205 -0
  161. data/lib/active_support/execution_wrapper.rb +128 -0
  162. data/lib/active_support/executor.rb +8 -0
  163. data/lib/active_support/file_update_checker.rb +63 -37
  164. data/lib/active_support/gem_version.rb +17 -0
  165. data/lib/active_support/gzip.rb +7 -5
  166. data/lib/active_support/hash_with_indifferent_access.rb +158 -35
  167. data/lib/active_support/i18n.rb +8 -6
  168. data/lib/active_support/i18n_railtie.rb +38 -20
  169. data/lib/active_support/inflections.rb +19 -12
  170. data/lib/active_support/inflector/inflections.rb +79 -30
  171. data/lib/active_support/inflector/methods.rb +197 -129
  172. data/lib/active_support/inflector/transliterate.rb +48 -27
  173. data/lib/active_support/inflector.rb +7 -5
  174. data/lib/active_support/json/decoding.rb +21 -25
  175. data/lib/active_support/json/encoding.rb +84 -292
  176. data/lib/active_support/json.rb +4 -2
  177. data/lib/active_support/key_generator.rb +26 -28
  178. data/lib/active_support/lazy_load_hooks.rb +51 -21
  179. data/lib/active_support/locale/en.yml +2 -0
  180. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  181. data/lib/active_support/log_subscriber.rb +13 -10
  182. data/lib/active_support/logger.rb +54 -3
  183. data/lib/active_support/logger_silence.rb +12 -7
  184. data/lib/active_support/logger_thread_safe_level.rb +34 -0
  185. data/lib/active_support/message_encryptor.rb +173 -50
  186. data/lib/active_support/message_verifier.rb +159 -22
  187. data/lib/active_support/messages/metadata.rb +71 -0
  188. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  189. data/lib/active_support/messages/rotator.rb +56 -0
  190. data/lib/active_support/multibyte/chars.rb +38 -26
  191. data/lib/active_support/multibyte/unicode.rb +138 -146
  192. data/lib/active_support/multibyte.rb +4 -2
  193. data/lib/active_support/notifications/fanout.rb +23 -16
  194. data/lib/active_support/notifications/instrumenter.rb +29 -8
  195. data/lib/active_support/notifications.rb +22 -13
  196. data/lib/active_support/number_helper/number_converter.rb +184 -0
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +29 -0
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +59 -0
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +14 -0
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +54 -0
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/number_helper.rb +125 -391
  206. data/lib/active_support/option_merger.rb +3 -1
  207. data/lib/active_support/ordered_hash.rb +6 -4
  208. data/lib/active_support/ordered_options.rb +31 -5
  209. data/lib/active_support/per_thread_registry.rb +19 -11
  210. data/lib/active_support/proxy_object.rb +2 -0
  211. data/lib/active_support/rails.rb +16 -8
  212. data/lib/active_support/railtie.rb +43 -9
  213. data/lib/active_support/reloader.rb +131 -0
  214. data/lib/active_support/rescuable.rb +108 -53
  215. data/lib/active_support/security_utils.rb +31 -0
  216. data/lib/active_support/string_inquirer.rb +11 -3
  217. data/lib/active_support/subscriber.rb +54 -17
  218. data/lib/active_support/tagged_logging.rb +14 -11
  219. data/lib/active_support/test_case.rb +42 -37
  220. data/lib/active_support/testing/assertions.rb +126 -39
  221. data/lib/active_support/testing/autorun.rb +5 -3
  222. data/lib/active_support/testing/constant_lookup.rb +3 -6
  223. data/lib/active_support/testing/declarative.rb +10 -22
  224. data/lib/active_support/testing/deprecation.rb +14 -10
  225. data/lib/active_support/testing/file_fixtures.rb +36 -0
  226. data/lib/active_support/testing/isolation.rb +55 -86
  227. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  228. data/lib/active_support/testing/setup_and_teardown.rb +30 -10
  229. data/lib/active_support/testing/stream.rb +44 -0
  230. data/lib/active_support/testing/tagged_logging.rb +5 -3
  231. data/lib/active_support/testing/time_helpers.rb +200 -0
  232. data/lib/active_support/time.rb +13 -13
  233. data/lib/active_support/time_with_zone.rb +223 -73
  234. data/lib/active_support/values/time_zone.rb +261 -126
  235. data/lib/active_support/values/unicode_tables.dat +0 -0
  236. data/lib/active_support/version.rb +6 -7
  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. data/lib/active_support/xml_mini.rb +69 -51
  244. data/lib/active_support.rb +29 -3
  245. metadata +84 -54
  246. data/lib/active_support/basic_object.rb +0 -11
  247. data/lib/active_support/buffered_logger.rb +0 -21
  248. data/lib/active_support/concurrency/latch.rb +0 -27
  249. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  250. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -40
  251. data/lib/active_support/core_ext/date_time/zones.rb +0 -24
  252. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  253. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  254. data/lib/active_support/core_ext/logger.rb +0 -67
  255. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  256. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  257. data/lib/active_support/core_ext/proc.rb +0 -17
  258. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  259. data/lib/active_support/core_ext/struct.rb +0 -6
  260. data/lib/active_support/core_ext/thread.rb +0 -79
  261. data/lib/active_support/core_ext/time/marshal.rb +0 -30
  262. data/lib/active_support/file_watcher.rb +0 -36
  263. data/lib/active_support/json/variable.rb +0 -18
  264. data/lib/active_support/testing/pending.rb +0 -14
@@ -1,5 +1,10 @@
1
- require 'base64'
2
- require 'active_support/core_ext/object/blank'
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"
3
8
 
4
9
  module ActiveSupport
5
10
  # +MessageVerifier+ makes it easy to generate and verify messages which are
@@ -14,7 +19,7 @@ module ActiveSupport
14
19
  # In the authentication filter:
15
20
  #
16
21
  # id, time = @verifier.verify(cookies[:remember_me])
17
- # if time < Time.now
22
+ # if Time.now < time
18
23
  # self.current_user = User.find(id)
19
24
  # end
20
25
  #
@@ -23,45 +28,177 @@ module ActiveSupport
23
28
  # hash upon initialization:
24
29
  #
25
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
26
101
  class MessageVerifier
102
+ prepend Messages::Rotator::Verifier
103
+
27
104
  class InvalidSignature < StandardError; end
28
105
 
29
106
  def initialize(secret, options = {})
107
+ raise ArgumentError, "Secret should not be nil." unless secret
30
108
  @secret = secret
31
- @digest = options[:digest] || 'SHA1'
109
+ @digest = options[:digest] || "SHA1"
32
110
  @serializer = options[:serializer] || Marshal
33
111
  end
34
112
 
35
- def verify(signed_message)
36
- 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?
124
+
125
+ data, digest = signed_message.split("--".freeze)
126
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
127
+ end
37
128
 
38
- data, digest = signed_message.split("--")
39
- if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
40
- @serializer.load(::Base64.decode64(data))
41
- else
42
- raise InvalidSignature
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)
152
+ begin
153
+ data = signed_message.split("--".freeze)[0]
154
+ message = Messages::Metadata.verify(decode(data), purpose)
155
+ @serializer.load(message) if message
156
+ rescue ArgumentError => argument_error
157
+ return if argument_error.message.include?("invalid base64")
158
+ raise
159
+ end
43
160
  end
44
161
  end
45
162
 
46
- def generate(value)
47
- data = ::Base64.strict_encode64(@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))
48
188
  "#{data}--#{generate_digest(data)}"
49
189
  end
50
190
 
51
191
  private
52
- # constant-time comparison algorithm to prevent timing attacks
53
- def secure_compare(a, b)
54
- return false unless a.bytesize == b.bytesize
55
-
56
- l = a.unpack "C#{a.bytesize}"
192
+ def encode(data)
193
+ ::Base64.strict_encode64(data)
194
+ end
57
195
 
58
- res = 0
59
- b.each_byte { |byte| res |= byte ^ l.shift }
60
- res == 0
196
+ def decode(data)
197
+ ::Base64.strict_decode64(data)
61
198
  end
62
199
 
63
200
  def generate_digest(data)
64
- require 'openssl' unless defined?(OpenSSL)
201
+ require "openssl" unless defined?(OpenSSL)
65
202
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
66
203
  end
67
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,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)
@@ -56,11 +59,10 @@ module ActiveSupport #:nodoc:
56
59
 
57
60
  # Forward all undefined methods to the wrapped string.
58
61
  def method_missing(method, *args, &block)
59
- if method.to_s =~ /!$/
60
- result = @wrapped_string.__send__(method, *args, &block)
62
+ result = @wrapped_string.__send__(method, *args, &block)
63
+ if /!$/.match?(method)
61
64
  self if result
62
65
  else
63
- result = @wrapped_string.__send__(method, *args, &block)
64
66
  result.kind_of?(String) ? chars(result) : result
65
67
  end
66
68
  end
@@ -87,17 +89,27 @@ module ActiveSupport #:nodoc:
87
89
  @wrapped_string.split(*args).map { |i| self.class.new(i) }
88
90
  end
89
91
 
90
- # Works like like <tt>String#slice!</tt>, but returns an instance of
91
- # 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'
92
101
  def slice!(*args)
93
- chars(@wrapped_string.slice!(*args))
102
+ string_sliced = @wrapped_string.slice!(*args)
103
+ if string_sliced
104
+ chars(string_sliced)
105
+ end
94
106
  end
95
107
 
96
108
  # Reverses all characters in the string.
97
109
  #
98
110
  # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
99
111
  def reverse
100
- chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
112
+ chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
101
113
  end
102
114
 
103
115
  # Limits the byte size of the string to a number of bytes without breaking
@@ -125,7 +137,7 @@ module ActiveSupport #:nodoc:
125
137
 
126
138
  # Converts characters in the string to the opposite case.
127
139
  #
128
- # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
140
+ # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
129
141
  def swapcase
130
142
  chars Unicode.swapcase(@wrapped_string)
131
143
  end
@@ -134,15 +146,15 @@ module ActiveSupport #:nodoc:
134
146
  #
135
147
  # 'über'.mb_chars.capitalize.to_s # => "Über"
136
148
  def capitalize
137
- (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
149
+ (slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase
138
150
  end
139
151
 
140
152
  # Capitalizes the first letter of every word, when possible.
141
153
  #
142
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
143
- # "日本語".mb_chars.titleize # => "日本語"
154
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
155
+ # "日本語".mb_chars.titleize.to_s # => "日本語"
144
156
  def titleize
145
- chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
157
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
146
158
  end
147
159
  alias_method :titlecase, :titleize
148
160
 
@@ -162,7 +174,7 @@ module ActiveSupport #:nodoc:
162
174
  # 'é'.length # => 2
163
175
  # 'é'.mb_chars.decompose.to_s.length # => 3
164
176
  def decompose
165
- chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
177
+ chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
166
178
  end
167
179
 
168
180
  # Performs composition on all the characters.
@@ -170,7 +182,7 @@ module ActiveSupport #:nodoc:
170
182
  # 'é'.length # => 3
171
183
  # 'é'.mb_chars.compose.to_s.length # => 2
172
184
  def compose
173
- chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
185
+ chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
174
186
  end
175
187
 
176
188
  # Returns the number of grapheme clusters in the string.
@@ -201,21 +213,21 @@ module ActiveSupport #:nodoc:
201
213
  end
202
214
  end
203
215
 
204
- protected
216
+ private
205
217
 
206
- def translate_offset(byte_offset) #:nodoc:
218
+ def translate_offset(byte_offset)
207
219
  return nil if byte_offset.nil?
208
- return 0 if @wrapped_string == ''
220
+ return 0 if @wrapped_string == ""
209
221
 
210
222
  begin
211
- @wrapped_string.byteslice(0...byte_offset).unpack('U*').length
223
+ @wrapped_string.byteslice(0...byte_offset).unpack("U*").length
212
224
  rescue ArgumentError
213
225
  byte_offset -= 1
214
226
  retry
215
227
  end
216
228
  end
217
229
 
218
- def chars(string) #:nodoc:
230
+ def chars(string)
219
231
  self.class.new(string)
220
232
  end
221
233
  end