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