Skip to content

Commit

Permalink
Allow manually completed critical origin trials
Browse files Browse the repository at this point in the history
In rare cases critical origin trials need to be completed manually.
This change introduces an expiry grace period of 30 days that gives
the origin trials team the necessary time to coordinate the necessary
completion tasks.

Bug: 1264731
Change-Id: I569673899f9fff241f62a179fa9d276e44db386e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3501834
Reviewed-by: Jason Chase <chasej@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Panos Astithas <pastithas@google.com>
Auto-Submit: Panos Astithas <pastithas@google.com>
Cr-Commit-Position: refs/heads/main@{#985537}
  • Loading branch information
past authored and Chromium LUCI CQ committed Mar 25, 2022
1 parent b425567 commit 34d95a1
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 12 deletions.
4 changes: 4 additions & 0 deletions build/OWNERS.setnoparent
Expand Up @@ -69,3 +69,7 @@ file://ash/login/LOGIN_LOCK_OWNERS
# that can make infra changes.
file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS
file://infra/config/groups/sheriff-rotations/CHROMIUM_OWNERS

# Origin Trials owners are responsible for determining trials that need to be
# completed manually.
file://third_party/blink/common/origin_trials/OT_OWNERS
1 change: 1 addition & 0 deletions third_party/blink/common/BUILD.gn
Expand Up @@ -182,6 +182,7 @@ source_set("common") {
"notifications/notification_mojom_traits.cc",
"notifications/notification_resources.cc",
"notifications/platform_notification_data.cc",
"origin_trials/manual_completion_origin_trial_features.cc",
"origin_trials/navigation_origin_trial_features.cc",
"origin_trials/trial_token.cc",
"origin_trials/trial_token_result.cc",
Expand Down
7 changes: 7 additions & 0 deletions third_party/blink/common/origin_trials/OT_OWNERS
@@ -0,0 +1,7 @@
# This file covers ownership of the following file:
# //third_party/blink/common/origin_trials/manual_completion_origin_trial_features.cc

chasej@chromium.org
danielrsmith@google.com
kyleju@chromium.org
pastithas@google.com
2 changes: 2 additions & 0 deletions third_party/blink/common/origin_trials/OWNERS
Expand Up @@ -8,5 +8,7 @@ chasej@chromium.org
iclelland@chromium.org
mek@chromium.org

per-file manual_completion_origin_trial_features.cc=set noparent
per-file manual_completion_origin_trial_features.cc=file://third_party/blink/common/origin_trials/OT_OWNERS
per-file navigation_origin_trial_features.cc=set noparent
per-file navigation_origin_trial_features.cc=file://third_party/blink/SECURITY_OWNERS
@@ -0,0 +1,25 @@
// Copyright 2022 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.

// This file provides FeatureHasExpiryGracePeriod which is declared in
// origin_trials.h. FeatureHasExpiryGracePeriod is defined in this file since
// changes to it require review from the origin trials team, listed in the
// OWNERS file.

#include "third_party/blink/public/common/origin_trials/origin_trials.h"

#include "base/containers/contains.h"

namespace blink::origin_trials {

bool FeatureHasExpiryGracePeriod(OriginTrialFeature feature) {
static OriginTrialFeature const kHasExpiryGracePeriod[] = {
// Enable the kOriginTrialsSampleAPIExpiryGracePeriod feature as a manual
// completion feature, for tests.
OriginTrialFeature::kOriginTrialsSampleAPIExpiryGracePeriod,
};
return base::Contains(kHasExpiryGracePeriod, feature);
}

} // namespace blink::origin_trials
56 changes: 44 additions & 12 deletions third_party/blink/common/origin_trials/trial_token_validator.cc
Expand Up @@ -12,6 +12,7 @@
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "third_party/blink/public/common/origin_trials/origin_trial_policy.h"
#include "third_party/blink/public/common/origin_trials/origin_trials.h"
#include "third_party/blink/public/common/origin_trials/trial_token.h"
#include "third_party/blink/public/common/origin_trials/trial_token_result.h"

Expand All @@ -30,6 +31,30 @@ bool IsDeprecationTrialPossible() {
return policy && policy->IsOriginTrialsSupported();
}

// Validates the provided trial_token. If provided, the third_party_origins is
// only used for validating third-party tokens.
OriginTrialTokenStatus IsTokenValid(
const TrialToken& trial_token,
const url::Origin& origin,
base::span<const url::Origin> third_party_origins,
base::Time current_time) {
OriginTrialTokenStatus status;
if (trial_token.is_third_party()) {
if (!third_party_origins.empty()) {
for (const auto& third_party_origin : third_party_origins) {
status = trial_token.IsValid(third_party_origin, current_time);
if (status == OriginTrialTokenStatus::kSuccess)
break;
}
} else {
status = OriginTrialTokenStatus::kWrongOrigin;
}
} else {
status = trial_token.IsValid(origin, current_time);
}
return status;
}

} // namespace

TrialTokenValidator::TrialTokenValidator() {}
Expand Down Expand Up @@ -80,20 +105,27 @@ TrialTokenResult TrialTokenValidator::ValidateToken(
if (status != OriginTrialTokenStatus::kSuccess)
return TrialTokenResult(status);

// If the third_party flag is set on the token, we match it against third
// party origin if it exists. Otherwise match against document origin.
if (trial_token->is_third_party()) {
if (!third_party_origins.empty()) {
for (const auto& third_party_origin : third_party_origins) {
status = trial_token->IsValid(third_party_origin, current_time);
if (status == OriginTrialTokenStatus::kSuccess)
break;
status =
IsTokenValid(*trial_token, origin, third_party_origins, current_time);

if (status == OriginTrialTokenStatus::kExpired) {
if (origin_trials::IsTrialValid(trial_token->feature_name())) {
base::Time validated_time = current_time;
// Manual completion trials have an expiry grace period. For these trials
// the token expiry time is valid if:
// token.expiry_time + kExpiryGracePeriod > current_time
for (OriginTrialFeature feature :
origin_trials::FeaturesForTrial(trial_token->feature_name())) {
if (origin_trials::FeatureHasExpiryGracePeriod(feature)) {
validated_time = current_time - kExpiryGracePeriod;
status = IsTokenValid(*trial_token, origin, third_party_origins,
validated_time);
if (status == OriginTrialTokenStatus::kSuccess) {
break;
}
}
}
} else {
status = OriginTrialTokenStatus::kWrongOrigin;
}
} else {
status = trial_token->IsValid(origin, current_time);
}

if (status != OriginTrialTokenStatus::kSuccess)
Expand Down
Expand Up @@ -170,6 +170,49 @@ const char kUsageSubsetToken[] =
"MiLCAidXNhZ2UiOiAic3Vic2V0IiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlVGhpcmRQYXJ0eSIs"
"ICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==";

// Well-formed token for a feature with an expiry grace period.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com FrobulateExpiryGracePeriod
// --expire-timestamp=2000000000
const char kExpiryGracePeriodToken[] =
"A2AVLsM2Set66KCwTfxH1ni9v8Jcs685qHKDLGam1LmpvnJE9GhYQwbLid3Xlqs/"
"2Em2HBp8CMZlj11Qk6R06QUAAABqeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLm"
"NvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGVFeHBpcnlHcmFjZVBlcmlvZCIsICJleHBp"
"cnkiOiAyMDAwMDAwMDAwfQ==";

// Well-formed token for match against third party origins and a feature with an
// expiry grace period.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com FrobulateExpiryGracePeriod
// --is-third-party --expire-timestamp=2000000000
const char kExpiryGracePeriodThirdPartyToken[] =
"A3wCXDPU5jfARV5KUetX5PI46W41gAbndIZKA7mKrTy6WyXoGFavV+"
"vBZejzC2D3Ffti4thz0AOMP+K/"
"oWxUvA8AAACAeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0NDMiLCAiZm"
"VhdHVyZSI6ICJGcm9idWxhdGVFeHBpcnlHcmFjZVBlcmlvZCIsICJleHBpcnkiOiAyMDAwMDAw"
"MDAwLCAiaXNUaGlyZFBhcnR5IjogdHJ1ZX0=";

// Well-formed token, with an unknown feature name.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Grokalyze
// --expire-timestamp=2000000000
const char kUnknownFeatureToken[] =
"AxjosEuqWyp9mrBFMOHJtO84YyY4QYuJ6TUNBMVzKMUWPE+B7Nwg2kgZKGO+"
"85m0bG0vWEs4m53TWtO1LNf0RgsAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcG"
"xlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJHcm9rYWx5emUiLCAiZXhwaXJ5IjogMjAwMDAwMDAw"
"MH0=";

// Well-formed token for match against third party origins, with an unknown
// feature name. Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Grokalyze
// --is-third-party --expire-timestamp=2000000000
const char kUnknownFeatureThirdPartyToken[] =
"A7BJkSTbLJ8/EM61BwStBGK3+hAnss/"
"fmvpkRmuGuBssyEKczr0iqmj4J3hvRM+"
"WzjotyzFopeNLSNU6FGlFZwMAAABveyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlL"
"mNvbTo0NDMiLCAiZmVhdHVyZSI6ICJHcm9rYWx5emUiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMCw"
"gImlzVGhpcmRQYXJ0eSI6IHRydWV9";

// 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 @@ -533,4 +576,69 @@ TEST_F(TrialTokenValidatorTest, ValidateRequestMultipleHeaderValues) {
kAppropriateFeatureName, Now()));
}

TEST_F(TrialTokenValidatorTest, ValidateValidExpiryGraceToken) {
// This token is valid one day before the end of the expiry grace period,
// even though it is past the token's expiry time.
auto current_time =
kSampleTokenExpiryTime + kExpiryGracePeriod - base::Days(1);
TrialTokenResult result = validator_.ValidateToken(
kExpiryGracePeriodToken, appropriate_origin_, current_time);
EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess);
EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time());
}

TEST_F(TrialTokenValidatorTest, ValidateExpiredExpiryGraceToken) {
// This token is expired at the end of the expiry grace period.
auto current_time = kSampleTokenExpiryTime + kExpiryGracePeriod;
TrialTokenResult result = validator_.ValidateToken(
kExpiryGracePeriodToken, appropriate_origin_, current_time);
EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kExpired);
EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time());
}

TEST_F(TrialTokenValidatorTest, ValidateValidExpiryGraceThirdPartyToken) {
url::Origin third_party_origins[] = {appropriate_origin_};
// This token is valid one day before the end of the expiry grace period,
// even though it is past the token's expiry time.
auto current_time =
kSampleTokenExpiryTime + kExpiryGracePeriod - base::Days(1);
TrialTokenResult result = validator_.ValidateToken(
kExpiryGracePeriodThirdPartyToken, appropriate_origin_,
third_party_origins, current_time);
EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess);
EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time());
EXPECT_EQ(true, result.ParsedToken()->is_third_party());
}

TEST_F(TrialTokenValidatorTest, ValidateExpiredExpiryGraceThirdPartyToken) {
url::Origin third_party_origins[] = {appropriate_origin_};
// This token is expired at the end of the expiry grace period.
auto current_time = kSampleTokenExpiryTime + kExpiryGracePeriod;
TrialTokenResult result = validator_.ValidateToken(
kExpiryGracePeriodThirdPartyToken, appropriate_origin_,
third_party_origins, current_time);
EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kExpired);
EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time());
EXPECT_EQ(true, result.ParsedToken()->is_third_party());
}

TEST_F(TrialTokenValidatorTest, ValidateUnknownFeatureToken) {
TrialTokenResult result = validator_.ValidateToken(
kUnknownFeatureToken, appropriate_origin_, Now());
EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess);
EXPECT_EQ(kInappropriateFeatureName, result.ParsedToken()->feature_name());
EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time());
}

TEST_F(TrialTokenValidatorTest, ValidateUnknownFeatureThirdPartyToken) {
url::Origin third_party_origins[] = {appropriate_origin_};
TrialTokenResult result =
validator_.ValidateToken(kUnknownFeatureThirdPartyToken,
appropriate_origin_, third_party_origins, Now());
EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess);
EXPECT_EQ(kInappropriateFeatureName, result.ParsedToken()->feature_name());
EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time());
EXPECT_EQ(true, result.ParsedToken()->is_third_party());
}

} // namespace blink::trial_token_validator_unittest
4 changes: 4 additions & 0 deletions third_party/blink/public/common/origin_trials/origin_trials.h
Expand Up @@ -52,6 +52,10 @@ BLINK_COMMON_EXPORT bool FeatureEnabledForOS(OriginTrialFeature feature);
BLINK_COMMON_EXPORT bool FeatureEnabledForNavigation(
OriginTrialFeature feature);

// Returns true if |feature| has an expiry grace period.
BLINK_COMMON_EXPORT bool FeatureHasExpiryGracePeriod(
OriginTrialFeature feature);

} // namespace origin_trials

} // namespace blink
Expand Down
Expand Up @@ -26,6 +26,9 @@ namespace blink {
class OriginTrialPolicy;
class TrialTokenResult;

// The expiry grace period for origin trials that must be manually completed.
constexpr base::TimeDelta kExpiryGracePeriod = base::Days(30);

// TrialTokenValidator checks that a page's OriginTrial token enables a certain
// feature.
//
Expand Down
Expand Up @@ -1725,6 +1725,12 @@
},
// As above. Do not change this flag to stable, as it exists solely to
// generate code used by the origin trials sample API implementation.
{
name: "OriginTrialsSampleAPIExpiryGracePeriod",
origin_trial_feature_name: "FrobulateExpiryGracePeriod",
},
// As above. Do not change this flag to stable, as it exists solely to
// generate code used by the origin trials sample API implementation.
{
name: "OriginTrialsSampleAPIImplied",
origin_trial_feature_name: "FrobulateImplied",
Expand Down

0 comments on commit 34d95a1

Please sign in to comment.