blob: 8433c9e6309f9c09b7a4df8990315e4e95e12448 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/public/common/origin_trials/trial_token_validator.h"
#include <memory>
#include <set>
#include <string>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/origin_trials/origin_trial_policy.h"
#include "third_party/blink/public/common/origin_trials/trial_token.h"
#include "url/gurl.h"
namespace blink {
namespace trial_token_validator_unittest {
// These are sample public keys for testing the API.
// For the first public key, the corresponding private key (use this
// to generate new samples for this test file) is:
//
// 0x83, 0x67, 0xf4, 0xcd, 0x2a, 0x1f, 0x0e, 0x04, 0x0d, 0x43, 0x13,
// 0x4c, 0x67, 0xc4, 0xf4, 0x28, 0xc9, 0x90, 0x15, 0x02, 0xe2, 0xba,
// 0xfd, 0xbb, 0xfa, 0xbc, 0x92, 0x76, 0x8a, 0x2c, 0x4b, 0xc7, 0x75,
// 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2, 0x9a,
// 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f, 0x64,
// 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0
// For the second public key, the corresponding private key is:
// 0x21, 0xee, 0xfa, 0x81, 0x6a, 0xff, 0xdf, 0xb8, 0xc1, 0xdd, 0x75,
// 0x05, 0x04, 0x29, 0x68, 0x67, 0x60, 0x85, 0x91, 0xd0, 0x50, 0x16,
// 0x0a, 0xcf, 0xa2, 0x37, 0xa3, 0x2e, 0x11, 0x7a, 0x17, 0x96, 0x50,
// 0x07, 0x4d, 0x76, 0x55, 0x56, 0x42, 0x17, 0x2d, 0x8a, 0x9c, 0x47,
// 0x96, 0x25, 0xda, 0x70, 0xaa, 0xb9, 0xfd, 0x53, 0x5d, 0x51, 0x3e,
// 0x16, 0xab, 0xb4, 0x86, 0xea, 0xf3, 0x35, 0xc6, 0xca
const uint8_t kTestPublicKeys[][32] = {
{
0x75, 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2,
0x9a, 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f,
0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0,
},
{
0x50, 0x07, 0x4d, 0x76, 0x55, 0x56, 0x42, 0x17, 0x2d, 0x8a, 0x9c,
0x47, 0x96, 0x25, 0xda, 0x70, 0xaa, 0xb9, 0xfd, 0x53, 0x5d, 0x51,
0x3e, 0x16, 0xab, 0xb4, 0x86, 0xea, 0xf3, 0x35, 0xc6, 0xca,
}};
const int kTestPublicKeysSize = 2;
// The corresponding private key can be found above.
const uint8_t kTestPublicKeys2[][32] = {{
0x50, 0x07, 0x4d, 0x76, 0x55, 0x56, 0x42, 0x17, 0x2d, 0x8a, 0x9c,
0x47, 0x96, 0x25, 0xda, 0x70, 0xaa, 0xb9, 0xfd, 0x53, 0x5d, 0x51,
0x3e, 0x16, 0xab, 0xb4, 0x86, 0xea, 0xf3, 0x35, 0xc6, 0xca,
}};
const int kTestPublicKeys2Size = 1;
// This is a good trial token, signed with the above test private key.
// TODO(iclelland): This token expires in 2033. Update it or find a way
// to autogenerate it before then.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Frobulate --expire-timestamp=2000000000
const char kSampleToken[] =
"AuR/1mg+/w5ROLN54Ok20rApK3opgR7Tq9ZfzhATQmnCa+BtPA1RRw4Nigf336r+"
"O4fM3Sa+MEd+5JcIgSZafw8AAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMjAwMDAwMDAwMH0=";
const uint8_t kSampleTokenSignature[] = {
0xe4, 0x7f, 0xd6, 0x68, 0x3e, 0xff, 0x0e, 0x51, 0x38, 0xb3, 0x79,
0xe0, 0xe9, 0x36, 0xd2, 0xb0, 0x29, 0x2b, 0x7a, 0x29, 0x81, 0x1e,
0xd3, 0xab, 0xd6, 0x5f, 0xce, 0x10, 0x13, 0x42, 0x69, 0xc2, 0x6b,
0xe0, 0x6d, 0x3c, 0x0d, 0x51, 0x47, 0x0e, 0x0d, 0x8a, 0x07, 0xf7,
0xdf, 0xaa, 0xfe, 0x3b, 0x87, 0xcc, 0xdd, 0x26, 0xbe, 0x30, 0x47,
0x7e, 0xe4, 0x97, 0x08, 0x81, 0x26, 0x5a, 0x7f, 0x0f};
// The expiry time of the sample token (2033-05-18 03:33:20 UTC).
const base::Time kSampleTokenExpiryTime = base::Time::FromJsTime(2000000000000);
// This is a trial token signed with the corresponding private key
// for kTestPublicKeys2
// TODO(iclelland): This token expires in 2033. Update it or find a way
// to autogenerate it before then.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Frobulate --expire-timestamp=2000000000
// --key-file=eftest2.key
const char kSampleToken2[] =
"Ar3e2ev1rH7T/5NRr/9g/ehLLk7dXBi4mjluPG7pohGifzTJCgBtuGhgJXO/8tD/"
"m59D2hj0sLjSYSDw4B5NiA4AAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5le"
"GFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5Ij"
"ogMjAwMDAwMDAwMH0=";
// The token should be valid for this origin and for this feature.
const char kAppropriateOrigin[] = "https://valid.example.com";
const char kAppropriateFeatureName[] = "Frobulate";
const char kAppropriateThirdPartyFeatureName[] = "FrobulateThirdParty";
const char kInappropriateFeatureName[] = "Grokalyze";
const char kInappropriateOrigin[] = "https://invalid.example.com";
const char kInsecureOrigin[] = "http://valid.example.com";
// Well-formed trial token with an invalid signature.
// This token is a corruption of the above valid token.
const char kInvalidSignatureToken[] =
"AuR/1mg+/w5ROLN54Ok20rApK3opgR7Tq9ZfzhATQmnCa+BtPA1RRw4Nigf336r+"
"RrOtlAwa0gPqqn+A8GTD3AQAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMjAwMDAwMDAwMH0=";
// Well-formed, but expired, trial token. (Expired in 2001)
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Frobulate --expire-timestamp=1000000000
const char kExpiredToken[] =
"AmHPUIXMaXe9jWW8kJeDFXolVjT93p4XMnK4+jMYd2pjqtFcYB1bUmdD8PunQKM+"
"RrOtlAwa0gPqqn+A8GTD3AQAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMTAwMDAwMDAwMH0=";
const uint8_t kExpiredTokenSignature[] = {
0x61, 0xcf, 0x50, 0x85, 0xcc, 0x69, 0x77, 0xbd, 0x8d, 0x65, 0xbc,
0x90, 0x97, 0x83, 0x15, 0x7a, 0x25, 0x56, 0x34, 0xfd, 0xde, 0x9e,
0x17, 0x32, 0x72, 0xb8, 0xfa, 0x33, 0x18, 0x77, 0x6a, 0x63, 0xaa,
0xd1, 0x5c, 0x60, 0x1d, 0x5b, 0x52, 0x67, 0x43, 0xf0, 0xfb, 0xa7,
0x40, 0xa3, 0x3e, 0x46, 0xb3, 0xad, 0x94, 0x0c, 0x1a, 0xd2, 0x03,
0xea, 0xaa, 0x7f, 0x80, 0xf0, 0x64, 0xc3, 0xdc, 0x04};
const char kUnparsableToken[] = "abcde";
// Well-formed token, for an insecure origin.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py http://valid.example.com Frobulate
// --expire-timestamp=2000000000
const char kInsecureOriginToken[] =
"AjfC47H1q8/Ho5ALFkjkwf9CBK6oUUeRTlFc50Dj+eZEyGGKFIY2WTxMBfy8cLc3"
"E0nmFroDA3OmABmO5jMCFgkAAABXeyJvcmlnaW4iOiAiaHR0cDovL3ZhbGlkLmV4"
"YW1wbGUuY29tOjgwIiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlIiwgImV4cGlyeSI6"
"IDIwMDAwMDAwMDB9";
// Well-formed token, for match against third party origins.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py 3 valid.example.com Frobulate
// --is-third-party --expire-timestamp=2000000000
const char kThirdPartyToken[] =
"A8ZESIWJHtuoIZyWgaHUPEhuc4CnbiETy5D4"
"PeABEP8NB8oI2fUfF9N53elgnNuyL0ltq+fzMta1pgU3VYLyuAcAAABveyJvcmln"
"aW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0NDMiLCAiaXNUaGlyZFBh"
"cnR5IjogdHJ1ZSwgImZlYXR1cmUiOiAiRnJvYnVsYXRlIiwgImV4cGlyeSI6IDIw"
"MDAwMDAwMDB9";
// Well-formed token, for match against third party origins and its usage
// set to user subset exclusion.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com FrobulateThirdParty
// --version 3 --is-third-party --usage-restriction subset
// --expire-timestamp=2000000000
const char kThirdPartyUsageSubsetToken[] =
"A3mGpVqzEea9V9Nl6Qr2LS84PxTf2ZnWdtU6cNZvGmX1rRX5khvJSYuYSCP0J8Ca"
"XLG+MH6jT+3IH7CWVASK0gcAAACMeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiaXNUaGlyZFBhcnR5IjogdHJ1ZSwgInVzYWdlIjog"
"InN1YnNldCIsICJmZWF0dXJlIjogIkZyb2J1bGF0ZVRoaXJkUGFydHkiLCAiZXhw"
"aXJ5IjogMjAwMDAwMDAwMH0=";
// This timestamp is set to a time after the expiry timestamp of kExpiredToken,
// but before the expiry timestamp of kValidToken.
double kNowTimestamp = 1500000000;
class TestOriginTrialPolicy : public OriginTrialPolicy {
public:
bool IsOriginTrialsSupported() const override { return true; }
bool IsOriginSecure(const GURL& url) const override {
return url.SchemeIs("https");
}
std::vector<base::StringPiece> GetPublicKeys() const override {
return keys_;
}
bool IsFeatureDisabled(base::StringPiece feature) const override {
return disabled_features_.count(feature.as_string()) > 0;
}
bool IsFeatureDisabledForUser(base::StringPiece feature) const override {
return disabled_features_for_user_.count(feature.as_string()) > 0;
}
// Test setup methods
void SetPublicKeys(const uint8_t keys[][32], const int keys_size) {
keys_.clear();
for (int n = 0; n < keys_size; n++) {
keys_.push_back(base::StringPiece(reinterpret_cast<const char*>(keys[n]),
base::size(keys[n])));
}
}
void DisableFeature(const std::string& feature) {
disabled_features_.insert(feature);
}
void DisableFeatureForUser(const std::string& feature) {
disabled_features_for_user_.insert(feature);
}
void DisableToken(const std::string& token) {
disabled_tokens_.insert(token);
}
protected:
bool IsTokenDisabled(base::StringPiece token_signature) const override {
return disabled_tokens_.count(token_signature.as_string()) > 0;
}
private:
std::vector<base::StringPiece> keys_;
std::set<std::string> disabled_features_;
std::set<std::string> disabled_features_for_user_;
std::set<std::string> disabled_tokens_;
};
class TrialTokenValidatorTest : public testing::Test {
public:
TrialTokenValidatorTest()
: appropriate_origin_(url::Origin::Create(GURL(kAppropriateOrigin))),
inappropriate_origin_(url::Origin::Create(GURL(kInappropriateOrigin))),
insecure_origin_(url::Origin::Create(GURL(kInsecureOrigin))),
valid_token_signature_(
std::string(reinterpret_cast<const char*>(kSampleTokenSignature),
base::size(kSampleTokenSignature))),
expired_token_signature_(
std::string(reinterpret_cast<const char*>(kExpiredTokenSignature),
base::size(kExpiredTokenSignature))),
response_headers_(new net::HttpResponseHeaders("")) {
TrialTokenValidator::SetOriginTrialPolicyGetter(
base::BindRepeating([](OriginTrialPolicy* policy) { return policy; },
base::Unretained(&policy_)));
SetPublicKeys(kTestPublicKeys, kTestPublicKeysSize);
}
~TrialTokenValidatorTest() override {
TrialTokenValidator::ResetOriginTrialPolicyGetter();
}
void SetPublicKeys(const uint8_t keys[][32], const int keys_size) {
policy_.SetPublicKeys(keys, keys_size);
}
void DisableFeature(const std::string& feature) {
policy_.DisableFeature(feature);
}
void DisableFeatureForUser(const std::string& feature) {
policy_.DisableFeatureForUser(feature);
}
void DisableToken(const std::string& token_signature) {
policy_.DisableToken(token_signature);
}
base::Time Now() { return base::Time::FromDoubleT(kNowTimestamp); }
const url::Origin appropriate_origin_;
const url::Origin inappropriate_origin_;
const url::Origin insecure_origin_;
std::string valid_token_signature_;
std::string expired_token_signature_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;
TestOriginTrialPolicy policy_;
const TrialTokenValidator validator_;
};
TEST_F(TrialTokenValidatorTest, ValidateValidToken) {
TrialTokenResult result =
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now());
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess, result.status);
EXPECT_EQ(kAppropriateFeatureName, result.feature_name);
EXPECT_EQ(kSampleTokenExpiryTime, result.expiry_time);
EXPECT_EQ(false, result.is_third_party);
// All signing keys should be able to validate their tokens.
result = validator_.ValidateToken(kSampleToken2, appropriate_origin_, Now());
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess, result.status);
EXPECT_EQ(kAppropriateFeatureName, result.feature_name);
EXPECT_EQ(kSampleTokenExpiryTime, result.expiry_time);
EXPECT_EQ(false, result.is_third_party);
}
TEST_F(TrialTokenValidatorTest, ValidateThirdPartyTokenFromExternalScript) {
TrialTokenResult result = validator_.ValidateToken(
kThirdPartyToken, inappropriate_origin_, &appropriate_origin_, Now());
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess, result.status);
EXPECT_EQ(kAppropriateFeatureName, result.feature_name);
EXPECT_EQ(kSampleTokenExpiryTime, result.expiry_time);
EXPECT_EQ(true, result.is_third_party);
}
TEST_F(TrialTokenValidatorTest,
ValidateThirdPartyTokenFromInappropriateScriptOrigin) {
EXPECT_EQ(blink::OriginTrialTokenStatus::kWrongOrigin,
validator_
.ValidateToken(kThirdPartyToken, appropriate_origin_,
&inappropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidateThirdPartyTokenNotFromExternalScript) {
EXPECT_EQ(
blink::OriginTrialTokenStatus::kWrongOrigin,
validator_
.ValidateToken(kThirdPartyToken, appropriate_origin_, nullptr, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidateInappropriateOrigin) {
EXPECT_EQ(blink::OriginTrialTokenStatus::kWrongOrigin,
validator_.ValidateToken(kSampleToken, inappropriate_origin_, Now())
.status);
EXPECT_EQ(
blink::OriginTrialTokenStatus::kWrongOrigin,
validator_.ValidateToken(kSampleToken, insecure_origin_, Now()).status);
}
TEST_F(TrialTokenValidatorTest, ValidateInvalidSignature) {
EXPECT_EQ(
blink::OriginTrialTokenStatus::kInvalidSignature,
validator_
.ValidateToken(kInvalidSignatureToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidateUnparsableToken) {
EXPECT_EQ(
blink::OriginTrialTokenStatus::kMalformed,
validator_.ValidateToken(kUnparsableToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidateExpiredToken) {
EXPECT_EQ(blink::OriginTrialTokenStatus::kExpired,
validator_.ValidateToken(kExpiredToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidateValidTokenWithIncorrectKey) {
SetPublicKeys(kTestPublicKeys2, kTestPublicKeys2Size);
EXPECT_EQ(blink::OriginTrialTokenStatus::kInvalidSignature,
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, PublicKeyNotAvailable) {
SetPublicKeys({}, 0);
EXPECT_EQ(blink::OriginTrialTokenStatus::kNotSupported,
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledFeatures) {
TrialTokenResult result =
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now());
// Disable an irrelevant feature; token should still validate
DisableFeature(kInappropriateFeatureName);
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess, result.status);
EXPECT_EQ(kAppropriateFeatureName, result.feature_name);
EXPECT_EQ(kSampleTokenExpiryTime, result.expiry_time);
// Disable the token's feature; it should no longer be valid
DisableFeature(kAppropriateFeatureName);
EXPECT_EQ(blink::OriginTrialTokenStatus::kFeatureDisabled,
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledFeaturesForUser) {
// Token should be valid if the feature is not disabled for user.
TrialTokenResult result = validator_.ValidateToken(
kThirdPartyUsageSubsetToken, inappropriate_origin_, &appropriate_origin_,
Now());
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess, result.status);
EXPECT_EQ(kAppropriateThirdPartyFeatureName, result.feature_name);
EXPECT_EQ(kSampleTokenExpiryTime, result.expiry_time);
// Token should be invalid when the feature is disabled for user.
DisableFeatureForUser(kAppropriateThirdPartyFeatureName);
EXPECT_EQ(
blink::OriginTrialTokenStatus::kFeatureDisabledForUser,
validator_
.ValidateToken(kThirdPartyUsageSubsetToken, inappropriate_origin_,
&appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledTokens) {
TrialTokenResult result =
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now());
// Disable an irrelevant token; token should still validate
DisableToken(expired_token_signature_);
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess, result.status);
EXPECT_EQ(kAppropriateFeatureName, result.feature_name);
EXPECT_EQ(kSampleTokenExpiryTime, result.expiry_time);
// Disable the token; it should no longer be valid
DisableToken(valid_token_signature_);
EXPECT_EQ(blink::OriginTrialTokenStatus::kTokenDisabled,
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidateRequestInsecure) {
response_headers_->AddHeader("Origin-Trial", kInsecureOriginToken);
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kInsecureOrigin), response_headers_.get(), kAppropriateFeatureName,
Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestValidToken) {
response_headers_->AddHeader("Origin-Trial", kSampleToken);
EXPECT_TRUE(validator_.RequestEnablesFeature(GURL(kAppropriateOrigin),
response_headers_.get(),
kAppropriateFeatureName, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestNoTokens) {
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kAppropriateOrigin), response_headers_.get(),
kAppropriateFeatureName, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestMultipleHeaders) {
response_headers_->AddHeader("Origin-Trial", kSampleToken);
response_headers_->AddHeader("Origin-Trial", kExpiredToken);
EXPECT_TRUE(validator_.RequestEnablesFeature(GURL(kAppropriateOrigin),
response_headers_.get(),
kAppropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kAppropriateOrigin), response_headers_.get(),
kInappropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kInappropriateOrigin), response_headers_.get(),
kAppropriateFeatureName, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestMultipleHeaderValues) {
response_headers_->AddHeader(
"Origin-Trial", std::string(kExpiredToken) + ", " + kSampleToken);
EXPECT_TRUE(validator_.RequestEnablesFeature(GURL(kAppropriateOrigin),
response_headers_.get(),
kAppropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kAppropriateOrigin), response_headers_.get(),
kInappropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kInappropriateOrigin), response_headers_.get(),
kAppropriateFeatureName, Now()));
}
} // namespace trial_token_validator_unittest
} // namespace blink