activesupport 4.2.11.1 → 6.0.3.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (263) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +399 -411
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -7
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +34 -6
  9. data/lib/active_support/benchmarkable.rb +6 -4
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache/file_store.rb +58 -53
  12. data/lib/active_support/cache/mem_cache_store.rb +95 -91
  13. data/lib/active_support/cache/memory_store.rb +39 -36
  14. data/lib/active_support/cache/null_store.rb +11 -7
  15. data/lib/active_support/cache/redis_cache_store.rb +493 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +75 -42
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  18. data/lib/active_support/cache.rb +331 -217
  19. data/lib/active_support/callbacks.rb +650 -592
  20. data/lib/active_support/concern.rb +35 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
  22. data/lib/active_support/concurrency/share_lock.rb +226 -0
  23. data/lib/active_support/configurable.rb +13 -14
  24. data/lib/active_support/core_ext/array/access.rb +41 -1
  25. data/lib/active_support/core_ext/array/conversions.rb +24 -20
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  28. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  29. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  30. data/lib/active_support/core_ext/array/prepend_and_append.rb +4 -6
  31. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  32. data/lib/active_support/core_ext/array.rb +9 -6
  33. data/lib/active_support/core_ext/benchmark.rb +3 -1
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  35. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  36. data/lib/active_support/core_ext/class/attribute.rb +45 -31
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  38. data/lib/active_support/core_ext/class/subclasses.rb +20 -6
  39. data/lib/active_support/core_ext/class.rb +4 -3
  40. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  41. data/lib/active_support/core_ext/date/blank.rb +14 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +17 -14
  43. data/lib/active_support/core_ext/date/conversions.rb +25 -23
  44. data/lib/active_support/core_ext/date/zones.rb +4 -2
  45. data/lib/active_support/core_ext/date.rb +6 -4
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +154 -65
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +4 -3
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -13
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  50. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +37 -19
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +8 -6
  53. data/lib/active_support/core_ext/date_time/conversions.rb +16 -13
  54. data/lib/active_support/core_ext/date_time.rb +7 -5
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/digest.rb +3 -0
  57. data/lib/active_support/core_ext/enumerable.rb +114 -22
  58. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  59. data/lib/active_support/core_ext/file.rb +3 -1
  60. data/lib/active_support/core_ext/hash/compact.rb +4 -23
  61. data/lib/active_support/core_ext/hash/conversions.rb +62 -41
  62. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  63. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  64. data/lib/active_support/core_ext/hash/except.rb +12 -9
  65. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  66. data/lib/active_support/core_ext/hash/keys.rb +19 -42
  67. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  68. data/lib/active_support/core_ext/hash/slice.rb +5 -27
  69. data/lib/active_support/core_ext/hash/transform_values.rb +4 -22
  70. data/lib/active_support/core_ext/hash.rb +10 -9
  71. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  72. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  73. data/lib/active_support/core_ext/integer/time.rb +11 -18
  74. data/lib/active_support/core_ext/integer.rb +5 -3
  75. data/lib/active_support/core_ext/kernel/concern.rb +5 -1
  76. data/lib/active_support/core_ext/kernel/reporting.rb +4 -84
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  78. data/lib/active_support/core_ext/kernel.rb +5 -5
  79. data/lib/active_support/core_ext/load_error.rb +3 -22
  80. data/lib/active_support/core_ext/marshal.rb +8 -8
  81. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  82. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  83. data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +46 -46
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +144 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +11 -12
  87. data/lib/active_support/core_ext/module/delegation.rb +133 -30
  88. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  89. data/lib/active_support/core_ext/module/introspection.rb +44 -19
  90. data/lib/active_support/core_ext/module/reachable.rb +5 -7
  91. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/module.rb +13 -11
  94. data/lib/active_support/core_ext/name_error.rb +22 -2
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +129 -136
  97. data/lib/active_support/core_ext/numeric/inquiry.rb +5 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +35 -23
  99. data/lib/active_support/core_ext/numeric.rb +5 -3
  100. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  101. data/lib/active_support/core_ext/object/blank.rb +27 -3
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  104. data/lib/active_support/core_ext/object/duplicable.rb +13 -93
  105. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +51 -20
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +10 -5
  110. data/lib/active_support/core_ext/object/try.rb +81 -23
  111. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  112. data/lib/active_support/core_ext/object.rb +14 -13
  113. data/lib/active_support/core_ext/range/compare_range.rb +76 -0
  114. data/lib/active_support/core_ext/range/conversions.rb +37 -15
  115. data/lib/active_support/core_ext/range/each.rb +18 -17
  116. data/lib/active_support/core_ext/range/include_range.rb +7 -21
  117. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  118. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  119. data/lib/active_support/core_ext/range.rb +7 -4
  120. data/lib/active_support/core_ext/regexp.rb +2 -0
  121. data/lib/active_support/core_ext/securerandom.rb +45 -0
  122. data/lib/active_support/core_ext/string/access.rb +16 -6
  123. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  124. data/lib/active_support/core_ext/string/conversions.rb +7 -4
  125. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  126. data/lib/active_support/core_ext/string/filters.rb +48 -6
  127. data/lib/active_support/core_ext/string/indent.rb +6 -4
  128. data/lib/active_support/core_ext/string/inflections.rb +66 -24
  129. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  130. data/lib/active_support/core_ext/string/multibyte.rb +16 -7
  131. data/lib/active_support/core_ext/string/output_safety.rb +93 -40
  132. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  133. data/lib/active_support/core_ext/string/strip.rb +6 -5
  134. data/lib/active_support/core_ext/string/zones.rb +4 -2
  135. data/lib/active_support/core_ext/string.rb +15 -13
  136. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  137. data/lib/active_support/core_ext/time/calculations.rb +115 -52
  138. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  139. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  140. data/lib/active_support/core_ext/time/zones.rb +41 -7
  141. data/lib/active_support/core_ext/time.rb +7 -6
  142. data/lib/active_support/core_ext/uri.rb +6 -7
  143. data/lib/active_support/core_ext.rb +3 -1
  144. data/lib/active_support/current_attributes.rb +203 -0
  145. data/lib/active_support/dependencies/autoload.rb +2 -0
  146. data/lib/active_support/dependencies/interlock.rb +57 -0
  147. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  148. data/lib/active_support/dependencies.rb +208 -166
  149. data/lib/active_support/deprecation/behaviors.rb +44 -11
  150. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  151. data/lib/active_support/deprecation/instance_delegator.rb +17 -2
  152. data/lib/active_support/deprecation/method_wrappers.rb +61 -21
  153. data/lib/active_support/deprecation/proxy_wrappers.rb +81 -30
  154. data/lib/active_support/deprecation/reporting.rb +32 -12
  155. data/lib/active_support/deprecation.rb +12 -9
  156. data/lib/active_support/descendants_tracker.rb +57 -9
  157. data/lib/active_support/digest.rb +20 -0
  158. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  159. data/lib/active_support/duration/iso8601_serializer.rb +53 -0
  160. data/lib/active_support/duration.rb +315 -40
  161. data/lib/active_support/encrypted_configuration.rb +45 -0
  162. data/lib/active_support/encrypted_file.rb +100 -0
  163. data/lib/active_support/evented_file_update_checker.rb +234 -0
  164. data/lib/active_support/execution_wrapper.rb +129 -0
  165. data/lib/active_support/executor.rb +8 -0
  166. data/lib/active_support/file_update_checker.rb +62 -37
  167. data/lib/active_support/gem_version.rb +6 -4
  168. data/lib/active_support/gzip.rb +7 -5
  169. data/lib/active_support/hash_with_indifferent_access.rb +129 -30
  170. data/lib/active_support/i18n.rb +9 -6
  171. data/lib/active_support/i18n_railtie.rb +50 -14
  172. data/lib/active_support/inflections.rb +13 -11
  173. data/lib/active_support/inflector/inflections.rb +58 -13
  174. data/lib/active_support/inflector/methods.rb +159 -145
  175. data/lib/active_support/inflector/transliterate.rb +84 -34
  176. data/lib/active_support/inflector.rb +7 -5
  177. data/lib/active_support/json/decoding.rb +32 -30
  178. data/lib/active_support/json/encoding.rb +17 -60
  179. data/lib/active_support/json.rb +4 -2
  180. data/lib/active_support/key_generator.rb +11 -43
  181. data/lib/active_support/lazy_load_hooks.rb +53 -20
  182. data/lib/active_support/locale/en.rb +33 -0
  183. data/lib/active_support/locale/en.yml +2 -0
  184. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  185. data/lib/active_support/log_subscriber.rb +44 -19
  186. data/lib/active_support/logger.rb +9 -23
  187. data/lib/active_support/logger_silence.rb +32 -14
  188. data/lib/active_support/logger_thread_safe_level.rb +32 -8
  189. data/lib/active_support/message_encryptor.rb +166 -53
  190. data/lib/active_support/message_verifier.rb +149 -16
  191. data/lib/active_support/messages/metadata.rb +72 -0
  192. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  193. data/lib/active_support/messages/rotator.rb +56 -0
  194. data/lib/active_support/multibyte/chars.rb +56 -63
  195. data/lib/active_support/multibyte/unicode.rb +56 -290
  196. data/lib/active_support/multibyte.rb +4 -2
  197. data/lib/active_support/notifications/fanout.rb +109 -22
  198. data/lib/active_support/notifications/instrumenter.rb +107 -16
  199. data/lib/active_support/notifications.rb +51 -10
  200. data/lib/active_support/number_helper/number_converter.rb +16 -15
  201. data/lib/active_support/number_helper/number_to_currency_converter.rb +14 -15
  202. data/lib/active_support/number_helper/number_to_delimited_converter.rb +11 -4
  203. data/lib/active_support/number_helper/number_to_human_converter.rb +13 -10
  204. data/lib/active_support/number_helper/number_to_human_size_converter.rb +11 -9
  205. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  206. data/lib/active_support/number_helper/number_to_phone_converter.rb +15 -5
  207. data/lib/active_support/number_helper/number_to_rounded_converter.rb +25 -57
  208. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  209. data/lib/active_support/number_helper.rb +105 -68
  210. data/lib/active_support/option_merger.rb +24 -4
  211. data/lib/active_support/ordered_hash.rb +7 -5
  212. data/lib/active_support/ordered_options.rb +27 -5
  213. data/lib/active_support/parameter_filter.rb +128 -0
  214. data/lib/active_support/per_thread_registry.rb +9 -4
  215. data/lib/active_support/proxy_object.rb +2 -0
  216. data/lib/active_support/rails.rb +10 -8
  217. data/lib/active_support/railtie.rb +43 -9
  218. data/lib/active_support/reloader.rb +130 -0
  219. data/lib/active_support/rescuable.rb +108 -53
  220. data/lib/active_support/security_utils.rb +15 -11
  221. data/lib/active_support/string_inquirer.rb +11 -4
  222. data/lib/active_support/subscriber.rb +74 -30
  223. data/lib/active_support/tagged_logging.rb +25 -13
  224. data/lib/active_support/test_case.rb +107 -44
  225. data/lib/active_support/testing/assertions.rb +151 -20
  226. data/lib/active_support/testing/autorun.rb +4 -2
  227. data/lib/active_support/testing/constant_lookup.rb +2 -1
  228. data/lib/active_support/testing/declarative.rb +3 -1
  229. data/lib/active_support/testing/deprecation.rb +13 -10
  230. data/lib/active_support/testing/file_fixtures.rb +38 -0
  231. data/lib/active_support/testing/isolation.rb +35 -26
  232. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  233. data/lib/active_support/testing/parallelization.rb +134 -0
  234. data/lib/active_support/testing/setup_and_teardown.rb +13 -8
  235. data/lib/active_support/testing/stream.rb +43 -0
  236. data/lib/active_support/testing/tagged_logging.rb +3 -1
  237. data/lib/active_support/testing/time_helpers.rb +84 -20
  238. data/lib/active_support/time.rb +14 -12
  239. data/lib/active_support/time_with_zone.rb +179 -39
  240. data/lib/active_support/values/time_zone.rb +203 -63
  241. data/lib/active_support/version.rb +3 -1
  242. data/lib/active_support/xml_mini/jdom.rb +116 -115
  243. data/lib/active_support/xml_mini/libxml.rb +16 -13
  244. data/lib/active_support/xml_mini/libxmlsax.rb +15 -14
  245. data/lib/active_support/xml_mini/nokogiri.rb +14 -12
  246. data/lib/active_support/xml_mini/nokogirisax.rb +14 -13
  247. data/lib/active_support/xml_mini/rexml.rb +11 -9
  248. data/lib/active_support/xml_mini.rb +38 -46
  249. data/lib/active_support.rb +13 -11
  250. metadata +84 -26
  251. data/lib/active_support/concurrency/latch.rb +0 -27
  252. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -16
  253. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  254. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  255. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  256. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  257. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -13
  258. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  259. data/lib/active_support/core_ext/object/itself.rb +0 -15
  260. data/lib/active_support/core_ext/struct.rb +0 -6
  261. data/lib/active_support/core_ext/thread.rb +0 -86
  262. data/lib/active_support/core_ext/time/marshal.rb +0 -30
  263. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,11 +1,11 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/logger_silence'
3
- require 'active_support/logger_thread_safe_level'
4
- require 'logger'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/logger_silence"
4
+ require "active_support/logger_thread_safe_level"
5
+ require "logger"
5
6
 
6
7
  module ActiveSupport
7
8
  class Logger < ::Logger
8
- include ActiveSupport::LoggerThreadSafeLevel
9
9
  include LoggerSilence
10
10
 
11
11
  # Returns true if the logger destination matches one of the sources
@@ -58,16 +58,16 @@ module ActiveSupport
58
58
  end
59
59
 
60
60
  define_method(:silence) do |level = Logger::ERROR, &block|
61
- if logger.respond_to?(:silence) && logger.method(:silence).owner != ::Kernel
61
+ if logger.respond_to?(:silence)
62
62
  logger.silence(level) do
63
- if respond_to?(:silence) && method(:silence).owner != ::Kernel
63
+ if defined?(super)
64
64
  super(level, &block)
65
65
  else
66
66
  block.call(self)
67
67
  end
68
68
  end
69
69
  else
70
- if respond_to?(:silence) && method(:silence).owner != ::Kernel
70
+ if defined?(super)
71
71
  super(level, &block)
72
72
  else
73
73
  block.call(self)
@@ -77,23 +77,9 @@ module ActiveSupport
77
77
  end
78
78
  end
79
79
 
80
- def initialize(*args)
80
+ def initialize(*args, **kwargs)
81
81
  super
82
82
  @formatter = SimpleFormatter.new
83
- after_initialize if respond_to? :after_initialize
84
- end
85
-
86
- def add(severity, message = nil, progname = nil, &block)
87
- return true if @logdev.nil? || (severity || UNKNOWN) < level
88
- super
89
- end
90
-
91
- Logger::Severity.constants.each do |severity|
92
- class_eval(<<-EOT, __FILE__, __LINE__ + 1)
93
- def #{severity.downcase}? # def debug?
94
- Logger::#{severity} >= level # DEBUG >= level
95
- end # end
96
- EOT
97
83
  end
98
84
 
99
85
  # Simple formatter which only displays the message.
@@ -1,27 +1,45 @@
1
- require 'active_support/concern'
2
- require 'thread_safe'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+ require "active_support/logger_thread_safe_level"
3
6
 
4
7
  module LoggerSilence
5
8
  extend ActiveSupport::Concern
6
9
 
7
10
  included do
8
- cattr_accessor :silencer
9
- self.silencer = true
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
10
17
  end
18
+ end
19
+
20
+ module ActiveSupport
21
+ module LoggerSilence
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ cattr_accessor :silencer, default: true
26
+ include ActiveSupport::LoggerThreadSafeLevel
27
+ end
11
28
 
12
- # Silences the logger for the duration of the block.
13
- def silence(temporary_level = Logger::ERROR)
14
- if silencer
15
- begin
16
- old_local_level = local_level
17
- self.local_level = temporary_level
29
+ # 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
18
35
 
36
+ yield self
37
+ ensure
38
+ self.local_level = old_local_level
39
+ end
40
+ else
19
41
  yield self
20
- ensure
21
- self.local_level = old_local_level
22
42
  end
23
- else
24
- yield self
25
43
  end
26
44
  end
27
45
  end
@@ -1,32 +1,56 @@
1
- require 'active_support/concern'
2
- require 'thread_safe'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+ require "concurrent"
6
+ require "fiber"
3
7
 
4
8
  module ActiveSupport
5
- module LoggerThreadSafeLevel
9
+ module LoggerThreadSafeLevel # :nodoc:
6
10
  extend ActiveSupport::Concern
7
11
 
12
+ included do
13
+ cattr_accessor :local_levels, default: Concurrent::Map.new(initial_capacity: 2), instance_accessor: false
14
+ end
15
+
16
+ Logger::Severity.constants.each do |severity|
17
+ class_eval(<<-EOT, __FILE__, __LINE__ + 1)
18
+ def #{severity.downcase}? # def debug?
19
+ Logger::#{severity} >= level # DEBUG >= level
20
+ end # end
21
+ EOT
22
+ end
23
+
8
24
  def after_initialize
9
- @local_levels = ThreadSafe::Cache.new
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
+ )
10
29
  end
11
30
 
12
31
  def local_log_id
13
- Thread.current.__id__
32
+ Fiber.current.__id__
14
33
  end
15
34
 
16
35
  def local_level
17
- @local_levels[local_log_id]
36
+ self.class.local_levels[local_log_id]
18
37
  end
19
38
 
20
39
  def local_level=(level)
21
40
  if level
22
- @local_levels[local_log_id] = level
41
+ self.class.local_levels[local_log_id] = level
23
42
  else
24
- @local_levels.delete(local_log_id)
43
+ self.class.local_levels.delete(local_log_id)
25
44
  end
26
45
  end
27
46
 
28
47
  def level
29
48
  local_level || super
30
49
  end
50
+
51
+ def add(severity, message = nil, progname = nil, &block) # :nodoc:
52
+ return true if @logdev.nil? || (severity || UNKNOWN) < level
53
+ super
54
+ end
31
55
  end
32
56
  end
@@ -1,6 +1,11 @@
1
- require 'openssl'
2
- require 'base64'
3
- require 'active_support/core_ext/array/extract_options'
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+ require "active_support/core_ext/array/extract_options"
6
+ require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/message_verifier"
8
+ require "active_support/messages/metadata"
4
9
 
5
10
  module ActiveSupport
6
11
  # MessageEncryptor is a simple way to encrypt values which get stored
@@ -12,13 +17,82 @@ module ActiveSupport
12
17
  # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
13
18
  # where you don't want users to be able to determine the value of the payload.
14
19
  #
15
- # salt = SecureRandom.random_bytes(64)
16
- # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
17
- # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
18
- # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
19
- # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
20
+ # len = ActiveSupport::MessageEncryptor.key_len
21
+ # salt = SecureRandom.random_bytes(len)
22
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."
23
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
24
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
25
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
26
+ #
27
+ # === Confining messages to a specific purpose
28
+ #
29
+ # By default any message can be used throughout your app. But they can also be
30
+ # confined to a specific +:purpose+.
31
+ #
32
+ # token = crypt.encrypt_and_sign("this is the chair", purpose: :login)
33
+ #
34
+ # Then that same purpose must be passed when verifying to get the data back out:
35
+ #
36
+ # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair"
37
+ # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil
38
+ # crypt.decrypt_and_verify(token) # => nil
39
+ #
40
+ # Likewise, if a message has no purpose it won't be returned when verifying with
41
+ # a specific purpose.
42
+ #
43
+ # token = crypt.encrypt_and_sign("the conversation is lively")
44
+ # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil
45
+ # crypt.decrypt_and_verify(token) # => "the conversation is lively"
46
+ #
47
+ # === Making messages expire
48
+ #
49
+ # By default messages last forever and verifying one year from now will still
50
+ # return the original value. But messages can be set to expire at a given
51
+ # time with +:expires_in+ or +:expires_at+.
52
+ #
53
+ # crypt.encrypt_and_sign(parcel, expires_in: 1.month)
54
+ # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
55
+ #
56
+ # Then the messages can be verified and returned up to the expire time.
57
+ # Thereafter, verifying returns +nil+.
58
+ #
59
+ # === Rotating keys
60
+ #
61
+ # MessageEncryptor also supports rotating out old configurations by falling
62
+ # back to a stack of encryptors. Call +rotate+ to build and add an encryptor
63
+ # so +decrypt_and_verify+ will also try the fallback.
64
+ #
65
+ # By default any rotated encryptors use the values of the primary
66
+ # encryptor unless specified otherwise.
67
+ #
68
+ # You'd give your encryptor the new defaults:
69
+ #
70
+ # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
71
+ #
72
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
73
+ # generated with the old values will then work until the rotation is removed.
74
+ #
75
+ # crypt.rotate old_secret # Fallback to an old secret instead of @secret.
76
+ # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
77
+ #
78
+ # Though if both the secret and the cipher was changed at the same time,
79
+ # the above should be combined into:
80
+ #
81
+ # crypt.rotate old_secret, cipher: "aes-256-cbc"
20
82
  class MessageEncryptor
21
- DEFAULT_CIPHER = "aes-256-cbc"
83
+ prepend Messages::Rotator::Encryptor
84
+
85
+ cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
86
+
87
+ class << self
88
+ def default_cipher #:nodoc:
89
+ if use_authenticated_message_encryption
90
+ "aes-256-gcm"
91
+ else
92
+ "aes-256-cbc"
93
+ end
94
+ end
95
+ end
22
96
 
23
97
  module NullSerializer #:nodoc:
24
98
  def self.load(value)
@@ -30,85 +104,124 @@ module ActiveSupport
30
104
  end
31
105
  end
32
106
 
107
+ module NullVerifier #:nodoc:
108
+ def self.verify(value)
109
+ value
110
+ end
111
+
112
+ def self.generate(value)
113
+ value
114
+ end
115
+ end
116
+
33
117
  class InvalidMessage < StandardError; end
34
118
  OpenSSLCipherError = OpenSSL::Cipher::CipherError
35
119
 
36
120
  # Initialize a new MessageEncryptor. +secret+ must be at least as long as
37
- # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
121
+ # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
38
122
  # bits. If you are using a user-entered secret, you can generate a suitable
39
- # key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
40
- # similar.
123
+ # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
124
+ # derivation function.
125
+ #
126
+ # First additional parameter is used as the signature key for +MessageVerifier+.
127
+ # This allows you to specify keys to encrypt and sign data.
128
+ #
129
+ # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
41
130
  #
42
131
  # Options:
43
132
  # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
44
- # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
45
- # * <tt>:digest</tt> - String of digest to use for signing. Default is +SHA1+.
133
+ # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-gcm'.
134
+ # * <tt>:digest</tt> - String of digest to use for signing. Default is
135
+ # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
46
136
  # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
47
137
  def initialize(secret, *signature_key_or_options)
48
138
  options = signature_key_or_options.extract_options!
49
139
  sign_secret = signature_key_or_options.first
50
140
  @secret = secret
51
141
  @sign_secret = sign_secret
52
- @cipher = options[:cipher] || 'aes-256-cbc'
53
- @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer)
142
+ @cipher = options[:cipher] || self.class.default_cipher
143
+ @digest = options[:digest] || "SHA1" unless aead_mode?
144
+ @verifier = resolve_verifier
54
145
  @serializer = options[:serializer] || Marshal
55
146
  end
56
147
 
57
148
  # Encrypt and sign a message. We need to sign the message in order to avoid
58
- # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
59
- def encrypt_and_sign(value)
60
- verifier.generate(_encrypt(value))
149
+ # padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
150
+ def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil)
151
+ verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose))
61
152
  end
62
153
 
63
154
  # Decrypt and verify a message. We need to verify the message in order to
64
- # avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
65
- def decrypt_and_verify(value)
66
- _decrypt(verifier.verify(value))
155
+ # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
156
+ def decrypt_and_verify(data, purpose: nil, **)
157
+ _decrypt(verifier.verify(data), purpose)
67
158
  end
68
159
 
69
160
  # Given a cipher, returns the key length of the cipher to help generate the key of desired size
70
- def self.key_len(cipher = DEFAULT_CIPHER)
161
+ def self.key_len(cipher = default_cipher)
71
162
  OpenSSL::Cipher.new(cipher).key_len
72
163
  end
73
164
 
74
165
  private
166
+ def _encrypt(value, **metadata_options)
167
+ cipher = new_cipher
168
+ cipher.encrypt
169
+ cipher.key = @secret
75
170
 
76
- def _encrypt(value)
77
- cipher = new_cipher
78
- cipher.encrypt
79
- cipher.key = @secret
171
+ # Rely on OpenSSL for the initialization vector
172
+ iv = cipher.random_iv
173
+ cipher.auth_data = "" if aead_mode?
80
174
 
81
- # Rely on OpenSSL for the initialization vector
82
- iv = cipher.random_iv
175
+ encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), **metadata_options))
176
+ encrypted_data << cipher.final
83
177
 
84
- encrypted_data = cipher.update(@serializer.dump(value))
85
- encrypted_data << cipher.final
86
-
87
- "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
88
- end
178
+ blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
179
+ blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
180
+ blob
181
+ end
89
182
 
90
- def _decrypt(encrypted_message)
91
- cipher = new_cipher
92
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
183
+ def _decrypt(encrypted_message, purpose)
184
+ cipher = new_cipher
185
+ encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) }
186
+
187
+ # Currently the OpenSSL bindings do not raise an error if auth_tag is
188
+ # truncated, which would allow an attacker to easily forge it. See
189
+ # https://github.com/ruby/openssl/issues/63
190
+ raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)
191
+
192
+ cipher.decrypt
193
+ cipher.key = @secret
194
+ cipher.iv = iv
195
+ if aead_mode?
196
+ cipher.auth_tag = auth_tag
197
+ cipher.auth_data = ""
198
+ end
199
+
200
+ decrypted_data = cipher.update(encrypted_data)
201
+ decrypted_data << cipher.final
202
+
203
+ message = Messages::Metadata.verify(decrypted_data, purpose)
204
+ @serializer.load(message) if message
205
+ rescue OpenSSLCipherError, TypeError, ArgumentError
206
+ raise InvalidMessage
207
+ end
93
208
 
94
- cipher.decrypt
95
- cipher.key = @secret
96
- cipher.iv = iv
209
+ def new_cipher
210
+ OpenSSL::Cipher.new(@cipher)
211
+ end
97
212
 
98
- decrypted_data = cipher.update(encrypted_data)
99
- decrypted_data << cipher.final
213
+ attr_reader :verifier
100
214
 
101
- @serializer.load(decrypted_data)
102
- rescue OpenSSLCipherError, TypeError, ArgumentError
103
- raise InvalidMessage
104
- end
105
-
106
- def new_cipher
107
- OpenSSL::Cipher.new(@cipher)
108
- end
215
+ def aead_mode?
216
+ @aead_mode ||= new_cipher.authenticated?
217
+ end
109
218
 
110
- def verifier
111
- @verifier
112
- end
219
+ def resolve_verifier
220
+ if aead_mode?
221
+ NullVerifier
222
+ else
223
+ MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer)
224
+ end
225
+ end
113
226
  end
114
227
  end
@@ -1,6 +1,10 @@
1
- require 'base64'
2
- require 'active_support/core_ext/object/blank'
3
- require 'active_support/security_utils'
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "active_support/core_ext/object/blank"
5
+ require "active_support/security_utils"
6
+ require "active_support/messages/metadata"
7
+ require "active_support/messages/rotator"
4
8
 
5
9
  module ActiveSupport
6
10
  # +MessageVerifier+ makes it easy to generate and verify messages which are
@@ -15,7 +19,7 @@ module ActiveSupport
15
19
  # In the authentication filter:
16
20
  #
17
21
  # id, time = @verifier.verify(cookies[:remember_me])
18
- # if time < Time.now
22
+ # if Time.now < time
19
23
  # self.current_user = User.find(id)
20
24
  # end
21
25
  #
@@ -24,34 +28,163 @@ module ActiveSupport
24
28
  # hash upon initialization:
25
29
  #
26
30
  # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
31
+ #
32
+ # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
33
+ # If you want to use a different hash algorithm, you can change it by providing
34
+ # +:digest+ key as an option while initializing the verifier:
35
+ #
36
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
37
+ #
38
+ # === Confining messages to a specific purpose
39
+ #
40
+ # By default any message can be used throughout your app. But they can also be
41
+ # confined to a specific +:purpose+.
42
+ #
43
+ # token = @verifier.generate("this is the chair", purpose: :login)
44
+ #
45
+ # Then that same purpose must be passed when verifying to get the data back out:
46
+ #
47
+ # @verifier.verified(token, purpose: :login) # => "this is the chair"
48
+ # @verifier.verified(token, purpose: :shipping) # => nil
49
+ # @verifier.verified(token) # => nil
50
+ #
51
+ # @verifier.verify(token, purpose: :login) # => "this is the chair"
52
+ # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature
53
+ # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature
54
+ #
55
+ # Likewise, if a message has no purpose it won't be returned when verifying with
56
+ # a specific purpose.
57
+ #
58
+ # token = @verifier.generate("the conversation is lively")
59
+ # @verifier.verified(token, purpose: :scare_tactics) # => nil
60
+ # @verifier.verified(token) # => "the conversation is lively"
61
+ #
62
+ # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature
63
+ # @verifier.verify(token) # => "the conversation is lively"
64
+ #
65
+ # === Making messages expire
66
+ #
67
+ # By default messages last forever and verifying one year from now will still
68
+ # return the original value. But messages can be set to expire at a given
69
+ # time with +:expires_in+ or +:expires_at+.
70
+ #
71
+ # @verifier.generate(parcel, expires_in: 1.month)
72
+ # @verifier.generate(doowad, expires_at: Time.now.end_of_year)
73
+ #
74
+ # Then the messages can be verified and returned 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
27
101
  class MessageVerifier
102
+ prepend Messages::Rotator::Verifier
103
+
28
104
  class InvalidSignature < StandardError; end
29
105
 
30
106
  def initialize(secret, options = {})
31
- raise ArgumentError, 'Secret should not be nil.' unless secret
107
+ raise ArgumentError, "Secret should not be nil." unless secret
32
108
  @secret = secret
33
- @digest = options[:digest] || 'SHA1'
109
+ @digest = options[:digest] || "SHA1"
34
110
  @serializer = options[:serializer] || Marshal
35
111
  end
36
112
 
37
- def verify(signed_message)
38
- raise InvalidSignature if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
113
+ # Checks if a signed message could have been generated by signing an object
114
+ # with the +MessageVerifier+'s secret.
115
+ #
116
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
117
+ # signed_message = verifier.generate 'a private message'
118
+ # verifier.valid_message?(signed_message) # => true
119
+ #
120
+ # tampered_message = signed_message.chop # editing the message invalidates the signature
121
+ # verifier.valid_message?(tampered_message) # => false
122
+ def valid_message?(signed_message)
123
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
39
124
 
40
125
  data, digest = signed_message.split("--")
41
- if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
126
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
127
+ end
128
+
129
+ # Decodes the signed message using the +MessageVerifier+'s secret.
130
+ #
131
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
132
+ #
133
+ # signed_message = verifier.generate 'a private message'
134
+ # verifier.verified(signed_message) # => 'a private message'
135
+ #
136
+ # Returns +nil+ if the message was not signed with the same secret.
137
+ #
138
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
139
+ # other_verifier.verified(signed_message) # => nil
140
+ #
141
+ # Returns +nil+ if the message is not Base64-encoded.
142
+ #
143
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
144
+ # verifier.verified(invalid_message) # => nil
145
+ #
146
+ # Raises any error raised while decoding the signed message.
147
+ #
148
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
149
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
150
+ def verified(signed_message, purpose: nil, **)
151
+ if valid_message?(signed_message)
42
152
  begin
43
- @serializer.load(decode(data))
153
+ data = signed_message.split("--")[0]
154
+ message = Messages::Metadata.verify(decode(data), purpose)
155
+ @serializer.load(message) if message
44
156
  rescue ArgumentError => argument_error
45
- raise InvalidSignature if argument_error.message =~ %r{invalid base64}
157
+ return if argument_error.message.include?("invalid base64")
46
158
  raise
47
159
  end
48
- else
49
- raise InvalidSignature
50
160
  end
51
161
  end
52
162
 
53
- def generate(value)
54
- data = encode(@serializer.dump(value))
163
+ # Decodes the signed message using the +MessageVerifier+'s secret.
164
+ #
165
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
166
+ # signed_message = verifier.generate 'a private message'
167
+ #
168
+ # verifier.verify(signed_message) # => 'a private message'
169
+ #
170
+ # Raises +InvalidSignature+ if the message was not signed with the same
171
+ # secret or was not Base64-encoded.
172
+ #
173
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
174
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
175
+ def verify(*args, **options)
176
+ verified(*args, **options) || raise(InvalidSignature)
177
+ end
178
+
179
+ # Generates a signed message for the provided value.
180
+ #
181
+ # The message is signed with the +MessageVerifier+'s secret. Without knowing
182
+ # the secret, the original value cannot be extracted from the message.
183
+ #
184
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
185
+ # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
186
+ def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
187
+ data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
55
188
  "#{data}--#{generate_digest(data)}"
56
189
  end
57
190
 
@@ -65,7 +198,7 @@ module ActiveSupport
65
198
  end
66
199
 
67
200
  def generate_digest(data)
68
- require 'openssl' unless defined?(OpenSSL)
201
+ require "openssl" unless defined?(OpenSSL)
69
202
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
70
203
  end
71
204
  end