blob: bdc08cb40ee26745907103f57397dedc1e74d12f [file] [log] [blame]
// Copyright 2020 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 "base/json/json_string_value_serializer.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "services/network/public/cpp/features.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-shared.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::Field;
using ::testing::Optional;
using ::testing::Property;
// These integration tests verify that calling the Fetch API with Trust Tokens
// parameters results in the parameters' counterparts appearing downstream in
// network::ResourceRequest.
//
// They use URLLoaderInterceptor, as opposed to an embedded test server, in
// order to directly inspect network::ResourceRequest instances.
//
// Separately, Blink layout tests check that the API correctly rejects invalid
// input.
namespace content {
namespace {
const char kTestHeaders[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
std::string TrustTokenEnumToString(
network::mojom::TrustTokenOperationType type) {
switch (type) {
case network::mojom::TrustTokenOperationType::kIssuance:
return "token-request";
case network::mojom::TrustTokenOperationType::kRedemption:
return "srr-token-redemption";
case network::mojom::TrustTokenOperationType::kSigning:
return "send-srr";
}
}
std::string TrustTokenEnumToString(
network::mojom::TrustTokenRefreshPolicy policy) {
switch (policy) {
case network::mojom::TrustTokenRefreshPolicy::kUseCached:
return "none";
case network::mojom::TrustTokenRefreshPolicy::kRefresh:
return "refresh";
}
}
std::string TrustTokenEnumToString(
network::mojom::TrustTokenSignRequestData sign_request_data) {
switch (sign_request_data) {
case network::mojom::TrustTokenSignRequestData::kOmit:
return "omit";
case network::mojom::TrustTokenSignRequestData::kHeadersOnly:
return "headers-only";
case network::mojom::TrustTokenSignRequestData::kInclude:
return "include";
}
}
// The values instantiations of this struct will be serialized and passed to a
// `fetch` call in executed JS.
struct Input {
// Input (nullopt in an optional field will be omitted from the parameter's
// value):
network::mojom::TrustTokenOperationType type;
base::Optional<network::mojom::TrustTokenRefreshPolicy> refresh_policy;
base::Optional<network::mojom::TrustTokenSignRequestData> sign_request_data;
base::Optional<bool> include_timestamp_header;
// Because static initialization of GURLs/Origins isn't allowed in tests, use
// the string representation of the issuer origin and convert it to an Origin
// in the test.
base::Optional<std::string> issuer_spec;
base::Optional<std::vector<std::string>> additional_signed_headers;
};
// For a given test case, creates and returns:
// 1. a serialized JSON dictionary suitable for passing as the value of
// `fetch`'s `trustToken` parameter.
// 2. a network::mojom::TrustTokenParams object that should equal the value
// eventually passed to network::ResourceRequest when a fetch is executed
// with the returned trustToken parameter value.
std::pair<std::string, network::mojom::TrustTokenParams>
SerializeParametersAndConstructExpectation(const Input& input) {
network::mojom::TrustTokenParams expectation;
base::Value parameters(base::Value::Type::DICTIONARY);
parameters.SetStringKey("type", TrustTokenEnumToString(input.type));
expectation.type = input.type;
if (input.refresh_policy.has_value()) {
parameters.SetStringKey("refreshPolicy",
TrustTokenEnumToString(*input.refresh_policy));
expectation.refresh_policy = *input.refresh_policy;
}
if (input.sign_request_data.has_value()) {
parameters.SetStringKey("signRequestData",
TrustTokenEnumToString(*input.sign_request_data));
expectation.sign_request_data = *input.sign_request_data;
}
if (input.include_timestamp_header.has_value()) {
parameters.SetBoolKey("includeTimestampHeader",
*input.include_timestamp_header);
expectation.include_timestamp_header = *input.include_timestamp_header;
}
if (input.issuer_spec.has_value()) {
parameters.SetStringKey("issuer", *input.issuer_spec);
expectation.issuer = url::Origin::Create(GURL(*input.issuer_spec));
}
if (input.additional_signed_headers.has_value()) {
base::Value headers(base::Value::Type::LIST);
for (const std::string& header : *input.additional_signed_headers)
headers.Append(header);
parameters.SetKey("additionalSignedHeaders", std::move(headers));
expectation.additional_signed_headers = *input.additional_signed_headers;
}
std::string serialized_parameters;
JSONStringValueSerializer serializer(&serialized_parameters);
CHECK(serializer.Serialize(parameters));
return std::make_pair(serialized_parameters, expectation);
}
const Input kIssuanceInputs[]{
// For issuance, there are no additional parameters to specify.
{.type = network::mojom::TrustTokenOperationType::kIssuance}};
const Input kRedemptionInputs[]{
// The only free parameter for redemption is refreshPolicy.
{.type = network::mojom::TrustTokenOperationType::kRedemption,
.refresh_policy = network::mojom::TrustTokenRefreshPolicy::kRefresh},
{.type = network::mojom::TrustTokenOperationType::kRedemption,
.refresh_policy = network::mojom::TrustTokenRefreshPolicy::kUseCached},
{.type = network::mojom::TrustTokenOperationType::kRedemption}};
const Input kSigningInputs[]{
// Signing's inputs are issuer, signRequestData, additionalSignedHeaders,
// and includeTimestampHeader; "issuer" has no default and must always be
// a secure origin.
{.type = network::mojom::TrustTokenOperationType::kSigning,
.sign_request_data = network::mojom::TrustTokenSignRequestData::kOmit,
.include_timestamp_header = true,
.issuer_spec = "https://issuer.example",
.additional_signed_headers =
std::vector<std::string>{"one header's name",
"another header's name"}},
{.type = network::mojom::TrustTokenOperationType::kSigning,
.sign_request_data =
network::mojom::TrustTokenSignRequestData::kHeadersOnly,
.include_timestamp_header = false,
.issuer_spec = "https://issuer.example"},
{.type = network::mojom::TrustTokenOperationType::kSigning,
.sign_request_data = network::mojom::TrustTokenSignRequestData::kInclude,
.issuer_spec = "https://issuer.example"},
};
} // namespace
class TrustTokenParametersBrowsertest
: public ::testing::WithParamInterface<Input>,
public ContentBrowserTest {
public:
TrustTokenParametersBrowsertest() {
features_.InitAndEnableFeature(network::features::kTrustTokens);
}
protected:
base::test::ScopedFeatureList features_;
};
INSTANTIATE_TEST_SUITE_P(WithIssuanceParameters,
TrustTokenParametersBrowsertest,
testing::ValuesIn(kIssuanceInputs));
INSTANTIATE_TEST_SUITE_P(WithRedemptionParameters,
TrustTokenParametersBrowsertest,
testing::ValuesIn(kRedemptionInputs));
INSTANTIATE_TEST_SUITE_P(WithSigningParameters,
TrustTokenParametersBrowsertest,
testing::ValuesIn(kSigningInputs));
IN_PROC_BROWSER_TEST_P(TrustTokenParametersBrowsertest,
PopulatesResourceRequest) {
bool attempted_to_load_image = false;
network::mojom::TrustTokenParams expectation;
std::string fetch_trust_token_parameter;
std::tie(fetch_trust_token_parameter, expectation) =
SerializeParametersAndConstructExpectation(GetParam());
URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&attempted_to_load_image, &fetch_trust_token_parameter,
&expectation](URLLoaderInterceptor::RequestParams* params) -> bool {
std::string spec = params->url_request.url.spec();
// On the first request, to "main.com", load a landing page from which
// to make the Trust Tokens request.
if (spec.find("main") != std::string::npos) {
URLLoaderInterceptor::WriteResponse(
kTestHeaders,
"<html><script>fetch('https://www.image.com/image.png', "
"{trustToken: " +
fetch_trust_token_parameter + "});</script></html>",
params->client.get());
return true;
}
// On the second request, to a path containing "image", verify that the
// network::ResourceRequest has the correct Trust Tokens parameters.
if (spec.find("image")) {
// Can't ASSERT_TRUE in a non-void-returning method, but we don't want
// to continue to the next line if there's no |trust_token_params|
// field present, because we'd trigger a check failure anyways, when
// dereferencing nullopt.
CHECK(params->url_request.trust_token_params);
EXPECT_TRUE(
params->url_request.trust_token_params->Equals(expectation));
attempted_to_load_image = true;
}
return false;
}));
EXPECT_TRUE(NavigateToURL(shell(), GURL("https://main.com/")));
// As a sanity check, make sure the test did actually try to load the
// subresource.
ASSERT_TRUE(attempted_to_load_image);
}
} // namespace content