910e62b5创建于 1月15日历史提交
// 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 "base/containers/contains.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "content/public/browser/network_service_util.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/fenced_frame_test_util.h"
#include "content/public/test/url_loader_monitor.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "net/dns/mock_host_resolver.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/public/mojom/url_loader.mojom-shared.h"
#include "services/network/test/trust_token_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

// These integration tests verify that calling the Fetch API with Trust Tokens
// parameters results in the parameters' counterparts appearing downstream in
// network::ResourceRequest.
//
// Separately, Blink layout tests check that the API correctly rejects invalid
// input.

namespace content {

class TrustTokenParametersBrowsertest
    : public ::testing::WithParamInterface<network::TrustTokenTestParameters>,
      public ContentBrowserTest {
 public:
  TrustTokenParametersBrowsertest() = default;
};

INSTANTIATE_TEST_SUITE_P(
    WithIssuanceParameters,
    TrustTokenParametersBrowsertest,
    testing::ValuesIn(network::kIssuanceTrustTokenTestParameters));

INSTANTIATE_TEST_SUITE_P(
    WithRedemptionParameters,
    TrustTokenParametersBrowsertest,
    testing::ValuesIn(network::kRedemptionTrustTokenTestParameters));

INSTANTIATE_TEST_SUITE_P(
    WithSigningParameters,
    TrustTokenParametersBrowsertest,
    testing::ValuesIn(network::kSigningTrustTokenTestParameters));

IN_PROC_BROWSER_TEST_P(TrustTokenParametersBrowsertest,
                       PopulatesResourceRequestViaFetch) {
  ASSERT_TRUE(embedded_test_server()->Start());

  network::TrustTokenParametersAndSerialization
      expected_params_and_serialization =
          network::SerializeTrustTokenParametersAndConstructExpectation(
              GetParam());

  GURL url(embedded_test_server()->GetURL("/title1.html"));
  GURL trust_token_url(embedded_test_server()->GetURL("/title2.html"));

  URLLoaderMonitor monitor({trust_token_url});

  EXPECT_TRUE(NavigateToURL(shell(), url));

  ExecuteScriptAsync(
      shell(), JsReplace("fetch($1, {privateToken: ", trust_token_url) +
                   expected_params_and_serialization.serialized_params + "});");

  monitor.WaitForUrls();
  std::optional<network::ResourceRequest> request =
      monitor.GetRequestInfo(trust_token_url);
  ASSERT_TRUE(request);
  ASSERT_TRUE(request->trust_token_params);
  EXPECT_TRUE(request->trust_token_params.as_ptr().Equals(
      expected_params_and_serialization.params));
}

IN_PROC_BROWSER_TEST_P(TrustTokenParametersBrowsertest,
                       PopulatesResourceRequestViaIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());

  network::TrustTokenParametersAndSerialization
      expected_params_and_serialization =
          network::SerializeTrustTokenParametersAndConstructExpectation(
              GetParam());

  // In the iframe interface to private state tokens, we only accept the
  // kSigning variant, i.e. the send-redemption-record operation.
  if (expected_params_and_serialization.params->operation !=
      network::mojom::TrustTokenOperationType::kSigning) {
    return;
  }

  GURL url(embedded_test_server()->GetURL("/title1.html"));
  GURL trust_token_url(embedded_test_server()->GetURL("/title2.html"));

  URLLoaderMonitor monitor({trust_token_url});

  EXPECT_TRUE(NavigateToURL(shell(), url));

  EXPECT_TRUE(ExecJs(
      shell(), JsReplace("let iframe = document.createElement('iframe');"
                         "iframe.src = $1;"
                         "iframe.privateToken = $2;"
                         "document.body.appendChild(iframe);",
                         trust_token_url,
                         expected_params_and_serialization.serialized_params)));

  monitor.WaitForUrls();
  std::optional<network::ResourceRequest> request =
      monitor.GetRequestInfo(trust_token_url);
  ASSERT_TRUE(request);
  ASSERT_TRUE(request->trust_token_params);

  EXPECT_TRUE(request->trust_token_params.as_ptr().Equals(
      expected_params_and_serialization.params));
}

IN_PROC_BROWSER_TEST_P(TrustTokenParametersBrowsertest,
                       PopulatesResourceRequestViaXhr) {
  ASSERT_TRUE(embedded_test_server()->Start());

  network::TrustTokenParametersAndSerialization
      expected_params_and_serialization =
          network::SerializeTrustTokenParametersAndConstructExpectation(
              GetParam());

  GURL url(embedded_test_server()->GetURL("/title1.html"));
  GURL trust_token_url(embedded_test_server()->GetURL("/title2.html"));

  URLLoaderMonitor monitor({trust_token_url});

  EXPECT_TRUE(NavigateToURL(shell(), url));

  EXPECT_TRUE(ExecJs(
      shell(),
      base::StrCat({"let request = new XMLHttpRequest(); ",
                    JsReplace("request.open(\"GET\", $1);", trust_token_url),
                    "request.setPrivateToken(",
                    expected_params_and_serialization.serialized_params,
                    "); request.send();"})));

  monitor.WaitForUrls();
  std::optional<network::ResourceRequest> request =
      monitor.GetRequestInfo(trust_token_url);
  ASSERT_TRUE(request);

  EXPECT_TRUE(request->trust_token_params.as_ptr().Equals(
      expected_params_and_serialization.params));
}

class TrustTokenPermissionsPolicyBrowsertest : public ContentBrowserTest {
 public:
  TrustTokenPermissionsPolicyBrowsertest() = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
  }
};

IN_PROC_BROWSER_TEST_F(TrustTokenPermissionsPolicyBrowsertest,
                       PassesDefaultValueToFactoryParams) {
  // Since the private-state-token-redemption Permissions Policy feature is
  // enabled by default in cross-site frames, the child's
  // URLLoaderFactoryParams should be populated with
  // TrustTokenOperationPolicyVerdict::kPotentiallyPermit.

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (base::Contains(origin.host(), 'b')) {
              ASSERT_TRUE(params);

              ASSERT_THAT(params->trust_token_redemption_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);
              ASSERT_THAT(params->trust_token_issuance_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);
              run_loop.Quit();
            }
          }));

  EXPECT_TRUE(NavigateToURL(shell(), url));

  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(TrustTokenPermissionsPolicyBrowsertest,
                       PassesNegativeRedemptionValueToFactoryParams) {
  // Even though the private-state-token-redemption Permissions Policy feature
  // is enabled by default in cross-site frames, the allow attribute on the
  // iframe can disable it for the b.com frame, so the child's
  // URLLoaderFactoryParams should be populated with
  // TrustTokenOperationPolicyVerdict::kForbid.

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL(
      "a.com",
      "/cross_site_iframe_factory.html?a(b{"
      "disallow-private-state-token-redemption})"));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (base::Contains(origin.host(), "b")) {
              ASSERT_TRUE(params);

              ASSERT_THAT(
                  params->trust_token_redemption_policy,
                  network::mojom::TrustTokenOperationPolicyVerdict::kForbid);
              ASSERT_THAT(params->trust_token_issuance_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);

              run_loop.Quit();
            }
          }));

  EXPECT_TRUE(NavigateToURL(shell(), url));

  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(TrustTokenPermissionsPolicyBrowsertest,
                       PassesNegativeIssuanceValueToFactoryParams) {
  // Even though the private-state-token-issuance Permissions Policy feature is
  // enabled by default in cross-site frames, the allow attribute on the iframe
  // can disable it for the b.com frame, so the child's URLLoaderFactoryParams
  // should be populated with
  // TrustTokenOperationPolicyVerdict::kForbid.

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL(
      "a.com",
      "/cross_site_iframe_factory.html?a(b{"
      "disallow-private-state-token-issuance})"));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (base::Contains(origin.host(), "b")) {
              ASSERT_TRUE(params);

              ASSERT_THAT(params->trust_token_redemption_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);
              ASSERT_THAT(
                  params->trust_token_issuance_policy,
                  network::mojom::TrustTokenOperationPolicyVerdict::kForbid);

              run_loop.Quit();
            }
          }));

  EXPECT_TRUE(NavigateToURL(shell(), url));

  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(TrustTokenPermissionsPolicyBrowsertest,
                       PassesDefaultValueToFactoryParamsAfterCrash) {
  // Since the private-state-token-redemption Permissions Policy feature is
  // enabled by default in cross-site frames, the child's
  // URLLoaderFactoryParams should be populated with
  // TrustTokenOperationPolicyVerdict::kPotentiallyPermit.
  //
  // In particular, this should be true for factory params repopulated after a
  // network service crash!

  // Can't test this on bots that use an in-process network service.
  if (IsInProcessNetworkService())
    return;

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (base::Contains(origin.host(), 'b')) {
              ASSERT_TRUE(params);

              ASSERT_THAT(params->trust_token_redemption_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);
              ASSERT_THAT(params->trust_token_issuance_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);
              run_loop.Quit();
            }
          }));

  SimulateNetworkServiceCrash();
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(TrustTokenPermissionsPolicyBrowsertest,
                       PassesNegativeRedemptionValueToFactoryParamsAfterCrash) {
  // Even though the private-state-token-redemption Permissions Policy feature
  // is enabled by default in cross-site frames, the allow attribute on the
  // iframe can disable it for the b.com frame, so the child's
  // URLLoaderFactoryParams should be populated with
  // TrustTokenOperationPolicyVerdict::kForbid.
  //
  // In particular, this should be true for factory params repopulated after a
  // network service crash!

  // Can't test this on bots that use an in-process network service.
  if (IsInProcessNetworkService())
    return;

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL(
      "a.com",
      "/cross_site_iframe_factory.html?a(b{"
      "disallow-private-state-token-redemption})"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (base::Contains(origin.host(), "b")) {
              ASSERT_TRUE(params);

              ASSERT_THAT(
                  params->trust_token_redemption_policy,
                  network::mojom::TrustTokenOperationPolicyVerdict::kForbid);
              ASSERT_THAT(params->trust_token_issuance_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);

              run_loop.Quit();
            }
          }));

  SimulateNetworkServiceCrash();
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(TrustTokenPermissionsPolicyBrowsertest,
                       PassesNegativeIssuanceValueToFactoryParamsAfterCrash) {
  // Even though the private-state-token-issuance Permissions Policy feature is
  // enabled by default in cross-site frames, the allow attribute on the iframe
  // can disable it for the b.com frame, so the child's URLLoaderFactoryParams
  // should be populated with
  // TrustTokenOperationPolicyVerdict::kForbid.
  //
  // In particular, this should be true for factory params repopulated after a
  // network service crash!

  // Can't test this on bots that use an in-process network service.
  if (IsInProcessNetworkService()) {
    return;
  }

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL(
      "a.com",
      "/cross_site_iframe_factory.html?a(b{"
      "disallow-private-state-token-issuance})"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (base::Contains(origin.host(), "b")) {
              ASSERT_TRUE(params);

              ASSERT_THAT(params->trust_token_redemption_policy,
                          network::mojom::TrustTokenOperationPolicyVerdict::
                              kPotentiallyPermit);
              ASSERT_THAT(
                  params->trust_token_issuance_policy,
                  network::mojom::TrustTokenOperationPolicyVerdict::kForbid);

              run_loop.Quit();
            }
          }));

  SimulateNetworkServiceCrash();
  run_loop.Run();
}

constexpr char kPrivateStateTokenRedemptionPolicyHeader[] =
    "/set-header?Feature-Policy: private-state-token-redemption 'self'";

constexpr char kPrivateStateTokenIssuancePolicyHeader[] =
    "/set-header?Feature-Policy: private-state-token-issuance 'self'";

class TrustTokenPermissionsPolicyFencedFrameTest
    : public TrustTokenPermissionsPolicyBrowsertest,
      public ::testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  TrustTokenPermissionsPolicyFencedFrameTest()
      : policy_header_in_primary_page_(std::get<0>(GetParam())),
        policy_header_in_fenced_frame_page_(std::get<1>(GetParam())) {}

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 protected:
  const bool policy_header_in_primary_page_;
  const bool policy_header_in_fenced_frame_page_;

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         TrustTokenPermissionsPolicyFencedFrameTest,
                         ::testing::Combine(::testing::Bool(),
                                            ::testing::Bool()));

IN_PROC_BROWSER_TEST_P(TrustTokenPermissionsPolicyFencedFrameTest,
                       PassesNegativeRedemptionValueToFactoryParams) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL primary_url(embedded_test_server()->GetURL(
      "a.com", policy_header_in_primary_page_
                   ? kPrivateStateTokenRedemptionPolicyHeader
                   : "/title1.html"));

  GURL fenced_frame_url(embedded_test_server()->GetURL(
      "b.com", policy_header_in_fenced_frame_page_
                   ? std::string(kPrivateStateTokenRedemptionPolicyHeader) +
                         "&Supports-Loading-Mode: fenced-frame"
                   : "/fenced_frames/title1.html"));

  EXPECT_TRUE(NavigateToURL(shell(), primary_url));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (origin.host() != "b.com")
              return;
            EXPECT_TRUE(params);
            EXPECT_THAT(
                params->trust_token_redemption_policy,
                network::mojom::TrustTokenOperationPolicyVerdict::kForbid);
            run_loop.Quit();
          }));

  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
      shell()->web_contents()->GetPrimaryMainFrame(), fenced_frame_url));

  run_loop.Run();
}

IN_PROC_BROWSER_TEST_P(TrustTokenPermissionsPolicyFencedFrameTest,
                       PassesNegativeIssuanceValueToFactoryParams) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL primary_url(embedded_test_server()->GetURL(
      "a.com", policy_header_in_primary_page_
                   ? kPrivateStateTokenIssuancePolicyHeader
                   : "/title1.html"));

  GURL fenced_frame_url(embedded_test_server()->GetURL(
      "b.com", policy_header_in_fenced_frame_page_
                   ? std::string(kPrivateStateTokenIssuancePolicyHeader) +
                         "&Supports-Loading-Mode: fenced-frame"
                   : "/fenced_frames/title1.html"));

  EXPECT_TRUE(NavigateToURL(shell(), primary_url));

  base::RunLoop run_loop;
  ShellContentBrowserClient::Get()->set_url_loader_factory_params_callback(
      base::BindLambdaForTesting(
          [&](const network::mojom::URLLoaderFactoryParams* params,
              const url::Origin& origin, bool unused_is_for_isolated_world,
              bool unused_is_for_service_worker) {
            if (origin.host() != "b.com") {
              return;
            }
            EXPECT_TRUE(params);
            EXPECT_THAT(
                params->trust_token_issuance_policy,
                network::mojom::TrustTokenOperationPolicyVerdict::kForbid);
            run_loop.Quit();
          }));

  ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
      shell()->web_contents()->GetPrimaryMainFrame(), fenced_frame_url));

  run_loop.Run();
}

}  // namespace content