From 25f7c030e4ea440ea6c2a84c92118299753392d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 18 May 2010 01:43:06 +0200 Subject: Simplify cookie_store by simply relying on cookies.signed. --- .../middleware/session/cookie_store.rb | 107 +++------------------ 1 file changed, 14 insertions(+), 93 deletions(-) (limited to 'actionpack/lib/action_dispatch/middleware/session/cookie_store.rb') diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 7114f42003..0c1712bf84 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,3 +1,4 @@ +require 'action_dispatch/middleware/cookies' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/blank' @@ -39,10 +40,6 @@ module ActionDispatch # # Note that changing digest or secret invalidates all existing sessions! class CookieStore - # Cookies can typically store 4096 bytes. - MAX = 4096 - SECRET_MIN_LENGTH = 30 # characters - DEFAULT_OPTIONS = { :key => '_session_id', :domain => nil, @@ -54,7 +51,7 @@ module ActionDispatch class OptionsHash < Hash def initialize(by, env, default_options) @session_data = env[CookieStore::ENV_SESSION_KEY] - default_options.each { |key, value| self[key] = value } + merge!(default_options) end def [](key) @@ -64,14 +61,12 @@ module ActionDispatch ENV_SESSION_KEY = "rack.session".freeze ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze - HTTP_SET_COOKIE = "Set-Cookie".freeze - - # Raised when storing more than 4K of session data. - class CookieOverflow < StandardError; end def initialize(app, options = {}) # Process legacy CGI options + # TODO Refactor and deprecate me options = options.symbolize_keys + if options.has_key?(:session_path) options[:path] = options.delete(:session_path) end @@ -88,15 +83,7 @@ module ActionDispatch ensure_session_key(options[:key]) @key = options.delete(:key).freeze - # The secret option is required. - ensure_secret_secure(options[:secret]) - @secret = options.delete(:secret).freeze - - @digest = options.delete(:digest) || 'SHA1' - @verifier = verifier_for(@secret, @digest) - @default_options = DEFAULT_OPTIONS.merge(options).freeze - freeze end @@ -111,66 +98,32 @@ module ActionDispatch if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) - session_data = marshal(session_data.to_hash) - - raise CookieOverflow if session_data.size > MAX + session_data = persistent_session_id!(session_data.to_hash) - cookie = Hash.new - cookie[:value] = session_data + cookie = { :value => session_data } unless options[:expire_after].nil? - cookie[:expires] = Time.now + options[:expire_after] + cookie[:expires] = Time.now + options.delete(:expire_after) end - cookie = build_cookie(@key, cookie.merge(options)) - unless headers[HTTP_SET_COOKIE].blank? - headers[HTTP_SET_COOKIE] << "\n#{cookie}" - else - headers[HTTP_SET_COOKIE] = cookie - end + request = ActionDispatch::Request.new(env) + request.cookie_jar.signed[@key] = cookie.merge!(options) end [status, headers, body] end private - # Should be in Rack::Utils soon - def build_cookie(key, value) - case value - when Hash - domain = "; domain=" + value[:domain] if value[:domain] - path = "; path=" + value[:path] if value[:path] - # According to RFC 2109, we need dashes here. - # N.B.: cgi.rb uses spaces... - expires = "; expires=" + value[:expires].clone.gmtime. - strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] - secure = "; secure" if value[:secure] - httponly = "; HttpOnly" if value[:httponly] - value = value[:value] - end - value = [value] unless Array === value - cookie = Rack::Utils.escape(key) + "=" + - value.map { |v| Rack::Utils.escape(v) }.join("&") + - "#{domain}#{path}#{expires}#{secure}#{httponly}" - end def load_session(env) - request = Rack::Request.new(env) - session_data = request.cookies[@key] - data = unmarshal(session_data) || persistent_session_id!({}) + request = ActionDispatch::Request.new(env) + data = request.cookie_jar.signed[@key] + data = persistent_session_id!(data || {}) data.stringify_keys! [data["session_id"], data] end - # Marshal a session hash into safe cookie data. Include an integrity hash. - def marshal(session) - @verifier.generate(persistent_session_id!(session)) - end - - # Unmarshal cookie data to a hash and verify its integrity. - def unmarshal(cookie) - persistent_session_id!(@verifier.verify(cookie)) if cookie - rescue ActiveSupport::MessageVerifier::InvalidSignature - nil + def generate_sid + ActiveSupport::SecureRandom.hex(16) end def ensure_session_key(key) @@ -182,38 +135,6 @@ module ActionDispatch end end - # 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) - # There's no way we can do this check if they've provided a proc for the - # secret. - return true if secret.is_a?(Proc) - - 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/application.rb" - end - - if secret.length < SECRET_MIN_LENGTH - raise ArgumentError, "Secret should be something secure, " + - "like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " + - "provided, \"#{secret}\", is shorter than the minimum length " + - "of #{SECRET_MIN_LENGTH} characters" - end - end - - def verifier_for(secret, digest) - key = secret.respond_to?(:call) ? secret.call : secret - ActiveSupport::MessageVerifier.new(key, digest) - end - - def generate_sid - ActiveSupport::SecureRandom.hex(16) - end - def persistent_session_id!(data) (data ||= {}).merge!(inject_persistent_session_id(data)) end -- cgit v1.2.3