Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve matching of cookie assertions. #47066

Merged
merged 1 commit into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 89 additions & 0 deletions actionpack/test/abstract_unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,95 @@ def jruby_skip(message = "")
end
end

module CookieAssertions
def parse_set_cookie_attributes(fields, attributes = {})
if fields.is_a?(String)
fields = fields.split(";").map(&:strip)
end

fields.each do |field|
key, value = field.split("=", 2)

# Normalize the key to lowercase:
key.downcase!

if value
attributes[key] = value
else
attributes[key] = true
end
end

attributes
end

# Parse the set-cookie header and return a hash of cookie names and values.
#
# Example:
# set_cookies = headers["set-cookie"]
# parse_set_cookies_headers(set_cookies)
def parse_set_cookies_headers(set_cookies)
if set_cookies.is_a?(String)
set_cookies = set_cookies.split("\n")
end

cookies = {}

set_cookies&.each do |cookie_string|
attributes = {}

fields = cookie_string.split(";").map(&:strip)

# The first one is the cookie name:
name, value = fields.shift.split("=", 2)

attributes[:value] = value

cookies[name] = parse_set_cookie_attributes(fields, attributes)
end

cookies
end

def assert_set_cookie_header(expected, header = @response.headers["Set-Cookie"])
# In Rack v2, this is newline delimited. In Rack v3, this is an array.
# Normalize the comparison so that we can assert equality in both cases.

if header.is_a?(String)
header = header.split("\n").sort
end

if expected.is_a?(String)
expected = expected.split("\n").sort
end

# While not strictly speaking correct, this is probably good enough for now:
header = parse_set_cookies_headers(header)
expected = parse_set_cookies_headers(expected)

expected.each do |key, value|
assert_equal value, header[key]
end
end

def assert_not_set_cookie_header(expected, header = @response.headers["Set-Cookie"])
if header.is_a?(String)
header = header.split("\n").sort
end

if expected.is_a?(String)
expected = expected.split("\n").sort
end

# While not strictly speaking correct, this is probably good enough for now:
header = parse_set_cookies_headers(header)

expected.each do |name|
assert_not_includes(header, name)
end
end
end

class DrivenByRackTest < ActionDispatch::SystemTestCase
driven_by :rack_test
end
Expand Down
4 changes: 3 additions & 1 deletion actionpack/test/controller/integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,14 @@ def test_post
end
end

include CookieAssertions

test "response cookies are added to the cookie jar for the next request" do
with_test_route_set do
cookies["cookie_1"] = "sugar"
cookies["cookie_2"] = "oatmeal"
get "/cookie_monster"
assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"]
assert_set_cookie_header "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"]
assert_equal({ "cookie_1" => "", "cookie_2" => "oatmeal", "cookie_3" => "chocolate" }, cookies.to_hash)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1241,9 +1241,14 @@ def test_csrf_token_cookie_has_same_site_lax
assert_match "SameSite=Lax", @response.headers["Set-Cookie"]
end

include CookieAssertions

def test_csrf_token_cookie_is_http_only
get :cookie
assert_match "HttpOnly", @response.headers["Set-Cookie"]

cookies = parse_set_cookies_headers(@response.headers["Set-Cookie"])
csrf_token_cookie = cookies["csrf_token"]
assert csrf_token_cookie["httponly"]
end

def test_csrf_token_cookie_is_permanent
Expand Down