Skip to content

Commit ca6e60a

Browse files
Merge pull request rails#54525 from jonathanhefner/add-rotation_coordinator-prepend
Add `{MessageEncryptors,MessageVerifiers}#prepend`
2 parents 9aadd2f + 8737f64 commit ca6e60a

File tree

4 files changed

+124
-0
lines changed

4 files changed

+124
-0
lines changed

activesupport/lib/active_support/message_encryptors.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class MessageEncryptors < Messages::RotationCoordinator
2626
# as the first rotation and <tt>transitional = true</tt>. Then, after all
2727
# servers have been updated, perform a second rolling deploy with
2828
# <tt>transitional = false</tt>.
29+
#
30+
#--
31+
# Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
2932

3033
##
3134
# :singleton-method: new
@@ -43,6 +46,9 @@ class MessageEncryptors < Messages::RotationCoordinator
4346
# end
4447
#
4548
# encryptors.rotate(base: "...")
49+
#
50+
#--
51+
# Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
4652

4753
##
4854
# :method: []
@@ -51,12 +57,18 @@ class MessageEncryptors < Messages::RotationCoordinator
5157
# Returns a MessageEncryptor configured with a secret derived from the
5258
# given +salt+, and options from #rotate. MessageEncryptor instances will
5359
# be memoized, so the same +salt+ will return the same instance.
60+
#
61+
#--
62+
# Implemented by ActiveSupport::Messages::RotationCoordinator#[]
5463

5564
##
5665
# :method: []=
5766
# :call-seq: []=(salt, encryptor)
5867
#
5968
# Overrides a MessageEncryptor instance associated with a given +salt+.
69+
#
70+
#--
71+
# Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
6072

6173
##
6274
# :method: rotate
@@ -106,18 +118,55 @@ class MessageEncryptors < Messages::RotationCoordinator
106118
#
107119
# # Uses `serializer: Marshal, url_safe: false`.
108120
# encryptors[:baz]
121+
#
122+
#--
123+
# Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
124+
125+
##
126+
# :method: prepend
127+
# :call-seq:
128+
# prepend(**options)
129+
# prepend(&block)
130+
#
131+
# Just like #rotate, but prepends the given options or block to the list of
132+
# option sets.
133+
#
134+
# This can be useful when you have an already-configured +MessageEncryptors+
135+
# instance, but you want to override the way messages are encrypted.
136+
#
137+
# module ThirdParty
138+
# ENCRYPTORS = ActiveSupport::MessageEncryptors.new { ... }.
139+
# rotate(serializer: Marshal, url_safe: true).
140+
# rotate(serializer: Marshal, url_safe: false)
141+
# end
142+
#
143+
# ThirdParty.ENCRYPTORS.prepend(serializer: JSON, url_safe: true)
144+
#
145+
# # Uses `serializer: JSON, url_safe: true`.
146+
# # Falls back to `serializer: Marshal, url_safe: true` or
147+
# # `serializer: Marshal, url_safe: false`.
148+
# ThirdParty.ENCRYPTORS[:foo]
149+
#
150+
#--
151+
# Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
109152

110153
##
111154
# :method: rotate_defaults
112155
# :call-seq: rotate_defaults
113156
#
114157
# Invokes #rotate with the default options.
158+
#
159+
#--
160+
# Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
115161

116162
##
117163
# :method: clear_rotations
118164
# :call-seq: clear_rotations
119165
#
120166
# Clears the list of option sets.
167+
#
168+
#--
169+
# Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
121170

122171
##
123172
# :method: on_rotation
@@ -129,6 +178,9 @@ class MessageEncryptors < Messages::RotationCoordinator
129178
# For example, this callback could log each time it is called, and thus
130179
# indicate whether old option sets are still in use or can be removed from
131180
# rotation.
181+
#
182+
#--
183+
# Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
132184

133185
##
134186
private

activesupport/lib/active_support/message_verifiers.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class MessageVerifiers < Messages::RotationCoordinator
2626
# as the first rotation and <tt>transitional = true</tt>. Then, after all
2727
# servers have been updated, perform a second rolling deploy with
2828
# <tt>transitional = false</tt>.
29+
#
30+
#--
31+
# Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
2932

3033
##
3134
# :singleton-method: new
@@ -42,6 +45,9 @@ class MessageVerifiers < Messages::RotationCoordinator
4245
# end
4346
#
4447
# verifiers.rotate(base: "...")
48+
#
49+
#--
50+
# Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
4551

4652
##
4753
# :method: []
@@ -50,12 +56,18 @@ class MessageVerifiers < Messages::RotationCoordinator
5056
# Returns a MessageVerifier configured with a secret derived from the
5157
# given +salt+, and options from #rotate. MessageVerifier instances will
5258
# be memoized, so the same +salt+ will return the same instance.
59+
#
60+
#--
61+
# Implemented by ActiveSupport::Messages::RotationCoordinator#[]
5362

5463
##
5564
# :method: []=
5665
# :call-seq: []=(salt, verifier)
5766
#
5867
# Overrides a MessageVerifier instance associated with a given +salt+.
68+
#
69+
#--
70+
# Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
5971

6072
##
6173
# :method: rotate
@@ -104,18 +116,55 @@ class MessageVerifiers < Messages::RotationCoordinator
104116
#
105117
# # Uses `serializer: Marshal, url_safe: false`.
106118
# verifiers[:baz]
119+
#
120+
#--
121+
# Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
122+
123+
##
124+
# :method: prepend
125+
# :call-seq:
126+
# prepend(**options)
127+
# prepend(&block)
128+
#
129+
# Just like #rotate, but prepends the given options or block to the list of
130+
# option sets.
131+
#
132+
# This can be useful when you have an already-configured +MessageVerifiers+
133+
# instance, but you want to override the way messages are signed.
134+
#
135+
# module ThirdParty
136+
# VERIFIERS = ActiveSupport::MessageVerifiers.new { ... }.
137+
# rotate(serializer: Marshal, url_safe: true).
138+
# rotate(serializer: Marshal, url_safe: false)
139+
# end
140+
#
141+
# ThirdParty.VERIFIERS.prepend(serializer: JSON, url_safe: true)
142+
#
143+
# # Uses `serializer: JSON, url_safe: true`.
144+
# # Falls back to `serializer: Marshal, url_safe: true` or
145+
# # `serializer: Marshal, url_safe: false`.
146+
# ThirdParty.VERIFIERS[:foo]
147+
#
148+
#--
149+
# Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
107150

108151
##
109152
# :method: rotate_defaults
110153
# :call-seq: rotate_defaults
111154
#
112155
# Invokes #rotate with the default options.
156+
#
157+
#--
158+
# Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
113159

114160
##
115161
# :method: clear_rotations
116162
# :call-seq: clear_rotations
117163
#
118164
# Clears the list of option sets.
165+
#
166+
#--
167+
# Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
119168

120169
##
121170
# :method: on_rotation
@@ -127,6 +176,9 @@ class MessageVerifiers < Messages::RotationCoordinator
127176
# For example, this callback could log each time it is called, and thus
128177
# indicate whether old option sets are still in use or can be removed from
129178
# rotation.
179+
#
180+
#--
181+
# Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
130182

131183
##
132184
private

activesupport/lib/active_support/messages/rotation_coordinator.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ def rotate(**options, &block)
3232
self
3333
end
3434

35+
def prepend(**options, &block)
36+
raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty?
37+
changing_configuration!
38+
39+
@rotate_options.unshift(block || options)
40+
41+
self
42+
end
43+
3544
def rotate_defaults
3645
rotate()
3746
end

activesupport/test/rotation_coordinator_tests.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ module RotationCoordinatorTests
3434
assert_nil roundtrip("message", codec, obsolete_codec)
3535
end
3636

37+
test "prioritizes prepended rotations" do
38+
@coordinator.prepend(digest: "MD5")
39+
codec = @coordinator["salt"]
40+
41+
old_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
42+
assert_equal "message", roundtrip("message", codec, old_codec)
43+
44+
new_codec = (make_coordinator.rotate_defaults)["salt"]
45+
assert_equal "message", roundtrip("message", new_codec, codec)
46+
end
47+
3748
test "raises when building a codec and no rotations are configured" do
3849
assert_raises { make_coordinator["salt"] }
3950
end

0 commit comments

Comments
 (0)