blob: 0ea026ef4c38d706473b39a39c971e76a6e44bbf [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/attribution_reporting/attribution_host.h"
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "content/browser/attribution_reporting/attribution_data_host_manager.h"
#include "content/browser/attribution_reporting/attribution_manager.h"
#include "content/browser/attribution_reporting/attribution_source_type.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/conversions/conversions.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
class AttributionHostTestPeer {
public:
static void SetCurrentTargetFrameForTesting(
AttributionHost* conversion_host,
RenderFrameHost* render_frame_host) {
conversion_host->receivers_.SetCurrentTargetFrameForTesting(
render_frame_host);
}
};
namespace {
using ::attribution_reporting::SuitableOrigin;
using testing::_;
using testing::Return;
using ::blink::mojom::AttributionNavigationType;
const char kConversionUrl[] = "https://b.com";
class AttributionHostTest : public RenderViewHostTestHarness {
public:
AttributionHostTest() = default;
void SetUp() override {
feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
RenderViewHostTestHarness::SetUp();
auto data_host_manager = std::make_unique<MockDataHostManager>();
mock_data_host_manager_ = data_host_manager.get();
auto mock_manager = std::make_unique<MockAttributionManager>();
mock_manager->SetDataHostManager(std::move(data_host_manager));
OverrideAttributionManager(std::move(mock_manager));
contents()->GetPrimaryMainFrame()->InitializeRenderFrameIfNeeded();
}
TestWebContents* contents() {
return static_cast<TestWebContents*>(web_contents());
}
blink::mojom::ConversionHost* conversion_host_mojom() {
return conversion_host();
}
AttributionHost* conversion_host() {
return AttributionHost::FromWebContents(web_contents());
}
void SetCurrentTargetFrameForTesting(RenderFrameHost* render_frame_host) {
AttributionHostTestPeer::SetCurrentTargetFrameForTesting(conversion_host(),
render_frame_host);
}
void ClearAttributionManager() {
mock_data_host_manager_ = nullptr;
OverrideAttributionManager(nullptr);
}
MockDataHostManager* mock_data_host_manager() {
return mock_data_host_manager_;
}
private:
void OverrideAttributionManager(std::unique_ptr<AttributionManager> manager) {
static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition())
->OverrideAttributionManagerForTesting(std::move(manager));
}
raw_ptr<MockDataHostManager> mock_data_host_manager_;
base::test::ScopedFeatureList feature_list_;
};
TEST_F(AttributionHostTest, NavigationWithNoImpression_Ignored) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost).Times(0);
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
NavigationSimulatorImpl::NavigateAndCommitFromDocument(GURL(kConversionUrl),
main_rfh());
}
TEST_F(AttributionHostTest, ValidAttributionSrc_ForwardedToManager) {
blink::Impression impression;
impression.nav_type = AttributionNavigationType::kWindowOpen;
EXPECT_CALL(*mock_data_host_manager(),
NotifyNavigationForDataHost(
impression.attribution_src_token,
*SuitableOrigin::Deserialize("https://secure_impression.com"),
impression.nav_type));
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(std::move(impression));
navigation->Commit();
}
TEST_F(AttributionHostTest, ImpressionWithNoManagerAvailable_NoCrash) {
ClearAttributionManager();
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(blink::Impression());
navigation->Commit();
}
TEST_F(AttributionHostTest, ImpressionInSubframe_Ignored) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost).Times(0);
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
// Create a subframe and use it as a target for the conversion registration
// mojo.
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), subframe);
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(blink::Impression());
navigation->Commit();
}
// Test that if we cannot access the initiator frame of the navigation, we
// ignore the associated impression.
TEST_F(AttributionHostTest, ImpressionNavigationWithDeadInitiator_Ignored) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost).Times(0);
base::HistogramTester histograms;
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
// This test explicitly requires no initiator frame being set.
navigation->SetInitiatorFrame(nullptr);
navigation->set_impression(blink::Impression());
navigation->Commit();
histograms.ExpectUniqueSample(
"Conversions.ImpressionNavigationHasDeadInitiator", true, 1);
}
TEST_F(AttributionHostTest, ImpressionNavigationCommitsToErrorPage_Ignored) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost).Times(0);
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(blink::Impression());
navigation->Fail(net::ERR_FAILED);
navigation->CommitErrorPage();
}
TEST_F(AttributionHostTest,
AttributionSrcNavigationCommitsToErrorPage_Ignored) {
blink::Impression impression;
EXPECT_CALL(*mock_data_host_manager(),
NotifyNavigationFailure(impression.attribution_src_token));
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(std::move(impression));
navigation->Fail(net::ERR_FAILED);
navigation->CommitErrorPage();
}
TEST_F(AttributionHostTest, ImpressionNavigationAborts_Ignored) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost).Times(0);
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(blink::Impression());
navigation->AbortCommit();
}
TEST_F(AttributionHostTest, AttributionSrcNavigationAborts_Ignored) {
blink::Impression impression;
EXPECT_CALL(*mock_data_host_manager(),
NotifyNavigationFailure(impression.attribution_src_token));
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(kConversionUrl), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(std::move(impression));
navigation->AbortCommit();
}
TEST_F(AttributionHostTest,
CommittedOriginDiffersFromConversionDesintation_Propagated) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost);
contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL("https://different.com"), main_rfh());
navigation->SetInitiatorFrame(main_rfh());
navigation->set_impression(blink::Impression());
navigation->Commit();
}
namespace {
const char kLocalHost[] = "http://localhost";
struct OriginTrustworthyChecksTestCase {
const char* source_origin;
const char* destination_origin;
bool expected_valid;
};
const OriginTrustworthyChecksTestCase kOriginTrustworthyChecksTestCases[] = {
{.source_origin = kLocalHost,
.destination_origin = kLocalHost,
.expected_valid = true},
{.source_origin = "http://127.0.0.1",
.destination_origin = "http://127.0.0.1",
.expected_valid = true},
{.source_origin = kLocalHost,
.destination_origin = "http://insecure.com",
.expected_valid = false},
{.source_origin = "http://insecure.com",
.destination_origin = kLocalHost,
.expected_valid = false},
{.source_origin = "https://secure.com",
.destination_origin = "https://secure.com",
.expected_valid = true},
};
class AttributionHostOriginTrustworthyChecksTest
: public AttributionHostTest,
public ::testing::WithParamInterface<OriginTrustworthyChecksTestCase> {};
} // namespace
TEST_P(AttributionHostOriginTrustworthyChecksTest,
ImpressionNavigation_OriginTrustworthyChecksPerformed) {
const OriginTrustworthyChecksTestCase& test_case = GetParam();
if (test_case.expected_valid) {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationForDataHost);
} else {
EXPECT_CALL(*mock_data_host_manager(), NotifyNavigationFailure);
}
contents()->NavigateAndCommit(GURL(test_case.source_origin));
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(test_case.destination_origin), main_rfh());
navigation->set_impression(blink::Impression());
navigation->SetInitiatorFrame(main_rfh());
navigation->Commit();
}
INSTANTIATE_TEST_SUITE_P(
AttributionHostOriginTrustworthyChecks,
AttributionHostOriginTrustworthyChecksTest,
::testing::ValuesIn(kOriginTrustworthyChecksTestCases));
TEST_F(AttributionHostTest, DataHost_RegisteredWithContext) {
EXPECT_CALL(
*mock_data_host_manager(),
RegisterDataHost(_, *SuitableOrigin::Deserialize("https://top.example"),
/*is_within_fenced_frame=*/false));
contents()->NavigateAndCommit(GURL("https://top.example"));
SetCurrentTargetFrameForTesting(main_rfh());
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterDataHost(
data_host_remote.BindNewPipeAndPassReceiver());
// Run loop to allow the bad message code to run if a bad message was
// triggered.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(bad_message_observer.got_bad_message());
}
// crbug.com/1378749.
TEST_F(AttributionHostTest, DISABLED_DataHostOnInsecurePage_BadMessage) {
contents()->NavigateAndCommit(GURL("http://top.example"));
SetCurrentTargetFrameForTesting(main_rfh());
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterDataHost(
data_host_remote.BindNewPipeAndPassReceiver());
EXPECT_EQ(
"blink.mojom.ConversionHost can only be used with a secure top-level "
"frame.",
bad_message_observer.WaitForBadMessage());
}
// crbug.com/1378749.
TEST_F(AttributionHostTest,
DISABLED_NavigationDataHostOnInsecurePage_BadMessage) {
contents()->NavigateAndCommit(GURL("http://top.example"));
SetCurrentTargetFrameForTesting(main_rfh());
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterNavigationDataHost(
data_host_remote.BindNewPipeAndPassReceiver(),
blink::AttributionSrcToken(), AttributionNavigationType::kAnchor);
EXPECT_EQ(
"blink.mojom.ConversionHost can only be used with a secure top-level "
"frame.",
bad_message_observer.WaitForBadMessage());
}
TEST_F(AttributionHostTest, DuplicateAttributionSrcToken_BadMessage) {
ON_CALL(*mock_data_host_manager(), RegisterNavigationDataHost)
.WillByDefault(Return(false));
contents()->NavigateAndCommit(GURL("https://top.example"));
SetCurrentTargetFrameForTesting(main_rfh());
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterNavigationDataHost(
data_host_remote.BindNewPipeAndPassReceiver(),
blink::AttributionSrcToken(), AttributionNavigationType::kAnchor);
EXPECT_EQ(
"Renderer attempted to register a data host with a duplicate "
"AttribtionSrcToken.",
bad_message_observer.WaitForBadMessage());
}
TEST_F(AttributionHostTest, DataHostInSubframe_ContextIsOutermostFrame) {
EXPECT_CALL(
*mock_data_host_manager(),
RegisterDataHost(_, *SuitableOrigin::Deserialize("https://top.example"),
/*is_within_fenced_frame=*/false));
contents()->NavigateAndCommit(GURL("https://top.example"));
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
subframe = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
GURL("https://subframe.example"), subframe);
SetCurrentTargetFrameForTesting(subframe);
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterDataHost(
data_host_remote.BindNewPipeAndPassReceiver());
// Run loop to allow the bad message code to run if a bad message was
// triggered.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(bad_message_observer.got_bad_message());
}
// crbug.com/1378749.
TEST_F(AttributionHostTest,
DISABLED_DataHostInSubframeOnInsecurePage_BadMessage) {
contents()->NavigateAndCommit(GURL("http://top.example"));
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
subframe = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
GURL("https://subframe.example"), subframe);
SetCurrentTargetFrameForTesting(subframe);
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterDataHost(
data_host_remote.BindNewPipeAndPassReceiver());
EXPECT_EQ(
"blink.mojom.ConversionHost can only be used with a secure top-level "
"frame.",
bad_message_observer.WaitForBadMessage());
}
TEST_F(AttributionHostTest, DataHost_RegisteredWithFencedFrame) {
EXPECT_CALL(
*mock_data_host_manager(),
RegisterDataHost(_, *SuitableOrigin::Deserialize("https://top.example"),
/*is_within_fenced_frame=*/true));
contents()->NavigateAndCommit(GURL("https://top.example"));
RenderFrameHost* fenced_frame =
RenderFrameHostTester::For(main_rfh())
->AppendFencedFrame(blink::mojom::FencedFrameMode::kOpaqueAds);
fenced_frame = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
GURL("https://fencedframe.example"), fenced_frame);
SetCurrentTargetFrameForTesting(fenced_frame);
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
conversion_host_mojom()->RegisterDataHost(
data_host_remote.BindNewPipeAndPassReceiver());
// Run loop to allow the bad message code to run if a bad message was
// triggered.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(bad_message_observer.got_bad_message());
}
} // namespace
} // namespace content