提交 ef8b845d 编写于 作者: S Santiago Pastorino

Merge pull request #8112 from rails/encrypted_cookies

Encrypted cookies
......@@ -249,9 +249,9 @@ def authentication_request(controller, realm, message = nil)
end
def secret_token(request)
secret = request.env["action_dispatch.secret_token"]
raise "You must set config.secret_token in your app's config" if secret.blank?
secret
key_generator = request.env["action_dispatch.key_generator"]
http_auth_salt = request.env["action_dispatch.http_auth_salt"]
key_generator.generate_key(http_auth_salt)
end
# Uses an MD5 digest based on time to generate a value to be used only once.
......
......@@ -121,11 +121,11 @@ def exists?
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
host = request.host
secure = request.ssl?
key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
host = request.host
secure = request.ssl?
new(secret, host, secure)
new(key_generator, host, secure)
end
def write(*)
......
......@@ -81,10 +81,11 @@ 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 :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store'
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
......
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/message_verifier'
module ActionDispatch
class Request < Rack::Request
......@@ -27,7 +28,7 @@ def cookie_jar
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
# # The cookie is signed by your app's <tt>config.secret_token</tt> value.
# # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
# # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
......@@ -79,8 +80,12 @@ def cookie_jar
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
class Cookies
HTTP_HEADER = "Set-Cookie".freeze
TOKEN_KEY = "action_dispatch.secret_token".freeze
HTTP_HEADER = "Set-Cookie".freeze
GENERATOR_KEY = "action_dispatch.key_generator".freeze
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
......@@ -103,21 +108,27 @@ class CookieJar #:nodoc:
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.build(request)
secret = request.env[TOKEN_KEY]
env = request.env
key_generator = env[GENERATOR_KEY]
options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT],
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT],
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] }
host = request.host
secure = request.ssl?
new(secret, host, secure).tap do |hash|
new(key_generator, host, secure, options).tap do |hash|
hash.update(request.cookies)
end
end
def initialize(secret = nil, host = nil, secure = false)
@secret = secret
def initialize(key_generator, host = nil, secure = false, options = {})
@key_generator = key_generator
@set_cookies = {}
@delete_cookies = {}
@host = host
@secure = secure
@options = options
@cookies = {}
end
......@@ -220,7 +231,7 @@ def clear(options = {})
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
@permanent ||= PermanentCookieJar.new(self, @secret)
@permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
......@@ -228,7 +239,7 @@ def permanent
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
#
......@@ -237,7 +248,23 @@ def permanent
#
# cookies.signed[:discount] # => 45
def signed
@signed ||= SignedCookieJar.new(self, @secret)
@signed ||= SignedCookieJar.new(self, @key_generator, @options)
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
# will be raised.
#
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
#
# cookies.encrypted[:discount] = 45
# # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
#
# cookies.encrypted[:discount] # => 45
def encrypted
@encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
end
def write(headers)
......@@ -261,8 +288,10 @@ def write_cookie?(cookie)
end
class PermanentCookieJar < CookieJar #:nodoc:
def initialize(parent_jar, secret)
@parent_jar, @secret = parent_jar, secret
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@key_generator = key_generator
@options = options
end
def []=(key, options)
......@@ -283,11 +312,11 @@ def method_missing(method, *arguments, &block)
class SignedCookieJar < CookieJar #:nodoc:
MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
SECRET_MIN_LENGTH = 30 # Characters
def initialize(parent_jar, secret)
ensure_secret_secure(secret)
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
@verifier = ActiveSupport::MessageVerifier.new(secret)
end
......@@ -314,26 +343,41 @@ def []=(key, options)
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
end
protected
# To prevent users from using something insecure like "Password" we make sure that the
# secret they've provided is at least 30 characters in length.
def ensure_secret_secure(secret)
if secret.blank?
raise ArgumentError, "A secret is required to generate an " +
"integrity hash for cookie session data. Use " +
"config.secret_token = \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\"" +
"in config/initializers/secret_token.rb"
class EncryptedCookieJar < SignedCookieJar #:nodoc:
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::DummyKeyGenerator === key_generator
raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
"Set config.secret_key_base in config/initializers/secret_token.rb"
end
if secret.length < SECRET_MIN_LENGTH
raise ArgumentError, "Secret should be something secure, " +
"like \"#{SecureRandom.hex(16)}\". The value you " +
"provided, \"#{secret}\", is shorter than the minimum length " +
"of #{SECRET_MIN_LENGTH} characters"
@parent_jar = parent_jar
@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)
end
def [](name)
if encrypted_message = @parent_jar[name]
@encryptor.decrypt_and_verify(encrypted_message)
end
rescue ActiveSupport::MessageVerifier::InvalidSignature,
ActiveSupport::MessageVerifier::InvalidMessage
nil
end
def []=(key, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
options = { :value => options }
end
options[:value] = @encryptor.encrypt_and_sign(options[:value])
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
end
......
......@@ -57,8 +57,7 @@ def destroy_session(env, session_id, options)
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
request = ActionDispatch::Request.new(env)
if data = request.cookie_jar.signed[@key]
if data = cookie_jar(env)[@key]
data.stringify_keys!
end
data || {}
......@@ -72,8 +71,26 @@ def set_session(env, sid, session_data, options)
end
def set_cookie(env, session_id, cookie)
cookie_jar(env)[@key] = cookie
end
def get_cookie
cookie_jar(env)[@key]
end
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
request.cookie_jar.signed
end
end
class EncryptedCookieStore < CookieStore
private
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
request.cookie_jar.signed[@key] = cookie
request.cookie_jar.encrypted
end
end
end
......
......@@ -13,6 +13,10 @@ class Railtie < Rails::Railtie
config.action_dispatch.rescue_responses = { }
config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = false
config.action_dispatch.http_auth_salt = 'http authentication'
config.action_dispatch.signed_cookie_salt = 'signed cookie'
config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
......
require 'abstract_unit'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class FlashTest < ActionController::TestCase
class TestController < ActionController::Base
......@@ -217,7 +219,7 @@ def test_redirect_to_with_adding_flash_types
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
class TestController < ActionController::Base
add_flash_types :bar
......@@ -291,7 +293,7 @@ def test_added_flash_types_method
# Overwrite get to send SessionSecret in env hash
def get(path, parameters = nil, env = {})
env["action_dispatch.secret_token"] ||= SessionSecret
env["action_dispatch.key_generator"] ||= Generator
super
end
......
require 'abstract_unit'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
class DummyDigestController < ActionController::Base
......@@ -40,8 +42,8 @@ def authenticate_with_request
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
@secret = "session_options_secret"
@request.env["action_dispatch.secret_token"] = @secret
@secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
@request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret)
end
teardown do
......
require 'abstract_unit'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
......@@ -65,6 +67,11 @@ def set_signed_cookie
head :ok
end
def set_encrypted_cookie
cookies.encrypted[:foo] = 'bar'
head :ok
end
def raise_data_overflow
cookies.signed[:foo] = 'bye!' * 1024
head :ok
......@@ -146,7 +153,10 @@ def noop
def setup
super
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
@request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.host = "www.nextangle.com"
end
......@@ -296,6 +306,16 @@ def test_signed_cookie
assert_equal 45, @controller.send(:cookies).signed[:user_id]
end
def test_encrypted_cookie
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_signed_cookie_should_not_raise_an_invalid_signature
get :set_signed_cookie
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
......@@ -329,29 +349,29 @@ def test_tampered_cookies
def test_raises_argument_error_if_missing_secret
assert_raise(ArgumentError, nil.inspect) {
@request.env["action_dispatch.secret_token"] = nil
@request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil)
get :set_signed_cookie
}
assert_raise(ArgumentError, ''.inspect) {
@request.env["action_dispatch.secret_token"] = ""
@request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("")
get :set_signed_cookie
}
end
def test_raises_argument_error_if_secret_is_probably_insecure
assert_raise(ArgumentError, "password".inspect) {
@request.env["action_dispatch.secret_token"] = "password"
@request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password")
get :set_signed_cookie
}
assert_raise(ArgumentError, "secret".inspect) {
@request.env["action_dispatch.secret_token"] = "secret"
@request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret")
get :set_signed_cookie
}
assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
@request.env["action_dispatch.secret_token"] = "12345678901234567890123456789"
@request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789")
get :set_signed_cookie
}
end
......
require 'abstract_unit'
require 'stringio'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
......@@ -330,7 +333,7 @@ def test_session_store_with_all_domains
# Overwrite get to send SessionSecret in env hash
def get(path, parameters = nil, env = {})
env["action_dispatch.secret_token"] ||= SessionSecret
env["action_dispatch.key_generator"] ||= Generator
super
end
......
require 'mutex_m'
require 'openssl'
module ActiveSupport
......@@ -20,4 +21,51 @@ def generate_key(salt, key_size=64)
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
end
end
class CachingKeyGenerator
def initialize(key_generator)
@key_generator = key_generator
@cache_keys = {}.extend(Mutex_m)
end
def generate_key(salt, key_size=64)
@cache_keys.synchronize do
@cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
end
end
end
class DummyKeyGenerator
SECRET_MIN_LENGTH = 30 # Characters
def initialize(secret)
ensure_secret_secure(secret)
@secret = secret
end
def generate_key(salt)
@secret
end
private
# To prevent users from using something insecure like "Password" we make sure that the
# secret they've provided is at least 30 characters in length.
def ensure_secret_secure(secret)
if secret.blank?
raise ArgumentError, "A secret is required to generate an " +
"integrity hash for cookie session data. Use " +
"config.secret_key_base = \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\"" +
"in config/initializers/secret_token.rb"
end
if secret.length < SECRET_MIN_LENGTH
raise ArgumentError, "Secret should be something secure, " +
"like \"#{SecureRandom.hex(16)}\". The value you " +
"provided, \"#{secret}\", is shorter than the minimum length " +
"of #{SECRET_MIN_LENGTH} characters"
end
end
end
end
......@@ -39,10 +39,13 @@ class InvalidMessage < StandardError; end
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
def initialize(secret, options = {})
def initialize(secret, *signature_key_or_options)
options = signature_key_or_options.extract_options!
sign_secret = signature_key_or_options.first
@secret = secret
@sign_secret = sign_secret
@cipher = options[:cipher] || 'aes-256-cbc'
@verifier = MessageVerifier.new(@secret, :serializer => NullSerializer)
@verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
@serializer = options[:serializer] || Marshal
end
......
......@@ -56,7 +56,7 @@ def test_signed_round_tripping
end
def test_alternative_serialization_method
encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), :serializer => JSONSerializer.new)
encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new)
message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) })
assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
end
......
......@@ -6,4 +6,4 @@
# no regular words or you'll be exposed to dictionary attacks.
# Make sure your secret key is kept private
# if you're sharing your code publicly.
Blog::Application.config.secret_token = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
Blog::Application.config.secret_key_base = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
......@@ -219,7 +219,7 @@ Rails sets up (for the CookieStore) a secret key used for signing the session da
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...'
YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'
```
NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions.
......
......@@ -113,7 +113,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
* `config.secret_token` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_token` initialized to a random key in `config/initializers/secret_token.rb`.
* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`.
* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
......
require 'fileutils'
require 'active_support/queueing'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
require 'rails/engine'
module Rails
......@@ -106,32 +108,57 @@ def reload_routes!
def key_generator
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
@key_generator ||= ActiveSupport::KeyGenerator.new(config.secret_token, iterations: 1000)
@caching_key_generator ||= begin
if config.secret_key_base
key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
ActiveSupport::DummyKeyGenerator.new(config.secret_token)
end
end
end
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
# Currently stores:
#
# * "action_dispatch.parameter_filter" => config.filter_parameters,
# * "action_dispatch.secret_token" => config.secret_token,
# * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
# * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
# * "action_dispatch.logger" => Rails.logger,
# * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
# * "action_dispatch.parameter_filter" => config.filter_parameters
# * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
# * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local
# * "action_dispatch.logger" => Rails.logger
# * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
# * "action_dispatch.key_generator" => key_generator
# * "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
#
# These parameters will be used by middlewares and engines to configure themselves
#
def env_config
@env_config ||= super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.secret_token" => config.secret_token,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
"action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
"action_dispatch.key_generator" => key_generator
})
@env_config ||= begin
if config.secret_key_base.nil?
ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " +
"This should be used instead of the old deprecated config.secret_token. " +
"Set config.secret_key_base instead of config.secret_token in config/initializers/secret_token.rb"
if config.secret_token.blank?
raise "You must set config.secret_key_base in your app's config"
end
end
super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
"action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
"action_dispatch.key_generator" => key_generator,
"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
})
end
end
## Rails internal API
......
......@@ -10,7 +10,7 @@ class Configuration < ::Rails::Engine::Configuration
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
:railties_order, :relative_url_root, :secret_token,
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:queue, :queue_consumer, :beginning_of_week
......@@ -46,6 +46,8 @@ def initialize(*)
@queue = ActiveSupport::SynchronousQueue.new
@queue_consumer = nil
@eager_load = nil
@secret_token = nil
@secret_key_base = nil
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
......
......@@ -7,6 +7,6 @@
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
# Make sure your secret_token is kept private
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
<%= app_const %>.config.secret_token = '<%= app_secret %>'
<%= app_const %>.config.secret_key_base = '<%= app_secret %>'
# Be sure to restart your server when you modify this file.
<%= app_const %>.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
<%= app_const %>.config.session_store :encrypted_cookie_store, key: <%= "'_#{app_name}_session'" %>
......@@ -14,5 +14,6 @@
module TestApp
class Application < Rails::Application
config.root = File.dirname(__FILE__)
config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
end
end
......@@ -225,21 +225,24 @@ def assert_utf8
assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
test "config.secret_token is sent in env" do
test "Use key_generator when secret_key_base is set" do
make_basic_app do |app|
app.config.secret_token = 'b3c631c314c0bbca50c1b2843150fe33'
app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
app.config.session_store :disabled
end
class ::OmgController < ActionController::Base
def index
cookies.signed[:some_key] = "some_value"
render text: env["action_dispatch.secret_token"]
render text: cookies[:some_key]
end
end
get "/"
assert_equal 'b3c631c314c0bbca50c1b2843150fe33', last_response.body
secret = app.key_generator.generate_key('signed cookie')
verifier = ActiveSupport::MessageVerifier.new(secret)
assert_equal 'some_value', verifier.verify(last_response.body)
end
test "protect from forgery is the default in a new app" do
......@@ -568,7 +571,6 @@ def index
assert_respond_to app, :env_config
assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters
assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token
assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
assert_equal app.env_config['action_dispatch.logger'], Rails.logger
assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner
......
require 'isolation/abstract_unit'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
module ApplicationTests
class RemoteIpTest < ActiveSupport::TestCase
......@@ -8,7 +10,7 @@ def remote_ip(env = {})
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
'action_dispatch.show_exceptions' => false,
'action_dispatch.secret_token' => 'b3c631c314c0bbca50c1b2843150fe33'
'action_dispatch.key_generator' => ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
)
endpoint = Proc.new do |e|
......
......@@ -128,5 +128,56 @@ def read_cookie
get '/foo/read_cookie' # Cookie shouldn't be changed
assert_equal '"1"', last_response.body
end
test "session using encrypted cookie store" do
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
get ':controller(/:action)'
end
RUBY
controller :foo, <<-RUBY
class FooController < ActionController::Base
def write_session
session[:foo] = 1
render nothing: true
end
def read_session
render text: session[:foo]
end
def read_encrypted_cookie
render text: cookies.encrypted[:_myapp_session]['foo']
end
def read_raw_cookie
render text: cookies[:_myapp_session]
end
end
RUBY
add_to_config <<-RUBY
config.session_store :encrypted_cookie_store, key: '_myapp_session'
config.action_dispatch.derive_signed_cookie_key = true
RUBY
require "#{app_path}/config/environment"
get '/foo/write_session'
get '/foo/write_session'
get '/foo/read_session'
assert_equal '1', last_response.body
get '/foo/read_encrypted_cookie'
assert_equal '1', last_response.body
secret = app.key_generator.generate_key('encrypted cookie')
sign_secret = app.key_generator.generate_key('signed encrypted cookie')
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
get '/foo/read_raw_cookie'
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
end
end
end
......@@ -14,7 +14,7 @@ def app
require "action_controller/railtie"
class MyApp < Rails::Application
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.eager_load = false
......
......@@ -341,7 +341,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 :encrypted_cookie_store, key: '_.+_session'/, file)
end
end
......
......@@ -119,7 +119,7 @@ def build_app(options = {})
add_to_config <<-RUBY
config.eager_load = false
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.action_controller.allow_forgery_protection = false
......@@ -138,7 +138,7 @@ def make_basic_app
app = Class.new(Rails::Application)
app.config.eager_load = false
app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
app.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, key: "_myapp_session"
app.config.active_support.deprecation = :log
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册