Skip to content

Commit

Permalink
Merge pull request #13692 from lukesarnacki/change-default-session-se…
Browse files Browse the repository at this point in the history
…rializer

Allow session serializer key in config.session_store
  • Loading branch information
guilleiguaran committed Jan 29, 2014
2 parents 40e7fe3 + b23ffd0 commit b242552
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 10 deletions.
14 changes: 14 additions & 0 deletions actionpack/CHANGELOG.md
Expand Up @@ -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:
Expand Down
10 changes: 6 additions & 4 deletions actionpack/lib/action_dispatch.rb
Expand Up @@ -82,10 +82,12 @@ module Http
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
Expand Down
16 changes: 14 additions & 2 deletions actionpack/lib/action_dispatch/middleware/cookies.rb
Expand Up @@ -89,6 +89,7 @@ class Cookies
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
Expand Down Expand Up @@ -210,7 +211,8 @@ def self.options_for_env(env) #:nodoc:
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

Expand Down Expand Up @@ -435,7 +437,7 @@ def initialize(parent_jar, key_generator, options = {})
@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)
Expand All @@ -462,6 +464,16 @@ def decrypt_and_verify(encrypted_message)
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
Expand Down
@@ -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
@@ -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

33 changes: 33 additions & 0 deletions actionpack/test/dispatch/cookies_test.rb
Expand Up @@ -379,6 +379,39 @@ def test_encrypted_cookie
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]
Expand Down
2 changes: 1 addition & 1 deletion activesupport/lib/active_support/message_encryptor.rb
Expand Up @@ -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..."
Expand Down
22 changes: 22 additions & 0 deletions guides/source/action_controller_overview.md
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion railties/lib/rails/application.rb
Expand Up @@ -205,7 +205,8 @@ def env_config
"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
Expand Down
@@ -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
2 changes: 1 addition & 1 deletion railties/test/generators/app_generator_test.rb
Expand Up @@ -433,7 +433,7 @@ def test_no_active_record_or_test_unit_if_skips_given
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

Expand Down

0 comments on commit b242552

Please sign in to comment.