0

Allow user subset exclusion for first-party origin trial tokens

Previously, we added support to the token format to specify the usage
restriction to be applied. This |usage| field was only supported for
third-party tokens, for scope reasons (as described in the bug).

Now, there's a use case for specifying the usage restriction on
first-party tokens. This CL adds support for all tokens to have the
|usage| field. That mostly means removing the logic to limit the field
to third-party tokens.

Bug: 1151330
Change-Id: I2fdc27a030e49a8b8c83a695a496c1263678c06c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2560560
Commit-Queue: Jason Chase <chasej@chromium.org>
Reviewed-by: Ian Clelland <iclelland@chromium.org>
Cr-Commit-Position: refs/heads/master@{#831381}
This commit is contained in:
Jason Chase
2020-11-26 17:18:43 +00:00
committed by Commit Bot
parent bc77425849
commit dbd8b97311
8 changed files with 91 additions and 19 deletions

@ -7,6 +7,11 @@ changes required.
## Code Changes
NOTE: You can land these code changes before requesting to run an origin trial.
These code changes make it possible to control a feature via an origin trial,
but don't require an origin trial to be approved. For more on the process, see
[Running an Origin Trial].
### Runtime Enabled Features
First, youll need to configure [runtime\_enabled\_features.json5]. This is
@ -186,6 +191,14 @@ To test an origin trial feature during development, follow these steps:
tools/origin_trials/generate_token.py http://localhost:8000 MyFeature
```
There are additional flags to generate third-party tokens, set the expiry
date, and control other options. See the command help for details (`--help`).
For example, to generate a third-party token, with [user subset exclusion]:
```
tools/origin_trials/generate_token.py --is-third-party --usage-restriction=subset http://localhost:8000 MyFeature
```
2. Copy the token from the end of the output and use it in a `<meta>` tag or
an `Origin-Trial` header as described in the [Developer Guide].
@ -236,4 +249,5 @@ as tests for script-added tokens. For examples, refer to the existing tests in
[css\_properties.json5]: /third_party/blink/renderer/core/css/css_properties.json5
[origin-trial-test-property]: https://chromium.googlesource.com/chromium/src/+/ff2ab8b89745602c8300322c2a0158e210178c7e/third_party/blink/renderer/core/css/css_properties.json5#2635
[CSSStyleDeclaration]: /third_party/blink/renderer/core/css/css_style_declaration.idl
[Running an Origin Trial]: https://www.chromium.org/blink/origin-trials/running-an-origin-trial
[user subset exclusion]: https://docs.google.com/document/d/1xALH9W7rWmX0FpjudhDeS2TNTEOXuPn4Tlc9VmuPdHA/edit#heading=h.myaz1twlipw

@ -238,13 +238,10 @@ std::unique_ptr<TrialToken> TrialToken::Parse(const std::string& token_payload,
is_third_party = is_third_party_value->GetBool();
}
// The |usage| field is optional and can only be set if |isThirdParty| flag
// is true. If found, ensure its value is either empty or "subset".
// The |usage| field is optional. If found, ensure its value is either empty
// or "subset".
std::string* usage_value = datadict->FindStringKey("usage");
if (usage_value) {
if (!is_third_party) {
return nullptr;
}
if (usage_value->empty()) {
usage = UsageRestriction::kNone;
} else if (*usage_value == kUsageSubset) {

@ -263,6 +263,14 @@ const char kSampleSubdomainTokenJSON[] =
"{\"origin\": \"https://example.com:443\", \"isSubdomain\": true, "
"\"feature\": \"Frobulate\", \"expiry\": 1458766277}";
const char kUsageEmptyTokenJSON[] =
"{\"origin\": \"https://valid.example.com:443\", \"usage\": \"\", "
"\"feature\": \"Frobulate\", \"expiry\": 1458766277}";
const char kUsageSubsetTokenJSON[] =
"{\"origin\": \"https://valid.example.com:443\", \"usage\": \"subset\", "
"\"feature\": \"Frobulate\", \"expiry\": 1458766277}";
const char kSampleNonThirdPartyTokenJSON[] =
"{\"origin\": \"https://valid.example.com:443\", \"isThirdParty\": false, "
"\"feature\": \"Frobulate\", \"expiry\": 1458766277}";
@ -327,10 +335,6 @@ const char* kInvalidTokensVersion3[] = {
"{\"origin\": \"https://a.a\", \"isThirdParty\": true, \"usage\": "
"\"cycle\", \"feature\": \"a\", "
"\"expiry\": 1458766277}",
// usage in non third party token
"{\"origin\": \"https://a.a\", \"isThirdParty\": false, \"usage\": "
"\"subset\", \"feature\": \"a\", "
"\"expiry\": 1458766277}",
};
// Valid token JSON. The feature name matches matches kExpectedLongFeatureName
@ -994,6 +998,7 @@ TEST_P(TrialTokenParseTest, ParseValidToken) {
EXPECT_FALSE(token->match_subdomains());
EXPECT_EQ(expected_origin_, token->origin());
EXPECT_EQ(expected_expiry_, token->expiry_time());
EXPECT_EQ(TrialToken::UsageRestriction::kNone, token->usage_restriction());
}
TEST_P(TrialTokenParseTest, ParseValidNonSubdomainToken) {
@ -1135,6 +1140,26 @@ TEST_F(TrialTokenTest, ParseValidThirdPartyTokenInvalidVersion) {
EXPECT_EQ(expected_expiry_, token->expiry_time());
}
TEST_F(TrialTokenTest, ParseValidUsageEmptyToken) {
std::unique_ptr<TrialToken> token = Parse(kUsageEmptyTokenJSON, kVersion3);
ASSERT_TRUE(token);
EXPECT_EQ(kExpectedFeatureName, token->feature_name());
EXPECT_FALSE(token->is_third_party());
EXPECT_EQ(TrialToken::UsageRestriction::kNone, token->usage_restriction());
EXPECT_EQ(expected_origin_, token->origin());
EXPECT_EQ(expected_expiry_, token->expiry_time());
}
TEST_F(TrialTokenTest, ParseValidUsageSubsetToken) {
std::unique_ptr<TrialToken> token = Parse(kUsageSubsetTokenJSON, kVersion3);
ASSERT_TRUE(token);
EXPECT_EQ(kExpectedFeatureName, token->feature_name());
EXPECT_FALSE(token->is_third_party());
EXPECT_EQ(TrialToken::UsageRestriction::kSubset, token->usage_restriction());
EXPECT_EQ(expected_origin_, token->origin());
EXPECT_EQ(expected_expiry_, token->expiry_time());
}
TEST_F(TrialTokenTest, ParseValidThirdPartyUsageSubsetToken) {
std::unique_ptr<TrialToken> token =
Parse(kSampleThirdPartyTokenUsageSubsetJSON, kVersion3);

@ -107,8 +107,7 @@ TrialTokenResult TrialTokenValidator::ValidateToken(
if (policy->IsTokenDisabled(trial_token->signature()))
return TrialTokenResult(OriginTrialTokenStatus::kTokenDisabled);
if (trial_token->is_third_party() &&
trial_token->usage_restriction() ==
if (trial_token->usage_restriction() ==
TrialToken::UsageRestriction::kSubset &&
policy->IsFeatureDisabledForUser(trial_token->feature_name()))
return TrialTokenResult(OriginTrialTokenStatus::kFeatureDisabledForUser);

@ -167,6 +167,17 @@ const char kThirdPartyUsageSubsetToken[] =
"InN1YnNldCIsICJmZWF0dXJlIjogIkZyb2J1bGF0ZVRoaXJkUGFydHkiLCAiZXhw"
"aXJ5IjogMjAwMDAwMDAwMH0=";
// Well-formed token, for first party, with 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 --usage-restriction subset --expire-timestamp=2000000000
const char kUsageSubsetToken[] =
"Axi0wjIp8gaGr/"
"pTPzwrHqeWXnmhCiZhE2edsJ9fHX25GV6A8zg1fCv27qhBNnbxjqDpU0a+"
"xKScEiqKK1MS3QUAAAB2eyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0ND"
"MiLCAidXNhZ2UiOiAic3Vic2V0IiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlVGhpcmRQYXJ0eSIs"
"ICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==";
// This timestamp is set to a time after the expiry timestamp of kExpiredToken,
// but before the expiry timestamp of kValidToken.
double kNowTimestamp = 1500000000;
@ -372,8 +383,24 @@ TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledFeatures) {
validator_.ValidateToken(kSampleToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest,
ValidatorRespectsDisabledFeaturesForUserWithFirstPartyToken) {
// Token should be valid if the feature is not disabled for user.
TrialTokenResult result =
validator_.ValidateToken(kUsageSubsetToken, 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(kUsageSubsetToken, appropriate_origin_, Now())
.status);
}
TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledFeaturesForUser) {
TEST_F(TrialTokenValidatorTest,
ValidatorRespectsDisabledFeaturesForUserWithThirdPartyToken) {
// Token should be valid if the feature is not disabled for user.
TrialTokenResult result = validator_.ValidateToken(
kThirdPartyUsageSubsetToken, inappropriate_origin_, &appropriate_origin_,

@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test that trial which supports third-party is enabled by valid first-party token provided in markup</title>
<!-- Generate this token with the command:
generate_token.py http://127.0.0.1:8000 FrobulateThirdParty --expire-timestamp=2000000000
-->
<meta http-equiv="origin-trial"
content="A0TyTDFs2N+ecIcAeQo8DlipLQwIEcD+bJlRGpZj5NfDDGF8VEcEL4zByhPrdadxF1PX8VG4bfd2XZep1O6m3wsAAABbeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlVGhpcmRQYXJ0eSIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==" />
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/origintrials.js"></script>
<script>
// The trial should be enabled, as first party tokens can be used normally,
// when the trial has third-party support enabled.
expect_success_third_party();
</script>

@ -222,9 +222,6 @@ def main():
if (usage_restriction is not None and version != VERSION3):
print("The usage field can only be be set in Version 3 token.")
sys.exit(1)
if (usage_restriction is not None and not is_third_party):
print("Only third party token supports alternative usage restriction.")
sys.exit(1)
if (usage_restriction not in USAGE_RESTRICTION):
print("Only empty string and \"subset\" are supported in the usage field.")
sys.exit(1)

@ -226,9 +226,6 @@ def main():
if (args.version[0] != 3):
print("Only version 3 token supports alternative usage restriction.")
sys.exit(1)
if (not args.is_third_party):
print("Only third party token supports alternative usage restriction.")
sys.exit(1)
if (args.usage_restriction not in USAGE_RESTRICTION):
print(
"Only empty string and \"subset\" are supported in alternative usage "