aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/message_verifier.rb45
-rw-r--r--activesupport/test/message_verifier_test.rb25
4 files changed, 73 insertions, 0 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 5e1b1cb4b0..755ec246ab 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*2.3.0 [Edge]*
+* Added ActiveSupport::MessageVerifier to aid users who need to store signed messages. [Koz]
+
* Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers [DHH]
* Added Object#try. ( Taken from http://ozmm.org/posts/try.html ) [Chris Wanstrath]
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index cbfd95f092..7337b55a21 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -56,6 +56,7 @@ require 'active_support/base64'
require 'active_support/time_with_zone'
require 'active_support/secure_random'
+require 'active_support/message_verifier'
require 'active_support/rescuable'
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
new file mode 100644
index 0000000000..f3f905c15e
--- /dev/null
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -0,0 +1,45 @@
+module ActiveSupport
+ # MessageVerifier makes it easy to generate and verify messages which are signed
+ # to prevent tampering.
+ #
+ # This is useful for cases like remember-me tokens and auto-unsubscribe links where the
+ # session store isn't suitable or available.
+ #
+ # Remember Me:
+ # cookies[:remember_me] = @verifier.generate_message([@user.id, 2.weeks.from_now])
+ #
+ # In the authentication filter:
+ #
+ # id, time = @verifier.verify_message(cookies[:remember_me])
+ # if time < Time.now
+ # self.current_user = User.find(id)
+ # end
+ #
+ class MessageVerifier
+ class InvalidSignature < StandardError; end
+
+ def initialize(secret, digest = 'SHA1')
+ @secret = secret
+ @digest = digest
+ end
+
+ def verify_message(signed_message)
+ data, digest = signed_message.split("--")
+ if digest != generate_digest(data)
+ raise InvalidSignature
+ else
+ Marshal.load(ActiveSupport::Base64.decode64(data))
+ end
+ end
+
+ def generate_message(value)
+ data = ActiveSupport::Base64.encode64s(Marshal.dump(value))
+ "#{data}--#{generate_digest(data)}"
+ end
+
+ private
+ def generate_digest(data)
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data)
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
new file mode 100644
index 0000000000..c7ea4cb962
--- /dev/null
+++ b/activesupport/test/message_verifier_test.rb
@@ -0,0 +1,25 @@
+require 'abstract_unit'
+
+class MessageVerifierTest < Test::Unit::TestCase
+ def setup
+ @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
+ @data = {:some=>"data", :now=>Time.now}
+ end
+
+ def test_simple_round_tripping
+ message = @verifier.generate_message(@data)
+ assert_equal @data, @verifier.verify_message(message)
+ end
+
+ def test_tampered_data_raises
+ data, hash = @verifier.generate_message(@data).split("--")
+ assert_not_verified("#{data.reverse}--#{hash}")
+ assert_not_verified("#{data}--#{hash.reverse}")
+ end
+
+ def assert_not_verified(message)
+ assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @verifier.verify_message(message)
+ end
+ end
+end