From 7ad541f955dfa396f4a6b29700bed2e188cd9187 Mon Sep 17 00:00:00 2001 From: Logan Leger Date: Fri, 21 Nov 2014 17:52:22 -0600 Subject: Add `#verified` and `#valid_message?` to MessageVerifier This commit adds a `#verified` method to `ActiveSupport::MessageVerifier` which will return either `false` when it encounters an error or the message. `#verify` continues to raise an `InvalidSignature` exception on error. This commit also adds a convenience boolean method on `MessageVerifier` as a way to check if a message is valid without performing the decoding. --- activesupport/CHANGELOG.md | 8 +++++ .../lib/active_support/message_verifier.rb | 21 ++++++++---- activesupport/test/message_verifier_test.rb | 40 +++++++++++++--------- 3 files changed, 47 insertions(+), 22 deletions(-) (limited to 'activesupport') diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 810fc22cf5..72bdd0c509 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1 +1,9 @@ +* Added `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier` + + Previously, the only way to decode a message with `ActiveSupport::MessageVerifier` was to use `#verify`, which would raise an exception on invalid messages. Now, `#verified` will return either `false` when it encounters an error or the message. + + Previously, there was no way to check if a message's format was valid without attempting to decode it. `#valid_message?` is a boolean convenience method that checks whether the message is valid without actually decoding it. + + *Logan Leger* + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index a8a8de5672..8e5d050540 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -34,21 +34,30 @@ module ActiveSupport @serializer = options[:serializer] || Marshal end - def verify(signed_message) - raise InvalidSignature if signed_message.blank? - + def valid_message?(signed_message) + return false if signed_message.blank? + data, digest = signed_message.split("--") - if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end + + def verified(signed_message) + if valid_message?(signed_message) begin + data = signed_message.split("--")[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error - raise InvalidSignature if argument_error.message =~ %r{invalid base64} + return false if argument_error.message =~ %r{invalid base64} raise end else - raise InvalidSignature + false end end + + def verify(signed_message) + verified(signed_message) || raise(InvalidSignature) + end def generate(value) data = encode(@serializer.dump(value)) diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 28035bc428..68f40fbb28 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -27,21 +27,29 @@ class MessageVerifierTest < ActiveSupport::TestCase @data = { :some => "data", :now => Time.local(2010) } end + def test_valid_message + data, hash = @verifier.generate(@data).split("--") + assert !@verifier.valid_message?(nil) + assert !@verifier.valid_message?("") + assert !@verifier.valid_message?("#{data.reverse}--#{hash}") + assert !@verifier.valid_message?("#{data}--#{hash.reverse}") + assert !@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_missing_signature_raises - assert_not_verified(nil) - assert_not_verified("") + + def test_verified_returns_false_on_invalid_message + assert !@verifier.verified("purejunk") end - - def test_tampered_data_raises - data, hash = @verifier.generate(@data).split("--") - assert_not_verified("#{data.reverse}--#{hash}") - assert_not_verified("#{data}--#{hash.reverse}") - assert_not_verified("purejunk") + + def test_verify_exception_on_invalid_message + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify("purejunk") + end end def test_alternative_serialization_method @@ -50,6 +58,7 @@ class MessageVerifierTest < ActiveSupport::TestCase 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 @@ -62,6 +71,11 @@ class MessageVerifierTest < ActiveSupport::TestCase # 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 @@ -75,12 +89,6 @@ class MessageVerifierTest < ActiveSupport::TestCase end assert_equal exception.message, 'Secret should not be nil.' end - - def assert_not_verified(message) - assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do - @verifier.verify(message) - end - end end end -- cgit v1.2.3