summaryrefslogtreecommitdiffstats
path: root/chrome/browser/password_manager
diff options
context:
space:
mode:
authortim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-23 17:25:37 +0000
committertim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-23 17:25:37 +0000
commitea68c06a2c5182d1a06ce804306aeab17ed75dc5 (patch)
tree9d2c818c5982c43a989f24c43f7763ab9b3466c8 /chrome/browser/password_manager
parentfee679820e1d129068a22e0576b26de53e675673 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/password_manager/password_manager.cc101
-rw-r--r--chrome/browser/password_manager/password_manager.h38
-rw-r--r--chrome/browser/password_manager/password_manager_unittest.cc206
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();
+}