mirror of
https://github.com/github/rails.git
synced 2026-01-09 14:48:08 -05:00
Merge pull request #31 from github/3-2-github+json-sessions
[3.2] JSON sessions
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user