Commit 3089b5ea authored by David Van Cleve's avatar David Van Cleve Committed by Chromium LUCI CQ

Trust Tokens: Mark platform-provided issuance as a success in DevTools

A Trust Tokens issuance request is a request created by
  fetch(..., {trustToken: {type: 'token-request'}})

With the new "platform-provided trust tokens" expansion of the Trust
Tokens feature, the request's destination origin can provide
configuration indicating that the browser should divert the origin's
Trust Tokens issuance requests to system-local providers, on platforms
that support this behavior (currently Android). [1]

In this case, there's no need to send an HTTP request directly to the
destination URL provided in the fetch() call, so we return early via
URLLoader::OnComplete(URLLoaderCompletionStatus). We provide a
non-net::OK error code to this URLLoader::OnComplete call since we
won't receive an HTTP response from the request's destination.

Since this case is semantically a success, we don't want it to show up
as an error in the DevTools console. This change gives it the same
treatment as Trust Tokens redemption cache hits, making it return
TRUST_TOKEN_OPERATION_SUCCESS_WITHOUT_SENDING_REQUEST from URLLoader.

In order to support the DevTools browser test covering this behavior,
this CL exposes some platform-provided issuance support fixtures from
trust_token_browsertest.cc in the corresponding header file.

[1]: https://bit.ly/platform-provided-trust-tokens

Test: A DevTools browser test confirms the request shows as "finished"
Bug: 1154847
Change-Id: I2d8a00b780f12faa16052d07ccba88c50dbde848
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2587436
Auto-Submit: David Van Cleve <davidvc@chromium.org>
Commit-Queue: Matt Menke <mmenke@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Reviewed-by: default avatarSimon Zünd <szuend@chromium.org>
Reviewed-by: default avatarPeter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#840198}
parent e1ccc572
......@@ -2,6 +2,7 @@
// 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"
......@@ -12,6 +13,7 @@
#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"
namespace content {
......@@ -70,6 +72,85 @@ IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest,
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 defined(OS_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_EQ(notifications_.size(), 0ul);
// 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 // defined(OS_ANDROID)
namespace {
bool MatchStatus(const std::string& expected_status,
......
......@@ -12,10 +12,8 @@
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/common/trust_tokens.mojom.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
......@@ -45,11 +43,6 @@
#include "url/origin.h"
#include "url/url_canon_stdstring.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/java_interfaces.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#endif // defined(OS_ANDROID)
namespace content {
namespace {
......@@ -1668,6 +1661,41 @@ class TrustTokenBrowsertestWithPlatformIssuance : public TrustTokenBrowsertest {
};
#if defined(OS_ANDROID)
HandlerWrappingLocalTrustTokenFulfiller::
HandlerWrappingLocalTrustTokenFulfiller(TrustTokenRequestHandler& handler)
: handler_(handler) {
interface_overrider_.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindRepeating(&HandlerWrappingLocalTrustTokenFulfiller::Bind,
base::Unretained(this)));
}
HandlerWrappingLocalTrustTokenFulfiller::
~HandlerWrappingLocalTrustTokenFulfiller() = default;
void HandlerWrappingLocalTrustTokenFulfiller::FulfillTrustTokenIssuance(
network::mojom::FulfillTrustTokenIssuanceRequestPtr request,
FulfillTrustTokenIssuanceCallback callback) {
base::Optional<std::string> maybe_result =
handler_.Issue(std::move(request->request));
if (maybe_result) {
std::move(callback).Run(
network::mojom::FulfillTrustTokenIssuanceAnswer::New(
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk,
std::move(*maybe_result)));
return;
}
std::move(callback).Run(network::mojom::FulfillTrustTokenIssuanceAnswer::New(
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kUnknownError,
""));
}
void HandlerWrappingLocalTrustTokenFulfiller::Bind(
mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(
mojo::PendingReceiver<content::mojom::LocalTrustTokenFulfiller>(
std::move(handle)));
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertestWithPlatformIssuance,
EndToEndAndroidPlatformIssuance) {
TrustTokenRequestHandler::Options options;
......@@ -1675,49 +1703,7 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertestWithPlatformIssuance,
network::mojom::TrustTokenKeyCommitmentResult::Os::kAndroid};
request_handler_.UpdateOptions(std::move(options));
class HandlerWrappingLocalTrustTokenFulfiller
: public content::mojom::LocalTrustTokenFulfiller {
public:
HandlerWrappingLocalTrustTokenFulfiller(TrustTokenRequestHandler& handler)
: handler_(handler) {}
void FulfillTrustTokenIssuance(
network::mojom::FulfillTrustTokenIssuanceRequestPtr request,
FulfillTrustTokenIssuanceCallback callback) override {
base::Optional<std::string> maybe_result =
handler_.Issue(std::move(request->request));
if (maybe_result) {
std::move(callback).Run(
network::mojom::FulfillTrustTokenIssuanceAnswer::New(
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk,
std::move(*maybe_result)));
return;
}
std::move(callback).Run(
network::mojom::FulfillTrustTokenIssuanceAnswer::New(
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::
kUnknownError,
""));
}
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(
mojo::PendingReceiver<content::mojom::LocalTrustTokenFulfiller>(
std::move(handle)));
}
private:
TrustTokenRequestHandler& handler_;
mojo::Receiver<content::mojom::LocalTrustTokenFulfiller> receiver_{this};
};
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
HandlerWrappingLocalTrustTokenFulfiller fulfiller(request_handler_);
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindRepeating(&HandlerWrappingLocalTrustTokenFulfiller::Bind,
base::Unretained(&fulfiller)));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
......
......@@ -6,9 +6,16 @@
#define CONTENT_BROWSER_NET_TRUST_TOKEN_BROWSERTEST_H_
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/public/common/trust_tokens.mojom.h"
#include "content/public/test/content_browser_test.h"
#include "services/network/trust_tokens/test/trust_token_request_handler.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/java_interfaces.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#endif // defined(OS_ANDROID)
namespace content {
using network::test::TrustTokenRequestHandler;
......@@ -59,6 +66,38 @@ class TrustTokenBrowsertest : virtual public ContentBrowserTest {
net::EmbeddedTestServer server_{net::EmbeddedTestServer::TYPE_HTTPS};
};
#if defined(OS_ANDROID)
// HandlerWrappingLocalTrustTokenFulfiller serves two purposes:
//
// 1. Its lifetime scopes an override to content::GetGlobalJavaInterfaces()'s
// interface provider, forwarding requests to bind a
// content::mojom::LocalTrustTokenFulfiller to this object;
//
// 2. It forwards the requests it receives to the TrustTokenRequestHandler
// passed to its constructor (this will likely be
// TrustTokenBrowsertest::request_handler_). This arrangement means that the
// calls to FulfillTrustTokenIssuance receive well-formed issuance responses
// signed using the request handler's issuance keys.
class HandlerWrappingLocalTrustTokenFulfiller final
: public content::mojom::LocalTrustTokenFulfiller {
public:
HandlerWrappingLocalTrustTokenFulfiller(TrustTokenRequestHandler& handler);
~HandlerWrappingLocalTrustTokenFulfiller() override;
void FulfillTrustTokenIssuance(
network::mojom::FulfillTrustTokenIssuanceRequestPtr request,
FulfillTrustTokenIssuanceCallback callback) override;
private:
void Bind(mojo::ScopedMessagePipeHandle handle);
TrustTokenRequestHandler& handler_;
service_manager::InterfaceProvider::TestApi interface_overrider_{
content::GetGlobalJavaInterfaces()};
mojo::Receiver<content::mojom::LocalTrustTokenFulfiller> receiver_{this};
};
#endif
} // namespace content
#endif // CONTENT_BROWSER_NET_TRUST_TOKEN_BROWSERTEST_H_
......@@ -888,17 +888,21 @@ void URLLoader::OnDoneBeginningTrustTokenOperation(
mojom::TrustTokenOperationStatus status) {
trust_token_status_ = status;
// In case the operation failed or we hit the cache, the DevTools event is
// emitted from here. Otherwise the DevTools event is always emitted from
// |OnDoneFinalizeTrustTokenOperation|.
// In case the operation failed or it succeeded in a manner where the request
// does not need to be sent onwards, the DevTools event is emitted from here.
// Otherwise the DevTools event is always emitted from
// |OnDoneFinalizingTrustTokenOperation|.
if (status != mojom::TrustTokenOperationStatus::kOk) {
MaybeSendTrustTokenOperationResultToDevTools();
}
if (status == mojom::TrustTokenOperationStatus::kOk) {
ScheduleStart();
} else if (status == mojom::TrustTokenOperationStatus::kAlreadyExists) {
// Cache hit: no need to send the request; we return an empty resource.
} else if (status == mojom::TrustTokenOperationStatus::kAlreadyExists ||
status == mojom::TrustTokenOperationStatus::
kOperationSuccessfullyFulfilledLocally) {
// The Trust Tokens operation succeeded without needing to send the request;
// we return early with an "error" representing this success.
//
// Here and below, defer calling NotifyCompleted to make sure the URLLoader
// finishes initializing before getting deleted.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment