summaryrefslogtreecommitdiffstats
path: root/chrome/browser/password_manager
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-15 01:35:45 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-15 01:35:45 +0000
commitd330185b649cc66f60362c01cbead62e5b2545ba (patch)
treee71046177bf83524e11051db7698a0a588181eb2 /chrome/browser/password_manager
parent7236eb109ef090be1163c384584714c7d139ed98 (diff)
downloadchromium_src-d330185b649cc66f60362c01cbead62e5b2545ba.zip
chromium_src-d330185b649cc66f60362c01cbead62e5b2545ba.tar.gz
chromium_src-d330185b649cc66f60362c01cbead62e5b2545ba.tar.bz2
Move password manager files into a subdir
Review URL: http://codereview.chromium.org/18259 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8066 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/password_manager')
-rw-r--r--chrome/browser/password_manager/encryptor.cc65
-rw-r--r--chrome/browser/password_manager/encryptor.h44
-rw-r--r--chrome/browser/password_manager/encryptor_unittest.cc79
-rw-r--r--chrome/browser/password_manager/ie7_password.cc198
-rw-r--r--chrome/browser/password_manager/ie7_password.h47
-rw-r--r--chrome/browser/password_manager/password_form_manager.cc514
-rw-r--r--chrome/browser/password_manager/password_form_manager.h166
-rw-r--r--chrome/browser/password_manager/password_form_manager_unittest.cc196
-rw-r--r--chrome/browser/password_manager/password_manager.cc219
-rw-r--r--chrome/browser/password_manager/password_manager.h123
10 files changed, 1651 insertions, 0 deletions
diff --git a/chrome/browser/password_manager/encryptor.cc b/chrome/browser/password_manager/encryptor.cc
new file mode 100644
index 0000000..b5c0755
--- /dev/null
+++ b/chrome/browser/password_manager/encryptor.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2006-2008 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 "chrome/browser/password_manager/encryptor.h"
+
+#include <windows.h>
+#include <wincrypt.h>
+#include "base/string_util.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+bool Encryptor::EncryptWideString(const std::wstring& plaintext,
+ std::string* ciphertext) {
+ return EncryptString(WideToUTF8(plaintext), ciphertext);
+}
+
+bool Encryptor::DecryptWideString(const std::string& ciphertext,
+ std::wstring* plaintext){
+ std::string utf8;
+ if (!DecryptString(ciphertext, &utf8))
+ return false;
+
+ *plaintext = UTF8ToWide(utf8);
+ return true;
+}
+
+bool Encryptor::EncryptString(const std::string& plaintext,
+ std::string* ciphertext) {
+ DATA_BLOB input;
+ input.pbData = const_cast<BYTE*>(
+ reinterpret_cast<const BYTE*>(plaintext.data()));
+ input.cbData = static_cast<DWORD>(plaintext.length());
+
+ DATA_BLOB output;
+ BOOL result = CryptProtectData(&input, L"", NULL, NULL, NULL,
+ 0, &output);
+ if (!result)
+ return false;
+
+ // this does a copy
+ ciphertext->assign(reinterpret_cast<std::string::value_type*>(output.pbData),
+ output.cbData);
+
+ LocalFree(output.pbData);
+ return true;
+}
+
+bool Encryptor::DecryptString(const std::string& ciphertext,
+ std::string* plaintext){
+ DATA_BLOB input;
+ input.pbData = const_cast<BYTE*>(
+ reinterpret_cast<const BYTE*>(ciphertext.data()));
+ input.cbData = static_cast<DWORD>(ciphertext.length());
+
+ DATA_BLOB output;
+ BOOL result = CryptUnprotectData(&input, NULL, NULL, NULL, NULL,
+ 0, &output);
+ if(!result)
+ return false;
+
+ plaintext->assign(reinterpret_cast<char*>(output.pbData), output.cbData);
+ LocalFree(output.pbData);
+ return true;
+}
diff --git a/chrome/browser/password_manager/encryptor.h b/chrome/browser/password_manager/encryptor.h
new file mode 100644
index 0000000..7233960
--- /dev/null
+++ b/chrome/browser/password_manager/encryptor.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2006-2008 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.
+// A class for encrypting/decrypting strings
+
+#ifndef CHROME_BROWSER_ENCRYPTOR_H__
+#define CHROME_BROWSER_ENCRYPTOR_H__
+
+#include <string>
+
+#include "base/values.h"
+
+class Encryptor {
+public:
+ // Encrypt a wstring. The output (second argument) is
+ // really an array of bytes, but we're passing it back
+ // as a std::string
+ static bool EncryptWideString(const std::wstring& plaintext,
+ std::string* ciphertext);
+
+ // Decrypt an array of bytes obtained with EnctryptWideString
+ // back into a wstring. Note that the input (first argument)
+ // is a std::string, so you need to first get your (binary)
+ // data into a string.
+ static bool DecryptWideString(const std::string& ciphertext,
+ std::wstring* plaintext);
+
+ // Encrypt a string.
+ static bool EncryptString(const std::string& plaintext,
+ std::string* ciphertext);
+
+ // Decrypt an array of bytes obtained with EnctryptString
+ // back into a string. Note that the input (first argument)
+ // is a std::string, so you need to first get your (binary)
+ // data into a string.
+ static bool DecryptString(const std::string& ciphertext,
+ std::string* plaintext);
+
+private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Encryptor);
+};
+
+#endif // CHROME_BROWSER_ENCRYPTOR_H__
+
diff --git a/chrome/browser/password_manager/encryptor_unittest.cc b/chrome/browser/password_manager/encryptor_unittest.cc
new file mode 100644
index 0000000..a1bbd36
--- /dev/null
+++ b/chrome/browser/password_manager/encryptor_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2006-2008 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 "chrome/browser/password_manager/encryptor.h"
+
+#include <string>
+
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(EncryptorTest, WideEncryptionDecryption) {
+ std::wstring plaintext;
+ std::wstring result;
+ std::string utf8_plaintext;
+ std::string utf8_result;
+ std::string ciphertext;
+
+ // test borderline cases (empty strings)
+ EXPECT_TRUE(Encryptor::EncryptWideString(plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptWideString(ciphertext, &result));
+ EXPECT_EQ(plaintext, result);
+
+ // test a simple string
+ plaintext = L"hello";
+ EXPECT_TRUE(Encryptor::EncryptWideString(plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptWideString(ciphertext, &result));
+ EXPECT_EQ(plaintext, result);
+
+ // test unicode
+ std::wstring::value_type wchars[] = { 0xdbeb,0xdf1b,0x4e03,0x6708,0x8849,
+ 0x661f,0x671f,0x56db,0x597c,0x4e03,
+ 0x6708,0x56db,0x6708,0xe407,0xdbaf,
+ 0xdeb5,0x4ec5,0x544b,0x661f,0x671f,
+ 0x65e5,0x661f,0x671f,0x4e94,0xd8b1,
+ 0xdce1,0x7052,0x5095,0x7c0b,0xe586, 0};
+ plaintext = wchars;
+ utf8_plaintext = WideToUTF8(plaintext);
+ EXPECT_EQ(plaintext, UTF8ToWide(utf8_plaintext));
+ EXPECT_TRUE(Encryptor::EncryptWideString(plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptWideString(ciphertext, &result));
+ EXPECT_EQ(plaintext, result);
+ EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &utf8_result));
+ EXPECT_EQ(utf8_plaintext, WideToUTF8(result));
+
+ EXPECT_TRUE(Encryptor::EncryptString(utf8_plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptWideString(ciphertext, &result));
+ EXPECT_EQ(plaintext, result);
+ EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &utf8_result));
+ EXPECT_EQ(utf8_plaintext, WideToUTF8(result));
+}
+
+TEST(EncryptorTest, EncryptionDecryption) {
+ std::string plaintext;
+ std::string result;
+ std::string ciphertext;
+
+ // test borderline cases (empty strings)
+ EXPECT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &result));
+ EXPECT_EQ(plaintext, result);
+
+ // test a simple string
+ plaintext = "hello";
+ EXPECT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &result));
+ EXPECT_EQ(plaintext, result);
+
+ // Make sure it null terminates.
+ plaintext.assign("hello", 3);
+ EXPECT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext));
+ EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &result));
+ EXPECT_EQ(plaintext, "hel");
+}
+
+} // namespace
+
diff --git a/chrome/browser/password_manager/ie7_password.cc b/chrome/browser/password_manager/ie7_password.cc
new file mode 100644
index 0000000..c448f92
--- /dev/null
+++ b/chrome/browser/password_manager/ie7_password.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2006-2008 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 "chrome/browser/password_manager/ie7_password.h"
+
+#include <wincrypt.h>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+
+namespace {
+
+// Structures that IE7/IE8 use to store a username/password.
+// Some of the fields might have been incorrectly reverse engineered.
+struct PreHeader {
+ DWORD pre_header_size; // Size of this header structure. Always 12.
+ DWORD header_size; // Size of the real Header: sizeof(Header) +
+ // item_count * sizeof(Entry);
+ DWORD data_size; // Size of the data referenced by the entries.
+};
+
+struct Header {
+ char wick[4]; // The string "WICK". I don't know what it means.
+ DWORD fixed_header_size; // The size of this structure without the entries:
+ // sizeof(Header).
+ DWORD item_count; // Number of entries. It should always be 2. One for
+ // the username, and one for the password.
+ wchar_t two_letters[2]; // Two unknown bytes.
+ DWORD unknown[2]; // Two unknown DWORDs.
+};
+
+struct Entry {
+ DWORD offset; // Offset where the data referenced by this entry is
+ // located.
+ FILETIME time_stamp; // Timestamp when the password got added.
+ DWORD string_length; // The length of the data string.
+};
+
+// Main data structure.
+struct PasswordEntry {
+ PreHeader pre_header; // Contains the size of the different sections.
+ Header header; // Contains the number of items.
+ Entry entry[1]; // List of entries containing a string. The first one
+ // is the username, the second one if the password.
+};
+
+// Cleans up a crypt prov and a crypt hash.
+void CleanupHashContext(HCRYPTPROV prov, HCRYPTHASH hash) {
+ if (hash)
+ CryptDestroyHash(hash);
+ if (prov)
+ CryptReleaseContext(prov, 0);
+}
+
+} // namespace
+
+namespace ie7_password {
+
+bool GetUserPassFromData(const std::vector<unsigned char>& data,
+ std::wstring* username,
+ std::wstring* password) {
+ const PasswordEntry* information =
+ reinterpret_cast<const PasswordEntry*>(&data.front());
+
+ // Some expected values. If it's not what we expect we don't even try to
+ // understand the data.
+ if (information->pre_header.pre_header_size != sizeof(PreHeader))
+ return false;
+
+ if (information->header.item_count != 2) // Username and Password
+ return false;
+
+ if (information->header.fixed_header_size != sizeof(Header))
+ return false;
+
+ const uint8* ptr = &data.front();
+ const uint8* offset_to_data = ptr + information->pre_header.header_size +
+ information->pre_header.pre_header_size;
+
+ const Entry* user_entry = information->entry;
+ const Entry* pass_entry = user_entry+1;
+
+ *username = reinterpret_cast<const wchar_t*>(offset_to_data +
+ user_entry->offset);
+ *password = reinterpret_cast<const wchar_t*>(offset_to_data +
+ pass_entry->offset);
+ return true;
+}
+
+std::wstring GetUrlHash(const std::wstring& url) {
+ HCRYPTPROV prov = NULL;
+ HCRYPTHASH hash = NULL;
+
+ std::wstring lower_case_url = StringToLowerASCII(url);
+ BOOL result = CryptAcquireContext(&prov, 0, 0, PROV_RSA_FULL, 0);
+ if (!result) {
+ if (GetLastError() == NTE_BAD_KEYSET) {
+ // The keyset does not exist. Create one.
+ result = CryptAcquireContext(&prov, 0, 0, PROV_RSA_FULL, CRYPT_NEWKEYSET);
+ }
+ }
+
+ if (!result) {
+ DCHECK(false);
+ return std::wstring();
+ }
+
+ // Initialize the hash.
+ if (!CryptCreateHash(prov, CALG_SHA1, 0, 0, &hash)) {
+ CleanupHashContext(prov, hash);
+ DCHECK(false);
+ return std::wstring();
+ }
+
+ // Add the data to the hash.
+ const unsigned char* buffer =
+ reinterpret_cast<const unsigned char*>(lower_case_url.c_str());
+ DWORD data_len = static_cast<DWORD>((lower_case_url.size() + 1) *
+ sizeof(wchar_t));
+ if (!CryptHashData(hash, buffer, data_len, 0)) {
+ CleanupHashContext(prov, hash);
+ DCHECK(false);
+ return std::wstring();
+ }
+
+ // Get the size of the resulting hash.
+ DWORD hash_len = 0;
+ DWORD buffer_size = sizeof(hash_len);
+ if (!CryptGetHashParam(hash, HP_HASHSIZE,
+ reinterpret_cast<unsigned char*>(&hash_len),
+ &buffer_size, 0)) {
+ CleanupHashContext(prov, hash);
+ DCHECK(false);
+ return std::wstring();
+ }
+
+ // Get the hash data.
+ scoped_array<unsigned char> new_buffer(new unsigned char[hash_len]);
+ if (!CryptGetHashParam(hash, HP_HASHVAL, new_buffer.get(), &hash_len, 0)) {
+ CleanupHashContext(prov, hash);
+ DCHECK(false);
+ return std::wstring();
+ }
+
+ std::wstring url_hash;
+
+ // Transform the buffer to an hexadecimal string.
+ unsigned char checksum = 0;
+ for (DWORD i = 0; i < hash_len; ++i) {
+ checksum += new_buffer.get()[i];
+ url_hash += StringPrintf(L"%2.2X", new_buffer.get()[i]);
+ }
+
+ url_hash += StringPrintf(L"%2.2X", checksum);
+
+ CleanupHashContext(prov, hash);
+ return url_hash;
+}
+
+bool DecryptPassword(const std::wstring& url,
+ const std::vector<unsigned char>& data,
+ std::wstring* username, std::wstring* password) {
+ std::wstring lower_case_url = StringToLowerASCII(url);
+ DATA_BLOB input = {0};
+ DATA_BLOB output = {0};
+ DATA_BLOB url_key = {0};
+
+ input.pbData = const_cast<unsigned char*>(&data.front());
+ input.cbData = static_cast<DWORD>((data.size()) *
+ sizeof(std::string::value_type));
+
+ url_key.pbData = reinterpret_cast<unsigned char*>(
+ const_cast<wchar_t*>(lower_case_url.data()));
+ url_key.cbData = static_cast<DWORD>((lower_case_url.size() + 1) *
+ sizeof(std::wstring::value_type));
+
+ if (CryptUnprotectData(&input, NULL, &url_key, NULL, NULL,
+ CRYPTPROTECT_UI_FORBIDDEN, &output)) {
+ // Now that we have the decrypted information, we need to understand it.
+ std::vector<unsigned char> decrypted_data;
+ decrypted_data.resize(output.cbData);
+ memcpy(&decrypted_data.front(), output.pbData, output.cbData);
+
+ GetUserPassFromData(decrypted_data, username, password);
+
+ LocalFree(output.pbData);
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace ie7_password
+
diff --git a/chrome/browser/password_manager/ie7_password.h b/chrome/browser/password_manager/ie7_password.h
new file mode 100644
index 0000000..c90b8e2
--- /dev/null
+++ b/chrome/browser/password_manager/ie7_password.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_IE7_PASSWORD_H__
+#define CHROME_BROWSER_IE7_PASSWORD_H__
+
+#include <windows.h>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/time.h"
+
+// Contains the information read from the IE7/IE8 Storage2 key in the registry.
+struct IE7PasswordInfo {
+ // Hash of the url.
+ std::wstring url_hash;
+
+ // Encrypted data containing the username, password and some more undocumented
+ // fields.
+ std::vector<unsigned char> encrypted_data;
+
+ // When the login was imported.
+ base::Time date_created;
+};
+
+namespace ie7_password {
+
+// Parses a data structure to find the password and the username.
+bool GetUserPassFromData(const std::vector<unsigned char>& data,
+ std::wstring* username,
+ std::wstring* password);
+
+// Decrypts the username and password for a given data vector using the url as
+// the key.
+bool DecryptPassword(const std::wstring& url,
+ const std::vector<unsigned char>& data,
+ std::wstring* username, std::wstring* password);
+
+// Returns the hash of a url.
+std::wstring GetUrlHash(const std::wstring& url);
+
+} // namespace ie7_password
+
+#endif // CHROME_BROWSER_IE7_PASSWORD_H__
+
diff --git a/chrome/browser/password_manager/password_form_manager.cc b/chrome/browser/password_manager/password_form_manager.cc
new file mode 100644
index 0000000..757dc63
--- /dev/null
+++ b/chrome/browser/password_manager/password_form_manager.cc
@@ -0,0 +1,514 @@
+// Copyright (c) 2006-2008 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 "chrome/browser/password_manager/password_form_manager.h"
+
+#include <algorithm>
+
+#include "base/string_util.h"
+#include "chrome/browser/password_manager/ie7_password.h"
+#include "chrome/browser/password_manager/password_manager.h"
+#include "chrome/browser/profile.h"
+#include "webkit/glue/password_form_dom_manager.h"
+
+using base::Time;
+
+PasswordFormManager::PasswordFormManager(Profile* profile,
+ PasswordManager* password_manager,
+ const PasswordForm& observed_form,
+ bool ssl_valid)
+ : observed_form_(observed_form),
+ password_manager_(password_manager),
+ profile_(profile),
+ is_new_login_(true),
+ pending_login_query_(NULL),
+ preferred_match_(NULL),
+ best_matches_deleter_(&best_matches_),
+ state_(PRE_MATCHING_PHASE) {
+ DCHECK(profile_);
+ if (observed_form_.origin.is_valid())
+ SplitString(observed_form_.origin.path(), '/', &form_path_tokens_);
+ observed_form_.ssl_valid = ssl_valid;
+}
+
+PasswordFormManager::~PasswordFormManager() {
+ CancelLoginsQuery();
+}
+
+// TODO(timsteele): use a hash of some sort in the future?
+bool PasswordFormManager::DoesManage(const PasswordForm& form) const {
+ if (form.scheme != PasswordForm::SCHEME_HTML)
+ return observed_form_.signon_realm == form.signon_realm;
+
+ // HTML form case.
+ // At a minimum, username and password element must match.
+ if (!((form.username_element == observed_form_.username_element) &&
+ (form.password_element == observed_form_.password_element))) {
+ return false;
+ }
+
+ // The action URL must also match, but the form is allowed to have an empty
+ // action URL (See bug 1107719).
+ if (form.action.is_valid() && (form.action != observed_form_.action))
+ return false;
+
+ // If this is a replay of the same form in the case a user entered an invalid
+ // password, the origin of the new form may equal the action of the "first"
+ // form.
+ if (!((form.origin == observed_form_.origin) ||
+ (form.origin == observed_form_.action))) {
+ if (form.origin.SchemeIsSecure() &&
+ !observed_form_.origin.SchemeIsSecure()) {
+ // Compare origins, ignoring scheme. There is no easy way to do this
+ // with GURL because clearing the scheme would result in an invalid url.
+ // This is for some sites (such as Hotmail) that begin on an http page and
+ // head to https for the retry when password was invalid.
+ std::string::const_iterator after_scheme1 = form.origin.spec().begin() +
+ form.origin.scheme().length();
+ std::string::const_iterator after_scheme2 =
+ observed_form_.origin.spec().begin() +
+ observed_form_.origin.scheme().length();
+ return std::search(after_scheme1,
+ form.origin.spec().end(),
+ after_scheme2,
+ observed_form_.origin.spec().end())
+ != form.origin.spec().end();
+ }
+ return false;
+ }
+ return true;
+}
+
+bool PasswordFormManager::IsBlacklisted() {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+ if (preferred_match_ && preferred_match_->blacklisted_by_user)
+ return true;
+ return false;
+}
+
+void PasswordFormManager::PermanentlyBlacklist() {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+
+ // Configure the form about to be saved for blacklist status.
+ pending_credentials_.preferred = true;
+ pending_credentials_.blacklisted_by_user = true;
+ pending_credentials_.username_value.clear();
+ pending_credentials_.password_value.clear();
+
+ // Retroactively forget existing matches for this form, so we NEVER prompt or
+ // autofill it again.
+ if (!best_matches_.empty()) {
+ PasswordFormMap::const_iterator iter;
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+ for (iter = best_matches_.begin(); iter != best_matches_.end(); ++iter) {
+ // We want to remove existing matches for this form so that the exact
+ // origin match with |blackisted_by_user == true| is the only result that
+ // shows up in the future for this origin URL. However, we don't want to
+ // delete logins that were actually saved on a different page (hence with
+ // different origin URL) and just happened to match this form because of
+ // the scoring algorithm. See bug 1204493.
+ if (iter->second->origin == observed_form_.origin)
+ web_data_service->RemoveLogin(*iter->second);
+ }
+ }
+
+ // Save the pending_credentials_ entry marked as blacklisted.
+ SaveAsNewLogin();
+}
+
+bool PasswordFormManager::IsNewLogin() {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+ return is_new_login_;
+}
+
+void PasswordFormManager::ProvisionallySave(const PasswordForm& credentials) {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+ DCHECK(DoesManage(credentials));
+
+ // Make sure the important fields stay the same as the initially observed or
+ // autofilled ones, as they may have changed if the user experienced a login
+ // failure.
+ // Look for these credentials in the list containing auto-fill entries.
+ PasswordFormMap::const_iterator it =
+ best_matches_.find(credentials.username_value);
+ if (it != best_matches_.end()) {
+ // The user signed in with a login we autofilled.
+ pending_credentials_ = *it->second;
+ is_new_login_ = false;
+ // If the user selected credentials we autofilled from a PasswordForm
+ // that contained no action URL (IE6/7 imported passwords, for example),
+ // bless it with the action URL from the observed form. See bug 1107719.
+ if (pending_credentials_.action.is_empty())
+ pending_credentials_.action = observed_form_.action;
+ } else {
+ pending_credentials_ = observed_form_;
+ pending_credentials_.username_value = credentials.username_value;
+ }
+
+ pending_credentials_.password_value = credentials.password_value;
+ pending_credentials_.preferred = credentials.preferred;
+}
+
+void PasswordFormManager::Save() {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+ DCHECK(!profile_->IsOffTheRecord());
+
+ if (IsNewLogin())
+ SaveAsNewLogin();
+ else
+ UpdateLogin();
+}
+
+void PasswordFormManager::FetchMatchingLoginsFromWebDatabase() {
+ DCHECK_EQ(state_, PRE_MATCHING_PHASE);
+ DCHECK(!pending_login_query_);
+ state_ = MATCHING_PHASE;
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+ pending_login_query_ = web_data_service->GetLogins(observed_form_, this);
+}
+
+void PasswordFormManager::FetchMatchingIE7LoginFromWebDatabase() {
+ DCHECK_EQ(state_, PRE_MATCHING_PHASE);
+ DCHECK(!pending_login_query_);
+ state_ = MATCHING_PHASE;
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+
+ IE7PasswordInfo info;
+ std::wstring url = ASCIIToWide(observed_form_.origin.spec());
+ info.url_hash = ie7_password::GetUrlHash(url);
+ pending_login_query_ = web_data_service->GetIE7Login(info, this);
+}
+
+bool PasswordFormManager::HasCompletedMatching() {
+ return state_ == POST_MATCHING_PHASE;
+}
+
+void PasswordFormManager::OnRequestDone(WebDataService::Handle h,
+ const WDTypedResult* result) {
+ // Get the result from the database into a usable form.
+ const WDResult<std::vector<PasswordForm*> >* r =
+ static_cast<const WDResult<std::vector<PasswordForm*> >*>(result);
+ std::vector<PasswordForm*> logins_result = r->GetValue();
+ // Note that the result gets deleted after this call completes, but we own
+ // the PasswordForm objects pointed to by the result vector, thus we keep
+ // copies to a minimum here.
+
+ int best_score = 0;
+ std::vector<PasswordForm> empties; // Empty-path matches in result set.
+ for (size_t i = 0; i < logins_result.size(); i++) {
+ if (IgnoreResult(*logins_result[i])) {
+ delete logins_result[i];
+ continue;
+ }
+ // Score and update best matches.
+ int current_score = ScoreResult(*logins_result[i]);
+ // This check is here so we can append empty path matches in the event
+ // they don't score as high as others and aren't added to best_matches_.
+ // This is most commonly imported firefox logins. We skip blacklisted
+ // ones because clearly we don't want to autofill them, and secondly
+ // because they only mean something when we have no other matches already
+ // saved in Chrome - in which case they'll make it through the regular
+ // scoring flow below by design. Note signon_realm == origin implies empty
+ // path logins_result, since signon_realm is a prefix of origin for HTML
+ // password forms.
+ // TODO(timsteele): Bug 1269400. We probably should do something more
+ // elegant for any shorter-path match instead of explicitly handling empty
+ // path matches.
+ if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) &&
+ (observed_form_.signon_realm == logins_result[i]->origin.spec()) &&
+ (current_score > 0) && (!logins_result[i]->blacklisted_by_user)){
+ empties.push_back(*logins_result[i]);
+ }
+
+ if (current_score < best_score) {
+ delete logins_result[i];
+ continue;
+ }
+ if (current_score == best_score) {
+ best_matches_[logins_result[i]->username_value] = logins_result[i];
+ } else if (current_score > best_score) {
+ best_score = current_score;
+ // This new login has a better score than all those up to this point
+ // Note 'this' owns all the PasswordForms in best_matches_.
+ STLDeleteValues(&best_matches_);
+ best_matches_.clear();
+ preferred_match_ = NULL; // Don't delete, its owned by best_matches_.
+ best_matches_[logins_result[i]->username_value] = logins_result[i];
+ }
+ preferred_match_ = logins_result[i]->preferred ? logins_result[i]
+ : preferred_match_;
+ }
+ // We're done matching now.
+ state_ = POST_MATCHING_PHASE;
+
+ if (best_score <= 0) {
+ state_ = PRE_MATCHING_PHASE;
+ FetchMatchingIE7LoginFromWebDatabase();
+ return;
+ }
+
+ for (std::vector<PasswordForm>::const_iterator it = empties.begin();
+ it != empties.end(); ++it) {
+ // If we don't already have a result with the same username, add the
+ // lower-scored empty-path match (if it had equal score it would already be
+ // in best_matches_).
+ if (best_matches_.find(it->username_value) == best_matches_.end())
+ best_matches_[it->username_value] = new PasswordForm(*it);
+ }
+
+ // Its possible we have at least one match but have no preferred_match_,
+ // because a user may have chosen to 'Forget' the preferred match. So we
+ // just pick the first one and whichever the user selects for submit will
+ // be saved as preferred.
+ DCHECK(!best_matches_.empty());
+ if (!preferred_match_)
+ preferred_match_ = best_matches_.begin()->second;
+
+ // Now we determine if the user told us to ignore this site in the past.
+ // If they haven't, we proceed to auto-fill.
+ if (!preferred_match_->blacklisted_by_user) {
+ password_manager_->Autofill(observed_form_, best_matches_,
+ preferred_match_);
+ }
+}
+
+void PasswordFormManager::OnIE7RequestDone(WebDataService::Handle h,
+ const WDTypedResult* result) {
+ // Get the result from the database into a usable form.
+ const WDResult<IE7PasswordInfo>* r =
+ static_cast<const WDResult<IE7PasswordInfo>*>(result);
+ IE7PasswordInfo result_value = r->GetValue();
+
+ state_ = POST_MATCHING_PHASE;
+
+ if (!result_value.encrypted_data.empty()) {
+ // We got a result.
+ // Delete the entry. If it's good we will add it to the real saved password
+ // table.
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+ web_data_service->RemoveIE7Login(result_value);
+
+ std::wstring username;
+ std::wstring password;
+ std::wstring url = ASCIIToWide(observed_form_.origin.spec());
+ if (!ie7_password::DecryptPassword(url, result_value.encrypted_data,
+ &username, &password)) {
+ return;
+ }
+
+ PasswordForm* auto_fill = new PasswordForm(observed_form_);
+ auto_fill->username_value = username;
+ auto_fill->password_value = password;
+ auto_fill->preferred = true;
+ auto_fill->ssl_valid = observed_form_.origin.SchemeIsSecure();
+ auto_fill->date_created = result_value.date_created;
+ // Add this PasswordForm to the saved password table.
+ web_data_service->AddLogin(*auto_fill);
+
+ if (IgnoreResult(*auto_fill)) {
+ delete auto_fill;
+ return;
+ }
+
+ best_matches_[auto_fill->username_value] = auto_fill;
+ preferred_match_ = auto_fill;
+ password_manager_->Autofill(observed_form_, best_matches_,
+ preferred_match_);
+ }
+}
+
+void PasswordFormManager::OnWebDataServiceRequestDone(WebDataService::Handle h,
+ const WDTypedResult* result) {
+ DCHECK_EQ(state_, MATCHING_PHASE);
+ DCHECK_EQ(pending_login_query_, h);
+ DCHECK(result);
+ pending_login_query_ = NULL;
+
+ if (!result)
+ return;
+
+ switch (result->GetType()) {
+ case PASSWORD_RESULT: {
+ OnRequestDone(h, result);
+ break;
+ }
+ case PASSWORD_IE7_RESULT: {
+ OnIE7RequestDone(h, result);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+bool PasswordFormManager::IgnoreResult(const PasswordForm& form) const {
+ // Ignore change password forms until we have some change password
+ // functionality
+ if (observed_form_.old_password_element.length() != 0) {
+ return true;
+ }
+ // Don't match an invalid SSL form with one saved under secure
+ // circumstances.
+ if (form.ssl_valid && !observed_form_.ssl_valid) {
+ return true;
+ }
+ return false;
+}
+
+void PasswordFormManager::SaveAsNewLogin() {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+ DCHECK(IsNewLogin());
+ // The new_form is being used to sign in, so it is preferred.
+ DCHECK(pending_credentials_.preferred);
+ // new_form contains the same basic data as observed_form_ (because its the
+ // same form), but with the newly added credentials.
+
+ DCHECK(!profile_->IsOffTheRecord());
+
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::IMPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+ pending_credentials_.date_created = Time::Now();
+ web_data_service->AddLogin(pending_credentials_);
+}
+
+void PasswordFormManager::UpdateLogin() {
+ DCHECK_EQ(state_, POST_MATCHING_PHASE);
+ DCHECK(preferred_match_);
+ // If we're doing an Update, its because we autofilled a form and the user
+ // submitted it with a possibly new password value, page security, or selected
+ // one of the non-preferred matches, thus requiring a swap of preferred bits.
+ DCHECK(!IsNewLogin() && pending_credentials_.preferred);
+ DCHECK(!profile_->IsOffTheRecord());
+
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::IMPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+
+ // Update all matches to reflect new preferred status.
+ PasswordFormMap::iterator iter;
+ for (iter = best_matches_.begin(); iter != best_matches_.end(); iter++) {
+ if ((iter->second->username_value != pending_credentials_.username_value) &&
+ iter->second->preferred) {
+ // This wasn't the selected login but it used to be preferred.
+ iter->second->preferred = false;
+ web_data_service->UpdateLogin(*iter->second);
+ }
+ }
+ // Update the new preferred login.
+ // Note origin.spec().length > signon_realm.length implies the origin has a
+ // path, since signon_realm is a prefix of origin for HTML password forms.
+ if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) &&
+ (observed_form_.origin.spec().length() >
+ observed_form_.signon_realm.length()) &&
+ (observed_form_.signon_realm == pending_credentials_.origin.spec())) {
+ // The user logged in successfully with one of our autofilled logins on a
+ // page with non-empty path, but the autofilled entry was initially saved/
+ // imported with an empty path. Rather than just mark this entry preferred,
+ // we create a more specific copy for this exact page and leave the "master"
+ // unchanged. This is to prevent the case where that master login is used
+ // on several sites (e.g site.com/a and site.com/b) but the user actually
+ // has a different preference on each site. For example, on /a, he wants the
+ // general empty-path login so it is flagged as preferred, but on /b he logs
+ // in with a different saved entry - we don't want to remove the preferred
+ // status of the former because upon return to /a it won't be the default-
+ // fill match.
+ // TODO(timsteele): Bug 1188626 - expire the master copies.
+ PasswordForm copy(pending_credentials_);
+ copy.origin = observed_form_.origin;
+ copy.action = observed_form_.action;
+ web_data_service->AddLogin(copy);
+ } else {
+ web_data_service->UpdateLogin(pending_credentials_);
+ }
+}
+
+void PasswordFormManager::CancelLoginsQuery() {
+ if (!pending_login_query_)
+ return;
+ WebDataService* web_data_service =
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS);
+ if (!web_data_service) {
+ NOTREACHED();
+ return;
+ }
+ web_data_service->CancelRequest(pending_login_query_);
+ pending_login_query_ = NULL;
+}
+
+int PasswordFormManager::ScoreResult(const PasswordForm& candidate) const {
+ DCHECK_EQ(state_, MATCHING_PHASE);
+ // For scoring of candidate login data:
+ // The most important element that should match is the origin, followed by
+ // the action, the password name, the submit button name, and finally the
+ // username input field name.
+ // Exact origin match gives an addition of 32 (1 << 5) + # of matching url
+ // dirs.
+ // Partial match gives an addition of 16 (1 << 4) + # matching url dirs
+ // That way, a partial match cannot trump an exact match even if
+ // the partial one matches all other attributes (action, elements) (and
+ // regardless of the matching depth in the URL path).
+ int score = 0;
+ if (candidate.origin == observed_form_.origin) {
+ // This check is here for the most common case which
+ // is we have a single match in the db for the given host,
+ // so we don't generally need to walk the entire URL path (the else
+ // clause).
+ score += (1 << 5) + static_cast<int>(form_path_tokens_.size());
+ } else {
+ // Walk the origin URL paths one directory at a time to see how
+ // deep the two match.
+ std::vector<std::string> candidate_path_tokens;
+ SplitString(candidate.origin.path(), '/', &candidate_path_tokens);
+ size_t depth = 0;
+ size_t max_dirs = std::min(form_path_tokens_.size(),
+ candidate_path_tokens.size());
+ while ((depth < max_dirs) && (form_path_tokens_[depth] ==
+ candidate_path_tokens[depth])) {
+ depth++;
+ score++;
+ }
+ // do we have a partial match?
+ score += (depth > 0) ? 1 << 4 : 0;
+ }
+ if (observed_form_.scheme == PasswordForm::SCHEME_HTML) {
+ if (candidate.action == observed_form_.action)
+ score += 1 << 3;
+ if (candidate.password_element == observed_form_.password_element)
+ score += 1 << 2;
+ if (candidate.submit_element == observed_form_.submit_element)
+ score += 1 << 1;
+ if (candidate.username_element == observed_form_.username_element)
+ score += 1 << 0;
+ }
+
+ return score;
+}
+
diff --git a/chrome/browser/password_manager/password_form_manager.h b/chrome/browser/password_manager/password_form_manager.h
new file mode 100644
index 0000000..9d0d8a6
--- /dev/null
+++ b/chrome/browser/password_manager/password_form_manager.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_PASSWORD_FORM_MANAGER_H__
+#define CHROME_BROWSER_PASSWORD_FORM_MANAGER_H__
+
+#include "chrome/common/stl_util-inl.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "webkit/glue/password_form.h"
+
+class PasswordManager;
+class Profile;
+
+// Per-password-form-{on-page, dialog} class responsible for interactions
+// between a given form, the per-tab PasswordManager, and the web database.
+class PasswordFormManager : public WebDataServiceConsumer {
+ public:
+ // web_data_service allows access to current profile's Web Data
+ // password_manager owns this object
+ // form_on_page is the form that may be submitted and could need login data.
+ // ssl_valid represents the security of the page containing observed_form,
+ // used to filter login results from database.
+ PasswordFormManager(Profile* profile,
+ PasswordManager* password_manager,
+ const PasswordForm& observed_form,
+ bool ssl_valid);
+ virtual ~PasswordFormManager();
+
+ // Compare basic data of observed_form_ with argument.
+ bool DoesManage(const PasswordForm& form) const;
+
+ // Retrieves potential matching logins from the database.
+ void FetchMatchingLoginsFromWebDatabase();
+ void FetchMatchingIE7LoginFromWebDatabase();
+
+ // Simple state-check to verify whether this object as received a callback
+ // from the web database and completed its matching phase. Note that the
+ // callback in question occurs on the same (and only) main thread from which
+ // instances of this class are ever used, but it is required since it is
+ // conceivable that a user (or ui test) could attempt to submit a login
+ // prompt before the callback has occured, which would InvokeLater a call to
+ // PasswordManager::ProvisionallySave, which would interact with this object
+ // before the db has had time to answer with matching password entries.
+ // This is intended to be a one-time check; if the return value is false the
+ // expectation is caller will give up. This clearly won't work if you put it
+ // in a loop and wait for matching to complete; you're (supposed to be) on
+ // the same thread!
+ bool HasCompletedMatching();
+
+ // Determines if the user opted to 'never remember' passwords for this form.
+ bool IsBlacklisted();
+
+ // Used by PasswordManager to determine whether or not to display
+ // a SavePasswordBar when given the green light to save the PasswordForm
+ // managed by this.
+ bool IsNewLogin();
+
+ // WebDataServiceConsumer implementation. If matches were found
+ // (in *result), this is where we determine we need to autofill.
+ virtual void OnWebDataServiceRequestDone(WebDataService::Handle h,
+ const WDTypedResult* result);
+
+ // Determines if we need to autofill given the results of the query.
+ void OnRequestDone(WebDataService::Handle h, const WDTypedResult* result);
+
+ // Determines if we need to autofill given the results of the query in the
+ // ie7_password table.
+ void OnIE7RequestDone(WebDataService::Handle h, const WDTypedResult* result);
+
+ // A user opted to 'never remember' passwords for this form.
+ // Blacklist it so that from now on when it is seen we ignore it.
+ void PermanentlyBlacklist();
+
+ // If the user has submitted observed_form_, provisionally hold on to
+ // the submitted credentials until we are told by PasswordManager whether
+ // or not the login was successful.
+ void ProvisionallySave(const PasswordForm& credentials);
+
+ // Handles save-as-new or update of the form managed by this manager.
+ // Note the basic data of updated_credentials must match that of
+ // observed_form_ (e.g DoesManage(pending_credentials_) == true).
+ void Save();
+
+ private:
+ friend class PasswordFormManagerTest;
+ // Called by destructor to ensure if this object is deleted, no potential
+ // outstanding callbacks can call OnWebDataServiceRequestDone.
+ void CancelLoginsQuery();
+
+ // Helper for OnWebDataServiceRequestDone to determine whether or not
+ // the given result form is worth scoring.
+ bool IgnoreResult(const PasswordForm& form) const;
+
+ // Helper for Save in the case that best_matches.size() == 0, meaning
+ // we have no prior record of this form/username/password and the user
+ // has opted to 'Save Password'.
+ void SaveAsNewLogin();
+
+ // Helper for OnWebDataServiceRequestDone to score an individual result
+ // against the observed_form_.
+ int ScoreResult(const PasswordForm& form) const;
+
+ // Helper for Save in the case that best_matches.size() > 0, meaning
+ // we have at least one match for this form/username/password. This
+ // Updates the form managed by this object, as well as any matching forms
+ // that now need to have preferred bit changed, since updated_credentials
+ // is now implicitly 'preferred'.
+ void UpdateLogin();
+
+ // Set of PasswordForms from the DB that best match the form
+ // being managed by this. Use a map instead of vector, because we most
+ // frequently require lookups by username value in IsNewLogin.
+ PasswordFormMap best_matches_;
+
+ // Cleans up when best_matches_ goes out of scope.
+ STLValueDeleter<PasswordFormMap> best_matches_deleter_;
+
+ // The PasswordForm from the page or dialog managed by this.
+ PasswordForm observed_form_;
+
+ // The origin url path of observed_form_ tokenized, for convenience when
+ // scoring.
+ std::vector<std::string> form_path_tokens_;
+
+ // Stores updated credentials when the form was submitted but success is
+ // still unknown.
+ PasswordForm pending_credentials_;
+
+ // Whether pending_credentials_ stores a new login or is an update
+ // to an existing one.
+ bool is_new_login_;
+
+ // PasswordManager owning this.
+ const PasswordManager* const password_manager_;
+
+ // Handle to any pending WebDataService::GetLogins query.
+ WebDataService::Handle pending_login_query_;
+
+ // Convenience pointer to entry in best_matches_ that is marked
+ // as preferred. This is only allowed to be null if there are no best matches
+ // at all, since there will always be one preferred login when there are
+ // multiple matches (when first saved, a login is marked preferred).
+ const PasswordForm* preferred_match_;
+
+ typedef enum {
+ PRE_MATCHING_PHASE, // Have not yet invoked a GetLogins query to find
+ // matching login information from DB.
+ MATCHING_PHASE, // We've made a GetLogins request, but
+ // haven't received or finished processing result.
+ POST_MATCHING_PHASE // We've queried the DB and processed matching
+ // login results.
+ } PasswordFormManagerState;
+
+ // State of matching process, used to verify that we don't call methods
+ // assuming we've already processed the web data request for matching logins,
+ // when we actually haven't.
+ PasswordFormManagerState state_;
+
+ // The profile from which we get the WebDataService.
+ Profile* profile_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(PasswordFormManager);
+};
+#endif // CHROME_BROWSER_PASSWORD_FORM_MANAGER_H__
+
diff --git a/chrome/browser/password_manager/password_form_manager_unittest.cc b/chrome/browser/password_manager/password_form_manager_unittest.cc
new file mode 100644
index 0000000..ffcee20
--- /dev/null
+++ b/chrome/browser/password_manager/password_form_manager_unittest.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2006-2008 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 "chrome/browser/password_manager/password_form_manager.h"
+#include "chrome/browser/password_manager/password_manager.h"
+#include "chrome/browser/profile_manager.h"
+#include "chrome/test/testing_profile.h"
+#include "webkit/glue/password_form.h"
+
+class PasswordFormManagerTest : public testing::Test {
+ public:
+ PasswordFormManagerTest() {
+ }
+ virtual void SetUp() {
+ observed_form_.origin = GURL(L"http://www.google.com/a/LoginAuth");
+ observed_form_.action = GURL(L"http://www.google.com/a/Login");
+ observed_form_.username_element = L"Email";
+ observed_form_.password_element = L"Passwd";
+ observed_form_.submit_element = L"signIn";
+ observed_form_.signon_realm = "http://www.google.com";
+
+ saved_match_ = observed_form_;
+ saved_match_.origin = GURL(L"http://www.google.com/a/ServiceLoginAuth");
+ saved_match_.action = GURL(L"http://www.google.com/a/ServiceLogin");
+ saved_match_.preferred = true;
+ saved_match_.username_value = L"test@gmail.com";
+ saved_match_.password_value = L"test1";
+ 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;
+ }
+
+ 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_; }
+
+ private:
+ PasswordForm observed_form_;
+ PasswordForm saved_match_;
+ std::wstring test_dir_;
+ Profile* profile_;
+};
+
+TEST_F(PasswordFormManagerTest, TestNewLogin) {
+ PasswordFormManager* manager = new PasswordFormManager(
+ profile(), 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);
+
+ // 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_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.
+ std::wstring new_user = L"newuser";
+ std::wstring new_pass = L"newpass";
+ credentials.username_value = new_user;
+ credentials.password_value = new_pass;
+ manager->ProvisionallySave(credentials);
+
+ // 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, *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.
+ std::wstring new_pass = L"newpassword";
+ PasswordForm credentials = *observed_form();
+ credentials.username_value = saved_match()->username_value;
+ credentials.password_value = new_pass;
+ credentials.preferred = true;
+ manager->ProvisionallySave(credentials);
+
+ // 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, *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(L"http://www.google.com/b/Login");
+ saved_match()->origin = GURL(L"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, *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);
+ 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);
+}
+
diff --git a/chrome/browser/password_manager/password_manager.cc b/chrome/browser/password_manager/password_manager.cc
new file mode 100644
index 0000000..4187151
--- /dev/null
+++ b/chrome/browser/password_manager/password_manager.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 2006-2008 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 "chrome/browser/password_manager/password_manager.h"
+
+#include "base/string_util.h"
+#include "chrome/app/theme/theme_resources.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/common/stl_util-inl.h"
+
+#include "chromium_strings.h"
+#include "generated_resources.h"
+
+// static
+void PasswordManager::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterBooleanPref(prefs::kPasswordManagerEnabled, true);
+}
+
+PasswordManager::PasswordManager(WebContents* web_contents)
+ : ConfirmInfoBarDelegate(web_contents),
+ web_contents_(web_contents),
+ observer_(NULL),
+ login_managers_deleter_(&pending_login_managers_) {
+ password_manager_enabled_.Init(prefs::kPasswordManagerEnabled,
+ web_contents->profile()->GetPrefs(), NULL);
+}
+
+PasswordManager::~PasswordManager() {
+ // Remove any InfoBars we may be showing.
+ web_contents_->RemoveInfoBar(this);
+}
+
+void PasswordManager::ProvisionallySavePassword(PasswordForm form) {
+ if (!web_contents_->controller() || !web_contents_->profile() ||
+ web_contents_->profile()->IsOffTheRecord() || !*password_manager_enabled_)
+ return;
+
+ // No password to save? Then don't.
+ if (form.password_value.empty())
+ return;
+
+ LoginManagers::iterator iter;
+ PasswordFormManager* manager = NULL;
+ for (iter = pending_login_managers_.begin();
+ iter != pending_login_managers_.end(); iter++) {
+ if ((*iter)->DoesManage(form)) {
+ manager = *iter;
+ break;
+ }
+ }
+ // If we didn't find a manager, this means a form was submitted without
+ // first loading the page containing the form. Don't offer to save
+ // passwords in this case.
+ if (!manager)
+ return;
+
+ // If we found a manager but it didn't finish matching yet, the user has
+ // tried to submit credentials before we had time to even find matching
+ // results for the given form and autofill. If this is the case, we just
+ // give up.
+ if (!manager->HasCompletedMatching())
+ return;
+
+ // Also get out of here if the user told us to 'never remember' passwords for
+ // this form.
+ if (manager->IsBlacklisted())
+ return;
+
+ form.ssl_valid = form.origin.SchemeIsSecure() &&
+ !web_contents_->controller()->ssl_manager()->
+ ProcessedSSLErrorFromRequest();
+ form.preferred = true;
+ manager->ProvisionallySave(form);
+ provisional_save_manager_.reset(manager);
+ pending_login_managers_.erase(iter);
+ // We don't care about the rest of the forms on the page now that one
+ // was selected.
+ STLDeleteElements(&pending_login_managers_);
+}
+
+void PasswordManager::DidNavigate() {
+ // As long as this navigation isn't due to a currently pending
+ // password form submit, we're ready to reset and move on.
+ if (!provisional_save_manager_.get() && !pending_login_managers_.empty())
+ STLDeleteElements(&pending_login_managers_);
+}
+
+void PasswordManager::ClearProvisionalSave() {
+ provisional_save_manager_.reset();
+}
+
+void PasswordManager::DidStopLoading() {
+ if (!provisional_save_manager_.get())
+ return;
+
+ DCHECK(!web_contents_->profile()->IsOffTheRecord());
+ DCHECK(!provisional_save_manager_->IsBlacklisted());
+
+ if (!web_contents_->profile() ||
+ !web_contents_->profile()->GetWebDataService(Profile::IMPLICIT_ACCESS))
+ return;
+ if (!web_contents_->controller())
+ return;
+
+ if (provisional_save_manager_->IsNewLogin()) {
+ web_contents_->AddInfoBar(this);
+ pending_decision_manager_.reset(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.
+ provisional_save_manager_->Save();
+ provisional_save_manager_.reset();
+ }
+}
+
+void PasswordManager::PasswordFormsSeen(const std::vector<PasswordForm>& forms) {
+ if (!web_contents_->profile() ||
+ !web_contents_->profile()->GetWebDataService(Profile::EXPLICIT_ACCESS))
+ return;
+ if (!web_contents_->controller())
+ return;
+ if (!*password_manager_enabled_)
+ return;
+
+ // Ask the SSLManager for current security.
+ bool had_ssl_error = web_contents_->controller()->ssl_manager()->
+ ProcessedSSLErrorFromRequest();
+
+ std::vector<PasswordForm>::const_iterator iter;
+ for (iter = forms.begin(); iter != forms.end(); iter++) {
+ if (provisional_save_manager_.get() &&
+ provisional_save_manager_->DoesManage(*iter)) {
+ // The form trying to be saved has immediately re-appeared. Assume
+ // login failure and abort this save. Fallback to pending login state
+ // since the user may try again.
+ pending_login_managers_.push_back(provisional_save_manager_.release());
+ // Don't delete the login managers since the user may try again
+ // and we want to be able to save in that case.
+ break;
+ } else {
+ bool ssl_valid = iter->origin.SchemeIsSecure() && !had_ssl_error;
+ PasswordFormManager* manager =
+ new PasswordFormManager(web_contents_->profile(),
+ this, *iter, ssl_valid);
+ pending_login_managers_.push_back(manager);
+ manager->FetchMatchingLoginsFromWebDatabase();
+ }
+ }
+}
+
+void PasswordManager::Autofill(const PasswordForm& form_for_autofill,
+ const PasswordFormMap& best_matches,
+ const PasswordForm* const preferred_match) const {
+ DCHECK(web_contents_);
+ DCHECK(preferred_match);
+ switch (form_for_autofill.scheme) {
+ case PasswordForm::SCHEME_HTML: {
+ // Note the check above is required because the observer_ for a non-HTML
+ // schemed password form may have been freed, so we need to distinguish.
+ bool action_mismatch = form_for_autofill.action.GetWithEmptyPath() !=
+ preferred_match->action.GetWithEmptyPath();
+ scoped_ptr<PasswordFormDomManager::FillData> fill_data(
+ PasswordFormDomManager::CreateFillData(form_for_autofill,
+ best_matches, preferred_match,
+ action_mismatch));
+ web_contents_->render_view_host()->FillPasswordForm(*fill_data);
+ return;
+ }
+ default:
+ if (observer_)
+ observer_->OnAutofillDataAvailable(preferred_match->username_value,
+ preferred_match->password_value);
+ }
+}
+
+// PasswordManager, ConfirmInfoBarDelegate implementation: ---------------------
+
+void PasswordManager::InfoBarClosed() {
+ pending_decision_manager_.reset(NULL);
+}
+
+std::wstring PasswordManager::GetMessageText() const {
+ return l10n_util::GetString(IDS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT);
+}
+
+SkBitmap* PasswordManager::GetIcon() const {
+ return ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_INFOBAR_SAVE_PASSWORD);
+}
+
+int PasswordManager::GetButtons() const {
+ return BUTTON_OK | BUTTON_CANCEL;
+}
+
+std::wstring PasswordManager::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();
+}
+
+bool PasswordManager::Accept() {
+ pending_decision_manager_->Save();
+ return true;
+}
+
+bool PasswordManager::Cancel() {
+ pending_decision_manager_->PermanentlyBlacklist();
+ return true;
+}
+
diff --git a/chrome/browser/password_manager/password_manager.h b/chrome/browser/password_manager/password_manager.h
new file mode 100644
index 0000000..74c5554
--- /dev/null
+++ b/chrome/browser/password_manager/password_manager.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_PASSWORD_MANAGER_H__
+#define CHROME_BROWSER_PASSWORD_MANAGER_H__
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/password_manager/password_form_manager.h"
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+#include "chrome/browser/views/login_view.h"
+#include "chrome/common/pref_member.h"
+#include "chrome/common/stl_util-inl.h"
+#include "webkit/glue/password_form.h"
+#include "webkit/glue/password_form_dom_manager.h"
+
+class PrefService;
+class WebContents;
+
+// Per-tab password manager. Handles creation and management of UI elements,
+// receiving password form data from the renderer and managing the password
+// database through the WebDataService. The PasswordManager is a LoginModel
+// for purposes of supporting HTTP authentication dialogs.
+class PasswordManager : public views::LoginModel,
+ public ConfirmInfoBarDelegate {
+ public:
+ static void RegisterUserPrefs(PrefService* prefs);
+
+ explicit PasswordManager(WebContents* web_contents);
+ ~PasswordManager();
+
+ // Called by a PasswordFormManager when it decides a form can be autofilled
+ // on the page.
+ void Autofill(const PasswordForm& form_for_autofill,
+ const PasswordFormMap& best_matches,
+ const PasswordForm* const preferred_match) const;
+
+ // Notification that the user navigated away from the current page.
+ // Unless this is a password form submission, for our purposes this
+ // means we're done with the current page, so we can clean-up.
+ void DidNavigate();
+
+ // Show a prompt to save submitted password if it is a new username for
+ // the form, or else just update the stored value.
+ void DidStopLoading();
+
+ // Notifies the password manager that password forms were parsed on the page.
+ void PasswordFormsSeen(const std::vector<PasswordForm>& forms);
+
+ // When a form is submitted, we prepare to save the password but wait
+ // until we decide the user has successfully logged in. This is step 1
+ // of 2 (see SavePassword).
+ void ProvisionallySavePassword(PasswordForm form);
+
+ // Clear any pending saves
+ void ClearProvisionalSave();
+
+ // LoginModel implementation.
+ virtual void SetObserver(views::LoginModelObserver* observer) {
+ observer_ = observer;
+ }
+
+ private:
+ // Overridden from ConfirmInfoBarDelegate:
+ virtual void InfoBarClosed();
+ virtual std::wstring GetMessageText() const;
+ virtual SkBitmap* GetIcon() const;
+ virtual int GetButtons() const;
+ virtual std::wstring GetButtonLabel(InfoBarButton button) const;
+ virtual bool Accept();
+ virtual bool Cancel();
+
+ // Note about how a PasswordFormManager can transition from
+ // pending_login_managers_ to {provisional_save, pending_decision_}manager_.
+ //
+ // 1. form "seen"
+ // | new
+ // | ___ pending_decision
+ // pending_login -- form submit --> provisional_save ___/
+ // ^ | \___ (update DB)
+ // | fail
+ // |-----------<------<---------| !new
+ //
+ // When a form is "seen" on a page, a PasswordFormManager is created
+ // and stored in this collection until user navigates away from page.
+ typedef std::vector<PasswordFormManager*> LoginManagers;
+ LoginManagers pending_login_managers_;
+
+ // Deleter for pending_login_managers_ when PasswordManager is deleted (e.g
+ // tab closes) on a page with a password form, thus containing login managers.
+ STLElementDeleter<LoginManagers> login_managers_deleter_;
+
+ // When the user submits a password/credential, this contains the
+ // PasswordFormManager for the form in question until we deem the login
+ // attempt to have succeeded (as in valid credentials). If it fails, we
+ // send the PasswordFormManager back to the pending_login_managers_ set.
+ // Scoped in case PasswordManager gets deleted (e.g tab closes) between the
+ // time a user submits a login form and gets to the next page.
+ scoped_ptr<PasswordFormManager> provisional_save_manager_;
+
+ // After a successful *new* login attempt, we take the PasswordFormManager in
+ // provisional_save_manager_ and move it here 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
+ // pending_decision_manager_.
+ scoped_ptr<PasswordFormManager> pending_decision_manager_;
+
+ // The containing WebContents
+ WebContents* web_contents_;
+
+ // The LoginModelObserver (i.e LoginView) requiring autofill.
+ views::LoginModelObserver* observer_;
+
+ // Set to false to disable the password manager (will no longer fill
+ // passwords or ask you if you want to save passwords).
+ BooleanPrefMember password_manager_enabled_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(PasswordManager);
+};
+
+#endif
+