#include "content/browser/network/trust_token_browsertest.h"
#include <memory>
#include <string>
#include <string_view>
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.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/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/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/public/test/url_loader_monitor.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/trust_token_http_headers.h"
#include "services/network/public/cpp/trust_token_parameterization.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/test/trust_token_request_handler.h"
#include "services/network/test/trust_token_test_server_handler_registration.h"
#include "services/network/test/trust_token_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_canon_stdstring.h"
namespace content {
namespace {
using network::test::TrustTokenRequestHandler;
using SignedRequest = network::test::TrustTokenSignedRequest;
using ::testing::AllOf;
using ::testing::DescribeMatcher;
using ::testing::Eq;
using ::testing::ExplainMatchResult;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::IsFalse;
using ::testing::IsSubsetOf;
using ::testing::Not;
using ::testing::Optional;
using ::testing::StrEq;
using ::testing::Truly;
MATCHER_P(HasHeader, name, base::StringPrintf("Has header %s", name)) {
if (!arg.headers.HasHeader(name)) {
*result_listener << base::StringPrintf("%s wasn't present", name);
return false;
}
*result_listener << base::StringPrintf("%s was present", name);
return true;
}
MATCHER_P2(HasHeader,
name,
other_matcher,
"has header " + std::string(name) + " that " +
DescribeMatcher<std::string>(other_matcher)) {
std::optional<std::string> header = arg.headers.GetHeader(name);
if (!header) {
*result_listener << base::StringPrintf("%s wasn't present", name);
return false;
}
return ExplainMatchResult(other_matcher, *header, result_listener);
}
MATCHER(
ReflectsSigningFailure,
"The given signed request reflects a client-side signing failure, having "
"an empty redemption record and no other related headers.") {
return ExplainMatchResult(
AllOf(HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord,
StrEq("")),
Not(HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))),
arg, result_listener);
}
}
TrustTokenBrowsertest::TrustTokenBrowsertest() = default;
void TrustTokenBrowsertest::SetUpOnMainThread() {
host_resolver()->AddRule("*", "127.0.0.1");
server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
server_.AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("content/test/data")));
SetupCrossSiteRedirector(embedded_test_server());
SetupCrossSiteRedirector(&server_);
network::test::RegisterTrustTokenTestHandlers(&server_, &request_handler_);
TrustTokenBrowsertest::Observe(shell()->web_contents());
ASSERT_TRUE(server_.Start());
}
void TrustTokenBrowsertest::ProvideRequestHandlerKeyCommitmentsToNetworkService(
std::vector<std::string_view> hosts) {
base::flat_map<url::Origin, std::string_view> origins_and_commitments;
std::string key_commitments = request_handler_.GetKeyCommitmentRecord();
for (std::string_view host : hosts) {
GURL::Replacements replacements;
replacements.SetHostStr(host);
origins_and_commitments.insert_or_assign(
url::Origin::Create(server_.base_url().ReplaceComponents(replacements)),
key_commitments);
}
if (origins_and_commitments.empty()) {
origins_and_commitments = {
{url::Origin::Create(server_.base_url()), key_commitments}};
}
base::RunLoop run_loop;
GetNetworkService()->SetTrustTokenKeyCommitments(
network::WrapKeyCommitmentsForIssuers(std::move(origins_and_commitments)),
run_loop.QuitClosure());
run_loop.Run();
}
std::string TrustTokenBrowsertest::IssuanceOriginFromHost(
const std::string& host) const {
auto ret = url::Origin::Create(server_.GetURL(host, "/")).Serialize();
return ret;
}
void TrustTokenBrowsertest::OnTrustTokensAccessed(
RenderFrameHost* render_frame_host,
const TrustTokenAccessDetails& details) {
access_count_++;
}
void TrustTokenBrowsertest::OnTrustTokensAccessed(
NavigationHandle* navigation_handle,
const TrustTokenAccessDetails& details) {
access_count_++;
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, FetchEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
await fetch("/sign", {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: [$1]}});
return "Success"; })(); )";
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, FetchEndToEndThirdParty) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"b.test"});
const GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch($1, {privateToken: {version: 1,
operation: 'token-request'}});
await fetch($2, {privateToken: {version: 1,
operation: 'token-redemption'}});
await fetch($3, {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: [$4]}});
return "Success"; })(); )";
const std::string issuer_origin = IssuanceOriginFromHost("b.test");
const std::string issuance_url = server_.GetURL("b.test", "/issue").spec();
const std::string redemption_url = server_.GetURL("b.test", "/redeem").spec();
const std::string signature_url = server_.GetURL("b.test", "/sign").spec();
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(command, issuance_url, redemption_url,
signature_url, issuer_origin)));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, XhrEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
let request = new XMLHttpRequest();
request.open('GET', '/issue');
request.setPrivateToken({
version: 1,
operation: 'token-request'
});
let promise = new Promise((res, rej) => {
request.onload = res; request.onerror = rej;
});
request.send();
await promise;
request = new XMLHttpRequest();
request.open('GET', '/redeem');
request.setPrivateToken({
version: 1,
operation: 'token-redemption'
});
promise = new Promise((res, rej) => {
request.onload = res; request.onerror = rej;
});
request.send();
await promise;
request = new XMLHttpRequest();
request.open('GET', '/sign');
request.setPrivateToken({
version: 1,
operation: 'send-redemption-record',
issuers: [$1]
});
promise = new Promise((res, rej) => {
request.onload = res; request.onerror = rej;
});
request.send();
await promise;
return "Success";
})(); )";
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IframeSendRedemptionRecord) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
std::string command = R"(
(async () => {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
return "Success";
})())";
GURL start_url = server_.GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("Success", EvalJs(shell(), command));
auto execute_op_via_iframe = [&](std::string_view path,
std::string_view trust_token) {
EXPECT_TRUE(ExecJs(
shell(), JsReplace(
R"( const myFrame = document.getElementById("test_iframe");
myFrame.privateToken = $1;
myFrame.src = $2;)",
trust_token, path)));
TestNavigationObserver load_observer(shell()->web_contents());
load_observer.WaitForNavigationFinished();
};
execute_op_via_iframe("/sign", JsReplace(
R"({"version": 1,
"operation": "send-redemption-record",
"issuers": [$1]})",
IssuanceOriginFromHost("a.test")));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
IframeCanOnlySendRedemptionRecord) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
auto fail_to_execute_op_via_iframe = [&](std::string_view path,
std::string_view trust_token) {
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();
};
fail_to_execute_op_via_iframe("/issue", R"({"type": "token-request"})");
std::string command = JsReplace(R"(
(async () => {
return await document.hasPrivateToken($1);
})();)",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ(false, EvalJs(shell(), command));
fail_to_execute_op_via_iframe("/redeem", R"({"type": "token-redemption"})");
command = JsReplace(R"(
(async () => {
return document.hasRedemptionRecord($1);
})();)",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ(false, EvalJs(shell(), command));
fail_to_execute_op_via_iframe("/bad", R"({"type": "bad-type"})");
command = JsReplace(R"(
(async () => {
return await document.hasPrivateToken($1)
|| document.hasRedemptionRecord($1);
})();)",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ(false, EvalJs(shell(), command));
EXPECT_EQ(0, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, HasTrustTokenAfterIssuance) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = JsReplace(R"(
(async () => {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
return await document.hasPrivateToken($1);
})();)",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ(true, EvalJs(shell(), command));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
SigningWithNoRedemptionRecordDoesntCancelRequest) {
TrustTokenRequestHandler::Options options;
request_handler_.UpdateOptions(std::move(options));
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = JsReplace(R"((async () => {
await fetch("/sign", {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: [$1]}});
return "Success";
})(); )",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ("Success", EvalJs(shell(), command));
EXPECT_THAT(request_handler_.last_incoming_signed_request(),
Optional(ReflectsSigningFailure()));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, FetchEndToEndInIsolatedWorld) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
await fetch("/sign", {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: [$1]}});
return "Success"; })(); )";
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test")),
EXECUTE_SCRIPT_DEFAULT_OPTIONS,
30));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsTimers) {
base::HistogramTester histograms;
base::RunLoop run_loop;
content::URLLoaderInterceptor completion_waiter(
base::BindRepeating([](URLLoaderInterceptor::RequestParams*) {
return false;
}),
base::BindLambdaForTesting(
[&run_loop](const GURL& url,
const network::URLLoaderCompletionStatus& status) {
if (url.spec().find("sign") != std::string::npos)
run_loop.Quit();
}),
base::NullCallback());
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
await fetch("/sign", {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: [$1]}});
return "Success"; })(); )";
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
run_loop.Run();
content::FetchHistogramsFromChildProcesses();
for (const std::string& op : {"Issuance", "Redemption", "Signing"}) {
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationBeginTime.Success." + op, 1);
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationTotalTime.Success." + op, 1);
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationServerTime.Success." + op, 1);
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationFinalizeTime.Success." + op, 1);
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Success." + op, net::OK,
1);
}
histograms.ExpectTotalCount("Net.TrustTokens.ProtocolVersion", 1);
histograms.ExpectUniqueSample(
"Net.TrustTokens.ProtocolVersion",
network::mojom::TrustTokenProtocolVersion::kTrustTokenV3Pmb, 1);
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsNetErrorCodes) {
base::HistogramTester histograms;
ProvideRequestHandlerKeyCommitmentsToNetworkService(
{"no-cert-for-this.domain"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_THAT(
EvalJs(shell(), JsReplace(
R"(fetch($1, {privateToken: {
version: 1,
operation: 'token-request'}})
.then(() => "Unexpected success!")
.catch(err => err.message);)",
IssuanceOriginFromHost("no-cert-for-this.domain")))
.ExtractString(),
HasSubstr("Failed to fetch"));
EXPECT_THAT(
EvalJs(shell(), JsReplace(
R"(fetch($1, {privateToken: {
version: 1,
operation: 'send-redemption-record',
issuers: ['https://nonexistent-issuer.example']}})
.then(() => "Unexpected success!")
.catch(err => err.message);)",
IssuanceOriginFromHost("no-cert-for-this.domain")))
.ExtractString(),
HasSubstr("Failed to fetch"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Success.Issuance",
net::ERR_CERT_COMMON_NAME_INVALID, 1);
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Success.Signing",
net::ERR_CERT_COMMON_NAME_INVALID, 1);
EXPECT_EQ("InvalidStateError",
EvalJs(shell(), JsReplace(
R"(fetch($1, {privateToken: {
version: 1,
operation: 'token-redemption'}})
.then(() => "Unexpected success!")
.catch(err => err.name);)",
IssuanceOriginFromHost("a.test"))));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Failure.Redemption",
net::ERR_TRUST_TOKEN_OPERATION_FAILED, 1);
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsFetchFailureReasons) {
base::HistogramTester histograms;
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("TypeError", EvalJs(shell(),
R"(fetch("/cross-site/b.test/issue", {
redirect: 'error',
privateToken: {version: 1,
operation: 'token-request'}
})
.then(() => "Unexpected success!")
.catch(err => err.name);)"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForFetchFailure.Issuance", net::ERR_FAILED,
1);
EXPECT_EQ("OperationError", EvalJs(shell(),
R"(fetch("/redeem", {privateToken: {
version: 1,
operation: 'token-redemption'}})
.then(() => "Unexpected success!")
.catch(err => err.name);)"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForFetchFailure.Redemption",
net::ERR_TRUST_TOKEN_OPERATION_FAILED,
1);
ASSERT_TRUE(NavigateToURL(
shell(),
server_.GetURL("b.test",
"/cross-origin-opener-policy_redirect_final.html")));
GURL site_a_issuance_url =
GURL(IssuanceOriginFromHost("a.test")).Resolve("/issue");
EXPECT_THAT(EvalJs(shell(), JsReplace(R"(fetch($1, {
mode: 'no-cors',
privateToken: {version: 1,
operation: 'token-request'}})
.then(() => "Unexpected success!")
.catch(err => err.message);)",
site_a_issuance_url))
.ExtractString(),
HasSubstr("Failed to fetch"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectBucketCount(
"Net.TrustTokens.NetErrorForFetchFailure.Issuance",
net::ERR_BLOCKED_BY_RESPONSE,
1);
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, OperationsRequireSecureContext) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL start_url =
embedded_test_server()->GetURL("insecure.test", "/page_with_iframe.html");
ASSERT_FALSE(network::IsUrlPotentiallyTrustworthy(start_url));
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command =
R"(fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}})
.catch(error => error.message);)";
EXPECT_THAT(EvalJs(shell(), command).ExtractString(),
HasSubstr("secure context"));
EXPECT_EQ(false, EvalJs(shell(), "'setTrustToken' in (new XMLHttpRequest);"));
GURL issuance_url = server_.GetURL("/issue");
URLLoaderMonitor monitor({issuance_url});
EXPECT_TRUE(ExecJs(
shell(), JsReplace(
R"( const myFrame = document.getElementById("test_iframe");
myFrame.trustToken = $1;
myFrame.src = $2;)",
R"({"operation": "token-request"})", issuance_url)));
monitor.WaitForUrls();
EXPECT_THAT(monitor.GetRequestInfo(issuance_url),
Optional(Field(&network::ResourceRequest::trust_token_params,
IsFalse())));
EXPECT_EQ(0, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IssuanceRequiresKeys) {
ProvideRequestHandlerKeyCommitmentsToNetworkService(
{"not-the-right-server.example"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
fetch('/issue', {privateToken: {version: 1,
operation: 'token-request'}})
.then(() => 'Success').catch(err => err.name); )";
EXPECT_EQ("InvalidStateError", EvalJs(shell(), command));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
CorrectlyReportsServerErrorDuringIssuance) {
TrustTokenRequestHandler::Options options;
options.issuance_outcome =
TrustTokenRequestHandler::ServerOperationOutcome::kUnconditionalFailure;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("OperationError", EvalJs(shell(), R"(fetch('/issue',
{ privateToken: { version: 1, operation: 'token-request' } })
.then(()=>'Success').catch(err => err.name); )"));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, CrossOriginIssuanceWorks) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"sub1.b.test"});
GURL start_url = server_.GetURL("sub2.b.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(R"(
fetch($1, { privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("sub1.b.test", "/issue"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, CrossSiteIssuanceWorks) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("b.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch($1, { privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
IssuanceRespectsAssociatedIssuersCap) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
static_assert(
network::kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers < 10,
"Consider rewriting this test for performance's sake if the "
"number-of-issuers limit gets too large.");
for (int i = 0;
i < network::kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers; ++i) {
ASSERT_EQ("Success", EvalJs(shell(), "document.hasPrivateToken('https://a" +
base::NumberToString(i) +
".test').then(()=>'Success');"));
}
EXPECT_EQ("OperationError", EvalJs(shell(), R"(
fetch('/issue', { privateToken: { version: 1,
operation: 'token-request' } })
.then(() => 'Success').catch(error => error.name); )"));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
CorsModeCrossOriginRedirectIssuanceUsesNewOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(fetch($1, {privateToken: {
version: 1,
operation: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ(
"Success",
EvalJs(shell(),
JsReplace(command,
server_.GetURL("a.test", "/cross-site/b.test/issue"))));
EXPECT_EQ(true, EvalJs(shell(), JsReplace("document.hasPrivateToken($1);",
IssuanceOriginFromHost("b.test"))));
EXPECT_EQ(false,
EvalJs(shell(), JsReplace("document.hasPrivateToken($1);",
IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(2, access_count_);
}
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
NoCorsModeCrossOriginRedirectIssuanceUsesOriginalOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(fetch($1, {mode: 'no-cors',
privateToken: {
version: 1,
operation: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ(
"Success",
EvalJs(shell(),
JsReplace(command,
server_.GetURL("a.test", "/cross-site/b.test/issue"))));
EXPECT_EQ(true, EvalJs(shell(), JsReplace("document.hasPrivateToken($1);",
IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(false,
EvalJs(shell(), JsReplace("document.hasPrivateToken($1);",
IssuanceOriginFromHost("b.test"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
IssuanceRequiresSuitableTopFrameOrigin) {
ProvideRequestHandlerKeyCommitmentsToNetworkService();
GURL file_url = GetTestUrl(nullptr, "title1.html");
ASSERT_TRUE(file_url.SchemeIsFile());
ASSERT_TRUE(NavigateToURL(shell(), file_url));
std::string command =
R"(fetch($1, {privateToken: {version: 1,
operation: 'token-request'}})
.catch(error => error.name);)";
EXPECT_EQ("InvalidStateError",
EvalJs(shell(), JsReplace(command, server_.GetURL("/issue"))));
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ(
false,
EvalJs(shell(),
JsReplace("document.hasPrivateToken($1);",
url::Origin::Create(server_.base_url()).Serialize())));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RedemptionRequiresSuitableTopFrameOrigin) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command =
R"(fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ("Success", EvalJs(shell(), command));
GURL file_url = GetTestUrl(nullptr, "title1.html");
ASSERT_TRUE(NavigateToURL(shell(), file_url));
command = R"(fetch($1, {privateToken: {version: 1,
operation: 'token-redemption'}})
.catch(error => error.name);)";
EXPECT_EQ(
"InvalidStateError",
EvalJs(shell(), JsReplace(command, server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ(2, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
HasTrustTokenRequiresSuitableTopFrameOrigin) {
GURL file_url = GetTestUrl(nullptr, "title1.html");
ASSERT_TRUE(file_url.SchemeIsFile());
ASSERT_TRUE(NavigateToURL(shell(), file_url));
EXPECT_EQ("NotAllowedError",
EvalJs(shell(),
R"(document.hasPrivateToken('https://issuer.example')
.catch(error => error.name);)"));
EXPECT_EQ(0, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
HasTrustTokenFromSecureSubframeWithOpaqueOrigin) {
ASSERT_TRUE(NavigateToURL(
shell(), server_.GetURL("a.test", "/page_with_sandboxed_iframe.html")));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
EXPECT_EQ("Success",
EvalJs(root->child_at(0)->current_frame_host(),
R"(document.hasPrivateToken('https://davids.website')
.then(()=>'Success');)"));
EXPECT_EQ(0, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
OperationFromSecureSubframeWithOpaqueOrigin) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(
shell(), server_.GetURL("a.test", "/page_with_sandboxed_iframe.html")));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
EXPECT_EQ("Success", EvalJs(root->child_at(0)->current_frame_host(),
JsReplace(R"(
fetch($1, {mode: 'no-cors',
privateToken: {
version: 1,
operation: 'token-request'}
}).then(()=>'Success');)",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IssuanceWithAbsentKeyFails) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
request_handler_.UpdateOptions(TrustTokenRequestHandler::Options());
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command =
R"(fetch($1, {privateToken: {version: 1,
operation: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ(
"OperationError",
EvalJs(shell(), JsReplace(command, server_.GetURL("a.test", "/issue"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
SignFromFrameLackingACommittedNavigation) {
GURL start_url = server_.GetURL(
"a.test", "/page-executing-trust-token-signing-from-204-subframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
EXPECT_EQ("Success", EvalJs(root->child_at(0)->current_frame_host(),
JsReplace(R"(
fetch($1, {mode: 'no-cors',
privateToken: {
version: 1,
operation: 'send-redemption-record',
issuers: [
'https://issuer.example'
]}
}).then(()=>'Success');)",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RedemptionRequiresKeys) {
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("InvalidStateError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RedemptionRequiresTokens) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("OperationError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ(1, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RedemptionWithoutTokensForDesiredIssuerFails) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("OperationError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )",
server_.GetURL("b.test", "/redeem"))));
EXPECT_EQ(2, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
CorrectlyReportsServerErrorDuringRedemption) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("Success", EvalJs(shell(), R"(fetch('/issue',
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )"));
EXPECT_EQ("OperationError", EvalJs(shell(), R"(fetch('/issue',
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )"));
EXPECT_EQ(2, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RedemptionHitsRedemptionRecordCache) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ("NoModificationAllowedError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RefreshPolicyRefreshWorksInIssuerContext) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption',
refreshPolicy: 'refresh' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RefreshPolicyRefreshRequiresIssuerContext) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"b.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("b.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("b.test", "/redeem"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption',
refreshPolicy: 'refresh' } })
.then(()=>'Success').catch(err => err.name); )",
server_.GetURL("b.test", "/redeem"))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
CorsModeCrossOriginRedirectRedemptionUsesNewOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("b.test", "/issue"))));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/cross-site/b.test/redeem',
{ privateToken: { mode: 'cors',
version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )"));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ privateToken: { version: 1,
operation: 'send-redemption-record',
issuers: [$1],
} }).then(()=>'Success');)",
IssuanceOriginFromHost("b.test"))));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ privateToken: { version: 1,
operation: 'send-redemption-record',
issuers: [$1],
} }).then(()=>'Success');)",
IssuanceOriginFromHost("a.test"))));
EXPECT_THAT(request_handler_.last_incoming_signed_request(),
Optional(ReflectsSigningFailure()));
EXPECT_EQ(6, access_count_);
}
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
NoCorsModeCrossOriginRedirectRedemptionUsesOriginalOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/issue',
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )"));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/cross-site/b.test/redeem',
{ mode: 'no-cors',
privateToken: { version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )"));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ privateToken: { version: 1,
operation: 'send-redemption-record',
issuers: [$1]
} })
.then(()=>'Success'); )",
IssuanceOriginFromHost("a.test"))));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ privateToken: { version: 1,
operation: 'send-redemption-record',
issuers: [$1]
} })
.then(()=>'Success'); )",
IssuanceOriginFromHost("b.test"))));
EXPECT_THAT(request_handler_.last_incoming_signed_request(),
Optional(ReflectsSigningFailure()));
EXPECT_EQ(4, access_count_);
}
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
NoCorsModeCrossOriginRedirectRedemptionRecyclesSameRedemptionRequest) {
TrustTokenRequestHandler::Options options;
options.batch_size = 1;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/issue',
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )"));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/cross-site/b.test/redeem',
{ mode: 'no-cors',
privateToken: { version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )"));
EXPECT_EQ(2, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
SigningRequiresRedemptionRecordInStorage) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
try {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
await fetch("/sign", {privateToken: {
version: 1,
operation: 'send-redemption-record',
issuers: [$1]} // b.test, set below
});
return "Requests succeeded";
} catch (err) {
return "Requests failed unexpectedly";
}
})(); )";
EXPECT_EQ(
"Requests succeeded",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("b.test"))));
EXPECT_THAT(request_handler_.last_incoming_signed_request(),
Optional(ReflectsSigningFailure()));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, FetchEndToEndWithServiceWorker) {
ASSERT_TRUE(embedded_test_server()->Start());
const char* const hostname = "a.test";
ProvideRequestHandlerKeyCommitmentsToNetworkService({hostname});
const std::string origin = IssuanceOriginFromHost(hostname);
const GURL create_sw_url =
server_.GetURL(hostname, "/service_worker/create_service_worker.html");
EXPECT_TRUE(NavigateToURL(shell(), create_sw_url));
EXPECT_EQ("DONE",
EvalJs(shell(), "register('fetch_event_respond_with_fetch.js');"));
const GURL empty_page_url =
server_.GetURL(hostname, "/service_worker/empty.html");
EXPECT_TRUE(NavigateToURL(shell(), empty_page_url));
const std::string trust_token_fetch_snippet = R"(
(async () => {
if (navigator.serviceWorker.controller === null) return "NotServiceWorker";
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
await fetch("/sign", {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: [$1]}});
return "TTSuccess"; })(); )";
EXPECT_EQ("TTSuccess",
EvalJs(shell(), JsReplace(trust_token_fetch_snippet, origin)));
EXPECT_THAT(
request_handler_.last_incoming_signed_request(),
Optional(AllOf(
HasHeader(network::kTrustTokensRequestHeaderSecRedemptionRecord),
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
EXPECT_EQ(3, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RedemptionLimit) {
TrustTokenRequestHandler::Options options;
options.batch_size = 10;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption',
refreshPolicy: 'refresh' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ("Error",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ privateToken: { version: 1,
operation: 'token-redemption',
refreshPolicy: 'refresh' } })
.then(()=>'Success')
.catch(()=>'Error'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ(4, access_count_);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, CheckDepreciatedTypeField) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(fetch(
"/issue", {privateToken: {type: 'token-request'}})
.then(()=>'Success')
.catch(error => error.message); )";
EXPECT_THAT(EvalJs(shell(), command).ExtractString(),
HasSubstr("Failed to read the 'operation'\
property from 'PrivateToken': Required member is undefined."));
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
SendRedemptionRequestWithEmptyIssuers) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {privateToken: {version: 1,
operation: 'token-request'}});
await fetch("/redeem", {privateToken: {version: 1,
operation: 'token-redemption'}});
return "Success"; })(); )";
ASSERT_EQ("Success", EvalJs(shell(), command));
command = R"(
fetch("/sign", {privateToken: {version: 1,
operation: 'send-redemption-record',
issuers: []}})
.then(() => 'Success')
.catch(error => error.message); )";
EXPECT_THAT(EvalJs(shell(), command).ExtractString(),
HasSubstr("Failed to execute 'fetch' on 'Window':\
privateToken: operation type 'send-redemption-record' requires that\
the 'issuers' field be present and contain at least one secure,\
HTTP(S) URL, but it was missing or empty."));
}
}