1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
require 'abstract_unit'
require 'action_controller/cgi_process'
require 'action_controller/cgi_ext'
require 'stringio'
class CGI::Session::CookieStore
def ensure_secret_secure_with_test_hax(secret)
if secret == CookieStoreTest.default_session_options['secret']
return true
else
ensure_secret_secure_without_test_hax(secret)
end
end
alias_method_chain :ensure_secret_secure, :test_hax
end
# Expose for tests.
class CGI
attr_reader :output_cookies, :output_hidden
class Session
attr_reader :dbman
class CookieStore
attr_reader :data, :original, :cookie_options
end
end
end
class CookieStoreTest < Test::Unit::TestCase
def self.default_session_options
{ 'database_manager' => CGI::Session::CookieStore,
'session_key' => '_myapp_session',
'secret' => 'Keep it secret; keep it safe.',
'no_cookies' => true,
'no_hidden' => true }
end
def self.cookies
{ :empty => ['BAgw--0686dcaccc01040f4bd4f35fe160afe9bc04c330', {}],
:a_one => ['BAh7BiIGYWkG--5689059497d7f122a7119f171aef81dcfd807fec', { 'a' => 1 }],
:typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--9d20154623b9eeea05c62ab819be0e2483238759', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}],
:flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }] }
end
def setup
ENV.delete('HTTP_COOKIE')
end
def test_raises_argument_error_if_missing_session_key
[nil, ''].each do |blank|
assert_raise(ArgumentError, blank.inspect) { new_session 'session_key' => blank }
end
end
def test_raises_argument_error_if_missing_secret
[nil, ''].each do |blank|
assert_raise(ArgumentError, blank.inspect) { new_session 'secret' => blank }
end
end
def test_raises_argument_error_if_secret_is_probably_insecure
["password", "secret", "12345678901234567890123456789"].each do |blank|
assert_raise(ArgumentError, blank.inspect) { new_session 'secret' => blank }
end
end
def test_reconfigures_session_to_omit_id_cookie_and_hidden_field
new_session do |session|
assert_equal true, @options['no_hidden']
assert_equal true, @options['no_cookies']
end
end
def test_restore_unmarshals_missing_cookie_as_empty_hash
new_session do |session|
assert_nil session.dbman.data
assert_nil session['test']
assert_equal Hash.new, session.dbman.data
end
end
def test_restore_unmarshals_good_cookies
cookies(:empty, :a_one, :typical).each do |value, expected|
set_cookie! value
new_session do |session|
assert_nil session['lazy loads the data hash']
assert_equal expected, session.dbman.data
end
end
end
def test_restore_deletes_tampered_cookies
set_cookie! 'a--b'
new_session do |session|
assert_raise(CGI::Session::CookieStore::TamperedWithCookie) { session['fail'] }
assert_cookie_deleted session
end
end
def test_close_doesnt_write_cookie_if_data_is_blank
new_session do |session|
assert_no_cookies session
session.close
assert_no_cookies session
end
end
def test_close_doesnt_write_cookie_if_data_is_unchanged
set_cookie! cookie_value(:typical)
new_session do |session|
assert_no_cookies session
session['user_id'] = session['user_id']
session.close
assert_no_cookies session
end
end
def test_close_raises_when_data_overflows
set_cookie! cookie_value(:empty)
new_session do |session|
session['overflow'] = 'bye!' * 1024
assert_raise(CGI::Session::CookieStore::CookieOverflow) { session.close }
assert_no_cookies session
end
end
def test_close_marshals_and_writes_cookie
set_cookie! cookie_value(:typical)
new_session do |session|
assert_no_cookies session
session['flash'] = {}
assert_no_cookies session
session.close
assert_equal 1, session.cgi.output_cookies.size
cookie = session.cgi.output_cookies.first
assert_cookie cookie, cookie_value(:flashed)
end
end
def test_delete_writes_expired_empty_cookie_and_sets_data_to_nil
set_cookie! cookie_value(:typical)
new_session do |session|
assert_no_cookies session
session.delete
assert_cookie_deleted session
# @data is set to nil so #close doesn't send another cookie.
session.close
assert_cookie_deleted session
end
end
def test_new_session_doesnt_reuse_deleted_cookie_data
set_cookie! cookie_value(:typical)
new_session do |session|
assert_not_nil session['user_id']
session.delete
# Start a new session using the same CGI instance.
post_delete_session = CGI::Session.new(session.cgi, self.class.default_session_options)
assert_nil post_delete_session['user_id']
end
end
private
def assert_no_cookies(session)
assert_nil session.cgi.output_cookies, session.cgi.output_cookies.inspect
end
def assert_cookie_deleted(session, message = 'Expected session deletion cookie to be set')
assert_equal 1, session.cgi.output_cookies.size
cookie = session.cgi.output_cookies.first
assert_cookie cookie, nil, 1.year.ago.to_date, "#{message}: #{cookie.name} => #{cookie.value}"
end
def assert_cookie(cookie, value = nil, expires = nil, message = nil)
assert_equal '_myapp_session', cookie.name, message
assert_equal [value].compact, cookie.value, message
assert_equal expires, cookie.expires ? cookie.expires.to_date : cookie.expires, message
end
def cookies(*which)
self.class.cookies.values_at(*which)
end
def cookie_value(which)
self.class.cookies[which].first
end
def set_cookie!(value)
ENV['HTTP_COOKIE'] = "_myapp_session=#{value}"
end
def new_session(options = {})
with_cgi do |cgi|
assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}"
assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}"
@options = self.class.default_session_options.merge(options)
session = CGI::Session.new(cgi, @options)
assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}"
assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}"
yield session if block_given?
session
end
end
def with_cgi
ENV['REQUEST_METHOD'] = 'GET'
ENV['HTTP_HOST'] = 'example.com'
ENV['QUERY_STRING'] = ''
cgi = CGI.new('query', StringIO.new(''))
yield cgi if block_given?
cgi
end
end
class CookieStoreWithBlockAsSecretTest < CookieStoreTest
def self.default_session_options
CookieStoreTest.default_session_options.merge 'secret' => Proc.new { 'Keep it secret; keep it safe.' }
end
end
class CookieStoreWithMD5DigestTest < CookieStoreTest
def self.default_session_options
CookieStoreTest.default_session_options.merge 'digest' => 'MD5'
end
def self.cookies
{ :empty => ['BAgw--0415cc0be9579b14afc22ee2d341aa21', {}],
:a_one => ['BAh7BiIGYWkG--5a0ed962089cc6600ff44168a5d59bc8', { 'a' => 1 }],
:typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--f426763f6ef435b3738b493600db8d64', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}],
:flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--0af9156650dab044a53a91a4ddec2c51', { 'user_id' => 123, 'flash' => {} }] }
end
end
|