Files
prism/test/test_cookie_jar.rb

125 lines
4.2 KiB
Ruby

require 'minitest/autorun'
require 'prism'
require 'stringio'
class CookieJarTest < Minitest::Test
def setup
@secret = 'jar-secret-key'
end
def jar_for(cookie_header, secret: @secret, previous_secrets: [])
env = {
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/',
'QUERY_STRING' => '',
'rack.input' => StringIO.new(''),
'CONTENT_LENGTH' => '0'
}
env['HTTP_COOKIE'] = cookie_header if cookie_header
request = Prism::Request.new(env)
response = Prism::Response.new
[Prism::CookieJar.new(request, response, secret: secret, previous_secrets: previous_secrets), response]
end
def test_plain_set_and_read
jar, response = jar_for(nil)
jar[:theme] = 'dark'
assert_match(/theme=dark/, response['Set-Cookie'])
end
def test_signed_round_trip_via_two_requests
jar, response = jar_for(nil)
jar.signed[:user_id] = 42
cookie_header = extract_cookie_header(response, 'user_id')
reader_jar, = jar_for(cookie_header)
assert_equal 42, reader_jar.signed[:user_id]
end
def test_signed_read_rejects_tampered_cookie
jar, response = jar_for(nil)
jar.signed[:user_id] = 42
cookie_header = extract_cookie_header(response, 'user_id')
tampered = cookie_header.sub('user_id=', 'user_id=tampered')
reader_jar, = jar_for(tampered)
assert_nil reader_jar.signed[:user_id]
end
def test_encrypted_round_trip_hides_plaintext
jar, response = jar_for(nil)
jar.encrypted[:secret] = 'super-sensitive-value'
cookie_header = extract_cookie_header(response, 'secret')
refute_includes cookie_header, 'super-sensitive-value'
reader_jar, = jar_for(cookie_header)
assert_equal 'super-sensitive-value', reader_jar.encrypted[:secret]
end
def test_encrypted_read_rejects_tampered_cookie
jar, response = jar_for(nil)
jar.encrypted[:secret] = 'value'
cookie_header = extract_cookie_header(response, 'secret')
tampered = cookie_header + 'AA'
reader_jar, = jar_for(tampered)
assert_nil reader_jar.encrypted[:secret]
end
def test_missing_cookie_returns_nil_for_signed_and_encrypted
jar, = jar_for(nil)
assert_nil jar.signed[:missing]
assert_nil jar.encrypted[:missing]
end
def test_value_hash_form_passes_through_cookie_options
jar, response = jar_for(nil)
jar.signed[:remember] = { value: 'token', max_age: 3600 }
header = response['Set-Cookie']
assert_match(/Max-Age=3600/, header)
end
def test_signed_previous_secret_still_decodes_after_rotation
old_jar, response = jar_for(nil, secret: 'old-jar-secret')
old_jar.signed[:user_id] = 42
cookie_header = extract_cookie_header(response, 'user_id')
rotated_jar, = jar_for(cookie_header, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret'])
assert_equal 42, rotated_jar.signed[:user_id]
end
def test_encrypted_previous_secret_still_decodes_after_rotation
old_jar, response = jar_for(nil, secret: 'old-jar-secret')
old_jar.encrypted[:secret] = 'value-from-before-rotation'
cookie_header = extract_cookie_header(response, 'secret')
rotated_jar, = jar_for(cookie_header, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret'])
assert_equal 'value-from-before-rotation', rotated_jar.encrypted[:secret]
end
def test_signed_read_rejects_a_secret_that_was_never_current_or_previous
old_jar, response = jar_for(nil, secret: 'unrelated-secret')
old_jar.signed[:user_id] = 42
cookie_header = extract_cookie_header(response, 'user_id')
rotated_jar, = jar_for(cookie_header, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret'])
assert_nil rotated_jar.signed[:user_id]
end
def test_writes_after_rotation_use_the_current_secret_not_a_previous_one
jar, response = jar_for(nil, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret'])
jar.signed[:user_id] = 42
cookie_header = extract_cookie_header(response, 'user_id')
reader_on_old_secret_only, = jar_for(cookie_header, secret: 'old-jar-secret')
assert_nil reader_on_old_secret_only.signed[:user_id]
end
private
def extract_cookie_header(response, name)
line = response['Set-Cookie'].split("\n").find { |entry| entry.start_with?("#{name}=") }
line.split(';').first
end
end