rails/activesupport/test/message_verifier_test.rb
Jonathan Hefner 9fbfd8100b Unify Message{Encryptor,Verifier} serializer config
In #42843 and #42846, several config settings were added to control the
default serializer for `MessageEncryptor` and `MessageVerifier`, and to
provide a migration path from a default `Marshal` serializer to a
default `JSON` serializer:

* `config.active_support.default_message_encryptor_serializer`
  * Supports `:marshal`, `:hybrid`, or `:json`.
* `config.active_support.default_message_verifier_serializer`
  * Supports `:marshal`, `:hybrid`, or `:json`.
* `config.active_support.fallback_to_marshal_deserialization`
  * Affects `:hybrid` for both `MessageEncryptor` and `MessageVerifier`.
* `config.active_support.use_marshal_serialization`
  * Affects `:hybrid` for both `MessageEncryptor` and `MessageVerifier`.

This commit unifies those config settings into a single setting,
`config.active_support.message_serializer`, which supports `:marshal`,
`:json_allow_marshal`, and `:json` values.  So, for example,

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = true
  config.active_support.use_marshal_serialization = false
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :json_allow_marshal
  ```

and

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = false
  config.active_support.use_marshal_serialization = false
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :json
  ```

This commit also replaces `ActiveSupport::JsonWithMarshalFallback` with
`ActiveSupport::Messages::SerializerWithFallback`, which implements a
generic mechanism for serializer fallback.  The `:marshal` serializer
uses this mechanism too, so

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = false
  config.active_support.use_marshal_serialization = true
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :marshal
  ```

Additionally, the logging behavior of `JsonWithMarshalFallback` has been
replaced with notifications which include the names of the intended and
actual serializers, as well as the serialized and deserialized message
data.  This provides a more targeted means of tracking serializer
fallback events.  It also allows the user to "silence" such events, if
desired, without an additional config setting.

All of these changes make it easier to add migration paths for new
serializers such as `ActiveSupport::MessagePack`.
2023-05-08 12:09:45 -05:00

117 lines
3.9 KiB
Ruby

# frozen_string_literal: true
require_relative "abstract_unit"
require "openssl"
require "active_support/time"
require "active_support/json"
require_relative "messages/message_codec_tests"
class MessageVerifierTest < ActiveSupport::TestCase
include MessageCodecTests
class JSONSerializer
def dump(value)
ActiveSupport::JSON.encode(value)
end
def load(value)
ActiveSupport::JSON.decode(value)
end
end
def setup
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
@data = { "some" => "data", "now" => Time.utc(2010) }
@secret = SecureRandom.random_bytes(32)
end
def test_valid_message
data, hash = @verifier.generate(@data).split("--")
assert_not @verifier.valid_message?(nil)
assert_not @verifier.valid_message?("")
assert_not @verifier.valid_message?("\xff") # invalid encoding
assert_not @verifier.valid_message?("#{data.reverse}--#{hash}")
assert_not @verifier.valid_message?("#{data}--#{hash.reverse}")
assert_not @verifier.valid_message?("purejunk")
end
def test_simple_round_tripping
message = @verifier.generate(@data)
assert_equal @data, @verifier.verified(message)
assert_equal @data, @verifier.verify(message)
end
def test_round_tripping_nil
message = @verifier.generate(nil)
assert_nil @verifier.verified(message)
assert_nil @verifier.verify(message)
end
def test_verified_returns_false_on_invalid_message
assert_not @verifier.verified("purejunk")
end
def test_verify_exception_on_invalid_message
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
@verifier.verify("purejunk")
end
end
test "supports URL-safe encoding" do
verifier = ActiveSupport::MessageVerifier.new(@secret, url_safe: true, serializer: JSON)
# To verify that the message payload uses a URL-safe encoding (i.e. does not
# use "+" or "/"), the unencoded bytes should have a 6-bit aligned
# occurrence of `0b111110` or `0b111111`. Also, to verify that the message
# payload is unpadded, the number of unencoded bytes should not be a
# multiple of 3.
#
# The JSON serializer adds quotes around strings, adding 1 byte before and
# 1 byte after the input string. So we choose an input string of "??",
# which is serialized as:
# 00100010 00111111 00111111 00100010
# Which is 6-bit aligned as:
# 001000 100011 111100 111111 001000 10xxxx
data = "??"
message = verifier.generate(data)
assert_equal data, verifier.verified(message)
assert_equal message, URI.encode_www_form_component(message)
assert_not_equal 0, message.rpartition("--").first.length % 4,
"Unable to assert that the message payload is unpadded, because it does not require padding"
end
def test_alternative_serialization_method
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.use_standard_json_time_format = true
verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", serializer: JSONSerializer.new)
message = verifier.generate({ :foo => 123, "bar" => Time.utc(2010) })
exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" }
assert_equal exp, verifier.verified(message)
assert_equal exp, verifier.verify(message)
ensure
ActiveSupport.use_standard_json_time_format = prev
end
def test_verify_with_parse_json_times
previous = [ ActiveSupport.parse_json_times, Time.zone ]
ActiveSupport.parse_json_times, Time.zone = true, "UTC"
assert_equal "hi", @verifier.verify(@verifier.generate("hi", expires_at: Time.now.utc + 10))
ensure
ActiveSupport.parse_json_times, Time.zone = previous
end
def test_raise_error_when_secret_is_nil
exception = assert_raise(ArgumentError) do
ActiveSupport::MessageVerifier.new(nil)
end
assert_equal "Secret should not be nil.", exception.message
end
private
def make_codec(**options)
ActiveSupport::MessageVerifier.new(@secret, **options)
end
end