diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware')
-rw-r--r-- | actionpack/lib/action_dispatch/middleware/cookies.rb | 115 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/middleware/session/cookie_store.rb | 96 |
2 files changed, 115 insertions, 96 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index f21d1d4ee5..08c75632ba 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -117,6 +117,9 @@ module ActionDispatch # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed # cookie was tampered with by the user (or a 3rd party), nil will be returned. # + # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set, + # legacy cookies signed with the old key generator will be transparently upgraded. + # # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. # # Example: @@ -126,23 +129,20 @@ module ActionDispatch # # cookies.signed[:discount] # => 45 def signed - @signed ||= begin - if @options[:upgrade_legacy_signed_cookie_jar] + @signed ||= + if @options[:upgrade_legacy_signed_cookies] UpgradeLegacySignedCookieJar.new(self, @key_generator, @options) else SignedCookieJar.new(self, @key_generator, @options) end - end - end - - # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this - def signed_using_old_secret #:nodoc: - @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:secret_token]), @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), nil will be returned. # + # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set, + # legacy cookies signed with the old key generator will be transparently upgraded. + # # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. # # Example: @@ -152,7 +152,38 @@ module ActionDispatch # # cookies.encrypted[:discount] # => 45 def encrypted - @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) + @encrypted ||= + if @options[:upgrade_legacy_signed_cookies] + UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options) + else + EncryptedCookieJar.new(self, @key_generator, @options) + end + end + + # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set. + # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. + def signed_or_encrypted + @signed_or_encrypted ||= + if @options[:secret_key_base].present? + encrypted + else + signed + end + end + end + + module VerifyAndUpgradeLegacySignedMessage + def initialize(*args) + super + @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token]) + end + + def verify_and_upgrade_legacy_signed_message(name, signed_message) + @legacy_verifier.verify(signed_message).tap do |value| + self[name] = value + end + rescue ActiveSupport::MessageVerifier::InvalidSignature + nil end end @@ -179,7 +210,7 @@ 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_cookie_jar: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present? + upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present? } end @@ -354,10 +385,8 @@ module ActionDispatch def [](name) if signed_message = @parent_jar[name] - @verifier.verify(signed_message) + verify(signed_message) end - rescue ActiveSupport::MessageVerifier::InvalidSignature - nil end def []=(key, options) @@ -371,6 +400,14 @@ module ActionDispatch raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE @parent_jar[key] = options end + + private + + def verify(signed_message) + @verifier.verify(signed_message) + rescue ActiveSupport::MessageVerifier::InvalidSignature + nil + end end # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if @@ -378,30 +415,13 @@ module ActionDispatch # legacy cookies signed with the old dummy key generator and re-saves # them using the new key generator to provide a smooth upgrade path. class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc: - def initialize(*args) - super - @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token]) - end + include VerifyAndUpgradeLegacySignedMessage def [](name) if signed_message = @parent_jar[name] - verify_signed_message(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message) + verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message) end end - - def verify_signed_message(signed_message) - @verifier.verify(signed_message) - rescue ActiveSupport::MessageVerifier::InvalidSignature - nil - end - - def verify_and_upgrade_legacy_signed_message(name, signed_message) - @legacy_verifier.verify(signed_message).tap do |value| - self[name] = value - end - rescue ActiveSupport::MessageVerifier::InvalidSignature - nil - end end class EncryptedCookieJar #:nodoc: @@ -409,8 +429,8 @@ module ActionDispatch 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" + raise "You didn't set config.secret_key_base, which is required for this cookie jar. " + + "Read the upgrade documentation to learn more about this new config option." end @parent_jar = parent_jar @@ -422,11 +442,8 @@ module ActionDispatch def [](key) if encrypted_message = @parent_jar[key] - @encryptor.decrypt_and_verify(encrypted_message) + decrypt_and_verify(encrypted_message) end - rescue ActiveSupport::MessageVerifier::InvalidSignature, - ActiveSupport::MessageEncryptor::InvalidMessage - nil end def []=(key, options) @@ -440,6 +457,28 @@ module ActionDispatch raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE @parent_jar[key] = options end + + private + + def decrypt_and_verify(encrypted_message) + @encryptor.decrypt_and_verify(encrypted_message) + rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage + nil + end + end + + # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore + # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base + # are both set. It reads legacy cookies signed with the old dummy key generator and + # encrypts and re-saves them using the new key generator to provide a smooth upgrade path. + class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc: + include VerifyAndUpgradeLegacySignedMessage + + def [](name) + if encrypted_or_signed_message = @parent_jar[name] + decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message) + end + end end def initialize(app) diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 1e6ed624b0..b9eb8036e9 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -4,36 +4,51 @@ require 'rack/session/cookie' module ActionDispatch module Session - # This cookie-based session store is the Rails default. Sessions typically - # contain at most a user_id and flash message; both fit within the 4K cookie - # size limit. Cookie-based sessions are dramatically faster than the - # alternatives. + # This cookie-based session store is the Rails default. It is + # dramatically faster than the alternatives. # - # If you have more than 4K of session data or don't want your data to be - # visible to the user, pick another session store. + # Sessions typically contain at most a user_id and flash message; both fit + # within the 4K cookie size limit. A CookieOverflow exception is raised if + # you attempt to store more than 4K of data. # - # CookieOverflow is raised if you attempt to store more than 4K of data. + # The cookie jar used for storage is automatically configured to be the + # best possible option given your application's configuration. # - # A message digest is included with the cookie to ensure data integrity: - # a user cannot alter his +user_id+ without knowing the secret key - # included in the hash. New apps are generated with a pregenerated secret - # in config/environment.rb. Set your own for old apps you're upgrading. + # If you only have secret_token set, your cookies will be signed, but + # not encrypted. This means a user cannot alter his +user_id+ without + # knowing your app's secret key, but can easily read his +user_id+. This + # was the default for Rails 3 apps. # - # Session options: + # If you have secret_key_base set, your cookies will be encrypted. This + # goes a step further than signed cookies in that encrypted cookies cannot + # be altered or read by users. This is the default starting in Rails 4. # - # * <tt>:secret</tt>: An application-wide key string. It's important that - # the secret is not vulnerable to a dictionary attack. Therefore, you - # should choose a secret consisting of random numbers and letters and - # more than 30 characters. + # If you have both secret_token and secret_key base set, your cookies will + # be encrypted, and signed cookies generated by Rails 3 will be + # transparently read and encrypted to provide a smooth upgrade path. # - # secret: '449fe2e7daee471bffae2fd8dc02313d' + # Configure your session store in config/initializers/session_store.rb: # - # * <tt>:digest</tt>: The message digest algorithm used to verify session - # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL, - # such as 'MD5', 'RIPEMD160', 'SHA256', etc. + # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session' # - # To generate a secret key for an existing application, run - # "rake secret" and set the key in config/initializers/secret_token.rb. + # Configure your secret key in config/initializers/secret_token.rb: + # + # Myapp::Application.config.secret_key_base 'secret key' + # + # To generate a secret key for an existing application, run `rake secret`. + # + # If you are upgrading an existing Rails 3 app, you should leave your + # existing secret_token in place and simply add the new secret_key_base. + # Note that you should wait to set secret_key_base until you have 100% of + # your userbase on Rails 4 and are reasonably sure you will not need to + # rollback to Rails 3. This is because cookies signed based on the new + # secret_key_base in Rails 4 are not backwards compatible with Rails 3. + # You are free to leave your existing secret_token in place, not set the + # new secret_key_base, and ignore the deprecation warnings until you are + # reasonably sure that your upgrade is otherwise complete. Additionally, + # you should take care to make sure you are not relying on the ability to + # decode signed cookies generated by your app in external applications or + # Javascript before upgrading. # # Note that changing digest or secret invalidates all existing sessions! class CookieStore < Rack::Session::Abstract::ID @@ -100,42 +115,7 @@ module ActionDispatch 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.encrypted - end - end - - # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+ - # To use this CookieStore set - # - # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session' - # - # in your config/initializers/session_store.rb - # - # You will also need to add - # - # Myapp::Application.config.secret_key_base = 'some secret' - # - # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+ - class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore - private - - def get_cookie(env) - signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key] - end - - def signed_using_old_secret_cookie_jar(env) - request = ActionDispatch::Request.new(env) - request.cookie_jar.signed_using_old_secret + request.cookie_jar.signed_or_encrypted end end end |