aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md14
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/json_serializer.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb14
-rw-r--r--actionpack/test/dispatch/cookies_test.rb33
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--guides/source/action_controller_overview.md22
-rw-r--r--railties/lib/rails/application.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/test/generators/app_generator_test.rb2
11 files changed, 121 insertions, 10 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index fc05ae3cec..60b0195510 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -49,6 +49,20 @@
*Alessandro Diaferia*
+* Add `:serializer` option for `config.session_store :cookie_store`. This
+ changes default serializer when using `:cookie_store` to
+ `ActionDispatch::Session::MarshalSerializer` which is wrapper on Marshal.
+
+ It is also possible to pass:
+
+ * `:json_serializer` which is secure wrapper on JSON using `JSON.parse` and
+ `JSON.generate` methods with quirks mode;
+ * any other Symbol or String like `:my_custom_serializer` which will be
+ camelized and constantized in `ActionDispatch::Session` namespace;
+ * serializer object with `load` and `dump` methods defined.
+
+ *Ɓukasz Sarnacki*
+
* Allow an absolute controller path inside a module scope. Fixes #12777.
Example:
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 920e651b08..36dcca2905 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -82,10 +82,12 @@ module ActionDispatch
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :JsonSerializer, 'action_dispatch/middleware/session/json_serializer'
+ autoload :MarshalSerializer, 'action_dispatch/middleware/session/marshal_serializer'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index fe110d7938..f9f034952e 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -89,6 +89,7 @@ module ActionDispatch
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
+ SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -210,7 +211,8 @@ module ActionDispatch
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
- upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
+ session_serializer: env[SESSION_SERIALIZER]
}
end
@@ -435,7 +437,7 @@ module ActionDispatch
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: serializer)
end
def [](name)
@@ -462,6 +464,16 @@ module ActionDispatch
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
nil
end
+
+ def serializer
+ serializer = @options[:session_serializer] || :marshal_serializer
+ case serializer
+ when Symbol, String
+ ActionDispatch::Session.const_get(serializer.to_s.camelize)
+ else
+ serializer
+ end
+ end
end
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
diff --git a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
new file mode 100644
index 0000000000..d341853f7a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
@@ -0,0 +1,13 @@
+module ActionDispatch
+ module Session
+ class JsonSerializer
+ def self.load(value)
+ JSON.parse(value, quirks_mode: true)
+ end
+
+ def self.dump(value)
+ JSON.generate(value, quirks_mode: true)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb
new file mode 100644
index 0000000000..26622f682d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb
@@ -0,0 +1,14 @@
+module ActionDispatch
+ module Session
+ class MarshalSerializer
+ def self.load(value)
+ Marshal.load(value)
+ end
+
+ def self.dump(value)
+ Marshal.dump(value)
+ end
+ end
+ end
+end
+
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 91ac13e7c6..b19ce905f5 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -379,6 +379,39 @@ class CookiesTest < ActionController::TestCase
assert_equal 'bar', cookies.encrypted[:foo]
end
+ class ActionDispatch::Session::CustomJsonSerializer
+ def self.load(value)
+ JSON.load(value) + " and loaded"
+ end
+
+ def self.dump(value)
+ JSON.dump(value + " was dumped")
+ end
+ end
+
+ def test_encrypted_cookie_using_custom_json_serializer
+ @request.env["action_dispatch.session_serializer"] = :custom_json_serializer
+ get :set_encrypted_cookie
+ assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_serializer_object
+ @request.env["action_dispatch.session_serializer"] = ActionDispatch::Session::CustomJsonSerializer
+ get :set_encrypted_cookie
+ assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_json_serializer
+ @request.env["action_dispatch.session_serializer"] = :json_serializer
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 7773611e11..b019ad0dec 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -12,7 +12,7 @@ module ActiveSupport
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
# where you don't want users to be able to determine the value of the payload.
#
- # salt = SecureRandom.random_bytes(64)
+ # salt = SecureRandom.random_bytes(64)
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index c55637eb0a..0234120b45 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -381,6 +381,28 @@ You can also pass a `:domain` key and specify the domain name for the cookie:
YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
```
+You can pass `:serializer` key to specify serializer for serializing session:
+
+```ruby
+YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :json_serializer
+```
+
+Default serializer is `:marshal_serializer`. When Symbol or String is passed it
+will look for appropriate class in `ActionDispatch::Session` namespace, so
+passing `:my_custom_serializer` would load
+`ActionDispatch::Session::MyCustomSerializer`.
+
+```ruby
+YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :my_custom_serializer
+```
+
+It is also possible to pass serializer object with defined `load` and `dump`
+public methods:
+
+```ruby
+YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: MyCustomSerializer
+```
+
Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/initializers/secret_token.rb`
```ruby
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 05acd78d98..36432e56ba 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -205,7 +205,8 @@ module Rails
"action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt,
"action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt,
"action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
- "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
+ "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
+ "action_dispatch.session_serializer" => config.session_options[:serializer]
})
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index 2bb9b82c61..923d423287 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
+Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>, serializer: :json_serializer
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index ddecee2ca1..8aa306c8e0 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -433,7 +433,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_new_hash_style
run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file|
- assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
+ assert_match(/config.session_store :cookie_store, key: '_.+_session', serializer: :json_serializer/, file)
end
end