blob: 9ff790126f252350a205e3c2a009147f185ad2c5 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sstream>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/callback_forward.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/interest_group/ad_auction_service_impl.h"
#include "content/browser/interest_group/interest_group_manager.h"
#include "content/browser/interest_group/interest_group_service_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/url_loader_monitor.h"
#include "content/shell/browser/shell.h"
#include "content/test/test_content_browser_client.h"
#include "net/base/isolation_info.h"
#include "net/base/network_isolation_key.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/interest_group/ad_auction_service.mojom.h"
#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
#include "third_party/blink/public/mojom/interest_group/restricted_interest_group_store.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ::testing::Eq;
using ::testing::Optional;
class AllowlistedOriginContentBrowserClient : public TestContentBrowserClient {
public:
explicit AllowlistedOriginContentBrowserClient() = default;
void SetAllowList(base::flat_set<url::Origin>&& allow_list) {
allow_list_ = allow_list;
}
// ContentBrowserClient overrides:
bool IsInterestGroupAPIAllowed(content::BrowserContext* browser_context,
const url::Origin& top_frame_origin,
const GURL& api_url) override {
return allow_list_.contains(top_frame_origin) &&
allow_list_.contains(url::Origin::Create(api_url));
}
private:
base::flat_set<url::Origin> allow_list_;
DISALLOW_COPY_AND_ASSIGN(AllowlistedOriginContentBrowserClient);
};
class InterestGroupBrowserTest : public ContentBrowserTest {
public:
InterestGroupBrowserTest() {
feature_list_.InitWithFeatures({blink::features::kFledgeInterestGroups,
blink::features::kFledgeInterestGroupAPI},
{});
}
~InterestGroupBrowserTest() override {
if (old_content_browser_client_)
SetBrowserClientForTesting(old_content_browser_client_);
}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
ASSERT_TRUE(embedded_test_server()->Start());
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::test_server::EmbeddedTestServer::TYPE_HTTPS);
https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server_->AddDefaultHandlers(GetTestDataFilePath());
https_server_->RegisterRequestMonitor(base::BindRepeating(
&InterestGroupBrowserTest::OnHttpsTestServerRequestMonitor,
base::Unretained(this)));
ASSERT_TRUE(https_server_->Start());
storage_ =
static_cast<StoragePartitionImpl*>(shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition())
->GetInterestGroupStorage();
content_browser_client_.SetAllowList(
{url::Origin::Create(https_server_->GetURL("a.test", "/")),
url::Origin::Create(https_server_->GetURL("b.test", "/")),
url::Origin::Create(https_server_->GetURL("c.test", "/")),
// HTTP origins like those below aren't supported for FLEDGE -- some
// tests verify that HTTP origins are rejected, even if somehow they
// are allowed by the allowlist.
url::Origin::Create(embedded_test_server()->GetURL("a.test", "/")),
url::Origin::Create(embedded_test_server()->GetURL("b.test", "/")),
url::Origin::Create(embedded_test_server()->GetURL("c.test", "/"))});
old_content_browser_client_ =
SetBrowserClientForTesting(&content_browser_client_);
}
bool JoinInterestGroupInJS(url::Origin owner,
std::string name) WARN_UNUSED_RESULT {
return "done" ==
EvalJs(shell(),
base::StringPrintf(R"(
(function() {
navigator.joinAdInterestGroup(
{name: '%s', owner: '%s'}, /*joinDurationSec=*/ 300);
return 'done';
})())",
name.c_str(), owner.Serialize().c_str()));
}
// The `trusted_bidding_signals_keys` and `ads` fields of `group` will be
// ignored in favor of the passed in values.
bool JoinInterestGroupInJS(const blink::mojom::InterestGroupPtr& group,
const std::string& ads = std::string(),
const std::string& trusted_bidding_signals_keys =
std::string()) WARN_UNUSED_RESULT {
// TODO(qingxin): Use base::Value to replace ostringstream.
std::ostringstream buf;
buf << "{"
<< "name: '" << group->name << "', "
<< "owner: '" << group->owner << "'";
if (group->bidding_url) {
buf << ", biddingLogicUrl: '" << *group->bidding_url << "'";
}
if (group->update_url) {
buf << ", dailyUpdateUrl: '" << *group->update_url << "'";
}
if (group->trusted_bidding_signals_url) {
buf << ", trustedBiddingSignalsUrl: '"
<< *group->trusted_bidding_signals_url << "'";
}
if (group->user_bidding_signals) {
buf << ", userBiddingSignals: " << group->user_bidding_signals.value();
}
if (!trusted_bidding_signals_keys.empty()) {
buf << ", trustedBiddingSignalsKeys: " << trusted_bidding_signals_keys;
}
if (!ads.empty()) {
buf << ", ads: " << ads;
}
buf << "}";
return "done" == EvalJs(shell(), base::StringPrintf(R"(
(function() {
navigator.joinAdInterestGroup(
%s, /*join_duration_sec=*/ 300);
return 'done';
})())",
buf.str().c_str()));
}
bool LeaveInterestGroupInJS(url::Origin owner, std::string name) {
return "done" ==
EvalJs(shell(),
base::StringPrintf(R"(
(function() {
navigator.leaveAdInterestGroup({name: '%s', owner: '%s'});
return 'done';
})())",
name.c_str(), owner.Serialize().c_str()));
}
std::vector<url::Origin> GetAllInterestGroupsOwners() {
std::vector<url::Origin> interest_group_owners;
base::RunLoop run_loop;
storage_->GetAllInterestGroupOwners(base::BindLambdaForTesting(
[&run_loop, &interest_group_owners](std::vector<url::Origin> owners) {
interest_group_owners = std::move(owners);
run_loop.Quit();
}));
run_loop.Run();
return interest_group_owners;
}
std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
GetInterestGroupsForOwner(const url::Origin& owner) {
std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
interest_groups;
base::RunLoop run_loop;
storage_->GetInterestGroupsForOwner(
owner,
base::BindLambdaForTesting(
[&run_loop, &interest_groups](
std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
groups) {
interest_groups = std::move(groups);
run_loop.Quit();
}));
run_loop.Run();
return interest_groups;
}
std::vector<std::pair<url::Origin, std::string>> GetAllInterestGroups() {
std::vector<std::pair<url::Origin, std::string>> interest_groups;
for (const auto& owner : GetAllInterestGroupsOwners()) {
for (const auto& interest_group : GetInterestGroupsForOwner(owner)) {
interest_groups.emplace_back(interest_group->group->owner,
interest_group->group->name);
}
}
return interest_groups;
}
int GetJoinCount(url::Origin owner, std::string name) {
for (const auto& interest_group : GetInterestGroupsForOwner(owner)) {
if (interest_group->group->name == name) {
return interest_group->signals->join_count;
}
}
return 0;
}
bool JoinInterestGroupAndWaitInJs(url::Origin owner,
std::string name) WARN_UNUSED_RESULT {
int initial_count = GetJoinCount(owner, name);
if (!JoinInterestGroupInJS(owner, name)) {
return false;
}
while (GetJoinCount(owner, name) != initial_count + 1) {
}
return true;
}
bool JoinInterestGroupAndWaitInJs(
const blink::mojom::InterestGroupPtr& group,
const std::string& ads = std::string(),
const std::string& trusted_bidding_signals_keys = std::string())
WARN_UNUSED_RESULT {
int initial_count = GetJoinCount(group->owner, group->name);
if (!JoinInterestGroupInJS(group, ads, trusted_bidding_signals_keys)) {
return false;
}
while (GetJoinCount(group->owner, group->name) != initial_count + 1) {
}
return true;
}
bool LeaveInterestGroupAndWait(url::Origin owner, std::string name) {
if (!LeaveInterestGroupInJS(owner, name)) {
return false;
}
while (GetJoinCount(owner, name) != 0) {
}
return true;
}
content::EvalJsResult RunAuctionAndWait(
const std::string& auction_config_json) WARN_UNUSED_RESULT {
return EvalJs(shell(), base::StringPrintf(
R"(
(async function() {
try {
return await navigator.runAdAuction(%s);
} catch (e) {
return e.toString();
}
})())",
auction_config_json.c_str()));
}
void WaitForURL(const GURL& url) {
{
base::AutoLock auto_lock(requests_lock_);
if (received_https_test_server_requests_.count(url) > 0u)
return;
wait_for_url_ = url;
request_run_loop_ = std::make_unique<base::RunLoop>();
}
request_run_loop_->Run();
request_run_loop_.reset();
}
void OnHttpsTestServerRequestMonitor(
const net::test_server::HttpRequest& request) {
base::AutoLock auto_lock(requests_lock_);
received_https_test_server_requests_.insert(request.GetURL());
if (wait_for_url_ == request.GetURL()) {
wait_for_url_ = GURL();
request_run_loop_->Quit();
}
}
protected:
std::unique_ptr<net::EmbeddedTestServer> https_server_;
base::test::ScopedFeatureList feature_list_;
AllowlistedOriginContentBrowserClient content_browser_client_;
ContentBrowserClient* old_content_browser_client_;
InterestGroupManager* storage_;
base::Lock requests_lock_;
std::set<GURL> received_https_test_server_requests_
GUARDED_BY(requests_lock_);
std::unique_ptr<base::RunLoop> request_run_loop_;
GURL wait_for_url_ GUARDED_BY(requests_lock_);
};
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, JoinLeaveInterestGroup) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
// This join should succeed and be added to the database.
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(test_origin_a, "cars"));
// This join should silently fail since a.test is not the same origin as
// foo.a.test.
EXPECT_TRUE(JoinInterestGroupInJS(
url::Origin::Create(GURL("https://foo.a.test")), "cars"));
// This join should silently fail since a.test is not the same origin as
// the bidding_url, bid.a.test
EXPECT_TRUE(JoinInterestGroupInJS(blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ test_origin_a,
/* name = */ "bicycles",
/* bidding_url = */ GURL("https://bid.a.test"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ absl::nullopt,
/* ads = */ absl::nullopt)));
// This join should silently fail since a.test is not the same origin as
// the update_url, update.a.test
EXPECT_TRUE(JoinInterestGroupInJS(blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ test_origin_a,
/* name = */ "tricycles",
/* bidding_url = */ absl::nullopt,
/* update_url = */ GURL("https://update.a.test"),
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ absl::nullopt,
/* ads = */ absl::nullopt)));
// This join should silently fail since a.test is not the same origin as
// the trusted_bidding_signals_url, signals.a.test
EXPECT_TRUE(JoinInterestGroupInJS(blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ test_origin_a,
/* name = */ "four-wheelers",
/* bidding_url = */ absl::nullopt,
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ GURL("https://signals.a.test"),
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ absl::nullopt,
/* ads = */ absl::nullopt)));
// This join should silently fail since d.test is not allowlisted for the API
GURL test_url_d = https_server_->GetURL("d.test", "/echo");
url::Origin test_origin_d = url::Origin::Create(test_url_d);
ASSERT_TRUE(NavigateToURL(shell(), test_url_d));
EXPECT_TRUE(JoinInterestGroupInJS(test_origin_d, "toys"));
// Another successful join.
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(test_origin_b, "trucks"));
// Check that only the a.test and b.test interest groups were added to
// the database.
std::vector<std::pair<url::Origin, std::string>> expected_groups = {
{test_origin_a, "cars"}, {test_origin_b, "trucks"}};
std::vector<std::pair<url::Origin, std::string>> received_groups;
received_groups = GetAllInterestGroups();
EXPECT_THAT(received_groups,
testing::UnorderedElementsAreArray(expected_groups));
// Now test leaving
// Test that we can't leave an interest group from a site not allowedlisted
// for the API.
// Inject an interest group into the DB for that for that site so we can try
// to remove it.
storage_->JoinInterestGroup(blink::mojom::InterestGroup::New(
/* expiry */ base::Time::Now() + base::TimeDelta::FromSeconds(300),
/* owner= */ test_origin_d,
/* name = */ "candy",
/* bidding_url = */ absl::nullopt,
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ absl::nullopt,
/* ads = */ absl::nullopt));
ASSERT_TRUE(NavigateToURL(shell(), test_url_d));
// This leave should do nothing because origin_d is not allowed by privacy
// sandbox.
EXPECT_TRUE(LeaveInterestGroupInJS(test_origin_d, "candy"));
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// This leave should do nothing because there is not interest group of that
// name.
EXPECT_TRUE(LeaveInterestGroupInJS(test_origin_b, "cars"));
// This leave should silently fail because it is cross-origin
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
EXPECT_TRUE(LeaveInterestGroupInJS(test_origin_b, "trucks"));
// This leave should succeed.
EXPECT_TRUE(LeaveInterestGroupAndWait(test_origin_a, "cars"));
// We expect that test_origin_b and the (injected) test_origin_d's interest
// groups remain.
expected_groups = {{test_origin_b, "trucks"}, {test_origin_d, "candy"}};
received_groups = GetAllInterestGroups();
EXPECT_THAT(received_groups,
testing::UnorderedElementsAreArray(expected_groups));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionBasic) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
EXPECT_EQ(
nullptr,
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("b.test", "/interest_group/decision_logic.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, JoinInterestGroupFull) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ("done", EvalJs(shell(), R"(
(function() {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
biddingLogicUrl: 'https://test.com/bidding_url',
dailyUpdateUrl: 'https://test.com/update_url',
trustedBiddingSignalsUrl:
'https://test.com/trusted_bidding_signals_url',
trustedBiddingSignalsKeys: ['key1', 'key2'],
userBiddingSignals: {some: 'json', data: {here: [1, 2, 3]}},
ads: [{
renderUrl: 'https://test.com/ad_url',
metadata: {ad: 'metadata', here: [1, 2, 3]}
}]
},
/*joinDurationSec=*/1);
return 'done';
})())"));
// We just verify that the operation didn't crash and that the JS completes
// successfully.
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionFull) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(nullptr, RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com/decision_logic',
interestGroupBuyers: ['https://www.buyer1.com', 'https://www.buyer2.com'],
auctionSignals: {more: 'json', stuff: {}},
sellerSignals: {yet: 'more', info: 1},
perBuyerSignals: {
'https://www.buyer1.com': {even: 'more', x: 4.5},
'https://www.buyer2.com': {the: 'end'}
}
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionStarInterestGroupBuyers) {
ASSERT_TRUE(NavigateToURL(shell(), https_server_->GetURL("a.test", "/echo")));
EXPECT_EQ(nullptr, RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com/decision_logic',
interestGroupBuyers: '*',
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidOwner) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': "
"owner 'https://invalid^&' for AuctionAdInterestGroup with name 'cars' "
"must be a valid https origin.",
EvalJs(shell(), R"(
(function() {
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://invalid^&',
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidBiddingLogicUrl) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': "
"biddingLogicUrl 'https://invalid^&' for AuctionAdInterestGroup with "
"owner 'https://test.com' and name 'cars' cannot be resolved to a valid "
"URL.",
EvalJs(shell(), R"(
(function() {
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
biddingLogicUrl: 'https://invalid^&',
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidDailyUpdateUrl) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': "
"dailyUpdateUrl 'https://invalid^&' for AuctionAdInterestGroup with "
"owner 'https://test.com' and name 'cars' cannot be resolved to a valid "
"URL.",
EvalJs(shell(), R"(
(function() {
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
dailyUpdateUrl: 'https://invalid^&',
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidTrustedBiddingSignalsUrl) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': "
"trustedBiddingSignalsUrl 'https://invalid^&' for "
"AuctionAdInterestGroup with owner 'https://test.com' and name 'cars' "
"cannot be resolved to a valid URL.",
EvalJs(shell(), R"(
(function() {
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
trustedBiddingSignalsUrl: 'https://invalid^&',
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidUserBiddingSignals) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': "
"userBiddingSignals for AuctionAdInterestGroup with owner "
"'https://test.com' and name 'cars' must be a JSON-serializable object.",
EvalJs(shell(), R"(
(function() {
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
userBiddingSignals: function() {},
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidAdUrl) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': "
"ad renderUrl 'https://invalid^&' for AuctionAdInterestGroup with owner "
"'https://test.com' and name 'cars' cannot be resolved to a valid URL.",
EvalJs(shell(), R"(
(function() {
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
ads: [{renderUrl:"https://invalid^&"}],
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupInvalidAdMetadata) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'joinAdInterestGroup' on 'Navigator': ad "
"metadata for AuctionAdInterestGroup with owner 'https://test.com' and "
"name 'cars' must be a JSON-serializable object.",
EvalJs(shell(), R"(
(function() {
let x = {};
let y = {};
x.a = y;
y.a = x;
try {
navigator.joinAdInterestGroup(
{
name: 'cars',
owner: 'https://test.com',
ads: [{renderUrl:"https://test.com", metadata:x}],
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
LeaveInterestGroupInvalidOwner) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'leaveAdInterestGroup' on 'Navigator': "
"owner 'https://invalid^&' for AuctionAdInterestGroup with name 'cars' "
"must be a valid https origin.",
EvalJs(shell(), R"(
(function() {
try {
navigator.leaveAdInterestGroup(
{
name: 'cars',
owner: 'https://invalid^&',
},
/*joinDurationSec=*/1);
} catch (e) {
return e.toString();
}
return 'done';
})())"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionInvalidSeller) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': seller "
"'https://invalid^&' for AuctionAdConfig must be a valid https origin.",
RunAuctionAndWait(R"({
seller: 'https://invalid^&',
decisionLogicUrl: 'https://test.com/decision_logic'
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionHttpSeller) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': seller "
"'http://test.com' for AuctionAdConfig must be a valid https origin.",
RunAuctionAndWait(R"({
seller: 'http://test.com',
decisionLogicUrl: 'https://test.com/decision_logic'
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidDecisionLogicUrl) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"decisionLogicUrl 'https://invalid^&' for AuctionAdConfig with seller "
"'https://test.com' cannot be resolved to a valid URL.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://invalid^&'
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionDecisionLogicUrlDifferentFromSeller) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string ads =
"[{renderUrl : 'https://example.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads));
EXPECT_EQ(
nullptr,
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("b.test", "/interest_group/decision_logic.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidInterestGroupBuyers) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"interestGroupBuyers buyer 'https://invalid^&' for AuctionAdConfig "
"with seller 'https://test.com' must be a valid https origin.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
interestGroupBuyers: ['https://invalid^&'],
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidInterestGroupBuyersStr) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"interestGroupBuyers 'not star' for AuctionAdConfig with seller "
"'https://test.com' must be \"*\" (wildcard) or a list of buyer "
"https origin strings.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
interestGroupBuyers: 'not star',
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionNoInterestGroupBuyersField) {
ASSERT_TRUE(NavigateToURL(shell(), https_server_->GetURL("a.test", "/echo")));
EXPECT_EQ(nullptr, RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionNoInterestGroupBuyers) {
ASSERT_TRUE(NavigateToURL(shell(), https_server_->GetURL("a.test", "/echo")));
EXPECT_EQ(nullptr, RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
interestGroupBuyers: [],
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidAuctionSignals) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"auctionSignals for AuctionAdConfig with seller 'https://test.com' must "
"be a JSON-serializable object.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
auctionSignals: alert
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidSellerSignals) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"sellerSignals for AuctionAdConfig with seller 'https://test.com' must "
"be a JSON-serializable object.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
sellerSignals: function() {}
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidPerBuyerSignalsOrigin) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"perBuyerSignals buyer 'https://invalid^&' for AuctionAdConfig with "
"seller 'https://test.com' must be a valid https origin.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
perBuyerSignals: {'https://invalid^&': {a:1}}
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionPerBuyerSignalsOriginNotInBuyers) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string ads =
"[{renderUrl : 'https://example.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads));
EXPECT_EQ(
nullptr,
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
perBuyerSignals: {$1: {a:1}, 'https://not_in_buyers.com': {a:1}}
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionInvalidPerBuyerSignals) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_EQ(
"TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
"perBuyerSignals for AuctionAdConfig with seller 'https://test.com' "
"must be a JSON-serializable object.",
RunAuctionAndWait(R"({
seller: 'https://test.com',
decisionLogicUrl: 'https://test.com',
perBuyerSignals: {'https://test.com': function() {}}
})"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionBuyersNoInterestGroup) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
EXPECT_EQ(
nullptr,
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionPrivacySandboxDisabled) {
// Successful join at a.test
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
std::string ads =
"[{renderUrl : 'https://example.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url_a),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads, "['key1']"));
GURL test_url_d = https_server_->GetURL("d.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url_d));
// Auction should not be run since d.test has the API disabled.
EXPECT_EQ(
nullptr,
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
auctionSignals: {x: 1},
sellerSignals: {yet: 'more', info: 1},
perBuyerSignals: {$3: {even: 'more', x: 4.5}}
})",
test_url_d.GetOrigin(),
https_server_->GetURL("d.test", "/interest_group/decision_logic.js"),
test_url_a.GetOrigin())));
// No requests should have been made for the interest group or auction URLs.
base::AutoLock auto_lock(requests_lock_);
EXPECT_FALSE(base::Contains(
received_https_test_server_requests_,
https_server_->GetURL("/interest_group/bidding_logic.js")));
EXPECT_FALSE(base::Contains(
received_https_test_server_requests_,
https_server_->GetURL("/interest_group/trusted_bidding_signals.json")));
EXPECT_FALSE(base::Contains(
received_https_test_server_requests_,
https_server_->GetURL("/interest_group/decision_logic.js")));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionDisabledInterestGroup) {
// Inject an interest group into the DB for that for a disabled site so we can
// try to remove it.
GURL disabled_domain = https_server_->GetURL("d.test", "/");
blink::mojom::InterestGroupPtr disabled_group =
blink::mojom::InterestGroup::New();
disabled_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
disabled_group->owner = url::Origin::Create(disabled_domain);
disabled_group->name = "candy";
disabled_group->bidding_url = https_server_->GetURL(
disabled_domain.host(),
"/interest_group/bidding_logic_stop_bidding_after_win.js");
disabled_group->ads = std::vector<blink::mojom::InterestGroupAdPtr>();
disabled_group->ads->emplace_back(blink::mojom::InterestGroupAd::New(
GURL("https://stop_bidding_after_win.com/render"), absl::nullopt));
storage_->JoinInterestGroup(std::move(disabled_group));
ASSERT_EQ(1, GetJoinCount(url::Origin::Create(disabled_domain), "candy"));
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string ads =
"[{renderUrl : 'https://example.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL(test_url.host(),
"/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */
https_server_->GetURL(test_url.host(),
"/interest_group/trusted_bidding_signals.json"),
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads, "['key1']"));
EXPECT_EQ("https://example.com/render",
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1, $3],
auctionSignals: {x: 1},
sellerSignals: {yet: 'more', info: 1},
perBuyerSignals: {$1: {even: 'more', x: 4.5}}
})",
test_url.GetOrigin(),
https_server_->GetURL(test_url.host(),
"/interest_group/decision_logic.js"),
disabled_domain.GetOrigin())));
// No requests should have been made for the disabled interest group's URLs.
base::AutoLock auto_lock(requests_lock_);
EXPECT_FALSE(base::Contains(
received_https_test_server_requests_,
https_server_->GetURL(
"/interest_group/bidding_logic_stop_bidding_after_win.js")));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionWithWinner) {
URLLoaderMonitor url_loader_monitor;
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string ads =
"[{renderUrl : 'https://example.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads, "['key1']"));
EXPECT_EQ(
"https://example.com/render",
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
auctionSignals: {x: 1},
sellerSignals: {yet: 'more', info: 1},
perBuyerSignals: {$1: {even: 'more', x: 4.5}}
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
// Reporting urls should be fetched after an auction succeeded.
WaitForURL(https_server_->GetURL("/echoall?report_seller"));
WaitForURL(https_server_->GetURL("/echoall?report_bidder"));
// Check ResourceRequest structs of requests issued by the worklet process.
const struct ExpectedRequest {
GURL url;
const char* accept_header;
bool expect_trusted_params;
} kExpectedRequests[] = {
{https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
"application/javascript", true /* expect_trusted_params */},
{https_server_->GetURL(
"a.test",
"/interest_group/"
"trusted_bidding_signals.json?hostname=a.test&keys=key1"),
"application/json", true /* expect_trusted_params */},
{https_server_->GetURL("a.test", "/interest_group/decision_logic.js"),
"application/javascript", false /* expect_trusted_params */},
};
for (const auto& expected_request : kExpectedRequests) {
SCOPED_TRACE(expected_request.url);
absl::optional<network::ResourceRequest> request =
url_loader_monitor.GetRequestInfo(expected_request.url);
ASSERT_TRUE(request);
EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
request->credentials_mode);
EXPECT_EQ(network::mojom::RedirectMode::kError, request->redirect_mode);
EXPECT_EQ(url::Origin::Create(test_url), request->request_initiator);
EXPECT_EQ(1u, request->headers.GetHeaderVector().size());
std::string accept_value;
ASSERT_TRUE(request->headers.GetHeader(net::HttpRequestHeaders::kAccept,
&accept_value));
EXPECT_EQ(expected_request.accept_header, accept_value);
EXPECT_EQ(expected_request.expect_trusted_params,
request->trusted_params.has_value());
if (!request->trusted_params) {
// Requests for render-provided URLs use an empty trusted params value and
// enable CORS (and should use the RenderFrameHosts's URLLoaderFactory,
// which is validated in the next test).
EXPECT_EQ(network::mojom::RequestMode::kCors, request->mode);
} else {
// Requests for interest-group provided URLs are cross-origin, and set
// trusted params to use the right cache shard, since they use a trusted
// URLLoaderFactory.
EXPECT_EQ(network::mojom::RequestMode::kNoCors, request->mode);
const net::IsolationInfo& isolation_info =
request->trusted_params->isolation_info;
EXPECT_EQ(net::IsolationInfo::RequestType::kOther,
isolation_info.request_type());
url::Origin expected_origin = url::Origin::Create(expected_request.url);
EXPECT_EQ(expected_origin, isolation_info.top_frame_origin());
EXPECT_EQ(expected_origin, isolation_info.frame_origin());
EXPECT_TRUE(isolation_info.site_for_cookies().IsNull());
}
}
// Check ResourceRequest structs of report requests.
const GURL kExpectedReportUrls[] = {
https_server_->GetURL("a.test", "/echoall?report_seller"),
https_server_->GetURL("a.test", "/echoall?report_bidder"),
};
for (const auto& expected_report_url : kExpectedReportUrls) {
SCOPED_TRACE(expected_report_url);
absl::optional<network::ResourceRequest> request =
url_loader_monitor.GetRequestInfo(expected_report_url);
ASSERT_TRUE(request);
EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
request->credentials_mode);
EXPECT_EQ(network::mojom::RedirectMode::kError, request->redirect_mode);
EXPECT_EQ(url::Origin::Create(test_url), request->request_initiator);
EXPECT_TRUE(request->headers.IsEmpty());
ASSERT_TRUE(request->trusted_params);
const net::IsolationInfo& isolation_info =
request->trusted_params->isolation_info;
EXPECT_EQ(net::IsolationInfo::RequestType::kOther,
isolation_info.request_type());
EXPECT_TRUE(isolation_info.network_isolation_key().IsTransient());
EXPECT_TRUE(isolation_info.site_for_cookies().IsNull());
}
// The two reporting requests should use different NIKs to prevent the
// requests from being correlated.
EXPECT_NE(url_loader_monitor.GetRequestInfo(kExpectedReportUrls[0])
->trusted_params->isolation_info.network_isolation_key(),
url_loader_monitor.GetRequestInfo(kExpectedReportUrls[1])
->trusted_params->isolation_info.network_isolation_key());
}
// Use different origins for publisher, bidder, and seller, and make sure
// everything works as expected.
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, CrossOrigin) {
const char kPublisher[] = "a.test";
const char kBidder[] = "b.test";
const char kSeller[] = "c.test";
// Navigate to bidder site, and add an interest group.
GURL bidder_url = https_server_->GetURL(kBidder, "/echo");
ASSERT_TRUE(NavigateToURL(shell(), bidder_url));
std::string ads =
"[{renderUrl : 'https://example.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(bidder_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL(kBidder, "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */
https_server_->GetURL(kBidder,
"/interest_group/trusted_bidding_signals.json"),
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads, "['key1']"));
// Navigate to publisher.
ASSERT_TRUE(
NavigateToURL(shell(), https_server_->GetURL(kPublisher, "/echo")));
// Run auction with a seller script missing an "Access-Control-Allow-Origin"
// header. The request for the seller script should fail, and so should the
// auction.
GURL seller_logic_url =
https_server_->GetURL(kSeller, "/interest_group/decision_logic.js");
EXPECT_EQ(nullptr,
RunAuctionAndWait(JsReplace(
R"(
{
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$3],
auctionSignals: {x: 1},
sellerSignals: {yet: 'more', info: 1},
perBuyerSignals: {$3: {even: 'more', x: 4.5}}
}
)",
url::Origin::Create(seller_logic_url), seller_logic_url.spec(),
url::Origin::Create(bidder_url))));
// Run auction with a seller script with an "Access-Control-Allow-Origin"
// header. The auction should succeed.
seller_logic_url = https_server_->GetURL(
kSeller, "/interest_group/decision_logic_cross_origin.js");
ASSERT_EQ("https://example.com/render",
RunAuctionAndWait(JsReplace(
R"(
{
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$3],
auctionSignals: {x: 1},
sellerSignals: {yet: 'more', info: 1},
perBuyerSignals: {$3: {even: 'more', x: 4.5}}
}
)",
url::Origin::Create(seller_logic_url), seller_logic_url.spec(),
url::Origin::Create(bidder_url))));
// Reporting urls should be fetched after an auction succeeded.
WaitForURL(https_server_->GetURL("/echoall?report_seller"));
WaitForURL(https_server_->GetURL("/echoall?report_bidder"));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
RunAdAuctionWithWinnerManyInterestGroups) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL(
"a.test",
"/interest_group/bidding_logic_stop_bidding_after_win.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
"[{renderUrl : 'https://stop_bidding_after_win.com/render'}]"));
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "bikes",
/* bidding_url = */
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
"[{renderUrl : 'https://example.com/render'}]", "['key1']"));
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "shoes",
/* bidding_url = */
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
"[{renderUrl : 'https://example.com/render2'}]"));
EXPECT_EQ(
"https://stop_bidding_after_win.com/render",
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1, $3],
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
// Seller and winning bidder should get reports, and other bidders shouldn't
// get reports.
WaitForURL(https_server_->GetURL("/echoall?report_seller"));
WaitForURL(
https_server_->GetURL("/echoall?report_bidder_stop_bidding_after_win"));
base::AutoLock auto_lock(requests_lock_);
EXPECT_FALSE(base::Contains(received_https_test_server_requests_,
https_server_->GetURL("/echoall?report_bidder")));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionMultipleAuctions) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string ads =
"[{renderUrl : 'https://stop_bidding_after_win.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
// This group will win if it has never won an auction.
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL(
"a.test",
"/interest_group/bidding_logic_stop_bidding_after_win.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads));
GURL test_url2 = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url2));
// This group will win if the other interest group has won an auction.
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url2.GetOrigin()),
/* name = */ "shoes",
/* bidding_url = */
https_server_->GetURL("b.test", "/interest_group/bidding_logic.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
"[{renderUrl : 'https://example.com/render'}]"));
// Both owners have one interest group in storage, and both interest groups
// have no `prev_wins`.
const url::Origin origin = url::Origin::Create(test_url);
const url::Origin origin2 = url::Origin::Create(test_url2);
std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
bidding_interest_groups = GetInterestGroupsForOwner(origin);
EXPECT_EQ(bidding_interest_groups.size(), 1u);
EXPECT_EQ(bidding_interest_groups.front()->signals->prev_wins.size(), 0u);
EXPECT_EQ(bidding_interest_groups.front()->signals->bid_count, 0);
std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
bidding_interest_groups2 = GetInterestGroupsForOwner(origin2);
EXPECT_EQ(bidding_interest_groups2.size(), 1u);
EXPECT_EQ(bidding_interest_groups2.front()->signals->prev_wins.size(), 0u);
EXPECT_EQ(bidding_interest_groups2.front()->signals->bid_count, 0);
std::string auction_config = JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1, $3],
})",
test_url2.GetOrigin().spec(),
https_server_->GetURL("b.test", "/interest_group/decision_logic.js")
.spec(),
test_url.GetOrigin().spec());
// Run an ad auction. Interest group cars of owner `test_url` wins.
EXPECT_EQ("https://stop_bidding_after_win.com/render",
RunAuctionAndWait(auction_config));
// `prev_wins` of `test_url`'s interest group cars is updated in storage.
bidding_interest_groups = GetInterestGroupsForOwner(origin);
bidding_interest_groups2 = GetInterestGroupsForOwner(origin2);
EXPECT_EQ(bidding_interest_groups.front()->signals->prev_wins.size(), 1u);
EXPECT_EQ(bidding_interest_groups2.front()->signals->prev_wins.size(), 0u);
EXPECT_EQ(
bidding_interest_groups.front()->signals->prev_wins.front()->ad_json,
R"({"render_url":"https://stop_bidding_after_win.com/render","metadata":{"ad":"metadata","here":[1,2]}})");
EXPECT_EQ(bidding_interest_groups.front()->signals->bid_count, 1);
EXPECT_EQ(bidding_interest_groups2.front()->signals->bid_count, 1);
// Run auction again. Interest group shoes of owner `test_url2` wins.
EXPECT_EQ("https://example.com/render", RunAuctionAndWait(auction_config));
// `test_url2`'s interest group shoes has one `prev_wins` in storage.
bidding_interest_groups = GetInterestGroupsForOwner(origin);
bidding_interest_groups2 = GetInterestGroupsForOwner(origin2);
EXPECT_EQ(bidding_interest_groups.front()->signals->prev_wins.size(), 1u);
EXPECT_EQ(bidding_interest_groups2.front()->signals->prev_wins.size(), 1u);
EXPECT_EQ(
bidding_interest_groups2.front()->signals->prev_wins.front()->ad_json,
R"({"render_url":"https://example.com/render"})");
EXPECT_EQ(bidding_interest_groups.front()->signals->bid_count, 2);
EXPECT_EQ(bidding_interest_groups2.front()->signals->bid_count, 2);
// Run auction third time, and only interest group "shoes" bids this time.
EXPECT_EQ(
"https://example.com/render",
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
})",
test_url2.GetOrigin().spec(),
https_server_->GetURL("b.test", "/interest_group/decision_logic.js")
.spec())));
// `test_url2`'s interest group shoes has two `prev_wins` in storage.
bidding_interest_groups = GetInterestGroupsForOwner(origin);
bidding_interest_groups2 = GetInterestGroupsForOwner(origin2);
EXPECT_EQ(bidding_interest_groups.front()->signals->prev_wins.size(), 1u);
EXPECT_EQ(bidding_interest_groups2.front()->signals->prev_wins.size(), 2u);
EXPECT_EQ(
bidding_interest_groups2.front()->signals->prev_wins.back()->ad_json,
R"({"render_url":"https://example.com/render"})");
EXPECT_EQ(bidding_interest_groups.front()->signals->bid_count, 2);
EXPECT_EQ(bidding_interest_groups2.front()->signals->bid_count, 3);
}
// The winning ad's render url is invalid (invalid url or has http scheme).
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionWithInvalidAdUrl) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string ads =
"[{renderUrl : 'https://shoes.com/render',"
"metadata : {ad:'metadata', here : [ 1, 2 ]}}]";
EXPECT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/* expiry */ base::Time(),
/* owner= */ url::Origin::Create(test_url.GetOrigin()),
/* name = */ "cars",
/* bidding_url = */
https_server_->GetURL(
"a.test", "/interest_group/bidding_logic_invalid_ad_url.js"),
/* update_url = */ absl::nullopt,
/* trusted_bidding_signals_url = */ absl::nullopt,
/* trusted_bidding_signals_keys = */ absl::nullopt,
/* user_bidding_signals = */ "{some: 'json', data: {here: [1, 2]}}",
/* ads = */ absl::nullopt),
ads));
EXPECT_EQ(
nullptr,
RunAuctionAndWait(JsReplace(
R"({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
})",
test_url.GetOrigin().spec(),
https_server_->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
}
// These end-to-end tests validate that information from navigator-exposed APIs
// is correctly passed to worklets.
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
BuyerWorkletThrowsFailsAuction) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
ASSERT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/*expiry=*/base::Time() + base::TimeDelta::FromSeconds(300),
/*owner=*/url::Origin::Create(test_url.GetOrigin()),
/*name=*/"cars",
/*bidding_url=*/
https_server_->GetURL("a.test",
"/interest_group/bidding_logic_throws.js"),
/*update_url=*/absl::nullopt,
/*trusted_bidding_signals_url=*/
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/*trusted_bidding_signals_keys=*/absl::nullopt,
/*user_bidding_signals=*/"{some: 'json', data: {here: [1, 2, 3]}}",
/*ads=*/absl::nullopt),
/*ads=*/
"[{renderUrl: 'https://example.com/render', metadata: {ad: 'metadata', "
"here: [1, 2, 3]}}]",
/*trusted_bidding_signals_keys=*/"['key1']"));
EXPECT_EQ(
nullptr,
EvalJs(shell(),
JsReplace(
R"(
(async function() {
return await navigator.runAdAuction({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
});
})())",
test_url.GetOrigin().spec(),
https_server_
->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, ValidateGenerateBid) {
// Start by adding a placeholder bidder in domain b.test, used for
// perBuyerSignals validation.
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
ASSERT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/*expiry=*/base::Time() + base::TimeDelta::FromSeconds(300),
/*owner=*/url::Origin::Create(test_url_b.GetOrigin()),
/*name=*/"boats",
/*bidding_url=*/
https_server_->GetURL("b.test", "/interest_group/bidding_logic.js"),
/*update_url=*/absl::nullopt,
/*trusted_bidding_signals_url=*/
https_server_->GetURL("b.test",
"/interest_group/trusted_bidding_signals.json"),
/*trusted_bidding_signals_keys=*/absl::nullopt,
/*user_bidding_signals=*/"{some: 'json', data: {here: [1, 2, 3]}}",
/*ads=*/absl::nullopt),
/*ads=*/
"[{renderUrl: 'https://example2.com/render', metadata: {ad: 'metadata', "
"here: [1, 2, 3]}}]",
/*trusted_bidding_signals_keys=*/"['key1']"));
// This is the primary interest group that wins the auction because
// bidding_argument_validator.js bids 2, whereas bidding_logic.js bids 1, and
// decision_logic.js just returns the bid as the rank -- highest rank wins.
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
ASSERT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/*expiry=*/base::Time() + base::TimeDelta::FromSeconds(300),
/*owner=*/url::Origin::Create(test_url.GetOrigin()),
/*name=*/"cars",
/*bidding_url=*/
https_server_->GetURL(
"a.test", "/interest_group/bidding_argument_validator.js"),
/*update_url=*/absl::nullopt,
/*trusted_bidding_signals_url=*/
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/*trusted_bidding_signals_keys=*/absl::nullopt,
/*user_bidding_signals=*/"{some: 'json', data: {here: [1, 2, 3]}}",
/*ads=*/absl::nullopt),
/*ads=*/
"[{renderUrl: 'https://example.com/render', metadata: {ad: 'metadata', "
"here: [1, 2, 3]}}]",
/*trusted_bidding_signals_keys=*/"['key1']"));
EXPECT_EQ(
"https://example.com/render",
EvalJs(shell(),
JsReplace(
R"(
(async function() {
return await navigator.runAdAuction({
seller: $1,
decisionLogicUrl: $3,
interestGroupBuyers: [$1, $2],
auctionSignals: {so: 'I', hear: ['you', 'like', 'json']},
sellerSignals: {signals: 'from', the: ['seller']},
perBuyerSignals: {$1: {signalsForBuyer: 1}, $2: {signalsForBuyer: 2}}
});
})())",
test_url.GetOrigin().spec(), test_url_b.GetOrigin().spec(),
https_server_
->GetURL("a.test", "/interest_group/decision_logic.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
SellerWorkletThrowsFailsAuction) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
ASSERT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/*expiry=*/base::Time() + base::TimeDelta::FromSeconds(300),
/*owner=*/url::Origin::Create(test_url.GetOrigin()),
/*name=*/"cars",
/*bidding_url=*/
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/*update_url=*/absl::nullopt,
/*trusted_bidding_signals_url=*/
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/*trusted_bidding_signals_keys=*/absl::nullopt,
/*user_bidding_signals=*/"{some: 'json', data: {here: [1, 2, 3]}}",
/*ads=*/absl::nullopt),
/*ads=*/
"[{renderUrl: 'https://example.com/render', metadata: {ad: 'metadata', "
"here: [1, 2, 3]}}]",
/*trusted_bidding_signals_keys=*/"['key1']"));
EXPECT_EQ(nullptr,
EvalJs(shell(),
JsReplace(
R"(
(async function() {
return await navigator.runAdAuction({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
});
})())",
test_url.GetOrigin().spec(),
https_server_
->GetURL("a.test",
"/interest_group/decision_logic_throws.js")
.spec())));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, ValidateScoreAd) {
GURL test_url = https_server_->GetURL("a.test", "/echo");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
ASSERT_TRUE(JoinInterestGroupAndWaitInJs(
blink::mojom::InterestGroup::New(
/*expiry=*/base::Time() + base::TimeDelta::FromSeconds(300),
/*owner=*/url::Origin::Create(test_url.GetOrigin()),
/*name=*/"cars",
/*bidding_url=*/
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
/*update_url=*/absl::nullopt,
/*trusted_bidding_signals_url=*/
https_server_->GetURL("a.test",
"/interest_group/trusted_bidding_signals.json"),
/*trusted_bidding_signals_keys=*/absl::nullopt,
/*user_bidding_signals=*/"{some: 'json', data: {here: [1, 2, 3]}}",
/*ads=*/absl::nullopt),
/*ads=*/
"[{renderUrl: 'https://example.com/render', metadata: {ad: 'metadata', "
"here: [1, 2, 3]}}]",
/*trusted_bidding_signals_keys=*/"['key1']"));
EXPECT_EQ(
"https://example.com/render",
EvalJs(shell(),
JsReplace(
R"(
(async function() {
return await navigator.runAdAuction({
seller: $1,
decisionLogicUrl: $2,
interestGroupBuyers: [$1],
auctionSignals: {so: 'I', hear: ['you', 'like', 'json']},
sellerSignals: {signals: 'from', the: ['seller']},
perBuyerSignals: {$1: {signalsForBuyer: 1}}
});
})())",
test_url.GetOrigin().spec(),
https_server_
->GetURL("a.test",
"/interest_group/decision_argument_validator.js")
.spec())));
}
// These tests exercise the interest group and ad auction services directly,
// rather than via Blink, to ensure that those services running in the browser
// implement important security checks (Blink may also perform its own
// checking, but the render process is untrusted).
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupBasicBypassBlink) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_a;
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(1, GetJoinCount(test_origin_a, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupOriginNotHttps) {
// JS API is https-only, but a compromised renderer could send a http origin.
GURL test_url_http = embedded_test_server()->GetURL("a.test", "/echo");
url::Origin test_origin_http = url::Origin::Create(test_url_http);
ASSERT_TRUE(test_url_http.SchemeIs(url::kHttpScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_http));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Silently fails to join -- the frame origin isn't https.
auto interest_group_http = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group_http->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group_http->name = kGroupName;
interest_group_http->owner = test_origin_http;
interest_service->JoinInterestGroup(std::move(interest_group_http));
interest_service.FlushForTesting();
EXPECT_EQ(0, GetJoinCount(test_origin_http, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupWrongOwnerOrigin) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Silently fails to join -- the owner origin doesn't match the frame origin.
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_b;
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(0, GetJoinCount(test_origin_b, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupWrongBiddingUrlOrigin) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Silently fails to join -- the bidding URL origin doesn't match the frame
// origin.
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_a;
interest_group->bidding_url = test_url_b;
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(0, GetJoinCount(test_origin_a, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupWrongUpdateUrlOrigin) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Silently fails to join -- the update URL origin doesn't match the frame
// origin.
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_a;
interest_group->update_url = test_url_b;
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(0, GetJoinCount(test_origin_a, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupWrongTrustedBiddingSignalsOrigin) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Silently fails to join -- the trusted bidding signals URL origin doesn't
// match the frame origin.
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_a;
interest_group->trusted_bidding_signals_url = test_url_b;
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(0, GetJoinCount(test_origin_a, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
JoinInterestGroupAdRenderingUrlNotHttps) {
GURL test_url_https = https_server_->GetURL("a.test", "/echo");
GURL test_url_http = embedded_test_server()->GetURL("b.test", "/echo");
url::Origin test_origin_https = url::Origin::Create(test_url_https);
ASSERT_TRUE(test_url_https.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(test_url_http.SchemeIs(url::kHttpScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_https));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Silently fails to join -- the ad renderingUrl doesn't use https.
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_https;
auto ad = blink::mojom::InterestGroupAd::New();
ad->render_url = test_url_http;
interest_group->ads.emplace();
interest_group->ads->push_back(std::move(ad));
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(0, GetJoinCount(test_origin_https, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
LeaveInterestGroupWrongOwnerOrigin) {
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
url::Origin test_origin_a = url::Origin::Create(test_url_a);
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service1;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service1.BindNewPipeAndPassReceiver());
// Join succeeds.
auto interest_group = blink::mojom::InterestGroup::New();
constexpr char kGroupName[] = "cars";
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
interest_group->name = kGroupName;
interest_group->owner = test_origin_a;
interest_service1->JoinInterestGroup(std::move(interest_group));
interest_service1.FlushForTesting();
EXPECT_EQ(1, GetJoinCount(test_origin_a, kGroupName));
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// The frame changes upon navigation -- connect to the new service.
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service2;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service2.BindNewPipeAndPassReceiver());
// Silently fails to leave -- the owner origin doesn't match the frame origin.
interest_service2->LeaveInterestGroup(test_origin_a, kGroupName);
interest_service2.FlushForTesting();
EXPECT_EQ(1, GetJoinCount(test_origin_a, kGroupName));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionBasicBypassBlink) {
ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
mojo::Remote<blink::mojom::AdAuctionService> auction_service;
AdAuctionServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
auction_service.BindNewPipeAndPassReceiver());
base::RunLoop run_loop;
auction_service->RunAdAuction(
blink::mojom::AuctionAdConfig::New(),
base::BindLambdaForTesting([&run_loop](const absl::optional<GURL>& url) {
EXPECT_THAT(url, Eq(absl::nullopt));
run_loop.Quit();
}));
run_loop.Run();
}
// Fixture for Blink-bypassing auction tests that share the same interest group
// -- useful for checking auction service security validations.
class InterestGroupBrowserTestRunAdAuctionBypassBlink
: public InterestGroupBrowserTest {
protected:
const GURL kAdUrl{"https://example.com/render"};
void SetUpOnMainThread() override {
InterestGroupBrowserTest::SetUpOnMainThread();
GURL test_url_a = https_server_->GetURL("a.test", "/echo");
test_origin_a_ = url::Origin::Create(test_url_a);
ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
InterestGroupServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
interest_service.BindNewPipeAndPassReceiver());
// Set up kAdUrl as the only interest group ad in the auction.
auto interest_group = blink::mojom::InterestGroup::New();
interest_group->expiry =
base::Time::Now() + base::TimeDelta::FromSeconds(300);
constexpr char kGroupName[] = "cars";
interest_group->name = kGroupName;
interest_group->owner = test_origin_a_;
interest_group->bidding_url =
https_server_->GetURL("a.test", "/interest_group/bidding_logic.js");
interest_group->trusted_bidding_signals_url = https_server_->GetURL(
"a.test", "/interest_group/trusted_bidding_signals.json");
interest_group->trusted_bidding_signals_keys.emplace();
interest_group->trusted_bidding_signals_keys->push_back("key1");
interest_group->user_bidding_signals =
"{\"some\": \"json\", \"data\": {\"here\": [1, 2, 3]}}";
interest_group->ads.emplace();
auto ad = blink::mojom::InterestGroupAd::New();
ad->render_url = kAdUrl;
ad->metadata = "{\"ad\": \"metadata\", \"here\": [1, 2, 3]}";
interest_group->ads->push_back(std::move(ad));
interest_service->JoinInterestGroup(std::move(interest_group));
interest_service.FlushForTesting();
EXPECT_EQ(1, GetJoinCount(test_origin_a_, kGroupName));
}
absl::optional<GURL> RunAuctionBypassBlink(
blink::mojom::AuctionAdConfigPtr config) {
absl::optional<GURL> maybe_url;
base::RunLoop run_loop;
mojo::Remote<blink::mojom::AdAuctionService> auction_service;
AdAuctionServiceImpl::CreateMojoService(
shell()->web_contents()->GetMainFrame(),
auction_service.BindNewPipeAndPassReceiver());
auction_service->RunAdAuction(
std::move(config),
base::BindLambdaForTesting(
[&run_loop, &maybe_url](const absl::optional<GURL>& url) {
maybe_url = url;
run_loop.Quit();
}));
run_loop.Run();
return maybe_url;
}
url::Origin test_origin_a_;
};
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
BasicSuccess) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Optional(Eq(kAdUrl)));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
SellerNotHttps) {
// Running an ad auction on a non-https origin isn't allowed.
GURL test_url_b = embedded_test_server()->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url = embedded_test_server()->GetURL(
"b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
SellerDoesntMatchFrameOrigin) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
// Frame is `test_origin_a`, which doesn't match seller `test_origin_b`, so
// the auction fails.
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
WrongDecisionUrlOrigin) {
// The `decision_logic_url` origin doesn't match `seller`s, which is invalid.
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_a_;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
InterestGroupBuyerOriginNotHttps) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// Same hostname as `test_url_a_`, different scheme. This buyer is not valid
// because it is not https, so the auction fails.
GURL test_url_a_http = embedded_test_server()->GetURL("a.test", "/echo");
ASSERT_TRUE(test_url_a_http.SchemeIs(url::kHttpScheme));
url::Origin test_origin_a_http = url::Origin::Create(test_url_a_http);
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_http});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
InterestGroupBuyerOriginNotHttpsMultipleBuyers) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// Same hostname as `test_url_a_`, different scheme. This buyer is not valid
// because it is not https, so the auction fails, even though the other buyer
// is valid.
GURL test_url_a_http = embedded_test_server()->GetURL("a.test", "/echo");
ASSERT_TRUE(test_url_a_http.SchemeIs(url::kHttpScheme));
url::Origin test_origin_a_http = url::Origin::Create(test_url_a_http);
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers(
{test_origin_a_, test_origin_a_http});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
BuyerWithNoRegisteredInterestGroupsIgnored) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// New valid origin, not associated with any registered interest group. Its
// presence in the auctions `interest_group_buyers` shouldn't affect the
// auction outcome.
GURL test_url_c = https_server_->GetURL("c.test", "/echo");
ASSERT_TRUE(test_url_c.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_c = url::Origin::Create(test_url_c);
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_, test_origin_c});
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Optional(Eq(kAdUrl)));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
InterestGroupWildcardStarNotSupported) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_all_buyers(blink::mojom::AllBuyers::New());
// All buyers isn't supported.
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
PerBuyerSignalsValid) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// Per-buyer signals are valid because `test_origin_a_` is in the set of
// buyers, so the auction succeeds.
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_});
config->per_buyer_signals.emplace();
config->per_buyer_signals.value()[test_origin_a_] =
"{\"even\": \"more\", \"x\": 4.5}";
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Optional(Eq(kAdUrl)));
}
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTestRunAdAuctionBypassBlink,
PerBuyerSignalsNotSubsetOfBuyers) {
GURL test_url_b = https_server_->GetURL("b.test", "/echo");
ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
url::Origin test_origin_b = url::Origin::Create(test_url_b);
ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
// Per-buyer signals are invalid because `test_origin_a_` is not in the set of
// buyers, so the auction fails.
auto config = blink::mojom::AuctionAdConfig::New();
config->seller = test_origin_b;
config->decision_logic_url =
https_server_->GetURL("b.test", "/interest_group/decision_logic.js");
config->interest_group_buyers = blink::mojom::InterestGroupBuyers::New();
config->interest_group_buyers->set_buyers({test_origin_a_});
config->per_buyer_signals.emplace();
// `test_origin_b` isn't in `interest_group_buyers`.
config->per_buyer_signals.value()[test_origin_b] =
"{\"even\": \"more\", \"x\": 4.5}";
EXPECT_THAT(RunAuctionBypassBlink(std::move(config)), Eq(absl::nullopt));
}
} // namespace
} // namespace content