aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware/cookies.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware/cookies.rb')
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb54
1 files changed, 44 insertions, 10 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 42ab1d1ebb..87e8dd5010 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -52,9 +52,15 @@ module ActionDispatch
# * <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
+
+ # Raised when storing more than 4K of session data.
+ class CookieOverflow < StandardError; end
+
class CookieJar < Hash #:nodoc:
def self.build(request)
- secret = request.env["action_dispatch.secret_token"]
+ secret = request.env[TOKEN_KEY]
new(secret).tap do |hash|
hash.update(request.cookies)
end
@@ -134,9 +140,9 @@ module ActionDispatch
@signed ||= SignedCookieJar.new(self, @secret)
end
- def write(response)
- @set_cookies.each { |k, v| response.set_cookie(k, v) }
- @delete_cookies.each { |k, v| response.delete_cookie(k, v) }
+ def write(headers)
+ @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) }
+ @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
end
end
@@ -166,8 +172,11 @@ module ActionDispatch
end
class SignedCookieJar < CookieJar #:nodoc:
+ MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
+ SECRET_MIN_LENGTH = 30 # Characters
+
def initialize(parent_jar, secret)
- raise "You must set config.secret_token in your app's config" if secret.blank?
+ ensure_secret_secure(secret)
@parent_jar = parent_jar
@verifier = ActiveSupport::MessageVerifier.new(secret)
end
@@ -176,6 +185,8 @@ module ActionDispatch
if signed_message = @parent_jar[name]
@verifier.verify(signed_message)
end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
end
def []=(key, options)
@@ -186,12 +197,34 @@ module ActionDispatch
options = { :value => @verifier.generate(options) }
end
+ raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
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/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
end
def initialize(app)
@@ -202,12 +235,13 @@ module ActionDispatch
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
- response = Rack::Response.new(body, status, headers)
- cookie_jar.write(response)
- response.to_a
- else
- [status, headers, body]
+ cookie_jar.write(headers)
+ if headers[HTTP_HEADER].respond_to?(:join)
+ headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
+ end
end
+
+ [status, headers, body]
end
end
end