| // 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_dispatcher.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/prefs/pref_registry_simple.h" |
| #include "base/prefs/testing_pref_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "components/password_manager/content/common/credential_manager_messages.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/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 "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" |
| |
| using content::BrowserContext; |
| using content::WebContents; |
| |
| using testing::_; |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| // Chosen by fair dice roll. Guaranteed to be random. |
| const int kRequestId = 4; |
| |
| 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(IsOffTheRecord, bool()); |
| MOCK_CONST_METHOD0(DidLastPageLoadEncounterSSLErrors, bool()); |
| MOCK_METHOD1(NotifyUserAutoSigninPtr, |
| bool(const std::vector<autofill::PasswordForm*>& local_forms)); |
| MOCK_METHOD2(PromptUserToSavePasswordPtr, |
| void(PasswordFormManager*, CredentialSourceType type)); |
| MOCK_METHOD4(PromptUserToChooseCredentialsPtr, |
| bool(const std::vector<autofill::PasswordForm*>& local_forms, |
| const std::vector<autofill::PasswordForm*>& federated_forms, |
| const GURL& origin, |
| base::Callback<void(const CredentialInfo&)> callback)); |
| |
| explicit MockPasswordManagerClient(PasswordStore* store) : store_(store) { |
| prefs_.registry()->RegisterBooleanPref(prefs::kPasswordManagerAutoSignin, |
| true); |
| } |
| ~MockPasswordManagerClient() override {} |
| |
| bool PromptUserToSaveOrUpdatePassword(scoped_ptr<PasswordFormManager> manager, |
| CredentialSourceType type, |
| bool update_password) override { |
| manager_.swap(manager); |
| PromptUserToSavePasswordPtr(manager_.get(), type); |
| return true; |
| } |
| |
| PasswordStore* GetPasswordStore() const override { return store_; } |
| |
| PrefService* GetPrefs() override { return &prefs_; } |
| |
| bool PromptUserToChooseCredentials( |
| ScopedVector<autofill::PasswordForm> local_forms, |
| ScopedVector<autofill::PasswordForm> federated_forms, |
| const GURL& origin, |
| base::Callback<void(const CredentialInfo&)> callback) { |
| EXPECT_FALSE(local_forms.empty() && federated_forms.empty()); |
| CredentialInfo info( |
| local_forms.empty() ? *federated_forms[0] : *local_forms[0], |
| local_forms.empty() ? CredentialType::CREDENTIAL_TYPE_FEDERATED |
| : CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| base::Bind(callback, info)); |
| PromptUserToChooseCredentialsPtr(local_forms.get(), federated_forms.get(), |
| origin, callback); |
| return true; |
| } |
| |
| void NotifyUserAutoSignin( |
| ScopedVector<autofill::PasswordForm> local_forms) override { |
| EXPECT_FALSE(local_forms.empty()); |
| NotifyUserAutoSigninPtr(local_forms.get()); |
| } |
| |
| PasswordFormManager* pending_manager() const { return manager_.get(); } |
| |
| void set_zero_click_enabled(bool zero_click_enabled) { |
| prefs_.SetBoolean(prefs::kPasswordManagerAutoSignin, zero_click_enabled); |
| } |
| |
| private: |
| TestingPrefServiceSimple prefs_; |
| PasswordStore* store_; |
| scoped_ptr<PasswordFormManager> manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient); |
| }; |
| |
| class TestCredentialManagerDispatcher : public CredentialManagerDispatcher { |
| public: |
| TestCredentialManagerDispatcher(content::WebContents* web_contents, |
| PasswordManagerClient* client, |
| PasswordManagerDriver* driver); |
| |
| private: |
| base::WeakPtr<PasswordManagerDriver> GetDriver() override; |
| |
| base::WeakPtr<PasswordManagerDriver> driver_; |
| }; |
| |
| TestCredentialManagerDispatcher::TestCredentialManagerDispatcher( |
| content::WebContents* web_contents, |
| PasswordManagerClient* client, |
| PasswordManagerDriver* driver) |
| : CredentialManagerDispatcher(web_contents, client), |
| driver_(driver->AsWeakPtr()) {} |
| |
| base::WeakPtr<PasswordManagerDriver> |
| TestCredentialManagerDispatcher::GetDriver() { |
| return driver_; |
| } |
| |
| void RunAllPendingTasks() { |
| base::RunLoop run_loop; |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); |
| run_loop.Run(); |
| } |
| |
| } // namespace |
| |
| class CredentialManagerDispatcherTest |
| : public content::RenderViewHostTestHarness { |
| public: |
| CredentialManagerDispatcherTest() {} |
| |
| void SetUp() override { |
| content::RenderViewHostTestHarness::SetUp(); |
| store_ = new TestPasswordStore; |
| client_.reset(new MockPasswordManagerClient(store_.get())); |
| dispatcher_.reset(new TestCredentialManagerDispatcher( |
| web_contents(), client_.get(), &stub_driver_)); |
| ON_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillByDefault(testing::Return(true)); |
| ON_CALL(*client_, IsOffTheRecord()).WillByDefault(testing::Return(false)); |
| ON_CALL(*client_, DidLastPageLoadEncounterSSLErrors()) |
| .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_.password_value = base::ASCIIToUTF16("Password"); |
| form_.origin = web_contents()->GetLastCommittedURL().GetOrigin(); |
| form_.signon_realm = form_.origin.spec(); |
| form_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| form_.skip_zero_click = false; |
| form_.ssl_valid = true; |
| |
| 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_form1_.ssl_valid = true; |
| |
| 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; |
| affiliated_form2_.ssl_valid = true; |
| |
| 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.spec(); |
| origin_path_form_.scheme = autofill::PasswordForm::SCHEME_HTML; |
| origin_path_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.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 { |
| store_->Shutdown(); |
| content::RenderViewHostTestHarness::TearDown(); |
| } |
| |
| void ExpectZeroClickSignInFailure() { |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr(_)) |
| .Times(testing::Exactly(0)); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| ASSERT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, |
| base::get<1>(send_param).type); |
| } |
| |
| void ExpectZeroClickSignInSuccess() { |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr(_)) |
| .Times(testing::Exactly(1)); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| ASSERT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_PASSWORD, |
| base::get<1>(send_param).type); |
| } |
| |
| CredentialManagerDispatcher* dispatcher() { return dispatcher_.get(); } |
| |
| protected: |
| autofill::PasswordForm form_; |
| autofill::PasswordForm affiliated_form1_; |
| autofill::PasswordForm affiliated_form2_; |
| autofill::PasswordForm origin_path_form_; |
| autofill::PasswordForm cross_origin_form_; |
| scoped_refptr<TestPasswordStore> store_; |
| scoped_ptr<MockPasswordManagerClient> client_; |
| StubPasswordManagerDriver stub_driver_; |
| scoped_ptr<CredentialManagerDispatcher> dispatcher_; |
| }; |
| |
| TEST_F(CredentialManagerDispatcherTest, CredentialManagerOnStore) { |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr( |
| _, CredentialSourceType::CREDENTIAL_SOURCE_API)) |
| .Times(testing::Exactly(1)); |
| |
| dispatcher()->OnStore(kRequestId, info); |
| |
| const uint32 kMsgID = CredentialManagerMsg_AcknowledgeStore::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| process()->sink().ClearMessages(); |
| |
| // Allow the PasswordFormManager to talk to the password store, determine |
| // that the form is new, and set it as pending. |
| RunAllPendingTasks(); |
| |
| EXPECT_TRUE(client_->pending_manager()->HasCompletedMatching()); |
| |
| 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(autofill::PasswordForm::SCHEME_HTML, new_form.scheme); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| CredentialManagerSignInWithSavingDisabledForCurrentPage) { |
| CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(false)); |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr( |
| _, CredentialSourceType::CREDENTIAL_SOURCE_API)) |
| .Times(testing::Exactly(0)); |
| |
| dispatcher()->OnStore(kRequestId, info); |
| |
| const uint32 kMsgID = CredentialManagerMsg_AcknowledgeStore::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| process()->sink().ClearMessages(); |
| |
| RunAllPendingTasks(); |
| |
| EXPECT_FALSE(client_->pending_manager()); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| CredentialManagerOnRequireUserMediation) { |
| store_->AddLogin(form_); |
| store_->AddLogin(cross_origin_form_); |
| RunAllPendingTasks(); |
| |
| 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_FALSE(passwords[form_.signon_realm][0].skip_zero_click); |
| EXPECT_FALSE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| |
| dispatcher()->OnRequireUserMediation(kRequestId); |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = |
| CredentialManagerMsg_AcknowledgeRequireUserMediation::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| process()->sink().ClearMessages(); |
| |
| 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_FALSE(passwords[cross_origin_form_.signon_realm][0].skip_zero_click); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| CredentialManagerOnRequireUserMediationWithAffiliation) { |
| store_->AddLogin(form_); |
| store_->AddLogin(cross_origin_form_); |
| store_->AddLogin(affiliated_form1_); |
| store_->AddLogin(affiliated_form2_); |
| |
| MockAffiliatedMatchHelper* mock_helper = new MockAffiliatedMatchHelper; |
| store_->SetAffiliatedMatchHelper(make_scoped_ptr(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| mock_helper->ExpectCallToGetAffiliatedAndroidRealms( |
| dispatcher_->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); |
| |
| dispatcher()->OnRequireUserMediation(kRequestId); |
| RunAllPendingTasks(); |
| process()->sink().ClearMessages(); |
| |
| 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(CredentialManagerDispatcherTest, |
| CredentialManagerOnRequestCredentialWithEmptyPasswordStore) { |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr( |
| _, CredentialSourceType::CREDENTIAL_SOURCE_API)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr(_)).Times(testing::Exactly(0)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, false, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param param; |
| CredentialManagerMsg_SendCredential::Read(message, ¶m); |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, base::get<1>(param).type); |
| process()->sink().ClearMessages(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| CredentialManagerOnRequestCredentialWithCrossOriginPasswordStore) { |
| store_->AddLogin(cross_origin_form_); |
| |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToSavePasswordPtr( |
| _, CredentialSourceType::CREDENTIAL_SOURCE_API)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr(_)).Times(testing::Exactly(0)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, false, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param param; |
| CredentialManagerMsg_SendCredential::Read(message, ¶m); |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, base::get<1>(param).type); |
| process()->sink().ClearMessages(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, false, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| } |
| |
| TEST_F( |
| CredentialManagerDispatcherTest, |
| CredentialManagerOnRequestCredentialWithZeroClickOnlyEmptyPasswordStore) { |
| std::vector<GURL> federations; |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _, _)) |
| .Times(testing::Exactly(0)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr(_)).Times(testing::Exactly(0)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, |
| base::get<1>(send_param).type); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| CredentialManagerOnRequestCredentialWithZeroClickOnlyFullPasswordStore) { |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| ExpectZeroClickSignInSuccess(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, RequestCredentialWithTLSErrors) { |
| // If we encounter TLS errors, we won't return credentials. |
| EXPECT_CALL(*client_, DidLastPageLoadEncounterSSLErrors()) |
| .WillRepeatedly(testing::Return(true)); |
| |
| store_->AddLogin(form_); |
| |
| std::vector<GURL> federations; |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| ExpectZeroClickSignInFailure(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| |
| // With two items in the password store, we shouldn't get credentials back. |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, |
| base::get<1>(send_param).type); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| |
| // With two items in the password store, we shouldn't get credentials back, |
| // even though only one item has |skip_zero_click| set |false|. |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, |
| base::get<1>(send_param).type); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| |
| // We only have cross-origin zero-click credentials; they should not be |
| // returned. |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, |
| base::get<1>(send_param).type); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, false, federations); |
| dispatcher()->OnRequestCredential(kRequestId, false, federations); |
| |
| // Check that the second request triggered a rejection. |
| uint32 kMsgID = CredentialManagerMsg_RejectCredentialRequest::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| |
| CredentialManagerMsg_RejectCredentialRequest::Param reject_param; |
| CredentialManagerMsg_RejectCredentialRequest::Read(message, &reject_param); |
| EXPECT_EQ(blink::WebCredentialManagerError::ErrorTypePendingRequest, |
| base::get<1>(reject_param)); |
| EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _, _)) |
| .Times(testing::Exactly(1)); |
| EXPECT_CALL(*client_, NotifyUserAutoSigninPtr(_)).Times(testing::Exactly(0)); |
| |
| process()->sink().ClearMessages(); |
| |
| // Execute the PasswordStore asynchronousness. |
| RunAllPendingTasks(); |
| |
| // Check that the first request resolves. |
| kMsgID = CredentialManagerMsg_SendCredential::ID; |
| message = process()->sink().GetFirstMessageMatching(kMsgID); |
| EXPECT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param send_param; |
| CredentialManagerMsg_SendCredential::Read(message, &send_param); |
| EXPECT_NE(CredentialType::CREDENTIAL_TYPE_EMPTY, |
| base::get<1>(send_param).type); |
| process()->sink().ClearMessages(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, 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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, false, federations); |
| 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(CredentialManagerDispatcherTest, IncognitoZeroClickRequestCredential) { |
| EXPECT_CALL(*client_, IsOffTheRecord()).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)); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| RunAllPendingTasks(); |
| |
| const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID; |
| const IPC::Message* message = |
| process()->sink().GetFirstMessageMatching(kMsgID); |
| ASSERT_TRUE(message); |
| CredentialManagerMsg_SendCredential::Param param; |
| CredentialManagerMsg_SendCredential::Read(message, ¶m); |
| EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, base::get<1>(param).type); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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_); |
| |
| MockAffiliatedMatchHelper* mock_helper = new MockAffiliatedMatchHelper; |
| store_->SetAffiliatedMatchHelper(make_scoped_ptr(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| mock_helper->ExpectCallToGetAffiliatedAndroidRealms( |
| dispatcher_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| ExpectZeroClickSignInSuccess(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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_); |
| |
| MockAffiliatedMatchHelper* mock_helper = new MockAffiliatedMatchHelper; |
| store_->SetAffiliatedMatchHelper(make_scoped_ptr(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| affiliated_realms.push_back(kTestAndroidRealm1); |
| affiliated_realms.push_back(kTestAndroidRealm2); |
| mock_helper->ExpectCallToGetAffiliatedAndroidRealms( |
| dispatcher_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| ExpectZeroClickSignInFailure(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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_); |
| |
| MockAffiliatedMatchHelper* mock_helper = new MockAffiliatedMatchHelper; |
| store_->SetAffiliatedMatchHelper(make_scoped_ptr(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| mock_helper->ExpectCallToGetAffiliatedAndroidRealms( |
| dispatcher_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| ExpectZeroClickSignInFailure(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, |
| 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_); |
| |
| MockAffiliatedMatchHelper* mock_helper = new MockAffiliatedMatchHelper; |
| store_->SetAffiliatedMatchHelper(make_scoped_ptr(mock_helper)); |
| |
| std::vector<GURL> federations; |
| std::vector<std::string> affiliated_realms; |
| mock_helper->ExpectCallToGetAffiliatedAndroidRealms( |
| dispatcher_->GetSynthesizedFormForOrigin(), affiliated_realms); |
| |
| dispatcher()->OnRequestCredential(kRequestId, true, federations); |
| |
| ExpectZeroClickSignInSuccess(); |
| } |
| |
| TEST_F(CredentialManagerDispatcherTest, GetSynthesizedFormForOrigin) { |
| autofill::PasswordForm synthesized = |
| dispatcher_->GetSynthesizedFormForOrigin(); |
| EXPECT_EQ(kTestWebOrigin, synthesized.origin.spec()); |
| EXPECT_EQ(kTestWebOrigin, synthesized.signon_realm); |
| EXPECT_EQ(autofill::PasswordForm::SCHEME_HTML, synthesized.scheme); |
| EXPECT_TRUE(synthesized.ssl_valid); |
| } |
| |
| } // namespace password_manager |