Revert "Revert "Merge pull request #42843 from buckley-w-david/message-verifier-default-serializer""
This reverts commit fd4e63cc28
.
This commit is contained in:
parent
fd4e63cc28
commit
ea9f0103fd
|
@ -42,11 +42,13 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_verified_key
|
def decode_verified_key
|
||||||
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
||||||
|
key&.deep_symbolize_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_verified_token
|
def decode_verified_token
|
||||||
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
token = ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
||||||
|
token&.deep_symbolize_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def acceptable_content?(token)
|
def acceptable_content?(token)
|
||||||
|
|
|
@ -72,7 +72,7 @@ module ActiveSupport
|
||||||
#
|
#
|
||||||
# === Alternative serializers
|
# === Alternative serializers
|
||||||
#
|
#
|
||||||
# By default MessageVerifier uses Marshal to serialize the message. If you want to use
|
# By default MessageVerifier uses JSON to serialize the message. If you want to use
|
||||||
# another serialization method, you can set the serializer in the options
|
# another serialization method, you can set the serializer in the options
|
||||||
# hash upon initialization:
|
# hash upon initialization:
|
||||||
#
|
#
|
||||||
|
@ -115,11 +115,20 @@ module ActiveSupport
|
||||||
SEPARATOR = "--" # :nodoc:
|
SEPARATOR = "--" # :nodoc:
|
||||||
SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
|
SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
|
||||||
|
|
||||||
|
cattr_accessor :default_message_verifier_serializer, instance_accessor: false, default: :marshal
|
||||||
|
|
||||||
def initialize(secret, digest: nil, serializer: nil)
|
def initialize(secret, digest: nil, serializer: nil)
|
||||||
raise ArgumentError, "Secret should not be nil." unless secret
|
raise ArgumentError, "Secret should not be nil." unless secret
|
||||||
@secret = secret
|
@secret = secret
|
||||||
@digest = digest&.to_s || "SHA1"
|
@digest = digest&.to_s || "SHA1"
|
||||||
@serializer = serializer || Marshal
|
@serializer = serializer ||
|
||||||
|
if @@default_message_verifier_serializer.equal?(:marshal)
|
||||||
|
Marshal
|
||||||
|
elsif @@default_message_verifier_serializer.equal?(:hybrid)
|
||||||
|
JsonWithMarshalFallback
|
||||||
|
elsif @@default_message_verifier_serializer.equal?(:json)
|
||||||
|
JSON
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if a signed message could have been generated by signing an object
|
# Checks if a signed message could have been generated by signing an object
|
||||||
|
|
|
@ -167,6 +167,15 @@ module ActiveSupport
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
initializer "active_support.set_default_message_verifier_serializer" do |app|
|
||||||
|
config.after_initialize do
|
||||||
|
unless app.config.active_support.default_message_verifier_serializer.nil?
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer =
|
||||||
|
app.config.active_support.default_message_verifier_serializer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
initializer "active_support.set_marshal_serialization" do |app|
|
initializer "active_support.set_marshal_serialization" do |app|
|
||||||
config.after_initialize do
|
config.after_initialize do
|
||||||
unless app.config.active_support.use_marshal_serialization.nil?
|
unless app.config.active_support.use_marshal_serialization.nil?
|
||||||
|
|
|
@ -19,7 +19,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
@data = { some: "data", now: Time.utc(2010) }
|
@data = { "some" => "data", "now" => Time.utc(2010) }
|
||||||
@secret = SecureRandom.random_bytes(32)
|
@secret = SecureRandom.random_bytes(32)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -70,25 +70,6 @@ class MessageVerifierTest < ActiveSupport::TestCase
|
||||||
ActiveSupport.parse_json_times, Time.zone = previous
|
ActiveSupport.parse_json_times, Time.zone = previous
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_raise_error_when_argument_class_is_not_loaded
|
|
||||||
# To generate the valid message below:
|
|
||||||
#
|
|
||||||
# AutoloadClass = Struct.new(:foo)
|
|
||||||
# valid_message = @verifier.generate(foo: AutoloadClass.new('foo'))
|
|
||||||
#
|
|
||||||
valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
|
|
||||||
exception = assert_raise(ArgumentError, NameError) do
|
|
||||||
@verifier.verified(valid_message)
|
|
||||||
end
|
|
||||||
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
|
|
||||||
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
|
|
||||||
exception = assert_raise(ArgumentError, NameError) do
|
|
||||||
@verifier.verify(valid_message)
|
|
||||||
end
|
|
||||||
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
|
|
||||||
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raise_error_when_secret_is_nil
|
def test_raise_error_when_secret_is_nil
|
||||||
exception = assert_raise(ArgumentError) do
|
exception = assert_raise(ArgumentError) do
|
||||||
ActiveSupport::MessageVerifier.new(nil)
|
ActiveSupport::MessageVerifier.new(nil)
|
||||||
|
@ -96,12 +77,6 @@ class MessageVerifierTest < ActiveSupport::TestCase
|
||||||
assert_equal "Secret should not be nil.", exception.message
|
assert_equal "Secret should not be nil.", exception.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_backward_compatibility_messages_signed_without_metadata
|
|
||||||
signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96"
|
|
||||||
assert_equal @data, @verifier.verify(signed_message)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def test_rotating_secret
|
def test_rotating_secret
|
||||||
old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA1").generate("old")
|
old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA1").generate("old")
|
||||||
|
|
||||||
|
@ -124,6 +99,35 @@ class MessageVerifierTest < ActiveSupport::TestCase
|
||||||
assert_equal "older", verifier.verified(older_message)
|
assert_equal "older", verifier.verified(older_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_rotations_with_metadata
|
||||||
|
old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation)
|
||||||
|
|
||||||
|
verifier = ActiveSupport::MessageVerifier.new(@secret)
|
||||||
|
verifier.rotate "old"
|
||||||
|
|
||||||
|
assert_equal "old", verifier.verified(old_message, purpose: :rotation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DefaultMarshalSerializerMessageVerifierTest < MessageVerifierTest
|
||||||
|
def setup
|
||||||
|
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :marshal
|
||||||
|
|
||||||
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
|
@data = { some: "data", now: Time.utc(2010) }
|
||||||
|
@secret = SecureRandom.random_bytes(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_backward_compatibility_messages_signed_without_metadata
|
||||||
|
signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96"
|
||||||
|
assert_equal @data, @verifier.verify(signed_message)
|
||||||
|
end
|
||||||
|
|
||||||
def test_on_rotation_is_called_and_verified_returns_message
|
def test_on_rotation_is_called_and_verified_returns_message
|
||||||
older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate({ encoded: "message" })
|
older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate({ encoded: "message" })
|
||||||
|
|
||||||
|
@ -138,13 +142,127 @@ class MessageVerifierTest < ActiveSupport::TestCase
|
||||||
assert rotated
|
assert rotated
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_rotations_with_metadata
|
def test_raise_error_when_argument_class_is_not_loaded
|
||||||
old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation)
|
# To generate the valid message below:
|
||||||
|
#
|
||||||
|
# AutoloadClass = Struct.new(:foo)
|
||||||
|
# valid_message = @verifier.generate(foo: AutoloadClass.new('foo'))
|
||||||
|
#
|
||||||
|
valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
|
||||||
|
exception = assert_raise(ArgumentError, NameError) do
|
||||||
|
@verifier.verified(valid_message)
|
||||||
|
end
|
||||||
|
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
|
||||||
|
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
|
||||||
|
exception = assert_raise(ArgumentError, NameError) do
|
||||||
|
@verifier.verify(valid_message)
|
||||||
|
end
|
||||||
|
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
|
||||||
|
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
verifier = ActiveSupport::MessageVerifier.new(@secret)
|
class MarshalSerializeAndFallbackMessageVerifierTest < DefaultMarshalSerializerMessageVerifierTest
|
||||||
verifier.rotate "old"
|
def setup
|
||||||
|
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
@default_use_marshal = ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
|
||||||
|
@default_fallback = ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :hybrid
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = true
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = true
|
||||||
|
|
||||||
assert_equal "old", verifier.verified(old_message, purpose: :rotation)
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
|
@data = { some: "data", now: Time.utc(2010) }
|
||||||
|
@secret = SecureRandom.random_bytes(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = @default_use_marshal
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = @default_fallback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class JsonSerializeMarshalFallbackMessageVerifierTest < MessageVerifierTest
|
||||||
|
def setup
|
||||||
|
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
@default_use_marshal = ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
|
||||||
|
@default_fallback = ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :hybrid
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = false
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = true
|
||||||
|
|
||||||
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
|
@data = { "some" => "data", "now" => Time.utc(2010) }
|
||||||
|
@secret = SecureRandom.random_bytes(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = @default_use_marshal
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = @default_fallback
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_on_rotation_is_called_and_verified_returns_message
|
||||||
|
older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate({ encoded: "message" })
|
||||||
|
|
||||||
|
verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512")
|
||||||
|
verifier.rotate "old", digest: "SHA256"
|
||||||
|
verifier.rotate "older", digest: "SHA1"
|
||||||
|
|
||||||
|
rotated = false
|
||||||
|
message = verifier.verified(older_message, on_rotation: proc { rotated = true })
|
||||||
|
|
||||||
|
assert_equal({ "encoded" => "message" }, message)
|
||||||
|
assert rotated
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_backward_compatibility_messages_signed_marshal_serialized
|
||||||
|
marshal_serialized_signed_message = "BAh7B0kiCXNvbWUGOgZFVEkiCWRhdGEGOwBUSSIIbm93BjsAVEl1OglUaW1lDSCAG8AAAAAABjoJem9uZUkiCFVUQwY7AEY=--ae7480422168507f4a8aec6b1d68bfdfd5c6ef48"
|
||||||
|
assert_equal @data, @verifier.verify(marshal_serialized_signed_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class JsonSerializeAndNoFallbackMessageVerifierTest < JsonSerializeMarshalFallbackMessageVerifierTest
|
||||||
|
def setup
|
||||||
|
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
@default_use_marshal = ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
|
||||||
|
@default_fallback = ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :hybrid
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = false
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = false
|
||||||
|
|
||||||
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
|
@data = { "some" => "data", "now" => Time.utc(2010) }
|
||||||
|
@secret = SecureRandom.random_bytes(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization = @default_use_marshal
|
||||||
|
ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization = @default_fallback
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_backward_compatibility_messages_signed_marshal_serialized
|
||||||
|
marshal_serialized_signed_message = "BAh7B0kiCXNvbWUGOgZFVEkiCWRhdGEGOwBUSSIIbm93BjsAVEl1OglUaW1lDSCAG8AAAAAABjoJem9uZUkiCFVUQwY7AEY=--ae7480422168507f4a8aec6b1d68bfdfd5c6ef48"
|
||||||
|
assert_raise(JSON::ParserError) do
|
||||||
|
@verifier.verify(marshal_serialized_signed_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DefaultJsonSerializerMessageVerifierTest < JsonSerializeAndNoFallbackMessageVerifierTest
|
||||||
|
def setup
|
||||||
|
@default_verifier = ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = :json
|
||||||
|
|
||||||
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
|
@data = { "some" => "data", "now" => Time.utc(2010) }
|
||||||
|
@secret = SecureRandom.random_bytes(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveSupport::MessageVerifier.default_message_verifier_serializer = @default_verifier
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -199,7 +317,22 @@ class MessageVerifierMetadataMarshalTest < MessageVerifierMetadataTest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class MessageVerifierMetadataJSONTest < MessageVerifierMetadataTest
|
class MessageVerifierMetadataJsonWithMarshalFallbackTest < MessageVerifierMetadataTest
|
||||||
|
private
|
||||||
|
def verifier_options
|
||||||
|
{ serializer: ActiveSupport::JsonWithMarshalFallback }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MessageVerifierMetadataJsonTest < MessageVerifierMetadataTest
|
||||||
|
private
|
||||||
|
def verifier_options
|
||||||
|
{ serializer: JSON }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class MessageVerifierMetadataCustomJSONTest < MessageVerifierMetadataTest
|
||||||
private
|
private
|
||||||
def verifier_options
|
def verifier_options
|
||||||
{ serializer: MessageVerifierTest::JSONSerializer.new }
|
{ serializer: MessageVerifierTest::JSONSerializer.new }
|
||||||
|
|
|
@ -63,6 +63,7 @@ Below are the default values associated with each target version. In cases of co
|
||||||
- [`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" }`
|
- [`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" }`
|
||||||
- [`config.add_autoload_paths_to_load_path`](#config-add-autoload-paths-to-load-path): `false`
|
- [`config.add_autoload_paths_to_load_path`](#config-add-autoload-paths-to-load-path): `false`
|
||||||
- [`config.active_support.default_message_encryptor_serializer`](#config-active-support-default-message-encryptor-serializer): `:json`
|
- [`config.active_support.default_message_encryptor_serializer`](#config-active-support-default-message-encryptor-serializer): `:json`
|
||||||
|
- [`config.active_support.default_message_verifier_serializer`](#config-active-support-default-message-verifier-serializer): `:json`
|
||||||
|
|
||||||
#### Default Values for Target Version 7.0
|
#### Default Values for Target Version 7.0
|
||||||
|
|
||||||
|
@ -1909,6 +1910,19 @@ Used to help migrate apps from `Marshal` to `JSON` as the default serializer for
|
||||||
|
|
||||||
Defaults to `true`.
|
Defaults to `true`.
|
||||||
|
|
||||||
|
#### `config.active_support.default_message_verifier_serializer`
|
||||||
|
|
||||||
|
Specifies what serializer the `MessageVerifier` class will use by default.
|
||||||
|
|
||||||
|
Options are `:json`, `:hybrid`, and `:marshal`. `:hybrid` uses the `JsonWithMarshalFallback` class.
|
||||||
|
|
||||||
|
The default value depends on the `config.load_defaults` target version:
|
||||||
|
|
||||||
|
| Starting with version | The default value is |
|
||||||
|
| --------------------- | -------------------- |
|
||||||
|
| (original) | `:marshal` |
|
||||||
|
| 7.1 | `:json` |
|
||||||
|
|
||||||
### Configuring Active Job
|
### Configuring Active Job
|
||||||
|
|
||||||
`config.active_job` provides the following configuration options:
|
`config.active_job` provides the following configuration options:
|
||||||
|
|
|
@ -169,6 +169,85 @@ Alternatively, you could load defaults for 7.1
|
||||||
config.load_defaults 7.1
|
config.load_defaults 7.1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### New `ActiveSupport::MessageVerifier` default serializer
|
||||||
|
|
||||||
|
As of Rails 7.1, the default serializer in use by the `MessageVerifier` is `JSON`.
|
||||||
|
This offers a more secure alternative to the current default serializer.
|
||||||
|
|
||||||
|
The `MessageVerifier` offers the ability to migrate the default serializer from `Marshal` to `JSON`.
|
||||||
|
|
||||||
|
If you would like to ignore this change in existing applications, set the following: `config.active_support.default_message_verifier_serializer = :marshal`.
|
||||||
|
|
||||||
|
In order to roll out the new default when upgrading from `7.0` to `7.1`, there are three configuration variables to keep in mind.
|
||||||
|
```
|
||||||
|
config.active_support.default_verifier_serializer
|
||||||
|
config.active_support.fallback_to_marshal_deserialization
|
||||||
|
config.active_support.use_marshal_serialization
|
||||||
|
```
|
||||||
|
|
||||||
|
`default_message_verifier_serializer` defaults to `:json` as of `7.1` but it offers both a `:hybrid` and `:marshal` option.
|
||||||
|
|
||||||
|
In order to migrate an older deployment to `:json`, first ensure that the `default_message_verifier_serializer` is set to `:marshal`.
|
||||||
|
```ruby
|
||||||
|
# config/application.rb
|
||||||
|
config.load_defaults 7.0
|
||||||
|
config.active_support.default_message_verifier_serializer = :marshal
|
||||||
|
```
|
||||||
|
|
||||||
|
Once this is deployed on all Rails processes, set `default_message_verifier_serializer` to `:hybrid` to begin using the
|
||||||
|
`ActiveSupport::JsonWithMarshalFallback` class as the serializer. The defaults for this class are to use `Marshal`
|
||||||
|
as the serializer and to allow the deserialisation of both `Marshal` and `JSON` serialized payloads.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
config.load_defaults 7.0
|
||||||
|
config.active_support.default_message_verifier_serializer = :hybrid
|
||||||
|
```
|
||||||
|
|
||||||
|
Once this is deployed on all Rails processes, set the following configuration options in order to stop the
|
||||||
|
`ActiveSupport::JsonWithMarshalFallback` class from using `Marshal` to serialize new payloads.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# config/application.rb
|
||||||
|
config.load_defaults 7.0
|
||||||
|
config.active_support.default_message_verifier_serializer = :hybrid
|
||||||
|
config.active_support.use_marshal_serialization = false
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow this configuration to run on all processes for a considerable amount of time.
|
||||||
|
`ActiveSupport::JsonWithMarshalFallback` logs the following each time the `Marshal` fallback
|
||||||
|
is used:
|
||||||
|
```
|
||||||
|
JsonWithMarshalFallback: Marshal load fallback occurred.
|
||||||
|
```
|
||||||
|
|
||||||
|
Once those message stop appearing in your logs and you're confident that all `MessageVerifier`
|
||||||
|
payloads in transit are `JSON` serialized, the following configuration options will disable the
|
||||||
|
Marshal fallback in `ActiveSupport::JsonWithMarshalFallback`.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# config/application.rb
|
||||||
|
config.load_defaults 7.0
|
||||||
|
config.active_support.default_message_verifier_serializer = :hybrid
|
||||||
|
config.active_support.use_marshal_serialization = false
|
||||||
|
config.active_support.fallback_to_marshal_deserialization = false
|
||||||
|
```
|
||||||
|
|
||||||
|
If all goes well, you should now be safe to migrate the Message Verifier from
|
||||||
|
`ActiveSupport::JsonWithMarshalFallback` to `ActiveSupport::JSON`.
|
||||||
|
To do so, simply swap the `:hybrid` serializer for the `:json` serializer.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# config/application.rb
|
||||||
|
config.load_defaults 7.0
|
||||||
|
config.active_support.default_message_verifier_serializer = :json
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you could load defaults for 7.1
|
||||||
|
```ruby
|
||||||
|
# config/application.rb
|
||||||
|
config.load_defaults 7.1
|
||||||
|
```
|
||||||
|
|
||||||
Upgrading from Rails 6.1 to Rails 7.0
|
Upgrading from Rails 6.1 to Rails 7.0
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -270,6 +270,7 @@ module Rails
|
||||||
|
|
||||||
if respond_to?(:active_support)
|
if respond_to?(:active_support)
|
||||||
active_support.default_message_encryptor_serializer = :json
|
active_support.default_message_encryptor_serializer = :json
|
||||||
|
active_support.default_message_verifier_serializer = :json
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise "Unknown version #{target_version.to_s.inspect}"
|
raise "Unknown version #{target_version.to_s.inspect}"
|
||||||
|
|
|
@ -3189,6 +3189,14 @@ module ApplicationTests
|
||||||
assert_equal true, ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
|
assert_equal true, ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization is true by default for upgraded apps" do
|
||||||
|
remove_from_config '.*config\.load_defaults.*\n'
|
||||||
|
|
||||||
|
app "development"
|
||||||
|
|
||||||
|
assert_equal true, ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization
|
||||||
|
end
|
||||||
|
|
||||||
test "ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization can be configured via config.active_support.fallback_to_marshal_deserialization" do
|
test "ActiveSupport::JsonWithMarshalFallback.fallback_to_marshal_deserialization can be configured via config.active_support.fallback_to_marshal_deserialization" do
|
||||||
remove_from_config '.*config\.load_defaults.*\n'
|
remove_from_config '.*config\.load_defaults.*\n'
|
||||||
|
|
||||||
|
@ -3207,6 +3215,14 @@ module ApplicationTests
|
||||||
assert_equal true, ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
|
assert_equal true, ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization is true by default for upgraded apps" do
|
||||||
|
remove_from_config '.*config\.load_defaults.*\n'
|
||||||
|
|
||||||
|
app "development"
|
||||||
|
|
||||||
|
assert_equal true, ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization
|
||||||
|
end
|
||||||
|
|
||||||
test "ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization can be configured via config.active_support.use_marshal_serialization" do
|
test "ActiveSupport::JsonWithMarshalFallback.use_marshal_serialization can be configured via config.active_support.use_marshal_serialization" do
|
||||||
remove_from_config '.*config\.load_defaults.*\n'
|
remove_from_config '.*config\.load_defaults.*\n'
|
||||||
|
|
||||||
|
@ -3246,6 +3262,32 @@ module ApplicationTests
|
||||||
assert_equal :hybrid, ActiveSupport::MessageEncryptor.default_message_encryptor_serializer
|
assert_equal :hybrid, ActiveSupport::MessageEncryptor.default_message_encryptor_serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ActiveSupport::MessageVerifier.default_message_verifier_serializer is :json by default for new apps" do
|
||||||
|
app "development"
|
||||||
|
|
||||||
|
assert_equal :json, ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ActiveSupport::MessageVerifier.default_message_verifier_serializer is :marshal by default for upgraded apps" do
|
||||||
|
remove_from_config '.*config\.load_defaults.*\n'
|
||||||
|
|
||||||
|
app "development"
|
||||||
|
|
||||||
|
assert_equal :marshal, ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ActiveSupport::MessageVerifier.default_message_verifier_serializer can be configured via config.active_support.default_message_verifier_serializer" do
|
||||||
|
remove_from_config '.*config\.load_defaults.*\n'
|
||||||
|
|
||||||
|
app_file "config/initializers/default_message_verifier_serializer.rb", <<-RUBY
|
||||||
|
Rails.application.config.active_support.default_message_verifier_serializer = :hybrid
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
app "development"
|
||||||
|
|
||||||
|
assert_equal :hybrid, ActiveSupport::MessageVerifier.default_message_verifier_serializer
|
||||||
|
end
|
||||||
|
|
||||||
test "unknown_asset_fallback is false by default" do
|
test "unknown_asset_fallback is false by default" do
|
||||||
app "development"
|
app "development"
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ module ApplicationTests
|
||||||
assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
|
assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
|
||||||
end
|
end
|
||||||
|
|
||||||
test "signed cookies with SHA512 digest and rotated out SHA256 and SHA1 digests" do
|
test "signed cookies with SHA512 digest and marshal serializer and rotated out SHA256 and SHA1 digests" do
|
||||||
app_file "config/routes.rb", <<-RUBY
|
app_file "config/routes.rb", <<-RUBY
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
get ':controller(/:action)'
|
get ':controller(/:action)'
|
||||||
|
@ -88,8 +88,8 @@ module ApplicationTests
|
||||||
sha256_secret = Rails.application.key_generator.generate_key("sha256")
|
sha256_secret = Rails.application.key_generator.generate_key("sha256")
|
||||||
|
|
||||||
::TestVerifiers = Class.new do
|
::TestVerifiers = Class.new do
|
||||||
class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1")
|
class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1", serializer: Marshal)
|
||||||
class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256")
|
class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256", serializer: Marshal)
|
||||||
end
|
end
|
||||||
|
|
||||||
config.action_dispatch.signed_cookie_digest = "SHA512"
|
config.action_dispatch.signed_cookie_digest = "SHA512"
|
||||||
|
@ -104,7 +104,77 @@ module ApplicationTests
|
||||||
|
|
||||||
require "#{app_path}/config/environment"
|
require "#{app_path}/config/environment"
|
||||||
|
|
||||||
verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512)
|
verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512, serializer: Marshal)
|
||||||
|
|
||||||
|
get "/foo/write_raw_cookie_sha1"
|
||||||
|
get "/foo/read_signed"
|
||||||
|
assert_equal "signed cookie".inspect, last_response.body
|
||||||
|
|
||||||
|
get "/foo/read_raw_cookie"
|
||||||
|
assert_equal "signed cookie", verifier_sha512.verify(last_response.body, purpose: "cookie.signed_cookie")
|
||||||
|
|
||||||
|
get "/foo/write_raw_cookie_sha256"
|
||||||
|
get "/foo/read_signed"
|
||||||
|
assert_equal "signed cookie".inspect, last_response.body
|
||||||
|
|
||||||
|
get "/foo/read_raw_cookie"
|
||||||
|
assert_equal "signed cookie", verifier_sha512.verify(last_response.body, purpose: "cookie.signed_cookie")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "signed cookies with SHA512 digest and json serializer and rotated out SHA256 and SHA1 digests" do
|
||||||
|
app_file "config/routes.rb", <<-RUBY
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
get ':controller(/:action)'
|
||||||
|
post ':controller(/:action)'
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
controller :foo, <<-RUBY
|
||||||
|
class FooController < ActionController::Base
|
||||||
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
|
def write_raw_cookie_sha1
|
||||||
|
cookies[:signed_cookie] = TestVerifiers.sha1.generate("signed cookie")
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_raw_cookie_sha256
|
||||||
|
cookies[:signed_cookie] = TestVerifiers.sha256.generate("signed cookie")
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_signed
|
||||||
|
render plain: cookies.signed[:signed_cookie].inspect
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_raw_cookie
|
||||||
|
render plain: cookies[:signed_cookie]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
add_to_config <<-RUBY
|
||||||
|
sha1_secret = Rails.application.key_generator.generate_key("sha1")
|
||||||
|
sha256_secret = Rails.application.key_generator.generate_key("sha256")
|
||||||
|
|
||||||
|
::TestVerifiers = Class.new do
|
||||||
|
class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1", serializer: JSON)
|
||||||
|
class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256", serializer: JSON)
|
||||||
|
end
|
||||||
|
|
||||||
|
config.action_dispatch.signed_cookie_digest = "SHA512"
|
||||||
|
config.action_dispatch.signed_cookie_salt = "sha512 salt"
|
||||||
|
config.action_dispatch.cookies_serializer = :json
|
||||||
|
|
||||||
|
config.action_dispatch.cookies_rotations.tap do |cookies|
|
||||||
|
cookies.rotate :signed, sha1_secret, digest: "SHA1"
|
||||||
|
cookies.rotate :signed, sha256_secret, digest: "SHA256"
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
require "#{app_path}/config/environment"
|
||||||
|
|
||||||
|
verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512, serializer: JSON)
|
||||||
|
|
||||||
get "/foo/write_raw_cookie_sha1"
|
get "/foo/write_raw_cookie_sha1"
|
||||||
get "/foo/read_signed"
|
get "/foo/read_signed"
|
||||||
|
|
Loading…
Reference in New Issue