Skip to content

Commit

Permalink
Allow user subset exclusion for first-party origin trial tokens
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
jpchase authored and Commit Bot committed Nov 26, 2020
1 parent bc77425 commit dbd8b97
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 19 deletions.
16 changes: 15 additions & 1 deletion docs/origin_trials_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, you’ll need to configure [runtime\_enabled\_features.json5]. This is
Expand Down Expand Up @@ -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].
Expand Down Expand Up @@ -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
7 changes: 2 additions & 5 deletions third_party/blink/common/origin_trials/trial_token.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
33 changes: 29 additions & 4 deletions third_party/blink/common/origin_trials/trial_token_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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_,
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
3 changes: 0 additions & 3 deletions tools/origin_trials/check_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 0 additions & 3 deletions tools/origin_trials/generate_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down

0 comments on commit dbd8b97

Please sign in to comment.