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*
|
||||
|
||||
* 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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
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]
|
||||
|
|
|
@ -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..."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue