activesupport 6.0.4.4 → 7.0.4.1

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.

Potentially problematic release.


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

Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +257 -532
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +2 -2
  6. data/lib/active_support/backtrace_cleaner.rb +5 -5
  7. data/lib/active_support/benchmarkable.rb +3 -3
  8. data/lib/active_support/cache/file_store.rb +16 -10
  9. data/lib/active_support/cache/mem_cache_store.rb +163 -42
  10. data/lib/active_support/cache/memory_store.rb +57 -29
  11. data/lib/active_support/cache/null_store.rb +10 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +79 -98
  13. data/lib/active_support/cache/strategy/local_cache.rb +49 -57
  14. data/lib/active_support/cache.rb +378 -179
  15. data/lib/active_support/callbacks.rb +230 -122
  16. data/lib/active_support/code_generator.rb +65 -0
  17. data/lib/active_support/concern.rb +49 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  19. data/lib/active_support/concurrency/share_lock.rb +2 -2
  20. data/lib/active_support/configurable.rb +9 -6
  21. data/lib/active_support/configuration_file.rb +51 -0
  22. data/lib/active_support/core_ext/array/access.rb +1 -5
  23. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  24. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  25. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  26. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +9 -9
  34. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +17 -4
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +164 -23
  45. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -3
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  53. data/lib/active_support/core_ext/load_error.rb +1 -1
  54. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  55. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  56. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  57. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  58. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  59. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  60. data/lib/active_support/core_ext/name_error.rb +23 -2
  61. data/lib/active_support/core_ext/numeric/conversions.rb +80 -73
  62. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  63. data/lib/active_support/core_ext/numeric.rb +1 -0
  64. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  65. data/lib/active_support/core_ext/object/blank.rb +2 -2
  66. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  67. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  68. data/lib/active_support/core_ext/object/json.rb +42 -26
  69. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  70. data/lib/active_support/core_ext/object/try.rb +20 -20
  71. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  72. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  73. data/lib/active_support/core_ext/pathname.rb +3 -0
  74. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  75. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  76. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  77. data/lib/active_support/core_ext/range/each.rb +1 -1
  78. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  79. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  80. data/lib/active_support/core_ext/range.rb +1 -1
  81. data/lib/active_support/core_ext/regexp.rb +8 -1
  82. data/lib/active_support/core_ext/securerandom.rb +1 -1
  83. data/lib/active_support/core_ext/string/access.rb +5 -24
  84. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  85. data/lib/active_support/core_ext/string/filters.rb +1 -1
  86. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  87. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  88. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  89. data/lib/active_support/core_ext/string/output_safety.rb +92 -41
  90. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  91. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  92. data/lib/active_support/core_ext/symbol.rb +3 -0
  93. data/lib/active_support/core_ext/time/calculations.rb +25 -7
  94. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  95. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  96. data/lib/active_support/core_ext/time/zones.rb +7 -22
  97. data/lib/active_support/core_ext/time.rb +1 -0
  98. data/lib/active_support/core_ext/uri.rb +3 -23
  99. data/lib/active_support/core_ext.rb +2 -1
  100. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  101. data/lib/active_support/current_attributes.rb +39 -16
  102. data/lib/active_support/dependencies/interlock.rb +10 -18
  103. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  104. data/lib/active_support/dependencies.rb +58 -769
  105. data/lib/active_support/deprecation/behaviors.rb +23 -7
  106. data/lib/active_support/deprecation/disallowed.rb +56 -0
  107. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  108. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  109. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  110. data/lib/active_support/deprecation/reporting.rb +50 -7
  111. data/lib/active_support/deprecation.rb +7 -2
  112. data/lib/active_support/descendants_tracker.rb +174 -64
  113. data/lib/active_support/digest.rb +5 -3
  114. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  115. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  116. data/lib/active_support/duration.rb +134 -55
  117. data/lib/active_support/encrypted_configuration.rb +13 -2
  118. data/lib/active_support/encrypted_file.rb +32 -3
  119. data/lib/active_support/environment_inquirer.rb +20 -0
  120. data/lib/active_support/error_reporter.rb +117 -0
  121. data/lib/active_support/evented_file_update_checker.rb +72 -138
  122. data/lib/active_support/execution_context/test_helper.rb +13 -0
  123. data/lib/active_support/execution_context.rb +53 -0
  124. data/lib/active_support/execution_wrapper.rb +43 -21
  125. data/lib/active_support/executor/test_helper.rb +7 -0
  126. data/lib/active_support/fork_tracker.rb +71 -0
  127. data/lib/active_support/gem_version.rb +3 -3
  128. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  129. data/lib/active_support/html_safe_translation.rb +43 -0
  130. data/lib/active_support/i18n.rb +1 -0
  131. data/lib/active_support/i18n_railtie.rb +14 -19
  132. data/lib/active_support/inflector/inflections.rb +24 -9
  133. data/lib/active_support/inflector/methods.rb +29 -49
  134. data/lib/active_support/inflector/transliterate.rb +5 -5
  135. data/lib/active_support/isolated_execution_state.rb +72 -0
  136. data/lib/active_support/json/decoding.rb +4 -4
  137. data/lib/active_support/json/encoding.rb +8 -4
  138. data/lib/active_support/key_generator.rb +23 -6
  139. data/lib/active_support/lazy_load_hooks.rb +28 -4
  140. data/lib/active_support/locale/en.yml +8 -4
  141. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  142. data/lib/active_support/log_subscriber.rb +23 -5
  143. data/lib/active_support/logger.rb +1 -1
  144. data/lib/active_support/logger_silence.rb +2 -26
  145. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  146. data/lib/active_support/message_encryptor.rb +16 -13
  147. data/lib/active_support/message_verifier.rb +50 -18
  148. data/lib/active_support/messages/metadata.rb +2 -2
  149. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  150. data/lib/active_support/messages/rotator.rb +6 -5
  151. data/lib/active_support/multibyte/chars.rb +13 -52
  152. data/lib/active_support/multibyte/unicode.rb +1 -87
  153. data/lib/active_support/multibyte.rb +1 -1
  154. data/lib/active_support/notifications/fanout.rb +110 -69
  155. data/lib/active_support/notifications/instrumenter.rb +37 -29
  156. data/lib/active_support/notifications.rb +55 -28
  157. data/lib/active_support/number_helper/number_converter.rb +2 -4
  158. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  159. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  160. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  161. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  162. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  163. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  164. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  165. data/lib/active_support/number_helper.rb +29 -16
  166. data/lib/active_support/option_merger.rb +11 -18
  167. data/lib/active_support/ordered_hash.rb +1 -1
  168. data/lib/active_support/ordered_options.rb +9 -3
  169. data/lib/active_support/parameter_filter.rb +21 -11
  170. data/lib/active_support/per_thread_registry.rb +6 -1
  171. data/lib/active_support/rails.rb +1 -4
  172. data/lib/active_support/railtie.rb +77 -5
  173. data/lib/active_support/reloader.rb +1 -1
  174. data/lib/active_support/rescuable.rb +16 -16
  175. data/lib/active_support/ruby_features.rb +7 -0
  176. data/lib/active_support/secure_compare_rotator.rb +51 -0
  177. data/lib/active_support/security_utils.rb +19 -12
  178. data/lib/active_support/string_inquirer.rb +2 -2
  179. data/lib/active_support/subscriber.rb +19 -25
  180. data/lib/active_support/tagged_logging.rb +31 -6
  181. data/lib/active_support/test_case.rb +13 -21
  182. data/lib/active_support/testing/assertions.rb +50 -13
  183. data/lib/active_support/testing/deprecation.rb +52 -1
  184. data/lib/active_support/testing/isolation.rb +2 -2
  185. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  186. data/lib/active_support/testing/parallelization/server.rb +82 -0
  187. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  188. data/lib/active_support/testing/parallelization.rb +16 -95
  189. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  190. data/lib/active_support/testing/stream.rb +3 -5
  191. data/lib/active_support/testing/tagged_logging.rb +1 -1
  192. data/lib/active_support/testing/time_helpers.rb +53 -5
  193. data/lib/active_support/time_with_zone.rb +126 -62
  194. data/lib/active_support/values/time_zone.rb +54 -23
  195. data/lib/active_support/version.rb +1 -1
  196. data/lib/active_support/xml_mini/jdom.rb +1 -1
  197. data/lib/active_support/xml_mini/libxml.rb +5 -5
  198. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  199. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  200. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  201. data/lib/active_support/xml_mini/rexml.rb +9 -2
  202. data/lib/active_support/xml_mini.rb +5 -4
  203. data/lib/active_support.rb +29 -1
  204. metadata +46 -45
  205. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  206. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  207. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  208. data/lib/active_support/core_ext/marshal.rb +0 -24
  209. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  210. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  211. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  212. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -22,8 +22,8 @@ module ActiveSupport
22
22
  Encoding.json_encoder.new(options).encode(value)
23
23
  end
24
24
 
25
- module Encoding #:nodoc:
26
- class JSONGemEncoder #:nodoc:
25
+ module Encoding # :nodoc:
26
+ class JSONGemEncoder # :nodoc:
27
27
  attr_reader :options
28
28
 
29
29
  def initialize(options = nil)
@@ -51,7 +51,7 @@ module ActiveSupport
51
51
  ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
52
52
 
53
53
  # This class wraps all the strings we see and does the extra escaping
54
- class EscapedString < String #:nodoc:
54
+ class EscapedString < String # :nodoc:
55
55
  def to_json(*)
56
56
  if Encoding.escape_html_entities_in_json
57
57
  s = super
@@ -93,7 +93,11 @@ module ActiveSupport
93
93
  when Numeric, NilClass, TrueClass, FalseClass
94
94
  value.as_json
95
95
  when Hash
96
- Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
96
+ result = {}
97
+ value.each do |k, v|
98
+ result[jsonify(k)] = jsonify(v)
99
+ end
100
+ result
97
101
  when Array
98
102
  value.map { |v| jsonify(v) }
99
103
  else
@@ -9,24 +9,41 @@ module ActiveSupport
9
9
  # This lets Rails applications have a single secure secret, but avoid reusing that
10
10
  # key in multiple incompatible contexts.
11
11
  class KeyGenerator
12
+ class << self
13
+ def hash_digest_class=(klass)
14
+ if klass.kind_of?(Class) && klass < OpenSSL::Digest
15
+ @hash_digest_class = klass
16
+ else
17
+ raise ArgumentError, "#{klass} is expected to be an OpenSSL::Digest subclass"
18
+ end
19
+ end
20
+
21
+ def hash_digest_class
22
+ @hash_digest_class ||= OpenSSL::Digest::SHA1
23
+ end
24
+ end
25
+
12
26
  def initialize(secret, options = {})
13
27
  @secret = secret
14
28
  # The default iterations are higher than required for our key derivation uses
15
29
  # on the off chance someone uses this for password storage
16
30
  @iterations = options[:iterations] || 2**16
31
+ # Also allow configuration here so people can use this to build a rotation
32
+ # scheme when switching the digest class.
33
+ @hash_digest_class = options[:hash_digest_class] || self.class.hash_digest_class
17
34
  end
18
35
 
19
- # Returns a derived key suitable for use. The default key_size is chosen
36
+ # Returns a derived key suitable for use. The default +key_size+ is chosen
20
37
  # to be compatible with the default settings of ActiveSupport::MessageVerifier.
21
- # i.e. OpenSSL::Digest::SHA1#block_length
38
+ # i.e. <tt>OpenSSL::Digest::SHA1#block_length</tt>
22
39
  def generate_key(salt, key_size = 64)
23
- OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
40
+ OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, @iterations, key_size, @hash_digest_class.new)
24
41
  end
25
42
  end
26
43
 
27
44
  # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
28
- # re-executing the key generation process when it's called using the same salt and
29
- # key_size.
45
+ # re-executing the key generation process when it's called using the same +salt+ and
46
+ # +key_size+.
30
47
  class CachingKeyGenerator
31
48
  def initialize(key_generator)
32
49
  @key_generator = key_generator
@@ -35,7 +52,7 @@ module ActiveSupport
35
52
 
36
53
  # Returns a derived key suitable for use.
37
54
  def generate_key(*args)
38
- @cache_keys[args.join] ||= @key_generator.generate_key(*args)
55
+ @cache_keys[args.join("|")] ||= @key_generator.generate_key(*args)
39
56
  end
40
57
  end
41
58
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # lazy_load_hooks allows Rails to lazily load a lot of components and thus
4
+ # LazyLoadHooks allows Rails to lazily load a lot of components and thus
5
5
  # making the app boot faster. Because of this feature now there is no need to
6
6
  # require <tt>ActiveRecord::Base</tt> at boot time purely to apply
7
7
  # configuration. Instead a hook is registered that applies configuration once
8
8
  # <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
9
9
  # used as example but this feature can be applied elsewhere too.
10
10
  #
11
- # Here is an example where +on_load+ method is called to register a hook.
11
+ # Here is an example where on_load method is called to register a hook.
12
12
  #
13
13
  # initializer 'active_record.initialize_timezone' do
14
14
  # ActiveSupport.on_load(:active_record) do
@@ -18,10 +18,26 @@ module ActiveSupport
18
18
  # end
19
19
  #
20
20
  # When the entirety of +ActiveRecord::Base+ has been
21
- # evaluated then +run_load_hooks+ is invoked. The very last line of
21
+ # evaluated then run_load_hooks is invoked. The very last line of
22
22
  # +ActiveRecord::Base+ is:
23
23
  #
24
24
  # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
25
+ #
26
+ # run_load_hooks will then execute all the hooks that were registered
27
+ # with the on_load method. In the case of the above example, it will
28
+ # execute the block of code that is in the +initializer+.
29
+ #
30
+ # Registering a hook that has already run results in that hook executing
31
+ # immediately. This allows hooks to be nested for code that relies on
32
+ # multiple lazily loaded components:
33
+ #
34
+ # initializer "action_text.renderer" do
35
+ # ActiveSupport.on_load(:action_controller_base) do
36
+ # ActiveSupport.on_load(:action_text_content) do
37
+ # self.default_renderer = Class.new(ActionController::Base).renderer
38
+ # end
39
+ # end
40
+ # end
25
41
  module LazyLoadHooks
26
42
  def self.extended(base) # :nodoc:
27
43
  base.class_eval do
@@ -32,7 +48,8 @@ module ActiveSupport
32
48
  end
33
49
 
34
50
  # Declares a block that will be executed when a Rails component is fully
35
- # loaded.
51
+ # loaded. If the component has already loaded, the block is executed
52
+ # immediately.
36
53
  #
37
54
  # Options:
38
55
  #
@@ -46,6 +63,13 @@ module ActiveSupport
46
63
  @load_hooks[name] << [block, options]
47
64
  end
48
65
 
66
+ # Executes all blocks registered to +name+ via on_load, using +base+ as the
67
+ # evaluation context.
68
+ #
69
+ # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
70
+ #
71
+ # In the case of the above example, it will execute all hooks registered
72
+ # for +:active_record+ within the class +ActiveRecord::Base+.
49
73
  def run_load_hooks(name, base = Object)
50
74
  @loaded[name] << base
51
75
  @load_hooks[name].each do |hook, options|
@@ -44,22 +44,25 @@ en:
44
44
  delimiter: ","
45
45
  # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
46
46
  precision: 3
47
+ # Determine how rounding is performed (see BigDecimal::mode)
48
+ round_mode: default
47
49
  # If set to true, precision will mean the number of significant digits instead
48
50
  # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
49
51
  significant: false
50
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
52
+ # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2)
51
53
  strip_insignificant_zeros: false
52
54
 
53
55
  # Used in NumberHelper.number_to_currency()
54
56
  currency:
55
57
  format:
56
- # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
58
+ # Where is the currency sign? %u is the currency unit, %n is the number (default: $5.00)
57
59
  format: "%u%n"
58
60
  unit: "$"
59
- # These five are to override number.format and are optional
61
+ # These six are to override number.format and are optional
60
62
  separator: "."
61
63
  delimiter: ","
62
64
  precision: 2
65
+ # round_mode:
63
66
  significant: false
64
67
  strip_insignificant_zeros: false
65
68
 
@@ -87,10 +90,11 @@ en:
87
90
  # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
88
91
  human:
89
92
  format:
90
- # These five are to override number.format and are optional
93
+ # These six are to override number.format and are optional
91
94
  # separator:
92
95
  delimiter: ""
93
96
  precision: 3
97
+ # round_mode:
94
98
  significant: true
95
99
  strip_insignificant_zeros: true
96
100
  # Used in number_to_human_size()
@@ -27,13 +27,13 @@ module ActiveSupport
27
27
  #
28
28
  # All you need to do is to ensure that your log subscriber is added to
29
29
  # Rails::Subscriber, as in the second line of the code above. The test
30
- # helpers are responsible for setting up the queue, subscriptions and
30
+ # helpers are responsible for setting up the queue and subscriptions, and
31
31
  # turning colors in logs off.
32
32
  #
33
33
  # The messages are available in the @logger instance, which is a logger with
34
34
  # limited powers (it actually does not send anything to your output), and
35
35
  # you can collect them doing @logger.logged(level), where level is the level
36
- # used in logging, like info, debug, warn and so on.
36
+ # used in logging, like info, debug, warn, and so on.
37
37
  module TestHelper
38
38
  def setup # :nodoc:
39
39
  @logger = MockLogger.new
@@ -6,7 +6,7 @@ require "active_support/subscriber"
6
6
 
7
7
  module ActiveSupport
8
8
  # <tt>ActiveSupport::LogSubscriber</tt> is an object set to consume
9
- # <tt>ActiveSupport::Notifications</tt> with the sole purpose of logging them.
9
+ # ActiveSupport::Notifications with the sole purpose of logging them.
10
10
  # The log subscriber dispatches notifications to a registered object based
11
11
  # on its given namespace.
12
12
  #
@@ -29,11 +29,14 @@ module ActiveSupport
29
29
  # subscriber, the line above should be called after your
30
30
  # <tt>ActiveRecord::LogSubscriber</tt> definition.
31
31
  #
32
+ # A logger also needs to be set with <tt>ActiveRecord::LogSubscriber.logger=</tt>.
33
+ # This is assigned automatically in a Rails environment.
34
+ #
32
35
  # After configured, whenever a <tt>"sql.active_record"</tt> notification is published,
33
36
  # it will properly dispatch the event
34
37
  # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method.
35
38
  #
36
- # Being an <tt>ActiveSupport::Notifications</tt> consumer,
39
+ # Being an ActiveSupport::Notifications consumer,
37
40
  # <tt>ActiveSupport::LogSubscriber</tt> exposes a simple interface to check if
38
41
  # instrumented code raises an exception. It is common to log a different
39
42
  # message in case of an error, and this can be achieved by extending
@@ -93,6 +96,11 @@ module ActiveSupport
93
96
  def flush_all!
94
97
  logger.flush if logger.respond_to?(:flush)
95
98
  end
99
+
100
+ private
101
+ def fetch_public_methods(subscriber, inherit_all)
102
+ subscriber.public_methods(inherit_all) - LogSubscriber.public_instance_methods(true)
103
+ end
96
104
  end
97
105
 
98
106
  def logger
@@ -106,9 +114,13 @@ module ActiveSupport
106
114
  def finish(name, id, payload)
107
115
  super if logger
108
116
  rescue => e
109
- if logger
110
- logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
111
- end
117
+ log_exception(name, e)
118
+ end
119
+
120
+ def publish_event(event)
121
+ super if logger
122
+ rescue => e
123
+ log_exception(event.name, e)
112
124
  end
113
125
 
114
126
  private
@@ -130,5 +142,11 @@ module ActiveSupport
130
142
  bold = bold ? BOLD : ""
131
143
  "#{bold}#{color}#{text}#{CLEAR}"
132
144
  end
145
+
146
+ def log_exception(name, e)
147
+ if logger
148
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
149
+ end
150
+ end
133
151
  end
134
152
  end
@@ -14,7 +14,7 @@ module ActiveSupport
14
14
  # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
15
15
  # # => true
16
16
  def self.logger_outputs_to?(logger, *sources)
17
- logdev = logger.instance_variable_get("@logdev")
17
+ logdev = logger.instance_variable_get(:@logdev)
18
18
  logger_source = logdev.dev if logdev.respond_to?(:dev)
19
19
  sources.any? { |source| source == logger_source }
20
20
  end
@@ -4,19 +4,6 @@ require "active_support/concern"
4
4
  require "active_support/core_ext/module/attribute_accessors"
5
5
  require "active_support/logger_thread_safe_level"
6
6
 
7
- module LoggerSilence
8
- extend ActiveSupport::Concern
9
-
10
- included do
11
- ActiveSupport::Deprecation.warn(
12
- "Including LoggerSilence is deprecated and will be removed in Rails 6.1. " \
13
- "Please use `ActiveSupport::LoggerSilence` instead"
14
- )
15
-
16
- include ActiveSupport::LoggerSilence
17
- end
18
- end
19
-
20
7
  module ActiveSupport
21
8
  module LoggerSilence
22
9
  extend ActiveSupport::Concern
@@ -27,19 +14,8 @@ module ActiveSupport
27
14
  end
28
15
 
29
16
  # Silences the logger for the duration of the block.
30
- def silence(temporary_level = Logger::ERROR)
31
- if silencer
32
- begin
33
- old_local_level = local_level
34
- self.local_level = temporary_level
35
-
36
- yield self
37
- ensure
38
- self.local_level = old_local_level
39
- end
40
- else
41
- yield self
42
- end
17
+ def silence(severity = Logger::ERROR)
18
+ silencer ? log_at(severity) { yield self } : yield(self)
43
19
  end
44
20
  end
45
21
  end
@@ -9,10 +9,6 @@ module ActiveSupport
9
9
  module LoggerThreadSafeLevel # :nodoc:
10
10
  extend ActiveSupport::Concern
11
11
 
12
- included do
13
- cattr_accessor :local_levels, default: Concurrent::Map.new(initial_capacity: 2), instance_accessor: false
14
- end
15
-
16
12
  Logger::Severity.constants.each do |severity|
17
13
  class_eval(<<-EOT, __FILE__, __LINE__ + 1)
18
14
  def #{severity.downcase}? # def debug?
@@ -21,36 +17,53 @@ module ActiveSupport
21
17
  EOT
22
18
  end
23
19
 
24
- def after_initialize
25
- ActiveSupport::Deprecation.warn(
26
- "Logger don't need to call #after_initialize directly anymore. It will be deprecated without replacement in " \
27
- "Rails 6.1."
28
- )
29
- end
30
-
31
- def local_log_id
32
- Fiber.current.__id__
33
- end
34
-
35
20
  def local_level
36
- self.class.local_levels[local_log_id]
21
+ IsolatedExecutionState[:logger_thread_safe_level]
37
22
  end
38
23
 
39
24
  def local_level=(level)
40
- if level
41
- self.class.local_levels[local_log_id] = level
25
+ case level
26
+ when Integer
27
+ when Symbol
28
+ level = Logger::Severity.const_get(level.to_s.upcase)
29
+ when nil
42
30
  else
43
- self.class.local_levels.delete(local_log_id)
31
+ raise ArgumentError, "Invalid log level: #{level.inspect}"
44
32
  end
33
+ IsolatedExecutionState[:logger_thread_safe_level] = level
45
34
  end
46
35
 
47
36
  def level
48
37
  local_level || super
49
38
  end
50
39
 
40
+ # Change the thread-local level for the duration of the given block.
41
+ def log_at(level)
42
+ old_local_level, self.local_level = local_level, level
43
+ yield
44
+ ensure
45
+ self.local_level = old_local_level
46
+ end
47
+
48
+ # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
49
+ # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
51
50
  def add(severity, message = nil, progname = nil, &block) # :nodoc:
52
- return true if @logdev.nil? || (severity || UNKNOWN) < level
53
- super
51
+ severity ||= UNKNOWN
52
+ progname ||= @progname
53
+
54
+ return true if @logdev.nil? || severity < level
55
+
56
+ if message.nil?
57
+ if block_given?
58
+ message = yield
59
+ else
60
+ message = progname
61
+ progname = @progname
62
+ end
63
+ end
64
+
65
+ @logdev.write \
66
+ format_message(format_severity(severity), Time.now, progname, message)
54
67
  end
55
68
  end
56
69
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "openssl"
4
4
  require "base64"
5
- require "active_support/core_ext/array/extract_options"
6
5
  require "active_support/core_ext/module/attribute_accessors"
7
6
  require "active_support/message_verifier"
8
7
  require "active_support/messages/metadata"
@@ -14,7 +13,7 @@ module ActiveSupport
14
13
  # The cipher text and initialization vector are base64 encoded and returned
15
14
  # to you.
16
15
  #
17
- # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
16
+ # This can be used in situations similar to the MessageVerifier, but
18
17
  # where you don't want users to be able to determine the value of the payload.
19
18
  #
20
19
  # len = ActiveSupport::MessageEncryptor.key_len
@@ -24,6 +23,12 @@ module ActiveSupport
24
23
  # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
25
24
  # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
26
25
  #
26
+ # The +decrypt_and_verify+ method will raise an
27
+ # <tt>ActiveSupport::MessageEncryptor::InvalidMessage</tt> exception if the data
28
+ # provided cannot be decrypted or verified.
29
+ #
30
+ # crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
31
+ #
27
32
  # === Confining messages to a specific purpose
28
33
  #
29
34
  # By default any message can be used throughout your app. But they can also be
@@ -85,7 +90,7 @@ module ActiveSupport
85
90
  cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
86
91
 
87
92
  class << self
88
- def default_cipher #:nodoc:
93
+ def default_cipher # :nodoc:
89
94
  if use_authenticated_message_encryption
90
95
  "aes-256-gcm"
91
96
  else
@@ -94,7 +99,7 @@ module ActiveSupport
94
99
  end
95
100
  end
96
101
 
97
- module NullSerializer #:nodoc:
102
+ module NullSerializer # :nodoc:
98
103
  def self.load(value)
99
104
  value
100
105
  end
@@ -104,7 +109,7 @@ module ActiveSupport
104
109
  end
105
110
  end
106
111
 
107
- module NullVerifier #:nodoc:
112
+ module NullVerifier # :nodoc:
108
113
  def self.verify(value)
109
114
  value
110
115
  end
@@ -120,10 +125,10 @@ module ActiveSupport
120
125
  # Initialize a new MessageEncryptor. +secret+ must be at least as long as
121
126
  # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
122
127
  # bits. If you are using a user-entered secret, you can generate a suitable
123
- # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
128
+ # key by using ActiveSupport::KeyGenerator or a similar key
124
129
  # derivation function.
125
130
  #
126
- # First additional parameter is used as the signature key for +MessageVerifier+.
131
+ # First additional parameter is used as the signature key for MessageVerifier.
127
132
  # This allows you to specify keys to encrypt and sign data.
128
133
  #
129
134
  # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
@@ -134,15 +139,13 @@ module ActiveSupport
134
139
  # * <tt>:digest</tt> - String of digest to use for signing. Default is
135
140
  # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
136
141
  # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
137
- def initialize(secret, *signature_key_or_options)
138
- options = signature_key_or_options.extract_options!
139
- sign_secret = signature_key_or_options.first
142
+ def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil)
140
143
  @secret = secret
141
144
  @sign_secret = sign_secret
142
- @cipher = options[:cipher] || self.class.default_cipher
143
- @digest = options[:digest] || "SHA1" unless aead_mode?
145
+ @cipher = cipher || self.class.default_cipher
146
+ @digest = digest || "SHA1" unless aead_mode?
144
147
  @verifier = resolve_verifier
145
- @serializer = options[:serializer] || Marshal
148
+ @serializer = serializer || Marshal
146
149
  end
147
150
 
148
151
  # Encrypt and sign a message. We need to sign the message in order to avoid
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openssl"
3
4
  require "base64"
4
5
  require "active_support/core_ext/object/blank"
5
6
  require "active_support/security_utils"
@@ -68,8 +69,8 @@ module ActiveSupport
68
69
  # return the original value. But messages can be set to expire at a given
69
70
  # time with +:expires_in+ or +:expires_at+.
70
71
  #
71
- # @verifier.generate(parcel, expires_in: 1.month)
72
- # @verifier.generate(doowad, expires_at: Time.now.end_of_year)
72
+ # @verifier.generate("parcel", expires_in: 1.month)
73
+ # @verifier.generate("doowad", expires_at: Time.now.end_of_year)
73
74
  #
74
75
  # Then the messages can be verified and returned up to the expire time.
75
76
  # Thereafter, the +verified+ method returns +nil+ while +verify+ raises
@@ -78,8 +79,8 @@ module ActiveSupport
78
79
  # === Rotating keys
79
80
  #
80
81
  # 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.
82
+ # back to a stack of verifiers. Call +rotate+ to build and add a verifier so
83
+ # either +verified+ or +verify+ will also try verifying with the fallback.
83
84
  #
84
85
  # By default any rotated verifiers use the values of the primary
85
86
  # verifier unless specified otherwise.
@@ -103,11 +104,14 @@ module ActiveSupport
103
104
 
104
105
  class InvalidSignature < StandardError; end
105
106
 
106
- def initialize(secret, options = {})
107
+ SEPARATOR = "--" # :nodoc:
108
+ SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
109
+
110
+ def initialize(secret, digest: nil, serializer: nil)
107
111
  raise ArgumentError, "Secret should not be nil." unless secret
108
112
  @secret = secret
109
- @digest = options[:digest] || "SHA1"
110
- @serializer = options[:serializer] || Marshal
113
+ @digest = digest&.to_s || "SHA1"
114
+ @serializer = serializer || Marshal
111
115
  end
112
116
 
113
117
  # Checks if a signed message could have been generated by signing an object
@@ -120,10 +124,8 @@ module ActiveSupport
120
124
  # tampered_message = signed_message.chop # editing the message invalidates the signature
121
125
  # verifier.valid_message?(tampered_message) # => false
122
126
  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
+ data, digest = get_data_and_digest_from(signed_message)
128
+ digest_matches_data?(digest, data)
127
129
  end
128
130
 
129
131
  # Decodes the signed message using the +MessageVerifier+'s secret.
@@ -148,9 +150,9 @@ module ActiveSupport
148
150
  # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
149
151
  # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
150
152
  def verified(signed_message, purpose: nil, **)
151
- if valid_message?(signed_message)
153
+ data, digest = get_data_and_digest_from(signed_message)
154
+ if digest_matches_data?(digest, data)
152
155
  begin
153
- data = signed_message.split("--")[0]
154
156
  message = Messages::Metadata.verify(decode(data), purpose)
155
157
  @serializer.load(message) if message
156
158
  rescue ArgumentError => argument_error
@@ -178,14 +180,14 @@ module ActiveSupport
178
180
 
179
181
  # Generates a signed message for the provided value.
180
182
  #
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
+ # The message is signed with the +MessageVerifier+'s secret.
184
+ # Returns Base64-encoded message joined with the generated signature.
183
185
  #
184
186
  # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
185
187
  # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
186
188
  def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
187
189
  data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
188
- "#{data}--#{generate_digest(data)}"
190
+ "#{data}#{SEPARATOR}#{generate_digest(data)}"
189
191
  end
190
192
 
191
193
  private
@@ -198,8 +200,38 @@ module ActiveSupport
198
200
  end
199
201
 
200
202
  def generate_digest(data)
201
- require "openssl" unless defined?(OpenSSL)
202
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
203
+ OpenSSL::HMAC.hexdigest(@digest, @secret, data)
204
+ end
205
+
206
+ def digest_length_in_hex
207
+ # In hexadecimal (AKA base16) it takes 4 bits to represent a character,
208
+ # hence we multiply the digest's length (in bytes) by 8 to get it in
209
+ # bits and divide by 4 to get its number of characters it hex. Well, 8
210
+ # divided by 4 is 2.
211
+ @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2
212
+ end
213
+
214
+ def separator_index_for(signed_message)
215
+ index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH
216
+ return if index.negative? || signed_message[index, SEPARATOR_LENGTH] != SEPARATOR
217
+
218
+ index
219
+ end
220
+
221
+ def get_data_and_digest_from(signed_message)
222
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.empty?
223
+
224
+ separator_index = separator_index_for(signed_message)
225
+ return if separator_index.nil?
226
+
227
+ data = signed_message[0...separator_index]
228
+ digest = signed_message[separator_index + SEPARATOR_LENGTH..-1]
229
+
230
+ [data, digest]
231
+ end
232
+
233
+ def digest_matches_data?(digest, data)
234
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
203
235
  end
204
236
  end
205
237
  end
@@ -3,8 +3,8 @@
3
3
  require "time"
4
4
 
5
5
  module ActiveSupport
6
- module Messages #:nodoc:
7
- class Metadata #:nodoc:
6
+ module Messages # :nodoc:
7
+ class Metadata # :nodoc:
8
8
  def initialize(message, expires_at = nil, purpose = nil)
9
9
  @message, @purpose = message, purpose
10
10
  @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at
@@ -9,7 +9,8 @@ module ActiveSupport
9
9
  @signed, @encrypted = [], []
10
10
  end
11
11
 
12
- def rotate(kind, *args)
12
+ def rotate(kind, *args, **options)
13
+ args << options unless options.empty?
13
14
  case kind
14
15
  when :signed
15
16
  @signed << args