blob: d6da8bfc3d5188f52f645299724598ac1444e2e8 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/component_updater/zxcvbn_data_component_installer.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_piece.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/zxcvbn-cpp/native-src/zxcvbn/frequency_lists.hpp"
namespace component_updater {
namespace {
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;
constexpr char kTextfilesOnlyVersion[] = "1";
constexpr char kMemoryMappedVersion[] = "2";
constexpr char kFutureVersion[] = "2.1";
} // namespace
class ZxcvbnDataComponentInstallerPolicyTest : public ::testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(component_install_dir_.CreateUniqueTempDir());
SetVersion(kTextfilesOnlyVersion);
}
ZxcvbnDataComponentInstallerPolicy& policy() { return policy_; }
base::test::TaskEnvironment& task_env() { return task_env_; }
const base::Version& version() const { return version_; }
const base::Value::Dict& manifest() const { return manifest_; }
const base::FilePath& GetPath() const {
return component_install_dir_.GetPath();
}
void SetVersion(base::StringPiece version_str) {
version_ = base::Version(version_str);
manifest_.Set("version", version_str);
}
void CreateEmptyTextFiles() {
base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kEnglishWikipediaTxtFileName),
"");
base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kFemaleNamesTxtFileName),
"");
base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kMaleNamesTxtFileName),
"");
base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kPasswordsTxtFileName),
"");
base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kSurnamesTxtFileName),
"");
base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kUsTvAndFilmTxtFileName),
"");
}
void CreateEmptyCombinedBinaryFile() {
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kCombinedRankedDictsFileName),
""));
}
void CreateTextFiles() {
// Populated files should be read and fed to the correct ranked zxcvbn
// dictionary.
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kEnglishWikipediaTxtFileName),
"english\nwikipedia"));
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kFemaleNamesTxtFileName),
"female\nfnames"));
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kMaleNamesTxtFileName),
"male\nmnames"));
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kPasswordsTxtFileName),
"passwords"));
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kSurnamesTxtFileName),
"surnames"));
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kUsTvAndFilmTxtFileName),
"us\ntv\nand\nfilm"));
}
void CreateCombinedBinaryFile() {
// This replicates the internal data structure of `zxcvbn::RankedDicts`.
std::vector<uint8_t> binary_data;
constexpr uint8_t MARKER_BIT = 0x80;
auto add_entry = [&binary_data](uint16_t rank, base::StringPiece word) {
ASSERT_LT(rank, 1 << 15);
binary_data.push_back((rank >> 8) | MARKER_BIT);
binary_data.push_back(rank & 0xff);
for (const char letter : word) {
binary_data.push_back(letter);
}
};
// The entries must be ordered alphabetically to replicate the internal
// structure of `RankedDicts`.
add_entry(3UL, "and");
add_entry(1UL, "english");
add_entry(1UL, "female");
add_entry(4UL, "film");
add_entry(2UL, "fnames");
add_entry(1UL, "male");
add_entry(2UL, "mnames");
add_entry(1UL, "passwords");
add_entry(1UL, "surnames");
add_entry(2UL, "tv");
add_entry(1UL, "us");
add_entry(2UL, "wikipedia");
binary_data.push_back(MARKER_BIT);
ASSERT_TRUE(base::WriteFile(
GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kCombinedRankedDictsFileName),
std::move(binary_data)));
}
void VerifyRankedDicts() {
zxcvbn::RankedDicts& ranked_dicts = zxcvbn::default_ranked_dicts();
EXPECT_EQ(ranked_dicts.Find("english"), 1UL);
EXPECT_EQ(ranked_dicts.Find("wikipedia"), 2UL);
EXPECT_EQ(ranked_dicts.Find("female"), 1UL);
EXPECT_EQ(ranked_dicts.Find("fnames"), 2UL);
EXPECT_EQ(ranked_dicts.Find("male"), 1UL);
EXPECT_EQ(ranked_dicts.Find("mnames"), 2UL);
EXPECT_EQ(ranked_dicts.Find("passwords"), 1UL);
EXPECT_EQ(ranked_dicts.Find("surnames"), 1UL);
EXPECT_EQ(ranked_dicts.Find("us"), 1UL);
EXPECT_EQ(ranked_dicts.Find("tv"), 2UL);
EXPECT_EQ(ranked_dicts.Find("and"), 3UL);
EXPECT_EQ(ranked_dicts.Find("film"), 4UL);
}
private:
base::test::TaskEnvironment task_env_;
base::Version version_;
base::Value::Dict manifest_;
ZxcvbnDataComponentInstallerPolicy policy_;
base::ScopedTempDir component_install_dir_;
};
// Tests that VerifyInstallation only returns true when all expected text files
// are present.
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
VerifyInstallationForTextfileOnlyVersion) {
// An empty directory lacks all required files.
EXPECT_FALSE(policy().VerifyInstallation(manifest(), GetPath()));
CreateEmptyTextFiles();
// All files should exist.
EXPECT_TRUE(policy().VerifyInstallation(manifest(), GetPath()));
base::DeleteFile(GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kEnglishWikipediaTxtFileName));
EXPECT_FALSE(policy().VerifyInstallation(manifest(), GetPath()));
}
// Tests that VerifyInstallation only returns true when both the text files and
// the combined binary file are present in the case of the version with support
// for memory mapping.
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
VerifyInstallationForMemoryMappedVersion) {
SetVersion(kMemoryMappedVersion);
// An empty directory lacks all required files.
EXPECT_FALSE(policy().VerifyInstallation(manifest(), GetPath()));
CreateEmptyTextFiles();
// The combined data file is still missing.
EXPECT_FALSE(policy().VerifyInstallation(manifest(), GetPath()));
CreateEmptyCombinedBinaryFile();
EXPECT_TRUE(policy().VerifyInstallation(manifest(), GetPath()));
base::DeleteFile(GetPath().Append(
ZxcvbnDataComponentInstallerPolicy::kEnglishWikipediaTxtFileName));
EXPECT_FALSE(policy().VerifyInstallation(manifest(), GetPath()));
}
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
VerifyInstallationExpectsValidVersion) {
CreateEmptyTextFiles();
EXPECT_TRUE(policy().VerifyInstallation(manifest(), GetPath()));
// Verification fails for a missing version.
EXPECT_FALSE(policy().VerifyInstallation(base::Value::Dict(), GetPath()));
// Verification fails for an invalid version.
SetVersion("1.x2");
EXPECT_FALSE(policy().VerifyInstallation(manifest(), GetPath()));
}
TEST_F(ZxcvbnDataComponentInstallerPolicyTest, ComponentReadyForMissingFiles) {
// Empty / non-existent files should result in empty dictionaries.
policy().ComponentReady(version(), GetPath(), manifest().Clone());
task_env().RunUntilIdle();
EXPECT_FALSE(zxcvbn::default_ranked_dicts().Find("english").has_value());
}
// Tests that ComponentReady reads in the file contents and properly populates
// zxcvbn::default_ranked_dicts().
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
ComponentReadyForTextfileOnlyVersion) {
CreateTextFiles();
policy().ComponentReady(version(), GetPath(), manifest().Clone());
task_env().RunUntilIdle();
VerifyRankedDicts();
}
// Tests that ComponentReady reads in the file contents and properly populates
// zxcvbn::default_ranked_dicts() when using a memory mapped file.
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
ComponentReadyForMemoryMappedVersion) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
password_manager::features::kMemoryMapWeaknessCheckDictionaries);
SetVersion(kMemoryMappedVersion);
CreateEmptyTextFiles();
CreateCombinedBinaryFile();
policy().ComponentReady(version(), GetPath(), manifest().Clone());
task_env().RunUntilIdle();
VerifyRankedDicts();
}
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
ComponentReadyForMemoryMappedVersionWithDisabledFeature) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
password_manager::features::kMemoryMapWeaknessCheckDictionaries);
SetVersion(kMemoryMappedVersion);
CreateTextFiles();
CreateEmptyCombinedBinaryFile();
policy().ComponentReady(version(), GetPath(), manifest().Clone());
task_env().RunUntilIdle();
VerifyRankedDicts();
}
// Tests that updates are handled gracefully and despite potentially blocking
// behavior while closing a memory mapped file.
TEST_F(ZxcvbnDataComponentInstallerPolicyTest,
ComponentReadyHandlesUpdateProperly) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
password_manager::features::kMemoryMapWeaknessCheckDictionaries);
SetVersion(kMemoryMappedVersion);
CreateEmptyTextFiles();
CreateCombinedBinaryFile();
policy().ComponentReady(version(), GetPath(), manifest().Clone());
task_env().RunUntilIdle();
VerifyRankedDicts();
SetVersion(kFutureVersion);
policy().ComponentReady(version(), GetPath(), manifest().Clone());
task_env().RunUntilIdle();
VerifyRankedDicts();
}
} // namespace component_updater