mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Allow session serializer key in config.session_store
MessageEncryptor has :serializer option, where any serializer object can be passed. This commit make it possible to set this serializer from configuration level. There are predefined serializers (:marshal_serializer, :json_serialzier) and custom serializer can be passed as String, Symbol (camelized and constantized in ActionDispatch::Session namepspace) or serializer object. Default :json_serializer was also added to generators to provide secure defalt.
This commit is contained in:
parent
f142527eb3
commit
b23ffd0dac
11 changed files with 121 additions and 10 deletions
|
@ -49,6 +49,20 @@
|
||||||
|
|
||||||
*Alessandro Diaferia*
|
*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.
|
* Allow an absolute controller path inside a module scope. Fixes #12777.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
|
@ -82,10 +82,12 @@ module ActionDispatch
|
||||||
end
|
end
|
||||||
|
|
||||||
module Session
|
module Session
|
||||||
autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
|
autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
|
||||||
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
|
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
|
||||||
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
|
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
|
||||||
autoload :CacheStore, 'action_dispatch/middleware/session/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
|
end
|
||||||
|
|
||||||
mattr_accessor :test_app
|
mattr_accessor :test_app
|
||||||
|
|
|
@ -89,6 +89,7 @@ module ActionDispatch
|
||||||
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
|
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
|
||||||
SECRET_TOKEN = "action_dispatch.secret_token".freeze
|
SECRET_TOKEN = "action_dispatch.secret_token".freeze
|
||||||
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
|
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
|
||||||
|
SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze
|
||||||
|
|
||||||
# Cookies can typically store 4096 bytes.
|
# Cookies can typically store 4096 bytes.
|
||||||
MAX_COOKIE_SIZE = 4096
|
MAX_COOKIE_SIZE = 4096
|
||||||
|
@ -210,7 +211,8 @@ module ActionDispatch
|
||||||
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
|
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
|
||||||
secret_token: env[SECRET_TOKEN],
|
secret_token: env[SECRET_TOKEN],
|
||||||
secret_key_base: env[SECRET_KEY_BASE],
|
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
|
end
|
||||||
|
|
||||||
|
@ -435,7 +437,7 @@ module ActionDispatch
|
||||||
@options = options
|
@options = options
|
||||||
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
|
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
|
||||||
sign_secret = key_generator.generate_key(@options[:encrypted_signed_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
|
end
|
||||||
|
|
||||||
def [](name)
|
def [](name)
|
||||||
|
@ -462,6 +464,16 @@ module ActionDispatch
|
||||||
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
|
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
|
||||||
nil
|
nil
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
|
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -379,6 +379,39 @@ class CookiesTest < ActionController::TestCase
|
||||||
assert_equal 'bar', cookies.encrypted[:foo]
|
assert_equal 'bar', cookies.encrypted[:foo]
|
||||||
end
|
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
|
def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
|
||||||
get :set_encrypted_cookie
|
get :set_encrypted_cookie
|
||||||
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
|
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
|
||||||
|
|
|
@ -12,7 +12,7 @@ module ActiveSupport
|
||||||
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
|
# 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.
|
# 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..."
|
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
|
||||||
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
|
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
|
||||||
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
|
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
|
||||||
|
|
|
@ -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"
|
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`
|
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
|
```ruby
|
||||||
|
|
|
@ -205,7 +205,8 @@ module Rails
|
||||||
"action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt,
|
"action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt,
|
||||||
"action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_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_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
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Be sure to restart your server when you modify this file.
|
# 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
|
||||||
|
|
|
@ -433,7 +433,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
|
||||||
def test_new_hash_style
|
def test_new_hash_style
|
||||||
run_generator [destination_root]
|
run_generator [destination_root]
|
||||||
assert_file "config/initializers/session_store.rb" do |file|
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue