Commit 26cd1e86 authored by Karthika Pai's avatar Karthika Pai Committed by Commit Bot

Report low entropy source value in X-Client-Data

The low entropy source is reported as a variation ID offset from
3320978. For example, a low entropy source value of 10 is reported
as variation ID 3320988.

The low entropy source is a locally-generated value in the range of
0-7999 that's used by Chrome to derive the variation ids included
in the X-Client-Data header. Since other variation ids have been
selected based on this value, this does not expose any additional
entropy in the header given enough variation ids.

Note: 8000 possible values is under 2^13, therefore having 13+ A/B
variations would carry the same amount of entropy. A typical
Chrome client has more than 13 variation ids reported.

Adding this value allows for retrospective A/A tests for
validating that there's no existing bias between two randomized
groups of clients for a later A/B study.

Bug: 1134444
Change-Id: Ia1af76f469cb2e3e25b03884abc4974cec237977
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2461906
Commit-Queue: Karthika Pai <karthikapai@google.com>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarCaitlin Fischer <caitlinfischer@google.com>
Reviewed-by: default avatarAlexei Svitkine <asvitkine@chromium.org>
Cr-Commit-Position: refs/heads/master@{#825962}
parent b67a9afc
...@@ -32,13 +32,17 @@ ...@@ -32,13 +32,17 @@
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h" #include "chrome/test/base/ui_test_utils.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/network_session_configurator/common/network_switches.h" #include "components/network_session_configurator/common/network_switches.h"
#include "components/optimization_guide/optimization_guide_features.h" #include "components/optimization_guide/optimization_guide_features.h"
#include "components/optimization_guide/proto/hints.pb.h" #include "components/optimization_guide/proto/hints.pb.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_test_utils.h" #include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/variations/net/variations_http_headers.h" #include "components/variations/net/variations_http_headers.h"
#include "components/variations/proto/study.pb.h" #include "components/variations/proto/study.pb.h"
#include "components/variations/variations.mojom.h" #include "components/variations/variations.mojom.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_features.h" #include "components/variations/variations_features.h"
#include "components/variations/variations_ids_provider.h" #include "components/variations/variations_ids_provider.h"
#include "components/variations/variations_test_utils.h" #include "components/variations/variations_test_utils.h"
...@@ -166,7 +170,7 @@ class VariationsHttpHeadersBrowserTest : public InProcessBrowserTest { ...@@ -166,7 +170,7 @@ class VariationsHttpHeadersBrowserTest : public InProcessBrowserTest {
return it->second.find(header) != it->second.end(); return it->second.find(header) != it->second.end();
} }
// Returns the |header| recievced by |url| or nullopt if it hasn't been // Returns the |header| received by |url| or nullopt if it hasn't been
// received. Fails an EXPECT if |url| hasn't been observed. // received. Fails an EXPECT if |url| hasn't been observed.
base::Optional<std::string> GetReceivedHeader( base::Optional<std::string> GetReceivedHeader(
const GURL& url, const GURL& url,
...@@ -405,7 +409,7 @@ void CreateGoogleSignedInFieldTrial(variations::VariationID id) { ...@@ -405,7 +409,7 @@ void CreateGoogleSignedInFieldTrial(variations::VariationID id) {
variations::mojom::GoogleWebVisibility::FIRST_PARTY)); variations::mojom::GoogleWebVisibility::FIRST_PARTY));
} }
// Creates FieldTrials associatedd with the FIRST_PARTY IDCollectionKeys and // Creates FieldTrials associated with the FIRST_PARTY IDCollectionKeys and
// their corresponding ANY_CONTEXT keys. // their corresponding ANY_CONTEXT keys.
void CreateFieldTrialsWithDifferentVisibilities() { void CreateFieldTrialsWithDifferentVisibilities() {
scoped_refptr<base::FieldTrial> trial_1(variations::CreateTrialAndAssociateId( scoped_refptr<base::FieldTrial> trial_1(variations::CreateTrialAndAssociateId(
...@@ -587,6 +591,64 @@ IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, UserNotSignedIn) { ...@@ -587,6 +591,64 @@ IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, UserNotSignedIn) {
EXPECT_FALSE(base::Contains(ids_any_context, signed_in_id)); EXPECT_FALSE(base::Contains(ids_any_context, signed_in_id));
} }
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
PRE_CheckLowEntropySourceValue) {
// We use the PRE_ prefix mechanism to ensure that this test always runs
// before CheckLowEntropyValue(). None of the subclasses in the
// InProcessBrowserTest class allow us to set this pref early enough to be
// read by the variations code, which runs very early during the browser
// startup.
PrefService* local_state = g_browser_process->local_state();
local_state->SetInteger(metrics::prefs::kMetricsLowEntropySource, 5);
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
CheckLowEntropySourceValue) {
std::unique_ptr<const base::FieldTrial::EntropyProvider>
low_entropy_provider = g_browser_process->GetMetricsServicesManager()
->CreateEntropyProvider();
// Create a trial with 100 groups and variation ids to validate that the group
// reported in the variations header is actually based on the low entropy
// source.
//
// TODO(crbug.com/1146199): Refactor this so that creating the field trial
// either uses a different API or tighten the current API to set up a field
// trial that can only be made with the low entropy provider.
scoped_refptr<base::FieldTrial> trial =
base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
"t1", 100, "default", base::FieldTrial::ONE_TIME_RANDOMIZED, 0,
/*default_group_number=*/nullptr, low_entropy_provider.get());
for (int i = 1; i < 101; ++i) {
const std::string group_name = base::StringPrintf("%d", i);
variations::AssociateGoogleVariationID(
variations::GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, trial->trial_name(),
group_name, i);
trial->AppendGroup(group_name, 1);
}
// Activate the trial. This corresponds to ACTIVATE_ON_STARTUP for server-side
// studies.
trial->group();
ui_test_utils::NavigateToURL(browser(), GetGoogleUrl());
base::Optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_TRUE(header);
std::set<variations::VariationID> variation_ids;
std::set<variations::VariationID> trigger_ids;
ASSERT_TRUE(variations::ExtractVariationIds(header.value(), &variation_ids,
&trigger_ids));
// 3320983 is the offset value of kLowEntropySourceVariationIdRangeMin + 5.
EXPECT_TRUE(base::Contains(variation_ids, 3320983));
// Check that the reported group in the header is consistent with the low
// entropy source. 33 is the group that is derived from the low entropy source
// value of 5.
EXPECT_TRUE(base::Contains(variation_ids, 33));
}
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
VariationsHttpHeadersBrowserTest, VariationsHttpHeadersBrowserTest,
VariationsHttpHeadersBrowserTestWithRestrictedVisibility, VariationsHttpHeadersBrowserTestWithRestrictedVisibility,
......
...@@ -18,6 +18,11 @@ ...@@ -18,6 +18,11 @@
namespace variations { namespace variations {
// Range of low entropy source values (8000) as variation ids for the
// X-Client-Data header. This range is reserved in cl/333331461 (internal CL).
const int kLowEntropySourceVariationIdRangeMin = 3320978;
const int kLowEntropySourceVariationIdRangeMax = 3328977;
bool VariationsHeaderKey::operator<(const VariationsHeaderKey& other) const { bool VariationsHeaderKey::operator<(const VariationsHeaderKey& other) const {
if (is_signed_in != other.is_signed_in) if (is_signed_in != other.is_signed_in)
return is_signed_in < other.is_signed_in; return is_signed_in < other.is_signed_in;
...@@ -116,8 +121,8 @@ void VariationsIdsProvider::SetLowEntropySourceValue( ...@@ -116,8 +121,8 @@ void VariationsIdsProvider::SetLowEntropySourceValue(
// inclusive. See components/metrics/metrics_state_manager.cc for the logic to // inclusive. See components/metrics/metrics_state_manager.cc for the logic to
// generate it. // generate it.
if (low_entropy_source_value) { if (low_entropy_source_value) {
DCHECK(low_entropy_source_value.value() >= 0 && DCHECK_GE(low_entropy_source_value.value(), 0);
low_entropy_source_value.value() <= 7999); DCHECK_LE(low_entropy_source_value.value(), 7999);
} }
low_entropy_source_value_ = low_entropy_source_value; low_entropy_source_value_ = low_entropy_source_value;
} }
...@@ -435,6 +440,27 @@ VariationsIdsProvider::GetAllVariationIds() { ...@@ -435,6 +440,27 @@ VariationsIdsProvider::GetAllVariationIds() {
for (const VariationIDEntry& entry : force_disabled_ids_set_) { for (const VariationIDEntry& entry : force_disabled_ids_set_) {
all_variation_ids_set.erase(entry); all_variation_ids_set.erase(entry);
} }
// Add the low entropy source value, if it exists, which has one of
// 8000 possible values (between kLowEntropySourceVariationIdRange[Min/Max],
// ~13 bits). This is the value that has been used for deriving the variation
// ids included in the X-Client-Data header and therefore does not reveal
// additional information about the client when there are more than 13
// variations. A typical Chrome client has more than 13 variation ids
// reported.
//
// The entropy source value is used for retrospective A/A tests to validate
// that there's no existing bias between two randomized groups of clients for
// a later A/B study.
if (low_entropy_source_value_) {
int source_value = low_entropy_source_value_.value() +
kLowEntropySourceVariationIdRangeMin;
DCHECK_GE(source_value, kLowEntropySourceVariationIdRangeMin);
DCHECK_LE(source_value, kLowEntropySourceVariationIdRangeMax);
all_variation_ids_set.insert(
VariationIDEntry(source_value, GOOGLE_WEB_PROPERTIES_FIRST_PARTY));
}
return all_variation_ids_set; return all_variation_ids_set;
} }
......
...@@ -146,6 +146,10 @@ class VariationsIdsProvider : public base::FieldTrialList::Observer, ...@@ -146,6 +146,10 @@ class VariationsIdsProvider : public base::FieldTrialList::Observer,
ForceDisableVariationIds_Invalid); ForceDisableVariationIds_Invalid);
FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTestWithRestrictedVisibility, FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTestWithRestrictedVisibility,
OnFieldTrialGroupFinalized); OnFieldTrialGroupFinalized);
FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTestWithRestrictedVisibility,
LowEntropySourceValue_Valid);
FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTestWithRestrictedVisibility,
LowEntropySourceValue_Null);
FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest,
GetGoogleAppVariationsString); GetGoogleAppVariationsString);
FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, GetVariationsString); FRIEND_TEST_ALL_PREFIXES(VariationsIdsProviderTest, GetVariationsString);
......
...@@ -159,6 +159,88 @@ INSTANTIATE_TEST_SUITE_P(All, ...@@ -159,6 +159,88 @@ INSTANTIATE_TEST_SUITE_P(All,
VariationsIdsProviderTestWithRestrictedVisibility, VariationsIdsProviderTestWithRestrictedVisibility,
::testing::Bool()); ::testing::Bool());
TEST_P(VariationsIdsProviderTestWithRestrictedVisibility,
LowEntropySourceValue_Valid) {
VariationsIdsProvider provider;
base::Optional<int> valid_low_entropy_source_value = 5;
provider.SetLowEntropySourceValue(valid_low_entropy_source_value);
provider.InitVariationIDsCacheIfNeeded();
variations::mojom::VariationsHeadersPtr headers =
provider.GetClientDataHeaders(/*is_signed_in=*/false);
EXPECT_FALSE(headers->headers_map.empty());
const std::string variations_header_first_party = headers->headers_map.at(
variations::mojom::GoogleWebVisibility::FIRST_PARTY);
const std::string variations_header_any_context =
headers->headers_map.at(variations::mojom::GoogleWebVisibility::ANY);
std::set<VariationID> variation_ids_first_party;
std::set<VariationID> trigger_ids_first_party;
ASSERT_TRUE(ExtractVariationIds(variations_header_first_party,
&variation_ids_first_party,
&trigger_ids_first_party));
std::set<VariationID> variation_ids_any_context;
std::set<VariationID> trigger_ids_any_context;
ASSERT_TRUE(ExtractVariationIds(variations_header_any_context,
&variation_ids_any_context,
&trigger_ids_any_context));
// 3320983 is the offset value of kLowEntropySourceVariationIdRangeMin + 5.
EXPECT_TRUE(base::Contains(variation_ids_first_party, 3320983));
// The value will be omitted from third-party contexts under
// kRestrictGoogleWebVisibility.
bool value_omitted =
base::FeatureList::IsEnabled(internal::kRestrictGoogleWebVisibility);
EXPECT_EQ(value_omitted, !base::Contains(variation_ids_any_context, 3320983));
}
TEST_P(VariationsIdsProviderTestWithRestrictedVisibility,
LowEntropySourceValue_Null) {
VariationsIdsProvider provider;
base::Optional<int> null_low_entropy_source_value = base::nullopt;
provider.SetLowEntropySourceValue(null_low_entropy_source_value);
// Valid experiment ids.
CreateTrialAndAssociateId("t1", "g1", GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, 12);
CreateTrialAndAssociateId("t2", "g2", GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, 456);
provider.InitVariationIDsCacheIfNeeded();
variations::mojom::VariationsHeadersPtr headers =
provider.GetClientDataHeaders(/*is_signed_in=*/false);
EXPECT_FALSE(headers->headers_map.empty());
const std::string variations_header_first_party = headers->headers_map.at(
variations::mojom::GoogleWebVisibility::FIRST_PARTY);
const std::string variations_header_any_context =
headers->headers_map.at(variations::mojom::GoogleWebVisibility::ANY);
std::set<VariationID> variation_ids_first_party;
std::set<VariationID> trigger_ids_first_party;
ASSERT_TRUE(ExtractVariationIds(variations_header_first_party,
&variation_ids_first_party,
&trigger_ids_first_party));
std::set<VariationID> variation_ids_any_context;
std::set<VariationID> trigger_ids_any_context;
ASSERT_TRUE(ExtractVariationIds(variations_header_any_context,
&variation_ids_any_context,
&trigger_ids_any_context));
// We test to make sure that only two valid variation IDs are present and that
// the low entropy source value is not added to the sets.
EXPECT_TRUE(base::Contains(variation_ids_first_party, 12));
EXPECT_TRUE(base::Contains(variation_ids_first_party, 456));
EXPECT_FALSE(base::Contains(variation_ids_first_party, 3320983));
EXPECT_TRUE(base::Contains(variation_ids_any_context, 12));
EXPECT_TRUE(base::Contains(variation_ids_any_context, 456));
EXPECT_FALSE(base::Contains(variation_ids_any_context, 3320983));
// Check to make sure that no other variation IDs are present.
EXPECT_EQ(2U, variation_ids_first_party.size());
EXPECT_EQ(2U, variation_ids_any_context.size());
}
TEST_P(VariationsIdsProviderTestWithRestrictedVisibility, TEST_P(VariationsIdsProviderTestWithRestrictedVisibility,
OnFieldTrialGroupFinalized) { OnFieldTrialGroupFinalized) {
VariationsIdsProvider provider; VariationsIdsProvider provider;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment