mirror of
https://github.com/github/rails.git
synced 2026-01-15 01:28:12 -05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
685cb901fc | ||
|
|
e9f9d05a94 | ||
|
|
7b6670cc08 | ||
|
|
ed2d852bdc | ||
|
|
726ab5316d | ||
|
|
ecd6fb250a | ||
|
|
9f8ee9dd97 |
@@ -209,7 +209,7 @@ module ActionController
|
|||||||
|
|
||||||
def verifier_for(secret, digest)
|
def verifier_for(secret, digest)
|
||||||
key = secret.respond_to?(:call) ? secret.call : secret
|
key = secret.respond_to?(:call) ? secret.call : secret
|
||||||
ActiveSupport::MessageVerifier.new(key, digest)
|
ActiveSupport::MessageVerifier.new(key, digest: digest)
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_sid
|
def generate_sid
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ module RequestForgeryProtectionActions
|
|||||||
render :inline => "<% form_remote_tag(:url => '/') {} %>"
|
render :inline => "<% form_remote_tag(:url => '/') {} %>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def external_form_for
|
||||||
|
render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_for_without_token
|
||||||
|
render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
|
||||||
|
end
|
||||||
|
|
||||||
def unsafe
|
def unsafe
|
||||||
render :text => 'pwn'
|
render :text => 'pwn'
|
||||||
end
|
end
|
||||||
@@ -156,6 +164,20 @@ module RequestForgeryProtectionTests
|
|||||||
assert_nothing_raised { yield }
|
assert_nothing_raised { yield }
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_should_render_form_with_token_tag_with_authenticity_token_requested
|
||||||
|
assert_not_blocked do
|
||||||
|
get :external_form_for
|
||||||
|
end
|
||||||
|
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', 'external_token'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_render_form_without_token_tag_with_authenticity_token_set_to_false
|
||||||
|
assert_not_blocked do
|
||||||
|
get :form_for_without_token
|
||||||
|
end
|
||||||
|
assert_select 'form>div>input[name=?]', 'authenticity_token', false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# OK let's get our test on
|
# OK let's get our test on
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class CookieStoreTest < ActionController::IntegrationTest
|
|||||||
|
|
||||||
DispatcherApp = ActionController::Dispatcher.new
|
DispatcherApp = ActionController::Dispatcher.new
|
||||||
|
|
||||||
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
|
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, digest: 'SHA1')
|
||||||
|
|
||||||
SignedBar = "BAh7BjoIZm9vIghiYXI%3D--fef868465920f415f2c0652d6910d3af288a0367"
|
SignedBar = "BAh7BjoIZm9vIghiYXI%3D--fef868465920f415f2c0652d6910d3af288a0367"
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +1,74 @@
|
|||||||
|
require 'active_support/base64'
|
||||||
|
require 'active_support/deprecation'
|
||||||
|
require 'active_support/core_ext/object/blank'
|
||||||
|
|
||||||
module ActiveSupport
|
module ActiveSupport
|
||||||
# MessageVerifier makes it easy to generate and verify messages which are signed
|
# +MessageVerifier+ makes it easy to generate and verify messages which are signed
|
||||||
# to prevent tampering.
|
# to prevent tampering.
|
||||||
#
|
#
|
||||||
# This is useful for cases like remember-me tokens and auto-unsubscribe links where the
|
# This is useful for cases like remember-me tokens and auto-unsubscribe links where the
|
||||||
# session store isn't suitable or available.
|
# session store isn't suitable or available.
|
||||||
#
|
#
|
||||||
# Remember Me:
|
# Remember Me:
|
||||||
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
|
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
|
||||||
#
|
#
|
||||||
# In the authentication filter:
|
# In the authentication filter:
|
||||||
#
|
#
|
||||||
# id, time = @verifier.verify(cookies[:remember_me])
|
# id, time = @verifier.verify(cookies[:remember_me])
|
||||||
# if time < Time.now
|
# if time < Time.now
|
||||||
# self.current_user = User.find(id)
|
# self.current_user = User.find(id)
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# By default it uses Marshal to serialize the message. If you want to use another
|
||||||
|
# serialization method, you can set the serializer attribute to something that responds
|
||||||
|
# to dump and load, e.g.:
|
||||||
|
#
|
||||||
|
# @verifier.serializer = YAML
|
||||||
class MessageVerifier
|
class MessageVerifier
|
||||||
class InvalidSignature < StandardError; end
|
class InvalidSignature < StandardError; end
|
||||||
|
|
||||||
def initialize(secret, digest = 'SHA1')
|
def initialize(secret, options = {})
|
||||||
|
unless options.is_a?(Hash)
|
||||||
|
ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :digest => 'algorithm' to specify the digest algorithm."
|
||||||
|
options = { :digest => options }
|
||||||
|
end
|
||||||
|
|
||||||
@secret = secret
|
@secret = secret
|
||||||
@digest = digest
|
@digest = options[:digest] || 'SHA1'
|
||||||
|
@serializer = options[:serializer] || Marshal
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify(signed_message)
|
def verify(signed_message)
|
||||||
raise InvalidSignature if signed_message.blank?
|
raise InvalidSignature if signed_message.blank?
|
||||||
|
|
||||||
data, digest = signed_message.split("--")
|
data, digest = signed_message.split("--")
|
||||||
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
|
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
|
||||||
Marshal.load(ActiveSupport::Base64.decode64(data))
|
@serializer.load(::Base64.decode64(data))
|
||||||
else
|
else
|
||||||
raise InvalidSignature
|
raise InvalidSignature
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate(value)
|
def generate(value)
|
||||||
data = ActiveSupport::Base64.encode64s(Marshal.dump(value))
|
data = ::Base64.strict_encode64(@serializer.dump(value))
|
||||||
"#{data}--#{generate_digest(data)}"
|
"#{data}--#{generate_digest(data)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
if "foo".respond_to?(:force_encoding)
|
|
||||||
# constant-time comparison algorithm to prevent timing attacks
|
|
||||||
def secure_compare(a, b)
|
|
||||||
a = a.dup.force_encoding(Encoding::BINARY)
|
|
||||||
b = b.dup.force_encoding(Encoding::BINARY)
|
|
||||||
|
|
||||||
if a.length == b.length
|
private
|
||||||
result = 0
|
# constant-time comparison algorithm to prevent timing attacks
|
||||||
for i in 0..(a.length - 1)
|
def secure_compare(a, b)
|
||||||
result |= a[i].ord ^ b[i].ord
|
return false unless a.bytesize == b.bytesize
|
||||||
end
|
|
||||||
result == 0
|
l = a.unpack "C#{a.bytesize}"
|
||||||
else
|
|
||||||
false
|
res = 0
|
||||||
end
|
b.each_byte { |byte| res |= byte ^ l.shift }
|
||||||
end
|
res == 0
|
||||||
else
|
|
||||||
# For <= 1.8.6
|
|
||||||
def secure_compare(a, b)
|
|
||||||
if a.length == b.length
|
|
||||||
result = 0
|
|
||||||
for i in 0..(a.length - 1)
|
|
||||||
result |= a[i] ^ b[i]
|
|
||||||
end
|
|
||||||
result == 0
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_digest(data)
|
def generate_digest(data)
|
||||||
require 'openssl' unless defined?(OpenSSL)
|
require 'openssl' unless defined?(OpenSSL)
|
||||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data)
|
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +1,36 @@
|
|||||||
require 'abstract_unit'
|
require 'abstract_unit'
|
||||||
|
|
||||||
class MessageVerifierTest < Test::Unit::TestCase
|
begin
|
||||||
def setup
|
require 'openssl'
|
||||||
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
OpenSSL::Digest::SHA1
|
||||||
@data = {:some=>"data", :now=>Time.now}
|
rescue LoadError, NameError
|
||||||
|
$stderr.puts "Skipping MessageVerifier test: broken OpenSSL install"
|
||||||
|
else
|
||||||
|
|
||||||
|
require 'active_support/json'
|
||||||
|
|
||||||
|
class MessageVerifierTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
|
class JSONSerializer
|
||||||
|
def dump(value)
|
||||||
|
ActiveSupport::JSON.encode(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(value)
|
||||||
|
ActiveSupport::JSON.decode(value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
|
||||||
|
@data = { :some => "data", :now => Time.local(2010) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_simple_round_tripping
|
def test_simple_round_tripping
|
||||||
message = @verifier.generate(@data)
|
message = @verifier.generate(@data)
|
||||||
assert_equal @data, @verifier.verify(message)
|
assert_equal @data, @verifier.verify(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_missing_signature_raises
|
def test_missing_signature_raises
|
||||||
assert_not_verified(nil)
|
assert_not_verified(nil)
|
||||||
assert_not_verified("")
|
assert_not_verified("")
|
||||||
@@ -23,9 +43,23 @@ class MessageVerifierTest < Test::Unit::TestCase
|
|||||||
assert_not_verified("purejunk")
|
assert_not_verified("purejunk")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_alternative_serialization_method
|
||||||
|
verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new)
|
||||||
|
message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) })
|
||||||
|
assert_equal verifier.verify(message), { "foo" => 123, "bar" => "2010/01/01 00:00:00 +0000" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_digest_algorithm_as_second_parameter_deprecation
|
||||||
|
assert_deprecated(/options hash/) do
|
||||||
|
ActiveSupport::MessageVerifier.new("secret", "SHA1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def assert_not_verified(message)
|
def assert_not_verified(message)
|
||||||
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
|
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
|
||||||
@verifier.verify(message)
|
@verifier.verify(message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user