Index: components/password_manager/core/browser/password_store_unittest.cc |
diff --git a/components/password_manager/core/browser/password_store_unittest.cc b/components/password_manager/core/browser/password_store_unittest.cc |
index f5e2a14eed75bd8d2bc520bbbbf70fc7375dd8c8..b4aac9e57dcae75010900339efb523e5b307f627 100644 |
--- a/components/password_manager/core/browser/password_store_unittest.cc |
+++ b/components/password_manager/core/browser/password_store_unittest.cc |
@@ -12,6 +12,7 @@ |
#include "base/files/scoped_temp_dir.h" |
#include "base/stl_util.h" |
#include "base/strings/string_util.h" |
+#include "base/strings/utf_string_conversions.h" |
#include "base/synchronization/waitable_event.h" |
#include "base/time/time.h" |
#include "components/password_manager/core/browser/affiliated_match_helper.h" |
@@ -36,6 +37,18 @@ const char kTestWebRealm1[] = "https://one.example.com/"; |
const char kTestWebOrigin1[] = "https://one.example.com/origin"; |
const char kTestWebRealm2[] = "https://two.example.com/"; |
const char kTestWebOrigin2[] = "https://two.example.com/origin"; |
+const char kTestWebRealm3[] = "https://three.example.com/"; |
+const char kTestWebOrigin3[] = "https://three.example.com/origin"; |
+const char kTestWebRealm4[] = "https://four.example.com/"; |
+const char kTestWebOrigin4[] = "https://four.example.com/origin"; |
+const char kTestWebRealm5[] = "https://five.example.com/"; |
+const char kTestWebOrigin5[] = "https://five.example.com/origin"; |
+const char kTestPSLMatchingWebRealm[] = "https://psl.example.com/"; |
+const char kTestPSLMatchingWebOrigin[] = "https://psl.example.com/origin"; |
+const char kTestUnrelatedWebRealm[] = "https://notexample.com/"; |
+const char kTestUnrelatedWebOrigin[] = "https:/notexample.com/origin"; |
+const char kTestInsecureWebRealm[] = "http://one.example.com/"; |
+const char kTestInsecureWebOrigin[] = "http://one.example.com/origin"; |
const char kTestAndroidRealm1[] = "android://hash@com.example.android/"; |
const char kTestAndroidRealm2[] = "android://hash@com.example.two.android/"; |
const char kTestAndroidRealm3[] = "android://hash@com.example.three.android/"; |
@@ -53,6 +66,13 @@ class MockPasswordStoreConsumer : public PasswordStoreConsumer { |
} |
}; |
+class MockPasswordStoreObserver |
+ : public password_manager::PasswordStore::Observer { |
+ public: |
+ MOCK_METHOD1(OnLoginsChanged, |
+ void(const password_manager::PasswordStoreChangeList& changes)); |
+}; |
+ |
class MockAffiliatedMatchHelper : public AffiliatedMatchHelper { |
public: |
MockAffiliatedMatchHelper() |
@@ -70,9 +90,21 @@ class MockAffiliatedMatchHelper : public AffiliatedMatchHelper { |
.WillOnce(testing::Return(results_to_return)); |
} |
+ // Expects GetAffiliatedWebRealms() to be called with the |
+ // |expected_android_form|, and will cause the result callback supplied to |
+ // GetAffiliatedWebRealms() to be invoked with |results_to_return|. |
+ void ExpectCallToGetAffiliatedWebRealms( |
+ const autofill::PasswordForm& expected_android_form, |
+ const std::vector<std::string>& results_to_return) { |
+ EXPECT_CALL(*this, OnGetAffiliatedWebRealmsCalled(expected_android_form)) |
+ .WillOnce(testing::Return(results_to_return)); |
+ } |
+ |
private: |
MOCK_METHOD1(OnGetAffiliatedAndroidRealmsCalled, |
std::vector<std::string>(const PasswordForm&)); |
+ MOCK_METHOD1(OnGetAffiliatedWebRealmsCalled, |
+ std::vector<std::string>(const PasswordForm&)); |
void GetAffiliatedAndroidRealms( |
const autofill::PasswordForm& observed_form, |
@@ -82,6 +114,14 @@ class MockAffiliatedMatchHelper : public AffiliatedMatchHelper { |
result_callback.Run(affiliated_android_realms); |
} |
+ void GetAffiliatedWebRealms( |
+ const autofill::PasswordForm& android_form, |
+ const AffiliatedRealmsCallback& result_callback) override { |
+ std::vector<std::string> affiliated_web_realms = |
+ OnGetAffiliatedWebRealmsCalled(android_form); |
+ result_callback.Run(affiliated_web_realms); |
+ } |
+ |
DISALLOW_COPY_AND_ASSIGN(MockAffiliatedMatchHelper); |
}; |
@@ -273,8 +313,8 @@ TEST_F(PasswordStoreTest, GetLoginsWithoutAffiliations) { |
L"", true, true, 1}, |
// Credential that is a PSL match of the observed form. |
{PasswordForm::SCHEME_HTML, |
- kTestWebRealm2, |
- kTestWebOrigin2, |
+ kTestPSLMatchingWebRealm, |
+ kTestPSLMatchingWebOrigin, |
"", L"", L"", L"", |
L"username_value_2", |
L"", true, true, 1}, |
@@ -349,8 +389,8 @@ TEST_F(PasswordStoreTest, GetLoginsWithAffiliations) { |
L"", true, true, 1}, |
// Credential that is a PSL match of the observed form. |
{PasswordForm::SCHEME_HTML, |
- kTestWebRealm2, |
- kTestWebOrigin2, |
+ kTestPSLMatchingWebRealm, |
+ kTestPSLMatchingWebOrigin, |
"", L"", L"", L"", |
L"username_value_2", |
L"", true, true, 1}, |
@@ -435,4 +475,208 @@ TEST_F(PasswordStoreTest, GetLoginsWithAffiliations) { |
base::MessageLoop::current()->RunUntilIdle(); |
} |
+// This test must use passwords, which are not stored on Mac, therefore the test |
+// is disabled on Mac. This should not be a huge issue as functionality in the |
+// platform-independent base class is tested. See also the file-level comment. |
+#if defined(OS_MACOSX) |
+#define MAYBE_UpdatePasswordsStoredForAffiliatedWebsites \ |
+ DISABLED_UpdatePasswordsStoredForAffiliatedWebsites |
+#else |
+#define MAYBE_UpdatePasswordsStoredForAffiliatedWebsites \ |
+ UpdatePasswordsStoredForAffiliatedWebsites |
+#endif |
+ |
+// When the password stored for an Android application is updated, credentials |
+// with the same username stored for affiliated web sites should also be updated |
+// automatically. |
+TEST_F(PasswordStoreTest, MAYBE_UpdatePasswordsStoredForAffiliatedWebsites) { |
+ const wchar_t kTestUsername[] = L"username_value_1"; |
+ const wchar_t kTestOtherUsername[] = L"username_value_2"; |
+ const wchar_t kTestOldPassword[] = L"old_password_value"; |
+ const wchar_t kTestNewPassword[] = L"new_password_value"; |
+ const wchar_t kTestOtherPassword[] = L"other_password_value"; |
+ |
+ /* clang-format off */ |
+ static const PasswordFormData kTestCredentials[] = { |
+ // The credential for the Android application that will be updated |
+ // explicitly so it can be tested if the changes are correctly propagated |
+ // to affiliated Web credentials. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestAndroidRealm1, |
+ "", "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, true, 2}, |
+ |
+ // --- Positive samples --- Credentials that the password update should be |
+ // automatically propagated to. |
+ |
+ // Credential for an affiliated web site with the same username. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestWebRealm1, |
+ kTestWebOrigin1, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, true, 1}, |
+ // Credential for another affiliated web site with the same username. |
+ // Although the password is different than the current/old password for |
+ // the Android application, it should be updated regardless. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestWebRealm2, |
+ kTestWebOrigin2, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOtherPassword, true, true, 1}, |
+ |
+ // --- Negative samples --- Credentials that the password update should |
+ // not be propagated to. |
+ |
+ // Credential for another affiliated web site, but one that already has |
+ // the new password. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestWebRealm3, |
+ kTestWebOrigin3, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestNewPassword, true, true, 1}, |
+ // Credential for another affiliated web site, but one that was saved |
+ // under insecure conditions. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestWebRealm4, |
+ kTestWebOrigin4, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, false, 1}, |
+ // Credential for the HTTP version of an affiliated web site. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestInsecureWebRealm, |
+ kTestInsecureWebOrigin, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, false, 1}, |
+ // Credential for an affiliated web site, but with a different username. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestWebRealm1, |
+ kTestWebOrigin1, |
+ "", L"", L"", L"", |
+ kTestOtherUsername, |
+ kTestOldPassword, true, true, 1}, |
+ // Credential for a web site that is a PSL match to a web sites affiliated |
+ // with the Android application. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestPSLMatchingWebRealm, |
+ kTestPSLMatchingWebOrigin, |
+ "poisoned", L"poisoned", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, true, 1}, |
+ // Credential for an unrelated web site. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestUnrelatedWebRealm, |
+ kTestUnrelatedWebOrigin, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, true, 1}, |
+ // Credential for an affiliated Android application. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestAndroidRealm2, |
+ "", "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, true, 1}, |
+ // Credential for an unrelated Android application. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestUnrelatedAndroidRealm, |
+ "", "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOldPassword, true, true, 1}, |
+ // Credential for an affiliated web site with the same username, but one |
+ // that was updated at the same time via Sync as the Android credential. |
+ {PasswordForm::SCHEME_HTML, |
+ kTestWebRealm5, |
+ kTestWebOrigin5, |
+ "", L"", L"", L"", |
+ kTestUsername, |
+ kTestOtherPassword, true, true, 2}}; |
+ /* clang-format on */ |
+ |
+ // The number of positive samples in |kTestCredentials|. |
+ const size_t kExpectedNumberOfUpdatedCredentials = 2u; |
+ |
+ const bool kTestRemoveAndAddLogin[] = {false, true}; |
+ for (bool test_remove_and_add_login : kTestRemoveAndAddLogin) { |
vasilii
2015/04/20 13:04:08
Why not making this a test parameter?
engedy
2015/04/20 13:50:43
The parameter only makes sense for this test, not
|
+ SCOPED_TRACE(test_remove_and_add_login); |
+ |
+ scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault( |
+ base::MessageLoopProxy::current(), base::MessageLoopProxy::current(), |
+ make_scoped_ptr(new LoginDatabase(test_login_db_file_path())))); |
+ store->Init(syncer::SyncableService::StartSyncFlare()); |
+ store->RemoveLoginsCreatedBetween(base::Time(), base::Time::Max()); |
+ |
+ // Set up the initial test data set. |
+ ScopedVector<PasswordForm> all_credentials; |
+ for (size_t i = 0; i < arraysize(kTestCredentials); ++i) { |
+ all_credentials.push_back( |
+ CreatePasswordFormFromDataForTesting(kTestCredentials[i])); |
+ all_credentials.back()->date_synced = |
+ all_credentials.back()->date_created; |
+ store->AddLogin(*all_credentials.back()); |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ } |
+ |
+ // The helper must be injected after the initial test data is set up, |
+ // otherwise it will already start propagating updates as new Android |
+ // credentials are added. |
+ MockAffiliatedMatchHelper* mock_helper = new MockAffiliatedMatchHelper; |
+ store->SetAffiliatedMatchHelper(make_scoped_ptr(mock_helper)); |
+ |
+ // Calculate how the correctly updated test data set should look like. |
+ ScopedVector<PasswordForm> expected_credentials_after_update; |
+ for (size_t i = 0; i < all_credentials.size(); ++i) { |
+ expected_credentials_after_update.push_back( |
+ new autofill::PasswordForm(*all_credentials[i])); |
+ if (i < 1 + kExpectedNumberOfUpdatedCredentials) { |
+ expected_credentials_after_update.back()->password_value = |
+ base::WideToUTF16(kTestNewPassword); |
+ } |
+ } |
+ |
+ std::vector<std::string> affiliated_web_realms; |
+ affiliated_web_realms.push_back(kTestWebRealm1); |
+ affiliated_web_realms.push_back(kTestWebRealm2); |
+ affiliated_web_realms.push_back(kTestWebRealm3); |
+ affiliated_web_realms.push_back(kTestWebRealm4); |
+ affiliated_web_realms.push_back(kTestWebRealm5); |
+ mock_helper->ExpectCallToGetAffiliatedWebRealms( |
+ *expected_credentials_after_update[0], affiliated_web_realms); |
+ |
+ // Explicitly update the Android credential, wait until things calm down, |
+ // then query all passwords and expect that: |
+ // 1.) The positive samples in |kTestCredentials| have the new password, |
+ // but the negative samples do not. |
+ // 2.) Change notifications are sent about the updates. Note that as the |
+ // test interacts with the (Update|Add)LoginSync methods, only the |
+ // derived changes should trigger notifications, the first one would |
+ // normally be trigger by Sync. |
+ MockPasswordStoreObserver mock_observer; |
+ store->AddObserver(&mock_observer); |
+ EXPECT_CALL(mock_observer, OnLoginsChanged(testing::SizeIs(1u))) |
+ .Times(kExpectedNumberOfUpdatedCredentials); |
+ if (test_remove_and_add_login) { |
+ store->RemoveLoginSync(*all_credentials[0]); |
+ store->AddLoginSync(*expected_credentials_after_update[0]); |
+ } else { |
+ store->UpdateLoginSync(*expected_credentials_after_update[0]); |
+ } |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ store->RemoveObserver(&mock_observer); |
+ |
+ MockPasswordStoreConsumer mock_consumer; |
+ EXPECT_CALL( |
+ mock_consumer, |
+ OnGetPasswordStoreResultsConstRef(UnorderedPasswordFormElementsAre( |
+ expected_credentials_after_update.get()))); |
+ store->GetAutofillableLogins(&mock_consumer); |
+ store->Shutdown(); |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ } |
+} |
+ |
} // namespace password_manager |