| // Copyright 2014 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. |
| |
| #include "components/password_manager/content/browser/credential_manager_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "components/password_manager/core/browser/credential_manager_password_form_manager.h" |
| #include "components/password_manager/core/browser/mock_affiliated_match_helper.h" |
| #include "components/password_manager/core/browser/password_manager.h" |
| #include "components/password_manager/core/browser/stub_password_manager_client.h" |
| #include "components/password_manager/core/browser/stub_password_manager_driver.h" |
| #include "components/password_manager/core/browser/test_password_store.h" |
| #include "components/password_manager/core/common/credential_manager_types.h" |
| #include "components/password_manager/core/common/password_manager_pref_names.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| using content::BrowserContext; |
| using content::WebContents; |
| |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::Pointee; |
| using testing::UnorderedElementsAre; |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| const char kTestWebOrigin[] = "https://example.com/"; |
| const char kTestAndroidRealm1[] = "android://hash@com.example.one.android/"; |
| const char kTestAndroidRealm2[] = "android://hash@com.example.two.android/"; |
| |
| class MockPasswordManagerClient : public StubPasswordManagerClient { |
| public: |
| MOCK_CONST_METHOD0(IsSavingAndFillingEnabledForCurrentPage, bool()); |
| MOCK_CONST_METHOD0(IsFillingEnabledForCurrentPage, bool()); |
| MOCK_METHOD0(OnCredentialManagerUsed, bool()); |
| MOCK_CONST_METHOD0(IsIncognito, bool()); |
| MOCK_METHOD0(NotifyUserAutoSigninPtr, bool()); |
| MOCK_METHOD1(NotifyUserCouldBeAutoSignedInPtr, |
| bool(autofill::PasswordForm* form)); |
| MOCK_METHOD0(NotifyStorePasswordCalled, void()); |
| MOCK_METHOD1(PromptUserToSavePasswordPtr, void(PasswordFormManager*)); |
| MOCK_METHOD3(PromptUserToChooseCredentialsPtr, |
| bool(const std::vector<autofill::PasswordForm*>& local_forms, |
| const GURL& origin, |
| const CredentialsCallback& callback)); |
| |
| explicit MockPasswordManagerClient(PasswordStore* store) : store_(store) { |
| prefs_.registry()->RegisterBooleanPref(prefs::kCredentialsEnableAutosignin, |
| true); |
| prefs_.registry()->RegisterBooleanPref( |
| prefs::kWasAutoSignInFirstRunExperienceShown, true); |
| } |
| ~MockPasswordManagerClient() override {} |
| |
| bool PromptUserToSaveOrUpdatePassword( |
| std::unique_ptr<PasswordFormManager> manager, |
| bool update_password) override { |
| manager_.swap(manager); |
| PromptUserToSavePasswordPtr(manager_.get()); |
| return true; |
| } |
| |
| void NotifyUserCouldBeAutoSignedIn( |
| std::unique_ptr<autofill::PasswordForm> form) override { |
| NotifyUserCouldBeAutoSignedInPtr(form.get()); |
| } |
| |
| PasswordStore* GetPasswordStore() const override { return store_; } |
| |
| PrefService* GetPrefs() override { return &prefs_; } |
| |
| bool PromptUserToChooseCredentials( |
| std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, |
| const GURL& origin, |
| const CredentialsCallback& callback) override { |
| EXPECT_FALSE(local_forms.empty()); |
| const autofill::PasswordForm* form = local_forms[0].get(); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, base::Owned(new autofill::PasswordForm(*form)))); |
| std::vector<autofill::PasswordForm*> raw_forms(local_forms.size()); |
| std::transform(local_forms.begin(), local_forms.end(), raw_forms.begin(), |
| [](const std::unique_ptr<autofill::PasswordForm>& form) { |
| return form.get(); |
| }); |
| PromptUserToChooseCredentialsPtr(raw_forms, origin, callback); |
| return true; |
| } |
| |
| void NotifyUserAutoSignin( |
| std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, |
| const GURL& origin) override { |
| EXPECT_FALSE(local_forms.empty()); |
| NotifyUserAutoSigninPtr(); |
| } |
| |
| PasswordFormManager* pending_manager() const { return manager_.get(); } |
| |
| void set_zero_click_enabled(bool zero_click_enabled) { |
| prefs_.SetBoolean(prefs::kCredentialsEnableAutosignin, zero_click_enabled); |
| } |
| |
| void set_first_run_seen(bool first_run_seen) { |
| prefs_.SetBoolean(prefs::kWasAutoSignInFirstRunExperienceShown, |
| first_run_seen); |
| } |
| |
| private: |
| TestingPrefServiceSimple prefs_; |
| PasswordStore* store_; |
| std::unique_ptr<PasswordFormManager> manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient); |
| }; |
| |
| class TestCredentialManagerImpl : public CredentialManagerImpl { |
| public: |
| TestCredentialManagerImpl(content::WebContents* web_contents, |
| PasswordManagerClient* client, |
| PasswordManagerDriver* driver); |
| |
| private: |
| base::WeakPtr<PasswordManagerDriver> GetDriver() override; |
| |
| base::WeakPtr<PasswordManagerDriver> driver_; |
| }; |
| |
| TestCredentialManagerImpl::TestCredentialManagerImpl( |
| content::WebContents* web_contents, |
| PasswordManagerClient* client, |
| PasswordManagerDriver* driver) |
| : CredentialManagerImpl(web_contents, client), |
| driver_(driver->AsWeakPtr()) {} |
| |
| base::WeakPtr<PasswordManagerDriver> TestCredentialManagerImpl::GetDriver() { |
| return driver_; |
| } |
| |
| void RunAllPendingTasks() { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); |
| run_loop.Run(); |
| } |
| |
| class SlightlyLessStubbyPasswordManagerDriver |
| : public StubPasswordManagerDriver { |
| public: |
| explicit SlightlyLessStubbyPasswordManagerDriver( |
| PasswordManagerClient* client) |
| : password_manager_(client) {} |
| |
| PasswordManager* GetPasswordManager() override { return &password_manager_; } |
| |
| private: |
| PasswordManager password_manager_; |
| }; |
| |
| // Callbacks from CredentialManagerImpl methods |
| void RespondCallback(bool* called) { |
| *called = true; |
| } |
| |
| void GetCredentialCallback(bool* called, |
| mojom::CredentialManagerError* out_error, |
| base::Optional<CredentialInfo>* out_info, |
| mojom::CredentialManagerError error, |
| const base::Optional<CredentialInfo>& info) { |
| *called = true; |
| *out_error = error; |
| *out_info = info; |
| } |
| |
| GURL HttpURLFromHttps(const GURL& https_url) { |
| GURL::Replacements rep; |
| rep.SetSchemeStr(url::kHttpScheme); |
| return https_url.ReplaceComponents(rep); |
| } |
| |
| } // namespace |
| |
| class CredentialManagerImplTest : public content::RenderViewHostTestHarness { |
| public: |
| CredentialManagerImplTest() {} |
| |
| void SetUp() override { |
| content::RenderViewHostTestHarness::SetUp(); |
| store_ = new TestPasswordStore; |
| client_.reset( |
| new testing::NiceMock<MockPasswordManagerClient>(store_.get())); |
| stub_driver_.reset( |
| new SlightlyLessStubbyPasswordManagerDriver(client_.get())); |
| cm_service_impl_.reset(new TestCredentialManagerImpl( |
| web_contents(), client_.get(), stub_driver_.get())); |
| ON_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillByDefault(testing::Return(true)); |
| ON_CALL(*client_, IsFillingEnabledForCurrentPage()) |
| .WillByDefault(testing::Return(true)); |
| ON_CALL(*client_, OnCredentialManagerUsed()) |
| .WillByDefault(testing::Return(true)); |
| ON_CALL(*client_, IsIncognito()).WillByDefault(testing::Return(false)); |
| |
| NavigateAndCommit(GURL("https://example.com/test.html")); |
| |
| form_.username_value = base::ASCIIToUTF16("Username"); |
| form_.display_name = base::ASCIIToUTF16("Display Name"); |
| form_.icon_url = GURL("https://example.com/icon.png"); |
| form_.password_value = base::ASCIIToUTF16("Password"); |
| form_.origin = web_contents()->GetLastCommittedURL().GetOrigin(); |
| form_.signon_realm = form_.origin.GetOrigin().spec(); |
| form_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| form_.skip_zero_click = false; |
| |
| affiliated_form1_.username_value = base::ASCIIToUTF16("Affiliated 1"); |
| affiliated_form1_.display_name = base::ASCIIToUTF16("Display Name"); |
| affiliated_form1_.password_value = base::ASCIIToUTF16("Password"); |
| affiliated_form1_.origin = GURL(); |
| affiliated_form1_.signon_realm = kTestAndroidRealm1; |
| affiliated_form1_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| affiliated_form1_.skip_zero_click = false; |
| |
| affiliated_form2_.username_value = base::ASCIIToUTF16("Affiliated 2"); |
| affiliated_form2_.display_name = base::ASCIIToUTF16("Display Name"); |
| affiliated_form2_.password_value = base::ASCIIToUTF16("Password"); |
| affiliated_form2_.origin = GURL(); |
| affiliated_form2_.signon_realm = kTestAndroidRealm2; |
| affiliated_form2_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| affiliated_form2_.skip_zero_click = false; |
| |
| origin_path_form_.username_value = base::ASCIIToUTF16("Username 2"); |
| origin_path_form_.display_name = base::ASCIIToUTF16("Display Name 2"); |
| origin_path_form_.password_value = base::ASCIIToUTF16("Password 2"); |
| origin_path_form_.origin = GURL("https://example.com/path"); |
| origin_path_form_.signon_realm = |
| origin_path_form_.origin.GetOrigin().spec(); |
| origin_path_form_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| origin_path_form_.skip_zero_click = false; |
| |
| subdomain_form_.username_value = base::ASCIIToUTF16("Username 2"); |
| subdomain_form_.display_name = base::ASCIIToUTF16("Display Name 2"); |
| subdomain_form_.password_value = base::ASCIIToUTF16("Password 2"); |
| subdomain_form_.origin = GURL("https://subdomain.example.com/path"); |
| subdomain_form_.signon_realm = subdomain_form_.origin.GetOrigin().spec(); |
| subdomain_form_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| subdomain_form_.skip_zero_click = false; |
| |
| cross_origin_form_.username_value = base::ASCIIToUTF16("Username"); |
| cross_origin_form_.display_name = base::ASCIIToUTF16("Display Name"); |
| cross_origin_form_.password_value = base::ASCIIToUTF16("Password"); |
| cross_origin_form_.origin = GURL("https://example.net/"); |
| cross_origin_form_.signon_realm = |
| cross_origin_form_.origin.GetOrigin().spec(); |
| cross_origin_form_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| cross_origin_form_.skip_zero_click = false; |
| |
| store_->Clear(); |
| EXPECT_TRUE(store_->IsEmpty()); |
| } |
| |
| void TearDown() override { |
| cm_service_impl_.reset(); |
| |
| store_->ShutdownOnUIThread(); |
| content::RenderViewHostTestHarness::TearDown(); |
| } |
| |
| void ExpectZeroClickSignInFailure(CredentialMediationRequirement mediation, |
| bool include_passwords, |
| const std::vector<GURL>& federations) { |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| CallGet(mediation, include_passwords, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error); |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, credential->type); |
| } |
| |
| void ExpectZeroClickSignInSuccess(CredentialMediationRequirement mediation, |
| bool include_passwords, |
| const std::vector<GURL>& federations, |
| CredentialType type) { |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(1)); |
| CallGet(mediation, include_passwords, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error); |
| EXPECT_EQ(type, credential->type); |
| } |
| |
| void ExpectCredentialType(CredentialMediationRequirement mediation, |
| bool include_passwords, |
| const std::vector<GURL>& federations, |
| CredentialType type) { |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| CallGet(mediation, include_passwords, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error); |
| EXPECT_EQ(type, credential->type); |
| } |
| |
| CredentialManagerImpl* cm_service_impl() { return cm_service_impl_.get(); } |
| |
| // Helpers for testing CredentialManagerImpl methods. |
| void CallStore(const CredentialInfo& info, |
| CredentialManagerImpl::StoreCallback callback) { |
| cm_service_impl_->Store(info, std::move(callback)); |
| } |
| |
| void CallPreventSilentAccess( |
| CredentialManagerImpl::PreventSilentAccessCallback callback) { |
| cm_service_impl_->PreventSilentAccess(std::move(callback)); |
| } |
| |
| void CallGet(CredentialMediationRequirement mediation, |
| bool include_passwords, |
| const std::vector<GURL>& federations, |
| CredentialManagerImpl::GetCallback callback) { |
| cm_service_impl_->Get(mediation, include_passwords, federations, |
| std::move(callback)); |
| } |
| |
| protected: |
| autofill::PasswordForm form_; |
| autofill::PasswordForm affiliated_form1_; |
| autofill::PasswordForm affiliated_form2_; |
| autofill::PasswordForm origin_path_form_; |
| autofill::PasswordForm subdomain_form_; |
| autofill::PasswordForm cross_origin_form_; |
| scoped_refptr<TestPasswordStore> store_; |
| std::unique_ptr<testing::NiceMock<MockPasswordManagerClient>> client_; |
| std::unique_ptr<SlightlyLessStubbyPasswordManagerDriver> stub_driver_; |
| std::unique_ptr<CredentialManagerImpl> cm_service_impl_; |
| }; |
| |
| TEST_F(CredentialManagerImplTest, IsZeroClickAllowed) { |
| // IsZeroClickAllowed is uneffected by the first-run status. |
| client_->set_zero_click_enabled(true); |
| client_->set_first_run_seen(true); |
| EXPECT_TRUE(cm_service_impl()->IsZeroClickAllowed()); |
| |
| client_->set_zero_click_enabled(true); |
| client_->set_first_run_seen(false); |
| EXPECT_TRUE(cm_service_impl()->IsZeroClickAllowed()); |
| |
| client_->set_zero_click_enabled(false); |
| client_->set_first_run_seen(true); |
| EXPECT_FALSE(cm_service_impl()->IsZeroClickAllowed()); |
| |
| client_->set_zero_click_enabled(false); |
| client_->set_first_run_seen(false); |
| EXPECT_FALSE(cm_service_impl()->IsZeroClickAllowed()); |
| } |
| |
| TEST_F(CredentialManagerImplTest, CredentialManagerOnStore) { |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // that the form is new, and set it as pending. |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(FormFetcher::State::NOT_WAITING, |
| client_->pending_manager()->form_fetcher()->GetState()); |
| |
| autofill::PasswordForm new_form = |
| client_->pending_manager()->pending_credentials(); |
| EXPECT_EQ(form_.username_value, new_form.username_value); |
| EXPECT_EQ(form_.display_name, new_form.display_name); |
| EXPECT_EQ(form_.password_value, new_form.password_value); |
| EXPECT_EQ(form_.origin, new_form.origin); |
| EXPECT_EQ(form_.signon_realm, new_form.signon_realm); |
| EXPECT_TRUE(new_form.federation_origin.unique()); |
| EXPECT_EQ(form_.icon_url, new_form.icon_url); |
| EXPECT_FALSE(form_.skip_zero_click); |
| EXPECT_EQ(autofill::PasswordForm::SCHEME_HTML, new_form.scheme); |
| } |
| |
| TEST_F(CredentialManagerImplTest, CredentialManagerOnStoreFederated) { |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| |
| bool called = false; |
| form_.federation_origin = url::Origin(GURL("https://google.com/")); |
| form_.password_value = base::string16(); |
| form_.signon_realm = "federation://example.com/google.com"; |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // that the form is new, and set it as pending. |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(FormFetcher::State::NOT_WAITING, |
| client_->pending_manager()->form_fetcher()->GetState()); |
| |
| autofill::PasswordForm new_form = |
| client_->pending_manager()->pending_credentials(); |
| EXPECT_EQ(form_.username_value, new_form.username_value); |
| EXPECT_EQ(form_.display_name, new_form.display_name); |
| EXPECT_EQ(form_.password_value, new_form.password_value); |
| EXPECT_EQ(form_.origin, new_form.origin); |
| EXPECT_EQ(form_.signon_realm, new_form.signon_realm); |
| EXPECT_EQ(form_.federation_origin, new_form.federation_origin); |
| EXPECT_EQ(form_.icon_url, new_form.icon_url); |
| EXPECT_FALSE(form_.skip_zero_click); |
| EXPECT_EQ(autofill::PasswordForm::SCHEME_HTML, new_form.scheme); |
| } |
| |
| TEST_F(CredentialManagerImplTest, StoreFederatedAfterPassword) { |
| // Populate the PasswordStore with a form. |
| store_->AddLogin(form_); |
| |
| autofill::PasswordForm federated = form_; |
| federated.password_value.clear(); |
| federated.type = autofill::PasswordForm::TYPE_API; |
| federated.preferred = true; |
| federated.federation_origin = url::Origin(GURL("https://google.com/")); |
| federated.signon_realm = "federation://example.com/google.com"; |
| CredentialInfo info(federated, CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // that the form is new, and set it as pending. |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(FormFetcher::State::NOT_WAITING, |
| client_->pending_manager()->form_fetcher()->GetState()); |
| client_->pending_manager()->Save(); |
| |
| RunAllPendingTasks(); |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_THAT(passwords["https://example.com/"], ElementsAre(form_)); |
| federated.date_created = |
| passwords["federation://example.com/google.com"][0].date_created; |
| EXPECT_THAT(passwords["federation://example.com/google.com"], |
| ElementsAre(federated)); |
| } |
| |
| TEST_F(CredentialManagerImplTest, CredentialManagerStoreOverwrite) { |
| // Populate the PasswordStore with a form. |
| store_->AddLogin(form_); |
| RunAllPendingTasks(); |
| |
| // Calling 'Store' with a credential that matches |form_| should update |
| // the password without prompting the user. |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| info.password = base::ASCIIToUTF16("Totally new password."); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)).Times(0); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // the form is a match for an existing form, and update the PasswordStore. |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(1U, passwords.size()); |
| EXPECT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_EQ(base::ASCIIToUTF16("Totally new password."), |
| passwords[form_.signon_realm][0].password_value); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerStorePSLMatchDoesNotTriggerBubble) { |
| autofill::PasswordForm psl_form = subdomain_form_; |
| psl_form.username_value = form_.username_value; |
| psl_form.password_value = form_.password_value; |
| store_->AddLogin(psl_form); |
| |
| // Calling 'Store' with a new credential that is a PSL match for an existing |
| // credential with identical username and password should result in a silent |
| // save without prompting the user. |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| RunAllPendingTasks(); |
| EXPECT_TRUE(called); |
| |
| // Check that both credentials are present in the password store. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(2U, passwords.size()); |
| EXPECT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[psl_form.signon_realm].size()); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerStorePSLMatchWithDifferentUsernameTriggersBubble) { |
| base::string16 delta = base::ASCIIToUTF16("_totally_different"); |
| autofill::PasswordForm psl_form = subdomain_form_; |
| psl_form.username_value = form_.username_value + delta; |
| psl_form.password_value = form_.password_value; |
| store_->AddLogin(psl_form); |
| |
| // Calling 'Store' with a new credential that is a PSL match for an existing |
| // credential but has a different username should prompt the user and not |
| // result in a silent save. |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| RunAllPendingTasks(); |
| EXPECT_TRUE(called); |
| |
| // Check that only the initial credential is present in the password store |
| // and the new one is still pending. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(1U, passwords.size()); |
| EXPECT_EQ(1U, passwords[psl_form.signon_realm].size()); |
| |
| const auto& pending_cred = client_->pending_manager()->pending_credentials(); |
| EXPECT_EQ(info.id, pending_cred.username_value); |
| EXPECT_EQ(info.password, pending_cred.password_value); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerStorePSLMatchWithDifferentPasswordTriggersBubble) { |
| base::string16 delta = base::ASCIIToUTF16("_totally_different"); |
| autofill::PasswordForm psl_form = subdomain_form_; |
| psl_form.username_value = form_.username_value; |
| psl_form.password_value = form_.password_value + delta; |
| store_->AddLogin(psl_form); |
| |
| // Calling 'Store' with a new credential that is a PSL match for an existing |
| // credential but has a different password should prompt the user and not |
| // result in a silent save. |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| RunAllPendingTasks(); |
| EXPECT_TRUE(called); |
| |
| // Check that only the initial credential is present in the password store |
| // and the new one is still pending. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(1U, passwords.size()); |
| EXPECT_EQ(1U, passwords[psl_form.signon_realm].size()); |
| |
| const auto& pending_cred = client_->pending_manager()->pending_credentials(); |
| EXPECT_EQ(info.id, pending_cred.username_value); |
| EXPECT_EQ(info.password, pending_cred.password_value); |
| } |
| |
| TEST_F(CredentialManagerImplTest, CredentialManagerStoreOverwriteZeroClick) { |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| RunAllPendingTasks(); |
| |
| // Calling 'Store' with a credential that matches |form_| should update |
| // the credential without prompting the user. |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| bool called = false; |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // the form is a match for an existing form, and update the PasswordStore. |
| RunAllPendingTasks(); |
| |
| // Verify that the update toggled the skip_zero_click flag off. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerFederatedStoreOverwriteZeroClick) { |
| form_.federation_origin = url::Origin(GURL("https://example.com/")); |
| form_.password_value = base::string16(); |
| form_.skip_zero_click = true; |
| form_.signon_realm = "federation://example.com/example.com"; |
| store_->AddLogin(form_); |
| RunAllPendingTasks(); |
| |
| // Calling 'Store' with a credential that matches |form_| should update |
| // the credential without prompting the user. |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| bool called = false; |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()); |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // the form is a match for an existing form, and update the PasswordStore. |
| RunAllPendingTasks(); |
| |
| // Verify that the update toggled the skip_zero_click flag off. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, CredentialManagerGetOverwriteZeroClick) { |
| // Set the global zero click flag on, and populate the PasswordStore with a |
| // form that's set to skip zero click and has a primary key that won't match |
| // credentials initially created via `store()`. |
| client_->set_zero_click_enabled(true); |
| form_.skip_zero_click = true; |
| form_.username_element = base::ASCIIToUTF16("username-element"); |
| form_.password_element = base::ASCIIToUTF16("password-element"); |
| form_.origin = GURL("https://example.com/old_form.html"); |
| store_->AddLogin(form_); |
| RunAllPendingTasks(); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| CallGet(CredentialMediationRequirement::kOptional, true, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error); |
| |
| // Verify that the update toggled the skip_zero_click flag. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerSignInWithSavingDisabledForCurrentPage) { |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(false)); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyStorePasswordCalled()).Times(0); |
| |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_FALSE(client_->pending_manager()); |
| } |
| |
| TEST_F(CredentialManagerImplTest, CredentialManagerOnPreventSilentAccess) { |
| store_->AddLogin(form_); |
| store_->AddLogin(subdomain_form_); |
| store_->AddLogin(cross_origin_form_); |
| RunAllPendingTasks(); |
| |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(3U, passwords.size()); |
| EXPECT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[subdomain_form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[cross_origin_form_.signon_realm].size()); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[subdomain_form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| |
| bool called = false; |
| CallPreventSilentAccess(base::Bind(&RespondCallback, &called)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| |
| passwords = store_->stored_passwords(); |
| EXPECT_EQ(3U, passwords.size()); |
| EXPECT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[subdomain_form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[cross_origin_form_.signon_realm].size()); |
| EXPECT_TRUE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_TRUE(passwords[subdomain_form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnPreventSilentAccessIncognito) { |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(false)); |
| store_->AddLogin(form_); |
| RunAllPendingTasks(); |
| |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| ASSERT_EQ(1U, passwords.size()); |
| ASSERT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| |
| bool called = false; |
| CallPreventSilentAccess(base::Bind(&RespondCallback, &called)); |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| |
| passwords = store_->stored_passwords(); |
| ASSERT_EQ(1U, passwords.size()); |
| ASSERT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnPreventSilentAccessWithAffiliation) { |
| store_->AddLogin(form_); |
| store_->AddLogin(cross_origin_form_); |
| store_->AddLogin(affiliated_form1_); |
| store_->AddLogin(affiliated_form2_); |
| |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| RunAllPendingTasks(); |
| |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(4U, passwords.size()); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[affiliated_form1_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[affiliated_form2_.signon_realm][0].skip_zero_click); |
| |
| bool called = false; |
| CallPreventSilentAccess(base::Bind(&RespondCallback, &called)); |
| RunAllPendingTasks(); |
| |
| passwords = store_->stored_passwords(); |
| EXPECT_EQ(4U, passwords.size()); |
| EXPECT_TRUE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| EXPECT_TRUE(passwords[affiliated_form1_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[affiliated_form2_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithEmptyPasswordStore) { |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| ExpectCredentialType(CredentialMediationRequirement::kOptional, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithEmptyUsernames) { |
| form_.username_value.clear(); |
| store_->AddLogin(form_); |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| std::vector<GURL> federations; |
| ExpectCredentialType(CredentialMediationRequirement::kOptional, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithPSLCredential) { |
| store_->AddLogin(subdomain_form_); |
| subdomain_form_.is_public_suffix_match = true; |
| EXPECT_CALL(*client_, |
| PromptUserToChooseCredentialsPtr( |
| UnorderedElementsAre(Pointee(subdomain_form_)), _, _)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(0); |
| |
| ExpectCredentialType(CredentialMediationRequirement::kOptional, true, |
| std::vector<GURL>(), |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithPSLAndNormalCredentials) { |
| store_->AddLogin(form_); |
| store_->AddLogin(origin_path_form_); |
| store_->AddLogin(subdomain_form_); |
| |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr( |
| UnorderedElementsAre(Pointee(origin_path_form_), |
| Pointee(form_)), |
| _, _)); |
| |
| ExpectCredentialType(CredentialMediationRequirement::kOptional, true, |
| std::vector<GURL>(), |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithEmptyAndNonemptyUsernames) { |
| store_->AddLogin(form_); |
| autofill::PasswordForm empty = form_; |
| empty.username_value.clear(); |
| store_->AddLogin(empty); |
| autofill::PasswordForm duplicate = form_; |
| duplicate.username_element = base::ASCIIToUTF16("different_username_element"); |
| store_->AddLogin(duplicate); |
| |
| std::vector<GURL> federations; |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kOptional, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithDuplicates) { |
| // Add 6 credentials. Two buckets of duplicates, one empty username and one |
| // federated one. There should be just 3 in the account chooser. |
| form_.preferred = true; |
| form_.username_element = base::ASCIIToUTF16("username_element"); |
| store_->AddLogin(form_); |
| autofill::PasswordForm empty = form_; |
| empty.username_value.clear(); |
| store_->AddLogin(empty); |
| autofill::PasswordForm duplicate = form_; |
| duplicate.username_element = base::ASCIIToUTF16("username_element2"); |
| duplicate.preferred = false; |
| store_->AddLogin(duplicate); |
| |
| origin_path_form_.preferred = true; |
| store_->AddLogin(origin_path_form_); |
| duplicate = origin_path_form_; |
| duplicate.username_element = base::ASCIIToUTF16("username_element4"); |
| duplicate.preferred = false; |
| store_->AddLogin(duplicate); |
| autofill::PasswordForm federated = origin_path_form_; |
| federated.password_value.clear(); |
| federated.federation_origin = url::Origin(GURL("https://google.com/")); |
| federated.signon_realm = |
| "federation://" + federated.origin.host() + "/google.com"; |
| store_->AddLogin(federated); |
| |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr( |
| UnorderedElementsAre(Pointee(form_), |
| Pointee(origin_path_form_), |
| Pointee(federated)), |
| _, _)); |
| |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| std::vector<GURL> federations; |
| federations.push_back(GURL("https://google.com/")); |
| CallGet(CredentialMediationRequirement::kOptional, true, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithCrossOriginPasswordStore) { |
| store_->AddLogin(cross_origin_form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| ExpectCredentialType(CredentialMediationRequirement::kOptional, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithFullPasswordStore) { |
| client_->set_zero_click_enabled(false); |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| CallGet(CredentialMediationRequirement::kOptional, true, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error); |
| } |
| |
| TEST_F( |
| CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithZeroClickOnlyEmptyPasswordStore) { |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithZeroClickOnlyFullPasswordStore) { |
| store_->AddLogin(form_); |
| client_->set_first_run_seen(true); |
| |
| std::vector<GURL> federations; |
| |
| EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(_)).Times(0); |
| |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithoutPasswords) { |
| store_->AddLogin(form_); |
| client_->set_first_run_seen(true); |
| |
| std::vector<GURL> federations; |
| |
| EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(_)).Times(0); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, false, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialFederatedMatch) { |
| form_.federation_origin = url::Origin(GURL("https://example.com/")); |
| form_.password_value = base::string16(); |
| store_->AddLogin(form_); |
| client_->set_first_run_seen(true); |
| |
| std::vector<GURL> federations; |
| federations.push_back(GURL("https://example.com/")); |
| |
| EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(_)).Times(0); |
| |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialFederatedNoMatch) { |
| form_.federation_origin = url::Origin(GURL("https://example.com/")); |
| form_.password_value = base::string16(); |
| store_->AddLogin(form_); |
| client_->set_first_run_seen(true); |
| |
| std::vector<GURL> federations; |
| federations.push_back(GURL("https://not-example.com/")); |
| |
| EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(_)).Times(0); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialAffiliatedPasswordMatch) { |
| store_->AddLogin(affiliated_form1_); |
| client_->set_first_run_seen(true); |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| // We pass in 'true' for the 'include_passwords' argument to ensure that |
| // password-type credentials are included as potential matches. |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialAffiliatedPasswordNoMatch) { |
| store_->AddLogin(affiliated_form1_); |
| client_->set_first_run_seen(true); |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| // We pass in 'false' for the 'include_passwords' argument to ensure that |
| // password-type credentials are excluded as potential matches. |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, false, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialAffiliatedFederatedMatch) { |
| affiliated_form1_.federation_origin = |
| url::Origin(GURL("https://example.com/")); |
| affiliated_form1_.password_value = base::string16(); |
| store_->AddLogin(affiliated_form1_); |
| client_->set_first_run_seen(true); |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| federations.push_back(GURL("https://example.com/")); |
| |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialAffiliatedFederatedNoMatch) { |
| affiliated_form1_.federation_origin = |
| url::Origin(GURL("https://example.com/")); |
| affiliated_form1_.password_value = base::string16(); |
| store_->AddLogin(affiliated_form1_); |
| client_->set_first_run_seen(true); |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| federations.push_back(GURL("https://not-example.com/")); |
| |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, RequestCredentialWithoutFirstRun) { |
| client_->set_first_run_seen(false); |
| |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, |
| NotifyUserCouldBeAutoSignedInPtr(testing::Pointee(form_))) |
| .Times(1); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, RequestCredentialWithFirstRunAndSkip) { |
| client_->set_first_run_seen(true); |
| |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, |
| NotifyUserCouldBeAutoSignedInPtr(testing::Pointee(form_))) |
| .Times(1); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, RequestCredentialWithTLSErrors) { |
| // If we encounter TLS errors, we won't return credentials. |
| EXPECT_CALL(*client_, IsFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(false)); |
| |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, RequestCredentialWhilePrerendering) { |
| // The client disallows the credential manager for the current page. |
| EXPECT_CALL(*client_, OnCredentialManagerUsed()) |
| .WillRepeatedly(testing::Return(false)); |
| |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWithZeroClickOnlyTwoPasswordStore) { |
| store_->AddLogin(form_); |
| store_->AddLogin(origin_path_form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| // With two items in the password store, we shouldn't get credentials back. |
| ExpectCredentialType(CredentialMediationRequirement::kSilent, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| OnRequestCredentialWithZeroClickOnlyAndSkipZeroClickPasswordStore) { |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| store_->AddLogin(origin_path_form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| // With two items in the password store, we shouldn't get credentials back, |
| // even though only one item has |skip_zero_click| set |false|. |
| ExpectCredentialType(CredentialMediationRequirement::kSilent, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| OnRequestCredentialWithZeroClickOnlyCrossOriginPasswordStore) { |
| store_->AddLogin(cross_origin_form_); |
| |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| // We only have cross-origin zero-click credentials; they should not be |
| // returned. |
| ExpectCredentialType(CredentialMediationRequirement::kSilent, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| CredentialManagerOnRequestCredentialWhileRequestPending) { |
| client_->set_zero_click_enabled(false); |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| // 1st request. |
| bool called_1 = false; |
| mojom::CredentialManagerError error_1; |
| base::Optional<CredentialInfo> credential_1; |
| CallGet( |
| CredentialMediationRequirement::kOptional, true, federations, |
| base::Bind(&GetCredentialCallback, &called_1, &error_1, &credential_1)); |
| // 2nd request. |
| bool called_2 = false; |
| mojom::CredentialManagerError error_2; |
| base::Optional<CredentialInfo> credential_2; |
| CallGet( |
| CredentialMediationRequirement::kOptional, true, federations, |
| base::Bind(&GetCredentialCallback, &called_2, &error_2, &credential_2)); |
| |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| // Execute the PasswordStore asynchronousness. |
| RunAllPendingTasks(); |
| |
| // Check that the second request triggered a rejection. |
| EXPECT_TRUE(called_2); |
| EXPECT_EQ(mojom::CredentialManagerError::PENDINGREQUEST, error_2); |
| EXPECT_FALSE(credential_2); |
| |
| // Check that the first request resolves. |
| EXPECT_TRUE(called_1); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error_1); |
| EXPECT_NE(CredentialType::CREDENTIAL_TYPE_EMPTY, credential_1->type); |
| } |
| |
| TEST_F(CredentialManagerImplTest, ResetSkipZeroClickAfterPrompt) { |
| // Turn on the global zero-click flag, and add two credentials in separate |
| // origins, both set to skip zero-click. |
| client_->set_zero_click_enabled(true); |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| cross_origin_form_.skip_zero_click = true; |
| store_->AddLogin(cross_origin_form_); |
| |
| // Execute the PasswordStore asynchronousness to ensure everything is |
| // written before proceeding. |
| RunAllPendingTasks(); |
| |
| // Sanity check. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| EXPECT_EQ(2U, passwords.size()); |
| EXPECT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[cross_origin_form_.signon_realm].size()); |
| EXPECT_TRUE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_TRUE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| |
| // Trigger a request which should return the credential found in |form_|, and |
| // wait for it to process. |
| std::vector<GURL> federations; |
| // Check that the form in the database has been updated. `OnRequestCredential` |
| // generates a call to prompt the user to choose a credential. |
| // MockPasswordManagerClient mocks a user choice, and when users choose a |
| // credential (and have the global zero-click flag enabled), we make sure that |
| // they'll be logged in again next time. |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| CallGet(CredentialMediationRequirement::kOptional, true, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| passwords = store_->stored_passwords(); |
| EXPECT_EQ(2U, passwords.size()); |
| EXPECT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_EQ(1U, passwords[cross_origin_form_.signon_realm].size()); |
| EXPECT_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_TRUE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, NoResetSkipZeroClickAfterPromptInIncognito) { |
| EXPECT_CALL(*client_, IsIncognito()).WillRepeatedly(testing::Return(true)); |
| // Turn on the global zero-click flag which should be overriden by Incognito. |
| client_->set_zero_click_enabled(true); |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| RunAllPendingTasks(); |
| |
| // Sanity check. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| ASSERT_EQ(1U, passwords.size()); |
| ASSERT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_TRUE(passwords[form_.signon_realm][0].skip_zero_click); |
| |
| // Trigger a request which should return the credential found in |form_|, and |
| // wait for it to process. |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| CallGet(CredentialMediationRequirement::kOptional, true, std::vector<GURL>(), |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| // The form shouldn't become a zero-click one. |
| passwords = store_->stored_passwords(); |
| ASSERT_EQ(1U, passwords.size()); |
| ASSERT_EQ(1U, passwords[form_.signon_realm].size()); |
| EXPECT_TRUE(passwords[form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerImplTest, IncognitoZeroClickRequestCredential) { |
| EXPECT_CALL(*client_, IsIncognito()).WillRepeatedly(testing::Return(true)); |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| |
| ExpectCredentialType(CredentialMediationRequirement::kSilent, true, |
| federations, CredentialType::CREDENTIAL_TYPE_EMPTY); |
| } |
| |
| TEST_F(CredentialManagerImplTest, ZeroClickWithAffiliatedFormInPasswordStore) { |
| // Insert the affiliated form into the store, and mock out the association |
| // with the current origin. As it's the only form matching the origin, it |
| // ought to be returned automagically. |
| store_->AddLogin(affiliated_form1_); |
| |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| ZeroClickWithTwoAffiliatedFormsInPasswordStore) { |
| // Insert two affiliated forms into the store, and mock out the association |
| // with the current origin. Multiple forms === no zero-click sign in. |
| store_->AddLogin(affiliated_form1_); |
| store_->AddLogin(affiliated_form2_); |
| |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| affiliated_realms.push_back(kTestAndroidRealm2); |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| ZeroClickWithUnaffiliatedFormsInPasswordStore) { |
| // Insert the affiliated form into the store, but don't mock out the |
| // association with the current origin. No association === no zero-click sign |
| // in. |
| store_->AddLogin(affiliated_form1_); |
| |
| store_->SetAffiliatedMatchHelper( |
| base::MakeUnique<MockAffiliatedMatchHelper>()); |
| |
| std::vector<std::string> affiliated_realms; |
| PasswordStore::FormDigest digest = |
| cm_service_impl_->GetSynthesizedFormForOrigin(); |
| // First expect affiliations for the HTTPS domain. |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms(digest, affiliated_realms); |
| |
| digest.origin = HttpURLFromHttps(digest.origin); |
| digest.signon_realm = digest.origin.spec(); |
| // The second call happens for HTTP as the migration is triggered. |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms(digest, affiliated_realms); |
| |
| std::vector<GURL> federations; |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, |
| ZeroClickWithFormAndUnaffiliatedFormsInPasswordStore) { |
| // Insert the affiliated form into the store, along with a real form for the |
| // origin, and don't mock out the association with the current origin. No |
| // association + existing form === zero-click sign in. |
| store_->AddLogin(form_); |
| store_->AddLogin(affiliated_form1_); |
| |
| auto mock_helper = base::WrapUnique(new MockAffiliatedMatchHelper); |
| store_->SetAffiliatedMatchHelper(std::move(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) |
| ->ExpectCallToGetAffiliatedAndroidRealms( |
| cm_service_impl_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, ZeroClickWithPSLCredential) { |
| subdomain_form_.skip_zero_click = false; |
| store_->AddLogin(subdomain_form_); |
| |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true, |
| std::vector<GURL>()); |
| } |
| |
| TEST_F(CredentialManagerImplTest, ZeroClickWithPSLAndNormalCredentials) { |
| form_.password_value.clear(); |
| form_.federation_origin = url::Origin(GURL("https://google.com/")); |
| form_.signon_realm = "federation://" + form_.origin.host() + "/google.com"; |
| form_.skip_zero_click = false; |
| store_->AddLogin(form_); |
| store_->AddLogin(subdomain_form_); |
| |
| std::vector<GURL> federations = {GURL("https://google.com/")}; |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| } |
| |
| TEST_F(CredentialManagerImplTest, ZeroClickAfterMigratingHttpCredential) { |
| // There is an http credential saved. It should be migrated and used for auto |
| // sign-in. |
| form_.origin = HttpURLFromHttps(form_.origin); |
| form_.signon_realm = form_.origin.GetOrigin().spec(); |
| // That is the default value for old credentials. |
| form_.skip_zero_click = true; |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| ExpectZeroClickSignInSuccess(CredentialMediationRequirement::kSilent, true, |
| federations, |
| CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| } |
| |
| TEST_F(CredentialManagerImplTest, MigrateWithEmptyStore) { |
| // HTTP scheme is valid for localhost. Nothing should crash. |
| NavigateAndCommit(GURL("http://127.0.0.1:8000/")); |
| |
| std::vector<GURL> federations; |
| ExpectZeroClickSignInFailure(CredentialMediationRequirement::kOptional, true, |
| federations); |
| } |
| |
| TEST_F(CredentialManagerImplTest, MediationRequiredPreventsAutoSignIn) { |
| form_.skip_zero_click = false; |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| bool called = false; |
| mojom::CredentialManagerError error; |
| base::Optional<CredentialInfo> credential; |
| |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); |
| CallGet(CredentialMediationRequirement::kRequired, true, federations, |
| base::Bind(&GetCredentialCallback, &called, &error, &credential)); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(mojom::CredentialManagerError::SUCCESS, error); |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_PASSWORD, credential->type); |
| } |
| |
| TEST_F(CredentialManagerImplTest, GetSynthesizedFormForOrigin) { |
| PasswordStore::FormDigest synthesized = |
| cm_service_impl_->GetSynthesizedFormForOrigin(); |
| EXPECT_EQ(kTestWebOrigin, synthesized.origin.spec()); |
| EXPECT_EQ(kTestWebOrigin, synthesized.signon_realm); |
| EXPECT_EQ(autofill::PasswordForm::SCHEME_HTML, synthesized.scheme); |
| } |
| |
| TEST_F(CredentialManagerImplTest, BlacklistPasswordCredential) { |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)); |
| |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| // Allow the PasswordFormManager to talk to the password store |
| RunAllPendingTasks(); |
| |
| ASSERT_TRUE(client_->pending_manager()); |
| client_->pending_manager()->PermanentlyBlacklist(); |
| // Allow the PasswordFormManager to talk to the password store. |
| RunAllPendingTasks(); |
| |
| // Verify that the site is blacklisted. |
| autofill::PasswordForm blacklisted; |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| blacklisted.blacklisted_by_user = true; |
| blacklisted.origin = form_.origin; |
| blacklisted.signon_realm = form_.signon_realm; |
| blacklisted.type = autofill::PasswordForm::TYPE_API; |
| blacklisted.date_created = passwords[form_.signon_realm][0].date_created; |
| EXPECT_THAT(passwords[form_.signon_realm], testing::ElementsAre(blacklisted)); |
| } |
| |
| TEST_F(CredentialManagerImplTest, BlacklistFederatedCredential) { |
| form_.federation_origin = url::Origin(GURL("https://example.com/")); |
| form_.password_value = base::string16(); |
| form_.signon_realm = "federation://example.com/example.com"; |
| |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)); |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| bool called = false; |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| // Allow the PasswordFormManager to talk to the password store |
| RunAllPendingTasks(); |
| |
| ASSERT_TRUE(client_->pending_manager()); |
| client_->pending_manager()->PermanentlyBlacklist(); |
| // Allow the PasswordFormManager to talk to the password store. |
| RunAllPendingTasks(); |
| |
| // Verify that the site is blacklisted. |
| TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); |
| ASSERT_TRUE(passwords.count(form_.origin.spec())); |
| autofill::PasswordForm blacklisted; |
| blacklisted.blacklisted_by_user = true; |
| blacklisted.origin = form_.origin; |
| blacklisted.signon_realm = blacklisted.origin.spec(); |
| blacklisted.type = autofill::PasswordForm::TYPE_API; |
| blacklisted.date_created = |
| passwords[blacklisted.signon_realm][0].date_created; |
| EXPECT_THAT(passwords[blacklisted.signon_realm], |
| testing::ElementsAre(blacklisted)); |
| } |
| |
| TEST_F(CredentialManagerImplTest, RespectBlacklistingPasswordCredential) { |
| autofill::PasswordForm blacklisted; |
| blacklisted.blacklisted_by_user = true; |
| blacklisted.origin = form_.origin; |
| blacklisted.signon_realm = blacklisted.origin.spec(); |
| store_->AddLogin(blacklisted); |
| |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| bool called = false; |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)); |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| // Allow the PasswordFormManager to talk to the password store |
| RunAllPendingTasks(); |
| |
| ASSERT_TRUE(client_->pending_manager()); |
| EXPECT_TRUE(client_->pending_manager()->IsBlacklisted()); |
| } |
| |
| TEST_F(CredentialManagerImplTest, RespectBlacklistingFederatedCredential) { |
| autofill::PasswordForm blacklisted; |
| blacklisted.blacklisted_by_user = true; |
| blacklisted.origin = form_.origin; |
| blacklisted.signon_realm = blacklisted.origin.spec(); |
| store_->AddLogin(blacklisted); |
| |
| form_.federation_origin = url::Origin(GURL("https://example.com/")); |
| form_.password_value = base::string16(); |
| form_.signon_realm = "federation://example.com/example.com"; |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_FEDERATED); |
| bool called = false; |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)); |
| CallStore(info, base::Bind(&RespondCallback, &called)); |
| // Allow the PasswordFormManager to talk to the password store |
| RunAllPendingTasks(); |
| |
| ASSERT_TRUE(client_->pending_manager()); |
| EXPECT_TRUE(client_->pending_manager()->IsBlacklisted()); |
| } |
| |
| } // namespace password_manager |