1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/password_manager/core/browser/store_metrics_reporter.h"
6
7 #include "base/macros.h"
8 #include "base/memory/scoped_refptr.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/test/metrics/histogram_tester.h"
11 #include "components/password_manager/core/browser/mock_password_store.h"
12 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
13 #include "components/password_manager/core/browser/stub_password_manager_client.h"
14 #include "components/password_manager/core/browser/sync_username_test_base.h"
15 #include "components/password_manager/core/browser/test_password_store.h"
16 #include "components/password_manager/core/common/password_manager_pref_names.h"
17 #include "components/prefs/pref_registry_simple.h"
18 #include "components/prefs/testing_pref_service.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 using ::testing::Bool;
23 using ::testing::Range;
24 using ::testing::Return;
25
26 namespace password_manager {
27 namespace {
28
CreateForm(const std::string & signon_realm,const std::string & username,const std::string & password)29 PasswordForm CreateForm(const std::string& signon_realm,
30 const std::string& username,
31 const std::string& password) {
32 PasswordForm form;
33 form.signon_realm = signon_realm;
34 form.username_value = base::ASCIIToUTF16(username);
35 form.password_value = base::ASCIIToUTF16(password);
36 return form;
37 }
38
39 class MockPasswordManagerClient : public StubPasswordManagerClient {
40 public:
41 MOCK_CONST_METHOD0(GetProfilePasswordStore, PasswordStore*());
42 MOCK_CONST_METHOD0(GetAccountPasswordStore, PasswordStore*());
43 MOCK_CONST_METHOD0(GetPasswordSyncState, SyncState());
44 MOCK_CONST_METHOD0(IsUnderAdvancedProtection, bool());
45 };
46
47 class StoreMetricsReporterTest : public SyncUsernameTestBase {
48 public:
StoreMetricsReporterTest()49 StoreMetricsReporterTest() {
50 prefs_.registry()->RegisterBooleanPref(prefs::kCredentialsEnableService,
51 false);
52 prefs_.registry()->RegisterBooleanPref(prefs::kPasswordLeakDetectionEnabled,
53 false);
54 prefs_.registry()->RegisterBooleanPref(
55 password_manager::prefs::kWasAutoSignInFirstRunExperienceShown, false);
56 }
57
58 ~StoreMetricsReporterTest() override = default;
59
60 protected:
61 MockPasswordManagerClient client_;
62 TestingPrefServiceSimple prefs_;
63 DISALLOW_COPY_AND_ASSIGN(StoreMetricsReporterTest);
64 };
65
66 // The test fixture defines two tests, one that doesn't require a password store
67 // and one that does. Each of these tests depend on two boolean parameters,
68 // which are declared here. Each test then assigns the desired semantics to
69 // them.
70 class StoreMetricsReporterTestWithParams
71 : public StoreMetricsReporterTest,
72 public ::testing::WithParamInterface<std::tuple<bool, bool>> {};
73
74 // Test that store-independent metrics are reported correctly.
TEST_P(StoreMetricsReporterTestWithParams,StoreIndependentMetrics)75 TEST_P(StoreMetricsReporterTestWithParams, StoreIndependentMetrics) {
76 const bool password_manager_enabled = std::get<0>(GetParam());
77 const bool leak_detection_enabled = std::get<1>(GetParam());
78
79 prefs_.SetBoolean(password_manager::prefs::kCredentialsEnableService,
80 password_manager_enabled);
81 prefs_.SetBoolean(password_manager::prefs::kPasswordLeakDetectionEnabled,
82 leak_detection_enabled);
83 base::HistogramTester histogram_tester;
84 EXPECT_CALL(client_, GetProfilePasswordStore())
85 .WillRepeatedly(Return(nullptr));
86 StoreMetricsReporter reporter(&client_, sync_service(), identity_manager(),
87 &prefs_);
88
89 histogram_tester.ExpectUniqueSample("PasswordManager.Enabled",
90 password_manager_enabled, 1);
91 histogram_tester.ExpectUniqueSample("PasswordManager.LeakDetection.Enabled",
92 leak_detection_enabled, 1);
93 }
94
95 // Test that sync username and syncing state are passed correctly to the
96 // PasswordStore.
TEST_P(StoreMetricsReporterTestWithParams,StoreDependentMetrics)97 TEST_P(StoreMetricsReporterTestWithParams, StoreDependentMetrics) {
98 const bool syncing_with_passphrase = std::get<0>(GetParam());
99 const bool is_under_advanced_protection = std::get<1>(GetParam());
100
101 auto store = base::MakeRefCounted<MockPasswordStore>();
102 const auto sync_state = syncing_with_passphrase
103 ? password_manager::SYNCING_WITH_CUSTOM_PASSPHRASE
104 : password_manager::SYNCING_NORMAL_ENCRYPTION;
105 EXPECT_CALL(client_, GetPasswordSyncState())
106 .WillRepeatedly(Return(sync_state));
107 EXPECT_CALL(client_, GetProfilePasswordStore())
108 .WillRepeatedly(Return(store.get()));
109 EXPECT_CALL(client_, IsUnderAdvancedProtection())
110 .WillRepeatedly(Return(is_under_advanced_protection));
111 EXPECT_CALL(*store,
112 ReportMetrics("some.user@gmail.com", syncing_with_passphrase,
113 is_under_advanced_protection));
114 FakeSigninAs("some.user@gmail.com");
115
116 StoreMetricsReporter reporter(&client_, sync_service(), identity_manager(),
117 &prefs_);
118 store->ShutdownOnUIThread();
119 }
120
121 // A test that covers multi-store metrics, which are recorded by the
122 // StoreMetricsReporter directly.
TEST_F(StoreMetricsReporterTest,MultiStoreMetrics)123 TEST_F(StoreMetricsReporterTest, MultiStoreMetrics) {
124 auto profile_store =
125 base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
126 auto account_store =
127 base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
128 profile_store->Init(&prefs_);
129 account_store->Init(&prefs_);
130
131 EXPECT_CALL(client_, GetPasswordSyncState())
132 .WillRepeatedly(
133 Return(password_manager::ACCOUNT_PASSWORDS_ACTIVE_NORMAL_ENCRYPTION));
134 EXPECT_CALL(client_, IsUnderAdvancedProtection())
135 .WillRepeatedly(Return(false));
136 EXPECT_CALL(client_, GetProfilePasswordStore())
137 .WillRepeatedly(Return(profile_store.get()));
138 EXPECT_CALL(client_, GetAccountPasswordStore())
139 .WillRepeatedly(Return(account_store.get()));
140
141 const std::string kRealm1 = "https://example.com";
142 const std::string kRealm2 = "https://example2.com";
143
144 // Add test data to the profile store:
145 // - 3 credentials that don't exist in the account store
146 // - 1 credential that conflicts with the account store (exists there with the
147 // same username but different password)
148 // - 2 credentials with identical copies in the account store
149 // Note: In the implementation, the credentials are processed in alphabetical
150 // order of usernames. Choose usernames here so that some profile-store-only
151 // credentials end up at both the start and the end of the list, to make sure
152 // these cases are handled correctly.
153 profile_store->AddLogin(
154 CreateForm(kRealm1, "aprofileuser1", "aprofilepass1"));
155 profile_store->AddLogin(
156 CreateForm(kRealm1, "aprofileuser2", "aprofilepass2"));
157 profile_store->AddLogin(
158 CreateForm(kRealm1, "zprofileuser3", "zprofilepass3"));
159 profile_store->AddLogin(CreateForm(kRealm1, "conflictinguser", "localpass"));
160 profile_store->AddLogin(
161 CreateForm(kRealm1, "identicaluser1", "identicalpass1"));
162 profile_store->AddLogin(
163 CreateForm(kRealm1, "identicaluser2", "identicalpass2"));
164
165 // Add test data to the account store:
166 // - 2 credentials that don't exist in the account store
167 // - 1 credential that conflicts with the profile store (exists there with the
168 // same username but different password)
169 // - 2 credentials with identical copies in the profile store
170 account_store->AddLogin(CreateForm(kRealm1, "accountuser1", "accountpass1"));
171 account_store->AddLogin(
172 CreateForm(kRealm1, "zaccountuser2", "zaccountpass2"));
173 account_store->AddLogin(
174 CreateForm(kRealm1, "conflictinguser", "accountpass"));
175 account_store->AddLogin(
176 CreateForm(kRealm1, "identicaluser1", "identicalpass1"));
177 account_store->AddLogin(
178 CreateForm(kRealm1, "identicaluser2", "identicalpass2"));
179
180 // Finally, add one more identical credential to the profile store. However
181 // this one is on a different signon realm, so should be counted as just
182 // another (4th) credential that's missing in the account store.
183 profile_store->AddLogin(
184 CreateForm(kRealm2, "identicaluser1", "identicalpass1"));
185
186 for (bool opted_in : {false, true}) {
187 EXPECT_CALL(*client_.GetPasswordFeatureManager(),
188 IsOptedInForAccountStorage())
189 .WillRepeatedly(Return(opted_in));
190
191 base::HistogramTester histogram_tester;
192
193 StoreMetricsReporter reporter(&client_, sync_service(), identity_manager(),
194 &prefs_);
195 // Wait for the metrics to get reported. This is delayed by 30 seconds, and
196 // then involves queries to the stores, i.e. to background task runners.
197 FastForwardBy(base::TimeDelta::FromSeconds(30));
198 RunUntilIdle();
199
200 // The original version of the metrics (without "2") is still recorded, even
201 // if the user isn't opted in to the account storage.
202 histogram_tester.ExpectUniqueSample(
203 "PasswordManager.AccountStoreVsProfileStore.Additional", 2, 1);
204 histogram_tester.ExpectUniqueSample(
205 "PasswordManager.AccountStoreVsProfileStore.Missing", 4, 1);
206 histogram_tester.ExpectUniqueSample(
207 "PasswordManager.AccountStoreVsProfileStore.Identical", 2, 1);
208 histogram_tester.ExpectUniqueSample(
209 "PasswordManager.AccountStoreVsProfileStore.Conflicting", 1, 1);
210
211 // Version "2" of the metrics is only recorded if the user is opted in.
212 if (opted_in) {
213 histogram_tester.ExpectUniqueSample(
214 "PasswordManager.AccountStoreVsProfileStore2.Additional", 2, 1);
215 histogram_tester.ExpectUniqueSample(
216 "PasswordManager.AccountStoreVsProfileStore2.Missing", 4, 1);
217 histogram_tester.ExpectUniqueSample(
218 "PasswordManager.AccountStoreVsProfileStore2.Identical", 2, 1);
219 histogram_tester.ExpectUniqueSample(
220 "PasswordManager.AccountStoreVsProfileStore2.Conflicting", 1, 1);
221 } else {
222 histogram_tester.ExpectTotalCount(
223 "PasswordManager.AccountStoreVsProfileStore2.Additional", 0);
224 histogram_tester.ExpectTotalCount(
225 "PasswordManager.AccountStoreVsProfileStore2.Missing", 0);
226 histogram_tester.ExpectTotalCount(
227 "PasswordManager.AccountStoreVsProfileStore2.Identical", 0);
228 histogram_tester.ExpectTotalCount(
229 "PasswordManager.AccountStoreVsProfileStore2.Conflicting", 0);
230 }
231 }
232
233 account_store->ShutdownOnUIThread();
234 profile_store->ShutdownOnUIThread();
235 // Make sure the PasswordStore destruction parts on the background sequence
236 // finish, otherwise we get memory leak reports.
237 RunUntilIdle();
238 }
239
240 INSTANTIATE_TEST_SUITE_P(All,
241 StoreMetricsReporterTestWithParams,
242 testing::Combine(Bool(), Bool()));
243 } // namespace
244 } // namespace password_manager
245