Merge pull request #31 from github/3-2-github+json-sessions

[3.2] JSON sessions
This commit is contained in:
Charlie Somerville
2014-09-26 15:32:46 +10:00
7 changed files with 176 additions and 25 deletions

View File

@@ -489,6 +489,7 @@ module ActionController
@controller.recycle!
@controller.process_with_new_base_test(@request, @response)
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session['flash'] = @request.flash.to_session_value
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end

View File

@@ -81,6 +81,7 @@ module ActionDispatch
class Cookies
HTTP_HEADER = "Set-Cookie".freeze
TOKEN_KEY = "action_dispatch.secret_token".freeze
SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
@@ -106,13 +107,14 @@ module ActionDispatch
secret = request.env[TOKEN_KEY]
host = request.host
secure = request.ssl?
serializer = request.env[SESSION_SERIALIZER]
new(secret, host, secure).tap do |hash|
new(secret, host, secure, serializer).tap do |hash|
hash.update(request.cookies)
end
end
def initialize(secret = nil, host = nil, secure = false)
def initialize(secret = nil, host = nil, secure = false, serializer = nil)
@secret = secret
@set_cookies = {}
@delete_cookies = {}
@@ -120,6 +122,7 @@ module ActionDispatch
@secure = secure
@closed = false
@cookies = {}
@serializer = serializer
end
def each(&block)
@@ -211,7 +214,7 @@ module ActionDispatch
# 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, @secret, @serializer)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@@ -228,7 +231,7 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
@signed ||= SignedCookieJar.new(self, @secret)
@signed ||= SignedCookieJar.new(self, @secret, @serializer)
end
def write(headers)
@@ -252,8 +255,8 @@ module ActionDispatch
end
class PermanentCookieJar < CookieJar #:nodoc:
def initialize(parent_jar, secret)
@parent_jar, @secret = parent_jar, secret
def initialize(parent_jar, secret, serializer)
@parent_jar, @secret, @serializer = parent_jar, secret, serializer
end
def []=(key, options)
@@ -268,7 +271,7 @@ module ActionDispatch
end
def signed
@signed ||= SignedCookieJar.new(self, @secret)
@signed ||= SignedCookieJar.new(self, @secret, @serializer)
end
def method_missing(method, *arguments, &block)
@@ -280,10 +283,10 @@ module ActionDispatch
MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
SECRET_MIN_LENGTH = 30 # Characters
def initialize(parent_jar, secret)
def initialize(parent_jar, secret, serializer)
ensure_secret_secure(secret)
@parent_jar = parent_jar
@verifier = ActiveSupport::MessageVerifier.new(secret)
@verifier = ActiveSupport::MessageVerifier.new(secret, :serializer => serializer)
end
def [](name)

View File

@@ -4,7 +4,7 @@ module ActionDispatch
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
@env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new)
@env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
end
end
@@ -77,10 +77,24 @@ module ActionDispatch
class FlashHash
include Enumerable
def initialize #:nodoc:
@used = Set.new
def self.from_session_value(value)
case value
when Hash # After github/github-rails#9, only plain Hashes are in the session
new(value['flashes'], value['discard'])
else
new
end
end
def to_session_value
return nil if empty?
{'discard' => @used.to_a, 'flashes' => @flashes}
end
def initialize(flashes = {}, discard = []) #:nodoc:
@used = Set.new(discard)
@closed = false
@flashes = {}
@flashes = flashes
@now = nil
end
@@ -93,15 +107,17 @@ module ActionDispatch
end
def []=(k, v) #:nodoc:
k = k.to_s
keep(k)
@flashes[k] = v
end
def [](k)
@flashes[k]
@flashes[k.to_s]
end
def update(h) #:nodoc:
h.stringify_keys!
h.keys.each { |k| keep(k) }
@flashes.update h
self
@@ -112,11 +128,11 @@ module ActionDispatch
end
def key?(name)
@flashes.key? name
@flashes.key? name.to_s
end
def delete(key)
@flashes.delete key
@flashes.delete key.to_s
self
end
@@ -140,7 +156,7 @@ module ActionDispatch
def replace(h) #:nodoc:
@used = Set.new
@flashes.replace h
@flashes.replace h.stringify_keys
self
end
@@ -235,8 +251,9 @@ module ActionDispatch
end
def call(env)
if (session = env['rack.session']) && (flash = session['flash'])
if (session = env['rack.session']) && (flash = Flash::FlashHash.from_session_value(session["flash"]))
flash.sweep
env[KEY] = flash
end
@app.call(env)
@@ -246,7 +263,7 @@ module ActionDispatch
if flash_hash
if !flash_hash.empty? || session.key?('flash')
session["flash"] = flash_hash
session["flash"] = flash_hash.to_session_value
new_hash = flash_hash.dup
else
new_hash = flash_hash
@@ -255,7 +272,7 @@ module ActionDispatch
env[KEY] = new_hash
end
if session.key?('flash') && session['flash'].empty?
if session['flash'] && session['flash'].empty?
session.delete('flash')
end
end

View File

@@ -1,3 +1,5 @@
# Upstream FlashHash tests from:
# https://github.com/rails/rails/blob/a6ce984b49519de7701aa13d04300c9d03cf8f72/actionpack/test/controller/flash_hash_test.rb
require 'abstract_unit'
module ActionDispatch
@@ -46,6 +48,31 @@ module ActionDispatch
assert_equal({'foo' => 'bar'}, @hash.to_hash)
end
def test_to_session_value
@hash['foo'] = 'bar'
assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value)
@hash.discard('foo')
assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value)
@hash.now['qux'] = 1
assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value)
@hash.sweep
assert_equal(nil, @hash.to_session_value)
end
def test_from_session_value_on_json_serializer
decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
session = ActiveSupport::JSON.decode(decrypted_data)
hash = Flash::FlashHash.from_session_value(session['flash'])
hash.sweep
assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
assert_equal "hey you", hash[:message]
assert_equal "hey you", hash["message"]
end
def test_empty?
assert @hash.empty?
@hash['zomg'] = 'bears'
@@ -75,6 +102,7 @@ module ActionDispatch
def test_discard_no_args
@hash['hello'] = 'world'
@hash.discard
@hash.sweep
assert_equal({}, @hash.to_hash)
end
@@ -83,8 +111,88 @@ module ActionDispatch
@hash['hello'] = 'world'
@hash['omg'] = 'world'
@hash.discard 'hello'
@hash.sweep
assert_equal({'omg' => 'world'}, @hash.to_hash)
end
def test_keep_sweep
@hash['hello'] = 'world'
@hash.sweep
assert_equal({'hello' => 'world'}, @hash.to_hash)
end
def test_update_sweep
@hash['hello'] = 'world'
@hash.update({'hi' => 'mom'})
@hash.sweep
assert_equal({'hello' => 'world', 'hi' => 'mom'}, @hash.to_hash)
end
def test_update_delete_sweep
@hash['hello'] = 'world'
@hash.delete 'hello'
@hash.update({'hello' => 'mom'})
@hash.sweep
assert_equal({'hello' => 'mom'}, @hash.to_hash)
end
def test_delete_sweep
@hash['hello'] = 'world'
@hash['hi'] = 'mom'
@hash.delete 'hi'
@hash.sweep
assert_equal({'hello' => 'world'}, @hash.to_hash)
end
def test_clear_sweep
@hash['hello'] = 'world'
@hash.clear
@hash.sweep
assert_equal({}, @hash.to_hash)
end
def test_replace_sweep
@hash['hello'] = 'world'
@hash.replace({'hi' => 'mom'})
@hash.sweep
assert_equal({'hi' => 'mom'}, @hash.to_hash)
end
def test_discard_then_add
@hash['hello'] = 'world'
@hash['omg'] = 'world'
@hash.discard 'hello'
@hash['hello'] = 'world'
@hash.sweep
assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash)
end
def test_keep_all_sweep
@hash['hello'] = 'world'
@hash['omg'] = 'world'
@hash.discard 'hello'
@hash.keep
@hash.sweep
assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash)
end
def test_double_sweep
@hash['hello'] = 'world'
@hash.sweep
assert_equal({'hello' => 'world'}, @hash.to_hash)
@hash.sweep
assert_equal({}, @hash.to_hash)
end
end
end

View File

@@ -174,13 +174,13 @@ class FlashTest < ActionController::TestCase
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existant key passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed
assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existant key passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed
assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert

View File

@@ -63,6 +63,11 @@ class CookiesTest < ActionController::TestCase
head :ok
end
def set_signed_string_cookie
cookies.signed[:foo] = 'bar'
head :ok
end
def raise_data_overflow
cookies.signed[:foo] = 'bye!' * 1024
head :ok
@@ -332,6 +337,22 @@ class CookiesTest < ActionController::TestCase
}
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_signed_cookie_using_serializer_object
@request.env["action_dispatch.session_serializer"] = ActionDispatch::Session::CustomJsonSerializer
get :set_signed_string_cookie
assert_equal 'bar was dumped and loaded', @controller.send(:cookies).signed[:foo]
end
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success

View File

@@ -181,7 +181,8 @@ module Rails
"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.backtrace_cleaner" => Rails.backtrace_cleaner,
"action_dispatch.session_serializer" => config.session_options[:serializer],
})
end