blob: 85343887168820b94e3d646c75fef46f96682664 [file] [log] [blame]
// Copyright 2018 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 "components/autofill/core/browser/password_generator.h"
#include "base/logging.h"
#include "components/autofill/core/browser/proto/password_requirements.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace autofill {
namespace {
// These are strings instead of enums to have an easy way of logging them.
constexpr char kLowerCase[] = "lower_case";
constexpr char kUpperCase[] = "upper_case";
constexpr char kAlphabetic[] = "alphabetic";
constexpr char kNumeric[] = "numeric";
constexpr char kSymbol[] = "symbol";
constexpr const char* kAllClassesButSymbols[] = {kLowerCase, kUpperCase,
kAlphabetic, kNumeric};
constexpr const char* kAllClassesButSymbolsAndAlphabetic[] = {
kLowerCase, kUpperCase, kNumeric};
bool IsCharInClass(base::char16 c, const std::string& class_name) {
if (class_name == kLowerCase) {
return 'a' <= c && c <= 'z';
} else if (class_name == kUpperCase) {
return 'A' <= c && c <= 'Z';
} else if (class_name == kAlphabetic) {
return IsCharInClass(c, kLowerCase) || IsCharInClass(c, kUpperCase);
} else if (class_name == kNumeric) {
return '0' <= c && c <= '9';
}
// Symbols are not covered because there is not fixed definition and because
// symbols are treated like other character classes, so the importance of
// dealing with them here is limited.
NOTREACHED() << "Don't call IsCharInClass for symbols";
return false;
}
size_t CountCharsInClass(const base::string16& password,
const std::string& class_name) {
size_t num = 0;
for (base::char16 c : password) {
if (IsCharInClass(c, class_name))
++num;
}
return num;
}
PasswordRequirementsSpec_CharacterClass* GetMutableCharClass(
PasswordRequirementsSpec* spec,
const std::string& class_name) {
if (class_name == kLowerCase) {
return spec->mutable_lower_case();
} else if (class_name == kUpperCase) {
return spec->mutable_upper_case();
} else if (class_name == kAlphabetic) {
return spec->mutable_alphabetic();
} else if (class_name == kNumeric) {
return spec->mutable_numeric();
} else if (class_name == kSymbol) {
return spec->mutable_symbols();
}
NOTREACHED();
return nullptr;
}
class PasswordGeneratorTest : public testing::Test {
public:
PasswordGeneratorTest() { spec_.set_spec_version(1); }
~PasswordGeneratorTest() override = default;
PasswordRequirementsSpec spec_;
};
TEST_F(PasswordGeneratorTest, PasswordLengthDefault) {
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, PasswordLengthMaxLength) {
// Limit length according to requirement.
spec_.set_max_length(kDefaultPasswordLength - 5u);
EXPECT_EQ(kDefaultPasswordLength - 5u, GeneratePassword(spec_).length());
// If max is higher than default, it does not matter.
spec_.set_max_length(kDefaultPasswordLength + 5);
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, PasswordLengthMinLength) {
// If min is smaller than default, it does not matter.
spec_.set_min_length(kDefaultPasswordLength - 5u);
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
// If a higher minimum length is explicitly set, use it.
spec_.set_min_length(kDefaultPasswordLength + 5u);
EXPECT_EQ(kDefaultPasswordLength + 5u, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, PasswordLengthMinAndMax) {
// Configure a contradicting min and max length. The max length wins.
spec_.set_min_length(kDefaultPasswordLength + 5u);
spec_.set_max_length(kDefaultPasswordLength - 5u);
EXPECT_EQ(kDefaultPasswordLength - 5u, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, MinCharFrequenciesRespected) {
for (std::string char_class : kAllClassesButSymbols) {
SCOPED_TRACE(char_class);
spec_ = PasswordRequirementsSpec();
spec_.set_spec_version(1);
auto* char_class_config = GetMutableCharClass(&spec_, char_class);
char_class_config->set_min(10);
char_class_config->set_max(1000);
base::string16 password = GeneratePassword(spec_);
EXPECT_GE(CountCharsInClass(password, char_class), 10u);
}
}
TEST_F(PasswordGeneratorTest, MinCharFrequenciesInsane) {
// Nothing breaks if the min frequencies are way beyond what's possible
// with the password length. In this case the generated passwor may contain
// just characters of one class but its target length does not increase.
for (std::string char_class : kAllClassesButSymbols) {
SCOPED_TRACE(char_class);
spec_ = PasswordRequirementsSpec();
spec_.set_spec_version(1);
auto* char_class_config = GetMutableCharClass(&spec_, char_class);
char_class_config->set_min(1000);
char_class_config->set_max(1000);
base::string16 password = GeneratePassword(spec_);
// At least some of the characters should be there.
EXPECT_GE(CountCharsInClass(password, char_class), 1u);
// But the password length does not change by minimum length requirements.
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
}
TEST_F(PasswordGeneratorTest, MinCharFrequenciesBiggerThanMax) {
spec_.set_min_length(15);
spec_.set_max_length(15);
for (std::string char_class : kAllClassesButSymbolsAndAlphabetic) {
auto* char_class_config = GetMutableCharClass(&spec_, char_class);
// Min is reduced to max --> each class should have 5 representatives.
char_class_config->set_min(10);
char_class_config->set_max(5);
}
base::string16 password = GeneratePassword(spec_);
for (std::string char_class : kAllClassesButSymbolsAndAlphabetic) {
SCOPED_TRACE(char_class);
// Check that each character class is represented by 5 characters.
EXPECT_EQ(5u, CountCharsInClass(password, char_class));
}
EXPECT_EQ(15u, password.length());
}
TEST_F(PasswordGeneratorTest, MaxFrequenciesRespected) {
// Limit the maximum occurrences of characters of some classes to 2 and
// validate that this is respected.
for (std::string char_class : kAllClassesButSymbolsAndAlphabetic) {
SCOPED_TRACE(char_class);
spec_ = PasswordRequirementsSpec();
spec_.set_spec_version(1);
auto* char_class_config = GetMutableCharClass(&spec_, char_class);
char_class_config->set_max(2);
base::string16 password = GeneratePassword(spec_);
EXPECT_LE(CountCharsInClass(password, char_class), 2u);
// Ensure that the other character classes that are not limited fill up the
// password to the desired length.
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
}
TEST_F(PasswordGeneratorTest, MaxFrequenciesInsufficient) {
spec_.set_min_length(15);
spec_.set_max_length(15);
// Limit the maximum occurrences of characters of all classes to 2 which
// sums up to less than 15.
for (std::string char_class : kAllClassesButSymbolsAndAlphabetic) {
auto* char_class_config = GetMutableCharClass(&spec_, char_class);
char_class_config->set_max(2);
}
// The resulting password can contain only 6 characters.
EXPECT_EQ(6u, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, CharacterSetCanBeOverridden) {
// Limit lower case chars to 'a' and 'b' and require exacty 5 of those.
spec_.mutable_lower_case()->set_character_set("ab");
spec_.mutable_lower_case()->set_min(5);
spec_.mutable_lower_case()->set_max(5);
base::string16 password = GeneratePassword(spec_);
// Then ensure that 'a's and 'b's are generated at the expected frequencies
// as an indicator that the override was respected.
size_t num_as_and_bs = 0;
for (base::char16 c : password) {
if (c == 'a' || c == 'b')
++num_as_and_bs;
}
EXPECT_EQ(5u, num_as_and_bs);
}
TEST_F(PasswordGeneratorTest, AllCharactersAreGenerated) {
// Limit lower case chars to 'a' and 'b' and require exacty 5 of those.
spec_.mutable_lower_case()->set_character_set("ab");
spec_.mutable_lower_case()->set_min(5);
spec_.mutable_lower_case()->set_max(5);
bool success = false;
// The chance of not seing both an 'a' and a 'b' in one run are only 2/32 per
// run. Ultimately we should see success. If none of the 100 generated
// passwords contained an 'a' (or 'b'), then this would indicate that the
// generator does not fully use the character set (probably due to an
// off-by-one error).
for (size_t attempt = 0; attempt < 100; ++attempt) {
base::string16 password = GeneratePassword(spec_);
size_t num_as = 0;
size_t num_bs = 0;
for (base::char16 c : password) {
if (c == 'a')
++num_as;
if (c == 'b')
++num_bs;
}
if (num_as > 0u && num_bs > 0u) {
success = true;
break;
}
}
EXPECT_TRUE(success);
}
TEST_F(PasswordGeneratorTest, PasswordCanBeGeneratedWithEmptyCharSet) {
// If the character set is empty, min and max should be ignored.
spec_.mutable_lower_case()->set_character_set("");
spec_.mutable_lower_case()->set_min(5);
spec_.mutable_lower_case()->set_max(5);
base::string16 password = GeneratePassword(spec_);
EXPECT_EQ(0u, CountCharsInClass(password, kLowerCase));
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, AllCharactersForbidden) {
spec_.set_min_length(kDefaultPasswordLength + 2);
spec_.set_max_length(kDefaultPasswordLength + 2);
for (std::string char_class : kAllClassesButSymbolsAndAlphabetic) {
auto* char_class_config = GetMutableCharClass(&spec_, char_class);
char_class_config->set_max(0);
}
// If it is impossible to generate a password (due to max = 0), the generator
// delivers a password as per default spec and ignores everything else.
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
TEST_F(PasswordGeneratorTest, ZeroLength) {
spec_.set_min_length(0);
spec_.set_max_length(0);
// If the generated password following the spec is empty, a default spec is
// applied.
EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
}
} // namespace
} // namespace autofill