Skip to content

Commit ea9f010

Browse files
committed
Revert "Revert "Merge pull request rails#42843 from buckley-w-david/message-verifier-default-serializer""
This reverts commit fd4e63c.
1 parent fd4e63c commit ea9f010

File tree

9 files changed

+399
-40
lines changed

9 files changed

+399
-40
lines changed

activestorage/app/controllers/active_storage/disk_controller.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ def named_disk_service(name)
4242
end
4343

4444
def decode_verified_key
45-
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
45+
key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
46+
key&.deep_symbolize_keys
4647
end
4748

4849
def decode_verified_token
49-
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
50+
token = ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
51+
token&.deep_symbolize_keys
5052
end
5153

5254
def acceptable_content?(token)

activesupport/lib/active_support/message_verifier.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ module ActiveSupport
7272
#
7373
# === Alternative serializers
7474
#
75-
# By default MessageVerifier uses Marshal to serialize the message. If you want to use
75+
# By default MessageVerifier uses JSON to serialize the message. If you want to use
7676
# another serialization method, you can set the serializer in the options
7777
# hash upon initialization:
7878
#
@@ -115,11 +115,20 @@ class InvalidSignature < StandardError; end
115115
SEPARATOR = "--" # :nodoc:
116116
SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
117117

118+
cattr_accessor :default_message_verifier_serializer, instance_accessor: false, default: :marshal
119+
118120
def initialize(secret, digest: nil, serializer: nil)
119121
raise ArgumentError, "Secret should not be nil." unless secret
120122
@secret = secret
121123
@digest = digest&.to_s || "SHA1"
122-
@serializer = serializer || Marshal
124+
@serializer = serializer ||
125+
if @@default_message_verifier_serializer.equal?(:marshal)
126+
Marshal
127+
elsif @@default_message_verifier_serializer.equal?(:hybrid)
128+
JsonWithMarshalFallback
129+
elsif @@default_message_verifier_serializer.equal?(:json)
130+
JSON
131+
end
123132
end
124133

125134
# Checks if a signed message could have been generated by signing an object

activesupport/lib/active_support/railtie.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ class Railtie < Rails::Railtie # :nodoc:
167167
end
168168
end
169169

170+
initializer "active_support.set_default_message_verifier_serializer" do |app|
171+
config.after_initialize do
172+
unless app.config.active_support.default_message_verifier_serializer.nil?
173+
ActiveSupport::MessageVerifier.default_message_verifier_serializer =
174+
app.config.active_support.default_message_verifier_serializer
175+
end
176+
end
177+
end
178+
170179
initializer "active_support.set_marshal_serialization" do |app|
171180
config.after_initialize do
172181
unless app.config.active_support.use_marshal_serialization.nil?

activesupport/test/message_verifier_test.rb

Lines changed: 165 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def load(value)
1919

2020
def setup
2121
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
22-
@data = { some: "data", now: Time.utc(2010) }
22+
@data = { "some" => "data", "now" => Time.utc(2010) }
2323
@secret = SecureRandom.random_bytes(32)
2424
end
2525

@@ -70,38 +70,13 @@ def test_verify_with_parse_json_times
7070
ActiveSupport.parse_json_times, Time.zone = previous
7171
end
7272

73-
def test_raise_error_when_argument_class_is_not_loaded
74-
# To generate the valid message below:
75-
#
76-
# AutoloadClass = Struct.new(:foo)
77-
# valid_message = @verifier.generate(foo: AutoloadClass.new('foo'))
78-
#
79-
valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
80-
exception = assert_raise(ArgumentError, NameError) do
81-
@verifier.verified(valid_message)
82-
end
83-
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
84-
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
85-
exception = assert_raise(ArgumentError, NameError) do
86-
@verifier.verify(valid_message)
87-
end
88-
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
89-
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
90-
end
91-
9273
def test_raise_error_when_secret_is_nil
9374
exception = assert_raise(ArgumentError) do
9475
ActiveSupport::MessageVerifier.new(nil)
9576
end
9677
assert_equal "Secret should not be nil.", exception.message
9778
end
9879

99-
def test_backward_compatibility_messages_signed_without_metadata
100-
signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96"
101-
assert_equal @data, @verifier.verify(signed_message)
102-
end
103-
104-
10580
def test_rotating_secret
10681
old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA1").generate("old")
10782

@@ -124,6 +99,35 @@ def test_multiple_rotations
12499
assert_equal "older", verifier.verified(older_message)
125100
end
126101

102+
def test_rotations_with_metadata
103+
old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation)
104+
105+
verifier = ActiveSupport::MessageVerifier.new(@secret)
106+
verifier.rotate "old"
107+
108+
assert_equal "old", verifier.verified(old_message, purpose: :rotation)
109+
end
110+
end
111+
112+
class DefaultMarshalSerializerMessageVerifierTest < MessageVerifierTest
113+
def setup
114+
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
115+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :marshal
116+
117+
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
118+
@data = { some: "data", now: Time.utc(2010) }
119+
@secret = SecureRandom.random_bytes(32)
120+
end
121+
122+
def teardown
123+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
124+
end
125+
126+
def test_backward_compatibility_messages_signed_without_metadata
127+
signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96"
128+
assert_equal @data, @verifier.verify(signed_message)
129+
end
130+
127131
def test_on_rotation_is_called_and_verified_returns_message
128132
older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate({ encoded: "message" })
129133

@@ -138,13 +142,127 @@ def test_on_rotation_is_called_and_verified_returns_message
138142
assert rotated
139143
end
140144

141-
def test_rotations_with_metadata
142-
old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation)
145+
def test_raise_error_when_argument_class_is_not_loaded
146+
# To generate the valid message below:
147+
#
148+
# AutoloadClass = Struct.new(:foo)
149+
# valid_message = @verifier.generate(foo: AutoloadClass.new('foo'))
150+
#
151+
valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
152+
exception = assert_raise(ArgumentError, NameError) do
153+
@verifier.verified(valid_message)
154+
end
155+
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
156+
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
157+
exception = assert_raise(ArgumentError, NameError) do
158+
@verifier.verify(valid_message)
159+
end
160+
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
161+
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
162+
end
163+
end
143164

144-
verifier = ActiveSupport::MessageVerifier.new(@secret)
145-
verifier.rotate "old"
165+
class MarshalSerializeAndFallbackMessageVerifierTest < DefaultMarshalSerializerMessageVerifierTest
166+
def setup
167+
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
168+
@default_use_marshal = ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
169+
@default_fallback = ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
170+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :hybrid
171+
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = true
172+
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = true
146173

147-
assert_equal "old", verifier.verified(old_message, purpose: :rotation)
174+
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
175+
@data = { some: "data", now: Time.utc(2010) }
176+
@secret = SecureRandom.random_bytes(32)
177+
end
178+
179+
def teardown
180+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
181+
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = @default_use_marshal
182+
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = @default_fallback
183+
end
184+
end
185+
186+
class JsonSerializeMarshalFallbackMessageVerifierTest < MessageVerifierTest
187+
def setup
188+
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
189+
@default_use_marshal = ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
190+
@default_fallback = ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
191+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :hybrid
192+
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = false
193+
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = true
194+
195+
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
196+
@data = { "some" => "data", "now" => Time.utc(2010) }
197+
@secret = SecureRandom.random_bytes(32)
198+
end
199+
200+
def teardown
201+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
202+
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = @default_use_marshal
203+
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = @default_fallback
204+
end
205+
206+
def test_on_rotation_is_called_and_verified_returns_message
207+
older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate({ encoded: "message" })
208+
209+
verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512")
210+
verifier.rotate "old", digest: "SHA256"
211+
verifier.rotate "older", digest: "SHA1"
212+
213+
rotated = false
214+
message = verifier.verified(older_message, on_rotation: proc { rotated = true })
215+
216+
assert_equal({ "encoded" => "message" }, message)
217+
assert rotated
218+
end
219+
220+
def test_backward_compatibility_messages_signed_marshal_serialized
221+
marshal_serialized_signed_message = "BAh7B0kiCXNvbWUGOgZFVEkiCWRhdGEGOwBUSSIIbm93BjsAVEl1OglUaW1lDSCAG8AAAAAABjoJem9uZUkiCFVUQwY7AEY=--ae7480422168507f4a8aec6b1d68bfdfd5c6ef48"
222+
assert_equal @data, @verifier.verify(marshal_serialized_signed_message)
223+
end
224+
end
225+
226+
class JsonSerializeAndNoFallbackMessageVerifierTest < JsonSerializeMarshalFallbackMessageVerifierTest
227+
def setup
228+
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
229+
@default_use_marshal = ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
230+
@default_fallback = ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
231+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :hybrid
232+
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = false
233+
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = false
234+
235+
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
236+
@data = { "some" => "data", "now" => Time.utc(2010) }
237+
@secret = SecureRandom.random_bytes(32)
238+
end
239+
240+
def teardown
241+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
242+
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = @default_use_marshal
243+
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = @default_fallback
244+
end
245+
246+
def test_backward_compatibility_messages_signed_marshal_serialized
247+
marshal_serialized_signed_message = "BAh7B0kiCXNvbWUGOgZFVEkiCWRhdGEGOwBUSSIIbm93BjsAVEl1OglUaW1lDSCAG8AAAAAABjoJem9uZUkiCFVUQwY7AEY=--ae7480422168507f4a8aec6b1d68bfdfd5c6ef48"
248+
assert_raise(JSON::ParserError) do
249+
@verifier.verify(marshal_serialized_signed_message)
250+
end
251+
end
252+
end
253+
254+
class DefaultJsonSerializerMessageVerifierTest < JsonSerializeAndNoFallbackMessageVerifierTest
255+
def setup
256+
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
257+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :json
258+
259+
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
260+
@data = { "some" => "data", "now" => Time.utc(2010) }
261+
@secret = SecureRandom.random_bytes(32)
262+
end
263+
264+
def teardown
265+
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
148266
end
149267
end
150268

@@ -199,7 +317,22 @@ def verifier_options
199317
end
200318
end
201319

202-
class MessageVerifierMetadataJSONTest < MessageVerifierMetadataTest
320+
class MessageVerifierMetadataJsonWithMarshalFallbackTest < MessageVerifierMetadataTest
321+
private
322+
def verifier_options
323+
{ serializer: ActiveSupport::JsonWithMarshalFallback }
324+
end
325+
end
326+
327+
class MessageVerifierMetadataJsonTest < MessageVerifierMetadataTest
328+
private
329+
def verifier_options
330+
{ serializer: JSON }
331+
end
332+
end
333+
334+
335+
class MessageVerifierMetadataCustomJSONTest < MessageVerifierMetadataTest
203336
private
204337
def verifier_options
205338
{ serializer: MessageVerifierTest::JSONSerializer.new }

guides/source/configuring.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Below are the default values associated with each target version. In cases of co
6363
- [`config.action_dispatch.default_headers`](#config-action-dispatch-default-headers): `{ "X-Frame-Options" => "SAMEORIGIN", "X-XSS-Protection" => "0", "X-Content-Type-Options" => "nosniff", "X-Permitted-Cross-Domain-Policies" => "none", "Referrer-Policy" => "strict-origin-when-cross-origin" }`
6464
- [`config.add_autoload_paths_to_load_path`](#config-add-autoload-paths-to-load-path): `false`
6565
- [`config.active_support.default_message_encryptor_serializer`](#config-active-support-default-message-encryptor-serializer): `:json`
66+
- [`config.active_support.default_message_verifier_serializer`](#config-active-support-default-message-verifier-serializer): `:json`
6667

6768
#### Default Values for Target Version 7.0
6869

@@ -1909,6 +1910,19 @@ Used to help migrate apps from `Marshal` to `JSON` as the default serializer for
19091910
19101911
Defaults to `true`.
19111912
1913+
#### `config.active_support.default_message_verifier_serializer`
1914+
1915+
Specifies what serializer the `MessageVerifier` class will use by default.
1916+
1917+
Options are `:json`, `:hybrid`, and `:marshal`. `:hybrid` uses the `JsonWithMarshalFallback` class.
1918+
1919+
The default value depends on the `config.load_defaults` target version:
1920+
1921+
| Starting with version | The default value is |
1922+
| --------------------- | -------------------- |
1923+
| (original) | `:marshal` |
1924+
| 7.1 | `:json` |
1925+
19121926
### Configuring Active Job
19131927
19141928
`config.active_job` provides the following configuration options:

0 commit comments

Comments
 (0)