From b2c2f42f302667143b863dfb87c152285a80ae92 Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Nov 2019 17:51:56 +0100 Subject: [PATCH] Allow defining additional characters for the password generator See issue #3271 for a motivation of this feature. This patch adds an additional text input to the advanced view of the password generator. All characters of this input field (if not empty) will be added as another group to the password generator. The characters from the excluded field have precedence over the characters from this new field, meaning any character added to both fields will *not* appear in any generated password. As the characters from this new field will be added as their own group to the password generator, checking the 'Include characters from every group' checkbox will force at least character to be chosen from the new input field. The `PasswordGenerator` class has also been changed so that the `isValid` method returns `true` if only characters from the new input field would be used. There is a new, simple test that covers the new feature. While the test only uses ASCII characters, any Unicode characters can be used with the new feature. --- src/browser/BrowserSettings.cpp | 10 + src/browser/BrowserSettings.h | 2 + src/core/PasswordGenerator.cpp | 18 +- src/core/PasswordGenerator.h | 3 + src/gui/PasswordGeneratorWidget.cpp | 17 +- src/gui/PasswordGeneratorWidget.ui | 959 +++++++++++++++------------- tests/TestPasswordGenerator.cpp | 13 + tests/TestPasswordGenerator.h | 1 + 8 files changed, 560 insertions(+), 463 deletions(-) diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index b49af7005..eb5d81d29 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -412,6 +412,16 @@ void BrowserSettings::setAdvancedMode(bool advancedMode) config()->set("generator/AdvancedMode", advancedMode); } +QString BrowserSettings::passwordAdditionalChars() +{ + return config()->get("generator/AdditionalChars", PasswordGenerator::DefaultAdditionalChars).toString(); +} + +void BrowserSettings::setPasswordAdditionalChars(const QString& chars) +{ + config()->set("generator/AdditionalChars", chars); +} + QString BrowserSettings::passwordExcludedChars() { return config()->get("generator/ExcludedChars", PasswordGenerator::DefaultExcludedChars).toString(); diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index 395455cbc..9340cd0a3 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -107,6 +107,8 @@ public: void setPasswordUseEASCII(bool useEASCII); bool advancedMode(); void setAdvancedMode(bool advancedMode); + QString passwordAdditionalChars(); + void setPasswordAdditionalChars(const QString& chars); QString passwordExcludedChars(); void setPasswordExcludedChars(const QString& chars); int passPhraseWordCount(); diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index ff271a453..efe647880 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -20,12 +20,14 @@ #include "crypto/Random.h" +const char* PasswordGenerator::DefaultAdditionalChars = ""; const char* PasswordGenerator::DefaultExcludedChars = ""; PasswordGenerator::PasswordGenerator() : m_length(0) , m_classes(nullptr) , m_flags(nullptr) + , m_additional(PasswordGenerator::DefaultAdditionalChars) , m_excluded(PasswordGenerator::DefaultExcludedChars) { } @@ -53,6 +55,11 @@ void PasswordGenerator::setFlags(const GeneratorFlags& flags) m_flags = flags; } +void PasswordGenerator::setAdditionalChars(const QString& chars) +{ + m_additional = chars; +} + void PasswordGenerator::setExcludedChars(const QString& chars) { m_excluded = chars; @@ -107,7 +114,7 @@ QString PasswordGenerator::generatePassword() const bool PasswordGenerator::isValid() const { - if (m_classes == 0) { + if (m_classes == 0 && m_additional.isEmpty()) { return false; } else if (m_length == 0) { return false; @@ -259,6 +266,15 @@ QVector PasswordGenerator::passwordGroups() const passwordGroups.append(group); } + if (!m_additional.isEmpty()) { + PasswordGroup group; + + for (auto ch : m_additional) { + group.append(ch); + } + + passwordGroups.append(group); + } // Loop over character groups and remove excluded characters from them; // remove empty groups diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 55418b4ba..20681c233 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -60,6 +60,7 @@ public: void setLength(int length); void setCharClasses(const CharClasses& classes); void setFlags(const GeneratorFlags& flags); + void setAdditionalChars(const QString& chars); void setExcludedChars(const QString& chars); bool isValid() const; @@ -67,6 +68,7 @@ public: QString generatePassword() const; static const int DefaultLength = 16; + static const char* DefaultAdditionalChars; static const char* DefaultExcludedChars; static constexpr bool DefaultLower = (DefaultCharset & LowerLetters) != 0; static constexpr bool DefaultUpper = (DefaultCharset & UpperLetters) != 0; @@ -90,6 +92,7 @@ private: int m_length; CharClasses m_classes; GeneratorFlags m_flags; + QString m_additional; QString m_excluded; Q_DISABLE_COPY(PasswordGenerator) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 436124813..7ce45b1d1 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -48,6 +48,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); connect(m_ui->buttonAdvancedMode, SIGNAL(toggled(bool)), SLOT(setAdvancedMode(bool))); connect(m_ui->buttonAddHex, SIGNAL(clicked()), SLOT(excludeHexChars())); + connect(m_ui->editAdditionalChars, SIGNAL(textChanged(QString)), SLOT(updateGenerator())); connect(m_ui->editExcludedChars, SIGNAL(textChanged(QString)), SLOT(updateGenerator())); connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword())); connect(m_ui->buttonCopy, SIGNAL(clicked()), SLOT(copyPassword())); @@ -114,6 +115,8 @@ void PasswordGeneratorWidget::loadSettings() config()->get("generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool()); m_ui->checkBoxNumbersAdv->setChecked( config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool()); + m_ui->editAdditionalChars->setText( + config()->get("generator/AdditionalChars", PasswordGenerator::DefaultAdditionalChars).toString()); m_ui->editExcludedChars->setText( config()->get("generator/ExcludedChars", PasswordGenerator::DefaultExcludedChars).toString()); @@ -315,6 +318,7 @@ void PasswordGeneratorWidget::setAdvancedMode(bool state) { if (state) { m_ui->simpleBar->hide(); + m_ui->advancedContainer->show(); m_ui->checkBoxUpperAdv->setChecked(m_ui->checkBoxUpper->isChecked()); m_ui->checkBoxLowerAdv->setChecked(m_ui->checkBoxLower->isChecked()); m_ui->checkBoxNumbersAdv->setChecked(m_ui->checkBoxNumbers->isChecked()); @@ -325,15 +329,9 @@ void PasswordGeneratorWidget::setAdvancedMode(bool state) m_ui->checkBoxDashes->setChecked(m_ui->checkBoxSpecialChars->isChecked()); m_ui->checkBoxLogograms->setChecked(m_ui->checkBoxSpecialChars->isChecked()); m_ui->checkBoxExtASCIIAdv->setChecked(m_ui->checkBoxExtASCII->isChecked()); - m_ui->advancedBar->show(); - m_ui->excludedChars->show(); - m_ui->checkBoxExcludeAlike->show(); - m_ui->checkBoxEnsureEvery->show(); } else { - m_ui->advancedBar->hide(); - m_ui->excludedChars->hide(); - m_ui->checkBoxExcludeAlike->hide(); - m_ui->checkBoxEnsureEvery->hide(); + m_ui->simpleBar->show(); + m_ui->advancedContainer->hide(); m_ui->checkBoxUpper->setChecked(m_ui->checkBoxUpperAdv->isChecked()); m_ui->checkBoxLower->setChecked(m_ui->checkBoxLowerAdv->isChecked()); m_ui->checkBoxNumbers->setChecked(m_ui->checkBoxNumbersAdv->isChecked()); @@ -342,7 +340,6 @@ void PasswordGeneratorWidget::setAdvancedMode(bool state) | m_ui->checkBoxQuotes->isChecked() | m_ui->checkBoxMath->isChecked() | m_ui->checkBoxDashes->isChecked() | m_ui->checkBoxLogograms->isChecked()); m_ui->checkBoxExtASCII->setChecked(m_ui->checkBoxExtASCIIAdv->isChecked()); - m_ui->simpleBar->show(); } } @@ -523,8 +520,10 @@ void PasswordGeneratorWidget::updateGenerator() m_passwordGenerator->setCharClasses(classes); m_passwordGenerator->setFlags(flags); if (m_ui->buttonAdvancedMode->isChecked()) { + m_passwordGenerator->setAdditionalChars(m_ui->editAdditionalChars->text()); m_passwordGenerator->setExcludedChars(m_ui->editExcludedChars->text()); } else { + m_passwordGenerator->setAdditionalChars(""); m_passwordGenerator->setExcludedChars(""); } diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 62d96a2a1..4981d3ccd 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -199,6 +199,89 @@ QProgressBar::chunk { Password + + + + 15 + + + QLayout::SetMinimumSize + + + 0 + + + + + &Length: + + + spinBoxLength + + + + + + + Password length + + + 1 + + + 128 + + + 20 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + Password length + + + 1 + + + 128 + + + 20 + + + + + + + Qt::TabFocus + + + Switch to advanced mode + + + Advanced + + + true + + + optionButtons + + + + + @@ -401,11 +484,8 @@ QProgressBar::chunk { - - - true - - + + 0 @@ -419,323 +499,454 @@ QProgressBar::chunk { 0 - - - QLayout::SetMinimumSize + + + true - - - - - 40 - 25 - - - - Qt::TabFocus - - - Upper-case letters - - - Upper-case letters - + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetMinimumSize + + + + + + 40 + 25 + + + + Qt::TabFocus + + + Upper-case letters + + + Upper-case letters + + + A-Z + + + true + + + optionButtons + + + + + + + + 40 + 25 + + + + Qt::TabFocus + + + Lower-case letters + + + Lower-case letters + + + a-z + + + true + + + optionButtons + + + + + + + + + QLayout::SetMinimumSize + + + + + + 40 + 25 + + + + Qt::TabFocus + + + Numbers + + + 0-9 + + + true + + + optionButtons + + + + + + + + 40 + 25 + + + + Qt::TabFocus + + + Braces + + + Braces + + + {[( + + + true + + + optionButtons + + + + + + + + + QLayout::SetMinimumSize + + + + + + 40 + 25 + + + + Qt::TabFocus + + + Punctuation + + + Punctuation + + + .,:; + + + true + + + optionButtons + + + + + + + + 40 + 25 + + + + Qt::TabFocus + + + Quotes + + + " ' + + + true + + + optionButtons + + + + + + + + + QLayout::SetMinimumSize + + + + + + 60 + 25 + + + + Qt::TabFocus + + + Math Symbols + + + Math Symbols + + + <*+!?= + + + true + + + optionButtons + + + + + + + + 60 + 25 + + + + Qt::TabFocus + + + Dashes and Slashes + + + Dashes and Slashes + + + \_|-/ + + + true + + + optionButtons + + + + + + + + + QLayout::SetMinimumSize + + + + + + 105 + 25 + + + + Qt::TabFocus + + + Logograms + + + Logograms + + + #$%&&@^`~ + + + true + + + optionButtons + + + + + + + + 105 + 25 + + + + Qt::TabFocus + + + Extended ASCII + + + ExtendedASCII + + + true + + + optionButtons + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + 0 + + + - A-Z + Also choose from: - - true - - - optionButtons - - - + + + + + 0 + 0 + + - 40 - 25 + 200 + 0 + + Additional characters to use for the generated password + + + Additional characters + + + true + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + Character set to exclude from generated password + + + Excluded characters + + + true + + + + + + + Do not include: + + + + + Qt::TabFocus - Lower-case letters + Add non-hex letters to "do not include" list - Lower-case letters + Hex Passwords - a-z + Hex - - true - - - optionButtons - - - - QLayout::SetMinimumSize + + + Excluded characters: "0", "1", "l", "I", "O", "|", "﹒" - - - - - 40 - 25 - - - - Qt::TabFocus - - - Numbers - - - 0-9 - - - true - - - optionButtons - - - - - - - - 40 - 25 - - - - Qt::TabFocus - - - Braces - - - Braces - - - {[( - - - true - - - optionButtons - - - - + + Exclude look-alike characters + + + optionButtons + + - - - QLayout::SetMinimumSize + + + Pick characters from every group - - - - - 40 - 25 - - - - Qt::TabFocus - - - Punctuation - - - Punctuation - - - .,:; - - - true - - - optionButtons - - - - - - - - 40 - 25 - - - - Qt::TabFocus - - - Quotes - - - " ' - - - true - - - optionButtons - - - - - - - - - QLayout::SetMinimumSize - - - - - - 60 - 25 - - - - Qt::TabFocus - - - Math Symbols - - - Math Symbols - - - <*+!?= - - - true - - - optionButtons - - - - - - - - 60 - 25 - - - - Qt::TabFocus - - - Dashes and Slashes - - - Dashes and Slashes - - - \_|-/ - - - true - - - optionButtons - - - - - - - - - QLayout::SetMinimumSize - - - - - - 105 - 25 - - - - Qt::TabFocus - - - Logograms - - - Logograms - - - #$%&&@^`~ - - - true - - - optionButtons - - - - - - - - 105 - 25 - - - - Qt::TabFocus - - - Extended ASCII - - - ExtendedASCII - - - true - - - optionButtons - - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - + + optionButtons + + @@ -758,166 +969,12 @@ QProgressBar::chunk { 0 - - - - Do not include: - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - Character set to exclude from generated password - - - Excluded characters - - - true - - - - - - - Qt::TabFocus - - - Add non-hex letters to "do not include" list - - - Hex Passwords - - - Hex - - - - - - - Excluded characters: "0", "1", "l", "I", "O", "|", "﹒" - - - Exclude look-alike characters - - - optionButtons - - - - - - - Pick characters from every group - - - optionButtons - - - - - - - 15 - - - QLayout::SetMinimumSize - - - 0 - - - - - &Length: - - - spinBoxLength - - - - - - - Password length - - - 1 - - - 128 - - - 20 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - Password length - - - 1 - - - 128 - - - 20 - - - - - - - Qt::TabFocus - - - Switch to advanced mode - - - Advanced - - - true - - - optionButtons - - - - - @@ -1158,10 +1215,6 @@ QProgressBar::chunk { checkBoxQuotes checkBoxDashes checkBoxExtASCIIAdv - editExcludedChars - buttonAddHex - checkBoxExcludeAlike - checkBoxEnsureEvery comboBoxWordList sliderWordCount spinBoxWordCount diff --git a/tests/TestPasswordGenerator.cpp b/tests/TestPasswordGenerator.cpp index b043a7cd0..89e2eb91c 100644 --- a/tests/TestPasswordGenerator.cpp +++ b/tests/TestPasswordGenerator.cpp @@ -29,6 +29,19 @@ void TestPasswordGenerator::initTestCase() QVERIFY(Crypto::init()); } +void TestPasswordGenerator::testAdditionalChars() +{ + PasswordGenerator generator; + QVERIFY(!generator.isValid()); + generator.setAdditionalChars("aql"); + generator.setLength(2000); + QVERIFY(generator.isValid()); + QString password = generator.generatePassword(); + QCOMPARE(password.size(), 2000); + QRegularExpression regex(R"(^[aql]+$)"); + QVERIFY(regex.match(password).hasMatch()); +} + void TestPasswordGenerator::testCharClasses() { PasswordGenerator generator; diff --git a/tests/TestPasswordGenerator.h b/tests/TestPasswordGenerator.h index 56c4d65a1..454d16e06 100644 --- a/tests/TestPasswordGenerator.h +++ b/tests/TestPasswordGenerator.h @@ -26,6 +26,7 @@ class TestPasswordGenerator : public QObject private slots: void initTestCase(); + void testAdditionalChars(); void testCharClasses(); void testLookalikeExclusion(); };