blob: d6406be157e63594f6a16c4519f10c77e9ad190a [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 "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "content/test/fenced_frame_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
#include "url/gurl.h"
#include "url/url_constants.h"
namespace content {
namespace {
// Validates the mapping contained in `pending_ad_components`.
//
// If `add_to_new_map` is false, `pending_ad_components` will be added to
// `fenced_frame_url_mapping` to mimic ShadowDOM behavior. Otherwise, they'll be
// added to a new FencedFrameURLMapping to mimic MPArch behavior, and
// `fenced_frame_url_mapping` is ignored.
//
// `expected_mapped_urls` contains the URLs the first URNs are expected to map
// to, and will be padded with "about:blank" URLs until it's
// blink::kMaxAdAuctionAdComponents in length.
void ValidatePendingAdComponentsMap(
FencedFrameURLMapping* fenced_frame_url_mapping,
bool add_to_new_map,
const FencedFrameURLMapping::PendingAdComponentsMap& pending_ad_components,
std::vector<GURL> expected_mapped_urls) {
// Get URN array from `pending_ad_components` and validate the returned URN
// array as much as possible prior to adding the URNs to
// `fenced_frame_url_mapping`, to the extent that's possible. This needs to be
// done before adding the URNs to `fenced_frame_url_mapping` so that this loop
// can make sure the URNs don't exist in the mapping yet.
std::vector<GURL> ad_component_urns = pending_ad_components.GetURNs();
ASSERT_EQ(blink::kMaxAdAuctionAdComponents, ad_component_urns.size());
for (size_t i = 0; i < ad_component_urns.size(); ++i) {
// All entries in `ad_component_urns` should be distinct URNs.
EXPECT_EQ(url::kUrnScheme, ad_component_urns[i].scheme_piece());
for (size_t j = 0; j < i; ++j)
EXPECT_NE(ad_component_urns[j], ad_component_urns[i]);
// The URNs should not yet be in `fenced_frame_url_mapping`, so they can
// safely be added to it.
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping->ConvertFencedFrameURNToURL(ad_component_urns[i],
&observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_FALSE(observer.mapped_url());
EXPECT_FALSE(observer.pending_ad_components_map());
}
// Add the `pending_ad_components` to a mapping. If `add_to_new_map` is true,
// use a new URL mapping.
FencedFrameURLMapping new_frame_url_mapping;
if (add_to_new_map)
fenced_frame_url_mapping = &new_frame_url_mapping;
pending_ad_components.ExportToMapping(*fenced_frame_url_mapping);
// Now validate the changes made to `fenced_frame_url_mapping`.
// PendingAdComponentsMap does not directly expose the URLs it provides, so
// can only check the URLs in after the URN/URL pairs have been added to a
// FencedFrameURLMapping.
for (size_t i = 0; i < ad_component_urns.size(); ++i) {
// The URNs should now be in `fenced_frame_url_mapping`. Look up the
// corresponding URL, and make sure it's mapped to the correct URL.
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping->ConvertFencedFrameURNToURL(ad_component_urns[i],
&observer);
EXPECT_TRUE(observer.mapping_complete_observed());
if (i < expected_mapped_urls.size()) {
EXPECT_EQ(expected_mapped_urls[i], observer.mapped_url());
} else {
EXPECT_EQ(GURL(url::kAboutBlankURL), observer.mapped_url());
}
// Each added URN should also have a populated
// `observer.pending_ad_components_map()` structure, to prevent ads from
// knowing if they were loaded in a fenced frame as an ad component or as
// the main ad. Any information passed to ads violates the k-anonymity
// requirement.
EXPECT_TRUE(observer.pending_ad_components_map());
// If this it not an about:blank URL, then when loaded in a fenced frame, it
// can recursively access its own nested ad components array, so recursively
// check those as well.
if (*observer.mapped_url() != GURL(url::kAboutBlankURL)) {
// Nested URL maps map everything to "about:blank". They exist solely so
// that top-level and nested component ads can't tell which one they are,
// to prevent smuggling data based on whether an ad is loaded in a
// top-level ad URL or a component ad URL.
ValidatePendingAdComponentsMap(
fenced_frame_url_mapping, add_to_new_map,
*observer.pending_ad_components_map(),
/*expected_mapped_urls=*/std::vector<GURL>());
}
}
}
} // namespace
TEST(FencedFrameURLMappingTest, AddAndConvert) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL test_url("https://foo.test");
GURL urn_uuid = fenced_frame_url_mapping.AddFencedFrameURL(test_url);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(test_url, observer.mapped_url());
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
}
TEST(FencedFrameURLMappingTest, NonExistentUUID) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL urn_uuid("urn:uuid:c36973b5-e5d9-de59-e4c4-364f137b3c7a");
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(absl::nullopt, observer.mapped_url());
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
}
TEST(FencedFrameURLMappingTest, PendingMappedUUID_MappingSuccess) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid = fenced_frame_url_mapping.GeneratePendingMappedURN();
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_FALSE(observer.mapping_complete_observed());
fenced_frame_url_mapping.OnURNMappingResultDetermined(
urn_uuid, GURL("https://foo.com"));
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer.mapped_url());
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
}
TEST(FencedFrameURLMappingTest, PendingMappedUUID_MappingFailure) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid = fenced_frame_url_mapping.GeneratePendingMappedURN();
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_FALSE(observer.mapping_complete_observed());
fenced_frame_url_mapping.OnURNMappingResultDetermined(urn_uuid,
absl::nullopt);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(absl::nullopt, observer.mapped_url());
EXPECT_EQ(absl::nullopt, observer.pending_ad_components_map());
}
TEST(FencedFrameURLMappingTest, RemoveObserverOnPendingMappedUUID) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid = fenced_frame_url_mapping.GeneratePendingMappedURN();
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_FALSE(observer.mapping_complete_observed());
fenced_frame_url_mapping.RemoveObserverForURN(urn_uuid, &observer);
fenced_frame_url_mapping.OnURNMappingResultDetermined(
urn_uuid, GURL("https://foo.com"));
EXPECT_FALSE(observer.mapping_complete_observed());
}
TEST(FencedFrameURLMappingTest, RegisterTwoObservers) {
FencedFrameURLMapping fenced_frame_url_mapping;
const GURL urn_uuid = fenced_frame_url_mapping.GeneratePendingMappedURN();
TestFencedFrameURLMappingResultObserver observer1;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer1);
EXPECT_FALSE(observer1.mapping_complete_observed());
TestFencedFrameURLMappingResultObserver observer2;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer2);
EXPECT_FALSE(observer2.mapping_complete_observed());
fenced_frame_url_mapping.OnURNMappingResultDetermined(
urn_uuid, GURL("https://foo.com"));
EXPECT_TRUE(observer1.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer1.mapped_url());
EXPECT_EQ(absl::nullopt, observer1.pending_ad_components_map());
EXPECT_TRUE(observer2.mapping_complete_observed());
EXPECT_EQ(GURL("https://foo.com"), observer2.mapped_url());
EXPECT_EQ(absl::nullopt, observer2.pending_ad_components_map());
}
// Test the case `ad_component_urls` is empty. In this case, it should be filled
// with URNs that are mapped to about:blank.
TEST(FencedFrameURLMappingTest,
AddFencedFrameURLWithInterestGroupAdComponentUrlsNoAdComponentsUrls) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
std::vector<GURL> ad_component_urls;
GURL urn_uuid = fenced_frame_url_mapping
.AddFencedFrameURLWithInterestGroupAdComponentUrls(
top_level_url, ad_component_urls);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_TRUE(observer.pending_ad_components_map());
// Call with `add_to_new_map` set to false and true, to simulate ShadowDOM
// and MPArch behavior, respectively.
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/true,
*observer.pending_ad_components_map(),
/*expected_mapped_urls=*/{});
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/false,
*observer.pending_ad_components_map(),
/*expected_mapped_urls=*/{});
}
// Test the case `ad_component_urls` has a single URL.
TEST(FencedFrameURLMappingTest,
AddFencedFrameURLWithInterestGroupAdComponentUrlsOneAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
std::vector<GURL> ad_component_urls{GURL("https://bar.test")};
GURL urn_uuid = fenced_frame_url_mapping
.AddFencedFrameURLWithInterestGroupAdComponentUrls(
top_level_url, ad_component_urls);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_TRUE(observer.pending_ad_components_map());
// Call with `add_to_new_map` set to false and true, to simulate ShadowDOM
// and MPArch behavior, respectively.
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/true,
*observer.pending_ad_components_map(),
ad_component_urls);
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/false,
*observer.pending_ad_components_map(),
ad_component_urls);
}
// Test the case `ad_component_urls` has the maximum number of allowed ad
// component URLs.
TEST(FencedFrameURLMappingTest,
AddFencedFrameURLWithInterestGroupAdComponentUrlsMaxAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
std::vector<GURL> ad_component_urls;
for (size_t i = 0; i < blink::kMaxAdAuctionAdComponents; ++i) {
ad_component_urls.emplace_back(
GURL(base::StringPrintf("https://%zu.test/", i)));
}
GURL urn_uuid = fenced_frame_url_mapping
.AddFencedFrameURLWithInterestGroupAdComponentUrls(
top_level_url, ad_component_urls);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_TRUE(observer.pending_ad_components_map());
// Call with `add_to_new_map` set to false and true, to simulate ShadowDOM
// and MPArch behavior, respectively.
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/true,
*observer.pending_ad_components_map(),
ad_component_urls);
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/false,
*observer.pending_ad_components_map(),
ad_component_urls);
}
// Test the case `ad_component_urls` has the maximum number of allowed ad
// component URLs, and they're all identical. The main purpose of this test is
// to make sure they receive unique URNs, despite being identical URLs.
TEST(
FencedFrameURLMappingTest,
AddFencedFrameURLWithInterestGroupAdComponentUrlsMaxIdenticalAdComponentUrl) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL top_level_url("https://foo.test");
std::vector<GURL> ad_component_urls(blink::kMaxAdAuctionAdComponents,
GURL("https://bar.test/"));
GURL urn_uuid = fenced_frame_url_mapping
.AddFencedFrameURLWithInterestGroupAdComponentUrls(
top_level_url, ad_component_urls);
TestFencedFrameURLMappingResultObserver observer;
fenced_frame_url_mapping.ConvertFencedFrameURNToURL(urn_uuid, &observer);
EXPECT_TRUE(observer.mapping_complete_observed());
EXPECT_EQ(top_level_url, observer.mapped_url());
EXPECT_TRUE(observer.pending_ad_components_map());
// Call with `add_to_new_map` set to false and true, to simulate ShadowDOM
// and MPArch behavior, respectively.
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/true,
*observer.pending_ad_components_map(),
/*expected_mapped_urls=*/ad_component_urls);
ValidatePendingAdComponentsMap(&fenced_frame_url_mapping,
/*add_to_new_map=*/false,
*observer.pending_ad_components_map(),
/*expected_mapped_urls=*/ad_component_urls);
}
// Test the correctness of the URN format. The URN is expected to be in the
// format "urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" as per RFC-4122.
TEST(FencedFrameURLMappingTest, HasCorrectFormat) {
FencedFrameURLMapping fenced_frame_url_mapping;
GURL test_url("https://foo.test");
GURL urn_uuid = fenced_frame_url_mapping.AddFencedFrameURL(test_url);
std::string spec = urn_uuid.spec();
ASSERT_TRUE(base::StartsWith(
spec, "urn:uuid:", base::CompareCase::INSENSITIVE_ASCII));
EXPECT_EQ(spec.at(17), '-');
EXPECT_EQ(spec.at(22), '-');
EXPECT_EQ(spec.at(27), '-');
EXPECT_EQ(spec.at(32), '-');
EXPECT_TRUE(fenced_frame_url_mapping.IsValidUrnUuidURL(urn_uuid));
}
} // namespace content