blob: caebdfbeaa0430906348b12b4f13e5cbe9c8bf2e [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 "build/build_config.h"
#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "content/browser/devtools/protocol/network.h"
#include "content/browser/net/trust_token_browsertest.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace content {
class DevToolsTrustTokenBrowsertest : public DevToolsProtocolTest,
public TrustTokenBrowsertest {
public:
void SetUpOnMainThread() override {
TrustTokenBrowsertest::SetUpOnMainThread();
DevToolsProtocolTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
TrustTokenBrowsertest::TearDownOnMainThread();
DevToolsProtocolTest::TearDownOnMainThread();
}
// The returned view is only valid until the next |SendCommand| call.
base::Value::ConstListView GetTrustTokensViaProtocol() {
SendCommand("Storage.getTrustTokens", nullptr);
const base::Value* tokens = result()->Find("tokens");
CHECK(tokens);
return tokens->GetListDeprecated();
}
// Asserts that CDP reports |count| number of tokens for |issuerOrigin|.
void AssertTrustTokensViaProtocol(const std::string& issuerOrigin,
int expectedCount) {
auto tokens = GetTrustTokensViaProtocol();
EXPECT_GT(tokens.size(), 0ul);
for (const auto& token : tokens) {
const std::string* issuer = token.FindStringKey("issuerOrigin");
if (*issuer == issuerOrigin) {
const absl::optional<int> actualCount = token.FindIntPath("count");
EXPECT_THAT(actualCount, ::testing::Optional(expectedCount));
return;
}
}
FAIL() << "No trust tokens for issuer " << issuerOrigin;
}
};
// After a successful issuance and redemption, a subsequent redemption against
// the same issuer should hit the redemption record (RR) cache.
IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest,
RedemptionRecordCacheHitIsReportedAsLoadingFinished) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
// 1. Navigate to a test site, request and redeem a trust token.
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
// 2) Open DevTools and enable Network domain.
Attach();
SendCommand("Network.enable", std::make_unique<base::DictionaryValue>());
// Make sure there are no existing DevTools events in the queue.
EXPECT_FALSE(HasExistingNotification());
// 3) Issue another redemption, and verify its served from cache.
EXPECT_EQ("NoModificationAllowedError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-redemption' } })
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
// 4) Verify the request is marked as successful and not as failed.
WaitForNotification("Network.requestServedFromCache", true);
WaitForNotification("Network.loadingFinished", true);
WaitForNotification("Network.trustTokenOperationDone", true);
}
class DevToolsTrustTokenBrowsertestWithPlatformIssuance
: public DevToolsTrustTokenBrowsertest {
public:
DevToolsTrustTokenBrowsertestWithPlatformIssuance() {
// This assertion helps guard against the brittleness of deserializing
// "true", in case we refactor the parameter's type.
static_assert(
std::is_same<decltype(
network::features::kPlatformProvidedTrustTokenIssuance
.default_value),
const bool>::value,
"Need to update this initialization logic if the type of the param "
"changes.");
features_.InitAndEnableFeatureWithParameters(
network::features::kTrustTokens,
{{network::features::kPlatformProvidedTrustTokenIssuance.name,
"true"}});
}
private:
base::test::ScopedFeatureList features_;
};
#if BUILDFLAG(IS_ANDROID)
// After a successful platform-provided issuance operation (which involves an
// IPC to a system-local provider, not an HTTP request to a server), the
// request's outcome should show as a cache hit in the network panel.
IN_PROC_BROWSER_TEST_F(
DevToolsTrustTokenBrowsertestWithPlatformIssuance,
SuccessfulPlatformProvidedIssuanceIsReportedAsLoadingFinished) {
TrustTokenRequestHandler::Options options;
options.specify_platform_issuance_on = {
network::mojom::TrustTokenKeyCommitmentResult::Os::kAndroid};
request_handler_.UpdateOptions(std::move(options));
HandlerWrappingLocalTrustTokenFulfiller fulfiller(request_handler_);
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// Open DevTools and enable Network domain.
Attach();
SendCommand("Network.enable", std::make_unique<base::DictionaryValue>());
// Make sure there are no existing DevTools events in the queue.
EXPECT_FALSE(HasExistingNotification());
// Issuance operations successfully answered locally result in
// NoModificationAllowedError.
std::string command = R"(
(async () => {
try {
await fetch("/issue", {trustToken: {type: 'token-request'}});
return "Unexpected success";
} catch (e) {
if (e.name !== "NoModificationAllowedError") {
return "Unexpected exception";
}
const hasToken = await document.hasTrustToken($1);
if (!hasToken)
return "Unexpectedly absent token";
return "Success";
}})(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
// Verify the request is marked as successful and not as failed.
WaitForNotification("Network.requestServedFromCache", true);
WaitForNotification("Network.loadingFinished", true);
WaitForNotification("Network.trustTokenOperationDone", true);
}
#endif // BUILDFLAG(IS_ANDROID)
namespace {
bool MatchStatus(const std::string& expected_status,
const base::Value::Dict& params) {
const std::string* actual_status = params.FindString("status");
return expected_status == *actual_status;
}
base::RepeatingCallback<bool(const base::Value::Dict&)> okStatusMatcher =
base::BindRepeating(
&MatchStatus,
protocol::Network::TrustTokenOperationDone::StatusEnum::Ok);
} // namespace
IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest, FetchEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
// 1) Navigate to a test site.
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// 2) Open DevTools and enable Network domain.
Attach();
SendCommand("Network.enable", std::make_unique<base::DictionaryValue>());
// 3) Request and redeem a token, then use the redeemed token in a Signing
// request.
std::string command = R"(
(async () => {
await fetch('/issue', {trustToken: {type: 'token-request'}});
await fetch('/redeem', {trustToken: {type: 'token-redemption'}});
await fetch('/sign', {trustToken: {type: 'send-redemption-record',
signRequestData: 'include',
issuers: [$1]}});
return 'Success'; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
// 4) Verify that we received three successful events.
WaitForMatchingNotification("Network.trustTokenOperationDone",
okStatusMatcher);
WaitForMatchingNotification("Network.trustTokenOperationDone",
okStatusMatcher);
WaitForMatchingNotification("Network.trustTokenOperationDone",
okStatusMatcher);
}
IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest, IframeEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
// 1) Navigate to a test site.
GURL start_url = server_.GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// 2) Open DevTools and enable Network domain.
Attach();
SendCommand("Network.enable", std::make_unique<base::DictionaryValue>());
// 3) Request and redeem a token, then use the redeemed token in a Signing
// request.
auto execute_op_via_iframe = [&](base::StringPiece path,
base::StringPiece trust_token) {
// It's important to set the trust token arguments before updating src, as
// the latter triggers a load.
EXPECT_TRUE(ExecJs(
shell(), JsReplace(
R"( const myFrame = document.getElementById('test_iframe');
myFrame.trustToken = $1;
myFrame.src = $2;)",
trust_token, path)));
TestNavigationObserver load_observer(shell()->web_contents());
load_observer.WaitForNavigationFinished();
};
execute_op_via_iframe("/issue", R"({"type": "token-request"})");
execute_op_via_iframe("/redeem", R"({"type": "token-redemption"})");
execute_op_via_iframe("/sign", JsReplace(
R"({"type": "send-redemption-record",
"signRequestData": "include", "issuers": [$1]})",
IssuanceOriginFromHost("a.test")));
// 4) Verify that we received three successful events.
WaitForMatchingNotification("Network.trustTokenOperationDone",
okStatusMatcher);
WaitForMatchingNotification("Network.trustTokenOperationDone",
okStatusMatcher);
WaitForMatchingNotification("Network.trustTokenOperationDone",
okStatusMatcher);
}
// When the server rejects issuance, DevTools gets a failed notification.
IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest,
FailedIssuanceFiresFailedOperationEvent) {
TrustTokenRequestHandler::Options options;
options.issuance_outcome =
TrustTokenRequestHandler::ServerOperationOutcome::kUnconditionalFailure;
request_handler_.UpdateOptions(std::move(options));
// 1) Navigate to a test site.
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// 2) Open DevTools and enable Network domain.
Attach();
SendCommand("Network.enable", std::make_unique<base::DictionaryValue>());
// 3) Request some Trust Tokens.
EXPECT_EQ("OperationError", EvalJs(shell(), R"(fetch('/issue',
{ trustToken: { type: 'token-request' } })
.then(()=>'Success').catch(err => err.name); )"));
// 4) Verify that we received an Trust Token operation failed event.
WaitForMatchingNotification(
"Network.trustTokenOperationDone",
base::BindRepeating(
&MatchStatus,
protocol::Network::TrustTokenOperationDone::StatusEnum::BadResponse));
}
IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest, GetTrustTokens) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
// 1) Navigate to a test site.
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// 2) Open DevTools.
Attach();
// 3) Call Storage.getTrustTokens and expect none to be there.
EXPECT_EQ(GetTrustTokensViaProtocol().size(), 0ul);
// 4) Request some Trust Tokens.
std::string command = R"(
(async () => {
await fetch('/issue', {trustToken: {type: 'token-request'}});
return 'Success'; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success", EvalJs(shell(), command));
// 5) Call Storage.getTrustTokens and expect a Trust Token to be there.
EXPECT_EQ(GetTrustTokensViaProtocol().size(), 1ul);
}
IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest, ClearTrustTokens) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
// 1) Navigate to a test site.
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// 2) Open DevTools.
Attach();
// 3) Request some Trust Tokens.
std::string command = R"(
(async () => {
await fetch('/issue', {trustToken: {type: 'token-request'}});
return 'Success'; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success", EvalJs(shell(), command));
// 4) Call Storage.getTrustTokens and expect a Trust Token to be there.
AssertTrustTokensViaProtocol(IssuanceOriginFromHost("a.test"), 10);
// 5) Call Storage.clearTrustTokens
auto params = std::make_unique<base::DictionaryValue>();
params->SetStringPath("issuerOrigin", IssuanceOriginFromHost("a.test"));
auto* result = SendCommand("Storage.clearTrustTokens", std::move(params));
EXPECT_THAT(result->FindBool("didDeleteTokens"), ::testing::Optional(true));
// 6) Call Storage.getTrustTokens and expect no Trust Tokens to be there.
// Note that we still get an entry for our 'issuerOrigin', but the actual
// Token count must be 0.
AssertTrustTokensViaProtocol(IssuanceOriginFromHost("a.test"), 0);
}
} // namespace content