blob: a2d98d34ee7321ccc5015e6ef4337b0aac66bb9b [file] [log] [blame]
// Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/password_manager/password_form_manager.h"
#include "chrome/browser/password_manager/password_manager.h"
#include "chrome/browser/password_manager/password_manager_delegate.h"
#include "chrome/browser/password_manager/password_store.h"
#include "chrome/browser/password_manager/password_store_factory.h"
#include "chrome/browser/password_manager/test_password_store.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/test/base/testing_profile.h"
#include "components/autofill/core/common/password_form.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
using autofill::PasswordForm;
using ::testing::Eq;
namespace {
class TestPasswordManagerDelegate : public PasswordManagerDelegate {
public:
explicit TestPasswordManagerDelegate(Profile* profile) : profile_(profile) {}
virtual void FillPasswordForm(
const autofill::PasswordFormFillData& form_data) OVERRIDE {}
virtual void AddSavePasswordInfoBarIfPermitted(
PasswordFormManager* form_to_save) OVERRIDE {}
virtual Profile* GetProfile() OVERRIDE { return profile_; }
virtual bool DidLastPageLoadEncounterSSLErrors() OVERRIDE { return false; }
private:
Profile* profile_;
};
class TestPasswordManager : public PasswordManager {
public:
explicit TestPasswordManager(PasswordManagerDelegate* delegate)
: PasswordManager(NULL, delegate) {}
virtual void Autofill(
const autofill::PasswordForm& form_for_autofill,
const autofill::PasswordFormMap& best_matches,
const autofill::PasswordForm& preferred_match,
bool wait_for_username) const OVERRIDE {}
};
} // namespace
class TestPasswordFormManager : public PasswordFormManager {
public:
TestPasswordFormManager(Profile* profile,
PasswordManager* manager,
const autofill::PasswordForm& observed_form,
bool ssl_valid)
: PasswordFormManager(profile, manager, NULL, observed_form, ssl_valid),
num_sent_messages_(0) {}
virtual void SendNotBlacklistedToRenderer() OVERRIDE {
++num_sent_messages_;
}
size_t num_sent_messages() {
return num_sent_messages_;
}
private:
size_t num_sent_messages_;
};
class PasswordFormManagerTest : public testing::Test {
public:
PasswordFormManagerTest() {
}
virtual void SetUp() {
observed_form_.origin = GURL("http://accounts.google.com/a/LoginAuth");
observed_form_.action = GURL("http://accounts.google.com/a/Login");
observed_form_.username_element = ASCIIToUTF16("Email");
observed_form_.password_element = ASCIIToUTF16("Passwd");
observed_form_.submit_element = ASCIIToUTF16("signIn");
observed_form_.signon_realm = "http://accounts.google.com";
saved_match_ = observed_form_;
saved_match_.origin = GURL("http://accounts.google.com/a/ServiceLoginAuth");
saved_match_.action = GURL("http://accounts.google.com/a/ServiceLogin");
saved_match_.preferred = true;
saved_match_.username_value = ASCIIToUTF16("test@gmail.com");
saved_match_.password_value = ASCIIToUTF16("test1");
saved_match_.other_possible_usernames.push_back(
ASCIIToUTF16("test2@gmail.com"));
profile_ = new TestingProfile();
}
virtual void TearDown() {
delete profile_;
}
PasswordForm* GetPendingCredentials(PasswordFormManager* p) {
return &p->pending_credentials_;
}
void SimulateMatchingPhase(PasswordFormManager* p, bool find_match) {
// Roll up the state to mock out the matching phase.
p->state_ = PasswordFormManager::POST_MATCHING_PHASE;
if (!find_match)
return;
PasswordForm* match = new PasswordForm(saved_match_);
// Heap-allocated form is owned by p.
p->best_matches_[match->username_value] = match;
p->preferred_match_ = match;
}
void SimulateFetchMatchingLoginsFromPasswordStore(
PasswordFormManager* manager) {
// Just need to update the internal states.
manager->state_ = PasswordFormManager::MATCHING_PHASE;
}
void SimulateResponseFromPasswordStore(
PasswordFormManager* manager,
const std::vector<PasswordForm*>& result) {
// Simply call the callback method when request done. This will transfer
// the ownership of the objects in |result| to the |manager|.
manager->OnGetPasswordStoreResults(result);
}
void SanitizePossibleUsernames(PasswordFormManager* p, PasswordForm* form) {
p->SanitizePossibleUsernames(form);
}
bool IgnoredResult(PasswordFormManager* p, PasswordForm* form) {
return p->IgnoreResult(*form);
}
Profile* profile() { return profile_; }
PasswordForm* observed_form() { return &observed_form_; }
PasswordForm* saved_match() { return &saved_match_; }
PasswordForm* CreateSavedMatch(bool blacklisted) {
// Owned by the caller of this method.
PasswordForm* match = new PasswordForm(saved_match_);
match->blacklisted_by_user = blacklisted;
return match;
}
private:
PasswordForm observed_form_;
PasswordForm saved_match_;
Profile* profile_;
};
TEST_F(PasswordFormManagerTest, TestNewLogin) {
PasswordFormManager* manager = new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false);
SimulateMatchingPhase(manager, false);
// User submits credentials for the observed form.
PasswordForm credentials = *observed_form();
credentials.username_value = saved_match()->username_value;
credentials.password_value = saved_match()->password_value;
credentials.preferred = true;
manager->ProvisionallySave(
credentials,
PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES);
// Successful login. The PasswordManager would instruct PasswordFormManager
// to save, which should know this is a new login.
EXPECT_TRUE(manager->IsNewLogin());
// Make sure the credentials that would be submitted on successful login
// are going to match the stored entry in the db.
EXPECT_EQ(observed_form()->origin.spec(),
GetPendingCredentials(manager)->origin.spec());
EXPECT_EQ(observed_form()->signon_realm,
GetPendingCredentials(manager)->signon_realm);
EXPECT_EQ(observed_form()->action,
GetPendingCredentials(manager)->action);
EXPECT_TRUE(GetPendingCredentials(manager)->preferred);
EXPECT_EQ(saved_match()->password_value,
GetPendingCredentials(manager)->password_value);
EXPECT_EQ(saved_match()->username_value,
GetPendingCredentials(manager)->username_value);
// Now, suppose the user re-visits the site and wants to save an additional
// login for the site with a new username. In this case, the matching phase
// will yield the previously saved login.
SimulateMatchingPhase(manager, true);
// Set up the new login.
string16 new_user = ASCIIToUTF16("newuser");
string16 new_pass = ASCIIToUTF16("newpass");
credentials.username_value = new_user;
credentials.password_value = new_pass;
manager->ProvisionallySave(
credentials,
PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES);
// Again, the PasswordFormManager should know this is still a new login.
EXPECT_TRUE(manager->IsNewLogin());
// And make sure everything squares up again.
EXPECT_EQ(observed_form()->origin.spec(),
GetPendingCredentials(manager)->origin.spec());
EXPECT_EQ(observed_form()->signon_realm,
GetPendingCredentials(manager)->signon_realm);
EXPECT_TRUE(GetPendingCredentials(manager)->preferred);
EXPECT_EQ(new_pass,
GetPendingCredentials(manager)->password_value);
EXPECT_EQ(new_user,
GetPendingCredentials(manager)->username_value);
// Done.
delete manager;
}
TEST_F(PasswordFormManagerTest, TestUpdatePassword) {
// Create a PasswordFormManager with observed_form, as if we just
// saw this form and need to find matching logins.
PasswordFormManager* manager = new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false);
SimulateMatchingPhase(manager, true);
// User submits credentials for the observed form using a username previously
// stored, but a new password. Note that the observed form may have different
// origin URL (as it does in this case) than the saved_match, but we want to
// make sure the updated password is reflected in saved_match, because that is
// what we autofilled.
string16 new_pass = ASCIIToUTF16("newpassword");
PasswordForm credentials = *observed_form();
credentials.username_value = saved_match()->username_value;
credentials.password_value = new_pass;
credentials.preferred = true;
manager->ProvisionallySave(
credentials,
PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES);
// Successful login. The PasswordManager would instruct PasswordFormManager
// to save, and since this is an update, it should know not to save as a new
// login.
EXPECT_FALSE(manager->IsNewLogin());
// Make sure the credentials that would be submitted on successful login
// are going to match the stored entry in the db. (This verifies correct
// behaviour for bug 1074420).
EXPECT_EQ(GetPendingCredentials(manager)->origin.spec(),
saved_match()->origin.spec());
EXPECT_EQ(GetPendingCredentials(manager)->signon_realm,
saved_match()->signon_realm);
EXPECT_TRUE(GetPendingCredentials(manager)->preferred);
EXPECT_EQ(new_pass,
GetPendingCredentials(manager)->password_value);
// Done.
delete manager;
}
TEST_F(PasswordFormManagerTest, TestIgnoreResult) {
PasswordFormManager* manager = new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false);
// Make sure we don't match a PasswordForm if it was originally saved on
// an SSL-valid page and we are now on a page with invalid certificate.
saved_match()->ssl_valid = true;
EXPECT_TRUE(IgnoredResult(manager, saved_match()));
saved_match()->ssl_valid = false;
// Different paths for action / origin are okay.
saved_match()->action = GURL("http://www.google.com/b/Login");
saved_match()->origin = GURL("http://www.google.com/foo");
EXPECT_FALSE(IgnoredResult(manager, saved_match()));
// Done.
delete manager;
}
TEST_F(PasswordFormManagerTest, TestEmptyAction) {
scoped_ptr<PasswordFormManager> manager(new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false));
saved_match()->action = GURL();
SimulateMatchingPhase(manager.get(), true);
// User logs in with the autofilled username / password from saved_match.
PasswordForm login = *observed_form();
login.username_value = saved_match()->username_value;
login.password_value = saved_match()->password_value;
manager->ProvisionallySave(
login,
PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES);
EXPECT_FALSE(manager->IsNewLogin());
// We bless our saved PasswordForm entry with the action URL of the
// observed form.
EXPECT_EQ(observed_form()->action,
GetPendingCredentials(manager.get())->action);
}
TEST_F(PasswordFormManagerTest, TestUpdateAction) {
scoped_ptr<PasswordFormManager> manager(new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false));
SimulateMatchingPhase(manager.get(), true);
// User logs in with the autofilled username / password from saved_match.
PasswordForm login = *observed_form();
login.username_value = saved_match()->username_value;
login.password_value = saved_match()->password_value;
manager->ProvisionallySave(
login,
PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES);
EXPECT_FALSE(manager->IsNewLogin());
// The observed action URL is different from the previously saved one, and
// is the same as the one that would be submitted on successful login.
EXPECT_NE(observed_form()->action, saved_match()->action);
EXPECT_EQ(observed_form()->action,
GetPendingCredentials(manager.get())->action);
}
TEST_F(PasswordFormManagerTest, TestDynamicAction) {
scoped_ptr<PasswordFormManager> manager(new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false));
SimulateMatchingPhase(manager.get(), false);
PasswordForm login(*observed_form());
// The submitted action URL is different from the one observed on page load.
GURL new_action = GURL("http://www.google.com/new_action");
login.action = new_action;
manager->ProvisionallySave(
login,
PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES);
EXPECT_TRUE(manager->IsNewLogin());
// Check that the provisionally saved action URL is the same as the submitted
// action URL, not the one observed on page load.
EXPECT_EQ(new_action,
GetPendingCredentials(manager.get())->action);
}
TEST_F(PasswordFormManagerTest, TestAlternateUsername) {
// Need a MessageLoop for callbacks.
base::MessageLoop message_loop;
PasswordStoreFactory::GetInstance()->SetTestingFactory(
profile(), &TestPasswordStore::Create);
scoped_refptr<TestPasswordStore> password_store =
static_cast<TestPasswordStore*>(
PasswordStoreFactory::GetForProfile(profile(),
Profile::IMPLICIT_ACCESS).get());
TestPasswordManagerDelegate delegate(profile());
TestPasswordManager password_manager(&delegate);
scoped_ptr<TestPasswordFormManager> manager(new TestPasswordFormManager(
profile(), &password_manager, *observed_form(), false));
password_store->AddLogin(*saved_match());
manager->FetchMatchingLoginsFromPasswordStore();
content::RunAllPendingInMessageLoop();
// The saved match has the right username already.
PasswordForm login(*observed_form());
login.username_value = saved_match()->username_value;
login.password_value = saved_match()->password_value;
login.preferred = true;
manager->ProvisionallySave(
login,
PasswordFormManager::ALLOW_OTHER_POSSIBLE_USERNAMES);
EXPECT_FALSE(manager->IsNewLogin());
manager->Save();
content::RunAllPendingInMessageLoop();
// Should be only one password stored, and should not have
// |other_possible_usernames| set anymore.
TestPasswordStore::PasswordMap passwords = password_store->stored_passwords();
EXPECT_EQ(1U, passwords.size());
ASSERT_EQ(1U, passwords[saved_match()->signon_realm].size());
EXPECT_EQ(saved_match()->username_value,
passwords[saved_match()->signon_realm][0].username_value);
EXPECT_EQ(
0U,
passwords[saved_match()->signon_realm][0].
other_possible_usernames.size());
// This time use an alternate username
manager.reset(new TestPasswordFormManager(
profile(), &password_manager, *observed_form(), false));
password_store->Clear();
password_store->AddLogin(*saved_match());
manager->FetchMatchingLoginsFromPasswordStore();
content::RunAllPendingInMessageLoop();
string16 new_username = saved_match()->other_possible_usernames[0];
login.username_value = new_username;
manager->ProvisionallySave(
login,
PasswordFormManager::ALLOW_OTHER_POSSIBLE_USERNAMES);
EXPECT_FALSE(manager->IsNewLogin());
manager->Save();
content::RunAllPendingInMessageLoop();
// |other_possible_usernames| should also be empty, but username_value should
// be changed to match |new_username|
passwords = password_store->stored_passwords();
EXPECT_EQ(1U, passwords.size());
ASSERT_EQ(1U, passwords[saved_match()->signon_realm].size());
EXPECT_EQ(new_username,
passwords[saved_match()->signon_realm][0].username_value);
EXPECT_EQ(
0U,
passwords[saved_match()->signon_realm][0].
other_possible_usernames.size());
}
TEST_F(PasswordFormManagerTest, TestValidForms) {
// User submits credentials for the observed form.
PasswordForm credentials = *observed_form();
credentials.scheme = PasswordForm::SCHEME_HTML;
credentials.username_value = saved_match()->username_value;
credentials.password_value = saved_match()->password_value;
// Form with both username_element and password_element.
PasswordFormManager manager1(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager1, false);
EXPECT_TRUE(manager1.HasValidPasswordForm());
// Form without a username_element but with a password_element.
credentials.username_element.clear();
PasswordFormManager manager2(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager2, false);
EXPECT_FALSE(manager2.HasValidPasswordForm());
// Form without a password_element but with a username_element.
credentials.username_element = saved_match()->username_element;
credentials.password_element.clear();
PasswordFormManager manager3(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager3, false);
EXPECT_FALSE(manager3.HasValidPasswordForm());
// Form with neither a password_element nor a username_element.
credentials.username_element.clear();
credentials.password_element.clear();
PasswordFormManager manager4(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager4, false);
EXPECT_FALSE(manager4.HasValidPasswordForm());
}
TEST_F(PasswordFormManagerTest, TestValidFormsBasic) {
// User submits credentials for the observed form.
PasswordForm credentials = *observed_form();
credentials.scheme = PasswordForm::SCHEME_BASIC;
credentials.username_value = saved_match()->username_value;
credentials.password_value = saved_match()->password_value;
// Form with both username_element and password_element.
PasswordFormManager manager1(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager1, false);
EXPECT_TRUE(manager1.HasValidPasswordForm());
// Form without a username_element but with a password_element.
credentials.username_element.clear();
PasswordFormManager manager2(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager2, false);
EXPECT_TRUE(manager2.HasValidPasswordForm());
// Form without a password_element but with a username_element.
credentials.username_element = saved_match()->username_element;
credentials.password_element.clear();
PasswordFormManager manager3(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager3, false);
EXPECT_TRUE(manager3.HasValidPasswordForm());
// Form with neither a password_element nor a username_element.
credentials.username_element.clear();
credentials.password_element.clear();
PasswordFormManager manager4(profile(), NULL, NULL, credentials, false);
SimulateMatchingPhase(&manager4, false);
EXPECT_TRUE(manager4.HasValidPasswordForm());
}
TEST_F(PasswordFormManagerTest, TestSendNotBlacklistedMessage) {
// A dumb password manager.
TestPasswordManagerDelegate delegate(profile());
TestPasswordManager password_manager(&delegate);
// First time sign up attempt; No login result is found from password store;
// We should send the not blacklisted message.
scoped_ptr<TestPasswordFormManager> manager(new TestPasswordFormManager(
profile(), &password_manager, *observed_form(), false));
SimulateFetchMatchingLoginsFromPasswordStore(manager.get());
std::vector<PasswordForm*> result;
SimulateResponseFromPasswordStore(manager.get(), result);
EXPECT_EQ(1u, manager->num_sent_messages());
// Sign up attempt to previously visited sites; Login result is found from
// password store, and is not blacklisted; We should send the not blacklisted
// message.
manager.reset(new TestPasswordFormManager(
profile(), &password_manager, *observed_form(), false));
SimulateFetchMatchingLoginsFromPasswordStore(manager.get());
// We need add heap allocated objects to result.
result.push_back(CreateSavedMatch(false));
SimulateResponseFromPasswordStore(manager.get(), result);
EXPECT_EQ(1u, manager->num_sent_messages());
// Sign up attempt to previously visited sites; Login result is found from
// password store, but is blacklisted; We should not send the not blacklisted
// message.
manager.reset(new TestPasswordFormManager(
profile(), &password_manager, *observed_form(), false));
SimulateFetchMatchingLoginsFromPasswordStore(manager.get());
result.clear();
result.push_back(CreateSavedMatch(true));
SimulateResponseFromPasswordStore(manager.get(), result);
EXPECT_EQ(0u, manager->num_sent_messages());
}
TEST_F(PasswordFormManagerTest, TestSanitizePossibleUsernames) {
scoped_ptr<PasswordFormManager> manager(new PasswordFormManager(
profile(), NULL, NULL, *observed_form(), false));
PasswordForm credentials(*observed_form());
credentials.other_possible_usernames.push_back(ASCIIToUTF16("543-43-1234"));
credentials.other_possible_usernames.push_back(
ASCIIToUTF16("378282246310005"));
credentials.other_possible_usernames.push_back(
ASCIIToUTF16("other username"));
credentials.username_value = ASCIIToUTF16("test@gmail.com");
SanitizePossibleUsernames(manager.get(), &credentials);
// Possible credit card number and SSN are stripped.
std::vector<string16> expected;
expected.push_back(ASCIIToUTF16("other username"));
EXPECT_THAT(credentials.other_possible_usernames, Eq(expected));
credentials.other_possible_usernames.clear();
credentials.other_possible_usernames.push_back(ASCIIToUTF16("511-32-9830"));
credentials.other_possible_usernames.push_back(ASCIIToUTF16("duplicate"));
credentials.other_possible_usernames.push_back(ASCIIToUTF16("duplicate"));
credentials.other_possible_usernames.push_back(ASCIIToUTF16("random"));
credentials.other_possible_usernames.push_back(
ASCIIToUTF16("test@gmail.com"));
SanitizePossibleUsernames(manager.get(), &credentials);
// SSN, duplicate in |other_possible_usernames| and duplicate of
// |username_value| all removed.
expected.clear();
expected.push_back(ASCIIToUTF16("duplicate"));
expected.push_back(ASCIIToUTF16("random"));
EXPECT_THAT(credentials.other_possible_usernames, Eq(expected));
}