diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-23 17:25:37 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-23 17:25:37 +0000 |
commit | ea68c06a2c5182d1a06ce804306aeab17ed75dc5 (patch) | |
tree | 9d2c818c5982c43a989f24c43f7763ab9b3466c8 /chrome/browser/password_manager | |
parent | fee679820e1d129068a22e0576b26de53e675673 (diff) | |
download | chromium_src-ea68c06a2c5182d1a06ce804306aeab17ed75dc5.zip chromium_src-ea68c06a2c5182d1a06ce804306aeab17ed75dc5.tar.gz chromium_src-ea68c06a2c5182d1a06ce804306aeab17ed75dc5.tar.bz2 |
Add PasswordManager tests to verify basic form observation / submit cases.
This patch includes basic tests for infobar triggering, PasswordStore
interaction, and autofilling.
Now that the plumbing is sufficiently mocked / de-coupled from the dreaded
TabContents stack using RenderViewHostTestHarness and Google Mock, it is
pretty easy to add more tests and I plan to!
TEST=PasswordManagerTest (new), PasswordFormManagerTest (existing)
BUG=none
Review URL: http://codereview.chromium.org/1745006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45458 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/password_manager')
3 files changed, 253 insertions, 92 deletions
diff --git a/chrome/browser/password_manager/password_manager.cc b/chrome/browser/password_manager/password_manager.cc index 069ec4c..fc6c58e 100644 --- a/chrome/browser/password_manager/password_manager.cc +++ b/chrome/browser/password_manager/password_manager.cc @@ -13,100 +13,34 @@ #include "chrome/browser/password_manager/password_form_manager.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" -#include "chrome/browser/renderer_host/render_view_host.h" -#include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" -#include "grit/chromium_strings.h" #include "grit/generated_resources.h" -#include "grit/theme_resources.h" using webkit_glue::PasswordForm; using webkit_glue::PasswordFormMap; -// After a successful *new* login attempt, we take the PasswordFormManager in -// provisional_save_manager_ and move it to a SavePasswordInfoBarDelegate while -// the user makes up their mind with the "save password" infobar. Note if the -// login is one we already know about, the end of the line is -// provisional_save_manager_ because we just update it on success and so such -// forms never end up in an infobar. -class SavePasswordInfoBarDelegate : public ConfirmInfoBarDelegate { - public: - SavePasswordInfoBarDelegate(TabContents* tab_contents, - PasswordFormManager* form_to_save) : - ConfirmInfoBarDelegate(tab_contents), - form_to_save_(form_to_save) { - } - - virtual ~SavePasswordInfoBarDelegate() { } - - // Overridden from ConfirmInfoBarDelegate: - virtual void InfoBarClosed() { - delete this; - } - - virtual std::wstring GetMessageText() const { - return l10n_util::GetString(IDS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT); - } - - virtual SkBitmap* GetIcon() const { - return ResourceBundle::GetSharedInstance().GetBitmapNamed( - IDR_INFOBAR_SAVE_PASSWORD); - } - - virtual int GetButtons() const { - return BUTTON_OK | BUTTON_CANCEL; - } - - virtual std::wstring GetButtonLabel(InfoBarButton button) const { - if (button == BUTTON_OK) - return l10n_util::GetString(IDS_PASSWORD_MANAGER_SAVE_BUTTON); - if (button == BUTTON_CANCEL) - return l10n_util::GetString(IDS_PASSWORD_MANAGER_BLACKLIST_BUTTON); - NOTREACHED(); - return std::wstring(); - } - - virtual bool Accept() { - DCHECK(form_to_save_.get()); - form_to_save_->Save(); - return true; - } - - virtual bool Cancel() { - DCHECK(form_to_save_.get()); - form_to_save_->PermanentlyBlacklist(); - return true; - } - - private: - // The PasswordFormManager managing the form we're asking the user about, - // and should update as per her decision. - scoped_ptr<PasswordFormManager> form_to_save_; - - DISALLOW_COPY_AND_ASSIGN(SavePasswordInfoBarDelegate); -}; - // static void PasswordManager::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kPasswordManagerEnabled, true); } -PasswordManager::PasswordManager(TabContents* tab_contents) +PasswordManager::PasswordManager(Delegate* delegate) : login_managers_deleter_(&pending_login_managers_), - tab_contents_(tab_contents), + delegate_(delegate), observer_(NULL) { + DCHECK(delegate_); password_manager_enabled_.Init(prefs::kPasswordManagerEnabled, - tab_contents->profile()->GetPrefs(), NULL); + delegate_->GetProfileForPasswordManager()->GetPrefs(), NULL); } PasswordManager::~PasswordManager() { } void PasswordManager::ProvisionallySavePassword(PasswordForm form) { - if (!tab_contents_->profile() || - tab_contents_->profile()->IsOffTheRecord() || + if (!delegate_->GetProfileForPasswordManager() || + delegate_->GetProfileForPasswordManager()->IsOffTheRecord() || !*password_manager_enabled_) return; @@ -142,8 +76,7 @@ void PasswordManager::ProvisionallySavePassword(PasswordForm form) { return; form.ssl_valid = form.origin.SchemeIsSecure() && - !tab_contents_->controller().ssl_manager()-> - ProcessedSSLErrorFromRequest(); + !delegate_->DidLastPageLoadEncounterSSLErrors(); form.preferred = true; manager->ProvisionallySave(form); provisional_save_manager_.reset(manager); @@ -168,16 +101,13 @@ void PasswordManager::DidStopLoading() { if (!provisional_save_manager_.get()) return; - DCHECK(!tab_contents_->profile()->IsOffTheRecord()); + DCHECK(!delegate_->GetProfileForPasswordManager()->IsOffTheRecord()); DCHECK(!provisional_save_manager_->IsBlacklisted()); - if (!tab_contents_->profile() || - !tab_contents_->profile()->GetWebDataService(Profile::IMPLICIT_ACCESS)) + if (!delegate_->GetProfileForPasswordManager()) return; if (provisional_save_manager_->IsNewLogin()) { - tab_contents_->AddInfoBar( - new SavePasswordInfoBarDelegate(tab_contents_, - provisional_save_manager_.release())); + delegate_->AddSavePasswordInfoBar(provisional_save_manager_.release()); } else { // If the save is not a new username entry, then we just want to save this // data (since the user already has related data saved), so don't prompt. @@ -188,15 +118,13 @@ void PasswordManager::DidStopLoading() { void PasswordManager::PasswordFormsSeen( const std::vector<PasswordForm>& forms) { - if (!tab_contents_->profile() || - !tab_contents_->profile()->GetWebDataService(Profile::EXPLICIT_ACCESS)) + if (!delegate_->GetProfileForPasswordManager()) return; if (!*password_manager_enabled_) return; // Ask the SSLManager for current security. - bool had_ssl_error = tab_contents_->controller().ssl_manager()-> - ProcessedSSLErrorFromRequest(); + bool had_ssl_error = delegate_->DidLastPageLoadEncounterSSLErrors(); std::vector<PasswordForm>::const_iterator iter; for (iter = forms.begin(); iter != forms.end(); iter++) { @@ -212,7 +140,7 @@ void PasswordManager::PasswordFormsSeen( } else { bool ssl_valid = iter->origin.SchemeIsSecure() && !had_ssl_error; PasswordFormManager* manager = - new PasswordFormManager(tab_contents_->profile(), + new PasswordFormManager(delegate_->GetProfileForPasswordManager(), this, *iter, ssl_valid); pending_login_managers_.push_back(manager); manager->FetchMatchingLoginsFromWebDatabase(); @@ -224,7 +152,6 @@ void PasswordManager::Autofill( const PasswordForm& form_for_autofill, const PasswordFormMap& best_matches, const PasswordForm* const preferred_match) const { - DCHECK(tab_contents_); DCHECK(preferred_match); switch (form_for_autofill.scheme) { case PasswordForm::SCHEME_HTML: { @@ -238,7 +165,7 @@ void PasswordManager::Autofill( preferred_match, action_mismatch, &fill_data); - tab_contents_->render_view_host()->FillPasswordForm(fill_data); + delegate_->FillPasswordForm(fill_data); return; } default: diff --git a/chrome/browser/password_manager/password_manager.h b/chrome/browser/password_manager/password_manager.h index 002cb29..2e7991c 100644 --- a/chrome/browser/password_manager/password_manager.h +++ b/chrome/browser/password_manager/password_manager.h @@ -8,14 +8,13 @@ #include "base/scoped_ptr.h" #include "base/stl_util-inl.h" #include "chrome/browser/login_model.h" +#include "chrome/browser/password_manager/password_form_manager.h" #include "chrome/browser/pref_member.h" -#include "chrome/browser/tab_contents/infobar_delegate.h" #include "webkit/glue/password_form.h" #include "webkit/glue/password_form_dom_manager.h" class PasswordFormManager; class PrefService; -class TabContents; // Per-tab password manager. Handles creation and management of UI elements, // receiving password form data from the renderer and managing the password @@ -23,9 +22,37 @@ class TabContents; // for purposes of supporting HTTP authentication dialogs. class PasswordManager : public LoginModel { public: + // An abstraction of operations in the external environment (TabContents) + // that the PasswordManager depends on. This allows for more targeted + // unit testing. + class Delegate { + public: + Delegate() {} + virtual ~Delegate() {} + + // Fill forms matching |form_data| in |tab_contents|. By default, goes + // through the RenderViewHost to FillPasswordForm. Tests can override this + // to sever the dependency on the entire rendering stack. + virtual void FillPasswordForm( + const webkit_glue::PasswordFormDomManager::FillData& form_data) = 0; + + // A mechanism to show an infobar in the current tab at our request. + virtual void AddSavePasswordInfoBar(PasswordFormManager* form_to_save) = 0; + + // Get the profile for which we are managing passwords. + virtual Profile* GetProfileForPasswordManager() = 0; + + // If any SSL certificate errors were encountered as a result of the last + // page load. + virtual bool DidLastPageLoadEncounterSSLErrors() = 0; + private: + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + static void RegisterUserPrefs(PrefService* prefs); - explicit PasswordManager(TabContents* tab_contents); + // The delegate passed in is required to outlive the PasswordManager. + explicit PasswordManager(Delegate* delegate); ~PasswordManager(); // Called by a PasswordFormManager when it decides a form can be autofilled @@ -88,8 +115,9 @@ class PasswordManager : public LoginModel { // time a user submits a login form and gets to the next page. scoped_ptr<PasswordFormManager> provisional_save_manager_; - // The containing TabContents. - TabContents* tab_contents_; + // Our delegate for carrying out external operations. This is typically the + // containing TabContents. + Delegate* delegate_; // The LoginModelObserver (i.e LoginView) requiring autofill. LoginModelObserver* observer_; diff --git a/chrome/browser/password_manager/password_manager_unittest.cc b/chrome/browser/password_manager/password_manager_unittest.cc new file mode 100644 index 0000000..cd2fb2b --- /dev/null +++ b/chrome/browser/password_manager/password_manager_unittest.cc @@ -0,0 +1,206 @@ +// Copyright (c) 2010 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 "base/file_path.h" +#include "base/string_util.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/pref_service.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" + +using webkit_glue::PasswordForm; +using testing::_; +using testing::DoAll; +using ::testing::Exactly; +using ::testing::WithArg; +using ::testing::Return; + +class MockPasswordManagerDelegate : public PasswordManager::Delegate { + public: + MOCK_METHOD1(FillPasswordForm, void( + const webkit_glue::PasswordFormDomManager::FillData&)); + MOCK_METHOD1(AddSavePasswordInfoBar, void(PasswordFormManager*)); + MOCK_METHOD0(GetProfileForPasswordManager, Profile*()); + MOCK_METHOD0(DidLastPageLoadEncounterSSLErrors, bool()); +}; + +class TestingProfileWithPasswordStore : public TestingProfile { + public: + explicit TestingProfileWithPasswordStore(PasswordStore* store) + : store_(store) {} + virtual PasswordStore* GetPasswordStore(ServiceAccessType access) { + return store_; + } + private: + PasswordStore* store_; +}; + +class MockPasswordStore : public PasswordStore { + public: + MOCK_METHOD1(RemoveLogin, void(const PasswordForm&)); + MOCK_METHOD2(GetLogins, int(const PasswordForm&, PasswordStoreConsumer*)); + MOCK_METHOD1(AddLogin, void(const PasswordForm&)); + MOCK_METHOD1(UpdateLogin, void(const PasswordForm&)); + MOCK_METHOD1(AddLoginImpl, void(const PasswordForm&)); + MOCK_METHOD1(UpdateLoginImpl, void(const PasswordForm&)); + MOCK_METHOD1(RemoveLoginImpl, void(const PasswordForm&)); + MOCK_METHOD2(RemoveLoginsCreatedBetweenImpl, void(const base::Time&, + const base::Time&)); + MOCK_METHOD2(GetLoginsImpl, void(GetLoginsRequest*, const PasswordForm&)); + MOCK_METHOD1(GetAutofillableLoginsImpl, void(GetLoginsRequest*)); + MOCK_METHOD1(GetBlacklistLoginsImpl, void(GetLoginsRequest*)); +}; + +ACTION_P2(InvokeConsumer, handle, forms) { + arg0->OnPasswordStoreRequestDone(handle, forms); +} + +ACTION_P(SaveToScopedPtr, scoped) { + scoped->reset(arg0); +} + +class PasswordManagerTest : public testing::Test { + public: + PasswordManagerTest() {} + protected: + + virtual void SetUp() { + store_ = new MockPasswordStore(); + profile_.reset(new TestingProfileWithPasswordStore(store_)); + EXPECT_CALL(delegate_, GetProfileForPasswordManager()) + .WillRepeatedly(Return(profile_.get())); + manager_.reset(new PasswordManager(&delegate_)); + EXPECT_CALL(delegate_, DidLastPageLoadEncounterSSLErrors()) + .WillRepeatedly(Return(false)); + } + + virtual void TearDown() { + manager_.reset(); + store_ = NULL; + } + + PasswordForm MakeSimpleForm() { + PasswordForm form; + form.origin = GURL("http://www.google.com/a/LoginAuth"); + form.action = GURL("http://www.google.com/a/Login"); + form.username_element = ASCIIToUTF16("Email"); + form.password_element = ASCIIToUTF16("Passwd"); + form.username_value = ASCIIToUTF16("google"); + form.password_value = ASCIIToUTF16("password"); + form.submit_element = ASCIIToUTF16("signIn"); + form.signon_realm = "http://www.google.com"; + return form; + } + + PasswordManager* manager() { return manager_.get(); } + + scoped_ptr<Profile> profile_; + scoped_refptr<MockPasswordStore> store_; + MockPasswordManagerDelegate delegate_; // Owned by manager_. + scoped_ptr<PasswordManager> manager_; +}; + +MATCHER_P(FormMatches, form, "") { + return form.signon_realm == arg.signon_realm && + form.origin == arg.origin && + form.action == arg.action && + form.username_element == arg.username_element && + form.password_element == arg.password_element && + form.submit_element == arg.submit_element; +} + +TEST_F(PasswordManagerTest, FormSubmitEmptyStore) { + // Test that observing a newly submitted form shows the save password bar. + std::vector<PasswordForm*> result; // Empty password store. + EXPECT_CALL(delegate_, FillPasswordForm(_)).Times(Exactly(0)); + EXPECT_CALL(*store_, GetLogins(_,_)) + .WillOnce(DoAll(WithArg<1>(InvokeConsumer(0, result)), Return(0))); + std::vector<PasswordForm> observed; + PasswordForm form(MakeSimpleForm()); + observed.push_back(form); + manager()->PasswordFormsSeen(observed); // The initial load. + + // And the form submit contract is to call ProvisionallySavePassword. + manager()->ProvisionallySavePassword(form); + + scoped_ptr<PasswordFormManager> form_to_save; + EXPECT_CALL(delegate_, AddSavePasswordInfoBar(_)) + .WillOnce(WithArg<0>(SaveToScopedPtr(&form_to_save))); + + // Now the password manager waits for the navigation to complete. + manager()->DidStopLoading(); + + EXPECT_FALSE(NULL == form_to_save.get()); + EXPECT_CALL(*store_, AddLogin(FormMatches(form))); + + // Simulate saving the form, as if the info bar was accepted. + form_to_save->Save(); +} + +TEST_F(PasswordManagerTest, FormSubmitNoGoodMatch) { + // Same as above, except with an existing form for the same signon realm, + // but different origin. Detailed cases like this are covered by + // PasswordFormManagerTest. + std::vector<PasswordForm*> result; + PasswordForm* existing_different = new PasswordForm(MakeSimpleForm()); + existing_different->username_value = ASCIIToUTF16("google2"); + result.push_back(existing_different); + EXPECT_CALL(delegate_, FillPasswordForm(_)); + EXPECT_CALL(*store_, GetLogins(_,_)) + .WillOnce(DoAll(WithArg<1>(InvokeConsumer(0, result)), Return(0))); + + std::vector<PasswordForm> observed; + PasswordForm form(MakeSimpleForm()); + observed.push_back(form); + manager()->PasswordFormsSeen(observed); // The initial load. + manager()->ProvisionallySavePassword(form); + + // We still expect an add, since we didn't have a good match. + scoped_ptr<PasswordFormManager> form_to_save; + EXPECT_CALL(delegate_, AddSavePasswordInfoBar(_)) + .WillOnce(WithArg<0>(SaveToScopedPtr(&form_to_save))); + + manager()->DidStopLoading(); + + EXPECT_CALL(*store_, AddLogin(FormMatches(form))); + // Simulate saving the form. + form_to_save->Save(); +} + +TEST_F(PasswordManagerTest, FormSeenThenLeftPage) { + std::vector<PasswordForm*> result; // Empty password store. + EXPECT_CALL(delegate_, FillPasswordForm(_)).Times(Exactly(0)); + EXPECT_CALL(*store_, GetLogins(_,_)) + .WillOnce(DoAll(WithArg<1>(InvokeConsumer(0, result)), Return(0))); + std::vector<PasswordForm> observed; + PasswordForm form(MakeSimpleForm()); + observed.push_back(form); + manager()->PasswordFormsSeen(observed); // The initial load. + + manager()->DidNavigate(); + + // No expected calls. + manager()->DidStopLoading(); +} + +TEST_F(PasswordManagerTest, FormSubmitFailedLogin) { + std::vector<PasswordForm*> result; // Empty password store. + EXPECT_CALL(delegate_, FillPasswordForm(_)).Times(Exactly(0)); + EXPECT_CALL(*store_, GetLogins(_,_)) + .WillOnce(DoAll(WithArg<1>(InvokeConsumer(0, result)), Return(0))); + std::vector<PasswordForm> observed; + PasswordForm form(MakeSimpleForm()); + observed.push_back(form); + manager()->PasswordFormsSeen(observed); // The initial load. + + manager()->ProvisionallySavePassword(form); + + manager()->PasswordFormsSeen(observed); // Simulated re-appearance. + + // No expected calls to the PasswordStore... + manager()->DidStopLoading(); +} |