From eb1882453f607f2b3b5ad82ea48678f48cb30580 Mon Sep 17 00:00:00 2001 From: louib Date: Fri, 30 Aug 2019 22:50:32 -0400 Subject: [PATCH] CLI password generation options cleanup (#3275) Summary of changes: * Extract function for creating password generator from options into `Generate` command. This function is now reused in `Add` and `Edit` commands. * Updated manpage with missing password generation options. * Updated manpage with missing longer forms of password generation options. * Added unit tests for new password generation options in `Add` and `Edit`. * Handle case when `-g` and `-p` options are used at the same time. This PR adds password generation functionalities while reducing code duplication, but at the cost of 2 small breaking changes: * The password generation option for `Add` and `Edit` for specifying password length is now `-L` instead of `-l`, to not clash with the `-l --lower` option. * The `-u` shorthand for the `--upper` option has to be removed, to not clash with the `-u --username` option. * Add -U variant for uppercase. --- CHANGELOG | 5 ++ src/cli/Add.cpp | 50 ++++++++-------- src/cli/Edit.cpp | 45 +++++++++------ src/cli/Generate.cpp | 53 ++++++++++------- src/cli/Generate.h | 4 ++ src/cli/keepassxc-cli.1 | 27 ++++++--- tests/TestCli.cpp | 123 ++++++++++++++++++++++++++++++++++------ tests/TestCli.h | 2 +- 8 files changed, 221 insertions(+), 88 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 91c760421..8bbef9319 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ ========================= - Group sorting feature [#3282] - CLI: Add 'flatten' option to the 'ls' command [#3276] +- 💥💥 CLI: The password length option `-l` for the CLI commands + `Add` and `Edit` is now `-L` [#3275] +- 💥💥 CLI: the `-u` shorthand for the `--upper` password generation option + has been renamed `-U` [#3275] +- CLI: Add password generation options to `Add` and `Edit` commands [#3275] - Rework the Entry Preview panel [#3306] - Move notes to General tab on Group Preview Panel [#3336] - Add 'Monospaced font' option to the Notes field [#3321] diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 5e722e888..bd29a169a 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -20,6 +20,7 @@ #include "Add.h" +#include "cli/Generate.h" #include "cli/TextStream.h" #include "cli/Utils.h" #include "core/Database.h" @@ -44,11 +45,6 @@ const QCommandLineOption Add::GenerateOption = QCommandLineOption(QStringList() << "generate", QObject::tr("Generate a password for the entry.")); -const QCommandLineOption Add::PasswordLengthOption = - QCommandLineOption(QStringList() << "l" - << "password-length", - QObject::tr("Length for the generated password."), - QObject::tr("length")); Add::Add() { @@ -57,9 +53,19 @@ Add::Add() options.append(Add::UsernameOption); options.append(Add::UrlOption); options.append(Add::PasswordPromptOption); - options.append(Add::GenerateOption); - options.append(Add::PasswordLengthOption); positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to add."), QString("")}); + + // Password generation options. + options.append(Add::GenerateOption); + options.append(Generate::PasswordLengthOption); + options.append(Generate::LowerCaseOption); + options.append(Generate::UpperCaseOption); + options.append(Generate::NumbersOption); + options.append(Generate::SpecialCharsOption); + options.append(Generate::ExtendedAsciiOption); + options.append(Generate::ExcludeCharsOption); + options.append(Generate::ExcludeSimilarCharsOption); + options.append(Generate::IncludeEveryGroupOption); } int Add::executeWithDatabase(QSharedPointer database, QSharedPointer parser) @@ -72,14 +78,22 @@ int Add::executeWithDatabase(QSharedPointer database, QSharedPointervalue(Add::PasswordLengthOption); - if (!passwordLength.isEmpty() && !passwordLength.toInt()) { - errorTextStream << QObject::tr("Invalid value for password length %1.").arg(passwordLength) << endl; + // Cannot use those 2 options at the same time! + if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) { + errorTextStream << QObject::tr("Cannot generate a password and prompt at the same time!") << endl; return EXIT_FAILURE; } + // Validating the password generator here, before we actually create + // the entry. + QSharedPointer passwordGenerator; + if (parser->isSet(Add::GenerateOption)) { + passwordGenerator = Generate::createGenerator(parser); + if (passwordGenerator.isNull()) { + return EXIT_FAILURE; + } + } + Entry* entry = database->rootGroup()->addEntryWithPath(entryPath); if (!entry) { errorTextStream << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl; @@ -101,17 +115,7 @@ int Add::executeWithDatabase(QSharedPointer database, QSharedPointerisSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT); entry->setPassword(password); } else if (parser->isSet(Add::GenerateOption)) { - PasswordGenerator passwordGenerator; - - if (passwordLength.isEmpty()) { - passwordGenerator.setLength(PasswordGenerator::DefaultLength); - } else { - passwordGenerator.setLength(passwordLength.toInt()); - } - - passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); - passwordGenerator.setFlags(PasswordGenerator::DefaultFlags); - QString password = passwordGenerator.generatePassword(); + QString password = passwordGenerator->generatePassword(); entry->setPassword(password); } diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 8459f5717..0d1f59dbd 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -21,6 +21,7 @@ #include "Edit.h" #include "cli/Add.h" +#include "cli/Generate.h" #include "cli/TextStream.h" #include "cli/Utils.h" #include "core/Database.h" @@ -41,10 +42,20 @@ Edit::Edit() options.append(Add::UsernameOption); options.append(Add::UrlOption); options.append(Add::PasswordPromptOption); - options.append(Add::GenerateOption); - options.append(Add::PasswordLengthOption); options.append(Edit::TitleOption); positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to edit."), QString("")}); + + // Password generation options. + options.append(Add::GenerateOption); + options.append(Generate::PasswordLengthOption); + options.append(Generate::LowerCaseOption); + options.append(Generate::UpperCaseOption); + options.append(Generate::NumbersOption); + options.append(Generate::SpecialCharsOption); + options.append(Generate::ExtendedAsciiOption); + options.append(Generate::ExcludeCharsOption); + options.append(Generate::ExcludeSimilarCharsOption); + options.append(Generate::IncludeEveryGroupOption); } int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer parser) @@ -57,12 +68,23 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< const QString& databasePath = args.at(0); const QString& entryPath = args.at(1); - QString passwordLength = parser->value(Add::PasswordLengthOption); - if (!passwordLength.isEmpty() && !passwordLength.toInt()) { - errorTextStream << QObject::tr("Invalid value for password length: %1").arg(passwordLength) << endl; + // Cannot use those 2 options at the same time! + if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) { + errorTextStream << QObject::tr("Cannot generate a password and prompt at the same time!") << endl; return EXIT_FAILURE; } + // Validating the password generator here, before we actually start + // the update. + QSharedPointer passwordGenerator; + bool generate = parser->isSet(Add::GenerateOption); + if (generate) { + passwordGenerator = Generate::createGenerator(parser); + if (passwordGenerator.isNull()) { + return EXIT_FAILURE; + } + } + Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; @@ -72,7 +94,6 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< QString username = parser->value(Add::UsernameOption); QString url = parser->value(Add::UrlOption); QString title = parser->value(Edit::TitleOption); - bool generate = parser->isSet(Add::GenerateOption); bool prompt = parser->isSet(Add::PasswordPromptOption); if (username.isEmpty() && url.isEmpty() && title.isEmpty() && !prompt && !generate) { errorTextStream << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl; @@ -98,17 +119,7 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< QString password = Utils::getPassword(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT); entry->setPassword(password); } else if (generate) { - PasswordGenerator passwordGenerator; - - if (passwordLength.isEmpty()) { - passwordGenerator.setLength(PasswordGenerator::DefaultLength); - } else { - passwordGenerator.setLength(static_cast(passwordLength.toInt())); - } - - passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); - passwordGenerator.setFlags(PasswordGenerator::DefaultFlags); - QString password = passwordGenerator.generatePassword(); + QString password = passwordGenerator->generatePassword(); entry->setPassword(password); } diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index 27a5cf44e..2f465469a 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -22,7 +22,6 @@ #include "cli/TextStream.h" #include "cli/Utils.h" -#include "core/PasswordGenerator.h" const QCommandLineOption Generate::PasswordLengthOption = QCommandLineOption(QStringList() << "L" @@ -34,7 +33,7 @@ const QCommandLineOption Generate::LowerCaseOption = QCommandLineOption(QStringL << "lower", QObject::tr("Use lowercase characters")); -const QCommandLineOption Generate::UpperCaseOption = QCommandLineOption(QStringList() << "u" +const QCommandLineOption Generate::UpperCaseOption = QCommandLineOption(QStringList() << "U" << "upper", QObject::tr("Use uppercase characters")); @@ -75,26 +74,22 @@ Generate::Generate() options.append(Generate::IncludeEveryGroupOption); } -int Generate::execute(const QStringList& arguments) +/** + * Creates a password generator instance using the command line options + * of the parser object. + */ +QSharedPointer Generate::createGenerator(QSharedPointer parser) { - QSharedPointer parser = getCommandLineParser(arguments); - if (parser.isNull()) { - return EXIT_FAILURE; - } - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); - - const QStringList args = parser->positionalArguments(); - - PasswordGenerator passwordGenerator; + QSharedPointer passwordGenerator = QSharedPointer(new PasswordGenerator()); QString passwordLength = parser->value(Generate::PasswordLengthOption); if (passwordLength.isEmpty()) { - passwordGenerator.setLength(PasswordGenerator::DefaultLength); + passwordGenerator->setLength(PasswordGenerator::DefaultLength); } else if (passwordLength.toInt() <= 0) { errorTextStream << QObject::tr("Invalid password length %1").arg(passwordLength) << endl; - return EXIT_FAILURE; + return QSharedPointer(nullptr); } else { - passwordGenerator.setLength(passwordLength.toInt()); + passwordGenerator->setLength(passwordLength.toInt()); } PasswordGenerator::CharClasses classes = 0x0; @@ -124,16 +119,34 @@ int Generate::execute(const QStringList& arguments) flags |= PasswordGenerator::CharFromEveryGroup; } - passwordGenerator.setCharClasses(classes); - passwordGenerator.setFlags(flags); - passwordGenerator.setExcludedChars(parser->value(Generate::ExcludeCharsOption)); + // The default charset will be used if no explicit class + // option was set. + passwordGenerator->setCharClasses(classes); + passwordGenerator->setFlags(flags); + passwordGenerator->setExcludedChars(parser->value(Generate::ExcludeCharsOption)); - if (!passwordGenerator.isValid()) { + if (!passwordGenerator->isValid()) { errorTextStream << QObject::tr("invalid password generator after applying all options") << endl; + return QSharedPointer(nullptr); + } + + return passwordGenerator; +} + +int Generate::execute(const QStringList& arguments) +{ + QSharedPointer parser = getCommandLineParser(arguments); + if (parser.isNull()) { return EXIT_FAILURE; } - QString password = passwordGenerator.generatePassword(); + QSharedPointer passwordGenerator = Generate::createGenerator(parser); + if (passwordGenerator.isNull()) { + return EXIT_FAILURE; + } + + TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); + QString password = passwordGenerator->generatePassword(); outputTextStream << password << endl; return EXIT_SUCCESS; diff --git a/src/cli/Generate.h b/src/cli/Generate.h index 88477be99..c850ef1be 100644 --- a/src/cli/Generate.h +++ b/src/cli/Generate.h @@ -20,12 +20,16 @@ #include "Command.h" +#include "core/PasswordGenerator.h" + class Generate : public Command { public: Generate(); int execute(const QStringList& arguments) override; + static QSharedPointer createGenerator(QSharedPointer parser); + static const QCommandLineOption PasswordLengthOption; static const QCommandLineOption LowerCaseOption; static const QCommandLineOption UpperCaseOption; diff --git a/src/cli/keepassxc-cli.1 b/src/cli/keepassxc-cli.1 index 873a973ef..18d249170 100644 --- a/src/cli/keepassxc-cli.1 +++ b/src/cli/keepassxc-cli.1 @@ -1,4 +1,4 @@ -.TH KEEPASSXC-CLI 1 "Nov 04, 2018" +.TH KEEPASSXC-CLI 1 "June 15, 2019" .SH NAME keepassxc-cli \- command line interface for the \fBKeePassXC\fP password manager. @@ -15,6 +15,7 @@ keepassxc-cli \- command line interface for the \fBKeePassXC\fP password manager .IP "add [options] " Adds a new entry to a database. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option). +The same password generation options as documented for the generate command can be used when the \fI-g\fP option is set. .IP "analyze [options] " Analyze passwords in a database for weaknesses. @@ -30,6 +31,7 @@ Generate a random diceware passphrase. .IP "edit [options] " Edits a database entry. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option). +The same password generation options as documented for the generate command can be used when the \fI-g\fP option is set. .IP "estimate [options] [password]" Estimates the entropy of a password. The password to estimate can be provided as a positional argument, or using the standard input. @@ -94,6 +96,8 @@ Use the same credentials for unlocking both database. .SS "Add and edit options" +The same password generation options as documented for the generate command can be used +with those 2 commands when the -g option is set. .IP "-u, --username " Specify the username of the entry. @@ -107,9 +111,6 @@ Use a password prompt for the entry's password. .IP "-g, --generate" Generate a new password for the entry. -.IP "-l, --password-length" -Specify the length of the password to generate. - .SS "Edit options" @@ -176,21 +177,29 @@ Flattens the output to single lines. When this option is enabled, subgroups and .IP "-L, --length " Desired length for the generated password. [Default: 16] -.IP "-l" +.IP "-l --lower" Use lowercase characters for the generated password. [Default: Enabled] -.IP "-u" +.IP "-U --upper" Use uppercase characters for the generated password. [Default: Enabled] -.IP "-n" +.IP "-n --numeric" Use numbers characters for the generated password. [Default: Enabled] -.IP "-s" +.IP "-s --special" Use special characters for the generated password. [Default: Disabled] -.IP "-e" +.IP "-e --extended" Use extended ASCII characters for the generated password. [Default: Disabled] +.IP "-x --exclude " +Comma-separated list of characters to exclude from the generated password. None is excluded by default. + +.IP "--exclude-similar" +Exclude similar looking characters. [Default: Disabled] + +.IP "--every-group" +Include characters from every selected group. [Default: Disabled] .SH REPORTING BUGS diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index d65a7af6c..d51f90d03 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 KeePassXC Team + * Copyright (C) 2019 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -193,7 +193,7 @@ void TestCli::testAdd() "--url", "https://example.com/", "-g", - "-l", + "-L", "20", m_dbFile->fileName(), "/newuser-entry"}); @@ -212,13 +212,17 @@ void TestCli::testAdd() // Quiet option qint64 pos = m_stdoutFile->pos(); + qint64 posErr = m_stderrFile->pos(); Utils::Test::setNextPassword("a"); - addCmd.execute({"add", "-q", "-u", "newuser", "-g", "-l", "20", m_dbFile->fileName(), "/newentry-quiet"}); + addCmd.execute({"add", "-q", "-u", "newuser", "-g", "-L", "20", m_dbFile->fileName(), "/newentry-quiet"}); m_stdoutFile->seek(pos); + m_stderrFile->seek(posErr); QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("")); db = readTestDatabase(); entry = db->rootGroup()->findEntryByPath("/newentry-quiet"); QVERIFY(entry); + QCOMPARE(entry->password().size(), 20); Utils::Test::setNextPassword("a"); Utils::Test::setNextPassword("newpassword"); @@ -227,9 +231,6 @@ void TestCli::testAdd() "newuser2", "--url", "https://example.net/", - "-g", - "-l", - "20", "-p", m_dbFile->fileName(), "/newuser-entry2"}); @@ -240,6 +241,61 @@ void TestCli::testAdd() QCOMPARE(entry->username(), QString("newuser2")); QCOMPARE(entry->url(), QString("https://example.net/")); QCOMPARE(entry->password(), QString("newpassword")); + + // Password generation options + pos = m_stdoutFile->pos(); + posErr = m_stderrFile->pos(); + Utils::Test::setNextPassword("a"); + addCmd.execute({"add", + "-u", + "newuser3", + "-g", + "-L", + "34", + m_dbFile->fileName(), + "/newuser-entry3"}); + m_stdoutFile->seek(pos); + m_stderrFile->seek(posErr); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry3.\n")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/newuser-entry3"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser3")); + QCOMPARE(entry->password().size(), 34); + QRegularExpression defaultPasswordClassesRegex("^[a-zA-Z0-9]+$"); + QVERIFY(defaultPasswordClassesRegex.match(entry->password()).hasMatch()); + + pos = m_stdoutFile->pos(); + posErr = m_stderrFile->pos(); + Utils::Test::setNextPassword("a"); + addCmd.execute({"add", + "-u", + "newuser4", + "-g", + "-L", + "20", + "--every-group", + "-s", + "-n", + "-U", + "-l", + m_dbFile->fileName(), + "/newuser-entry4"}); + m_stdoutFile->seek(pos); + m_stderrFile->seek(posErr); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry4.\n")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/newuser-entry4"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser4")); + QCOMPARE(entry->password().size(), 20); + QVERIFY(!defaultPasswordClassesRegex.match(entry->password()).hasMatch()); } void TestCli::testAnalyze() @@ -515,15 +571,18 @@ void TestCli::testEdit() // Quiet option qint64 pos = m_stdoutFile->pos(); + qint64 posErr = m_stderrFile->pos(); Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", m_dbFile->fileName(), "-q", "-t", "newtitle", "/Sample Entry"}); + editCmd.execute({"edit", m_dbFile->fileName(), "-q", "-t", "newertitle", "/newtitle"}); m_stdoutFile->seek(pos); + m_stderrFile->seek(posErr); QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("")); Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newtitle"}); + editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newertitle"}); db = readTestDatabase(); - entry = db->rootGroup()->findEntryByPath("/newtitle"); + entry = db->rootGroup()->findEntryByPath("/newertitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); @@ -531,20 +590,48 @@ void TestCli::testEdit() QVERIFY(entry->password() != QString("Password")); Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", "-g", "-l", "34", "-t", "yet another title", m_dbFile->fileName(), "/newtitle"}); + editCmd.execute({"edit", "-g", "-L", "34", "-t", "evennewertitle", m_dbFile->fileName(), "/newertitle"}); db = readTestDatabase(); - entry = db->rootGroup()->findEntryByPath("/yet another title"); + entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); QVERIFY(entry->password() != QString("Password")); QCOMPARE(entry->password().size(), 34); + QRegularExpression defaultPasswordClassesRegex("^[a-zA-Z0-9]+$"); + QVERIFY(defaultPasswordClassesRegex.match(entry->password()).hasMatch()); + + pos = m_stdoutFile->pos(); + posErr = m_stderrFile->pos(); + Utils::Test::setNextPassword("a"); + editCmd.execute({"edit", + "-g", + "-L", + "20", + "--every-group", + "-s", + "-n", + "--upper", + "-l", + m_dbFile->fileName(), + "/evennewertitle"}); + m_stdoutFile->seek(pos); + m_stderrFile->seek(posErr); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully edited entry evennewertitle.\n")); + + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/evennewertitle"); + QVERIFY(entry); + QCOMPARE(entry->password().size(), 20); + QVERIFY(!defaultPasswordClassesRegex.match(entry->password()).hasMatch()); Utils::Test::setNextPassword("a"); Utils::Test::setNextPassword("newpassword"); - editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/yet another title"}); + editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/evennewertitle"}); db = readTestDatabase(); - entry = db->rootGroup()->findEntryByPath("/yet another title"); + entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->password(), QString("newpassword")); } @@ -697,7 +784,7 @@ void TestCli::testGenerate_data() QTest::newRow("default") << QStringList{"generate"} << "^[^\r\n]+$"; QTest::newRow("length") << QStringList{"generate", "-L", "13"} << "^.{13}$"; QTest::newRow("lowercase") << QStringList{"generate", "-L", "14", "-l"} << "^[a-z]{14}$"; - QTest::newRow("uppercase") << QStringList{"generate", "-L", "15", "-u"} << "^[A-Z]{15}$"; + QTest::newRow("uppercase") << QStringList{"generate", "-L", "15", "--upper"} << "^[A-Z]{15}$"; QTest::newRow("numbers") << QStringList{"generate", "-L", "16", "-n"} << "^[0-9]{16}$"; QTest::newRow("special") << QStringList{"generate", "-L", "200", "-s"} << R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!+-<=>?#$%&^`@~]{200}$)"; @@ -706,13 +793,13 @@ void TestCli::testGenerate_data() QTest::newRow("extended") << QStringList{"generate", "-L", "50", "-e"} << R"(^[^a-zA-Z0-9\(\)\[\]\{\}\.\-\*\|\\,:;"'\/\_!+-<=>?#$%&^`@~]{50}$)"; QTest::newRow("numbers + lowercase + uppercase") - << QStringList{"generate", "-L", "16", "-n", "-u", "-l"} << "^[0-9a-zA-Z]{16}$"; + << QStringList{"generate", "-L", "16", "-n", "--upper", "-l"} << "^[0-9a-zA-Z]{16}$"; QTest::newRow("numbers + lowercase + uppercase (exclude)") - << QStringList{"generate", "-L", "500", "-n", "-u", "-l", "-x", "abcdefg0123@"} << "^[^abcdefg0123@]{500}$"; + << QStringList{"generate", "-L", "500", "-n", "-U", "-l", "-x", "abcdefg0123@"} << "^[^abcdefg0123@]{500}$"; QTest::newRow("numbers + lowercase + uppercase (exclude similar)") - << QStringList{"generate", "-L", "200", "-n", "-u", "-l", "--exclude-similar"} << "^[^l1IO0]{200}$"; + << QStringList{"generate", "-L", "200", "-n", "-U", "-l", "--exclude-similar"} << "^[^l1IO0]{200}$"; QTest::newRow("uppercase + lowercase (every)") - << QStringList{"generate", "-L", "2", "-u", "-l", "--every-group"} << "^[a-z][A-Z]|[A-Z][a-z]$"; + << QStringList{"generate", "-L", "2", "--upper", "-l", "--every-group"} << "^[a-z][A-Z]|[A-Z][a-z]$"; QTest::newRow("numbers + lowercase (every)") << QStringList{"generate", "-L", "2", "-n", "-l", "--every-group"} << "^[a-z][0-9]|[0-9][a-z]$"; } diff --git a/tests/TestCli.h b/tests/TestCli.h index dcc84c2e3..c012fc807 100644 --- a/tests/TestCli.h +++ b/tests/TestCli.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 KeePassXC Team + * Copyright (C) 2019 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by