activesupport 1.2.4 → 8.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +505 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support/actionable_error.rb +50 -0
  6. data/lib/active_support/all.rb +5 -0
  7. data/lib/active_support/array_inquirer.rb +50 -0
  8. data/lib/active_support/backtrace_cleaner.rb +234 -0
  9. data/lib/active_support/benchmark.rb +21 -0
  10. data/lib/active_support/benchmarkable.rb +53 -0
  11. data/lib/active_support/broadcast_logger.rb +238 -0
  12. data/lib/active_support/builder.rb +8 -0
  13. data/lib/active_support/cache/coder.rb +153 -0
  14. data/lib/active_support/cache/entry.rb +134 -0
  15. data/lib/active_support/cache/file_store.rb +244 -0
  16. data/lib/active_support/cache/mem_cache_store.rb +288 -0
  17. data/lib/active_support/cache/memory_store.rb +264 -0
  18. data/lib/active_support/cache/null_store.rb +62 -0
  19. data/lib/active_support/cache/redis_cache_store.rb +498 -0
  20. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  21. data/lib/active_support/cache/strategy/local_cache.rb +246 -0
  22. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  23. data/lib/active_support/cache.rb +1170 -0
  24. data/lib/active_support/callbacks.rb +960 -0
  25. data/lib/active_support/class_attribute.rb +33 -0
  26. data/lib/active_support/code_generator.rb +79 -0
  27. data/lib/active_support/concern.rb +217 -0
  28. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  29. data/lib/active_support/concurrency/null_lock.rb +13 -0
  30. data/lib/active_support/concurrency/share_lock.rb +225 -0
  31. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  32. data/lib/active_support/configurable.rb +193 -0
  33. data/lib/active_support/configuration_file.rb +60 -0
  34. data/lib/active_support/continuous_integration.rb +145 -0
  35. data/lib/active_support/core_ext/array/access.rb +100 -0
  36. data/lib/active_support/core_ext/array/conversions.rb +209 -26
  37. data/lib/active_support/core_ext/array/extract.rb +21 -0
  38. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  39. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  40. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  41. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  42. data/lib/active_support/core_ext/array.rb +8 -4
  43. data/lib/active_support/core_ext/benchmark.rb +6 -0
  44. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  45. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  46. data/lib/active_support/core_ext/class/attribute.rb +137 -0
  47. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  48. data/lib/active_support/core_ext/class/subclasses.rb +24 -0
  49. data/lib/active_support/core_ext/class.rb +4 -0
  50. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  51. data/lib/active_support/core_ext/date/blank.rb +18 -0
  52. data/lib/active_support/core_ext/date/calculations.rb +161 -0
  53. data/lib/active_support/core_ext/date/conversions.rb +95 -28
  54. data/lib/active_support/core_ext/date/zones.rb +8 -0
  55. data/lib/active_support/core_ext/date.rb +6 -5
  56. data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
  57. data/lib/active_support/core_ext/date_and_time/compatibility.rb +23 -0
  58. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  59. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  60. data/lib/active_support/core_ext/date_time/blank.rb +18 -0
  61. data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
  62. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  63. data/lib/active_support/core_ext/date_time/conversions.rb +108 -0
  64. data/lib/active_support/core_ext/date_time.rb +7 -0
  65. data/lib/active_support/core_ext/digest/uuid.rb +76 -0
  66. data/lib/active_support/core_ext/digest.rb +3 -0
  67. data/lib/active_support/core_ext/enumerable.rb +277 -7
  68. data/lib/active_support/core_ext/erb/util.rb +201 -0
  69. data/lib/active_support/core_ext/file/atomic.rb +72 -0
  70. data/lib/active_support/core_ext/file.rb +3 -0
  71. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  72. data/lib/active_support/core_ext/hash/deep_merge.rb +43 -0
  73. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  74. data/lib/active_support/core_ext/hash/except.rb +12 -0
  75. data/lib/active_support/core_ext/hash/indifferent_access.rb +19 -55
  76. data/lib/active_support/core_ext/hash/keys.rb +134 -44
  77. data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -22
  78. data/lib/active_support/core_ext/hash/slice.rb +27 -0
  79. data/lib/active_support/core_ext/hash.rb +9 -8
  80. data/lib/active_support/core_ext/integer/inflections.rb +29 -13
  81. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  82. data/lib/active_support/core_ext/integer/time.rb +22 -0
  83. data/lib/active_support/core_ext/integer.rb +4 -6
  84. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  85. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  86. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  87. data/lib/active_support/core_ext/kernel.rb +4 -78
  88. data/lib/active_support/core_ext/load_error.rb +6 -35
  89. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  90. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  91. data/lib/active_support/core_ext/module/attr_internal.rb +48 -0
  92. data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
  93. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
  94. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  95. data/lib/active_support/core_ext/module/delegation.rb +225 -0
  96. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  97. data/lib/active_support/core_ext/module/introspection.rb +65 -0
  98. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  99. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  100. data/lib/active_support/core_ext/module.rb +13 -0
  101. data/lib/active_support/core_ext/name_error.rb +59 -0
  102. data/lib/active_support/core_ext/numeric/bytes.rb +73 -42
  103. data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
  104. data/lib/active_support/core_ext/numeric/time.rb +64 -57
  105. data/lib/active_support/core_ext/numeric.rb +4 -6
  106. data/lib/active_support/core_ext/object/acts_like.rb +45 -0
  107. data/lib/active_support/core_ext/object/blank.rb +199 -0
  108. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  109. data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
  110. data/lib/active_support/core_ext/object/duplicable.rb +69 -0
  111. data/lib/active_support/core_ext/object/inclusion.rb +37 -0
  112. data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
  113. data/lib/active_support/core_ext/object/json.rb +267 -0
  114. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  115. data/lib/active_support/core_ext/object/to_query.rb +93 -0
  116. data/lib/active_support/core_ext/object/try.rb +158 -0
  117. data/lib/active_support/core_ext/object/with.rb +46 -0
  118. data/lib/active_support/core_ext/object/with_options.rb +101 -0
  119. data/lib/active_support/core_ext/object.rb +17 -0
  120. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  121. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  122. data/lib/active_support/core_ext/pathname.rb +4 -0
  123. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  124. data/lib/active_support/core_ext/range/conversions.rb +58 -17
  125. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  126. data/lib/active_support/core_ext/range/sole.rb +17 -0
  127. data/lib/active_support/core_ext/range.rb +5 -4
  128. data/lib/active_support/core_ext/regexp.rb +14 -0
  129. data/lib/active_support/core_ext/securerandom.rb +57 -0
  130. data/lib/active_support/core_ext/string/access.rb +93 -56
  131. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  132. data/lib/active_support/core_ext/string/conversions.rb +57 -16
  133. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  134. data/lib/active_support/core_ext/string/filters.rb +151 -0
  135. data/lib/active_support/core_ext/string/indent.rb +45 -0
  136. data/lib/active_support/core_ext/string/inflections.rb +297 -54
  137. data/lib/active_support/core_ext/string/inquiry.rb +16 -0
  138. data/lib/active_support/core_ext/string/multibyte.rb +67 -0
  139. data/lib/active_support/core_ext/string/output_safety.rb +235 -0
  140. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -18
  141. data/lib/active_support/core_ext/string/strip.rb +27 -0
  142. data/lib/active_support/core_ext/string/zones.rb +16 -0
  143. data/lib/active_support/core_ext/string.rb +14 -10
  144. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  145. data/lib/active_support/core_ext/symbol.rb +3 -0
  146. data/lib/active_support/core_ext/thread/backtrace/location.rb +7 -0
  147. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  148. data/lib/active_support/core_ext/time/calculations.rb +358 -153
  149. data/lib/active_support/core_ext/time/compatibility.rb +15 -0
  150. data/lib/active_support/core_ext/time/conversions.rb +69 -30
  151. data/lib/active_support/core_ext/time/zones.rb +97 -0
  152. data/lib/active_support/core_ext/time.rb +6 -6
  153. data/lib/active_support/core_ext.rb +5 -1
  154. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  155. data/lib/active_support/current_attributes.rb +243 -0
  156. data/lib/active_support/deep_mergeable.rb +53 -0
  157. data/lib/active_support/delegation.rb +183 -0
  158. data/lib/active_support/dependencies/autoload.rb +72 -0
  159. data/lib/active_support/dependencies/interlock.rb +55 -0
  160. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  161. data/lib/active_support/dependencies.rb +84 -222
  162. data/lib/active_support/deprecation/behaviors.rb +148 -0
  163. data/lib/active_support/deprecation/constant_accessor.rb +74 -0
  164. data/lib/active_support/deprecation/deprecators.rb +104 -0
  165. data/lib/active_support/deprecation/disallowed.rb +54 -0
  166. data/lib/active_support/deprecation/method_wrappers.rb +68 -0
  167. data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
  168. data/lib/active_support/deprecation/reporting.rb +162 -0
  169. data/lib/active_support/deprecation.rb +81 -0
  170. data/lib/active_support/deprecator.rb +7 -0
  171. data/lib/active_support/descendants_tracker.rb +112 -0
  172. data/lib/active_support/digest.rb +22 -0
  173. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  174. data/lib/active_support/duration/iso8601_serializer.rb +64 -0
  175. data/lib/active_support/duration.rb +524 -0
  176. data/lib/active_support/editor.rb +70 -0
  177. data/lib/active_support/encrypted_configuration.rb +126 -0
  178. data/lib/active_support/encrypted_file.rb +133 -0
  179. data/lib/active_support/environment_inquirer.rb +40 -0
  180. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  181. data/lib/active_support/error_reporter.rb +318 -0
  182. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  183. data/lib/active_support/event_reporter.rb +592 -0
  184. data/lib/active_support/evented_file_update_checker.rb +185 -0
  185. data/lib/active_support/execution_context/test_helper.rb +13 -0
  186. data/lib/active_support/execution_context.rb +110 -0
  187. data/lib/active_support/execution_wrapper.rb +150 -0
  188. data/lib/active_support/executor/test_helper.rb +7 -0
  189. data/lib/active_support/executor.rb +8 -0
  190. data/lib/active_support/file_update_checker.rb +166 -0
  191. data/lib/active_support/fork_tracker.rb +43 -0
  192. data/lib/active_support/gem_version.rb +17 -0
  193. data/lib/active_support/gzip.rb +41 -0
  194. data/lib/active_support/hash_with_indifferent_access.rb +464 -0
  195. data/lib/active_support/html_safe_translation.rb +56 -0
  196. data/lib/active_support/i18n.rb +17 -0
  197. data/lib/active_support/i18n_railtie.rb +140 -0
  198. data/lib/active_support/inflections.rb +68 -49
  199. data/lib/active_support/inflector/inflections.rb +290 -0
  200. data/lib/active_support/inflector/methods.rb +387 -0
  201. data/lib/active_support/inflector/transliterate.rb +147 -0
  202. data/lib/active_support/inflector.rb +7 -164
  203. data/lib/active_support/isolated_execution_state.rb +76 -0
  204. data/lib/active_support/json/decoding.rb +78 -0
  205. data/lib/active_support/json/encoding.rb +256 -0
  206. data/lib/active_support/json.rb +4 -0
  207. data/lib/active_support/key_generator.rb +66 -0
  208. data/lib/active_support/lazy_load_hooks.rb +107 -0
  209. data/lib/active_support/locale/en.rb +33 -0
  210. data/lib/active_support/locale/en.yml +141 -0
  211. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  212. data/lib/active_support/log_subscriber.rb +188 -0
  213. data/lib/active_support/logger.rb +55 -0
  214. data/lib/active_support/logger_silence.rb +21 -0
  215. data/lib/active_support/logger_thread_safe_level.rb +50 -0
  216. data/lib/active_support/message_encryptor.rb +374 -0
  217. data/lib/active_support/message_encryptors.rb +193 -0
  218. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  219. data/lib/active_support/message_pack/extensions.rb +310 -0
  220. data/lib/active_support/message_pack/serializer.rb +63 -0
  221. data/lib/active_support/message_pack.rb +50 -0
  222. data/lib/active_support/message_verifier.rb +377 -0
  223. data/lib/active_support/message_verifiers.rb +189 -0
  224. data/lib/active_support/messages/codec.rb +65 -0
  225. data/lib/active_support/messages/metadata.rb +146 -0
  226. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  227. data/lib/active_support/messages/rotation_coordinator.rb +102 -0
  228. data/lib/active_support/messages/rotator.rb +69 -0
  229. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  230. data/lib/active_support/multibyte/chars.rb +188 -0
  231. data/lib/active_support/multibyte/unicode.rb +42 -0
  232. data/lib/active_support/multibyte.rb +27 -0
  233. data/lib/active_support/notifications/fanout.rb +467 -0
  234. data/lib/active_support/notifications/instrumenter.rb +240 -0
  235. data/lib/active_support/notifications.rb +281 -0
  236. data/lib/active_support/number_helper/number_converter.rb +190 -0
  237. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  238. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  239. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  240. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  241. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  242. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  243. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  244. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  245. data/lib/active_support/number_helper.rb +479 -0
  246. data/lib/active_support/option_merger.rb +38 -0
  247. data/lib/active_support/ordered_hash.rb +50 -0
  248. data/lib/active_support/ordered_options.rb +141 -25
  249. data/lib/active_support/parameter_filter.rb +157 -0
  250. data/lib/active_support/rails.rb +26 -0
  251. data/lib/active_support/railtie.rb +180 -0
  252. data/lib/active_support/reloader.rb +138 -0
  253. data/lib/active_support/rescuable.rb +176 -0
  254. data/lib/active_support/secure_compare_rotator.rb +58 -0
  255. data/lib/active_support/security_utils.rb +38 -0
  256. data/lib/active_support/string_inquirer.rb +35 -0
  257. data/lib/active_support/structured_event_subscriber.rb +99 -0
  258. data/lib/active_support/subscriber.rb +141 -0
  259. data/lib/active_support/syntax_error_proxy.rb +67 -0
  260. data/lib/active_support/tagged_logging.rb +157 -0
  261. data/lib/active_support/test_case.rb +365 -0
  262. data/lib/active_support/testing/assertions.rb +369 -0
  263. data/lib/active_support/testing/autorun.rb +10 -0
  264. data/lib/active_support/testing/constant_lookup.rb +51 -0
  265. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  266. data/lib/active_support/testing/declarative.rb +28 -0
  267. data/lib/active_support/testing/deprecation.rb +82 -0
  268. data/lib/active_support/testing/error_reporter_assertions.rb +124 -0
  269. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  270. data/lib/active_support/testing/file_fixtures.rb +38 -0
  271. data/lib/active_support/testing/isolation.rb +121 -0
  272. data/lib/active_support/testing/method_call_assertions.rb +69 -0
  273. data/lib/active_support/testing/notification_assertions.rb +92 -0
  274. data/lib/active_support/testing/parallelization/server.rb +98 -0
  275. data/lib/active_support/testing/parallelization/worker.rb +107 -0
  276. data/lib/active_support/testing/parallelization.rb +79 -0
  277. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  278. data/lib/active_support/testing/setup_and_teardown.rb +57 -0
  279. data/lib/active_support/testing/stream.rb +41 -0
  280. data/lib/active_support/testing/tagged_logging.rb +27 -0
  281. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  282. data/lib/active_support/testing/time_helpers.rb +273 -0
  283. data/lib/active_support/time.rb +20 -0
  284. data/lib/active_support/time_with_zone.rb +613 -0
  285. data/lib/active_support/values/time_zone.rb +599 -158
  286. data/lib/active_support/version.rb +7 -6
  287. data/lib/active_support/xml_mini/jdom.rb +175 -0
  288. data/lib/active_support/xml_mini/libxml.rb +80 -0
  289. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  290. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  291. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  292. data/lib/active_support/xml_mini/rexml.rb +137 -0
  293. data/lib/active_support/xml_mini.rb +212 -0
  294. data/lib/active_support.rb +122 -10
  295. metadata +524 -93
  296. data/CHANGELOG +0 -283
  297. data/lib/active_support/binding_of_caller.rb +0 -84
  298. data/lib/active_support/breakpoint.rb +0 -523
  299. data/lib/active_support/class_attribute_accessors.rb +0 -57
  300. data/lib/active_support/class_inheritable_attributes.rb +0 -117
  301. data/lib/active_support/clean_logger.rb +0 -36
  302. data/lib/active_support/core_ext/blank.rb +0 -38
  303. data/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +0 -14
  304. data/lib/active_support/core_ext/cgi.rb +0 -5
  305. data/lib/active_support/core_ext/exception.rb +0 -29
  306. data/lib/active_support/core_ext/integer/even_odd.rb +0 -24
  307. data/lib/active_support/core_ext/object_and_class.rb +0 -44
  308. data/lib/active_support/module_attribute_accessors.rb +0 -57
  309. data/lib/active_support/whiny_nil.rb +0 -38
@@ -0,0 +1,377 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+ require "active_support/core_ext/object/blank"
6
+ require "active_support/security_utils"
7
+ require "active_support/messages/codec"
8
+ require "active_support/messages/rotator"
9
+
10
+ module ActiveSupport
11
+ # = Active Support Message Verifier
12
+ #
13
+ # +MessageVerifier+ makes it easy to generate and verify messages which are
14
+ # signed to prevent tampering.
15
+ #
16
+ # In a \Rails application, you can use +Rails.application.message_verifier+
17
+ # to manage unique instances of verifiers for each use case.
18
+ # {Learn more}[link:classes/Rails/Application.html#method-i-message_verifier].
19
+ #
20
+ # This is useful for cases like remember-me tokens and auto-unsubscribe links
21
+ # where the session store isn't suitable or available.
22
+ #
23
+ # First, generate a signed message:
24
+ # cookies[:remember_me] = Rails.application.message_verifier(:remember_me).generate([@user.id, 2.weeks.from_now])
25
+ #
26
+ # Later verify that message:
27
+ #
28
+ # id, time = Rails.application.message_verifier(:remember_me).verify(cookies[:remember_me])
29
+ # if time.future?
30
+ # self.current_user = User.find(id)
31
+ # end
32
+ #
33
+ # === Signing is not encryption
34
+ #
35
+ # The signed messages are not encrypted. The payload is merely encoded (Base64 by default) and can be decoded by
36
+ # anyone. The signature is just assuring that the message wasn't tampered with. For example:
37
+ #
38
+ # message = Rails.application.message_verifier('my_purpose').generate('never put secrets here')
39
+ # # => "BAhJIhtuZXZlciBwdXQgc2VjcmV0cyBoZXJlBjoGRVQ=--a0c1c0827919da5e949e989c971249355735e140"
40
+ # Base64.decode64(message.split("--").first) # no key needed
41
+ # # => 'never put secrets here'
42
+ #
43
+ # If you also need to encrypt the contents, you must use ActiveSupport::MessageEncryptor instead.
44
+ #
45
+ # === Confine messages to a specific purpose
46
+ #
47
+ # It's not recommended to use the same verifier for different purposes in your application.
48
+ # Doing so could allow a malicious actor to re-use a signed message to perform an unauthorized
49
+ # action.
50
+ # You can reduce this risk by confining signed messages to a specific +:purpose+.
51
+ #
52
+ # token = @verifier.generate("signed message", purpose: :login)
53
+ #
54
+ # Then that same purpose must be passed when verifying to get the data back out:
55
+ #
56
+ # @verifier.verified(token, purpose: :login) # => "signed message"
57
+ # @verifier.verified(token, purpose: :shipping) # => nil
58
+ # @verifier.verified(token) # => nil
59
+ #
60
+ # @verifier.verify(token, purpose: :login) # => "signed message"
61
+ # @verifier.verify(token, purpose: :shipping) # => raises ActiveSupport::MessageVerifier::InvalidSignature
62
+ # @verifier.verify(token) # => raises ActiveSupport::MessageVerifier::InvalidSignature
63
+ #
64
+ # Likewise, if a message has no purpose it won't be returned when verifying with
65
+ # a specific purpose.
66
+ #
67
+ # token = @verifier.generate("signed message")
68
+ # @verifier.verified(token, purpose: :redirect) # => nil
69
+ # @verifier.verified(token) # => "signed message"
70
+ #
71
+ # @verifier.verify(token, purpose: :redirect) # => raises ActiveSupport::MessageVerifier::InvalidSignature
72
+ # @verifier.verify(token) # => "signed message"
73
+ #
74
+ # === Expiring messages
75
+ #
76
+ # By default messages last forever and verifying one year from now will still
77
+ # return the original value. But messages can be set to expire at a given
78
+ # time with +:expires_in+ or +:expires_at+.
79
+ #
80
+ # @verifier.generate("signed message", expires_in: 1.month)
81
+ # @verifier.generate("signed message", expires_at: Time.now.end_of_year)
82
+ #
83
+ # Messages can then be verified and returned until expiry.
84
+ # Thereafter, the +verified+ method returns +nil+ while +verify+ raises
85
+ # +ActiveSupport::MessageVerifier::InvalidSignature+.
86
+ #
87
+ # === Rotating keys
88
+ #
89
+ # MessageVerifier also supports rotating out old configurations by falling
90
+ # back to a stack of verifiers. Call +rotate+ to build and add a verifier so
91
+ # either +verified+ or +verify+ will also try verifying with the fallback.
92
+ #
93
+ # By default any rotated verifiers use the values of the primary
94
+ # verifier unless specified otherwise.
95
+ #
96
+ # You'd give your verifier the new defaults:
97
+ #
98
+ # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
99
+ #
100
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
101
+ # generated with the old values will then work until the rotation is removed.
102
+ #
103
+ # verifier.rotate(old_secret) # Fallback to an old secret instead of @secret.
104
+ # verifier.rotate(digest: "SHA256") # Fallback to an old digest instead of SHA512.
105
+ # verifier.rotate(serializer: Marshal) # Fallback to an old serializer instead of JSON.
106
+ #
107
+ # Though the above would most likely be combined into one rotation:
108
+ #
109
+ # verifier.rotate(old_secret, digest: "SHA256", serializer: Marshal)
110
+ class MessageVerifier < Messages::Codec
111
+ prepend Messages::Rotator
112
+
113
+ class InvalidSignature < StandardError; end
114
+
115
+ SEPARATOR = "--" # :nodoc:
116
+ SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
117
+
118
+ # Initialize a new MessageVerifier with a secret for the signature.
119
+ #
120
+ # ==== Options
121
+ #
122
+ # [+:digest+]
123
+ # Digest used for signing. The default is <tt>"SHA1"</tt>. See
124
+ # +OpenSSL::Digest+ for alternatives.
125
+ #
126
+ # [+:serializer+]
127
+ # The serializer used to serialize message data. You can specify any
128
+ # object that responds to +dump+ and +load+, or you can choose from
129
+ # several preconfigured serializers: +:marshal+, +:json_allow_marshal+,
130
+ # +:json+, +:message_pack_allow_marshal+, +:message_pack+.
131
+ #
132
+ # The preconfigured serializers include a fallback mechanism to support
133
+ # multiple deserialization formats. For example, the +:marshal+ serializer
134
+ # will serialize using +Marshal+, but can deserialize using +Marshal+,
135
+ # ActiveSupport::JSON, or ActiveSupport::MessagePack. This makes it easy
136
+ # to migrate between serializers.
137
+ #
138
+ # The +:marshal+, +:json_allow_marshal+, and +:message_pack_allow_marshal+
139
+ # serializers support deserializing using +Marshal+, but the others do
140
+ # not. Beware that +Marshal+ is a potential vector for deserialization
141
+ # attacks in cases where a message signing secret has been leaked. <em>If
142
+ # possible, choose a serializer that does not support +Marshal+.</em>
143
+ #
144
+ # The +:message_pack+ and +:message_pack_allow_marshal+ serializers use
145
+ # ActiveSupport::MessagePack, which can roundtrip some Ruby types that are
146
+ # not supported by JSON, and may provide improved performance. However,
147
+ # these require the +msgpack+ gem.
148
+ #
149
+ # When using \Rails, the default depends on +config.active_support.message_serializer+.
150
+ # Otherwise, the default is +:marshal+.
151
+ #
152
+ # [+:url_safe+]
153
+ # By default, MessageVerifier generates RFC 4648 compliant strings which are
154
+ # not URL-safe. In other words, they can contain "+" and "/". If you want to
155
+ # generate URL-safe strings (in compliance with "Base 64 Encoding with URL
156
+ # and Filename Safe Alphabet" in RFC 4648), you can pass +true+.
157
+ # Note that MessageVerifier will always accept both URL-safe and URL-unsafe
158
+ # encoded messages, to allow a smooth transition between the two settings.
159
+ #
160
+ # [+:force_legacy_metadata_serializer+]
161
+ # Whether to use the legacy metadata serializer, which serializes the
162
+ # message first, then wraps it in an envelope which is also serialized. This
163
+ # was the default in \Rails 7.0 and below.
164
+ #
165
+ # If you don't pass a truthy value, the default is set using
166
+ # +config.active_support.use_message_serializer_for_metadata+.
167
+ def initialize(secret, **options)
168
+ raise ArgumentError, "Secret should not be nil." unless secret
169
+ super(**options)
170
+ @secret = secret
171
+ @digest = options[:digest]&.to_s || "SHA1"
172
+ end
173
+
174
+ # Checks if a signed message could have been generated by signing an object
175
+ # with the +MessageVerifier+'s secret.
176
+ #
177
+ # verifier = ActiveSupport::MessageVerifier.new("secret")
178
+ # signed_message = verifier.generate("signed message")
179
+ # verifier.valid_message?(signed_message) # => true
180
+ #
181
+ # tampered_message = signed_message.chop # editing the message invalidates the signature
182
+ # verifier.valid_message?(tampered_message) # => false
183
+ def valid_message?(message)
184
+ !!catch_and_ignore(:invalid_message_format) { extract_encoded(message) }
185
+ end
186
+
187
+ # Decodes the signed message using the +MessageVerifier+'s secret.
188
+ #
189
+ # verifier = ActiveSupport::MessageVerifier.new("secret")
190
+ #
191
+ # signed_message = verifier.generate("signed message")
192
+ # verifier.verified(signed_message) # => "signed message"
193
+ #
194
+ # Returns +nil+ if the message was not signed with the same secret.
195
+ #
196
+ # other_verifier = ActiveSupport::MessageVerifier.new("different_secret")
197
+ # other_verifier.verified(signed_message) # => nil
198
+ #
199
+ # Returns +nil+ if the message is not Base64-encoded.
200
+ #
201
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
202
+ # verifier.verified(invalid_message) # => nil
203
+ #
204
+ # Raises any error raised while decoding the signed message.
205
+ #
206
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
207
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
208
+ #
209
+ # ==== Options
210
+ #
211
+ # [+:purpose+]
212
+ # The purpose that the message was generated with. If the purpose does not
213
+ # match, +verified+ will return +nil+.
214
+ #
215
+ # message = verifier.generate("hello", purpose: "greeting")
216
+ # verifier.verified(message, purpose: "greeting") # => "hello"
217
+ # verifier.verified(message, purpose: "chatting") # => nil
218
+ # verifier.verified(message) # => nil
219
+ #
220
+ # message = verifier.generate("bye")
221
+ # verifier.verified(message) # => "bye"
222
+ # verifier.verified(message, purpose: "greeting") # => nil
223
+ #
224
+ def verified(message, **options)
225
+ catch_and_ignore :invalid_message_format do
226
+ catch_and_raise :invalid_message_serialization do
227
+ catch_and_ignore :invalid_message_content do
228
+ read_message(message, **options)
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ # Decodes the signed message using the +MessageVerifier+'s secret.
235
+ #
236
+ # verifier = ActiveSupport::MessageVerifier.new("secret")
237
+ # signed_message = verifier.generate("signed message")
238
+ #
239
+ # verifier.verify(signed_message) # => "signed message"
240
+ #
241
+ # Raises +InvalidSignature+ if the message was not signed with the same
242
+ # secret or was not Base64-encoded.
243
+ #
244
+ # other_verifier = ActiveSupport::MessageVerifier.new("different_secret")
245
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
246
+ #
247
+ # ==== Options
248
+ #
249
+ # [+:purpose+]
250
+ # The purpose that the message was generated with. If the purpose does not
251
+ # match, +verify+ will raise ActiveSupport::MessageVerifier::InvalidSignature.
252
+ #
253
+ # message = verifier.generate("hello", purpose: "greeting")
254
+ # verifier.verify(message, purpose: "greeting") # => "hello"
255
+ # verifier.verify(message, purpose: "chatting") # => raises InvalidSignature
256
+ # verifier.verify(message) # => raises InvalidSignature
257
+ #
258
+ # message = verifier.generate("bye")
259
+ # verifier.verify(message) # => "bye"
260
+ # verifier.verify(message, purpose: "greeting") # => raises InvalidSignature
261
+ #
262
+ def verify(message, **options)
263
+ catch_and_raise :invalid_message_format, as: InvalidSignature do
264
+ catch_and_raise :invalid_message_serialization do
265
+ catch_and_raise :invalid_message_content, as: InvalidSignature do
266
+ read_message(message, **options)
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ # Generates a signed message for the provided value.
273
+ #
274
+ # The message is signed with the +MessageVerifier+'s secret.
275
+ # Returns Base64-encoded message joined with the generated signature.
276
+ #
277
+ # verifier = ActiveSupport::MessageVerifier.new("secret")
278
+ # verifier.generate("signed message") # => "BAhJIhNzaWduZWQgbWVzc2FnZQY6BkVU--f67d5f27c3ee0b8483cebf2103757455e947493b"
279
+ #
280
+ # ==== Options
281
+ #
282
+ # [+:expires_at+]
283
+ # The datetime at which the message expires. After this datetime,
284
+ # verification of the message will fail.
285
+ #
286
+ # message = verifier.generate("hello", expires_at: Time.now.tomorrow)
287
+ # verifier.verified(message) # => "hello"
288
+ # # 24 hours later...
289
+ # verifier.verified(message) # => nil
290
+ # verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature
291
+ #
292
+ # [+:expires_in+]
293
+ # The duration for which the message is valid. After this duration has
294
+ # elapsed, verification of the message will fail.
295
+ #
296
+ # message = verifier.generate("hello", expires_in: 24.hours)
297
+ # verifier.verified(message) # => "hello"
298
+ # # 24 hours later...
299
+ # verifier.verified(message) # => nil
300
+ # verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature
301
+ #
302
+ # [+:purpose+]
303
+ # The purpose of the message. If specified, the same purpose must be
304
+ # specified when verifying the message; otherwise, verification will fail.
305
+ # (See #verified and #verify.)
306
+ def generate(value, **options)
307
+ create_message(value, **options)
308
+ end
309
+
310
+ def create_message(value, **options) # :nodoc:
311
+ sign_encoded(encode(serialize_with_metadata(value, **options)))
312
+ end
313
+
314
+ def read_message(message, **options) # :nodoc:
315
+ deserialize_with_metadata(decode(extract_encoded(message)), **options)
316
+ end
317
+
318
+ def inspect # :nodoc:
319
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
320
+ end
321
+
322
+ private
323
+ def decode(encoded, url_safe: @url_safe)
324
+ catch :invalid_message_format do
325
+ return super
326
+ end
327
+ super(encoded, url_safe: !url_safe)
328
+ end
329
+
330
+ def sign_encoded(encoded)
331
+ digest = generate_digest(encoded)
332
+ encoded << SEPARATOR << digest
333
+ end
334
+
335
+ def extract_encoded(signed)
336
+ if signed.nil? || !signed.valid_encoding?
337
+ throw :invalid_message_format, "invalid message string"
338
+ end
339
+
340
+ if separator_index = separator_index_for(signed)
341
+ encoded = signed[0, separator_index]
342
+ digest = signed[separator_index + SEPARATOR_LENGTH, digest_length_in_hex]
343
+ end
344
+
345
+ unless digest_matches_data?(digest, encoded)
346
+ throw :invalid_message_format, "mismatched digest"
347
+ end
348
+
349
+ encoded
350
+ end
351
+
352
+ def generate_digest(data)
353
+ OpenSSL::HMAC.hexdigest(@digest, @secret, data)
354
+ end
355
+
356
+ def digest_length_in_hex
357
+ # In hexadecimal (AKA base16) it takes 4 bits to represent a character,
358
+ # hence we multiply the digest's length (in bytes) by 8 to get it in
359
+ # bits and divide by 4 to get its number of characters it hex. Well, 8
360
+ # divided by 4 is 2.
361
+ @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2
362
+ end
363
+
364
+ def separator_at?(signed_message, index)
365
+ signed_message[index, SEPARATOR_LENGTH] == SEPARATOR
366
+ end
367
+
368
+ def separator_index_for(signed_message)
369
+ index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH
370
+ index unless index.negative? || !separator_at?(signed_message, index)
371
+ end
372
+
373
+ def digest_matches_data?(digest, data)
374
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
375
+ end
376
+ end
377
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/messages/rotation_coordinator"
4
+
5
+ module ActiveSupport
6
+ class MessageVerifiers < Messages::RotationCoordinator
7
+ ##
8
+ # :attr_accessor: transitional
9
+ #
10
+ # If true, the first two rotation option sets are swapped when building
11
+ # message verifiers. For example, with the following configuration, message
12
+ # verifiers will generate messages using <tt>serializer: Marshal, url_safe: true</tt>,
13
+ # and will able to verify messages that were generated using any of the
14
+ # three option sets:
15
+ #
16
+ # verifiers = ActiveSupport::MessageVerifiers.new { ... }
17
+ # verifiers.rotate(serializer: JSON, url_safe: true)
18
+ # verifiers.rotate(serializer: Marshal, url_safe: true)
19
+ # verifiers.rotate(serializer: Marshal, url_safe: false)
20
+ # verifiers.transitional = true
21
+ #
22
+ # This can be useful when performing a rolling deploy of an application,
23
+ # wherein servers that have not yet been updated must still be able to
24
+ # verify messages from updated servers. In such a scenario, first perform a
25
+ # rolling deploy with the new rotation (e.g. <tt>serializer: JSON, url_safe: true</tt>)
26
+ # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
+ # servers have been updated, perform a second rolling deploy with
28
+ # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
32
+
33
+ ##
34
+ # :singleton-method: new
35
+ # :call-seq: new(&secret_generator)
36
+ #
37
+ # Initializes a new instance. +secret_generator+ must accept a salt, and
38
+ # return a suitable secret (string). +secret_generator+ may also accept
39
+ # arbitrary kwargs. If #rotate is called with any options matching those
40
+ # kwargs, those options will be passed to +secret_generator+ instead of to
41
+ # the message verifier.
42
+ #
43
+ # verifiers = ActiveSupport::MessageVerifiers.new do |salt, base:|
44
+ # MySecretGenerator.new(base).generate(salt)
45
+ # end
46
+ #
47
+ # verifiers.rotate(base: "...")
48
+ #
49
+ #--
50
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
51
+
52
+ ##
53
+ # :method: []
54
+ # :call-seq: [](salt)
55
+ #
56
+ # Returns a MessageVerifier configured with a secret derived from the
57
+ # given +salt+, and options from #rotate. MessageVerifier instances will
58
+ # be memoized, so the same +salt+ will return the same instance.
59
+ #
60
+ #--
61
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
62
+
63
+ ##
64
+ # :method: []=
65
+ # :call-seq: []=(salt, verifier)
66
+ #
67
+ # Overrides a MessageVerifier instance associated with a given +salt+.
68
+ #
69
+ #--
70
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
71
+
72
+ ##
73
+ # :method: rotate
74
+ # :call-seq:
75
+ # rotate(**options)
76
+ # rotate(&block)
77
+ #
78
+ # Adds +options+ to the list of option sets. Messages will be signed using
79
+ # the first set in the list. When verifying, however, each set will be
80
+ # tried, in order, until one succeeds.
81
+ #
82
+ # Notably, the +:secret_generator+ option can specify a different secret
83
+ # generator than the one initially specified. The secret generator must
84
+ # respond to +call+, accept a salt, and return a suitable secret (string).
85
+ # The secret generator may also accept arbitrary kwargs.
86
+ #
87
+ # If any options match the kwargs of the operative secret generator, those
88
+ # options will be passed to the secret generator instead of to the message
89
+ # verifier.
90
+ #
91
+ # For fine-grained per-salt rotations, a block form is supported. The block
92
+ # will receive the salt, and should return an appropriate options Hash. The
93
+ # block may also return +nil+ to indicate that the rotation does not apply
94
+ # to the given salt. For example:
95
+ #
96
+ # verifiers = ActiveSupport::MessageVerifiers.new { ... }
97
+ #
98
+ # verifiers.rotate do |salt|
99
+ # case salt
100
+ # when :foo
101
+ # { serializer: JSON, url_safe: true }
102
+ # when :bar
103
+ # { serializer: Marshal, url_safe: true }
104
+ # end
105
+ # end
106
+ #
107
+ # verifiers.rotate(serializer: Marshal, url_safe: false)
108
+ #
109
+ # # Uses `serializer: JSON, url_safe: true`.
110
+ # # Falls back to `serializer: Marshal, url_safe: false`.
111
+ # verifiers[:foo]
112
+ #
113
+ # # Uses `serializer: Marshal, url_safe: true`.
114
+ # # Falls back to `serializer: Marshal, url_safe: false`.
115
+ # verifiers[:bar]
116
+ #
117
+ # # Uses `serializer: Marshal, url_safe: false`.
118
+ # verifiers[:baz]
119
+ #
120
+ #--
121
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
122
+
123
+ ##
124
+ # :method: prepend
125
+ # :call-seq:
126
+ # prepend(**options)
127
+ # prepend(&block)
128
+ #
129
+ # Just like #rotate, but prepends the given options or block to the list of
130
+ # option sets.
131
+ #
132
+ # This can be useful when you have an already-configured +MessageVerifiers+
133
+ # instance, but you want to override the way messages are signed.
134
+ #
135
+ # module ThirdParty
136
+ # VERIFIERS = ActiveSupport::MessageVerifiers.new { ... }.
137
+ # rotate(serializer: Marshal, url_safe: true).
138
+ # rotate(serializer: Marshal, url_safe: false)
139
+ # end
140
+ #
141
+ # ThirdParty.VERIFIERS.prepend(serializer: JSON, url_safe: true)
142
+ #
143
+ # # Uses `serializer: JSON, url_safe: true`.
144
+ # # Falls back to `serializer: Marshal, url_safe: true` or
145
+ # # `serializer: Marshal, url_safe: false`.
146
+ # ThirdParty.VERIFIERS[:foo]
147
+ #
148
+ #--
149
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
150
+
151
+ ##
152
+ # :method: rotate_defaults
153
+ # :call-seq: rotate_defaults
154
+ #
155
+ # Invokes #rotate with the default options.
156
+ #
157
+ #--
158
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
159
+
160
+ ##
161
+ # :method: clear_rotations
162
+ # :call-seq: clear_rotations
163
+ #
164
+ # Clears the list of option sets.
165
+ #
166
+ #--
167
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
168
+
169
+ ##
170
+ # :method: on_rotation
171
+ # :call-seq: on_rotation(&callback)
172
+ #
173
+ # Sets a callback to invoke when a message is verified using an option set
174
+ # other than the first.
175
+ #
176
+ # For example, this callback could log each time it is called, and thus
177
+ # indicate whether old option sets are still in use or can be removed from
178
+ # rotation.
179
+ #
180
+ #--
181
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
182
+
183
+ ##
184
+ private
185
+ def build(salt, secret_generator:, secret_generator_options:, **options)
186
+ MessageVerifier.new(secret_generator.call(salt, **secret_generator_options), **options)
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/class/attribute"
4
+ require_relative "metadata"
5
+ require_relative "serializer_with_fallback"
6
+
7
+ module ActiveSupport
8
+ module Messages # :nodoc:
9
+ class Codec # :nodoc:
10
+ include Metadata
11
+
12
+ class_attribute :default_serializer, default: :marshal,
13
+ instance_accessor: false, instance_predicate: false
14
+
15
+ def initialize(**options)
16
+ @serializer = options[:serializer] || self.class.default_serializer
17
+ @serializer = SerializerWithFallback[@serializer] if @serializer.is_a?(Symbol)
18
+ @url_safe = options[:url_safe]
19
+ @force_legacy_metadata_serializer = options[:force_legacy_metadata_serializer]
20
+ end
21
+
22
+ private
23
+ attr_reader :serializer
24
+
25
+ def encode(data, url_safe: @url_safe)
26
+ url_safe ? ::Base64.urlsafe_encode64(data, padding: false) : ::Base64.strict_encode64(data)
27
+ end
28
+
29
+ def decode(encoded, url_safe: @url_safe)
30
+ url_safe ? ::Base64.urlsafe_decode64(encoded) : ::Base64.strict_decode64(encoded)
31
+ rescue StandardError => error
32
+ throw :invalid_message_format, error
33
+ end
34
+
35
+ def serialize(data)
36
+ serializer.dump(data)
37
+ end
38
+
39
+ def deserialize(serialized)
40
+ serializer.load(serialized)
41
+ rescue StandardError => error
42
+ throw :invalid_message_serialization, error
43
+ end
44
+
45
+ def catch_and_ignore(throwable, &block)
46
+ catch throwable do
47
+ return block.call
48
+ end
49
+ nil
50
+ end
51
+
52
+ def catch_and_raise(throwable, as: nil, &block)
53
+ error = catch throwable do
54
+ return block.call
55
+ end
56
+ error = as.new(error.to_s) if as
57
+ raise error
58
+ end
59
+
60
+ def use_message_serializer_for_metadata?
61
+ !@force_legacy_metadata_serializer && super
62
+ end
63
+ end
64
+ end
65
+ end