blob: 0f8c827ecd9fa200e1c729a828b9beb3c5791ea9 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/interest_group/ad_auction_headers_util.h"
#include <functional>
#include <string>
#include <string_view>
#include "base/base64url.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "content/browser/interest_group/ad_auction_page_data.h"
#include "content/browser/interest_group/header_direct_from_seller_signals.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/loader/subresource_proxying_url_loader_service.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_render_frame_host.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_version.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
constexpr char kLegitimateAdAuctionResponse[] =
"ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0=";
constexpr char kLegitimateAdAuctionNonceResponse[] =
"00000000-0000-0000-0000-000000000000";
constexpr char kLegitimateAdAuctionSignals[] =
R"([{"adSlot":"slot1", "sellerSignals":{"signal1":"value1"}}])";
std::string Base64UrlDecode(std::string_view input) {
std::string bytes;
CHECK(base::Base64UrlDecode(
input, base::Base64UrlDecodePolicy::IGNORE_PADDING, &bytes));
return bytes;
}
class InterceptingContentBrowserClient : public ContentBrowserClient {
public:
bool IsInterestGroupAPIAllowed(content::BrowserContext* browser_context,
RenderFrameHost* render_frame_host,
InterestGroupApiOperation operation,
const url::Origin& top_frame_origin,
const url::Origin& api_origin) override {
return interest_group_allowed_by_settings_;
}
void set_interest_group_allowed_by_settings(bool allowed) {
interest_group_allowed_by_settings_ = allowed;
}
private:
bool interest_group_allowed_by_settings_ = false;
};
network::ParsedPermissionsPolicy CreatePermissivePolicy() {
network::ParsedPermissionsPolicy policy;
policy.emplace_back(
network::mojom::PermissionsPolicyFeature::kRunAdAuction,
/*allowed_origins=*/
std::vector{*network::OriginWithPossibleWildcards::FromOrigin(
url::Origin::Create(GURL("https://google.com"))),
*network::OriginWithPossibleWildcards::FromOrigin(
url::Origin::Create(GURL("https://foo1.com")))},
/*self_if_matches=*/std::nullopt,
/*matches_all_origins=*/false,
/*matches_opaque_src=*/false);
return policy;
}
network::ParsedPermissionsPolicy CreateRestrictivePolicy() {
network::ParsedPermissionsPolicy policy;
policy.emplace_back(
network::mojom::PermissionsPolicyFeature::kRunAdAuction,
/*allowed_origins=*/std::vector<network::OriginWithPossibleWildcards>(),
/*self_if_matches=*/std::nullopt,
/*matches_all_origins=*/false,
/*matches_opaque_src=*/false);
return policy;
}
} // namespace
class IsAdAuctionHeadersEligibleTest
: public RenderViewHostTestHarness,
public testing::WithParamInterface<bool> {
public:
IsAdAuctionHeadersEligibleTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{network::features::kInterestGroupStorage},
/*disabled_features=*/{});
}
void SetUp() override {
RenderViewHostTestHarness::SetUp();
original_client_ = SetBrowserClientForTesting(&browser_client_);
browser_client_.set_interest_group_allowed_by_settings(true);
}
void TearDown() override {
SetBrowserClientForTesting(original_client_);
RenderViewHostTestHarness::TearDown();
}
RenderFrameHostImpl& NavigatePage(const GURL& url) {
auto main_frame_navigation =
NavigationSimulator::CreateBrowserInitiated(url, web_contents());
main_frame_navigation->SetPermissionsPolicyHeader(CreatePermissivePolicy());
main_frame_navigation->Commit();
if (GetParam()) {
return static_cast<RenderFrameHostImpl&>(
*main_frame_navigation->GetFinalRenderFrameHost());
}
TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
->AppendChild("child0"));
auto subframe_navigation =
NavigationSimulator::CreateRendererInitiated(url, subframe);
subframe_navigation->SetPermissionsPolicyHeader(CreatePermissivePolicy());
subframe_navigation->Commit();
return static_cast<RenderFrameHostImpl&>(
*subframe_navigation->GetFinalRenderFrameHost());
}
network::ResourceRequest CreateResourceRequest(const GURL& url) {
network::ResourceRequest resource_request;
resource_request.url = url;
resource_request.ad_auction_headers = true;
return resource_request;
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
InterceptingContentBrowserClient browser_client_;
raw_ptr<ContentBrowserClient> original_client_ = nullptr;
};
TEST_P(IsAdAuctionHeadersEligibleTest, RequestIsEligible) {
GURL test_url("https://google.com");
RenderFrameHostImpl& render_frame_host = NavigatePage(test_url);
EXPECT_TRUE(IsAdAuctionHeadersEligible(render_frame_host,
CreateResourceRequest(test_url)));
}
TEST_P(IsAdAuctionHeadersEligibleTest, NotEligibleDueToUntrustworthyOrigin) {
GURL test_url("http://google.com");
RenderFrameHostImpl& render_frame_host = NavigatePage(test_url);
EXPECT_FALSE(IsAdAuctionHeadersEligible(render_frame_host,
CreateResourceRequest(test_url)));
}
TEST_P(IsAdAuctionHeadersEligibleTest, NotEligibleDueToOpaqueOrigin) {
GURL test_url("https://google.com");
RenderFrameHostImpl& render_frame_host = NavigatePage(test_url);
network::ResourceRequest resource_request =
CreateResourceRequest(GURL("data:image/jpeg;base64,UklGRjwCAAA="));
EXPECT_FALSE(IsAdAuctionHeadersEligible(render_frame_host, resource_request));
}
TEST_P(IsAdAuctionHeadersEligibleTest, NotEligibleDueToInactiveFrame) {
GURL test_url("https://google.com");
RenderFrameHostImpl& render_frame_host = NavigatePage(test_url);
render_frame_host.GetMainFrame()->SetLifecycleState(
RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted);
EXPECT_FALSE(IsAdAuctionHeadersEligible(render_frame_host,
CreateResourceRequest(test_url)));
}
TEST_P(IsAdAuctionHeadersEligibleTest, NotEligibleDueToPermissionsPolicy) {
GURL test_url("https://google.com");
RenderFrameHostImpl& render_frame_host = NavigatePage(test_url);
// The permissions policy disallows `foo2.com`. The request won't be eligible
// for ad auction headers.
GURL unpermitted_url("https://foo2.com");
EXPECT_FALSE(IsAdAuctionHeadersEligible(
render_frame_host, CreateResourceRequest(unpermitted_url)));
}
TEST_P(IsAdAuctionHeadersEligibleTest,
NotEligibleDueToInterestGroupAPINotAllowed) {
browser_client_.set_interest_group_allowed_by_settings(false);
GURL test_url("https://google.com");
RenderFrameHostImpl& render_frame_host = NavigatePage(test_url);
EXPECT_FALSE(IsAdAuctionHeadersEligible(render_frame_host,
CreateResourceRequest(test_url)));
}
INSTANTIATE_TEST_SUITE_P(HasOptionalResourceRequest,
IsAdAuctionHeadersEligibleTest,
testing::Bool(),
[](const testing::TestParamInfo<bool>& info) {
return info.param ? "RequestFromMainFrame"
: "RequestFromIFrame";
});
class IsAdAuctionHeadersEligibleForNavigationTest
: public RenderViewHostTestHarness {
public:
IsAdAuctionHeadersEligibleForNavigationTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{network::features::kInterestGroupStorage},
/*disabled_features=*/{});
}
void SetUp() override {
RenderViewHostTestHarness::SetUp();
original_client_ = SetBrowserClientForTesting(&browser_client_);
browser_client_.set_interest_group_allowed_by_settings(true);
}
void TearDown() override {
SetBrowserClientForTesting(original_client_);
RenderViewHostTestHarness::TearDown();
}
const FrameTreeNode& NavigatePage(const GURL& url) {
auto main_frame_navigation =
NavigationSimulator::CreateBrowserInitiated(url, web_contents());
main_frame_navigation->SetPermissionsPolicyHeader(CreatePermissivePolicy());
main_frame_navigation->Commit();
TestRenderFrameHost* subframe = static_cast<TestRenderFrameHost*>(
RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
->AppendChild("child0"));
auto subframe_navigation =
NavigationSimulator::CreateRendererInitiated(url, subframe);
// IFrame navigations should use its parent frame's permission policy, and
// so this iframe's permission policy should be restrictive.
subframe_navigation->SetPermissionsPolicyHeader(CreateRestrictivePolicy());
subframe_navigation->Commit();
return *static_cast<RenderFrameHostImpl*>(
subframe_navigation->GetFinalRenderFrameHost())
->frame_tree_node();
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
InterceptingContentBrowserClient browser_client_;
raw_ptr<ContentBrowserClient> original_client_ = nullptr;
};
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest, RequestIsEligible) {
GURL test_url("https://google.com");
const FrameTreeNode& frame = NavigatePage(test_url);
EXPECT_TRUE(IsAdAuctionHeadersEligibleForNavigation(
frame, url::Origin::Create(test_url)));
}
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest,
NotEligibleDueToUntrustworthyOrigin) {
GURL test_url("http://google.com");
const FrameTreeNode& frame = NavigatePage(test_url);
EXPECT_FALSE(IsAdAuctionHeadersEligibleForNavigation(
frame, url::Origin::Create(test_url)));
}
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest,
NotEligibleDueToNonChildFrameProvided) {
GURL test_url("https://google.com");
auto main_frame_navigation =
NavigationSimulator::CreateBrowserInitiated(test_url, web_contents());
main_frame_navigation->SetPermissionsPolicyHeader(CreatePermissivePolicy());
main_frame_navigation->Commit();
const FrameTreeNode& frame =
*static_cast<RenderFrameHostImpl*>(
main_frame_navigation->GetFinalRenderFrameHost())
->frame_tree_node();
EXPECT_FALSE(IsAdAuctionHeadersEligibleForNavigation(
frame, url::Origin::Create(test_url)));
}
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest,
NotEligibleDueToOpaqueOrigin) {
GURL test_url("https://google.com");
const FrameTreeNode& frame = NavigatePage(test_url);
EXPECT_FALSE(IsAdAuctionHeadersEligibleForNavigation(frame, url::Origin()));
}
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest,
NotEligibleDueToInactiveFrame) {
GURL test_url("https://google.com");
const FrameTreeNode& frame = NavigatePage(test_url);
frame.GetParentOrOuterDocument()->SetLifecycleState(
RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted);
EXPECT_FALSE(IsAdAuctionHeadersEligibleForNavigation(
frame, url::Origin::Create(test_url)));
}
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest,
NotEligibleDueToPermissionsPolicy) {
GURL test_url("https://google.com");
const FrameTreeNode& frame = NavigatePage(test_url);
// The permissions policy disallows `foo2.com`. The request won't be eligible
// for ad auction headers.
GURL unpermitted_url("https://foo2.com");
EXPECT_FALSE(IsAdAuctionHeadersEligibleForNavigation(
frame, url::Origin::Create(unpermitted_url)));
}
TEST_F(IsAdAuctionHeadersEligibleForNavigationTest,
NotEligibleDueToInterestGroupAPINotAllowed) {
browser_client_.set_interest_group_allowed_by_settings(false);
GURL test_url("https://google.com");
const FrameTreeNode& frame = NavigatePage(test_url);
EXPECT_FALSE(IsAdAuctionHeadersEligibleForNavigation(
frame, url::Origin::Create(test_url)));
}
class ProcessAdAuctionResponseHeadersTest : public RenderViewHostTestHarness {
public:
ProcessAdAuctionResponseHeadersTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{blink::features::kFledgeBiddingAndAuctionServer,
blink::features::kAdAuctionSignals},
/*disabled_features=*/{});
}
bool WitnessedAuctionResultForOrigin(const url::Origin& origin,
const std::string& response) {
Page& page = web_contents()->GetPrimaryPage();
AdAuctionPageData* ad_auction_page_data =
PageUserData<AdAuctionPageData>::GetOrCreateForPage(page);
return ad_auction_page_data->WitnessedAuctionResultForOrigin(origin,
response);
}
bool WitnessedAuctionResultNonceForOrigin(const url::Origin& origin,
const std::string& nonce) {
Page& page = web_contents()->GetPrimaryPage();
AdAuctionPageData* ad_auction_page_data =
PageUserData<AdAuctionPageData>::GetOrCreateForPage(page);
return ad_auction_page_data->WitnessedAuctionResultNonceForOrigin(origin,
nonce);
}
const scoped_refptr<HeaderDirectFromSellerSignals::Result>
ParseAndFindAdAuctionSignals(const url::Origin& origin,
const std::string& ad_slot) {
Page& page = web_contents()->GetPrimaryPage();
AdAuctionPageData* ad_auction_page_data =
PageUserData<AdAuctionPageData>::GetOrCreateForPage(page);
scoped_refptr<HeaderDirectFromSellerSignals::Result> my_result;
base::RunLoop run_loop;
ad_auction_page_data->ParseAndFindAdAuctionSignals(
origin, ad_slot,
base::BindLambdaForTesting(
[&my_result, &run_loop](
scoped_refptr<HeaderDirectFromSellerSignals::Result> result) {
my_result = std::move(result);
run_loop.Quit();
}));
run_loop.Run();
return my_result;
}
std::vector<SignedAdditionalBidWithMetadata>
TakeAuctionAdditionalBidsForOriginAndNonce(const url::Origin& origin,
const std::string& nonce) {
Page& page = web_contents()->GetPrimaryPage();
AdAuctionPageData* ad_auction_page_data =
PageUserData<AdAuctionPageData>::GetOrCreateForPage(page);
return ad_auction_page_data->TakeAuctionAdditionalBidsForOriginAndNonce(
origin, nonce);
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(ProcessAdAuctionResponseHeadersTest,
MultipleAdAuctionResults_AllWitnessed) {
const char kLegitimateAdAuctionResponse2[] =
"8oX0szl-BNWitSuE3ZK5Npt05t83A1wrl94oBtlZHFs=";
const char kLegitimateAdAuctionResponse3[] =
"lIcI37kQp_ArBk_1JfdEjyQ0suSLUpYDIKO906THBdk=";
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey,
kLegitimateAdAuctionResponse);
std::string concatenated_header =
base::StrCat({kLegitimateAdAuctionResponse2, ",", "invalid", ",",
kLegitimateAdAuctionResponse3});
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey,
concatenated_header);
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey, "alsoInvalid");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// Unlike the signals and additional bid headers, the result header is not
// removed when it's stored in the browser.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));
EXPECT_TRUE(WitnessedAuctionResultForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
Base64UrlDecode(kLegitimateAdAuctionResponse)));
EXPECT_TRUE(WitnessedAuctionResultForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
Base64UrlDecode(kLegitimateAdAuctionResponse2)));
EXPECT_TRUE(WitnessedAuctionResultForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
Base64UrlDecode(kLegitimateAdAuctionResponse3)));
}
TEST_F(ProcessAdAuctionResponseHeadersTest,
InvalidAdAuctionResultResponseHeader) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey,
"invalid-response-header");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// Unlike the signals and additional bid headers, the result header is not
// removed when it's stored in the browser.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));
EXPECT_FALSE(WitnessedAuctionResultForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
"invalid-response-header"));
}
class ProcessAdAuctionNonceResponseHeadersTest
: public ProcessAdAuctionResponseHeadersTest {
public:
ProcessAdAuctionNonceResponseHeadersTest() {
scoped_feature_list_.InitAndEnableFeature(
features::kFledgeBiddingAndAuctionNonceSupport);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(ProcessAdAuctionNonceResponseHeadersTest,
MultipleAdAuctionResultNonces_AllWitnessed) {
const char kLegitimateAdAuctionNonceResponse2[] =
"1000000A-0000-0000-0000-000000000000";
const char kLegitimateAdAuctionNonceResponse3[] =
"2000000b-0000-0000-0000-000000000000";
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
kLegitimateAdAuctionNonceResponse);
std::string concatenated_header =
base::StrCat({kLegitimateAdAuctionNonceResponse2, ",", "invalid", ",",
kLegitimateAdAuctionNonceResponse3});
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
concatenated_header);
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
"alsoInvalid");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The result nonce header is removed when it's stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionResultNonceResponseHeaderKey));
EXPECT_TRUE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
kLegitimateAdAuctionNonceResponse));
EXPECT_TRUE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
// lower-case version of kLegitimateAdAuctionNonceResponse2
"1000000a-0000-0000-0000-000000000000"));
EXPECT_TRUE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
kLegitimateAdAuctionNonceResponse3));
}
TEST_F(ProcessAdAuctionNonceResponseHeadersTest,
InvalidAdAuctionResultNonceResponseHeader) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
"invalid-response-header");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The result header is not removed when it's stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionResultNonceResponseHeaderKey));
EXPECT_FALSE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
"invalid-response-header"));
}
TEST_F(ProcessAdAuctionResponseHeadersTest, AdAuctionSignalsResponseHeader) {
data_decoder::test::InProcessDataDecoder in_process_data_decoder;
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionSignalsResponseHeaderKey,
kLegitimateAdAuctionSignals);
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Signals` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionSignalsResponseHeaderKey));
const scoped_refptr<HeaderDirectFromSellerSignals::Result> signals =
ParseAndFindAdAuctionSignals(
url::Origin::Create(GURL("https://foo1.com")), "slot1");
EXPECT_EQ(*signals->seller_signals(), R"({"signal1":"value1"})");
}
TEST_F(ProcessAdAuctionResponseHeadersTest,
AdAuctionSignalsResponseHeaderTooLong) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
// This test value for the `Ad-Auction-Signals` header doesn't have to be
// valid JSON, since it doesn't get as far as JSON parsing.
// `ProcessAdAuctionResponseHeaders` discards this excessively long header
// before involving `AdAuctionPageData`.
std::string very_long_header_value(10001, '0');
headers_builder.AddHeader(kAdAuctionSignalsResponseHeaderKey,
very_long_header_value);
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Signals` header was removed from the headers, even though
// it wasn't stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionSignalsResponseHeaderKey));
const scoped_refptr<HeaderDirectFromSellerSignals::Result> signals =
ParseAndFindAdAuctionSignals(
url::Origin::Create(GURL("https://foo1.com")), "slot1");
EXPECT_EQ(signals, nullptr);
}
TEST_F(ProcessAdAuctionResponseHeadersTest, AdditionalBid) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:e30=");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
std::vector<SignedAdditionalBidWithMetadata> additional_bid =
TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000");
ASSERT_EQ(additional_bid.size(), 1u);
EXPECT_EQ(additional_bid[0].signed_additional_bid, "e30=");
EXPECT_EQ(additional_bid[0].seller_nonce, std::nullopt);
// Future calls to `TakeAuctionAdditionalBidsForOriginAndNonce` on the same
// origin and nonce should return nothing. Ideally this should be tested
// separately as a unitest for `AdAuctionPageData`.
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::IsEmpty());
}
TEST_F(ProcessAdAuctionResponseHeadersTest, AdditionalBidWithNoSellerNonce) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:e30=");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
std::vector<SignedAdditionalBidWithMetadata> additional_bids =
TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000");
ASSERT_EQ(additional_bids.size(), 1u);
EXPECT_EQ(additional_bids[0].signed_additional_bid, "e30=");
EXPECT_EQ(additional_bids[0].seller_nonce, std::nullopt);
// Future calls to `TakeAuctionAdditionalBidsForOriginAndNonce` on the same
// origin and nonce should return nothing. Ideally this should be tested
// separately as a unitest for `AdAuctionPageData`.
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::IsEmpty());
}
TEST_F(ProcessAdAuctionResponseHeadersTest, AdditionalBidWithSellerNonce) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:"
"00000000-0000-0000-0000-000000000001:e30=");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::ElementsAre(::testing::FieldsAre(
/*signed_additional_bid=*/"e30=",
/*seller_nonce=*/"00000000-0000-0000-0000-000000000001")));
// Future calls to `TakeAuctionAdditionalBidsForOriginAndNonce` on the same
// origin and nonce should return nothing. Ideally this should be tested
// separately as a unitest for `AdAuctionPageData`.
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::IsEmpty());
}
TEST_F(ProcessAdAuctionResponseHeadersTest,
AdditionalBid_MultipleNoncesAndMultipleBidsPerNonce) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:e30=,"
"00000000-0000-0000-0000-000000000001:e30=");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000001:e2E6IDF9");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
EXPECT_THAT(
TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::ElementsAre(::testing::FieldsAre(
/*signed_additional_bid=*/"e30=", /*seller_nonce=*/std::nullopt)));
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000001"),
::testing::ElementsAre(
::testing::FieldsAre(/*signed_additional_bid=*/"e30=",
/*seller_nonce=*/std::nullopt),
::testing::FieldsAre(/*signed_additional_bid=*/"e2E6IDF9",
/*seller_nonce=*/std::nullopt)));
// Future calls to `TakeAuctionAdditionalBidsForOriginAndNonce` on the same
// origin and nonce should return nothing. Ideally this should be tested
// separately as a unitest for `AdAuctionPageData`.
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000001"),
::testing::IsEmpty());
}
TEST_F(ProcessAdAuctionResponseHeadersTest,
AdditionalBid_MultipleNoncesAndMultipleBidsPerNonceWithSellerNonce) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:"
"00000000-0000-0000-0000-000000000002:e30=,"
"00000000-0000-0000-0000-000000000001:"
"00000000-0000-0000-0000-000000000003:e30=");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000001:"
"00000000-0000-0000-0000-000000000004:e2E6IDF9");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::ElementsAre(::testing::FieldsAre(
/*signed_additional_bid=*/"e30=",
/*seller_nonce=*/"00000000-0000-0000-0000-000000000002")));
EXPECT_THAT(
TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000001"),
::testing::ElementsAre(
::testing::FieldsAre(
/*signed_additional_bid=*/"e30=",
/*seller_nonce=*/"00000000-0000-0000-0000-000000000003"),
::testing::FieldsAre(
/*signed_additional_bid=*/"e2E6IDF9",
/*seller_nonce=*/"00000000-0000-0000-0000-000000000004")));
// Future calls to `TakeAuctionAdditionalBidsForOriginAndNonce` on the same
// origin and nonce should return nothing. Ideally this should be tested
// separately as a unitest for `AdAuctionPageData`.
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000001"),
::testing::IsEmpty());
}
TEST_F(ProcessAdAuctionResponseHeadersTest,
AdditionalBid_InvalidHeaderSkipped) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
// Entries with invalid nonce (i.e. doesn't have 36 characters) will be
// skipped.
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-00000000000:e30=");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-0000000000001:e30=,"
"00000000-0000-0000-0000-000000000001:e2E6IDF9");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-00000000000"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-0000000000001"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000001"),
::testing::ElementsAre(
::testing::FieldsAre(/*signed_additional_bid=*/"e2E6IDF9",
/*seller_nonce=*/std::nullopt)));
}
TEST_F(ProcessAdAuctionResponseHeadersTest,
AdditionalBid_InvalidHeaderSkippedWithSellerNonce) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
// Entries with invalid nonce (i.e. doesn't have 36 characters) will be
// skipped. This also includes the seller nonce.
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:"
"00000000-0000-0000-0000-00000000000:e30=,"
"00000000-0000-0000-0000-000000000001:"
"00000000-0000-0000-0000-0000000000000:e30=,");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-0000000000001:"
"00000000-0000-0000-0000-000000000000:e30=,"
"00000000-0000-0000-0000-000000000002:"
"00000000-0000-0000-0000-000000000003:e2E6IDF9");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);
// The `Ad-Auction-Additional-Bid` header was removed from the headers and
// stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
url::Origin request_origin = url::Origin::Create(GURL("https://foo1.com"));
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000000"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000001"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-0000000000001"),
::testing::IsEmpty());
EXPECT_THAT(TakeAuctionAdditionalBidsForOriginAndNonce(
request_origin, "00000000-0000-0000-0000-000000000002"),
::testing::ElementsAre(::testing::FieldsAre(
/*signed_additional_bid=*/"e2E6IDF9",
/*seller_nonce=*/"00000000-0000-0000-0000-000000000003")));
}
TEST_F(ProcessAdAuctionResponseHeadersTest, AdditionalBid_ErrorMessages) {
struct {
std::string input;
base::expected<void, std::string> result;
} kTestcases[] = {
{"00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-"
"000000000001:e30=",
base::ok()},
{"00000000-000-0000-0000-000000000000:00000000-0000-0000-0000-"
"000000000001:e30=",
base::unexpected(
"Malformed Ad-Auction-Additional-Bid: The first colon-delimited "
"part (the auction nonce) is expected to be 36 characters in size "
"(representing the canonical representation of a UUIDv4), but was "
"instead 35 characters. Header received: "
"00000000-000-0000-0000-000000000000:00000000-0000-0000-0000-"
"000000000001:e30=")},
{"00000000-0000-0000-0000-000000000000:00000000-000-0000-0000-"
"000000000001:e30=",
base::unexpected(
"Malformed Ad-Auction-Additional-Bid: The second colon-delimited "
"part (the seller nonce) is expected to be 36 characters in size "
"(representing the canonical representation of a UUIDv4), but was "
"instead 35 characters. Header received: "
"00000000-0000-0000-0000-000000000000:00000000-000-0000-0000-"
"000000000001:e30=")},
{"00000000-000-0000-0000-000000000000:e30=",
base::unexpected(
"Malformed Ad-Auction-Additional-Bid: The first colon-delimited "
"part (the auction nonce) is expected to be 36 characters in size "
"(representing the canonical representation of a UUIDv4), but was "
"instead 35 characters. Header received: "
"00000000-000-0000-0000-000000000000:e30=")},
{"00000000-0000-0000-0000-000000000000",
base::unexpected(
"Malformed Ad-Auction-Additional-Bid: Expected two or three "
"colon-delimited parts, but instead received 1. Header received: "
"00000000-0000-0000-0000-000000000000")},
{"",
base::unexpected(
"Malformed Ad-Auction-Additional-Bid: Expected two or three "
"colon-delimited parts, but instead received 0. Header received: ")},
};
std::map<std::string, std::vector<SignedAdditionalBidWithMetadata>>
unused_map;
for (const auto& test_case : kTestcases) {
SCOPED_TRACE(test_case.input);
EXPECT_EQ(
ParseAdAuctionAdditionalBidResponseHeader(test_case.input, unused_map),
test_case.result);
}
}
TEST(RemoveAdAuctionResponseHeadersTest,
SignalsAndAdditionalBidHeadersAreRemoved) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey,
kLegitimateAdAuctionResponse);
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
kLegitimateAdAuctionNonceResponse);
headers_builder.AddHeader(kAdAuctionSignalsResponseHeaderKey,
R"([{"adSlot":"slot1"}])");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
"00000000-0000-0000-0000-000000000000:e30=");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();
RemoveAdAuctionResponseHeaders(headers);
// Only the nonce, signals and additional bid headers are removed.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));
EXPECT_FALSE(headers->HasHeader(kAdAuctionResultNonceResponseHeaderKey));
EXPECT_FALSE(headers->HasHeader(kAdAuctionSignalsResponseHeaderKey));
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
}
} // namespace content