blob: 307cdc37ea665eec76c662609012098456eba0cd [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 "chrome/browser/password_manager/password_store_win.h"
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/test/histogram_tester.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/test/base/testing_profile.h"
#include "components/os_crypt/ie7_password_win.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/browser/password_store_consumer.h"
#include "components/password_manager/core/browser/webdata/logins_table.h"
#include "components/password_manager/core/browser/webdata/password_web_data_service_win.h"
#include "components/webdata/common/web_database_service.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "crypto/wincrypt_shim.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using autofill::PasswordForm;
using password_manager::LoginDatabase;
using password_manager::PasswordFormData;
using password_manager::PasswordStore;
using password_manager::PasswordStoreConsumer;
using password_manager::UnorderedPasswordFormElementsAre;
using testing::_;
using testing::DoAll;
using testing::IsEmpty;
using testing::WithArg;
namespace {
class MockPasswordStoreConsumer : public PasswordStoreConsumer {
public:
MOCK_METHOD1(OnGetPasswordStoreResultsConstRef,
void(const std::vector<std::unique_ptr<PasswordForm>>&));
// GMock cannot mock methods with move-only args.
void OnGetPasswordStoreResults(
std::vector<std::unique_ptr<PasswordForm>> results) override {
OnGetPasswordStoreResultsConstRef(results);
}
};
class MockWebDataServiceConsumer : public WebDataServiceConsumer {
public:
MOCK_METHOD0(OnWebDataServiceRequestDoneStub, void());
// GMock cannot mock methods with move-only args.
void OnWebDataServiceRequestDone(WebDataServiceBase::Handle h,
std::unique_ptr<WDTypedResult> result) {
OnWebDataServiceRequestDoneStub();
}
};
} // anonymous namespace
class PasswordStoreWinTest : public testing::Test {
protected:
PasswordStoreWinTest() {}
bool CreateIE7PasswordInfo(const std::wstring& url,
const base::Time& created,
IE7PasswordInfo* info) {
// Copied from chrome/browser/importer/importer_unittest.cc
// The username is "abcdefgh" and the password "abcdefghijkl".
unsigned char data[] =
"\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00"
"\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00"
"\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01"
"\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76"
"\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00"
"\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00"
"\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00"
"\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00"
"\x6c\x00\x00\x00";
DATA_BLOB input = {0};
DATA_BLOB url_key = {0};
DATA_BLOB output = {0};
input.pbData = data;
input.cbData = sizeof(data);
url_key.pbData =
reinterpret_cast<unsigned char*>(const_cast<wchar_t*>(url.data()));
url_key.cbData =
static_cast<DWORD>((url.size() + 1) * sizeof(std::wstring::value_type));
if (!CryptProtectData(&input, nullptr, &url_key, nullptr, nullptr,
CRYPTPROTECT_UI_FORBIDDEN, &output))
return false;
std::vector<unsigned char> encrypted_data;
encrypted_data.resize(output.cbData);
memcpy(&encrypted_data.front(), output.pbData, output.cbData);
LocalFree(output.pbData);
info->url_hash = ie7_password::GetUrlHash(url);
info->encrypted_data = encrypted_data;
info->date_created = created;
return true;
}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
profile_.reset(new TestingProfile());
base::FilePath path = temp_dir_.GetPath().AppendASCII("web_data_test");
// TODO(pkasting): http://crbug.com/740773 This should likely be sequenced,
// not single-threaded.
auto db_task_runner =
base::CreateSingleThreadTaskRunnerWithTraits({base::MayBlock()});
wdbs_ = new WebDatabaseService(path, base::ThreadTaskRunnerHandle::Get(),
db_task_runner);
// Need to add at least one table so the database gets created.
wdbs_->AddTable(std::unique_ptr<WebDatabaseTable>(new LoginsTable()));
wdbs_->LoadDatabase();
wds_ =
new PasswordWebDataService(wdbs_, base::ThreadTaskRunnerHandle::Get(),
WebDataServiceBase::ProfileErrorCallback());
wds_->Init();
}
void TearDown() override {
if (store_.get())
store_->ShutdownOnUIThread();
if (wds_) {
wds_->ShutdownOnUISequence();
wds_ = nullptr;
}
if (wdbs_) {
wdbs_->ShutdownDatabase();
wdbs_ = nullptr;
}
content::RunAllTasksUntilIdle();
}
base::FilePath test_login_db_file_path() const {
return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("login_test"));
}
PasswordStoreWin* CreatePasswordStore() {
return new PasswordStoreWin(
base::MakeUnique<LoginDatabase>(test_login_db_file_path()), wds_.get());
}
content::TestBrowserThreadBundle test_browser_thread_bundle_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<TestingProfile> profile_;
scoped_refptr<PasswordWebDataService> wds_;
scoped_refptr<WebDatabaseService> wdbs_;
scoped_refptr<PasswordStore> store_;
};
MATCHER(EmptyWDResult, "") {
return static_cast<
const WDResult<std::vector<std::unique_ptr<PasswordForm>>>*>(arg)
->GetValue()
.empty();
}
TEST_F(PasswordStoreWinTest, ReportIE7NoImport) {
base::HistogramTester histogram_tester;
store_ = CreatePasswordStore();
EXPECT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
MockPasswordStoreConsumer consumer;
PasswordStore::FormDigest observed_form(PasswordForm::SCHEME_HTML,
"http://example.com/origin",
GURL("http://example.com/origin"));
EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(_));
store_->GetLogins(observed_form, &consumer);
content::RunAllTasksUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.IE7LookupResult",
password_manager::metrics_util::IE7_RESULTS_ABSENT, 1);
}
TEST_F(PasswordStoreWinTest, ReportIE7Import) {
base::HistogramTester histogram_tester;
IE7PasswordInfo password_info;
ASSERT_TRUE(CreateIE7PasswordInfo(L"http://example.com/origin",
base::Time::FromDoubleT(1),
&password_info));
// This IE7 password will be retrieved by the GetLogins call.
wds_->AddIE7Login(password_info);
store_ = CreatePasswordStore();
EXPECT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
MockPasswordStoreConsumer consumer;
PasswordStore::FormDigest observed_form(PasswordForm::SCHEME_HTML,
"http://example.com/origin",
GURL("http://example.com/origin"));
EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(_));
store_->GetLogins(observed_form, &consumer);
content::RunAllTasksUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.IE7LookupResult",
password_manager::metrics_util::IE7_RESULTS_PRESENT, 1);
}
TEST_F(PasswordStoreWinTest, ConvertIE7Login) {
IE7PasswordInfo password_info;
ASSERT_TRUE(CreateIE7PasswordInfo(L"http://example.com/origin",
base::Time::FromDoubleT(1),
&password_info));
// Verify the URL hash
ASSERT_EQ(L"39471418FF5453FEEB3731E382DEB5D53E14FAF9B5",
password_info.url_hash);
// This IE7 password will be retrieved by the GetLogins call.
wds_->AddIE7Login(password_info);
store_ = CreatePasswordStore();
EXPECT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
MockPasswordStoreConsumer consumer;
PasswordFormData form_data = {
PasswordForm::SCHEME_HTML,
"http://example.com/",
"http://example.com/origin",
"http://example.com/action",
L"submit_element",
L"username_element",
L"password_element",
nullptr,
nullptr,
true,
1,
};
PasswordStore::FormDigest form(*FillPasswordFormWithData(form_data));
// The returned form will not have 'action' or '*_element' fields set. This
// is because credentials imported from IE don't have this information.
PasswordFormData expected_form_data = {
PasswordForm::SCHEME_HTML,
"http://example.com/",
"http://example.com/origin",
nullptr,
nullptr,
nullptr,
nullptr,
L"abcdefgh",
L"abcdefghijkl",
true,
1,
};
std::vector<std::unique_ptr<PasswordForm>> expected_forms;
expected_forms.push_back(PasswordFormFromData(expected_form_data));
// The IE7 password should be returned.
EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_forms)));
store_->GetLogins(form, &consumer);
content::RunAllTasksUntilIdle();
}
TEST_F(PasswordStoreWinTest, OutstandingWDSQueries) {
store_ = CreatePasswordStore();
EXPECT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
PasswordFormData form_data = {
PasswordForm::SCHEME_HTML,
"http://example.com/",
"http://example.com/origin",
"http://example.com/action",
L"submit_element",
L"username_element",
L"password_element",
nullptr,
nullptr,
true,
1,
};
PasswordStore::FormDigest form(*FillPasswordFormWithData(form_data));
MockPasswordStoreConsumer consumer;
store_->GetLogins(form, &consumer);
// Release the PSW and the WDS before the query can return.
store_->ShutdownOnUIThread();
store_ = nullptr;
wds_->ShutdownOnUISequence();
wds_ = nullptr;
wdbs_->ShutdownDatabase();
wdbs_ = nullptr;
content::RunAllTasksUntilIdle();
}
TEST_F(PasswordStoreWinTest, MultipleWDSQueriesOnDifferentSequences) {
IE7PasswordInfo password_info;
ASSERT_TRUE(CreateIE7PasswordInfo(L"http://example.com/origin",
base::Time::FromDoubleT(1),
&password_info));
wds_->AddIE7Login(password_info);
store_ = CreatePasswordStore();
EXPECT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
MockPasswordStoreConsumer password_consumer;
PasswordFormData form_data = {
PasswordForm::SCHEME_HTML,
"http://example.com/",
"http://example.com/origin",
"http://example.com/action",
L"submit_element",
L"username_element",
L"password_element",
nullptr,
nullptr,
true,
1,
};
PasswordStore::FormDigest form(*FillPasswordFormWithData(form_data));
// The returned form will not have 'action' or '*_element' fields set. This
// is because credentials imported from IE don't have this information.
PasswordFormData expected_form_data = {
PasswordForm::SCHEME_HTML,
"http://example.com/",
"http://example.com/origin",
nullptr,
nullptr,
nullptr,
nullptr,
L"abcdefgh",
L"abcdefghijkl",
true,
1,
};
std::vector<std::unique_ptr<PasswordForm>> expected_forms;
expected_forms.push_back(PasswordFormFromData(expected_form_data));
// The IE7 password should be returned.
EXPECT_CALL(password_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_forms)));
store_->GetLogins(form, &password_consumer);
MockWebDataServiceConsumer wds_consumer;
EXPECT_CALL(wds_consumer, OnWebDataServiceRequestDoneStub());
wds_->GetIE7Login(password_info, &wds_consumer);
content::RunAllTasksUntilIdle();
}
TEST_F(PasswordStoreWinTest, EmptyLogins) {
store_ = CreatePasswordStore();
store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
PasswordFormData form_data = {
PasswordForm::SCHEME_HTML,
"http://example.com/",
"http://example.com/origin",
"http://example.com/action",
L"submit_element",
L"username_element",
L"password_element",
nullptr,
nullptr,
true,
1,
};
PasswordStore::FormDigest form(*FillPasswordFormWithData(form_data));
MockPasswordStoreConsumer consumer;
EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
store_->GetLogins(form, &consumer);
content::RunAllTasksUntilIdle();
}
TEST_F(PasswordStoreWinTest, EmptyBlacklistLogins) {
store_ = CreatePasswordStore();
store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
MockPasswordStoreConsumer consumer;
EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
store_->GetBlacklistLogins(&consumer);
content::RunAllTasksUntilIdle();
}
TEST_F(PasswordStoreWinTest, EmptyAutofillableLogins) {
store_ = CreatePasswordStore();
store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
MockPasswordStoreConsumer consumer;
EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
store_->GetAutofillableLogins(&consumer);
content::RunAllTasksUntilIdle();
}