activesupport 4.2.0 → 5.2.0
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +366 -232
- data/MIT-LICENSE +2 -2
- data/README.rdoc +4 -5
- data/lib/active_support.rb +17 -7
- data/lib/active_support/all.rb +5 -3
- data/lib/active_support/array_inquirer.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +7 -5
- data/lib/active_support/benchmarkable.rb +6 -4
- data/lib/active_support/builder.rb +3 -1
- data/lib/active_support/cache.rb +271 -177
- data/lib/active_support/cache/file_store.rb +41 -35
- data/lib/active_support/cache/mem_cache_store.rb +97 -88
- data/lib/active_support/cache/memory_store.rb +27 -30
- data/lib/active_support/cache/null_store.rb +7 -8
- data/lib/active_support/cache/redis_cache_store.rb +454 -0
- data/lib/active_support/cache/strategy/local_cache.rb +67 -34
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
- data/lib/active_support/callbacks.rb +654 -560
- data/lib/active_support/concern.rb +5 -3
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
- data/lib/active_support/concurrency/share_lock.rb +227 -0
- data/lib/active_support/configurable.rb +8 -5
- data/lib/active_support/core_ext.rb +3 -1
- data/lib/active_support/core_ext/array.rb +9 -6
- data/lib/active_support/core_ext/array/access.rb +29 -1
- data/lib/active_support/core_ext/array/conversions.rb +22 -18
- data/lib/active_support/core_ext/array/extract_options.rb +2 -0
- data/lib/active_support/core_ext/array/grouping.rb +11 -18
- data/lib/active_support/core_ext/array/inquiry.rb +19 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
- data/lib/active_support/core_ext/array/wrap.rb +7 -4
- data/lib/active_support/core_ext/benchmark.rb +3 -1
- data/lib/active_support/core_ext/big_decimal.rb +3 -1
- data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
- data/lib/active_support/core_ext/class.rb +4 -3
- data/lib/active_support/core_ext/class/attribute.rb +41 -22
- data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
- data/lib/active_support/core_ext/class/subclasses.rb +20 -8
- data/lib/active_support/core_ext/date.rb +6 -4
- data/lib/active_support/core_ext/date/acts_like.rb +3 -1
- data/lib/active_support/core_ext/date/blank.rb +14 -0
- data/lib/active_support/core_ext/date/calculations.rb +11 -9
- data/lib/active_support/core_ext/date/conversions.rb +31 -23
- data/lib/active_support/core_ext/date/zones.rb +4 -2
- data/lib/active_support/core_ext/date_and_time/calculations.rb +179 -56
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
- data/lib/active_support/core_ext/date_time.rb +7 -4
- data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
- data/lib/active_support/core_ext/date_time/blank.rb +14 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +58 -20
- data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
- data/lib/active_support/core_ext/digest/uuid.rb +7 -5
- data/lib/active_support/core_ext/enumerable.rb +107 -28
- data/lib/active_support/core_ext/file.rb +3 -1
- data/lib/active_support/core_ext/file/atomic.rb +38 -31
- data/lib/active_support/core_ext/hash.rb +11 -9
- data/lib/active_support/core_ext/hash/compact.rb +24 -15
- data/lib/active_support/core_ext/hash/conversions.rb +63 -43
- data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
- data/lib/active_support/core_ext/hash/except.rb +11 -8
- data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
- data/lib/active_support/core_ext/hash/keys.rb +33 -27
- data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
- data/lib/active_support/core_ext/hash/slice.rb +8 -8
- data/lib/active_support/core_ext/hash/transform_values.rb +16 -7
- data/lib/active_support/core_ext/integer.rb +5 -3
- data/lib/active_support/core_ext/integer/inflections.rb +3 -1
- data/lib/active_support/core_ext/integer/multiple.rb +2 -0
- data/lib/active_support/core_ext/integer/time.rb +11 -33
- data/lib/active_support/core_ext/kernel.rb +6 -5
- data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
- data/lib/active_support/core_ext/kernel/concern.rb +5 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -83
- data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
- data/lib/active_support/core_ext/load_error.rb +3 -22
- data/lib/active_support/core_ext/marshal.rb +13 -10
- data/lib/active_support/core_ext/module.rb +14 -11
- data/lib/active_support/core_ext/module/aliasing.rb +6 -44
- data/lib/active_support/core_ext/module/anonymous.rb +12 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
- data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
- data/lib/active_support/core_ext/module/concerning.rb +11 -12
- data/lib/active_support/core_ext/module/delegation.rb +121 -39
- data/lib/active_support/core_ext/module/deprecation.rb +4 -2
- data/lib/active_support/core_ext/module/introspection.rb +9 -9
- data/lib/active_support/core_ext/module/reachable.rb +5 -2
- data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
- data/lib/active_support/core_ext/module/remove_method.rb +8 -3
- data/lib/active_support/core_ext/name_error.rb +22 -2
- data/lib/active_support/core_ext/numeric.rb +6 -3
- data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
- data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
- data/lib/active_support/core_ext/numeric/time.rb +35 -38
- data/lib/active_support/core_ext/object.rb +14 -13
- data/lib/active_support/core_ext/object/acts_like.rb +12 -1
- data/lib/active_support/core_ext/object/blank.rb +29 -4
- data/lib/active_support/core_ext/object/conversions.rb +6 -4
- data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
- data/lib/active_support/core_ext/object/duplicable.rb +98 -45
- data/lib/active_support/core_ext/object/inclusion.rb +5 -3
- data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
- data/lib/active_support/core_ext/object/json.rb +49 -19
- data/lib/active_support/core_ext/object/to_param.rb +3 -1
- data/lib/active_support/core_ext/object/to_query.rb +6 -4
- data/lib/active_support/core_ext/object/try.rb +70 -22
- data/lib/active_support/core_ext/object/with_options.rb +16 -3
- data/lib/active_support/core_ext/range.rb +7 -4
- data/lib/active_support/core_ext/range/conversions.rb +27 -7
- data/lib/active_support/core_ext/range/each.rb +19 -17
- data/lib/active_support/core_ext/range/include_range.rb +21 -19
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
- data/lib/active_support/core_ext/range/overlaps.rb +2 -0
- data/lib/active_support/core_ext/regexp.rb +6 -0
- data/lib/active_support/core_ext/securerandom.rb +25 -0
- data/lib/active_support/core_ext/string.rb +15 -13
- data/lib/active_support/core_ext/string/access.rb +9 -7
- data/lib/active_support/core_ext/string/behavior.rb +3 -1
- data/lib/active_support/core_ext/string/conversions.rb +8 -5
- data/lib/active_support/core_ext/string/exclude.rb +2 -0
- data/lib/active_support/core_ext/string/filters.rb +10 -8
- data/lib/active_support/core_ext/string/indent.rb +6 -4
- data/lib/active_support/core_ext/string/inflections.rb +61 -24
- data/lib/active_support/core_ext/string/inquiry.rb +3 -1
- data/lib/active_support/core_ext/string/multibyte.rb +15 -7
- data/lib/active_support/core_ext/string/output_safety.rb +35 -35
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
- data/lib/active_support/core_ext/string/strip.rb +4 -5
- data/lib/active_support/core_ext/string/zones.rb +4 -2
- data/lib/active_support/core_ext/time.rb +7 -5
- data/lib/active_support/core_ext/time/acts_like.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +101 -51
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +20 -13
- data/lib/active_support/core_ext/time/zones.rb +41 -7
- data/lib/active_support/core_ext/uri.rb +5 -4
- data/lib/active_support/current_attributes.rb +195 -0
- data/lib/active_support/dependencies.rb +143 -160
- data/lib/active_support/dependencies/autoload.rb +2 -0
- data/lib/active_support/dependencies/interlock.rb +57 -0
- data/lib/active_support/deprecation.rb +12 -9
- data/lib/active_support/deprecation/behaviors.rb +41 -12
- data/lib/active_support/deprecation/constant_accessor.rb +52 -0
- data/lib/active_support/deprecation/instance_delegator.rb +17 -2
- data/lib/active_support/deprecation/method_wrappers.rb +54 -21
- data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
- data/lib/active_support/deprecation/reporting.rb +32 -12
- data/lib/active_support/descendants_tracker.rb +2 -0
- data/lib/active_support/digest.rb +20 -0
- data/lib/active_support/duration.rb +326 -30
- data/lib/active_support/duration/iso8601_parser.rb +125 -0
- data/lib/active_support/duration/iso8601_serializer.rb +55 -0
- data/lib/active_support/encrypted_configuration.rb +49 -0
- data/lib/active_support/encrypted_file.rb +99 -0
- data/lib/active_support/evented_file_update_checker.rb +205 -0
- data/lib/active_support/execution_wrapper.rb +128 -0
- data/lib/active_support/executor.rb +8 -0
- data/lib/active_support/file_update_checker.rb +63 -37
- data/lib/active_support/gem_version.rb +4 -2
- data/lib/active_support/gzip.rb +7 -5
- data/lib/active_support/hash_with_indifferent_access.rb +130 -30
- data/lib/active_support/i18n.rb +8 -6
- data/lib/active_support/i18n_railtie.rb +34 -14
- data/lib/active_support/inflections.rb +13 -11
- data/lib/active_support/inflector.rb +7 -5
- data/lib/active_support/inflector/inflections.rb +61 -12
- data/lib/active_support/inflector/methods.rb +161 -136
- data/lib/active_support/inflector/transliterate.rb +48 -27
- data/lib/active_support/json.rb +4 -2
- data/lib/active_support/json/decoding.rb +16 -13
- data/lib/active_support/json/encoding.rb +15 -57
- data/lib/active_support/key_generator.rb +25 -25
- data/lib/active_support/lazy_load_hooks.rb +50 -20
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +13 -10
- data/lib/active_support/log_subscriber/test_helper.rb +14 -12
- data/lib/active_support/logger.rb +54 -3
- data/lib/active_support/logger_silence.rb +12 -7
- data/lib/active_support/logger_thread_safe_level.rb +33 -0
- data/lib/active_support/message_encryptor.rb +173 -51
- data/lib/active_support/message_verifier.rb +150 -17
- data/lib/active_support/messages/metadata.rb +71 -0
- data/lib/active_support/messages/rotation_configuration.rb +22 -0
- data/lib/active_support/messages/rotator.rb +56 -0
- data/lib/active_support/multibyte.rb +4 -2
- data/lib/active_support/multibyte/chars.rb +37 -24
- data/lib/active_support/multibyte/unicode.rb +100 -96
- data/lib/active_support/notifications.rb +11 -7
- data/lib/active_support/notifications/fanout.rb +10 -8
- data/lib/active_support/notifications/instrumenter.rb +27 -7
- data/lib/active_support/number_helper.rb +94 -68
- data/lib/active_support/number_helper/number_converter.rb +13 -11
- data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
- data/lib/active_support/number_helper/rounding_helper.rb +66 -0
- data/lib/active_support/option_merger.rb +3 -1
- data/lib/active_support/ordered_hash.rb +6 -4
- data/lib/active_support/ordered_options.rb +22 -4
- data/lib/active_support/per_thread_registry.rb +13 -6
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +16 -8
- data/lib/active_support/railtie.rb +43 -9
- data/lib/active_support/reloader.rb +131 -0
- data/lib/active_support/rescuable.rb +108 -53
- data/lib/active_support/security_utils.rb +17 -6
- data/lib/active_support/string_inquirer.rb +11 -3
- data/lib/active_support/subscriber.rb +15 -14
- data/lib/active_support/tagged_logging.rb +14 -11
- data/lib/active_support/test_case.rb +18 -46
- data/lib/active_support/testing/assertions.rb +137 -20
- data/lib/active_support/testing/autorun.rb +4 -2
- data/lib/active_support/testing/constant_lookup.rb +2 -1
- data/lib/active_support/testing/declarative.rb +3 -1
- data/lib/active_support/testing/deprecation.rb +14 -10
- data/lib/active_support/testing/file_fixtures.rb +36 -0
- data/lib/active_support/testing/isolation.rb +34 -25
- data/lib/active_support/testing/method_call_assertions.rb +43 -0
- data/lib/active_support/testing/setup_and_teardown.rb +12 -3
- data/lib/active_support/testing/stream.rb +44 -0
- data/lib/active_support/testing/tagged_logging.rb +3 -1
- data/lib/active_support/testing/time_helpers.rb +96 -27
- data/lib/active_support/time.rb +14 -12
- data/lib/active_support/time_with_zone.rb +195 -53
- data/lib/active_support/values/time_zone.rb +200 -61
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +3 -1
- data/lib/active_support/xml_mini.rb +69 -51
- data/lib/active_support/xml_mini/jdom.rb +116 -113
- data/lib/active_support/xml_mini/libxml.rb +17 -16
- data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
- data/lib/active_support/xml_mini/nokogiri.rb +15 -15
- data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
- data/lib/active_support/xml_mini/rexml.rb +17 -16
- metadata +55 -43
- data/lib/active_support/concurrency/latch.rb +0 -27
- data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
- data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
- data/lib/active_support/core_ext/date_time/zones.rb +0 -6
- data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
- data/lib/active_support/core_ext/module/method_transplanting.rb +0 -11
- data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
- data/lib/active_support/core_ext/object/itself.rb +0 -15
- data/lib/active_support/core_ext/struct.rb +0 -6
- data/lib/active_support/core_ext/thread.rb +0 -86
- data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,6 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
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
|
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 upto the expire time.
|
75
|
+
# Thereafter, the +verified+ method returns +nil+ while +verify+ raises
|
76
|
+
# <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
|
77
|
+
#
|
78
|
+
# === Rotating keys
|
79
|
+
#
|
80
|
+
# MessageVerifier also supports rotating out old configurations by falling
|
81
|
+
# back to a stack of verifiers. Call +rotate+ to build and add a verifier to
|
82
|
+
# so either +verified+ or +verify+ will also try verifying with the fallback.
|
83
|
+
#
|
84
|
+
# By default any rotated verifiers use the values of the primary
|
85
|
+
# verifier unless specified otherwise.
|
86
|
+
#
|
87
|
+
# You'd give your verifier the new defaults:
|
88
|
+
#
|
89
|
+
# verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
|
90
|
+
#
|
91
|
+
# Then gradually rotate the old values out by adding them as fallbacks. Any message
|
92
|
+
# generated with the old values will then work until the rotation is removed.
|
93
|
+
#
|
94
|
+
# verifier.rotate old_secret # Fallback to an old secret instead of @secret.
|
95
|
+
# verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512.
|
96
|
+
# verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON.
|
97
|
+
#
|
98
|
+
# Though the above would most likely be combined into one rotation:
|
99
|
+
#
|
100
|
+
# verifier.rotate old_secret, digest: "SHA256", serializer: Marshal
|
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,
|
107
|
+
raise ArgumentError, "Secret should not be nil." unless secret
|
32
108
|
@secret = secret
|
33
|
-
@digest = options[:digest] ||
|
109
|
+
@digest = options[:digest] || "SHA1"
|
34
110
|
@serializer = options[:serializer] || Marshal
|
35
111
|
end
|
36
112
|
|
37
|
-
|
38
|
-
|
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
|
-
data, digest = signed_message.split("--")
|
41
|
-
|
125
|
+
data, digest = signed_message.split("--".freeze)
|
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
|
-
|
153
|
+
data = signed_message.split("--".freeze)[0]
|
154
|
+
message = Messages::Metadata.verify(decode(data), purpose)
|
155
|
+
@serializer.load(message) if message
|
44
156
|
rescue ArgumentError => argument_error
|
45
|
-
|
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
|
-
|
54
|
-
|
163
|
+
# Decodes the signed message using the +MessageVerifier+'s secret.
|
164
|
+
#
|
165
|
+
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
|
166
|
+
# signed_message = verifier.generate 'a private message'
|
167
|
+
#
|
168
|
+
# verifier.verify(signed_message) # => 'a private message'
|
169
|
+
#
|
170
|
+
# Raises +InvalidSignature+ if the message was not signed with the same
|
171
|
+
# secret or was not Base64-encoded.
|
172
|
+
#
|
173
|
+
# other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
|
174
|
+
# other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
|
175
|
+
def verify(*args)
|
176
|
+
verified(*args) || raise(InvalidSignature)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Generates a signed message for the provided value.
|
180
|
+
#
|
181
|
+
# The message is signed with the +MessageVerifier+'s secret. Without knowing
|
182
|
+
# the secret, the original value cannot be extracted from the message.
|
183
|
+
#
|
184
|
+
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
|
185
|
+
# verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
|
186
|
+
def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
|
187
|
+
data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
|
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
|
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
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
module Messages #:nodoc:
|
7
|
+
class Metadata #:nodoc:
|
8
|
+
def initialize(message, expires_at = nil, purpose = nil)
|
9
|
+
@message, @expires_at, @purpose = message, expires_at, purpose
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_json(options = {})
|
13
|
+
{ _rails: { message: @message, exp: @expires_at, pur: @purpose } }
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def wrap(message, expires_at: nil, expires_in: nil, purpose: nil)
|
18
|
+
if expires_at || expires_in || purpose
|
19
|
+
JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose)
|
20
|
+
else
|
21
|
+
message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def verify(message, purpose)
|
26
|
+
extract_metadata(message).verify(purpose)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def pick_expiry(expires_at, expires_in)
|
31
|
+
if expires_at
|
32
|
+
expires_at.utc.iso8601(3)
|
33
|
+
elsif expires_in
|
34
|
+
Time.now.utc.advance(seconds: expires_in).iso8601(3)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_metadata(message)
|
39
|
+
data = JSON.decode(message) rescue nil
|
40
|
+
|
41
|
+
if data.is_a?(Hash) && data.key?("_rails")
|
42
|
+
new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"])
|
43
|
+
else
|
44
|
+
new(message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def encode(message)
|
49
|
+
::Base64.strict_encode64(message)
|
50
|
+
end
|
51
|
+
|
52
|
+
def decode(message)
|
53
|
+
::Base64.strict_decode64(message)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def verify(purpose)
|
58
|
+
@message if match?(purpose) && fresh?
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def match?(purpose)
|
63
|
+
@purpose.to_s == purpose.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def fresh?
|
67
|
+
@expires_at.nil? || Time.now.utc < Time.iso8601(@expires_at)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module Messages
|
5
|
+
class RotationConfiguration # :nodoc:
|
6
|
+
attr_reader :signed, :encrypted
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@signed, @encrypted = [], []
|
10
|
+
end
|
11
|
+
|
12
|
+
def rotate(kind, *args)
|
13
|
+
case kind
|
14
|
+
when :signed
|
15
|
+
@signed << args
|
16
|
+
when :encrypted
|
17
|
+
@encrypted << args
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module Messages
|
5
|
+
module Rotator # :nodoc:
|
6
|
+
def initialize(*, **options)
|
7
|
+
super
|
8
|
+
|
9
|
+
@options = options
|
10
|
+
@rotations = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def rotate(*secrets, **options)
|
14
|
+
@rotations << build_rotation(*secrets, @options.merge(options))
|
15
|
+
end
|
16
|
+
|
17
|
+
module Encryptor
|
18
|
+
include Rotator
|
19
|
+
|
20
|
+
def decrypt_and_verify(*args, on_rotation: nil, **options)
|
21
|
+
super
|
22
|
+
rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
|
23
|
+
run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def build_rotation(secret = @secret, sign_secret = @sign_secret, options)
|
28
|
+
self.class.new(secret, sign_secret, options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Verifier
|
33
|
+
include Rotator
|
34
|
+
|
35
|
+
def verified(*args, on_rotation: nil, **options)
|
36
|
+
super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def build_rotation(secret = @secret, options)
|
41
|
+
self.class.new(secret, options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def run_rotations(on_rotation)
|
47
|
+
@rotations.find do |rotation|
|
48
|
+
if message = yield(rotation) rescue next
|
49
|
+
on_rotation.call if on_rotation
|
50
|
+
return message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveSupport #:nodoc:
|
2
4
|
module Multibyte
|
3
|
-
autoload :Chars,
|
4
|
-
autoload :Unicode,
|
5
|
+
autoload :Chars, "active_support/multibyte/chars"
|
6
|
+
autoload :Unicode, "active_support/multibyte/unicode"
|
5
7
|
|
6
8
|
# The proxy class returned when calling mb_chars. You can use this accessor
|
7
9
|
# to configure your own proxy class so you can support other encodings. See
|
@@ -1,8 +1,10 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/json"
|
4
|
+
require "active_support/core_ext/string/access"
|
5
|
+
require "active_support/core_ext/string/behavior"
|
6
|
+
require "active_support/core_ext/module/delegation"
|
7
|
+
require "active_support/core_ext/regexp"
|
6
8
|
|
7
9
|
module ActiveSupport #:nodoc:
|
8
10
|
module Multibyte #:nodoc:
|
@@ -16,7 +18,8 @@ module ActiveSupport #:nodoc:
|
|
16
18
|
# through the +mb_chars+ method. Methods which would normally return a
|
17
19
|
# String object now return a Chars object so methods can be chained.
|
18
20
|
#
|
19
|
-
# 'The Perfect String '.mb_chars.downcase.strip.normalize
|
21
|
+
# 'The Perfect String '.mb_chars.downcase.strip.normalize
|
22
|
+
# # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
|
20
23
|
#
|
21
24
|
# Chars objects are perfectly interchangeable with String objects as long as
|
22
25
|
# no explicit class checks are made. If certain methods do explicitly check
|
@@ -46,7 +49,7 @@ module ActiveSupport #:nodoc:
|
|
46
49
|
alias to_s wrapped_string
|
47
50
|
alias to_str wrapped_string
|
48
51
|
|
49
|
-
delegate :<=>, :=~, :acts_like_string?, :
|
52
|
+
delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string
|
50
53
|
|
51
54
|
# Creates a new Chars instance by wrapping _string_.
|
52
55
|
def initialize(string)
|
@@ -57,7 +60,7 @@ module ActiveSupport #:nodoc:
|
|
57
60
|
# Forward all undefined methods to the wrapped string.
|
58
61
|
def method_missing(method, *args, &block)
|
59
62
|
result = @wrapped_string.__send__(method, *args, &block)
|
60
|
-
if method
|
63
|
+
if /!$/.match?(method)
|
61
64
|
self if result
|
62
65
|
else
|
63
66
|
result.kind_of?(String) ? chars(result) : result
|
@@ -86,17 +89,27 @@ module ActiveSupport #:nodoc:
|
|
86
89
|
@wrapped_string.split(*args).map { |i| self.class.new(i) }
|
87
90
|
end
|
88
91
|
|
89
|
-
# Works like
|
90
|
-
# Chars, or nil if the string was not modified.
|
92
|
+
# Works like <tt>String#slice!</tt>, but returns an instance of
|
93
|
+
# Chars, or +nil+ if the string was not modified. The string will not be
|
94
|
+
# modified if the range given is out of bounds
|
95
|
+
#
|
96
|
+
# string = 'Welcome'
|
97
|
+
# string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
|
98
|
+
# string # => 'Welome'
|
99
|
+
# string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
|
100
|
+
# string # => 'me'
|
91
101
|
def slice!(*args)
|
92
|
-
|
102
|
+
string_sliced = @wrapped_string.slice!(*args)
|
103
|
+
if string_sliced
|
104
|
+
chars(string_sliced)
|
105
|
+
end
|
93
106
|
end
|
94
107
|
|
95
108
|
# Reverses all characters in the string.
|
96
109
|
#
|
97
110
|
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
|
98
111
|
def reverse
|
99
|
-
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack(
|
112
|
+
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
|
100
113
|
end
|
101
114
|
|
102
115
|
# Limits the byte size of the string to a number of bytes without breaking
|
@@ -124,7 +137,7 @@ module ActiveSupport #:nodoc:
|
|
124
137
|
|
125
138
|
# Converts characters in the string to the opposite case.
|
126
139
|
#
|
127
|
-
# 'El Cañón
|
140
|
+
# 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
|
128
141
|
def swapcase
|
129
142
|
chars Unicode.swapcase(@wrapped_string)
|
130
143
|
end
|
@@ -133,15 +146,15 @@ module ActiveSupport #:nodoc:
|
|
133
146
|
#
|
134
147
|
# 'über'.mb_chars.capitalize.to_s # => "Über"
|
135
148
|
def capitalize
|
136
|
-
(slice(0) || chars(
|
149
|
+
(slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase
|
137
150
|
end
|
138
151
|
|
139
152
|
# Capitalizes the first letter of every word, when possible.
|
140
153
|
#
|
141
|
-
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
|
142
|
-
# "日本語".mb_chars.titleize
|
154
|
+
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
|
155
|
+
# "日本語".mb_chars.titleize.to_s # => "日本語"
|
143
156
|
def titleize
|
144
|
-
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
|
157
|
+
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
|
145
158
|
end
|
146
159
|
alias_method :titlecase, :titleize
|
147
160
|
|
@@ -161,7 +174,7 @@ module ActiveSupport #:nodoc:
|
|
161
174
|
# 'é'.length # => 2
|
162
175
|
# 'é'.mb_chars.decompose.to_s.length # => 3
|
163
176
|
def decompose
|
164
|
-
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack(
|
177
|
+
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
|
165
178
|
end
|
166
179
|
|
167
180
|
# Performs composition on all the characters.
|
@@ -169,7 +182,7 @@ module ActiveSupport #:nodoc:
|
|
169
182
|
# 'é'.length # => 3
|
170
183
|
# 'é'.mb_chars.compose.to_s.length # => 2
|
171
184
|
def compose
|
172
|
-
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack(
|
185
|
+
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
|
173
186
|
end
|
174
187
|
|
175
188
|
# Returns the number of grapheme clusters in the string.
|
@@ -200,21 +213,21 @@ module ActiveSupport #:nodoc:
|
|
200
213
|
end
|
201
214
|
end
|
202
215
|
|
203
|
-
|
216
|
+
private
|
204
217
|
|
205
|
-
def translate_offset(byte_offset)
|
218
|
+
def translate_offset(byte_offset)
|
206
219
|
return nil if byte_offset.nil?
|
207
|
-
return 0 if @wrapped_string ==
|
220
|
+
return 0 if @wrapped_string == ""
|
208
221
|
|
209
222
|
begin
|
210
|
-
@wrapped_string.byteslice(0...byte_offset).unpack(
|
223
|
+
@wrapped_string.byteslice(0...byte_offset).unpack("U*").length
|
211
224
|
rescue ArgumentError
|
212
225
|
byte_offset -= 1
|
213
226
|
retry
|
214
227
|
end
|
215
228
|
end
|
216
229
|
|
217
|
-
def chars(string)
|
230
|
+
def chars(string)
|
218
231
|
self.class.new(string)
|
219
232
|
end
|
220
233
|
end
|