require "test_helper" class RequestForgeryProtectionTest < ActionDispatch::IntegrationTest setup do sign_in_as :kevin @original_allow_forgery_protection = ActionController::Base.allow_forgery_protection ActionController::Base.allow_forgery_protection = true end teardown do ActionController::Base.allow_forgery_protection = @original_allow_forgery_protection end test "succeeds when CSRF token is valid" do assert_difference -> { Board.count }, +1 do post boards_path, params: { board: { name: "Test Board" }, authenticity_token: csrf_token }, headers: { "Sec-Fetch-Site" => "cross-site" } end assert_response :redirect end test "succeeds with same-origin Sec-Fetch-Site even without CSRF token" do assert_difference -> { Board.count }, +1 do post boards_path, params: { board: { name: "Test Board" } }, headers: { "Sec-Fetch-Site" => "same-origin" } end assert_response :redirect end test "succeeds with same-site Sec-Fetch-Site even without CSRF token" do assert_difference -> { Board.count }, +1 do post boards_path, params: { board: { name: "Test Board" } }, headers: { "Sec-Fetch-Site" => "same-site" } end assert_response :redirect end test "fails with cross-site Sec-Fetch-Site and no CSRF token" do assert_no_difference -> { Board.count } do post boards_path, params: { board: { name: "Test Board" } }, headers: { "Sec-Fetch-Site" => "cross-site" } end assert_response :unprocessable_entity end test "fails with none Sec-Fetch-Site and no CSRF token" do assert_no_difference -> { Board.count } do post boards_path, params: { board: { name: "Test Board" } }, headers: { "Sec-Fetch-Site" => "none" } end assert_response :unprocessable_entity end test "fails when Sec-Fetch-Site header is missing and no CSRF token" do assert_no_difference -> { Board.count } do post boards_path, params: { board: { name: "Test Board" } } end assert_response :unprocessable_entity end test "fails with invalid CSRF token and cross-site Sec-Fetch-Site" do assert_no_difference -> { Board.count } do post boards_path, params: { board: { name: "Test Board" }, authenticity_token: "invalid-token" }, headers: { "Sec-Fetch-Site" => "cross-site" } end assert_response :unprocessable_entity end test "GET requests succeed regardless of Sec-Fetch-Site header" do get board_path(boards(:writebook)), headers: { "Sec-Fetch-Site" => "cross-site" } assert_response :success end test "appends Sec-Fetch-Site to Vary header on GET requests" do get board_path(boards(:writebook)) assert_response :success assert_includes response.headers["Vary"], "Sec-Fetch-Site" end test "appends Sec-Fetch-Site to Vary header on POST requests" do post boards_path, params: { board: { name: "Test Board" } }, headers: { "Sec-Fetch-Site" => "same-origin" } assert_response :redirect assert_includes response.headers["Vary"], "Sec-Fetch-Site" end private def csrf_token @csrf_token ||= begin get new_board_path response.body[/name="authenticity_token" value="([^"]+)"/, 1] end end end