summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/ad_auction
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2021-10-26 13:57:00 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2021-11-02 11:31:01 +0000
commit1943b3c2a1dcee36c233724fc4ee7613d71b9cf6 (patch)
tree8c1b5f12357025c197da5427ae02cfdc2f3570d6 /chromium/third_party/blink/renderer/modules/ad_auction
parent21ba0c5d4bf8fba15dddd97cd693bad2358b77fd (diff)
downloadqtwebengine-chromium-1943b3c2a1dcee36c233724fc4ee7613d71b9cf6.tar.gz
BASELINE: Update Chromium to 94.0.4606.111
Change-Id: I924781584def20fc800bedf6ff41fdb96c438193 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/ad_auction')
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/BUILD.gn16
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/DEPS11
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/DIR_METADATA3
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/idls.gni11
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc105
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.h2
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl3
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.cc104
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h34
-rw-r--r--chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group_test.cc392
10 files changed, 642 insertions, 39 deletions
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/BUILD.gn b/chromium/third_party/blink/renderer/modules/ad_auction/BUILD.gn
index 820b2f7e2c9..22f7b5cbfa4 100644
--- a/chromium/third_party/blink/renderer/modules/ad_auction/BUILD.gn
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/BUILD.gn
@@ -8,5 +8,21 @@ blink_modules_sources("ad_auction") {
sources = [
"navigator_auction.cc",
"navigator_auction.h",
+ "validate_blink_interest_group.cc",
+ "validate_blink_interest_group.h",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "validate_blink_interest_group_test.cc" ]
+
+ deps = [
+ "//base",
+ "//testing/gtest:gtest",
+ "//third_party/blink/public:test_headers",
+ "//third_party/blink/public/common:headers",
+ "//third_party/blink/renderer/modules:modules",
+ "//url",
]
}
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/DEPS b/chromium/third_party/blink/renderer/modules/ad_auction/DEPS
new file mode 100644
index 00000000000..f6bde18f96f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ "+url/url_constants.h",
+]
+
+specific_include_rules = {
+ "validate_blink_interest_group_test.cc": [
+ "+base",
+ "+url/gurl.h",
+ "+url/origin.h",
+ ],
+}
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/DIR_METADATA b/chromium/third_party/blink/renderer/modules/ad_auction/DIR_METADATA
new file mode 100644
index 00000000000..5313345b4ca
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail {
+ component: "Blink>InterestGroups"
+}
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/idls.gni b/chromium/third_party/blink/renderer/modules/ad_auction/idls.gni
deleted file mode 100644
index 3ed27eec0c2..00000000000
--- a/chromium/third_party/blink/renderer/modules/ad_auction/idls.gni
+++ /dev/null
@@ -1,11 +0,0 @@
-# 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.
-
-modules_dependency_idl_files = [ "navigator_auction.idl" ]
-
-modules_dictionary_idl_files = [
- "auction_ad_config.idl",
- "auction_ad_interest_group.idl",
- "auction_ad.idl",
-]
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc b/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
index 0fba4da4ea8..956a70bb352 100644
--- a/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
@@ -4,11 +4,16 @@
#include "third_party/blink/renderer/modules/ad_auction/navigator_auction.h"
+#include <utility>
+
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_union_usvstring_usvstringsequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_auction_ad_interest_group.h"
+#include "third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin_hash.h"
@@ -90,7 +95,8 @@ scoped_refptr<const SecurityOrigin> ParseOrigin(const String& origin_string) {
// joinAdInterestGroup() copy functions.
-bool CopyOwnerFromIdlToMojo(ExceptionState& exception_state,
+bool CopyOwnerFromIdlToMojo(const ExecutionContext& execution_context,
+ ExceptionState& exception_state,
const AuctionAdInterestGroup& input,
mojom::blink::InterestGroup& output) {
scoped_refptr<const SecurityOrigin> owner = ParseOrigin(input.owner());
@@ -101,6 +107,16 @@ bool CopyOwnerFromIdlToMojo(ExceptionState& exception_state,
input.owner().Utf8().c_str(), input.name().Utf8().c_str()));
return false;
}
+
+ if (!execution_context.GetSecurityOrigin()->IsSameOriginWith(owner.get())) {
+ exception_state.ThrowTypeError(String::Format(
+ "owner '%s' for AuctionAdInterestGroup with name '%s' match frame "
+ "origin '%s'.",
+ input.owner().Utf8().c_str(), input.name().Utf8().c_str(),
+ owner->ToString().Utf8().c_str()));
+ return false;
+ }
+
output.owner = std::move(owner);
return true;
}
@@ -256,31 +272,37 @@ bool CopyInterestGroupBuyersFromIdlToMojo(
if (!input.hasInterestGroupBuyers())
return true;
output.interest_group_buyers = mojom::blink::InterestGroupBuyers::New();
- if (input.interestGroupBuyers().IsUSVString()) {
- String maybe_wildcard = input.interestGroupBuyers().GetAsUSVString();
- if (maybe_wildcard != "*") {
- exception_state.ThrowTypeError(ErrorInvalidAuctionConfig(
- input, "interestGroupBuyers", maybe_wildcard,
- "must be \"*\" (wildcard) or a list of buyer https origin strings."));
- return false;
- }
- output.interest_group_buyers->set_all_buyers(
- mojom::blink::AllBuyers::New());
- } else {
- DCHECK(input.interestGroupBuyers().IsUSVStringSequence());
- Vector<scoped_refptr<const SecurityOrigin>> buyers;
- for (const auto& buyer_str :
- input.interestGroupBuyers().GetAsUSVStringSequence()) {
- scoped_refptr<const SecurityOrigin> buyer = ParseOrigin(buyer_str);
- if (!buyer) {
+ switch (input.interestGroupBuyers()->GetContentType()) {
+ case V8UnionUSVStringOrUSVStringSequence::ContentType::kUSVString: {
+ const String& maybe_wildcard =
+ input.interestGroupBuyers()->GetAsUSVString();
+ if (maybe_wildcard != "*") {
exception_state.ThrowTypeError(ErrorInvalidAuctionConfig(
- input, "interestGroupBuyers buyer", buyer_str,
- "must be a valid https origin."));
+ input, "interestGroupBuyers", maybe_wildcard,
+ "must be \"*\" (wildcard) or a list of buyer https origin "
+ "strings."));
return false;
}
- buyers.push_back(buyer);
+ output.interest_group_buyers->set_all_buyers(
+ mojom::blink::AllBuyers::New());
+ break;
+ }
+ case V8UnionUSVStringOrUSVStringSequence::ContentType::kUSVStringSequence: {
+ Vector<scoped_refptr<const SecurityOrigin>> buyers;
+ for (const auto& buyer_str :
+ input.interestGroupBuyers()->GetAsUSVStringSequence()) {
+ scoped_refptr<const SecurityOrigin> buyer = ParseOrigin(buyer_str);
+ if (!buyer) {
+ exception_state.ThrowTypeError(ErrorInvalidAuctionConfig(
+ input, "interestGroupBuyers buyer", buyer_str,
+ "must be a valid https origin."));
+ return false;
+ }
+ buyers.push_back(buyer);
+ }
+ output.interest_group_buyers->set_buyers(std::move(buyers));
+ break;
}
- output.interest_group_buyers->set_buyers(std::move(buyers));
}
return true;
@@ -384,26 +406,42 @@ void NavigatorAuction::joinAdInterestGroup(ScriptState* script_state,
auto mojo_group = mojom::blink::InterestGroup::New();
mojo_group->expiry =
base::Time::Now() + base::TimeDelta::FromSecondsD(duration_seconds);
- if (!CopyOwnerFromIdlToMojo(exception_state, *group, *mojo_group))
+ if (!CopyOwnerFromIdlToMojo(*context, exception_state, *group, *mojo_group))
return;
mojo_group->name = group->name();
if (!CopyBiddingLogicUrlFromIdlToMojo(*context, exception_state, *group,
- *mojo_group))
+ *mojo_group)) {
return;
+ }
if (!CopyDailyUpdateUrlFromIdlToMojo(*context, exception_state, *group,
- *mojo_group))
+ *mojo_group)) {
return;
+ }
if (!CopyTrustedBiddingSignalsUrlFromIdlToMojo(*context, exception_state,
- *group, *mojo_group))
+ *group, *mojo_group)) {
return;
+ }
if (!CopyTrustedBiddingSignalsKeysFromIdlToMojo(*group, *mojo_group))
return;
if (!CopyUserBiddingSignalsFromIdlToMojo(*script_state, exception_state,
- *group, *mojo_group))
+ *group, *mojo_group)) {
return;
+ }
if (!CopyAdsFromIdlToMojo(*context, *script_state, exception_state, *group,
- *mojo_group))
+ *mojo_group)) {
return;
+ }
+
+ String error_field_name;
+ String error_field_value;
+ String error;
+ if (!ValidateBlinkInterestGroup(
+ *mojo_group, error_field_name, error_field_value, error)) {
+ exception_state.ThrowTypeError(ErrorInvalidInterestGroup(
+ *group, error_field_name, error_field_value, error));
+ return;
+ }
+
interest_group_store_->JoinInterestGroup(std::move(mojo_group));
}
@@ -441,6 +479,17 @@ void NavigatorAuction::leaveAdInterestGroup(ScriptState* script_state,
.leaveAdInterestGroup(script_state, group, exception_state);
}
+void NavigatorAuction::updateAdInterestGroups() {
+ interest_group_store_->UpdateAdInterestGroups();
+}
+
+/* static */
+void NavigatorAuction::updateAdInterestGroups(ScriptState* script_state,
+ Navigator& navigator) {
+ return From(ExecutionContext::From(script_state), navigator)
+ .updateAdInterestGroups();
+}
+
ScriptPromise NavigatorAuction::runAdAuction(ScriptState* script_state,
const AuctionAdConfig* config,
ExceptionState& exception_state) {
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.h b/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.h
index f58a2070d3f..ee8d74ddb6e 100644
--- a/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.h
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.h
@@ -47,6 +47,8 @@ class MODULES_EXPORT NavigatorAuction final
Navigator&,
const AuctionAdInterestGroup*,
ExceptionState&);
+ void updateAdInterestGroups();
+ static void updateAdInterestGroups(ScriptState*, Navigator&);
ScriptPromise runAdAuction(ScriptState*,
const AuctionAdConfig*,
ExceptionState&);
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl b/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl
index 44f95cbb45e..4d7941c6242 100644
--- a/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl
@@ -16,6 +16,9 @@
[CallWith=ScriptState, Measure, RaisesException]
void leaveAdInterestGroup(AuctionAdInterestGroup group);
+ [CallWith=ScriptState, Measure]
+ void updateAdInterestGroups();
+
[CallWith=ScriptState, Measure, RaisesException]
Promise<USVString?> runAdAuction(AuctionAdConfig config);
};
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.cc b/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.cc
new file mode 100644
index 00000000000..6ff2dbe16d3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.cc
@@ -0,0 +1,104 @@
+// 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 "third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h"
+
+#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-blink.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+#include "url/url_constants.h"
+
+namespace blink {
+
+namespace {
+
+// Check if `url` can be used as an interest group's ad render URL. Ad URLs can
+// be cross origin, unlike other interest group URLs, but are still restricted
+// to HTTPS with no embedded credentials.
+bool IsUrlAllowedForRenderUrls(const KURL& url) {
+ if (!url.IsValid() || !url.ProtocolIs(url::kHttpsScheme))
+ return false;
+
+ return url.User().IsEmpty() && url.Pass().IsEmpty();
+}
+
+// Check if `url` can be used with the specified interest group for any of
+// script URL, update URL, or realtime data URL. Ad render URLs should be
+// checked with IsUrlAllowedForRenderUrls(), which doesn't have the same-origin
+// check, and allows references.
+bool IsUrlAllowed(const KURL& url, const mojom::blink::InterestGroup& group) {
+ if (!group.owner->IsSameOriginWith(SecurityOrigin::Create(url).get()))
+ return false;
+
+ return IsUrlAllowedForRenderUrls(url) && !url.HasFragmentIdentifier();
+}
+
+} // namespace
+
+// The logic in this method must be kept in sync with InterestGroup::IsValid()
+// in blink/common/interest_group/.
+bool ValidateBlinkInterestGroup(const mojom::blink::InterestGroup& group,
+ String& error_field_name,
+ String& error_field_value,
+ String& error) {
+ if (group.owner->Protocol() != url::kHttpsScheme) {
+ error_field_name = String::FromUTF8("owner");
+ error_field_value = group.owner->ToString();
+ error = String::FromUTF8("owner origin must be HTTPS.");
+ return false;
+ }
+
+ if (group.bidding_url && !IsUrlAllowed(*group.bidding_url, group)) {
+ error_field_name = String::FromUTF8("biddingUrl");
+ error_field_value = group.bidding_url->GetString();
+ error = String::FromUTF8(
+ "biddingUrl must have the same origin as the InterestGroup owner "
+ "and have no fragment identifier or embedded credentials.");
+ return false;
+ }
+
+ if (group.update_url && !IsUrlAllowed(*group.update_url, group)) {
+ error_field_name = String::FromUTF8("updateUrl");
+ error_field_value = group.update_url->GetString();
+ error = String::FromUTF8(
+ "updateUrl must have the same origin as the InterestGroup owner "
+ "and have no fragment identifier or embedded credentials.");
+ return false;
+ }
+
+ if (group.trusted_bidding_signals_url) {
+ // In addition to passing the same checks used on the other URLs,
+ // `trusted_bidding_signals_url` must not have a query string, since the
+ // query parameter needs to be set as part of running an auction.
+ if (!IsUrlAllowed(*group.trusted_bidding_signals_url, group) ||
+ !group.trusted_bidding_signals_url->Query().IsEmpty()) {
+ error_field_name = String::FromUTF8("trustedBiddingSignalsUrl");
+ error_field_value = group.trusted_bidding_signals_url->GetString();
+ error = String::FromUTF8(
+ "trustedBiddingSignalsUrl must have the same origin as the "
+ "InterestGroup owner and have no query string, fragment identifier "
+ "or embedded credentials.");
+ return false;
+ }
+ }
+
+ if (group.ads) {
+ for (WTF::wtf_size_t i = 0; i < group.ads.value().size(); ++i) {
+ const KURL& render_url = group.ads.value()[i]->render_url;
+ if (!IsUrlAllowedForRenderUrls(render_url)) {
+ error_field_name = String::Format("ad[%u].renderUrl", i);
+ error_field_value = render_url.GetString();
+ error = String::FromUTF8(
+ "renderUrls must be HTTPS and have no embedded credentials.");
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h b/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h
new file mode 100644
index 00000000000..932c18715d8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_AD_AUCTION_VALIDATE_BLINK_INTEREST_GROUP_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_AD_AUCTION_VALIDATE_BLINK_INTEREST_GROUP_H_
+
+#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-blink-forward.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+// Checks that the specified mojom::blink::InterestGroup is valid. Invalid
+// interest groups contain auction or update URLs cross-origin to the owner, or
+// URLs that contain disallowed components (e.g., user/pass). When it returns
+// false, writes information about the error to `error_field_name`,
+// `error_field_value`, and `error`.
+//
+// Checks all provided URLs. Does no validation of expiration time. Does no
+// validation of values expected to be in JSON, since ValidateInterestGroup()
+// does not validate JSON. Must be kept in sync with ValidateInterestGroup(),
+// which performs the exact same logic, except on mojom::InterestGroups, and is
+// used to validate InterestGroups received from a less trusted renderer
+// process.
+MODULES_EXPORT bool ValidateBlinkInterestGroup(
+ const mojom::blink::InterestGroup& group,
+ String& error_field_name,
+ String& error_field_value,
+ String& error);
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_AD_AUCTION_VALIDATE_BLINK_INTEREST_GROUP_H_
diff --git a/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group_test.cc b/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group_test.cc
new file mode 100644
index 00000000000..ce14a446729
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group_test.cc
@@ -0,0 +1,392 @@
+// 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 "third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/bindings/array_traits_wtf_vector.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/interest_group/interest_group.h"
+#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-blink.h"
+#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace blink {
+
+namespace {
+
+constexpr char kOriginString[] = "https://origin.test/";
+constexpr char kNameString[] = "name";
+
+} // namespace
+
+// Test fixture for testing both ValidateBlinkInterestGroup() and
+// ValidateInterestGroup(), and making sure they behave the same.
+class ValidateBlinkInterestGroupTest : public testing::Test {
+ public:
+ // Check that `blink_interest_group` is valid, if added from its owner origin.
+ void ExpectInterestGroupIsValid(
+ const mojom::blink::InterestGroupPtr& blink_interest_group) {
+ String error_field_name;
+ String error_field_value;
+ String error;
+ EXPECT_TRUE(ValidateBlinkInterestGroup(
+ *blink_interest_group, error_field_name, error_field_value, error));
+ EXPECT_TRUE(error_field_name.IsNull());
+ EXPECT_TRUE(error_field_value.IsNull());
+ EXPECT_TRUE(error.IsNull());
+
+ EXPECT_TRUE(CanSerializeAndDeserialize(blink_interest_group));
+ }
+
+ // Check that `blink_interest_group` is valid, if added from `blink_origin`,
+ // and returns the provided error values.
+ void ExpectInterestGroupIsNotValid(
+ const mojom::blink::InterestGroupPtr& blink_interest_group,
+ const std::string& expected_error_field_name,
+ const std::string& expected_error_field_value,
+ const std::string& expected_error) {
+ String error_field_name;
+ String error_field_value;
+ String error;
+ EXPECT_FALSE(ValidateBlinkInterestGroup(
+ *blink_interest_group, error_field_name, error_field_value, error));
+ EXPECT_EQ(String::FromUTF8(expected_error_field_name), error_field_name);
+ EXPECT_EQ(String::FromUTF8(expected_error_field_value), error_field_value);
+ EXPECT_EQ(String::FromUTF8(expected_error), error);
+
+ EXPECT_FALSE(CanSerializeAndDeserialize(blink_interest_group));
+ }
+
+ // Tries to Converts a mojom::blink::InterestGroupPtr to a
+ // blink::InterestGroup by using Mojo to serialize and deserialize it. Returns
+ // true on success, false on failure. Failure indicates the traits conversion
+ // logic refused to serialize the InterestGroup, since it was invalid. Based
+ // off of mojo::test::SerializeAndDeserialize(), which can't convert between
+ // blink and non-blink types.
+ bool CanSerializeAndDeserialize(
+ const mojom::blink::InterestGroupPtr& blink_interest_group) {
+ mojo::Message message =
+ mojom::blink::InterestGroup::SerializeAsMessage(&blink_interest_group);
+ mojo::ScopedMessageHandle handle = message.TakeMojoMessage();
+ message = mojo::Message::CreateFromMessageHandle(&handle);
+ DCHECK(!message.IsNull());
+
+ auto interest_group = std::make_unique<blink::InterestGroup>();
+ return mojom::InterestGroup::DeserializeFromMessage(std::move(message),
+ interest_group.get());
+ }
+
+ // Creates and returns a minimally populated mojom::blink::InterestGroup.
+ mojom::blink::InterestGroupPtr CreateMinimalInterestGroup() {
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ mojom::blink::InterestGroup::New();
+ blink_interest_group->owner = kOrigin;
+ blink_interest_group->name = kName;
+ return blink_interest_group;
+ }
+
+ // Creates an interest group with all fields populated with valid values.
+ mojom::blink::InterestGroupPtr CreateFullyPopulatedInterestGroup() {
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateMinimalInterestGroup();
+
+ // Url that's allowed in every field. Populate all portions of the URL that
+ // are allowed in most places.
+ const KURL kAllowedUrl =
+ KURL(String::FromUTF8("https://origin.test/foo?bar"));
+ blink_interest_group->bidding_url = kAllowedUrl;
+ blink_interest_group->update_url = kAllowedUrl;
+
+ // `trusted_bidding_signals_url` doesn't allow query strings, unlike the
+ // above ones.
+ blink_interest_group->trusted_bidding_signals_url =
+ KURL(String::FromUTF8("https://origin.test/foo"));
+
+ blink_interest_group->trusted_bidding_signals_keys.emplace();
+ blink_interest_group->trusted_bidding_signals_keys->push_back(
+ String::FromUTF8("1"));
+ blink_interest_group->trusted_bidding_signals_keys->push_back(
+ String::FromUTF8("2"));
+ blink_interest_group->user_bidding_signals =
+ String::FromUTF8("\"This field isn't actually validated\"");
+
+ // Add two ads. Use different URLs, with references.
+ blink_interest_group->ads.emplace();
+ auto mojo_ad1 = mojom::blink::InterestGroupAd::New();
+ mojo_ad1->render_url =
+ KURL(String::FromUTF8("https://origin.test/foo?bar#baz"));
+ mojo_ad1->metadata =
+ String::FromUTF8("\"This field isn't actually validated\"");
+ blink_interest_group->ads->push_back(std::move(mojo_ad1));
+ auto mojo_ad2 = mojom::blink::InterestGroupAd::New();
+ mojo_ad2->render_url =
+ KURL(String::FromUTF8("https://origin.test/foo?bar#baz2"));
+ blink_interest_group->ads->push_back(std::move(mojo_ad2));
+
+ return blink_interest_group;
+ }
+
+ protected:
+ // SecurityOrigin used as the owner in most tests.
+ const scoped_refptr<const SecurityOrigin> kOrigin =
+ SecurityOrigin::CreateFromString(String::FromUTF8(kOriginString));
+
+ const String kName = String::FromUTF8(kNameString);
+};
+
+// Test behavior with an InterestGroup with as few fields populated as allowed.
+TEST_F(ValidateBlinkInterestGroupTest, MinimallyPopulated) {
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateMinimalInterestGroup();
+ ExpectInterestGroupIsValid(blink_interest_group);
+}
+
+// Test behavior with an InterestGroup with all fields populated with valid
+// values.
+TEST_F(ValidateBlinkInterestGroupTest, FullyPopulated) {
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateFullyPopulatedInterestGroup();
+ ExpectInterestGroupIsValid(blink_interest_group);
+}
+
+// Make sure that non-HTTPS origins are rejected, both as the frame origin, and
+// as the owner. HTTPS frame origins with non-HTTPS owners are currently
+// rejected due to origin mismatch, but once sites can add users to 3P interest
+// groups, they should still be rejected for being non-HTTPS.
+TEST_F(ValidateBlinkInterestGroupTest, NonHttpsOriginRejected) {
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateMinimalInterestGroup();
+ blink_interest_group->owner =
+ SecurityOrigin::CreateFromString(String::FromUTF8("http://origin.test/"));
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group, "owner" /* expected_error_field_name */,
+ "http://origin.test" /* expected_error_field_value */,
+ "owner origin must be HTTPS." /* expected_error */);
+
+ blink_interest_group->owner =
+ SecurityOrigin::CreateFromString(String::FromUTF8("data:,foo"));
+ // Data URLs have opaque origins, which are mapped to the string "null".
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group, "owner" /* expected_error_field_name */,
+ "null" /* expected_error_field_value */,
+ "owner origin must be HTTPS." /* expected_error */);
+}
+
+// Check that `bidding_url`, `update_url`, and `trusted_bidding_signals_url`
+// must be same-origin and HTTPS.
+//
+// Ad URLs do not have to be same origin, so they're checked in a different
+// test.
+TEST_F(ValidateBlinkInterestGroupTest, RejectedUrls) {
+ // Strings when each field has a bad URL, copied from cc file.
+ const char kBadBiddingUrlError[] =
+ "biddingUrl must have the same origin as the InterestGroup owner "
+ "and have no fragment identifier or embedded credentials.";
+ const char kBadUpdateUrlError[] =
+ "updateUrl must have the same origin as the InterestGroup owner "
+ "and have no fragment identifier or embedded credentials.";
+ const char kBadTrustedBiddingSignalsUrlError[] =
+ "trustedBiddingSignalsUrl must have the same origin as the "
+ "InterestGroup owner and have no query string, fragment identifier "
+ "or embedded credentials.";
+
+ // Nested URL schemes, like filesystem URLs, are the only cases where a URL
+ // being same origin with an HTTPS origin does not imply the URL itself is
+ // also HTTPS.
+ const KURL kFileSystemUrl =
+ KURL(String::FromUTF8("filesystem:https://origin.test/foo"));
+ EXPECT_TRUE(
+ kOrigin->IsSameOriginWith(SecurityOrigin::Create(kFileSystemUrl).get()));
+
+ const KURL kRejectedUrls[] = {
+ // HTTP URLs is rejected: it's both the wrong scheme, and cross-origin.
+ KURL(String::FromUTF8("filesystem:http://origin.test/foo")),
+ // Cross origin HTTPS URLs are rejected.
+ KURL(String::FromUTF8("https://origin2.test/foo")),
+ // URL with different ports are cross-origin.
+ KURL(String::FromUTF8("https://origin.test:1234/")),
+ // URLs with opaque origins are cross-origin.
+ KURL(String::FromUTF8("data://text/html,payload")),
+ // Unknown scheme.
+ KURL(String::FromUTF8("unknown-scheme://foo/")),
+
+ // filesystem URLs are rejected, even if they're same-origin with the page
+ // origin.
+ kFileSystemUrl,
+
+ // URLs with user/ports are rejected.
+ KURL(String::FromUTF8("https://user:pass@origin.test/")),
+ // References also aren't allowed, as they aren't sent over HTTP.
+ KURL(String::FromUTF8("https://origin.test/#foopy")),
+
+ // Invalid URLs.
+ KURL(String::FromUTF8("")),
+ KURL(String::FromUTF8("invalid url")),
+ KURL(String::FromUTF8("https://!@#$%^&*()/")),
+ KURL(String::FromUTF8("https://[1::::::2]/")),
+ KURL(String::FromUTF8("https://origin.test/%00")),
+ };
+
+ for (const KURL& rejected_url : kRejectedUrls) {
+ SCOPED_TRACE(rejected_url.GetString());
+
+ // Test `bidding_url`.
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateMinimalInterestGroup();
+ blink_interest_group->bidding_url = rejected_url;
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group, "biddingUrl" /* expected_error_field_name */,
+ rejected_url.GetString().Utf8() /* expected_error_field_value */,
+ kBadBiddingUrlError /* expected_error */);
+
+ // Test `update_url`.
+ blink_interest_group = CreateMinimalInterestGroup();
+ blink_interest_group->update_url = rejected_url;
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group, "updateUrl" /* expected_error_field_name */,
+ rejected_url.GetString().Utf8() /* expected_error_field_value */,
+ // expected_error
+ kBadUpdateUrlError /* expected_error */);
+
+ // Test `trusted_bidding_signals_url`.
+ blink_interest_group = CreateMinimalInterestGroup();
+ blink_interest_group->trusted_bidding_signals_url = rejected_url;
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group,
+ "trustedBiddingSignalsUrl" /* expected_error_field_name */,
+ rejected_url.GetString().Utf8() /* expected_error_field_value */,
+ kBadTrustedBiddingSignalsUrlError /* expected_error */);
+ }
+
+ // `trusted_bidding_signals_url` also can't include query strings.
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateMinimalInterestGroup();
+ KURL rejected_url = KURL(String::FromUTF8("https://origin.test/?query"));
+ blink_interest_group->trusted_bidding_signals_url = rejected_url;
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group,
+ "trustedBiddingSignalsUrl" /* expected_error_field_name */,
+ rejected_url.GetString().Utf8() /* expected_error_field_value */,
+ kBadTrustedBiddingSignalsUrlError /* expected_error */);
+}
+
+// Tests valid and invalid ad render URLs.
+TEST_F(ValidateBlinkInterestGroupTest, AdRenderUrlValidation) {
+ const char kBadAdUrlError[] =
+ "renderUrls must be HTTPS and have no embedded credentials.";
+
+ const struct {
+ bool expect_allowed;
+ const char* url;
+ } kTestCases[] = {
+ // Same origin URLs are allowed.
+ {true, "https://origin.test/foo?bar"},
+
+ // Cross origin URLs are allowed, as long as they're HTTPS.
+ {true, "https://b.test/"},
+ {true, "https://a.test:1234/"},
+
+ // URLs with the wrong scheme are rejected.
+ {false, "http://a.test/"},
+ {false, "data://text/html,payload"},
+ {false, "filesystem:https://a.test/foo"},
+
+ // URLs with user/ports are rejected.
+ {false, "https://user:pass@a.test/"},
+
+ // References are allowed for ads, though not other requests, since they
+ // only have an effect when loading a page in a renderer.
+ {true, "https://a.test/#foopy"},
+ };
+
+ for (const auto& test_case : kTestCases) {
+ SCOPED_TRACE(test_case.url);
+
+ KURL test_case_url = KURL(String::FromUTF8(test_case.url));
+
+ // Add an InterestGroup with the test cases's URL as the only ad's URL.
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ CreateMinimalInterestGroup();
+ blink_interest_group->ads.emplace();
+ blink_interest_group->ads->emplace_back(mojom::blink::InterestGroupAd::New(
+ test_case_url, String() /* metadata */));
+ if (test_case.expect_allowed) {
+ ExpectInterestGroupIsValid(blink_interest_group);
+ } else {
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group,
+ "ad[0].renderUrl" /* expected_error_field_name */,
+ test_case_url.GetString().Utf8() /* expected_error_field_value */,
+ kBadAdUrlError /* expected_error */);
+ }
+
+ // Add an InterestGroup with the test cases's URL as the second ad's URL.
+ blink_interest_group = CreateMinimalInterestGroup();
+ blink_interest_group->ads.emplace();
+ blink_interest_group->ads->emplace_back(mojom::blink::InterestGroupAd::New(
+ KURL(String::FromUTF8("https://origin.test/")),
+ String() /* metadata */));
+ blink_interest_group->ads->emplace_back(mojom::blink::InterestGroupAd::New(
+ test_case_url, String() /* metadata */));
+ if (test_case.expect_allowed) {
+ ExpectInterestGroupIsValid(blink_interest_group);
+ } else {
+ ExpectInterestGroupIsNotValid(
+ blink_interest_group,
+ "ad[1].renderUrl" /* expected_error_field_name */,
+ test_case_url.GetString().Utf8() /* expected_error_field_value */,
+ kBadAdUrlError /* expected_error */);
+ }
+ }
+}
+
+// Mojo rejects malformed URLs when converting mojom::blink::InterestGroup to
+// blink::InterestGroup. Since the rejection happens internally in Mojo,
+// typemapping code that invokes blink::InterestGroup::IsValid() isn't run, so
+// adding a AdRenderUrlValidation testcase to verify malformed URLs wouldn't
+// exercise blink::InterestGroup::IsValid(). Since blink::InterestGroup users
+// can call IsValid() directly (i.e when not using Mojo), we need a test that
+// also calls IsValid() directly.
+TEST_F(ValidateBlinkInterestGroupTest, MalformedUrl) {
+ constexpr char kMalformedUrl[] = "https://invalid^";
+
+ // First, check against mojom::blink::InterestGroup.
+ constexpr char kBadAdUrlError[] =
+ "renderUrls must be HTTPS and have no embedded credentials.";
+ mojom::blink::InterestGroupPtr blink_interest_group =
+ mojom::blink::InterestGroup::New();
+ blink_interest_group->owner = kOrigin;
+ blink_interest_group->name = kName;
+ blink_interest_group->ads.emplace();
+ blink_interest_group->ads->emplace_back(mojom::blink::InterestGroupAd::New(
+ KURL(kMalformedUrl), String() /* metadata */));
+ String error_field_name;
+ String error_field_value;
+ String error;
+ EXPECT_FALSE(ValidateBlinkInterestGroup(
+ *blink_interest_group, error_field_name, error_field_value, error));
+ EXPECT_EQ(error_field_name, String::FromUTF8("ad[0].renderUrl"));
+ // The invalid ^ gets escaped.
+ EXPECT_EQ(error_field_value, String::FromUTF8("https://invalid%5E/"));
+ EXPECT_EQ(error, String::FromUTF8(kBadAdUrlError));
+
+ // Now, test against blink::InterestGroup.
+ blink::InterestGroup interest_group;
+ interest_group.owner = url::Origin::Create(GURL(kOriginString));
+ interest_group.name = kNameString;
+ interest_group.ads.emplace();
+ interest_group.ads->emplace_back(
+ blink::InterestGroup::Ad(GURL(kMalformedUrl), /*metadata=*/""));
+ EXPECT_FALSE(interest_group.IsValid());
+}
+
+} // namespace blink