activesupport 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +572 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support.rb +96 -0
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/all.rb +5 -0
  8. data/lib/active_support/array_inquirer.rb +48 -0
  9. data/lib/active_support/backtrace_cleaner.rb +132 -0
  10. data/lib/active_support/benchmarkable.rb +51 -0
  11. data/lib/active_support/builder.rb +8 -0
  12. data/lib/active_support/cache.rb +830 -0
  13. data/lib/active_support/cache/file_store.rb +196 -0
  14. data/lib/active_support/cache/mem_cache_store.rb +212 -0
  15. data/lib/active_support/cache/memory_store.rb +174 -0
  16. data/lib/active_support/cache/null_store.rb +48 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +488 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +194 -0
  19. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  20. data/lib/active_support/callbacks.rb +856 -0
  21. data/lib/active_support/concern.rb +171 -0
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  23. data/lib/active_support/concurrency/share_lock.rb +227 -0
  24. data/lib/active_support/configurable.rb +146 -0
  25. data/lib/active_support/core_ext.rb +5 -0
  26. data/lib/active_support/core_ext/array.rb +9 -0
  27. data/lib/active_support/core_ext/array/access.rb +104 -0
  28. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -0
  34. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  35. data/lib/active_support/core_ext/benchmark.rb +16 -0
  36. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  37. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  38. data/lib/active_support/core_ext/class.rb +4 -0
  39. data/lib/active_support/core_ext/class/attribute.rb +141 -0
  40. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  41. data/lib/active_support/core_ext/class/subclasses.rb +54 -0
  42. data/lib/active_support/core_ext/date.rb +7 -0
  43. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  44. data/lib/active_support/core_ext/date/blank.rb +14 -0
  45. data/lib/active_support/core_ext/date/calculations.rb +146 -0
  46. data/lib/active_support/core_ext/date/conversions.rb +96 -0
  47. data/lib/active_support/core_ext/date/zones.rb +8 -0
  48. data/lib/active_support/core_ext/date_and_time/calculations.rb +351 -0
  49. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  50. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  51. data/lib/active_support/core_ext/date_time.rb +7 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +211 -0
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +107 -0
  57. data/lib/active_support/core_ext/digest.rb +3 -0
  58. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  59. data/lib/active_support/core_ext/enumerable.rb +188 -0
  60. data/lib/active_support/core_ext/file.rb +3 -0
  61. data/lib/active_support/core_ext/file/atomic.rb +70 -0
  62. data/lib/active_support/core_ext/hash.rb +10 -0
  63. data/lib/active_support/core_ext/hash/compact.rb +5 -0
  64. data/lib/active_support/core_ext/hash/conversions.rb +263 -0
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +34 -0
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +24 -0
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  69. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  71. data/lib/active_support/core_ext/hash/slice.rb +26 -0
  72. data/lib/active_support/core_ext/hash/transform_values.rb +5 -0
  73. data/lib/active_support/core_ext/integer.rb +5 -0
  74. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  75. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  76. data/lib/active_support/core_ext/integer/time.rb +22 -0
  77. data/lib/active_support/core_ext/kernel.rb +5 -0
  78. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  79. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  80. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  81. data/lib/active_support/core_ext/load_error.rb +9 -0
  82. data/lib/active_support/core_ext/marshal.rb +24 -0
  83. data/lib/active_support/core_ext/module.rb +13 -0
  84. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  85. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  86. data/lib/active_support/core_ext/module/attr_internal.rb +38 -0
  87. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  88. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +144 -0
  89. data/lib/active_support/core_ext/module/concerning.rb +134 -0
  90. data/lib/active_support/core_ext/module/delegation.rb +313 -0
  91. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  92. data/lib/active_support/core_ext/module/introspection.rb +86 -0
  93. data/lib/active_support/core_ext/module/reachable.rb +6 -0
  94. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  95. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  96. data/lib/active_support/core_ext/name_error.rb +38 -0
  97. data/lib/active_support/core_ext/numeric.rb +5 -0
  98. data/lib/active_support/core_ext/numeric/bytes.rb +66 -0
  99. data/lib/active_support/core_ext/numeric/conversions.rb +136 -0
  100. data/lib/active_support/core_ext/numeric/inquiry.rb +5 -0
  101. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  102. data/lib/active_support/core_ext/object.rb +16 -0
  103. data/lib/active_support/core_ext/object/acts_like.rb +21 -0
  104. data/lib/active_support/core_ext/object/blank.rb +155 -0
  105. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  106. data/lib/active_support/core_ext/object/deep_dup.rb +55 -0
  107. data/lib/active_support/core_ext/object/duplicable.rb +49 -0
  108. data/lib/active_support/core_ext/object/inclusion.rb +29 -0
  109. data/lib/active_support/core_ext/object/instance_variables.rb +30 -0
  110. data/lib/active_support/core_ext/object/json.rb +228 -0
  111. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  112. data/lib/active_support/core_ext/object/to_query.rb +89 -0
  113. data/lib/active_support/core_ext/object/try.rb +156 -0
  114. data/lib/active_support/core_ext/object/with_options.rb +82 -0
  115. data/lib/active_support/core_ext/range.rb +7 -0
  116. data/lib/active_support/core_ext/range/compare_range.rb +70 -0
  117. data/lib/active_support/core_ext/range/conversions.rb +41 -0
  118. data/lib/active_support/core_ext/range/each.rb +25 -0
  119. data/lib/active_support/core_ext/range/include_range.rb +9 -0
  120. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  121. data/lib/active_support/core_ext/range/overlaps.rb +10 -0
  122. data/lib/active_support/core_ext/regexp.rb +7 -0
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string.rb +15 -0
  125. data/lib/active_support/core_ext/string/access.rb +114 -0
  126. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  127. data/lib/active_support/core_ext/string/conversions.rb +59 -0
  128. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  129. data/lib/active_support/core_ext/string/filters.rb +145 -0
  130. data/lib/active_support/core_ext/string/indent.rb +45 -0
  131. data/lib/active_support/core_ext/string/inflections.rb +259 -0
  132. data/lib/active_support/core_ext/string/inquiry.rb +15 -0
  133. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  134. data/lib/active_support/core_ext/string/output_safety.rb +314 -0
  135. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  136. data/lib/active_support/core_ext/string/strip.rb +27 -0
  137. data/lib/active_support/core_ext/string/zones.rb +16 -0
  138. data/lib/active_support/core_ext/time.rb +7 -0
  139. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  140. data/lib/active_support/core_ext/time/calculations.rb +344 -0
  141. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  142. data/lib/active_support/core_ext/time/conversions.rb +72 -0
  143. data/lib/active_support/core_ext/time/zones.rb +113 -0
  144. data/lib/active_support/core_ext/uri.rb +25 -0
  145. data/lib/active_support/current_attributes.rb +203 -0
  146. data/lib/active_support/dependencies.rb +806 -0
  147. data/lib/active_support/dependencies/autoload.rb +79 -0
  148. data/lib/active_support/dependencies/interlock.rb +57 -0
  149. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  150. data/lib/active_support/deprecation.rb +46 -0
  151. data/lib/active_support/deprecation/behaviors.rb +109 -0
  152. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  153. data/lib/active_support/deprecation/instance_delegator.rb +39 -0
  154. data/lib/active_support/deprecation/method_wrappers.rb +78 -0
  155. data/lib/active_support/deprecation/proxy_wrappers.rb +173 -0
  156. data/lib/active_support/deprecation/reporting.rb +114 -0
  157. data/lib/active_support/descendants_tracker.rb +109 -0
  158. data/lib/active_support/digest.rb +20 -0
  159. data/lib/active_support/duration.rb +433 -0
  160. data/lib/active_support/duration/iso8601_parser.rb +124 -0
  161. data/lib/active_support/duration/iso8601_serializer.rb +54 -0
  162. data/lib/active_support/encrypted_configuration.rb +45 -0
  163. data/lib/active_support/encrypted_file.rb +100 -0
  164. data/lib/active_support/evented_file_update_checker.rb +235 -0
  165. data/lib/active_support/execution_wrapper.rb +129 -0
  166. data/lib/active_support/executor.rb +8 -0
  167. data/lib/active_support/file_update_checker.rb +163 -0
  168. data/lib/active_support/gem_version.rb +17 -0
  169. data/lib/active_support/gzip.rb +38 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +399 -0
  171. data/lib/active_support/i18n.rb +16 -0
  172. data/lib/active_support/i18n_railtie.rb +126 -0
  173. data/lib/active_support/inflections.rb +72 -0
  174. data/lib/active_support/inflector.rb +9 -0
  175. data/lib/active_support/inflector/inflections.rb +257 -0
  176. data/lib/active_support/inflector/methods.rb +398 -0
  177. data/lib/active_support/inflector/transliterate.rb +147 -0
  178. data/lib/active_support/json.rb +4 -0
  179. data/lib/active_support/json/decoding.rb +76 -0
  180. data/lib/active_support/json/encoding.rb +134 -0
  181. data/lib/active_support/key_generator.rb +41 -0
  182. data/lib/active_support/lazy_load_hooks.rb +82 -0
  183. data/lib/active_support/locale/en.rb +31 -0
  184. data/lib/active_support/locale/en.yml +135 -0
  185. data/lib/active_support/log_subscriber.rb +135 -0
  186. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  187. data/lib/active_support/logger.rb +93 -0
  188. data/lib/active_support/logger_silence.rb +45 -0
  189. data/lib/active_support/logger_thread_safe_level.rb +56 -0
  190. data/lib/active_support/message_encryptor.rb +227 -0
  191. data/lib/active_support/message_verifier.rb +205 -0
  192. data/lib/active_support/messages/metadata.rb +71 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  194. data/lib/active_support/messages/rotator.rb +56 -0
  195. data/lib/active_support/multibyte.rb +23 -0
  196. data/lib/active_support/multibyte/chars.rb +216 -0
  197. data/lib/active_support/multibyte/unicode.rb +157 -0
  198. data/lib/active_support/notifications.rb +253 -0
  199. data/lib/active_support/notifications/fanout.rb +244 -0
  200. data/lib/active_support/notifications/instrumenter.rb +164 -0
  201. data/lib/active_support/number_helper.rb +378 -0
  202. data/lib/active_support/number_helper/number_converter.rb +184 -0
  203. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  204. data/lib/active_support/number_helper/number_to_delimited_converter.rb +31 -0
  205. data/lib/active_support/number_helper/number_to_human_converter.rb +70 -0
  206. data/lib/active_support/number_helper/number_to_human_size_converter.rb +61 -0
  207. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  208. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  209. data/lib/active_support/number_helper/number_to_rounded_converter.rb +56 -0
  210. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  211. data/lib/active_support/option_merger.rb +27 -0
  212. data/lib/active_support/ordered_hash.rb +50 -0
  213. data/lib/active_support/ordered_options.rb +85 -0
  214. data/lib/active_support/parameter_filter.rb +129 -0
  215. data/lib/active_support/per_thread_registry.rb +60 -0
  216. data/lib/active_support/proxy_object.rb +15 -0
  217. data/lib/active_support/rails.rb +29 -0
  218. data/lib/active_support/railtie.rb +80 -0
  219. data/lib/active_support/reloader.rb +130 -0
  220. data/lib/active_support/rescuable.rb +174 -0
  221. data/lib/active_support/security_utils.rb +31 -0
  222. data/lib/active_support/string_inquirer.rb +34 -0
  223. data/lib/active_support/subscriber.rb +169 -0
  224. data/lib/active_support/tagged_logging.rb +88 -0
  225. data/lib/active_support/test_case.rb +163 -0
  226. data/lib/active_support/testing/assertions.rb +228 -0
  227. data/lib/active_support/testing/autorun.rb +7 -0
  228. data/lib/active_support/testing/constant_lookup.rb +51 -0
  229. data/lib/active_support/testing/declarative.rb +28 -0
  230. data/lib/active_support/testing/deprecation.rb +38 -0
  231. data/lib/active_support/testing/file_fixtures.rb +38 -0
  232. data/lib/active_support/testing/isolation.rb +110 -0
  233. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  234. data/lib/active_support/testing/parallelization.rb +128 -0
  235. data/lib/active_support/testing/setup_and_teardown.rb +55 -0
  236. data/lib/active_support/testing/stream.rb +44 -0
  237. data/lib/active_support/testing/tagged_logging.rb +27 -0
  238. data/lib/active_support/testing/time_helpers.rb +200 -0
  239. data/lib/active_support/time.rb +20 -0
  240. data/lib/active_support/time_with_zone.rb +561 -0
  241. data/lib/active_support/values/time_zone.rb +570 -0
  242. data/lib/active_support/version.rb +10 -0
  243. data/lib/active_support/xml_mini.rb +202 -0
  244. data/lib/active_support/xml_mini/jdom.rb +183 -0
  245. data/lib/active_support/xml_mini/libxml.rb +80 -0
  246. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  247. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  248. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  249. data/lib/active_support/xml_mini/rexml.rb +130 -0
  250. metadata +385 -0
@@ -0,0 +1,205 @@
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"
8
+
9
+ module ActiveSupport
10
+ # +MessageVerifier+ makes it easy to generate and verify messages which are
11
+ # signed to prevent tampering.
12
+ #
13
+ # This is useful for cases like remember-me tokens and auto-unsubscribe links
14
+ # where the session store isn't suitable or available.
15
+ #
16
+ # Remember Me:
17
+ # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
18
+ #
19
+ # In the authentication filter:
20
+ #
21
+ # id, time = @verifier.verify(cookies[:remember_me])
22
+ # if Time.now < time
23
+ # self.current_user = User.find(id)
24
+ # end
25
+ #
26
+ # By default it uses Marshal to serialize the message. If you want to use
27
+ # another serialization method, you can set the serializer in the options
28
+ # hash upon initialization:
29
+ #
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 up to 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
101
+ class MessageVerifier
102
+ prepend Messages::Rotator::Verifier
103
+
104
+ class InvalidSignature < StandardError; end
105
+
106
+ def initialize(secret, options = {})
107
+ raise ArgumentError, "Secret should not be nil." unless secret
108
+ @secret = secret
109
+ @digest = options[:digest] || "SHA1"
110
+ @serializer = options[:serializer] || Marshal
111
+ end
112
+
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("--")
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)
152
+ begin
153
+ data = signed_message.split("--")[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
160
+ end
161
+ end
162
+
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))
188
+ "#{data}--#{generate_digest(data)}"
189
+ end
190
+
191
+ private
192
+ def encode(data)
193
+ ::Base64.strict_encode64(data)
194
+ end
195
+
196
+ def decode(data)
197
+ ::Base64.strict_decode64(data)
198
+ end
199
+
200
+ def generate_digest(data)
201
+ require "openssl" unless defined?(OpenSSL)
202
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
203
+ end
204
+ end
205
+ 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
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport #:nodoc:
4
+ module Multibyte
5
+ autoload :Chars, "active_support/multibyte/chars"
6
+ autoload :Unicode, "active_support/multibyte/unicode"
7
+
8
+ # The proxy class returned when calling mb_chars. You can use this accessor
9
+ # to configure your own proxy class so you can support other encodings. See
10
+ # the ActiveSupport::Multibyte::Chars implementation for an example how to
11
+ # do this.
12
+ #
13
+ # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
14
+ def self.proxy_class=(klass)
15
+ @proxy_class = klass
16
+ end
17
+
18
+ # Returns the current proxy class.
19
+ def self.proxy_class
20
+ @proxy_class ||= ActiveSupport::Multibyte::Chars
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,216 @@
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
+
8
+ module ActiveSupport #:nodoc:
9
+ module Multibyte #:nodoc:
10
+ # Chars enables you to work transparently with UTF-8 encoding in the Ruby
11
+ # String class without having extensive knowledge about the encoding. A
12
+ # Chars object accepts a string upon initialization and proxies String
13
+ # methods in an encoding safe manner. All the normal String methods are also
14
+ # implemented on the proxy.
15
+ #
16
+ # String methods are proxied through the Chars object, and can be accessed
17
+ # through the +mb_chars+ method. Methods which would normally return a
18
+ # String object now return a Chars object so methods can be chained.
19
+ #
20
+ # 'The Perfect String '.mb_chars.downcase.strip
21
+ # # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
22
+ #
23
+ # Chars objects are perfectly interchangeable with String objects as long as
24
+ # no explicit class checks are made. If certain methods do explicitly check
25
+ # the class, call +to_s+ before you pass chars objects to them.
26
+ #
27
+ # bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
28
+ #
29
+ # The default Chars implementation assumes that the encoding of the string
30
+ # is UTF-8, if you want to handle different encodings you can write your own
31
+ # multibyte string handler and configure it through
32
+ # ActiveSupport::Multibyte.proxy_class.
33
+ #
34
+ # class CharsForUTF32
35
+ # def size
36
+ # @wrapped_string.size / 4
37
+ # end
38
+ #
39
+ # def self.accepts?(string)
40
+ # string.length % 4 == 0
41
+ # end
42
+ # end
43
+ #
44
+ # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
45
+ class Chars
46
+ include Comparable
47
+ attr_reader :wrapped_string
48
+ alias to_s wrapped_string
49
+ alias to_str wrapped_string
50
+
51
+ delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string
52
+
53
+ # Creates a new Chars instance by wrapping _string_.
54
+ def initialize(string)
55
+ @wrapped_string = string
56
+ @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
57
+ end
58
+
59
+ # Forward all undefined methods to the wrapped string.
60
+ def method_missing(method, *args, &block)
61
+ result = @wrapped_string.__send__(method, *args, &block)
62
+ if /!$/.match?(method)
63
+ self if result
64
+ else
65
+ result.kind_of?(String) ? chars(result) : result
66
+ end
67
+ end
68
+
69
+ # Returns +true+ if _obj_ responds to the given method. Private methods
70
+ # are included in the search only if the optional second parameter
71
+ # evaluates to +true+.
72
+ def respond_to_missing?(method, include_private)
73
+ @wrapped_string.respond_to?(method, include_private)
74
+ end
75
+
76
+ # Returns +true+ when the proxy class can handle the string. Returns
77
+ # +false+ otherwise.
78
+ def self.consumes?(string)
79
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
80
+ ActiveSupport::Multibyte::Chars.consumes? is deprecated and will be
81
+ removed from Rails 6.1. Use string.is_utf8? instead.
82
+ MSG
83
+
84
+ string.encoding == Encoding::UTF_8
85
+ end
86
+
87
+ # Works just like <tt>String#split</tt>, with the exception that the items
88
+ # in the resulting list are Chars instances instead of String. This makes
89
+ # chaining methods easier.
90
+ #
91
+ # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
92
+ def split(*args)
93
+ @wrapped_string.split(*args).map { |i| self.class.new(i) }
94
+ end
95
+
96
+ # Works like <tt>String#slice!</tt>, but returns an instance of
97
+ # Chars, or +nil+ if the string was not modified. The string will not be
98
+ # modified if the range given is out of bounds
99
+ #
100
+ # string = 'Welcome'
101
+ # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
102
+ # string # => 'Welome'
103
+ # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
104
+ # string # => 'me'
105
+ def slice!(*args)
106
+ string_sliced = @wrapped_string.slice!(*args)
107
+ if string_sliced
108
+ chars(string_sliced)
109
+ end
110
+ end
111
+
112
+ # Reverses all characters in the string.
113
+ #
114
+ # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
115
+ def reverse
116
+ chars(@wrapped_string.scan(/\X/).reverse.join)
117
+ end
118
+
119
+ # Limits the byte size of the string to a number of bytes without breaking
120
+ # characters. Usable when the storage for a string is limited for some
121
+ # reason.
122
+ #
123
+ # 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
124
+ def limit(limit)
125
+ truncate_bytes(limit, omission: nil)
126
+ end
127
+
128
+ # Capitalizes the first letter of every word, when possible.
129
+ #
130
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
131
+ # "日本語".mb_chars.titleize.to_s # => "日本語"
132
+ def titleize
133
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase })
134
+ end
135
+ alias_method :titlecase, :titleize
136
+
137
+ # Returns the KC normalization of the string by default. NFKC is
138
+ # considered the best normalization form for passing strings to databases
139
+ # and validations.
140
+ #
141
+ # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
142
+ # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
143
+ # ActiveSupport::Multibyte::Unicode.default_normalization_form
144
+ def normalize(form = nil)
145
+ form ||= Unicode.default_normalization_form
146
+
147
+ # See https://www.unicode.org/reports/tr15, Table 1
148
+ if alias_form = Unicode::NORMALIZATION_FORM_ALIASES[form]
149
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
150
+ ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
151
+ removed from Rails 6.1. Use #unicode_normalize(:#{alias_form}) instead.
152
+ MSG
153
+
154
+ send(:unicode_normalize, alias_form)
155
+ else
156
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
157
+ ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
158
+ removed from Rails 6.1. Use #unicode_normalize instead.
159
+ MSG
160
+
161
+ raise ArgumentError, "#{form} is not a valid normalization variant", caller
162
+ end
163
+ end
164
+
165
+ # Performs canonical decomposition on all the characters.
166
+ #
167
+ # 'é'.length # => 2
168
+ # 'é'.mb_chars.decompose.to_s.length # => 3
169
+ def decompose
170
+ chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
171
+ end
172
+
173
+ # Performs composition on all the characters.
174
+ #
175
+ # 'é'.length # => 3
176
+ # 'é'.mb_chars.compose.to_s.length # => 2
177
+ def compose
178
+ chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
179
+ end
180
+
181
+ # Returns the number of grapheme clusters in the string.
182
+ #
183
+ # 'क्षि'.mb_chars.length # => 4
184
+ # 'क्षि'.mb_chars.grapheme_length # => 3
185
+ def grapheme_length
186
+ @wrapped_string.scan(/\X/).length
187
+ end
188
+
189
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
190
+ # resulting in a valid UTF-8 string.
191
+ #
192
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
193
+ # encoding is entirely CP1252 or ISO-8859-1.
194
+ def tidy_bytes(force = false)
195
+ chars(Unicode.tidy_bytes(@wrapped_string, force))
196
+ end
197
+
198
+ def as_json(options = nil) #:nodoc:
199
+ to_s.as_json(options)
200
+ end
201
+
202
+ %w(reverse tidy_bytes).each do |method|
203
+ define_method("#{method}!") do |*args|
204
+ @wrapped_string = send(method, *args).to_s
205
+ self
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ def chars(string)
212
+ self.class.new(string)
213
+ end
214
+ end
215
+ end
216
+ end