reset_session doesn't work properly when using
ActiveRecordStore, or probably other derivatives of
ActionController::Session::AbstractStore.
After a reset_session, when
ActionController::Session::AbstractStore generates a new session ID
and tries to set the session cookie, it creates an invalid header
instead.
Here are the relevant extracts from a debugging session with
rails 2.3.8 and rack 1.1.0, although the problem was not fixed by
upgrading rack to 1.2.1.
[127, 136] in
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
130 puts "abstract_store env session before call = #{env[ENV_SESSION_KEY]}"
131 response = @app.call(env)
=> 132 puts "abstract_store env session after call =
#{env[ENV_SESSION_KEY]}"
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb:132
puts "abstract_store env session after call =
#{env[ENV_SESSION_KEY]}"
(rdb:1) p env['rack.session.options'] {:path=>"/",
:httponly=>true, :domain=>nil, :expire_after=>nil,
:cookie_only=>true, :key=>"_session_id",
:secret=>"35be19722d70e3549c1707b87baf40f341585c32bd2c9c63cfb19902675647d6b94f07fdcc2b1dc77a5a62a03851bb1e2ccdff7722e6718b367a064a034ade01",
:secure=>false}
There is no saved session ID in
rack.session.options at this point...
[135, 144] in
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
135 options = env[ENV_SESSION_OPTIONS_KEY]
136
137 if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
138 session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
139
=> 140 sid = options[:id] || generate_sid
141
142 unless set_session(env, sid, session_data.to_hash)
143 return response
144 end
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb:140
sid = options[:id] || generate_sid
(rdb:1) p options[:id] nil
So we generate a new session ID here, and save it in the
database, then generate a new cookie:
[141, 150] in
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
141
142 unless set_session(env, sid, session_data.to_hash)
143 return response
144 end
145
=> 146 cookie = Rack::Utils.escape(@key) + '=' +
Rack::Utils.escape(sid)
147 cookie << "; domain=#{options[:domain]}" if options[:domain]
148 cookie << "; path=#{options[:path]}" if options[:path]
149 if options[:expire_after]
150 expiry = Time.now + options[:expire_after]
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb:146
cookie = Rack::Utils.escape(@key) + '=' +
Rack::Utils.escape(sid)
(rdb:1) p @key "_session_id"
(rdb:1) p sid "fca9862ce0015820072e85ae40262159"
Stepped through the rest of the cookie creation to the header
setting:
[153, 162] in
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
153 cookie << "; Secure" if options[:secure]
154 cookie << "; HttpOnly" if options[:httponly]
155
156 headers = response[1]
157 unless headers[SET_COOKIE].blank?
=> 158 headers[SET_COOKIE] << "\n#{cookie}"
159 else
160 headers[SET_COOKIE] = cookie
161 end
162 end
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb:158
headers[SET_COOKIE] << "\n#{cookie}"
(rdb:1) p headers[SET_COOKIE] ["auth_token=; path=/;
expires=Thu, 01-Jan-1970 00:00:00 GMT"]
Whoops, it's an Array, not a String, but we're about to append a
new element:
[159, 168] in
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
159 else
160 headers[SET_COOKIE] = cookie
161 end
162 end
163
=> 164 response
165 end
166
167 private
168 def generate_sid
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb:164
response
(rdb:1) p headers[SET_COOKIE] ["auth_token=; path=/;
expires=Thu, 01-Jan-1970 00:00:00 GMT",
"\n_session_id=fca9862ce0015820072e85ae40262159; path=/;
HttpOnly"]
Boom, this is not a good cookie. Note the newline before
_session_id, which will prevent it being used as the actual session
ID, so the next request resumes the old session rather than
starting a new one, meaning that reset_session had no effect.
We are now here:
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb:164:in
call'
-
/home/chris/project/minetoo/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:34:in
cache'
-
/home/chris/project/minetoo/vendor/rails/activerecord/lib/active_record/query_cache.rb:9:in
cache'
-
/home/chris/project/minetoo/vendor/rails/activerecord/lib/active_record/query_cache.rb:28:in
call'
-
/home/chris/project/minetoo/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb:361:in
call'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/failsafe.rb:26:in
call'
-
/home/chris/project/minetoo/vendor/gems/rack-1.1.0/lib/rack/lock.rb:11:in
call'
-
/home/chris/project/minetoo/vendor/gems/rack-1.1.0/lib/rack/lock.rb:11:in
synchronize'
-
/home/chris/project/minetoo/vendor/gems/rack-1.1.0/lib/rack/lock.rb:11:in
call'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/dispatcher.rb:110:in
call'
-
/home/chris/project/minetoo/vendor/gems/rack-1.1.0/lib/rack/lint.rb:47:in
_call'
-
/home/chris/project/minetoo/vendor/gems/rack-1.1.0/lib/rack/lint.rb:35:in
call'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/integration.rb:316:in
process'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/integration.rb:197:in
get'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/integration.rb:503:in
__send__'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/integration.rb:503:in
get'
- test/functional/about_controller_test.rb:329:in
test_about_page'
- test/functional/about_controller_test.rb:284:in
each'
- test/functional/about_controller_test.rb:284:in
test_about_page'
- test/functional/about_controller_test.rb:281:in
each'
- test/functional/about_controller_test.rb:281:in
test_about_page'
-
/home/chris/project/minetoo/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in
__send__'
-
/home/chris/project/minetoo/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in
run'
-
/home/chris/project/minetoo/vendor/rails/actionpack/lib/action_controller/integration.rb:661:in
run'
- /usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in
run'
- /usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in
each'
- /usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in
run'
- /usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in
run'
- /usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in
each'
- /usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in
run'
- /usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in
run_suite'
- /usr/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:67:in
start_mediator'
- /usr/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:41:in
start'
- /usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in
run'
- /usr/lib/ruby/1.8/test/unit/autorunner.rb:216:in
run'
- /usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in
run'
- /usr/lib/ruby/1.8/test/unit.rb:278
- test/functional/about_controller_test.rb:280
I think this is the same problem as #4743 Session Cookie breaks if used with custom cookie in rails 2.3.8, but
was not resolved along with #4743 Session Cookie breaks if used with custom cookie in rails 2.3.8 because AbstractStore contains a
(still broken) copy of the old header code that CookieStore used to
have, which assumed that headers were Strings. It seems from
#4743 Session Cookie breaks if used with custom cookie in rails 2.3.8 that Rack recently changed headers to be
Arrays instead of Strings, and all the duplicated copies of this
header handling code in Rails need to be updated.
I think you could demonstrate this bug by applying the same
tests written for #4743 Session Cookie breaks if used with custom cookie in rails 2.3.8 (from [85b6d79d8a17fdef667770e31b44ac6647f8b584])
with an ActiveRecordStore session store instead of CookieStore, but
I don't know how to run these tests or how to switch session store
on the fly.
I was able to resolve the problem with the following patch:
---
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.8/lib/action_controller/session/abstract_store.rb
2010-07-11 15:55:36.000000000 +0200 +++
vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
2010-07-11 16:04:43.000000000 +0200 @@ -150,11 +154,8 @@
cookie << "; HttpOnly" if options[:httponly]
headers = response[1]
-
unless headers[SET_COOKIE].blank?
-
headers[SET_COOKIE] << "\n#{cookie}"
-
else
-
headers[SET_COOKIE] = cookie
-
end
-
headers[SET_COOKIE] = [] if headers[SET_COOKIE].blank?
-
headers[SET_COOKIE] << cookie
end
response