aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorAssain <assainjaleel20@gmail.com>2018-05-19 13:31:57 +0530
committerAssain <assainjaleel20@gmail.com>2018-08-12 21:50:35 +0530
commit1cda4fb5df519080032c9c0a16d3c4f8cf1f3d2c (patch)
tree5e3b2acdd55627088a99e91494481645e365c35e /actionpack
parentba1dab1e3b32a7c81cb9b8bdc22429f6620a3833 (diff)
downloadrails-1cda4fb5df519080032c9c0a16d3c4f8cf1f3d2c.tar.gz
rails-1cda4fb5df519080032c9c0a16d3c4f8cf1f3d2c.tar.bz2
rails-1cda4fb5df519080032c9c0a16d3c4f8cf1f3d2c.zip
Purpose Metadata For Signed And Encrypted Cookies
Purpose metadata prevents cookie values from being copy-pasted and ensures that the cookie is used only for its originally intended purpose. The Purpose and Expiry metadata are embedded inside signed/encrypted cookies and will not be readable on previous versions of Rails. We can switch off purpose and expiry metadata embedded in signed and encrypted cookies using config.action_dispatch.use_cookies_with_metadata = false if you want your cookies to be readable on older versions of Rails.
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb42
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb1
-rw-r--r--actionpack/test/dispatch/cookies_test.rb166
3 files changed, 196 insertions, 13 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index c45d947904..34331b7e4b 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -81,6 +81,10 @@ module ActionDispatch
get_header Cookies::COOKIES_ROTATIONS
end
+ def use_cookies_with_metadata
+ get_header Cookies::USE_COOKIES_WITH_METADATA
+ end
+
# :startdoc:
end
@@ -182,6 +186,7 @@ module ActionDispatch
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
+ USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -470,7 +475,7 @@ module ActionDispatch
def [](name)
if data = @parent_jar[name.to_s]
- parse name, data
+ parse(name, data, purpose: "cookie.#{name}") || parse(name, data)
end
end
@@ -481,7 +486,7 @@ module ActionDispatch
options = { value: options }
end
- commit(options)
+ commit(name, options)
@parent_jar[name] = options
end
@@ -497,13 +502,24 @@ module ActionDispatch
end
end
- def parse(name, data); data; end
- def commit(options); end
+ def cookie_metadata(name, options)
+ if request.use_cookies_with_metadata
+ metadata = expiry_options(options)
+ metadata[:purpose] = "cookie.#{name}"
+
+ metadata
+ else
+ {}
+ end
+ end
+
+ def parse(name, data, purpose: nil); data; end
+ def commit(name, options); end
end
class PermanentCookieJar < AbstractCookieJar # :nodoc:
private
- def commit(options)
+ def commit(name, options)
options[:expires] = 20.years.from_now
end
end
@@ -583,14 +599,14 @@ module ActionDispatch
end
private
- def parse(name, signed_message)
+ def parse(name, signed_message, purpose: nil)
deserialize(name) do |rotate|
- @verifier.verified(signed_message, on_rotation: rotate)
+ @verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
end
end
- def commit(options)
- options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options))
+ def commit(name, options)
+ options[:value] = @verifier.generate(serialize(options[:value]), cookie_metadata(name, options))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
@@ -631,16 +647,16 @@ module ActionDispatch
end
private
- def parse(name, encrypted_message)
+ def parse(name, encrypted_message, purpose: nil)
deserialize(name) do |rotate|
- @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
end
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
parse_legacy_signed_message(name, encrypted_message)
end
- def commit(options)
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options))
+ def commit(name, options)
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), cookie_metadata(name, options))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index eb6fbca6ba..efc3988bc3 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -21,6 +21,7 @@ module ActionDispatch
config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie"
config.action_dispatch.use_authenticated_cookie_encryption = false
+ config.action_dispatch.use_cookies_with_metadata = false
config.action_dispatch.perform_deep_munge = true
config.action_dispatch.default_headers = {
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index aba778fad6..34ead0a4c0 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -289,6 +289,46 @@ class CookiesTest < ActionController::TestCase
cookies[:user_name] = { value: "assain", expires: 2.hours }
head :ok
end
+
+ def encrypted_discount_and_user_id_cookie
+ cookies.encrypted[:user_id] = { value: 50, expires: 1.hour }
+ cookies.encrypted[:discount_percentage] = 10
+
+ head :ok
+ end
+
+ def signed_discount_and_user_id_cookie
+ cookies.signed[:user_id] = { value: 50, expires: 1.hour }
+ cookies.signed[:discount_percentage] = 10
+
+ head :ok
+ end
+
+ def rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_on
+ # cookies.encrypted[:favorite] = { value: "5-2-Stable Chocolate Cookies", expires: 1000.years }
+ cookies[:favorite] = "KvH5lIHvX5vPQkLIK63r/NuIMwzWky8M0Zwk8SZ6DwUv8+srf36geR4nWq5KmhsZIYXA8NRdCZYIfxMKJsOFlz77Gf+Fq8vBBCWJTp95rx39A28TCUTJEyMhCNJO5eie7Skef76Qt5Jo/SCnIADAhzyGQkGBopKRcA==--qXZZFWGbCy6N8AGy--WswoH+xHrNh9MzSXDpB2fA=="
+
+ head :ok
+ end
+
+ def rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off
+ cookies[:favorite] = "Wmg4amgvcVVvWGcwK3c4WjJEbTdRQUgrWXhBdDliUTR0cVNidXpmVTMrc2RjcitwUzVsWWEwZGtuVGtFUjJwNi0tcVhVMTFMOTQ1d0hIVE1FK0pJc05SQT09--8b2a55c375049a50f7a959b9d42b31ef0b2bb594"
+
+ head :ok
+ end
+
+ def rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_on
+ # cookies.signed[:favorite] = { value: "5-2-Stable Choco Chip Cookie", expires: 1000.years }
+ cookies[:favorite] = "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaUUxTFRJdFUzUmhZbXhsSUVOb2IyTnZJRU5vYVhBZ1EyOXZhMmxsQmpvR1JWUT0iLCJleHAiOiIzMDE4LTA3LTExVDE2OjExOjI2Ljc1M1oiLCJwdXIiOm51bGx9fQ==--7df5d885b78b70a501d6e82140ae91b24060ac00"
+
+ head :ok
+ end
+
+ def rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_off
+ cookies[:favorite] = "BAhJIiE1LTItU3RhYmxlIENob2NvIENoaXAgQ29va2llBjoGRVQ=--50bbdbf8d64f5a3ec3e54878f54d4f55b6cb3aff"
+
+ head :ok
+ end
end
tests TestController
@@ -1274,6 +1314,8 @@ class CookiesTest < ActionController::TestCase
end
def test_signed_cookie_with_expires_set_relatively
+ request.env["action_dispatch.use_cookies_with_metadata"] = true
+
cookies.signed[:user_name] = { value: "assain", expires: 2.hours }
travel 1.hour
@@ -1284,6 +1326,8 @@ class CookiesTest < ActionController::TestCase
end
def test_encrypted_cookie_with_expires_set_relatively
+ request.env["action_dispatch.use_cookies_with_metadata"] = true
+
cookies.encrypted[:user_name] = { value: "assain", expires: 2.hours }
travel 1.hour
@@ -1300,6 +1344,128 @@ class CookiesTest < ActionController::TestCase
end
end
+ def test_purpose_metadata_for_encrypted_cookies
+ get :encrypted_discount_and_user_id_cookie
+
+ cookies[:discount_percentage] = cookies[:user_id]
+ assert_equal 50, cookies.encrypted[:discount_percentage]
+
+ request.env["action_dispatch.use_cookies_with_metadata"] = true
+
+ get :encrypted_discount_and_user_id_cookie
+
+ cookies[:discount_percentage] = cookies[:user_id]
+ assert_nil cookies.encrypted[:discount_percentage]
+ end
+
+ def test_purpose_metadata_for_signed_cookies
+ get :signed_discount_and_user_id_cookie
+
+ cookies[:discount_percentage] = cookies[:user_id]
+ assert_equal 50, cookies.signed[:discount_percentage]
+
+ request.env["action_dispatch.use_cookies_with_metadata"] = true
+
+ get :signed_discount_and_user_id_cookie
+
+ cookies[:discount_percentage] = cookies[:user_id]
+ assert_nil cookies.signed[:discount_percentage]
+ end
+
+ def test_switch_off_metadata_for_encrypted_cookies_if_config_is_false
+ request.env["action_dispatch.use_cookies_with_metadata"] = false
+
+ get :encrypted_discount_and_user_id_cookie
+
+ travel 2.hours
+ assert_equal 50, cookies.encrypted[:user_id]
+
+ cookies[:discount_percentage] = cookies[:user_id]
+ assert_not_equal 10, cookies.encrypted[:discount_percentage]
+ assert_equal 50, cookies.encrypted[:discount_percentage]
+ end
+
+ def test_switch_off_metadata_for_signed_cookies_if_config_is_false
+ request.env["action_dispatch.use_cookies_with_metadata"] = false
+
+ get :signed_discount_and_user_id_cookie
+
+ travel 2.hours
+ assert_equal 50, cookies.signed[:user_id]
+
+ cookies[:discount_percentage] = cookies[:user_id]
+ assert_not_equal 10, cookies.signed[:discount_percentage]
+ assert_equal 50, cookies.signed[:discount_percentage]
+ end
+
+ def test_read_rails_5_2_stable_encrypted_cookies_if_config_is_false
+ request.env["action_dispatch.use_cookies_with_metadata"] = false
+
+ get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_on
+
+ assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite]
+
+ freeze_time do
+ travel 1001.years
+ assert_nil cookies.encrypted[:favorite]
+ end
+
+ get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off
+
+ assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite]
+ end
+
+ def test_read_rails_5_2_stable_signed_cookies_if_config_is_false
+ request.env["action_dispatch.use_cookies_with_metadata"] = false
+
+ get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_on
+
+ assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite]
+
+ freeze_time do
+ travel 1001.years
+ assert_nil cookies.signed[:favorite]
+ end
+
+ get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_off
+
+ assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite]
+ end
+
+ def test_read_rails_5_2_stable_encrypted_cookies_if_use_metadata_config_is_true
+ request.env["action_dispatch.use_cookies_with_metadata"] = true
+
+ get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_on
+
+ assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite]
+
+ freeze_time do
+ travel 1001.years
+ assert_nil cookies.encrypted[:favorite]
+ end
+
+ get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off
+
+ assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite]
+ end
+
+ def test_read_rails_5_2_stable_signed_cookies_if_use_metadata_config_is_true
+ request.env["action_dispatch.use_cookies_with_metadata"] = true
+
+ get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_on
+
+ assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite]
+
+ freeze_time do
+ travel 1001.years
+ assert_nil cookies.signed[:favorite]
+ end
+
+ get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_off
+
+ assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite]
+ end
+
private
def assert_cookie_header(expected)
header = @response.headers["Set-Cookie"]