Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test importing passwords from Chrome #82

Merged
merged 7 commits into from
Apr 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ test("brave_unit_tests") {
"//base/test:test_support",
"//brave/browser",
"//brave/common",
"//brave/components/content_settings/core/browser",
"//brave/extensions",
"//brave/renderer",
"//brave/utility",
Expand Down
49 changes: 49 additions & 0 deletions test/data/import/chrome/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Each subdirectory in this folder is a Chrome user data directory [0] populated
with test data for testing the Chrome profile importer.

The files in each test user data directory were created via the following
process:

1. Create a new profile in Chrome.
2. Simulate browsing activity (visit sites, save bookmarks, passwords, etc.).
3. Copy relevant files from the the new profile's user data directory to the
test user data directory.

If special care was taken to generate a certain type of test data, the process
should be documented below.

# Login Data

## Using the mock keychain

Some of the fields in the Login Data SQLite db, such as saved passwords, are
encrypted by Chrome's OSCrypt class. To test them reliably, Chrome was
launched with the `--use-mock-keychain` argument for the session where the
test login data was simulated:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --use-mock-keychain

When `--use-mock- keychain` is enabled, a fixed key is used to encrypt all
data that is encrypted with OSCrypt. We may then enable the mock keychain
during tests so the corresponding decryption will use the same fixed key.

As far as I know, this argument only has an effect on macOS.

## Triggering a password save prompt in Chrome

A web form submission must meet several criteria in order for it to cause
Chrome to prompt to save a password. To aid in creating additional test cases,
use `trigger_password_save_prompt.html`, included in this directory. To use,
serve on a local web server and navigate your test Chrome profile to the
corresponding address. For example, you could run Python 3's built-in
`http.server` in the same directory:

python3 -m http.server 8080 --bind 127.0.0.1

Then navigate to `127.0.0.1:8080/trigger_password_save_prompt.html` in Chrome.
Enter any username and password, and click `Submit`. You should be prompted to
save the password.

I recommend using a different port number to easily distinguish each test case.

[0]: https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md
Binary file added test/data/import/chrome/default/Login Data
Binary file not shown.
5 changes: 5 additions & 0 deletions test/data/import/chrome/trigger_password_save_prompt.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<form action="https://example.com" method="post">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Submit">
</form>
137 changes: 92 additions & 45 deletions utility/importer/chrome_importer_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/utf_string_conversions.h"
#include "base/path_service.h"
#include "chrome/common/chrome_paths.h"
Expand All @@ -15,9 +16,12 @@
#include "chrome/common/importer/importer_url_row.h"
#include "chrome/common/importer/mock_importer_bridge.h"
#include "components/favicon_base/favicon_usage_data.h"
#include "components/os_crypt/os_crypt_mocker.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::ASCIIToUTF16;
using base::UTF16ToASCII;
using ::testing::_;

// In order to test the Chrome import functionality effectively, we store a
// simulated Chrome profile directory containing dummy data files with the
Expand All @@ -31,25 +35,47 @@ base::FilePath GetTestChromeProfileDir(const std::string& profile) {
.AppendASCII(profile);
}

TEST(ChromeImporterTest, ImportHistory) {
using ::testing::_;

base::FilePath profile_dir = GetTestChromeProfileDir("default");
ASSERT_TRUE(base::DirectoryExists(profile_dir));
scoped_refptr<ChromeImporter> importer = new ChromeImporter;
importer::SourceProfile profile;
profile.source_path = profile_dir;
scoped_refptr<MockImporterBridge> bridge = new MockImporterBridge;
class ChromeImporterTest : public ::testing::Test {
protected:
void SetUpChromeProfile() {
// Creates a new profile in a new subdirectory in the temp directory.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
base::FilePath test_path = temp_dir_.GetPath().AppendASCII("ChromeImporterTest");
base::DeleteFile(test_path, true);
base::CreateDirectory(test_path);
profile_dir_ = test_path.AppendASCII("profile");

base::FilePath data_dir = GetTestChromeProfileDir("default");
ASSERT_TRUE(base::DirectoryExists(data_dir));
ASSERT_TRUE(base::CopyDirectory(data_dir, profile_dir_, true));

profile_.source_path = profile_dir_;
}

void SetUp() override {
SetUpChromeProfile();
importer_ = new ChromeImporter;
bridge_ = new MockImporterBridge;
}

base::ScopedTempDir temp_dir_;
base::FilePath profile_dir_;
importer::SourceProfile profile_;
scoped_refptr<ChromeImporter> importer_;
scoped_refptr<MockImporterBridge> bridge_;
};

TEST_F(ChromeImporterTest, ImportHistory) {
std::vector<ImporterURLRow> history;

EXPECT_CALL(*bridge, NotifyStarted());
EXPECT_CALL(*bridge, NotifyItemStarted(importer::HISTORY));
EXPECT_CALL(*bridge, SetHistoryItems(_, _))
EXPECT_CALL(*bridge_, NotifyStarted());
EXPECT_CALL(*bridge_, NotifyItemStarted(importer::HISTORY));
EXPECT_CALL(*bridge_, SetHistoryItems(_, _))
.WillOnce(::testing::SaveArg<0>(&history));
EXPECT_CALL(*bridge, NotifyItemEnded(importer::HISTORY));
EXPECT_CALL(*bridge, NotifyEnded());
EXPECT_CALL(*bridge_, NotifyItemEnded(importer::HISTORY));
EXPECT_CALL(*bridge_, NotifyEnded());

importer->StartImport(profile, importer::HISTORY, bridge.get());
importer_->StartImport(profile_, importer::HISTORY, bridge_.get());

ASSERT_EQ(4u, history.size());
EXPECT_EQ("https://brave.com/", history[0].url.spec());
Expand All @@ -58,25 +84,17 @@ TEST(ChromeImporterTest, ImportHistory) {
EXPECT_EQ("https://www.nytimes.com/", history[3].url.spec());
}

TEST(ChromeImporterTest, ImportBookmarks) {
using ::testing::_;

base::FilePath profile_dir = GetTestChromeProfileDir("default");
ASSERT_TRUE(base::DirectoryExists(profile_dir));
scoped_refptr<ChromeImporter> importer = new ChromeImporter;
importer::SourceProfile profile;
profile.source_path = profile_dir;
scoped_refptr<MockImporterBridge> bridge = new MockImporterBridge;
TEST_F(ChromeImporterTest, ImportBookmarks) {
std::vector<ImportedBookmarkEntry> bookmarks;

EXPECT_CALL(*bridge, NotifyStarted());
EXPECT_CALL(*bridge, NotifyItemStarted(importer::FAVORITES));
EXPECT_CALL(*bridge, AddBookmarks(_, _))
EXPECT_CALL(*bridge_, NotifyStarted());
EXPECT_CALL(*bridge_, NotifyItemStarted(importer::FAVORITES));
EXPECT_CALL(*bridge_, AddBookmarks(_, _))
.WillOnce(::testing::SaveArg<0>(&bookmarks));
EXPECT_CALL(*bridge, NotifyItemEnded(importer::FAVORITES));
EXPECT_CALL(*bridge, NotifyEnded());
EXPECT_CALL(*bridge_, NotifyItemEnded(importer::FAVORITES));
EXPECT_CALL(*bridge_, NotifyEnded());

importer->StartImport(profile, importer::FAVORITES, bridge.get());
importer_->StartImport(profile_, importer::FAVORITES, bridge_.get());

ASSERT_EQ(3u, bookmarks.size());

Expand All @@ -93,25 +111,17 @@ TEST(ChromeImporterTest, ImportBookmarks) {
EXPECT_FALSE(bookmarks[2].in_toolbar);
}

TEST(ChromeImporterTest, ImportFavicons) {
using ::testing::_;

base::FilePath profile_dir = GetTestChromeProfileDir("default");
ASSERT_TRUE(base::DirectoryExists(profile_dir));
scoped_refptr<ChromeImporter> importer = new ChromeImporter;
importer::SourceProfile profile;
profile.source_path = profile_dir;
scoped_refptr<MockImporterBridge> bridge = new MockImporterBridge;
TEST_F(ChromeImporterTest, ImportFavicons) {
favicon_base::FaviconUsageDataList favicons;

EXPECT_CALL(*bridge, NotifyStarted());
EXPECT_CALL(*bridge, NotifyItemStarted(importer::FAVORITES));
EXPECT_CALL(*bridge, SetFavicons(_))
EXPECT_CALL(*bridge_, NotifyStarted());
EXPECT_CALL(*bridge_, NotifyItemStarted(importer::FAVORITES));
EXPECT_CALL(*bridge_, SetFavicons(_))
.WillOnce(::testing::SaveArg<0>(&favicons));
EXPECT_CALL(*bridge, NotifyItemEnded(importer::FAVORITES));
EXPECT_CALL(*bridge, NotifyEnded());
EXPECT_CALL(*bridge_, NotifyItemEnded(importer::FAVORITES));
EXPECT_CALL(*bridge_, NotifyEnded());

importer->StartImport(profile, importer::FAVORITES, bridge.get());
importer_->StartImport(profile_, importer::FAVORITES, bridge_.get());

ASSERT_EQ(4u, favicons.size());
EXPECT_EQ("https://www.google.com/favicon.ico",
Expand All @@ -123,3 +133,40 @@ TEST(ChromeImporterTest, ImportFavicons) {
EXPECT_EQ("https://static.nytimes.com/favicon.ico",
favicons[3].favicon_url.spec());
}

// The mock keychain only works on macOS, so only run this test on macOS (for now)
#if defined(OS_MACOSX)
TEST_F(ChromeImporterTest, ImportPasswords) {
// Use mock keychain on mac to prevent blocking permissions dialogs.
OSCryptMocker::SetUp();

autofill::PasswordForm autofillable_login;
autofill::PasswordForm blacklisted_login;

EXPECT_CALL(*bridge_, NotifyStarted());
EXPECT_CALL(*bridge_, NotifyItemStarted(importer::PASSWORDS));
EXPECT_CALL(*bridge_, SetPasswordForm(_))
.WillOnce(::testing::SaveArg<0>(&autofillable_login))
.WillOnce(::testing::SaveArg<0>(&blacklisted_login));
EXPECT_CALL(*bridge_, NotifyItemEnded(importer::PASSWORDS));
EXPECT_CALL(*bridge_, NotifyEnded());

importer_->StartImport(profile_, importer::PASSWORDS, bridge_.get());

EXPECT_FALSE(autofillable_login.blacklisted_by_user);
EXPECT_EQ("http://127.0.0.1:8080/",
autofillable_login.signon_realm);
EXPECT_EQ("test-autofillable-login",
UTF16ToASCII(autofillable_login.username_value));
EXPECT_EQ("autofillable-login-password",
UTF16ToASCII(autofillable_login.password_value));

EXPECT_TRUE(blacklisted_login.blacklisted_by_user);
EXPECT_EQ("http://127.0.0.1:8081/",
blacklisted_login.signon_realm);
EXPECT_EQ("", UTF16ToASCII(blacklisted_login.username_value));
EXPECT_EQ("", UTF16ToASCII(blacklisted_login.password_value));

OSCryptMocker::TearDown();
}
#endif