mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Add CLI tests and improve coding style and i18n
The CLI module was lacking unit test coverage and showed some severe coding style violations, which this patch addresses. In addition, all uses of qCritical() with untranslatble raw char* sequences were removed in favor of proper locale strings. These are written to STDERR through QTextStreams and support output redirection for testing purposes. With this change, error messages don't depend on the global Qt logging settings and targets anymore and go directly to the terminal or into a file if needed. This patch also fixes a bug discovered during unit test development, where the extract command would just dump the raw XML contents without decrypting embedded Salsa20-protected values first, making the XML export mostly useless, since passwords are scrambled. Lastly, all CLI commands received a dedicated -h/--help option.
This commit is contained in:
parent
18b22834c1
commit
113c8eb702
@ -303,16 +303,16 @@ endif()
|
||||
include(CLangFormat)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools DBus REQUIRED)
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools DBus REQUIRED)
|
||||
elseif(APPLE)
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools REQUIRED
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools REQUIRED
|
||||
HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH
|
||||
)
|
||||
find_package(Qt5 COMPONENTS MacExtras
|
||||
HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH
|
||||
)
|
||||
else()
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools REQUIRED)
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools REQUIRED)
|
||||
endif()
|
||||
|
||||
if(Qt5Core_VERSION VERSION_LESS "5.2.0")
|
||||
@ -345,9 +345,7 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
|
||||
find_package(LibGPGError REQUIRED)
|
||||
find_package(Gcrypt 1.7.0 REQUIRED)
|
||||
find_package(Argon2 REQUIRED)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
find_package(QREncode REQUIRED)
|
||||
|
||||
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
|
||||
|
@ -57,7 +57,9 @@ RUN set -x \
|
||||
mesa-common-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev \
|
||||
libqrencode-dev
|
||||
libqrencode-dev \
|
||||
xclip \
|
||||
xvfb
|
||||
|
||||
ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}"
|
||||
ENV CMAKE_PREFIX_PATH="/opt/${QT5_VERSION}/lib/cmake"
|
||||
|
@ -39,6 +39,7 @@ RUN set -x \
|
||||
clang-3.6 \
|
||||
libclang-common-3.6-dev \
|
||||
clang-format-3.6 \
|
||||
llvm-3.6 \
|
||||
cmake3 \
|
||||
make \
|
||||
libgcrypt20-18-dev \
|
||||
@ -56,6 +57,7 @@ RUN set -x \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
libqrencode-dev \
|
||||
xclip \
|
||||
xvfb
|
||||
|
||||
ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}"
|
||||
|
@ -51,6 +51,7 @@ set(keepassx_SOURCES
|
||||
core/EntryAttributes.cpp
|
||||
core/EntrySearcher.cpp
|
||||
core/FilePath.cpp
|
||||
core/Bootstrap.cpp
|
||||
core/Global.h
|
||||
core/Group.cpp
|
||||
core/InactivityTimer.cpp
|
||||
|
@ -271,7 +271,6 @@ QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString password = browserSettings()->generatePassword();
|
||||
const QString bits = QString::number(browserSettings()->getbits()); // For some reason this always returns 1140 bits?
|
||||
|
||||
if (nonce.isEmpty() || password.isEmpty()) {
|
||||
return QJsonObject();
|
||||
|
@ -485,11 +485,6 @@ QString BrowserSettings::generatePassword()
|
||||
}
|
||||
}
|
||||
|
||||
int BrowserSettings::getbits()
|
||||
{
|
||||
return m_passwordGenerator.getbits();
|
||||
}
|
||||
|
||||
void BrowserSettings::updateBinaryPaths(QString customProxyLocation)
|
||||
{
|
||||
bool isProxy = supportBrowserProxy();
|
||||
|
@ -112,7 +112,6 @@ public:
|
||||
PasswordGenerator::CharClasses passwordCharClasses();
|
||||
PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
|
||||
QString generatePassword();
|
||||
int getbits();
|
||||
void updateBinaryPaths(QString customProxyLocation = QString());
|
||||
|
||||
private:
|
||||
|
@ -41,22 +41,20 @@ Add::~Add()
|
||||
|
||||
int Add::execute(const QStringList& arguments)
|
||||
{
|
||||
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
|
||||
QCommandLineOption username(QStringList() << "u"
|
||||
<< "username",
|
||||
QCommandLineOption username(QStringList() << "u" << "username",
|
||||
QObject::tr("Username for the entry."),
|
||||
QObject::tr("username"));
|
||||
parser.addOption(username);
|
||||
@ -64,23 +62,22 @@ int Add::execute(const QStringList& arguments)
|
||||
QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
|
||||
parser.addOption(url);
|
||||
|
||||
QCommandLineOption prompt(QStringList() << "p"
|
||||
<< "password-prompt",
|
||||
QCommandLineOption prompt(QStringList() << "p" << "password-prompt",
|
||||
QObject::tr("Prompt for the entry's password."));
|
||||
parser.addOption(prompt);
|
||||
|
||||
QCommandLineOption generate(QStringList() << "g"
|
||||
<< "generate",
|
||||
QCommandLineOption generate(QStringList() << "g" << "generate",
|
||||
QObject::tr("Generate a password for the entry."));
|
||||
parser.addOption(generate);
|
||||
|
||||
QCommandLineOption length(QStringList() << "l"
|
||||
<< "password-length",
|
||||
QCommandLineOption length(QStringList() << "l" << "password-length",
|
||||
QObject::tr("Length for the generated password."),
|
||||
QObject::tr("length"));
|
||||
parser.addOption(length);
|
||||
|
||||
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to add."));
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -89,11 +86,11 @@ int Add::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString databasePath = args.at(0);
|
||||
QString entryPath = args.at(1);
|
||||
const QString& databasePath = args.at(0);
|
||||
const QString& entryPath = args.at(1);
|
||||
|
||||
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile));
|
||||
if (db == nullptr) {
|
||||
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -101,13 +98,13 @@ int Add::execute(const QStringList& arguments)
|
||||
// the entry.
|
||||
QString passwordLength = parser.value(length);
|
||||
if (!passwordLength.isEmpty() && !passwordLength.toInt()) {
|
||||
qCritical("Invalid value for password length %s.", qPrintable(passwordLength));
|
||||
errorTextStream << QObject::tr("Invalid value for password length %1.").arg(passwordLength) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Entry* entry = db->rootGroup()->addEntryWithPath(entryPath);
|
||||
if (!entry) {
|
||||
qCritical("Could not create entry with path %s.", qPrintable(entryPath));
|
||||
errorTextStream << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -120,8 +117,7 @@ int Add::execute(const QStringList& arguments)
|
||||
}
|
||||
|
||||
if (parser.isSet(prompt)) {
|
||||
outputTextStream << "Enter password for new entry: ";
|
||||
outputTextStream.flush();
|
||||
outputTextStream << QObject::tr("Enter password for new entry: ") << flush;
|
||||
QString password = Utils::getPassword();
|
||||
entry->setPassword(password);
|
||||
} else if (parser.isSet(generate)) {
|
||||
@ -130,7 +126,7 @@ int Add::execute(const QStringList& arguments)
|
||||
if (passwordLength.isEmpty()) {
|
||||
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
|
||||
} else {
|
||||
passwordGenerator.setLength(passwordLength.toInt());
|
||||
passwordGenerator.setLength(static_cast<size_t>(passwordLength.toInt()));
|
||||
}
|
||||
|
||||
passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset);
|
||||
@ -141,10 +137,10 @@ int Add::execute(const QStringList& arguments)
|
||||
|
||||
QString errorMessage = db->saveToFile(databasePath);
|
||||
if (!errorMessage.isEmpty()) {
|
||||
qCritical("Writing the database failed %s.", qPrintable(errorMessage));
|
||||
errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
outputTextStream << "Successfully added entry " << entry->title() << "." << endl;
|
||||
outputTextStream << QObject::tr("Successfully added entry %1.").arg(entry->title()) << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -42,20 +42,19 @@ Clip::~Clip()
|
||||
|
||||
int Clip::execute(const QStringList& arguments)
|
||||
{
|
||||
|
||||
QTextStream out(stdout);
|
||||
QTextStream out(Utils::STDOUT);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"));
|
||||
parser.addPositionalArgument(
|
||||
"timeout", QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]"));
|
||||
parser.addPositionalArgument("timeout",
|
||||
QObject::tr("Timeout in seconds before clearing the clipboard."), "[timeout]");
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -64,29 +63,30 @@ int Clip::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return this->clipEntry(db, args.at(1), args.value(2));
|
||||
return clipEntry(db, args.at(1), args.value(2));
|
||||
}
|
||||
|
||||
int Clip::clipEntry(Database* database, QString entryPath, QString timeout)
|
||||
{
|
||||
QTextStream err(Utils::STDERR);
|
||||
|
||||
int timeoutSeconds = 0;
|
||||
if (!timeout.isEmpty() && !timeout.toInt()) {
|
||||
qCritical("Invalid timeout value %s.", qPrintable(timeout));
|
||||
err << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (!timeout.isEmpty()) {
|
||||
timeoutSeconds = timeout.toInt();
|
||||
}
|
||||
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
Entry* entry = database->rootGroup()->findEntry(entryPath);
|
||||
if (!entry) {
|
||||
qCritical("Entry %s not found.", qPrintable(entryPath));
|
||||
err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -95,20 +95,23 @@ int Clip::clipEntry(Database* database, QString entryPath, QString timeout)
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
outputTextStream << "Entry's password copied to the clipboard!" << endl;
|
||||
outputTextStream << QObject::tr("Entry's password copied to the clipboard!") << endl;
|
||||
|
||||
if (!timeoutSeconds) {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
QString lastLine = "";
|
||||
while (timeoutSeconds > 0) {
|
||||
outputTextStream << "\rClearing the clipboard in " << timeoutSeconds << " seconds...";
|
||||
outputTextStream.flush();
|
||||
outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r';
|
||||
lastLine = QObject::tr("Clearing the clipboard in %1 second(s)...", "", timeoutSeconds).arg(timeoutSeconds);
|
||||
outputTextStream << lastLine << flush;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
timeoutSeconds--;
|
||||
--timeoutSeconds;
|
||||
}
|
||||
Utils::clipText("");
|
||||
outputTextStream << "\nClipboard cleared!" << endl;
|
||||
outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r';
|
||||
outputTextStream << QObject::tr("Clipboard cleared!") << endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -41,19 +41,14 @@ Command::~Command()
|
||||
{
|
||||
}
|
||||
|
||||
int Command::execute(const QStringList&)
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString Command::getDescriptionLine()
|
||||
{
|
||||
|
||||
QString response = this->name;
|
||||
QString response = name;
|
||||
QString space(" ");
|
||||
QString spaces = space.repeated(15 - this->name.length());
|
||||
QString spaces = space.repeated(15 - name.length());
|
||||
response = response.append(spaces);
|
||||
response = response.append(this->description);
|
||||
response = response.append(description);
|
||||
response = response.append("\n");
|
||||
return response;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class Command
|
||||
{
|
||||
public:
|
||||
virtual ~Command();
|
||||
virtual int execute(const QStringList& arguments);
|
||||
virtual int execute(const QStringList& arguments) = 0;
|
||||
QString name;
|
||||
QString description;
|
||||
QString getDescriptionLine();
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/PassphraseGenerator.h"
|
||||
#include "Utils.h"
|
||||
|
||||
Diceware::Diceware()
|
||||
{
|
||||
@ -37,26 +38,25 @@ Diceware::~Diceware()
|
||||
|
||||
int Diceware::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream in(Utils::STDIN, QIODevice::ReadOnly);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
QCommandLineOption words(QStringList() << "W"
|
||||
<< "words",
|
||||
parser.setApplicationDescription(description);
|
||||
QCommandLineOption words(QStringList() << "W" << "words",
|
||||
QObject::tr("Word count for the diceware passphrase."),
|
||||
QObject::tr("count"));
|
||||
QObject::tr("count", "CLI parameter"));
|
||||
parser.addOption(words);
|
||||
QCommandLineOption wordlistFile(QStringList() << "w"
|
||||
<< "word-list",
|
||||
QCommandLineOption wordlistFile(QStringList() << "w" << "word-list",
|
||||
QObject::tr("Wordlist for the diceware generator.\n[Default: EFF English]"),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(wordlistFile);
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (!args.isEmpty()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -76,12 +76,12 @@ int Diceware::execute(const QStringList& arguments)
|
||||
}
|
||||
|
||||
if (!dicewareGenerator.isValid()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString password = dicewareGenerator.generatePassphrase();
|
||||
outputTextStream << password << endl;
|
||||
out << password << endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -41,22 +41,20 @@ Edit::~Edit()
|
||||
|
||||
int Edit::execute(const QStringList& arguments)
|
||||
{
|
||||
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream in(Utils::STDIN, QIODevice::ReadOnly);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
|
||||
QCommandLineOption username(QStringList() << "u"
|
||||
<< "username",
|
||||
QCommandLineOption username(QStringList() << "u" << "username",
|
||||
QObject::tr("Username for the entry."),
|
||||
QObject::tr("username"));
|
||||
parser.addOption(username);
|
||||
@ -64,61 +62,58 @@ int Edit::execute(const QStringList& arguments)
|
||||
QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
|
||||
parser.addOption(url);
|
||||
|
||||
QCommandLineOption title(QStringList() << "t"
|
||||
<< "title",
|
||||
QCommandLineOption title(QStringList() << "t" << "title",
|
||||
QObject::tr("Title for the entry."),
|
||||
QObject::tr("title"));
|
||||
parser.addOption(title);
|
||||
|
||||
QCommandLineOption prompt(QStringList() << "p"
|
||||
<< "password-prompt",
|
||||
QCommandLineOption prompt(QStringList() << "p" << "password-prompt",
|
||||
QObject::tr("Prompt for the entry's password."));
|
||||
parser.addOption(prompt);
|
||||
|
||||
QCommandLineOption generate(QStringList() << "g"
|
||||
<< "generate",
|
||||
QCommandLineOption generate(QStringList() << "g" << "generate",
|
||||
QObject::tr("Generate a password for the entry."));
|
||||
parser.addOption(generate);
|
||||
|
||||
QCommandLineOption length(QStringList() << "l"
|
||||
<< "password-length",
|
||||
QCommandLineOption length(QStringList() << "l" << "password-length",
|
||||
QObject::tr("Length for the generated password."),
|
||||
QObject::tr("length"));
|
||||
parser.addOption(length);
|
||||
|
||||
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to edit."));
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString databasePath = args.at(0);
|
||||
QString entryPath = args.at(1);
|
||||
const QString& databasePath = args.at(0);
|
||||
const QString& entryPath = args.at(1);
|
||||
|
||||
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile));
|
||||
if (db == nullptr) {
|
||||
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString passwordLength = parser.value(length);
|
||||
if (!passwordLength.isEmpty() && !passwordLength.toInt()) {
|
||||
qCritical("Invalid value for password length %s.", qPrintable(passwordLength));
|
||||
err << QObject::tr("Invalid value for password length: %1").arg(passwordLength) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Entry* entry = db->rootGroup()->findEntryByPath(entryPath);
|
||||
if (!entry) {
|
||||
qCritical("Could not find entry with path %s.", qPrintable(entryPath));
|
||||
err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (parser.value("username").isEmpty() && parser.value("url").isEmpty() && parser.value("title").isEmpty()
|
||||
&& !parser.isSet(prompt)
|
||||
&& !parser.isSet(generate)) {
|
||||
qCritical("Not changing any field for entry %s.", qPrintable(entryPath));
|
||||
err << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -137,8 +132,7 @@ int Edit::execute(const QStringList& arguments)
|
||||
}
|
||||
|
||||
if (parser.isSet(prompt)) {
|
||||
outputTextStream << "Enter new password for entry: ";
|
||||
outputTextStream.flush();
|
||||
out << QObject::tr("Enter new password for entry: ") << flush;
|
||||
QString password = Utils::getPassword();
|
||||
entry->setPassword(password);
|
||||
} else if (parser.isSet(generate)) {
|
||||
@ -147,7 +141,7 @@ int Edit::execute(const QStringList& arguments)
|
||||
if (passwordLength.isEmpty()) {
|
||||
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
|
||||
} else {
|
||||
passwordGenerator.setLength(passwordLength.toInt());
|
||||
passwordGenerator.setLength(static_cast<size_t>(passwordLength.toInt()));
|
||||
}
|
||||
|
||||
passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset);
|
||||
@ -160,10 +154,10 @@ int Edit::execute(const QStringList& arguments)
|
||||
|
||||
QString errorMessage = db->saveToFile(databasePath);
|
||||
if (!errorMessage.isEmpty()) {
|
||||
qCritical("Writing the database failed %s.", qPrintable(errorMessage));
|
||||
err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
outputTextStream << "Successfully edited entry " << entry->title() << "." << endl;
|
||||
out << QObject::tr("Successfully edited entry %1.").arg(entry->title()) << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "Estimate.h"
|
||||
#include "cli/Utils.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
@ -44,117 +45,126 @@ Estimate::~Estimate()
|
||||
|
||||
static void estimate(const char* pwd, bool advanced)
|
||||
{
|
||||
double e;
|
||||
int len = strlen(pwd);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
double e = 0.0;
|
||||
int len = static_cast<int>(strlen(pwd));
|
||||
if (!advanced) {
|
||||
e = ZxcvbnMatch(pwd, 0, 0);
|
||||
printf("Length %d\tEntropy %.3f\tLog10 %.3f\n", len, e, e * 0.301029996);
|
||||
e = ZxcvbnMatch(pwd, nullptr, nullptr);
|
||||
out << QObject::tr("Length %1").arg(len, 0) << '\t'
|
||||
<< QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t'
|
||||
<< QObject::tr("Log10 %1").arg(e * 0.301029996, 0, 'f', 3) << endl;
|
||||
} else {
|
||||
int ChkLen;
|
||||
int ChkLen = 0;
|
||||
ZxcMatch_t *info, *p;
|
||||
double m = 0.0;
|
||||
e = ZxcvbnMatch(pwd, 0, &info);
|
||||
e = ZxcvbnMatch(pwd, nullptr, &info);
|
||||
for (p = info; p; p = p->Next) {
|
||||
m += p->Entrpy;
|
||||
}
|
||||
m = e - m;
|
||||
printf("Length %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", len, e, e * 0.301029996, m);
|
||||
out << QObject::tr("Length %1").arg(len) << '\t'
|
||||
<< QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t'
|
||||
<< QObject::tr("Log10 %1").arg(e * 0.301029996, 0, 'f', 3) << "\n "
|
||||
<< QObject::tr("Multi-word extra bits %1").arg(m, 0, 'f', 1) << endl;
|
||||
p = info;
|
||||
ChkLen = 0;
|
||||
while (p) {
|
||||
int n;
|
||||
switch (static_cast<int>(p->Type)) {
|
||||
case BRUTE_MATCH:
|
||||
printf(" Type: Bruteforce ");
|
||||
out << " " << QObject::tr("Type: Bruteforce") << " ";
|
||||
break;
|
||||
case DICTIONARY_MATCH:
|
||||
printf(" Type: Dictionary ");
|
||||
out << " " << QObject::tr("Type: Dictionary") << " ";
|
||||
break;
|
||||
case DICT_LEET_MATCH:
|
||||
printf(" Type: Dict+Leet ");
|
||||
out << " " << QObject::tr("Type: Dict+Leet") << " ";
|
||||
break;
|
||||
case USER_MATCH:
|
||||
printf(" Type: User Words ");
|
||||
out << " " << QObject::tr("Type: User Words") << " ";
|
||||
break;
|
||||
case USER_LEET_MATCH:
|
||||
printf(" Type: User+Leet ");
|
||||
out << " " << QObject::tr("Type: User+Leet") << " ";
|
||||
break;
|
||||
case REPEATS_MATCH:
|
||||
printf(" Type: Repeated ");
|
||||
out << " " << QObject::tr("Type: Repeated") << " ";
|
||||
break;
|
||||
case SEQUENCE_MATCH:
|
||||
printf(" Type: Sequence ");
|
||||
out << " " << QObject::tr("Type: Sequence") << " ";
|
||||
break;
|
||||
case SPATIAL_MATCH:
|
||||
printf(" Type: Spatial ");
|
||||
out << " " << QObject::tr("Type: Spatial") << " ";
|
||||
break;
|
||||
case DATE_MATCH:
|
||||
printf(" Type: Date ");
|
||||
out << " " << QObject::tr("Type: Date") << " ";
|
||||
break;
|
||||
case BRUTE_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Bruteforce(Rep)");
|
||||
out << " " << QObject::tr("Type: Bruteforce(Rep)") << " ";
|
||||
break;
|
||||
case DICTIONARY_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Dictionary(Rep)");
|
||||
out << " " << QObject::tr("Type: Dictionary(Rep)") << " ";
|
||||
break;
|
||||
case DICT_LEET_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Dict+Leet(Rep) ");
|
||||
out << " " << QObject::tr("Type: Dict+Leet(Rep)") << " ";
|
||||
break;
|
||||
case USER_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: User Words(Rep)");
|
||||
out << " " << QObject::tr("Type: User Words(Rep)") << " ";
|
||||
break;
|
||||
case USER_LEET_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: User+Leet(Rep) ");
|
||||
out << " " << QObject::tr("Type: User+Leet(Rep)") << " ";
|
||||
break;
|
||||
case REPEATS_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Repeated(Rep) ");
|
||||
out << " " << QObject::tr("Type: Repeated(Rep)") << " ";
|
||||
break;
|
||||
case SEQUENCE_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Sequence(Rep) ");
|
||||
out << " " << QObject::tr("Type: Sequence(Rep)") << " ";
|
||||
break;
|
||||
case SPATIAL_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Spatial(Rep) ");
|
||||
out << " " << QObject::tr("Type: Spatial(Rep)") << " ";
|
||||
break;
|
||||
case DATE_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Date(Rep) ");
|
||||
out << " " << QObject::tr("Type: Date(Rep)") << " ";
|
||||
break;
|
||||
|
||||
default:
|
||||
printf(" Type: Unknown%d ", p->Type);
|
||||
out << " " << QObject::tr("Type: Unknown%1").arg(p->Type) << " ";
|
||||
break;
|
||||
}
|
||||
ChkLen += p->Length;
|
||||
printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996);
|
||||
|
||||
out << QObject::tr("Length %1").arg(p->Length) << '\t'
|
||||
<< QObject::tr("Entropy %1 (%2)").arg(p->Entrpy, 6, 'f', 3).arg(p->Entrpy * 0.301029996, 0, 'f', 2) << '\t';
|
||||
for (n = 0; n < p->Length; ++n, ++pwd) {
|
||||
printf("%c", *pwd);
|
||||
out << *pwd;
|
||||
}
|
||||
printf("\n");
|
||||
out << endl;
|
||||
p = p->Next;
|
||||
}
|
||||
ZxcvbnFreeInfo(info);
|
||||
if (ChkLen != len) {
|
||||
printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen);
|
||||
out << QObject::tr("*** Password length (%1) != sum of length of parts (%2) ***").arg(len).arg(ChkLen) << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Estimate::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream in(Utils::STDIN, QIODevice::ReadOnly);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("password", QObject::tr("Password for which to estimate the entropy."), "[password]");
|
||||
QCommandLineOption advancedOption(QStringList() << "a"
|
||||
<< "advanced",
|
||||
QCommandLineOption advancedOption(QStringList() << "a" << "advanced",
|
||||
QObject::tr("Perform advanced analysis on the password."));
|
||||
parser.addOption(advancedOption);
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() > 1) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -162,7 +172,7 @@ int Estimate::execute(const QStringList& arguments)
|
||||
if (args.size() == 1) {
|
||||
password = args.at(0);
|
||||
} else {
|
||||
password = inputTextStream.readLine();
|
||||
password = in.readLine();
|
||||
}
|
||||
|
||||
estimate(password.toLatin1(), parser.isSet(advancedOption));
|
||||
|
@ -43,17 +43,17 @@ Extract::~Extract()
|
||||
|
||||
int Extract::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream out(stdout);
|
||||
QTextStream errorTextStream(stderr);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database to extract."));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -62,8 +62,7 @@ int Extract::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0));
|
||||
out.flush();
|
||||
out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)) << flush;
|
||||
|
||||
auto compositeKey = QSharedPointer<CompositeKey>::create();
|
||||
|
||||
@ -74,52 +73,51 @@ int Extract::execute(const QStringList& arguments)
|
||||
|
||||
QString keyFilePath = parser.value(keyFile);
|
||||
if (!keyFilePath.isEmpty()) {
|
||||
// LCOV_EXCL_START
|
||||
auto fileKey = QSharedPointer<FileKey>::create();
|
||||
QString errorMsg;
|
||||
if (!fileKey->load(keyFilePath, &errorMsg)) {
|
||||
errorTextStream << QObject::tr("Failed to load key file %1 : %2").arg(keyFilePath).arg(errorMsg);
|
||||
errorTextStream << endl;
|
||||
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilePath).arg(errorMsg) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (fileKey->type() != FileKey::Hashed) {
|
||||
errorTextStream << QObject::tr("WARNING: You are using a legacy key file format which may become\n"
|
||||
"unsupported in the future.\n\n"
|
||||
"Please consider generating a new key file.");
|
||||
errorTextStream << endl;
|
||||
err << QObject::tr("WARNING: You are using a legacy key file format which may become\n"
|
||||
"unsupported in the future.\n\n"
|
||||
"Please consider generating a new key file.") << endl;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
compositeKey->addKey(fileKey);
|
||||
}
|
||||
|
||||
QString databaseFilename = args.at(0);
|
||||
const QString& databaseFilename = args.at(0);
|
||||
QFile dbFile(databaseFilename);
|
||||
if (!dbFile.exists()) {
|
||||
qCritical("File %s does not exist.", qPrintable(databaseFilename));
|
||||
err << QObject::tr("File %1 does not exist.").arg(databaseFilename) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!dbFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(databaseFilename));
|
||||
err << QObject::tr("Unable to open file %1.").arg(databaseFilename) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
reader.setSaveXml(true);
|
||||
Database* db = reader.readDatabase(&dbFile, compositeKey);
|
||||
delete db;
|
||||
QScopedPointer<Database> db(reader.readDatabase(&dbFile, compositeKey));
|
||||
|
||||
QByteArray xmlData = reader.reader()->xmlData();
|
||||
|
||||
if (reader.hasError()) {
|
||||
if (xmlData.isEmpty()) {
|
||||
qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString()));
|
||||
err << QObject::tr("Error while reading the database:\n%1").arg(reader.errorString()) << endl;
|
||||
} else {
|
||||
qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString()));
|
||||
err << QObject::tr("Error while parsing the database:\n%1").arg(reader.errorString()) << endl;
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << xmlData.constData() << "\n";
|
||||
out << xmlData.constData() << endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Generate.h"
|
||||
#include "cli/Utils.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
@ -37,38 +38,32 @@ Generate::~Generate()
|
||||
|
||||
int Generate::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream in(Utils::STDIN, QIODevice::ReadOnly);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
out.setCodec("UTF-8"); // force UTF-8 to prevent ??? characters in extended-ASCII passwords
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
QCommandLineOption len(QStringList() << "L"
|
||||
<< "length",
|
||||
parser.setApplicationDescription(description);
|
||||
QCommandLineOption len(QStringList() << "L" << "length",
|
||||
QObject::tr("Length of the generated password"),
|
||||
QObject::tr("length"));
|
||||
parser.addOption(len);
|
||||
QCommandLineOption lower(QStringList() << "l"
|
||||
<< "lower",
|
||||
QCommandLineOption lower(QStringList() << "l" << "lower",
|
||||
QObject::tr("Use lowercase characters"));
|
||||
parser.addOption(lower);
|
||||
QCommandLineOption upper(QStringList() << "u"
|
||||
<< "upper",
|
||||
QCommandLineOption upper(QStringList() << "u" << "upper",
|
||||
QObject::tr("Use uppercase characters"));
|
||||
parser.addOption(upper);
|
||||
QCommandLineOption numeric(QStringList() << "n"
|
||||
<< "numeric",
|
||||
QCommandLineOption numeric(QStringList() << "n" << "numeric",
|
||||
QObject::tr("Use numbers."));
|
||||
parser.addOption(numeric);
|
||||
QCommandLineOption special(QStringList() << "s"
|
||||
<< "special",
|
||||
QCommandLineOption special(QStringList() << "s" << "special",
|
||||
QObject::tr("Use special characters"));
|
||||
parser.addOption(special);
|
||||
QCommandLineOption extended(QStringList() << "e"
|
||||
<< "extended",
|
||||
QCommandLineOption extended(QStringList() << "e" << "extended",
|
||||
QObject::tr("Use extended ASCII"));
|
||||
parser.addOption(extended);
|
||||
QCommandLineOption exclude(QStringList() << "x"
|
||||
<< "exclude",
|
||||
QCommandLineOption exclude(QStringList() << "x" << "exclude",
|
||||
QObject::tr("Exclude character set"),
|
||||
QObject::tr("chars"));
|
||||
parser.addOption(exclude);
|
||||
@ -78,12 +73,12 @@ int Generate::execute(const QStringList& arguments)
|
||||
QCommandLineOption every_group(QStringList() << "every-group",
|
||||
QObject::tr("Include characters from every selected group"));
|
||||
parser.addOption(every_group);
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (!args.isEmpty()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -93,7 +88,7 @@ int Generate::execute(const QStringList& arguments)
|
||||
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
|
||||
} else {
|
||||
int length = parser.value(len).toInt();
|
||||
passwordGenerator.setLength(length);
|
||||
passwordGenerator.setLength(static_cast<size_t>(length));
|
||||
}
|
||||
|
||||
PasswordGenerator::CharClasses classes = 0x0;
|
||||
@ -128,12 +123,12 @@ int Generate::execute(const QStringList& arguments)
|
||||
passwordGenerator.setExcludedChars(parser.value(exclude));
|
||||
|
||||
if (!passwordGenerator.isValid()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString password = passwordGenerator.generatePassword();
|
||||
outputTextStream << password << endl;
|
||||
out << password << endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "List.h"
|
||||
#include "cli/Utils.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
@ -39,22 +40,20 @@ List::~List()
|
||||
|
||||
int List::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream out(stdout);
|
||||
QTextStream out(Utils::STDOUT);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), QString("[group]"));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), "[group]");
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
|
||||
QCommandLineOption recursiveOption(QStringList() << "R"
|
||||
<< "recursive",
|
||||
QCommandLineOption recursiveOption(QStringList() << "R" << "recursive",
|
||||
QObject::tr("Recursive mode, list elements recursively"));
|
||||
parser.addOption(recursiveOption);
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -65,33 +64,33 @@ int List::execute(const QStringList& arguments)
|
||||
|
||||
bool recursive = parser.isSet(recursiveOption);
|
||||
|
||||
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
if (db == nullptr) {
|
||||
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (args.size() == 2) {
|
||||
return this->listGroup(db, recursive, args.at(1));
|
||||
return listGroup(db.data(), recursive, args.at(1));
|
||||
}
|
||||
return this->listGroup(db, recursive);
|
||||
return listGroup(db.data(), recursive);
|
||||
}
|
||||
|
||||
int List::listGroup(Database* database, bool recursive, QString groupPath)
|
||||
int List::listGroup(Database* database, bool recursive, const QString& groupPath)
|
||||
{
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
if (groupPath.isEmpty()) {
|
||||
outputTextStream << database->rootGroup()->print(recursive);
|
||||
outputTextStream.flush();
|
||||
out << database->rootGroup()->print(recursive) << flush;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Group* group = database->rootGroup()->findGroupByPath(groupPath);
|
||||
if (group == nullptr) {
|
||||
qCritical("Cannot find group %s.", qPrintable(groupPath));
|
||||
if (!group) {
|
||||
err << QObject::tr("Cannot find group %1.").arg(groupPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
outputTextStream << group->print(recursive);
|
||||
outputTextStream.flush();
|
||||
out << group->print(recursive) << flush;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
List();
|
||||
~List();
|
||||
int execute(const QStringList& arguments);
|
||||
int listGroup(Database* database, bool recursive, QString groupPath = QString(""));
|
||||
int listGroup(Database* database, bool recursive, const QString& groupPath = {});
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_LIST_H
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include <utility>
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
@ -25,6 +27,7 @@
|
||||
#include <QTextStream>
|
||||
|
||||
#include "cli/Utils.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
@ -41,18 +44,17 @@ Locate::~Locate()
|
||||
|
||||
int Locate::execute(const QStringList& arguments)
|
||||
{
|
||||
|
||||
QTextStream out(stdout);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
parser.addPositionalArgument("term", QObject::tr("Search term."));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -61,26 +63,27 @@ int Locate::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return this->locateEntry(db, args.at(1));
|
||||
return locateEntry(db.data(), args.at(1));
|
||||
}
|
||||
|
||||
int Locate::locateEntry(Database* database, QString searchTerm)
|
||||
int Locate::locateEntry(Database* database, const QString& searchTerm)
|
||||
{
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QStringList results = database->rootGroup()->locate(searchTerm);
|
||||
if (results.isEmpty()) {
|
||||
outputTextStream << "No results for that search term" << endl;
|
||||
return EXIT_SUCCESS;
|
||||
err << "No results for that search term." << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for (QString result : results) {
|
||||
outputTextStream << result << endl;
|
||||
for (const QString& result : asConst(results)) {
|
||||
out << result << endl;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
Locate();
|
||||
~Locate();
|
||||
int execute(const QStringList& arguments);
|
||||
int locateEntry(Database* database, QString searchTerm);
|
||||
int locateEntry(Database* database, const QString& searchTerm);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_LOCATE_H
|
||||
|
@ -15,8 +15,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "Merge.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
@ -24,6 +22,9 @@
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Merger.h"
|
||||
#include "cli/Utils.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
Merge::Merge()
|
||||
{
|
||||
@ -37,29 +38,28 @@ Merge::~Merge()
|
||||
|
||||
int Merge::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream out(stdout);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database1", QObject::tr("Path of the database to merge into."));
|
||||
parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from."));
|
||||
|
||||
QCommandLineOption samePasswordOption(QStringList() << "s"
|
||||
<< "same-credentials",
|
||||
QCommandLineOption samePasswordOption(QStringList() << "s" << "same-credentials",
|
||||
QObject::tr("Use the same credentials for both database files."));
|
||||
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
QCommandLineOption keyFileFrom(QStringList() << "f"
|
||||
<< "key-file-from",
|
||||
QCommandLineOption keyFileFrom(QStringList() << "f" << "key-file-from",
|
||||
QObject::tr("Key file of the database to merge from."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFileFrom);
|
||||
|
||||
parser.addOption(samePasswordOption);
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -68,30 +68,30 @@ int Merge::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
if (db1 == nullptr) {
|
||||
QScopedPointer<Database> db1(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
|
||||
if (!db1) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database* db2;
|
||||
QScopedPointer<Database> db2;
|
||||
if (!parser.isSet("same-credentials")) {
|
||||
db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom));
|
||||
db2.reset(Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR));
|
||||
} else {
|
||||
db2 = Database::openDatabaseFile(args.at(1), db1->key());
|
||||
db2.reset(Database::openDatabaseFile(args.at(1), db1->key()));
|
||||
}
|
||||
if (db2 == nullptr) {
|
||||
if (!db2) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Merger merger(db2, db1);
|
||||
Merger merger(db2.data(), db1.data());
|
||||
merger.merge();
|
||||
|
||||
QString errorMessage = db1->saveToFile(args.at(0));
|
||||
if (!errorMessage.isEmpty()) {
|
||||
qCritical("Unable to save database to file : %s", qPrintable(errorMessage));
|
||||
err << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << "Successfully merged the database files.\n";
|
||||
out << "Successfully merged the database files." << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -44,40 +44,41 @@ Remove::~Remove()
|
||||
|
||||
int Remove::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream out(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main", "Remove an entry from the database."));
|
||||
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database."));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
parser.setApplicationDescription(QCoreApplication::tr("main", "Remove an entry from the database."));
|
||||
parser.addPositionalArgument("database", QCoreApplication::tr("main", "Path of the database."));
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Path of the entry to remove."));
|
||||
parser.addPositionalArgument("entry", QCoreApplication::tr("main", "Path of the entry to remove."));
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm");
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
if (db == nullptr) {
|
||||
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return this->removeEntry(db, args.at(0), args.at(1));
|
||||
return removeEntry(db.data(), args.at(0), args.at(1));
|
||||
}
|
||||
|
||||
int Remove::removeEntry(Database* database, QString databasePath, QString entryPath)
|
||||
int Remove::removeEntry(Database* database, const QString& databasePath, const QString& entryPath)
|
||||
{
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
|
||||
if (!entry) {
|
||||
qCritical("Entry %s not found.", qPrintable(entryPath));
|
||||
err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -92,14 +93,14 @@ int Remove::removeEntry(Database* database, QString databasePath, QString entryP
|
||||
|
||||
QString errorMessage = database->saveToFile(databasePath);
|
||||
if (!errorMessage.isEmpty()) {
|
||||
qCritical("Unable to save database to file : %s", qPrintable(errorMessage));
|
||||
err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (recycled) {
|
||||
outputTextStream << "Successfully recycled entry " << entryTitle << "." << endl;
|
||||
out << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl;
|
||||
} else {
|
||||
outputTextStream << "Successfully deleted entry " << entryTitle << "." << endl;
|
||||
out << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
@ -28,7 +28,7 @@ public:
|
||||
Remove();
|
||||
~Remove();
|
||||
int execute(const QStringList& arguments);
|
||||
int removeEntry(Database* database, QString databasePath, QString entryPath);
|
||||
int removeEntry(Database* database, const QString& databasePath, const QString& entryPath);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_REMOVE_H
|
||||
|
@ -15,17 +15,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Show.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Show.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Global.h"
|
||||
#include "Utils.h"
|
||||
|
||||
Show::Show()
|
||||
{
|
||||
@ -39,19 +41,17 @@ Show::~Show()
|
||||
|
||||
int Show::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream out(stdout);
|
||||
QTextStream out(Utils::STDOUT);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QCommandLineOption keyFile(QStringList() << "k" << "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
QCommandLineOption attributes(
|
||||
QStringList() << "a"
|
||||
<< "attributes",
|
||||
QStringList() << "a" << "attributes",
|
||||
QObject::tr(
|
||||
"Names of the attributes to show. "
|
||||
"This option can be specified more than once, with each attribute shown one-per-line in the given order. "
|
||||
@ -59,6 +59,7 @@ int Show::execute(const QStringList& arguments)
|
||||
QObject::tr("attribute"));
|
||||
parser.addOption(attributes);
|
||||
parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show."));
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
@ -67,23 +68,23 @@ int Show::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
if (db == nullptr) {
|
||||
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return this->showEntry(db, parser.values(attributes), args.at(1));
|
||||
return showEntry(db.data(), parser.values(attributes), args.at(1));
|
||||
}
|
||||
|
||||
int Show::showEntry(Database* database, QStringList attributes, QString entryPath)
|
||||
int Show::showEntry(Database* database, QStringList attributes, const QString& entryPath)
|
||||
{
|
||||
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
QTextStream in(Utils::STDIN, QIODevice::ReadOnly);
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
Entry* entry = database->rootGroup()->findEntry(entryPath);
|
||||
if (!entry) {
|
||||
qCritical("Could not find entry with path %s.", qPrintable(entryPath));
|
||||
err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -95,16 +96,16 @@ int Show::showEntry(Database* database, QStringList attributes, QString entryPat
|
||||
|
||||
// Iterate over the attributes and output them line-by-line.
|
||||
bool sawUnknownAttribute = false;
|
||||
for (QString attribute : attributes) {
|
||||
for (const QString& attribute : asConst(attributes)) {
|
||||
if (!entry->attributes()->contains(attribute)) {
|
||||
sawUnknownAttribute = true;
|
||||
qCritical("ERROR: unknown attribute '%s'.", qPrintable(attribute));
|
||||
err << QObject::tr("ERROR: unknown attribute %1.").arg(attribute) << endl;
|
||||
continue;
|
||||
}
|
||||
if (showAttributeNames) {
|
||||
outputTextStream << attribute << ": ";
|
||||
out << attribute << ": ";
|
||||
}
|
||||
outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl;
|
||||
out << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl;
|
||||
}
|
||||
return sawUnknownAttribute ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
Show();
|
||||
~Show();
|
||||
int execute(const QStringList& arguments);
|
||||
int showEntry(Database* database, QStringList attributes, QString entryPath);
|
||||
int showEntry(Database* database, QStringList attributes, const QString& entryPath);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_SHOW_H
|
||||
|
@ -25,9 +25,25 @@
|
||||
#endif
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTextStream>
|
||||
|
||||
void Utils::setStdinEcho(bool enable = true)
|
||||
namespace Utils
|
||||
{
|
||||
/**
|
||||
* STDOUT file handle for the CLI.
|
||||
*/
|
||||
FILE* STDOUT = stdout;
|
||||
|
||||
/**
|
||||
* STDERR file handle for the CLI.
|
||||
*/
|
||||
FILE* STDERR = stderr;
|
||||
|
||||
/**
|
||||
* STDIN file handle for the CLI.
|
||||
*/
|
||||
FILE* STDIN = stdin;
|
||||
|
||||
void setStdinEcho(bool enable = true)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
@ -56,28 +72,55 @@ void Utils::setStdinEcho(bool enable = true)
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Utils::getPassword()
|
||||
QStringList nextPasswords = {};
|
||||
|
||||
/**
|
||||
* Set the next password returned by \link getPassword() instead of reading it from STDIN.
|
||||
* Multiple calls to this method will fill a queue of passwords.
|
||||
* This function is intended for testing purposes.
|
||||
*
|
||||
* @param password password to return next
|
||||
*/
|
||||
void setNextPassword(const QString& password)
|
||||
{
|
||||
static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
static QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
nextPasswords.append(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a user password from STDIN or return a password previously
|
||||
* set by \link setNextPassword().
|
||||
*
|
||||
* @return the password
|
||||
*/
|
||||
QString getPassword()
|
||||
{
|
||||
QTextStream out(STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
// return preset password if one is set
|
||||
if (!nextPasswords.isEmpty()) {
|
||||
auto password = nextPasswords.takeFirst();
|
||||
// simulate user entering newline
|
||||
out << endl;
|
||||
return password;
|
||||
}
|
||||
|
||||
QTextStream in(STDIN, QIODevice::ReadOnly);
|
||||
|
||||
setStdinEcho(false);
|
||||
QString line = inputTextStream.readLine();
|
||||
QString line = in.readLine();
|
||||
setStdinEcho(true);
|
||||
|
||||
// The new line was also not echoed, but we do want to echo it.
|
||||
outputTextStream << "\n";
|
||||
outputTextStream.flush();
|
||||
out << endl;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* A valid and running event loop is needed to use the global QClipboard,
|
||||
* so we need to use this from the CLI.
|
||||
*/
|
||||
int Utils::clipText(const QString& text)
|
||||
int clipText(const QString& text)
|
||||
{
|
||||
QTextStream err(Utils::STDERR);
|
||||
|
||||
QString programName = "";
|
||||
QStringList arguments;
|
||||
@ -98,16 +141,18 @@ int Utils::clipText(const QString& text)
|
||||
#endif
|
||||
|
||||
if (programName.isEmpty()) {
|
||||
qCritical("No program defined for clipboard manipulation");
|
||||
err << QObject::tr("No program defined for clipboard manipulation");
|
||||
err.flush();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QProcess* clipProcess = new QProcess(nullptr);
|
||||
auto* clipProcess = new QProcess(nullptr);
|
||||
clipProcess->start(programName, arguments);
|
||||
clipProcess->waitForStarted();
|
||||
|
||||
if (clipProcess->state() != QProcess::Running) {
|
||||
qCritical("Unable to start program %s", qPrintable(programName));
|
||||
err << QObject::tr("Unable to start program %1").arg(programName);
|
||||
err.flush();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -120,3 +165,5 @@ int Utils::clipText(const QString& text)
|
||||
|
||||
return clipProcess->exitCode();
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
|
@ -19,13 +19,18 @@
|
||||
#define KEEPASSXC_UTILS_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QTextStream>
|
||||
|
||||
class Utils
|
||||
namespace Utils
|
||||
{
|
||||
public:
|
||||
static void setStdinEcho(bool enable);
|
||||
static QString getPassword();
|
||||
static int clipText(const QString& text);
|
||||
extern FILE* STDOUT;
|
||||
extern FILE* STDERR;
|
||||
extern FILE* STDIN;
|
||||
|
||||
void setStdinEcho(bool enable);
|
||||
QString getPassword();
|
||||
void setNextPassword(const QString& password);
|
||||
int clipText(const QString& text);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_UTILS_H
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include <cli/Command.h>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||
@ -34,17 +34,17 @@
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef QT_NO_DEBUG
|
||||
Tools::disableCoreDumps();
|
||||
#endif
|
||||
|
||||
if (!Crypto::init()) {
|
||||
qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString()));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
app.setApplicationVersion(KEEPASSX_VERSION);
|
||||
QCoreApplication::setApplicationVersion(KEEPASSX_VERSION);
|
||||
|
||||
#ifdef QT_NO_DEBUG
|
||||
Bootstrap::bootstrapApplication();
|
||||
#endif
|
||||
|
||||
QTextStream out(stdout);
|
||||
QStringList arguments;
|
||||
@ -69,7 +69,7 @@ int main(int argc, char** argv)
|
||||
// recognized by this parser.
|
||||
parser.parse(arguments);
|
||||
|
||||
if (parser.positionalArguments().size() < 1) {
|
||||
if (parser.positionalArguments().empty()) {
|
||||
if (parser.isSet("version")) {
|
||||
// Switch to parser.showVersion() when available (QT 5.4).
|
||||
out << KEEPASSX_VERSION << endl;
|
||||
|
236
src/core/Bootstrap.cpp
Normal file
236
src/core/Bootstrap.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Bootstrap.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Translator.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <aclapi.h> // for createWindowsDACL()
|
||||
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
|
||||
#endif
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
/**
|
||||
* When QNetworkAccessManager is instantiated it regularly starts polling
|
||||
* all network interfaces to see if anything changes and if so, what. This
|
||||
* creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >=
|
||||
* when on a wifi connection.
|
||||
* So here we disable it for lack of better measure.
|
||||
* This will also cause this message: QObject::startTimer: Timers cannot
|
||||
* have negative intervals
|
||||
* For more info see:
|
||||
* - https://bugreports.qt.io/browse/QTBUG-40332
|
||||
* - https://bugreports.qt.io/browse/QTBUG-46015
|
||||
*/
|
||||
static inline void applyEarlyQNetworkAccessManagerWorkaround()
|
||||
{
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform early application bootstrapping such as setting up search paths,
|
||||
* configuration OS security properties, and loading translators.
|
||||
* A QApplication object has to be instantiated before calling this function.
|
||||
*/
|
||||
void bootstrapApplication()
|
||||
{
|
||||
#ifdef QT_NO_DEBUG
|
||||
disableCoreDumps();
|
||||
#endif
|
||||
setupSearchPaths();
|
||||
applyEarlyQNetworkAccessManagerWorkaround();
|
||||
Translator::installTranslators();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Don't show menu icons on OSX
|
||||
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the main window's state after launch
|
||||
*
|
||||
* @param mainWindow the main window whose state to restore
|
||||
*/
|
||||
void restoreMainWindowState(MainWindow& mainWindow)
|
||||
{
|
||||
// start minimized if configured
|
||||
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
|
||||
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
|
||||
#ifndef Q_OS_LINUX
|
||||
if (minimizeOnStartup) {
|
||||
#else
|
||||
// On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at
|
||||
// the same time (which would happen if both minimize on startup and minimize to tray are set)
|
||||
// since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough.
|
||||
if (minimizeOnStartup && !minimizeToTray) {
|
||||
#endif
|
||||
mainWindow.setWindowState(Qt::WindowMinimized);
|
||||
}
|
||||
if (!(minimizeOnStartup && minimizeToTray)) {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
|
||||
const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
|
||||
for (const QString& filename : fileNames) {
|
||||
if (!filename.isEmpty() && QFile::exists(filename)) {
|
||||
mainWindow.openDatabase(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
void disableCoreDumps()
|
||||
{
|
||||
// default to true
|
||||
// there is no point in printing a warning if this is not implemented on the platform
|
||||
bool success = true;
|
||||
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#endif
|
||||
|
||||
// Mac OS X
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
success = success && createWindowsDACL();
|
||||
#endif
|
||||
|
||||
if (!success) {
|
||||
qWarning("Unable to disable core dumps.");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// This function grants the user associated with the process token minimal access rights and
|
||||
// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and
|
||||
// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory().
|
||||
// We do this using a discretionary access control list (DACL). Effectively this prevents
|
||||
// crash dumps and disallows other processes from accessing our memory. This works as long
|
||||
// as you do not have admin privileges, since then you are able to grant yourself the
|
||||
// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL.
|
||||
//
|
||||
bool createWindowsDACL()
|
||||
{
|
||||
bool bSuccess = false;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Process token and user
|
||||
HANDLE hToken = nullptr;
|
||||
PTOKEN_USER pTokenUser = nullptr;
|
||||
DWORD cbBufferSize = 0;
|
||||
|
||||
// Access control list
|
||||
PACL pACL = nullptr;
|
||||
DWORD cbACL = 0;
|
||||
|
||||
// Open the access token associated with the calling process
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Retrieve the token information in a TOKEN_USER structure
|
||||
GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize);
|
||||
|
||||
pTokenUser = static_cast<PTOKEN_USER>(HeapAlloc(GetProcessHeap(), 0, cbBufferSize));
|
||||
if (pTokenUser == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!IsValidSid(pTokenUser->User.Sid)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Calculate the amount of memory that must be allocated for the DACL
|
||||
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid);
|
||||
|
||||
// Create and initialize an ACL
|
||||
pACL = static_cast<PACL>(HeapAlloc(GetProcessHeap(), 0, cbACL));
|
||||
if (pACL == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Add allowed access control entries, everything else is denied
|
||||
if (!AddAccessAllowedAce(
|
||||
pACL,
|
||||
ACL_REVISION,
|
||||
SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process
|
||||
pTokenUser->User.Sid // pointer to the trustee's SID
|
||||
)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Set discretionary access control list
|
||||
bSuccess = ERROR_SUCCESS
|
||||
== SetSecurityInfo(GetCurrentProcess(), // object handle
|
||||
SE_KERNEL_OBJECT, // type of object
|
||||
DACL_SECURITY_INFORMATION, // change only the objects DACL
|
||||
nullptr,
|
||||
nullptr, // do not change owner or group
|
||||
pACL, // DACL specified
|
||||
nullptr // do not change SACL
|
||||
);
|
||||
|
||||
Cleanup:
|
||||
|
||||
if (pACL != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pACL);
|
||||
}
|
||||
if (pTokenUser != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pTokenUser);
|
||||
}
|
||||
if (hToken != nullptr) {
|
||||
CloseHandle(hToken);
|
||||
}
|
||||
#endif
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
void setupSearchPaths()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Make sure Windows doesn't load DLLs from the current working directory
|
||||
SetDllDirectoryA("");
|
||||
SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Bootstrap
|
34
src/core/Bootstrap.h
Normal file
34
src/core/Bootstrap.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef KEEPASSXC_BOOTSTRAP_H
|
||||
#define KEEPASSXC_BOOTSTRAP_H
|
||||
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
void bootstrapApplication();
|
||||
void restoreMainWindowState(MainWindow& mainWindow);
|
||||
void disableCoreDumps();
|
||||
bool createWindowsDACL();
|
||||
void setupSearchPaths();
|
||||
};
|
||||
|
||||
|
||||
#endif //KEEPASSXC_BOOTSTRAP_H
|
@ -47,7 +47,7 @@ Database::Database()
|
||||
, m_emitModified(false)
|
||||
, m_uuid(QUuid::createUuid())
|
||||
{
|
||||
m_data.cipher = KeePass2::CIPHER_AES;
|
||||
m_data.cipher = KeePass2::CIPHER_AES256;
|
||||
m_data.compressionAlgo = CompressionGZip;
|
||||
|
||||
// instantiate default AES-KDF with legacy KDBX3 flag set
|
||||
@ -501,14 +501,14 @@ Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer<con
|
||||
return db;
|
||||
}
|
||||
|
||||
Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename)
|
||||
Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename, FILE* outputDescriptor, FILE* errorDescriptor)
|
||||
{
|
||||
auto compositeKey = QSharedPointer<CompositeKey>::create();
|
||||
QTextStream outputTextStream(stdout);
|
||||
QTextStream errorTextStream(stderr);
|
||||
QTextStream out(outputDescriptor);
|
||||
QTextStream err(errorDescriptor);
|
||||
|
||||
outputTextStream << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
|
||||
outputTextStream.flush();
|
||||
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
|
||||
out.flush();
|
||||
|
||||
QString line = Utils::getPassword();
|
||||
auto passwordKey = QSharedPointer<PasswordKey>::create();
|
||||
@ -518,11 +518,19 @@ Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilenam
|
||||
if (!keyFilename.isEmpty()) {
|
||||
auto fileKey = QSharedPointer<FileKey>::create();
|
||||
QString errorMessage;
|
||||
// LCOV_EXCL_START
|
||||
if (!fileKey->load(keyFilename, &errorMessage)) {
|
||||
errorTextStream << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage);
|
||||
errorTextStream << endl;
|
||||
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fileKey->type() != FileKey::Hashed) {
|
||||
err << QObject::tr("WARNING: You are using a legacy key file format which may become\n"
|
||||
"unsupported in the future.\n\n"
|
||||
"Please consider generating a new key file.") << endl;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
compositeKey->addKey(fileKey);
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,8 @@ public:
|
||||
|
||||
static Database* databaseByUuid(const QUuid& uuid);
|
||||
static Database* openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key);
|
||||
static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = QString(""));
|
||||
static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = {},
|
||||
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
|
||||
|
||||
signals:
|
||||
void groupDataChanged(Group* group);
|
||||
|
@ -80,21 +80,21 @@ QString PasswordGenerator::generatePassword() const
|
||||
QString password;
|
||||
|
||||
if (m_flags & CharFromEveryGroup) {
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
int pos = randomGen()->randomUInt(groups[i].size());
|
||||
for (const auto& group : groups) {
|
||||
int pos = randomGen()->randomUInt(static_cast<quint32>(group.size()));
|
||||
|
||||
password.append(groups[i][pos]);
|
||||
password.append(group[pos]);
|
||||
}
|
||||
|
||||
for (int i = groups.size(); i < m_length; i++) {
|
||||
int pos = randomGen()->randomUInt(passwordChars.size());
|
||||
int pos = randomGen()->randomUInt(static_cast<quint32>(passwordChars.size()));
|
||||
|
||||
password.append(passwordChars[pos]);
|
||||
}
|
||||
|
||||
// shuffle chars
|
||||
for (int i = (password.size() - 1); i >= 1; i--) {
|
||||
int j = randomGen()->randomUInt(i + 1);
|
||||
int j = randomGen()->randomUInt(static_cast<quint32>(i + 1));
|
||||
|
||||
QChar tmp = password[i];
|
||||
password[i] = password[j];
|
||||
@ -102,7 +102,7 @@ QString PasswordGenerator::generatePassword() const
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < m_length; i++) {
|
||||
int pos = randomGen()->randomUInt(passwordChars.size());
|
||||
int pos = randomGen()->randomUInt(static_cast<quint32>(passwordChars.size()));
|
||||
|
||||
password.append(passwordChars[pos]);
|
||||
}
|
||||
@ -111,21 +111,6 @@ QString PasswordGenerator::generatePassword() const
|
||||
return password;
|
||||
}
|
||||
|
||||
int PasswordGenerator::getbits() const
|
||||
{
|
||||
const QVector<PasswordGroup> groups = passwordGroups();
|
||||
|
||||
int bits = 0;
|
||||
QVector<QChar> passwordChars;
|
||||
for (const PasswordGroup& group : groups) {
|
||||
bits += group.size();
|
||||
}
|
||||
|
||||
bits *= m_length;
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
bool PasswordGenerator::isValid() const
|
||||
{
|
||||
if (m_classes == 0) {
|
||||
@ -138,11 +123,8 @@ bool PasswordGenerator::isValid() const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (passwordGroups().size() == 0) {
|
||||
return false;
|
||||
}
|
||||
return !passwordGroups().isEmpty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<PasswordGroup> PasswordGenerator::passwordGroups() const
|
||||
@ -298,9 +280,9 @@ QVector<PasswordGroup> PasswordGenerator::passwordGroups() const
|
||||
j = group.indexOf(ch);
|
||||
}
|
||||
}
|
||||
if (group.size() > 0) {
|
||||
if (!group.isEmpty()) {
|
||||
passwordGroups.replace(i, group);
|
||||
i++;
|
||||
++i;
|
||||
} else {
|
||||
passwordGroups.remove(i);
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
QString generatePassword() const;
|
||||
int getbits() const;
|
||||
|
||||
static const int DefaultLength = 16;
|
||||
static const char* DefaultExcludedChars;
|
||||
|
@ -18,19 +18,20 @@
|
||||
*/
|
||||
|
||||
#include "Tools.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Translator.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QIODevice>
|
||||
#include <QImageReader>
|
||||
#include <QLocale>
|
||||
#include <QStringList>
|
||||
#include <cctype>
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include <cctype>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <aclapi.h> // for SetSecurityInfo()
|
||||
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
|
||||
#include <windows.h> // for Sleep()
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
@ -56,294 +57,161 @@
|
||||
|
||||
namespace Tools
|
||||
{
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision)
|
||||
{
|
||||
constexpr auto kibibyte = 1024;
|
||||
double size = bytes;
|
||||
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision)
|
||||
{
|
||||
constexpr auto kibibyte = 1024;
|
||||
double size = bytes;
|
||||
QStringList units = QStringList() << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB";
|
||||
int i = 0;
|
||||
int maxI = units.size() - 1;
|
||||
|
||||
QStringList units = QStringList() << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB";
|
||||
int i = 0;
|
||||
int maxI = units.size() - 1;
|
||||
|
||||
while ((size >= kibibyte) && (i < maxI)) {
|
||||
size /= kibibyte;
|
||||
i++;
|
||||
}
|
||||
|
||||
return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i));
|
||||
while ((size >= kibibyte) && (i < maxI)) {
|
||||
size /= kibibyte;
|
||||
i++;
|
||||
}
|
||||
|
||||
bool hasChild(const QObject* parent, const QObject* child)
|
||||
{
|
||||
if (!parent || !child) {
|
||||
return false;
|
||||
}
|
||||
return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i));
|
||||
}
|
||||
|
||||
const QObjectList children = parent->children();
|
||||
for (QObject* c : children) {
|
||||
if (child == c || hasChild(c, child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool hasChild(const QObject* parent, const QObject* child)
|
||||
{
|
||||
if (!parent || !child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size)
|
||||
{
|
||||
QByteArray buffer;
|
||||
buffer.resize(size);
|
||||
|
||||
qint64 readResult = device->read(buffer.data(), size);
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
buffer.resize(readResult);
|
||||
data = buffer;
|
||||
const QObjectList children = parent->children();
|
||||
for (QObject* c : children) {
|
||||
if (child == c || hasChild(c, child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data)
|
||||
{
|
||||
QByteArray result;
|
||||
qint64 readBytes = 0;
|
||||
qint64 readResult;
|
||||
do {
|
||||
result.resize(result.size() + 16384);
|
||||
readResult = device->read(result.data() + readBytes, result.size() - readBytes);
|
||||
if (readResult > 0) {
|
||||
readBytes += readResult;
|
||||
}
|
||||
} while (readResult > 0);
|
||||
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
result.resize(static_cast<int>(readBytes));
|
||||
data = result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QString imageReaderFilter()
|
||||
{
|
||||
const QList<QByteArray> formats = QImageReader::supportedImageFormats();
|
||||
QStringList formatsStringList;
|
||||
|
||||
for (const QByteArray& format : formats) {
|
||||
for (int i = 0; i < format.size(); i++) {
|
||||
if (!QChar(format.at(i)).isLetterOrNumber()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
formatsStringList.append("*." + QString::fromLatin1(format).toLower());
|
||||
}
|
||||
|
||||
return formatsStringList.join(" ");
|
||||
}
|
||||
|
||||
bool isHex(const QByteArray& ba)
|
||||
{
|
||||
for (const unsigned char c : ba) {
|
||||
if (!std::isxdigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size)
|
||||
{
|
||||
QByteArray buffer;
|
||||
buffer.resize(size);
|
||||
|
||||
qint64 readResult = device->read(buffer.data(), size);
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
buffer.resize(readResult);
|
||||
data = buffer;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool isBase64(const QByteArray& ba)
|
||||
{
|
||||
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
|
||||
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
|
||||
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
||||
|
||||
return regexp.exactMatch(base64);
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data)
|
||||
{
|
||||
QByteArray result;
|
||||
qint64 readBytes = 0;
|
||||
qint64 readResult;
|
||||
do {
|
||||
result.resize(result.size() + 16384);
|
||||
readResult = device->read(result.data() + readBytes, result.size() - readBytes);
|
||||
if (readResult > 0) {
|
||||
readBytes += readResult;
|
||||
}
|
||||
}
|
||||
while (readResult > 0);
|
||||
|
||||
void sleep(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
result.resize(static_cast<int>(readBytes));
|
||||
data = result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
QString imageReaderFilter()
|
||||
{
|
||||
const QList<QByteArray> formats = QImageReader::supportedImageFormats();
|
||||
QStringList formatsStringList;
|
||||
|
||||
for (const QByteArray& format : formats) {
|
||||
for (int i = 0; i < format.size(); i++) {
|
||||
if (!QChar(format.at(i)).isLetterOrNumber()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
formatsStringList.append("*." + QString::fromLatin1(format).toLower());
|
||||
}
|
||||
|
||||
return formatsStringList.join(" ");
|
||||
}
|
||||
|
||||
bool isHex(const QByteArray& ba)
|
||||
{
|
||||
for (const unsigned char c : ba) {
|
||||
if (!std::isxdigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isBase64(const QByteArray& ba)
|
||||
{
|
||||
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
|
||||
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
|
||||
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
||||
|
||||
return regexp.exactMatch(base64);
|
||||
}
|
||||
|
||||
void sleep(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Sleep(uint(ms));
|
||||
Sleep(uint(ms));
|
||||
#else
|
||||
timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000 * 1000;
|
||||
nanosleep(&ts, nullptr);
|
||||
timespec ts;
|
||||
ts.tv_sec = ms/1000;
|
||||
ts.tv_nsec = (ms%1000)*1000*1000;
|
||||
nanosleep(&ts, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void wait(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
void wait(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
if (ms <= 50) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
|
||||
sleep(qMax(ms - static_cast<int>(timer.elapsed()), 0));
|
||||
} else {
|
||||
int timeLeft;
|
||||
do {
|
||||
timeLeft = ms - timer.elapsed();
|
||||
if (timeLeft > 0) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft);
|
||||
sleep(10);
|
||||
}
|
||||
} while (!timer.hasExpired(ms));
|
||||
if (ms <= 50) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
|
||||
sleep(qMax(ms - static_cast<int>(timer.elapsed()), 0));
|
||||
} else {
|
||||
int timeLeft;
|
||||
do {
|
||||
timeLeft = ms - timer.elapsed();
|
||||
if (timeLeft > 0) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft);
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
while (!timer.hasExpired(ms));
|
||||
}
|
||||
|
||||
void disableCoreDumps()
|
||||
{
|
||||
// default to true
|
||||
// there is no point in printing a warning if this is not implemented on the platform
|
||||
bool success = true;
|
||||
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#endif
|
||||
|
||||
// Mac OS X
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
success = success && createWindowsDACL();
|
||||
#endif
|
||||
|
||||
if (!success) {
|
||||
qWarning("Unable to disable core dumps.");
|
||||
}
|
||||
}
|
||||
|
||||
void setupSearchPaths()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Make sure Windows doesn't load DLLs from the current working directory
|
||||
SetDllDirectoryA("");
|
||||
SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// This function grants the user associated with the process token minimal access rights and
|
||||
// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and
|
||||
// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory().
|
||||
// We do this using a discretionary access control list (DACL). Effectively this prevents
|
||||
// crash dumps and disallows other processes from accessing our memory. This works as long
|
||||
// as you do not have admin privileges, since then you are able to grant yourself the
|
||||
// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL.
|
||||
//
|
||||
bool createWindowsDACL()
|
||||
{
|
||||
bool bSuccess = false;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Process token and user
|
||||
HANDLE hToken = nullptr;
|
||||
PTOKEN_USER pTokenUser = nullptr;
|
||||
DWORD cbBufferSize = 0;
|
||||
|
||||
// Access control list
|
||||
PACL pACL = nullptr;
|
||||
DWORD cbACL = 0;
|
||||
|
||||
// Open the access token associated with the calling process
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Retrieve the token information in a TOKEN_USER structure
|
||||
GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize);
|
||||
|
||||
pTokenUser = static_cast<PTOKEN_USER>(HeapAlloc(GetProcessHeap(), 0, cbBufferSize));
|
||||
if (pTokenUser == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!IsValidSid(pTokenUser->User.Sid)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Calculate the amount of memory that must be allocated for the DACL
|
||||
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid);
|
||||
|
||||
// Create and initialize an ACL
|
||||
pACL = static_cast<PACL>(HeapAlloc(GetProcessHeap(), 0, cbACL));
|
||||
if (pACL == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Add allowed access control entries, everything else is denied
|
||||
if (!AddAccessAllowedAce(
|
||||
pACL,
|
||||
ACL_REVISION,
|
||||
SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process
|
||||
pTokenUser->User.Sid // pointer to the trustee's SID
|
||||
)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Set discretionary access control list
|
||||
bSuccess = ERROR_SUCCESS
|
||||
== SetSecurityInfo(GetCurrentProcess(), // object handle
|
||||
SE_KERNEL_OBJECT, // type of object
|
||||
DACL_SECURITY_INFORMATION, // change only the objects DACL
|
||||
nullptr,
|
||||
nullptr, // do not change owner or group
|
||||
pACL, // DACL specified
|
||||
nullptr // do not change SACL
|
||||
);
|
||||
|
||||
Cleanup:
|
||||
|
||||
if (pACL != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pACL);
|
||||
}
|
||||
if (pTokenUser != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pTokenUser);
|
||||
}
|
||||
if (hToken != nullptr) {
|
||||
CloseHandle(hToken);
|
||||
}
|
||||
#endif
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tools
|
||||
|
@ -30,31 +30,27 @@ class QIODevice;
|
||||
|
||||
namespace Tools
|
||||
{
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision = 2);
|
||||
bool hasChild(const QObject* parent, const QObject* child);
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384);
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data);
|
||||
QString imageReaderFilter();
|
||||
bool isHex(const QByteArray& ba);
|
||||
bool isBase64(const QByteArray& ba);
|
||||
void sleep(int ms);
|
||||
void wait(int ms);
|
||||
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision = 2);
|
||||
bool hasChild(const QObject* parent, const QObject* child);
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384);
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data);
|
||||
QString imageReaderFilter();
|
||||
bool isHex(const QByteArray& ba);
|
||||
bool isBase64(const QByteArray& ba);
|
||||
void sleep(int ms);
|
||||
void wait(int ms);
|
||||
void disableCoreDumps();
|
||||
void setupSearchPaths();
|
||||
bool createWindowsDACL();
|
||||
template <typename RandomAccessIterator, typename T>
|
||||
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
|
||||
{
|
||||
RandomAccessIterator it = std::lower_bound(begin, end, value);
|
||||
|
||||
template <typename RandomAccessIterator, typename T>
|
||||
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
|
||||
{
|
||||
RandomAccessIterator it = std::lower_bound(begin, end, value);
|
||||
|
||||
if ((it == end) || (value < *it)) {
|
||||
return end;
|
||||
} else {
|
||||
return it;
|
||||
}
|
||||
if ((it == end) || (value < *it)) {
|
||||
return end;
|
||||
} else {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tools
|
||||
|
||||
|
@ -98,13 +98,6 @@ void CryptoHash::setKey(const QByteArray& data)
|
||||
Q_ASSERT(error == 0);
|
||||
}
|
||||
|
||||
void CryptoHash::reset()
|
||||
{
|
||||
Q_D(CryptoHash);
|
||||
|
||||
gcry_md_reset(d->ctx);
|
||||
}
|
||||
|
||||
QByteArray CryptoHash::result() const
|
||||
{
|
||||
Q_D(const CryptoHash);
|
||||
|
@ -34,7 +34,6 @@ public:
|
||||
explicit CryptoHash(Algorithm algo, bool hmac = false);
|
||||
~CryptoHash();
|
||||
void addData(const QByteArray& data);
|
||||
void reset();
|
||||
QByteArray result() const;
|
||||
void setKey(const QByteArray& data);
|
||||
|
||||
|
@ -94,7 +94,7 @@ QString SymmetricCipher::errorString() const
|
||||
|
||||
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& cipher)
|
||||
{
|
||||
if (cipher == KeePass2::CIPHER_AES) {
|
||||
if (cipher == KeePass2::CIPHER_AES256) {
|
||||
return Aes256;
|
||||
} else if (cipher == KeePass2::CIPHER_CHACHA20) {
|
||||
return ChaCha20;
|
||||
@ -109,15 +109,17 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& ciphe
|
||||
QUuid SymmetricCipher::algorithmToCipher(Algorithm algo)
|
||||
{
|
||||
switch (algo) {
|
||||
case Aes128:
|
||||
return KeePass2::CIPHER_AES128;
|
||||
case Aes256:
|
||||
return KeePass2::CIPHER_AES;
|
||||
return KeePass2::CIPHER_AES256;
|
||||
case ChaCha20:
|
||||
return KeePass2::CIPHER_CHACHA20;
|
||||
case Twofish:
|
||||
return KeePass2::CIPHER_TWOFISH;
|
||||
default:
|
||||
qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo);
|
||||
return QUuid();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,8 +185,6 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
|
||||
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||
{
|
||||
// TODO: check block size
|
||||
|
||||
gcry_error_t error;
|
||||
|
||||
char* rawData = data.data();
|
||||
|
@ -110,14 +110,6 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
if (saveXml()) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.setBuffer(&m_xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlDevice = &buffer;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1);
|
||||
|
@ -124,14 +124,6 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
if (saveXml()) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.setBuffer(&m_xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlDevice = &buffer;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include <utility>
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
@ -18,6 +20,9 @@
|
||||
#include "KdbxReader.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Endian.h"
|
||||
#include "format/KdbxXmlWriter.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#define UUID_LENGTH 16
|
||||
|
||||
@ -92,7 +97,14 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
|
||||
}
|
||||
|
||||
// read payload
|
||||
return readDatabaseImpl(device, headerStream.storedData(), key, keepDatabase);
|
||||
auto* db = readDatabaseImpl(device, headerStream.storedData(), std::move(key), keepDatabase);
|
||||
|
||||
if (saveXml()) {
|
||||
m_xmlData.clear();
|
||||
decryptXmlInnerStream(m_xmlData, db);
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
bool KdbxReader::hasError() const
|
||||
@ -258,6 +270,23 @@ void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
|
||||
m_irsAlgo = irsAlgo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt protected inner stream fields in XML dump on demand.
|
||||
* Without the stream key from the KDBX header, the values become worthless.
|
||||
*
|
||||
* @param xmlOutput XML dump with decrypted fields
|
||||
* @param db the database object for which to generate the decrypted XML dump
|
||||
*/
|
||||
void KdbxReader::decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const
|
||||
{
|
||||
QBuffer buffer;
|
||||
buffer.setBuffer(&xmlOutput);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
KdbxXmlWriter writer(m_kdbxVersion);
|
||||
writer.disableInnerStreamProtection(true);
|
||||
writer.writeDatabase(&buffer, db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise an error. Use in case of an unexpected read error.
|
||||
*
|
||||
|
@ -86,6 +86,8 @@ protected:
|
||||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
|
||||
|
||||
QScopedPointer<Database> m_db;
|
||||
|
||||
QPair<quint32, quint32> m_kdbxSignature;
|
||||
|
@ -82,7 +82,6 @@ Database* KdbxXmlReader::readDatabase(QIODevice* device)
|
||||
* @param db database to read into
|
||||
* @param randomStream random stream to use for decryption
|
||||
*/
|
||||
#include "QDebug"
|
||||
void KdbxXmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
|
||||
{
|
||||
m_error = false;
|
||||
|
@ -358,10 +358,10 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
|
||||
|
||||
bool protect =
|
||||
(((key == "Title") && m_meta->protectTitle()) || ((key == "UserName") && m_meta->protectUsername())
|
||||
|| ((key == "Password") && m_meta->protectPassword())
|
||||
|| ((key == "URL") && m_meta->protectUrl())
|
||||
|| ((key == "Notes") && m_meta->protectNotes())
|
||||
|| entry->attributes()->isProtected(key));
|
||||
|| ((key == "Password") && m_meta->protectPassword())
|
||||
|| ((key == "URL") && m_meta->protectUrl())
|
||||
|| ((key == "Notes") && m_meta->protectNotes())
|
||||
|| entry->attributes()->isProtected(key));
|
||||
|
||||
writeString("Key", key);
|
||||
|
||||
@ -369,7 +369,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
|
||||
QString value;
|
||||
|
||||
if (protect) {
|
||||
if (m_randomStream) {
|
||||
if (!m_innerStreamProtectionDisabled && m_randomStream) {
|
||||
m_xml.writeAttribute("Protected", "True");
|
||||
bool ok;
|
||||
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok);
|
||||
@ -596,3 +596,24 @@ void KdbxXmlWriter::raiseError(const QString& errorMessage)
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable inner stream protection and write protected fields
|
||||
* in plaintext instead. This is useful for plaintext XML exports
|
||||
* where the inner stream key is not available.
|
||||
*
|
||||
* @param disable true to disable protection
|
||||
*/
|
||||
void KdbxXmlWriter::disableInnerStreamProtection(bool disable)
|
||||
{
|
||||
m_innerStreamProtectionDisabled = disable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if inner stream protection is disabled and protected
|
||||
* fields will be saved in plaintext
|
||||
*/
|
||||
bool KdbxXmlWriter::innerStreamProtectionDisabled() const
|
||||
{
|
||||
return m_innerStreamProtectionDisabled;
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ public:
|
||||
KeePass2RandomStream* randomStream = nullptr,
|
||||
const QByteArray& headerHash = QByteArray());
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
void disableInnerStreamProtection(bool disable);
|
||||
bool innerStreamProtectionDisabled() const;
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
@ -81,6 +83,8 @@ private:
|
||||
|
||||
const quint32 m_kdbxVersion;
|
||||
|
||||
bool m_innerStreamProtectionDisabled = false;
|
||||
|
||||
QXmlStreamWriter m_xml;
|
||||
QPointer<Database> m_db;
|
||||
QPointer<Metadata> m_meta;
|
||||
|
@ -23,7 +23,8 @@
|
||||
|
||||
#define UUID_LENGTH 16
|
||||
|
||||
const QUuid KeePass2::CIPHER_AES = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff");
|
||||
const QUuid KeePass2::CIPHER_AES128 = QUuid("61ab05a1-9464-41c3-8d74-3a563df8dd35");
|
||||
const QUuid KeePass2::CIPHER_AES256 = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff");
|
||||
const QUuid KeePass2::CIPHER_TWOFISH = QUuid("ad68f29f-576f-4bb9-a36a-d47af965346c");
|
||||
const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb59a");
|
||||
|
||||
@ -47,7 +48,7 @@ const QString KeePass2::KDFPARAM_ARGON2_SECRET("K");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A");
|
||||
|
||||
const QList<QPair<QUuid, QString>> KeePass2::CIPHERS{
|
||||
qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_AES256, QObject::tr("AES: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit"))
|
||||
};
|
||||
|
@ -46,7 +46,8 @@ namespace KeePass2
|
||||
|
||||
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
|
||||
|
||||
extern const QUuid CIPHER_AES;
|
||||
extern const QUuid CIPHER_AES128;
|
||||
extern const QUuid CIPHER_AES256;
|
||||
extern const QUuid CIPHER_TWOFISH;
|
||||
extern const QUuid CIPHER_CHACHA20;
|
||||
|
||||
|
@ -93,7 +93,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
|
||||
}
|
||||
|
||||
m_reader->setSaveXml(m_saveXml);
|
||||
return m_reader->readDatabase(device, key, keepDatabase);
|
||||
return m_reader->readDatabase(device, std::move(key), keepDatabase);
|
||||
}
|
||||
|
||||
bool KeePass2Reader::hasError() const
|
||||
|
@ -22,8 +22,8 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
class QLockFile;
|
||||
|
||||
class QLockFile;
|
||||
class QSocketNotifier;
|
||||
|
||||
class Application : public QApplication
|
||||
|
@ -216,7 +216,7 @@ private:
|
||||
void setIconFromParent();
|
||||
void replaceDatabase(Database* db);
|
||||
|
||||
Database* m_db;
|
||||
QPointer<Database> m_db;
|
||||
QWidget* m_mainWidget;
|
||||
EditEntryWidget* m_editEntryWidget;
|
||||
EditEntryWidget* m_historyEditEntryWidget;
|
||||
|
@ -81,7 +81,7 @@ void DatabaseSettingsWidgetEncryption::initialize()
|
||||
}
|
||||
if (!m_db->key()) {
|
||||
m_db->setKey(QSharedPointer<CompositeKey>::create());
|
||||
m_db->setCipher(KeePass2::CIPHER_AES);
|
||||
m_db->setCipher(KeePass2::CIPHER_AES256);
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
|
87
src/main.cpp
87
src/main.cpp
@ -16,19 +16,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/Translator.h"
|
||||
#include "core/Config.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "gui/Application.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#include "cli/Utils.h"
|
||||
|
||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||
@ -45,55 +44,29 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline void earlyQNetworkAccessManagerWorkaround()
|
||||
{
|
||||
// When QNetworkAccessManager is instantiated it regularly starts polling
|
||||
// all network interfaces to see if anything changes and if so, what. This
|
||||
// creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >=
|
||||
// when on a wifi connection.
|
||||
// So here we disable it for lack of better measure.
|
||||
// This will also cause this message: QObject::startTimer: Timers cannot
|
||||
// have negative intervals
|
||||
// For more info see:
|
||||
// - https://bugreports.qt.io/browse/QTBUG-40332
|
||||
// - https://bugreports.qt.io/browse/QTBUG-46015
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef QT_NO_DEBUG
|
||||
Tools::disableCoreDumps();
|
||||
#endif
|
||||
Tools::setupSearchPaths();
|
||||
|
||||
earlyQNetworkAccessManagerWorkaround();
|
||||
|
||||
Application app(argc, argv);
|
||||
Application::setApplicationName("keepassxc");
|
||||
Application::setApplicationVersion(KEEPASSX_VERSION);
|
||||
// don't set organizationName as that changes the return value of
|
||||
// QStandardPaths::writableLocation(QDesktopServices::DataLocation)
|
||||
Bootstrap::bootstrapApplication();
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(
|
||||
QCoreApplication::translate("main", "KeePassXC - cross-platform password manager"));
|
||||
parser.addPositionalArgument(
|
||||
"filename",
|
||||
QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"),
|
||||
"[filename(s)]");
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC - cross-platform password manager"));
|
||||
parser.addPositionalArgument("filename",
|
||||
QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"), "[filename(s)]");
|
||||
|
||||
QCommandLineOption configOption(
|
||||
"config", QCoreApplication::translate("main", "path to a custom config file"), "config");
|
||||
QCommandLineOption keyfileOption(
|
||||
"keyfile", QCoreApplication::translate("main", "key file of the database"), "keyfile");
|
||||
QCommandLineOption pwstdinOption("pw-stdin",
|
||||
QCoreApplication::translate("main", "read password of the database from stdin"));
|
||||
QCoreApplication::translate("main", "read password of the database from stdin"));
|
||||
// This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method
|
||||
QCommandLineOption parentWindowOption(QStringList() << "pw"
|
||||
<< "parent-window",
|
||||
QCoreApplication::translate("main", "Parent window handle"),
|
||||
"handle");
|
||||
QCommandLineOption parentWindowOption(
|
||||
QStringList() << "pw" << "parent-window", QCoreApplication::translate("main", "Parent window handle"), "handle");
|
||||
|
||||
QCommandLineOption helpOption = parser.addHelpOption();
|
||||
QCommandLineOption versionOption = parser.addVersionOption();
|
||||
@ -115,9 +88,7 @@ int main(int argc, char** argv)
|
||||
if (!fileNames.isEmpty()) {
|
||||
app.sendFileNamesToRunningInstance(fileNames);
|
||||
}
|
||||
qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.")
|
||||
.toUtf8()
|
||||
.constData();
|
||||
qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -135,46 +106,14 @@ int main(int argc, char** argv)
|
||||
Config::createConfigFromFile(parser.value(configOption));
|
||||
}
|
||||
|
||||
Translator::installTranslators();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Don't show menu icons on OSX
|
||||
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#endif
|
||||
|
||||
MainWindow mainWindow;
|
||||
app.setMainWindow(&mainWindow);
|
||||
|
||||
QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront()));
|
||||
QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));
|
||||
QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString)));
|
||||
QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection);
|
||||
|
||||
// start minimized if configured
|
||||
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
|
||||
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
|
||||
#ifndef Q_OS_LINUX
|
||||
if (minimizeOnStartup) {
|
||||
#else
|
||||
// On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at
|
||||
// the same time (which would happen if both minimize on startup and minimize to tray are set)
|
||||
// since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough.
|
||||
if (minimizeOnStartup && !minimizeToTray) {
|
||||
#endif
|
||||
mainWindow.setWindowState(Qt::WindowMinimized);
|
||||
}
|
||||
if (!(minimizeOnStartup && minimizeToTray)) {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
|
||||
const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
|
||||
for (const QString& filename : fileNames) {
|
||||
if (!filename.isEmpty() && QFile::exists(filename)) {
|
||||
mainWindow.openDatabase(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
Bootstrap::restoreMainWindowState(mainWindow);
|
||||
|
||||
const bool pwstdin = parser.isSet(pwstdinOption);
|
||||
for (const QString& filename : fileNames) {
|
||||
@ -193,7 +132,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = app.exec();
|
||||
int exitCode = Application::exec();
|
||||
|
||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||
// do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries
|
||||
|
@ -163,7 +163,10 @@ add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testmerge SOURCES TestMerge.cpp
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testtotp SOURCES TestTotp.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
@ -180,7 +183,7 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp
|
||||
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp
|
||||
add_unit_test(NAME testcsveporter SOURCES TestCsvExporter.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testykchallengeresponsekey
|
||||
@ -193,6 +196,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
|
||||
add_unit_test(NAME testtools SOURCES TestTools.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
|
||||
if(WITH_GUI_TESTS)
|
||||
# CLI clip tests need X environment on Linux
|
||||
add_unit_test(NAME testcli SOURCES TestCli.cpp
|
||||
LIBS testsupport cli ${TEST_LIBRARIES})
|
||||
|
||||
add_subdirectory(gui)
|
||||
endif(WITH_GUI_TESTS)
|
||||
|
756
tests/TestCli.cpp
Normal file
756
tests/TestCli.cpp
Normal file
@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TestCli.h"
|
||||
#include "config-keepassx-tests.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/Kdbx3Reader.h"
|
||||
#include "format/Kdbx4Reader.h"
|
||||
#include "format/Kdbx4Writer.h"
|
||||
#include "format/Kdbx3Writer.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
|
||||
#include "cli/Command.h"
|
||||
#include "cli/Utils.h"
|
||||
#include "cli/Add.h"
|
||||
#include "cli/Clip.h"
|
||||
#include "cli/Diceware.h"
|
||||
#include "cli/Edit.h"
|
||||
#include "cli/Estimate.h"
|
||||
#include "cli/Extract.h"
|
||||
#include "cli/Generate.h"
|
||||
#include "cli/List.h"
|
||||
#include "cli/Locate.h"
|
||||
#include "cli/Merge.h"
|
||||
#include "cli/Remove.h"
|
||||
#include "cli/Show.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QClipboard>
|
||||
#include <QFuture>
|
||||
#include <QtConcurrent>
|
||||
#include <QSet>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
QTEST_MAIN(TestCli)
|
||||
|
||||
void TestCli::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
|
||||
Config::createTempFileInstance();
|
||||
Bootstrap::bootstrapApplication();
|
||||
|
||||
// Load the NewDatabase.kdbx file into temporary storage
|
||||
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
|
||||
QVERIFY(sourceDbFile.open(QIODevice::ReadOnly));
|
||||
QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
|
||||
sourceDbFile.close();
|
||||
}
|
||||
|
||||
void TestCli::init()
|
||||
{
|
||||
m_dbFile.reset(new QTemporaryFile());
|
||||
m_dbFile->open();
|
||||
m_dbFile->write(m_dbData);
|
||||
m_dbFile->flush();
|
||||
|
||||
m_stdinFile.reset(new QTemporaryFile());
|
||||
m_stdinFile->open();
|
||||
m_stdinHandle = fdopen(m_stdinFile->handle(), "r+");
|
||||
Utils::STDIN = m_stdinHandle;
|
||||
|
||||
m_stdoutFile.reset(new QTemporaryFile());
|
||||
m_stdoutFile->open();
|
||||
m_stdoutHandle = fdopen(m_stdoutFile->handle(), "r+");
|
||||
Utils::STDOUT = m_stdoutHandle;
|
||||
|
||||
m_stderrFile.reset(new QTemporaryFile());
|
||||
m_stderrFile->open();
|
||||
m_stderrHandle = fdopen(m_stderrFile->handle(), "r+");
|
||||
Utils::STDERR = m_stderrHandle;
|
||||
}
|
||||
|
||||
void TestCli::cleanup()
|
||||
{
|
||||
m_dbFile.reset();
|
||||
|
||||
m_stdinFile.reset();
|
||||
m_stdinHandle = stdin;
|
||||
Utils::STDIN = stdin;
|
||||
|
||||
m_stdoutFile.reset();
|
||||
Utils::STDOUT = stdout;
|
||||
m_stdoutHandle = stdout;
|
||||
|
||||
m_stderrFile.reset();
|
||||
m_stderrHandle = stderr;
|
||||
Utils::STDERR = stderr;
|
||||
}
|
||||
|
||||
void TestCli::cleanupTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
QSharedPointer<Database> TestCli::readTestDatabase() const
|
||||
{
|
||||
Utils::setNextPassword("a");
|
||||
auto db = QSharedPointer<Database>(Database::unlockFromStdin(m_dbFile->fileName(), "", m_stdoutHandle));
|
||||
m_stdoutFile->seek(ftell(m_stdoutHandle)); // re-synchronize handles
|
||||
return db;
|
||||
}
|
||||
|
||||
void TestCli::testCommand()
|
||||
{
|
||||
QCOMPARE(Command::getCommands().size(), 12);
|
||||
QVERIFY(Command::getCommand("add"));
|
||||
QVERIFY(Command::getCommand("clip"));
|
||||
QVERIFY(Command::getCommand("diceware"));
|
||||
QVERIFY(Command::getCommand("edit"));
|
||||
QVERIFY(Command::getCommand("estimate"));
|
||||
QVERIFY(Command::getCommand("extract"));
|
||||
QVERIFY(Command::getCommand("generate"));
|
||||
QVERIFY(Command::getCommand("locate"));
|
||||
QVERIFY(Command::getCommand("ls"));
|
||||
QVERIFY(Command::getCommand("merge"));
|
||||
QVERIFY(Command::getCommand("rm"));
|
||||
QVERIFY(Command::getCommand("show"));
|
||||
QVERIFY(!Command::getCommand("doesnotexist"));
|
||||
}
|
||||
|
||||
void TestCli::testAdd()
|
||||
{
|
||||
Add addCmd;
|
||||
QVERIFY(!addCmd.name.isEmpty());
|
||||
QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
addCmd.execute({"add", "-u", "newuser", "--url", "https://example.com/", "-g", "-l", "20", m_dbFile->fileName(), "/newuser-entry"});
|
||||
|
||||
auto db = readTestDatabase();
|
||||
auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://example.com/"));
|
||||
QCOMPARE(entry->password().size(), 20);
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
Utils::setNextPassword("newpassword");
|
||||
addCmd.execute({"add", "-u", "newuser2", "--url", "https://example.net/", "-g", "-l", "20", "-p", m_dbFile->fileName(), "/newuser-entry2"});
|
||||
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/newuser-entry2");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser2"));
|
||||
QCOMPARE(entry->url(), QString("https://example.net/"));
|
||||
QCOMPARE(entry->password(), QString("newpassword"));
|
||||
}
|
||||
|
||||
void TestCli::testClip()
|
||||
{
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
clipboard->clear();
|
||||
|
||||
Clip clipCmd;
|
||||
QVERIFY(!clipCmd.name.isEmpty());
|
||||
QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"});
|
||||
|
||||
m_stderrFile->reset();
|
||||
QString errorOutput(m_stderrFile->readAll());
|
||||
|
||||
if (errorOutput.contains("Unable to start program")
|
||||
|| errorOutput.contains("No program defined for clipboard manipulation")) {
|
||||
QSKIP("Clip test skipped due to missing clipboard tool");
|
||||
}
|
||||
|
||||
QCOMPARE(clipboard->text(), QString("Password"));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
QFuture<void> future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"});
|
||||
|
||||
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500);
|
||||
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500);
|
||||
|
||||
future.waitForFinished();
|
||||
}
|
||||
|
||||
void TestCli::testDiceware()
|
||||
{
|
||||
Diceware dicewareCmd;
|
||||
QVERIFY(!dicewareCmd.name.isEmpty());
|
||||
QVERIFY(dicewareCmd.getDescriptionLine().contains(dicewareCmd.name));
|
||||
|
||||
dicewareCmd.execute({"diceware"});
|
||||
m_stdoutFile->reset();
|
||||
QString passphrase(m_stdoutFile->readLine());
|
||||
QVERIFY(!passphrase.isEmpty());
|
||||
|
||||
dicewareCmd.execute({"diceware", "-W", "2"});
|
||||
m_stdoutFile->seek(passphrase.toLatin1().size());
|
||||
passphrase = m_stdoutFile->readLine();
|
||||
QCOMPARE(passphrase.split(" ").size(), 2);
|
||||
|
||||
auto pos = m_stdoutFile->pos();
|
||||
dicewareCmd.execute({"diceware", "-W", "10"});
|
||||
m_stdoutFile->seek(pos);
|
||||
passphrase = m_stdoutFile->readLine();
|
||||
QCOMPARE(passphrase.split(" ").size(), 10);
|
||||
|
||||
QTemporaryFile wordFile;
|
||||
wordFile.open();
|
||||
for (int i = 0; i < 4500; ++i) {
|
||||
wordFile.write(QString("word" + QString::number(i) + "\n").toLatin1());
|
||||
}
|
||||
wordFile.close();
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
dicewareCmd.execute({"diceware", "-W", "11", "-w", wordFile.fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
passphrase = m_stdoutFile->readLine();
|
||||
const auto words = passphrase.split(" ");
|
||||
QCOMPARE(words.size(), 11);
|
||||
QRegularExpression regex("^word\\d+$");
|
||||
for (const auto& word: words) {
|
||||
QVERIFY2(regex.match(word).hasMatch(), qPrintable("Word " + word + " was not on the word list"));
|
||||
}
|
||||
}
|
||||
|
||||
void TestCli::testEdit()
|
||||
{
|
||||
Edit editCmd;
|
||||
QVERIFY(!editCmd.name.isEmpty());
|
||||
QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"});
|
||||
|
||||
auto db = readTestDatabase();
|
||||
auto* entry = db->rootGroup()->findEntryByPath("/newtitle");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://otherurl.example.com/"));
|
||||
QCOMPARE(entry->password(), QString("Password"));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newtitle"});
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/newtitle");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://otherurl.example.com/"));
|
||||
QVERIFY(!entry->password().isEmpty());
|
||||
QVERIFY(entry->password() != QString("Password"));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
editCmd.execute({"edit", "-g", "-l", "34", "-t", "yet another title", m_dbFile->fileName(), "/newtitle"});
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/yet another title");
|
||||
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);
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
Utils::setNextPassword("newpassword");
|
||||
editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/yet another title"});
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/yet another title");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->password(), QString("newpassword"));
|
||||
}
|
||||
|
||||
void TestCli::testEstimate_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<QString>("length");
|
||||
QTest::addColumn<QString>("entropy");
|
||||
QTest::addColumn<QString>("log10");
|
||||
QTest::addColumn<QStringList>("searchStrings");
|
||||
|
||||
QTest::newRow("Dictionary")
|
||||
<< "password" << "8" << "1.0" << "0.3"
|
||||
<< QStringList{"Type: Dictionary", "\tpassword"};
|
||||
|
||||
QTest::newRow("Spatial")
|
||||
<< "zxcv" << "4" << "10.3" << "3.1"
|
||||
<< QStringList{"Type: Spatial", "\tzxcv"};
|
||||
|
||||
QTest::newRow("Spatial(Rep)")
|
||||
<< "sdfgsdfg" << "8" << "11.3" << "3.4"
|
||||
<< QStringList{"Type: Spatial(Rep)", "\tsdfgsdfg"};
|
||||
|
||||
QTest::newRow("Dictionary / Sequence")
|
||||
<< "password123" << "11" << "4.5" << "1.3"
|
||||
<< QStringList{"Type: Dictionary", "Type: Sequence", "\tpassword", "\t123"};
|
||||
|
||||
QTest::newRow("Dict+Leet")
|
||||
<< "p455w0rd" << "8" << "2.5" << "0.7"
|
||||
<< QStringList{"Type: Dict+Leet", "\tp455w0rd"};
|
||||
|
||||
QTest::newRow("Dictionary(Rep)")
|
||||
<< "hellohello" << "10" << "7.3" << "2.2"
|
||||
<< QStringList{"Type: Dictionary(Rep)", "\thellohello"};
|
||||
|
||||
QTest::newRow("Sequence(Rep) / Dictionary")
|
||||
<< "456456foobar" << "12" << "16.7" << "5.0"
|
||||
<< QStringList{"Type: Sequence(Rep)", "Type: Dictionary", "\t456456", "\tfoobar"};
|
||||
|
||||
QTest::newRow("Bruteforce(Rep) / Bruteforce")
|
||||
<< "xzxzy" << "5" << "16.1" << "4.8"
|
||||
<< QStringList{"Type: Bruteforce(Rep)", "Type: Bruteforce", "\txzxz", "\ty"};
|
||||
|
||||
QTest::newRow("Dictionary / Date(Rep)")
|
||||
<< "pass20182018" << "12" << "15.1" << "4.56"
|
||||
<< QStringList{"Type: Dictionary", "Type: Date(Rep)", "\tpass", "\t20182018"};
|
||||
|
||||
QTest::newRow("Dictionary / Date / Bruteforce")
|
||||
<< "mypass2018-2" << "12" << "32.9" << "9.9"
|
||||
<< QStringList{"Type: Dictionary", "Type: Date", "Type: Bruteforce", "\tmypass", "\t2018", "\t-2"};
|
||||
|
||||
QTest::newRow("Strong Password")
|
||||
<< "E*!%.Qw{t.X,&bafw)\"Q!ah$%;U/" << "28" << "165.7" << "49.8"
|
||||
<< QStringList{"Type: Bruteforce", "\tE*"};
|
||||
|
||||
// TODO: detect passphrases and adjust entropy calculation accordingly (issue #2347)
|
||||
QTest::newRow("Strong Passphrase")
|
||||
<< "squint wooing resupply dangle isolation axis headsman" << "53" << "151.2" << "45.5"
|
||||
<< QStringList{"Type: Dictionary", "Type: Bruteforce", "Multi-word extra bits 22.0", "\tsquint", "\t ", "\twooing"};
|
||||
}
|
||||
|
||||
void TestCli::testEstimate()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(QString, length);
|
||||
QFETCH(QString, entropy);
|
||||
QFETCH(QString, log10);
|
||||
QFETCH(QStringList, searchStrings);
|
||||
|
||||
Estimate estimateCmd;
|
||||
QVERIFY(!estimateCmd.name.isEmpty());
|
||||
QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name));
|
||||
|
||||
QTextStream in(m_stdinFile.data());
|
||||
QTextStream out(m_stdoutFile.data());
|
||||
|
||||
in << input << endl;
|
||||
auto inEnd = in.pos();
|
||||
in.seek(0);
|
||||
estimateCmd.execute({"estimate"});
|
||||
auto outEnd = out.pos();
|
||||
out.seek(0);
|
||||
auto result = out.readAll();
|
||||
QVERIFY(result.startsWith("Length " + length));
|
||||
QVERIFY(result.contains("Entropy " + entropy));
|
||||
QVERIFY(result.contains("Log10 " + log10));
|
||||
|
||||
// seek to end of stream
|
||||
in.seek(inEnd);
|
||||
out.seek(outEnd);
|
||||
|
||||
in << input << endl;
|
||||
in.seek(inEnd);
|
||||
estimateCmd.execute({"estimate", "-a"});
|
||||
out.seek(outEnd);
|
||||
result = out.readAll();
|
||||
QVERIFY(result.startsWith("Length " + length));
|
||||
QVERIFY(result.contains("Entropy " + entropy));
|
||||
QVERIFY(result.contains("Log10 " + log10));
|
||||
for (const auto& string: asConst(searchStrings)) {
|
||||
QVERIFY2(result.contains(string), qPrintable("String " + string + " missing"));
|
||||
}
|
||||
}
|
||||
|
||||
void TestCli::testExtract()
|
||||
{
|
||||
Extract extractCmd;
|
||||
QVERIFY(!extractCmd.name.isEmpty());
|
||||
QVERIFY(extractCmd.getDescriptionLine().contains(extractCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
extractCmd.execute({"extract", m_dbFile->fileName()});
|
||||
|
||||
m_stdoutFile->seek(0);
|
||||
m_stdoutFile->readLine(); // skip prompt line
|
||||
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1);
|
||||
QScopedPointer<Database> db(new Database());
|
||||
reader.readDatabase(m_stdoutFile.data(), db.data());
|
||||
QVERIFY(!reader.hasError());
|
||||
QVERIFY(db.data());
|
||||
auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->password(), QString("Password"));
|
||||
}
|
||||
|
||||
void TestCli::testGenerate_data()
|
||||
{
|
||||
QTest::addColumn<QStringList>("parameters");
|
||||
QTest::addColumn<QString>("pattern");
|
||||
|
||||
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("numbers")<< QStringList{"generate", "-L", "16", "-n"} << "^[0-9]{16}$";
|
||||
QTest::newRow("special")
|
||||
<< QStringList{"generate", "-L", "200", "-s"}
|
||||
<< R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!+-<=>?#$%&^`@~]{200}$)";
|
||||
QTest::newRow("special (exclude)")
|
||||
<< QStringList{"generate", "-L", "200", "-s" , "-x", "+.?@&"}
|
||||
<< R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!-<=>#$%^`~]{200}$)";
|
||||
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}$";
|
||||
QTest::newRow("numbers + lowercase + uppercase (exclude)")
|
||||
<< 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}$";
|
||||
QTest::newRow("uppercase + lowercase (every)")
|
||||
<< QStringList{"generate", "-L", "2", "-u", "-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]$";
|
||||
}
|
||||
|
||||
void TestCli::testGenerate()
|
||||
{
|
||||
QFETCH(QStringList, parameters);
|
||||
QFETCH(QString, pattern);
|
||||
|
||||
Generate generateCmd;
|
||||
QVERIFY(!generateCmd.name.isEmpty());
|
||||
QVERIFY(generateCmd.getDescriptionLine().contains(generateCmd.name));
|
||||
|
||||
qint64 pos = 0;
|
||||
// run multiple times to make accidental passes unlikely
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
generateCmd.execute(parameters);
|
||||
m_stdoutFile->seek(pos);
|
||||
QRegularExpression regex(pattern);
|
||||
QString password = QString::fromUtf8(m_stdoutFile->readLine());
|
||||
pos = m_stdoutFile->pos();
|
||||
QVERIFY2(regex.match(password).hasMatch(), qPrintable("Password " + password + " does not match pattern " + pattern));
|
||||
}
|
||||
}
|
||||
|
||||
void TestCli::testList()
|
||||
{
|
||||
List listCmd;
|
||||
QVERIFY(!listCmd.name.isEmpty());
|
||||
QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", m_dbFile->fileName()});
|
||||
m_stdoutFile->reset();
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"
|
||||
"General/\n"
|
||||
"Windows/\n"
|
||||
"Network/\n"
|
||||
"Internet/\n"
|
||||
"eMail/\n"
|
||||
"Homebanking/\n"));
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", "-R", m_dbFile->fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"
|
||||
"General/\n"
|
||||
" [empty]\n"
|
||||
"Windows/\n"
|
||||
" [empty]\n"
|
||||
"Network/\n"
|
||||
" [empty]\n"
|
||||
"Internet/\n"
|
||||
" [empty]\n"
|
||||
"eMail/\n"
|
||||
" [empty]\n"
|
||||
"Homebanking/\n"
|
||||
" [empty]\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", m_dbFile->fileName(), "/General/"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("[empty]\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", m_dbFile->fileName(), "/DoesNotExist/"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("Cannot find group /DoesNotExist/.\n"));
|
||||
}
|
||||
|
||||
void TestCli::testLocate()
|
||||
{
|
||||
Locate locateCmd;
|
||||
QVERIFY(!locateCmd.name.isEmpty());
|
||||
QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", m_dbFile->fileName(), "Sample"});
|
||||
m_stdoutFile->reset();
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n"));
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", m_dbFile->fileName(), "Does Not Exist"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("No results for that search term.\n"));
|
||||
|
||||
// write a modified database
|
||||
auto db = readTestDatabase();
|
||||
QVERIFY(db);
|
||||
auto* group = db->rootGroup()->findGroupByPath("/General/");
|
||||
QVERIFY(group);
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle("New Entry");
|
||||
group->addEntry(entry);
|
||||
QTemporaryFile tmpFile;
|
||||
tmpFile.open();
|
||||
Kdbx4Writer writer;
|
||||
writer.writeDatabase(&tmpFile, db.data());
|
||||
tmpFile.close();
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", tmpFile.fileName(), "New"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("/General/New Entry\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", tmpFile.fileName(), "Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n/General/New Entry\n"));
|
||||
}
|
||||
|
||||
void TestCli::testMerge()
|
||||
{
|
||||
Merge mergeCmd;
|
||||
QVERIFY(!mergeCmd.name.isEmpty());
|
||||
QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name));
|
||||
|
||||
Kdbx4Writer writer;
|
||||
Kdbx4Reader reader;
|
||||
|
||||
// load test database and save a copy
|
||||
auto db = readTestDatabase();
|
||||
QVERIFY(db);
|
||||
QTemporaryFile targetFile1;
|
||||
targetFile1.open();
|
||||
writer.writeDatabase(&targetFile1, db.data());
|
||||
targetFile1.close();
|
||||
|
||||
// save another copy with a different password
|
||||
QTemporaryFile targetFile2;
|
||||
targetFile2.open();
|
||||
auto oldKey = db->key();
|
||||
auto key = QSharedPointer<CompositeKey>::create();
|
||||
key->addKey(QSharedPointer<PasswordKey>::create("b"));
|
||||
db->setKey(key);
|
||||
writer.writeDatabase(&targetFile2, db.data());
|
||||
targetFile2.close();
|
||||
db->setKey(oldKey);
|
||||
|
||||
// then add a new entry to the in-memory database and save another copy
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle("Some Website");
|
||||
entry->setPassword("secretsecretsecret");
|
||||
auto* group = db->rootGroup()->findGroupByPath("/Internet/");
|
||||
QVERIFY(group);
|
||||
group->addEntry(entry);
|
||||
QTemporaryFile sourceFile;
|
||||
sourceFile.open();
|
||||
writer.writeDatabase(&sourceFile, db.data());
|
||||
sourceFile.close();
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n"));
|
||||
|
||||
QFile readBack(targetFile1.fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
QScopedPointer<Database> mergedDb(reader.readDatabase(&readBack, oldKey));
|
||||
readBack.close();
|
||||
QVERIFY(mergedDb);
|
||||
auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website");
|
||||
QVERIFY(entry1);
|
||||
QCOMPARE(entry1->title(), QString("Some Website"));
|
||||
QCOMPARE(entry1->password(), QString("secretsecretsecret"));
|
||||
|
||||
// try again with different passwords for both files
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("b");
|
||||
Utils::setNextPassword("a");
|
||||
mergeCmd.execute({"merge", targetFile2.fileName(), sourceFile.fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine();
|
||||
m_stdoutFile->readLine();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n"));
|
||||
|
||||
readBack.setFileName(targetFile2.fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
mergedDb.reset(reader.readDatabase(&readBack, key));
|
||||
readBack.close();
|
||||
QVERIFY(mergedDb);
|
||||
entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website");
|
||||
QVERIFY(entry1);
|
||||
QCOMPARE(entry1->title(), QString("Some Website"));
|
||||
QCOMPARE(entry1->password(), QString("secretsecretsecret"));
|
||||
}
|
||||
|
||||
void TestCli::testRemove()
|
||||
{
|
||||
Remove removeCmd;
|
||||
QVERIFY(!removeCmd.name.isEmpty());
|
||||
QVERIFY(removeCmd.getDescriptionLine().contains(removeCmd.name));
|
||||
|
||||
Kdbx3Reader reader;
|
||||
Kdbx3Writer writer;
|
||||
|
||||
// load test database and save a copy with disabled recycle bin
|
||||
auto db = readTestDatabase();
|
||||
QVERIFY(db);
|
||||
QTemporaryFile fileCopy;
|
||||
fileCopy.open();
|
||||
db->metadata()->setRecycleBinEnabled(false);
|
||||
writer.writeDatabase(&fileCopy, db.data());
|
||||
fileCopy.close();
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
|
||||
// delete entry and verify
|
||||
Utils::setNextPassword("a");
|
||||
removeCmd.execute({"rm", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully recycled entry Sample Entry.\n"));
|
||||
|
||||
auto key = QSharedPointer<CompositeKey>::create();
|
||||
key->addKey(QSharedPointer<PasswordKey>::create("a"));
|
||||
QFile readBack(m_dbFile->fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
QScopedPointer<Database> readBackDb(reader.readDatabase(&readBack, key));
|
||||
readBack.close();
|
||||
QVERIFY(readBackDb);
|
||||
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));
|
||||
QVERIFY(readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
|
||||
// try again, this time without recycle bin
|
||||
Utils::setNextPassword("a");
|
||||
removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully deleted entry Sample Entry.\n"));
|
||||
|
||||
readBack.setFileName(fileCopy.fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
readBackDb.reset(reader.readDatabase(&readBack, key));
|
||||
readBack.close();
|
||||
QVERIFY(readBackDb);
|
||||
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));
|
||||
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
|
||||
// finally, try deleting a non-existent entry
|
||||
Utils::setNextPassword("a");
|
||||
removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n"));
|
||||
}
|
||||
|
||||
void TestCli::testShow()
|
||||
{
|
||||
Show showCmd;
|
||||
QVERIFY(!showCmd.name.isEmpty());
|
||||
QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->reset();
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: Password\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"));
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"
|
||||
"http://www.somesite.com/\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("ERROR: unknown attribute DoesNotExist.\n"));
|
||||
}
|
69
tests/TestCli.h
Normal file
69
tests/TestCli.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_TESTCLI_H
|
||||
#define KEEPASSXC_TESTCLI_H
|
||||
|
||||
#include "core/Database.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QTextStream>
|
||||
#include <QFile>
|
||||
#include <QScopedPointer>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
class TestCli : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QSharedPointer<Database> readTestDatabase() const;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
void cleanupTestCase();
|
||||
|
||||
void testCommand();
|
||||
void testAdd();
|
||||
void testClip();
|
||||
void testDiceware();
|
||||
void testEdit();
|
||||
void testEstimate_data();
|
||||
void testEstimate();
|
||||
void testExtract();
|
||||
void testGenerate_data();
|
||||
void testGenerate();
|
||||
void testList();
|
||||
void testLocate();
|
||||
void testMerge();
|
||||
void testRemove();
|
||||
void testShow();
|
||||
|
||||
private:
|
||||
QByteArray m_dbData;
|
||||
QScopedPointer<QTemporaryFile> m_dbFile;
|
||||
QScopedPointer<QTemporaryFile> m_stdoutFile;
|
||||
QScopedPointer<QTemporaryFile> m_stderrFile;
|
||||
QScopedPointer<QTemporaryFile> m_stdinFile;
|
||||
FILE* m_stdoutHandle = stdout;
|
||||
FILE* m_stderrHandle = stderr;
|
||||
FILE* m_stdinHandle = stdin;
|
||||
};
|
||||
|
||||
#endif //KEEPASSXC_TESTCLI_H
|
@ -199,12 +199,12 @@ void TestKdbx4::testFormat400Upgrade_data()
|
||||
auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << false << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << false << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << false << kdbx3;
|
||||
QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << true << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << true << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << true << kdbx4;
|
||||
QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << false << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << false << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << false << kdbx3;
|
||||
QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << true << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << true << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << true << kdbx4;
|
||||
|
||||
QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
|
||||
QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
|
||||
|
131
tests/TestPasswordGenerator.cpp
Normal file
131
tests/TestPasswordGenerator.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TestPasswordGenerator.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QRegularExpression>
|
||||
|
||||
QTEST_GUILESS_MAIN(TestPasswordGenerator)
|
||||
|
||||
void TestPasswordGenerator::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestPasswordGenerator::testCharClasses()
|
||||
{
|
||||
PasswordGenerator generator;
|
||||
QVERIFY(!generator.isValid());
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters);
|
||||
generator.setLength(16);
|
||||
QVERIFY(generator.isValid());
|
||||
QCOMPARE(generator.generatePassword().size(), 16);
|
||||
|
||||
generator.setLength(2000);
|
||||
QString password = generator.generatePassword();
|
||||
QCOMPARE(password.size(), 2000);
|
||||
QRegularExpression regex(R"(^[a-z]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::UpperLetters);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[A-Z]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Numbers);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^\d+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Braces);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[\(\)\[\]\{\}]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Punctuation);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[\.,:;]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Quotes);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^["']+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Dashes);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[\-/\\_|]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Math);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[!\*\+\-<=>\?]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Logograms);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[#`~%&^$@]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::EASCII);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[^a-zA-Z0-9\.,:;"'\-/\\_|!\*\+\-<=>\?#`~%&^$@]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters
|
||||
| PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Braces);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[a-zA-Z\(\)\[\]\{\}]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Quotes
|
||||
| PasswordGenerator::CharClass::Numbers | PasswordGenerator::CharClass::Dashes);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^["'\d\-/\\_|]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
}
|
||||
|
||||
void TestPasswordGenerator::testLookalikeExclusion()
|
||||
{
|
||||
PasswordGenerator generator;
|
||||
generator.setLength(2000);
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters);
|
||||
QVERIFY(generator.isValid());
|
||||
QString password = generator.generatePassword();
|
||||
QCOMPARE(password.size(), 2000);
|
||||
|
||||
generator.setFlags(PasswordGenerator::GeneratorFlag::ExcludeLookAlike);
|
||||
password = generator.generatePassword();
|
||||
QRegularExpression regex("^[^lI0]+$");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters |
|
||||
PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern("^[^lI01]+$");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters
|
||||
| PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers
|
||||
| PasswordGenerator::CharClass::EASCII);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern("^[^lI01﹒]+$");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
}
|
33
tests/TestPasswordGenerator.h
Normal file
33
tests/TestPasswordGenerator.h
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_TESTPASSWORDGENERATOR_H
|
||||
#define KEEPASSXC_TESTPASSWORDGENERATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class TestPasswordGenerator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testCharClasses();
|
||||
void testLookalikeExclusion();
|
||||
};
|
||||
|
||||
#endif //KEEPASSXC_TESTPASSWORDGENERATOR_H
|
@ -26,76 +26,171 @@
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestSymmetricCipher)
|
||||
Q_DECLARE_METATYPE(SymmetricCipher::Algorithm);
|
||||
Q_DECLARE_METATYPE(SymmetricCipher::Mode);
|
||||
Q_DECLARE_METATYPE(SymmetricCipher::Direction);
|
||||
|
||||
void TestSymmetricCipher::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes128CbcEncryption()
|
||||
void TestSymmetricCipher::testAlgorithmToCipher()
|
||||
{
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes128), KeePass2::CIPHER_AES128);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes256), KeePass2::CIPHER_AES256);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Twofish), KeePass2::CIPHER_TWOFISH);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::ChaCha20), KeePass2::CIPHER_CHACHA20);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::InvalidAlgorithm), QUuid());
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testEncryptionDecryption_data()
|
||||
{
|
||||
QTest::addColumn<SymmetricCipher::Algorithm>("algorithm");
|
||||
QTest::addColumn<SymmetricCipher::Mode>("mode");
|
||||
QTest::addColumn<SymmetricCipher::Direction>("direction");
|
||||
QTest::addColumn<QByteArray>("key");
|
||||
QTest::addColumn<QByteArray>("iv");
|
||||
QTest::addColumn<QByteArray>("plainText");
|
||||
QTest::addColumn<QByteArray>("cipherText");
|
||||
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
QTest::newRow("AES128-CBC Encryption")
|
||||
<< SymmetricCipher::Aes128
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Encrypt
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2");
|
||||
|
||||
QTest::newRow("AES128-CBC Decryption")
|
||||
<< SymmetricCipher::Aes128
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Decrypt
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
|
||||
|
||||
QTest::newRow("AES256-CBC Encryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Encrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d");
|
||||
|
||||
QTest::newRow("AES256-CBC Decryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Decrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
|
||||
|
||||
QTest::newRow("AES256-CTR Encryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Ctr
|
||||
<< SymmetricCipher::Encrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5");
|
||||
|
||||
QTest::newRow("AES256-CTR Decryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Ctr
|
||||
<< SymmetricCipher::Decrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
|
||||
<< QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testEncryptionDecryption()
|
||||
{
|
||||
QFETCH(SymmetricCipher::Algorithm, algorithm);
|
||||
QFETCH(SymmetricCipher::Mode, mode);
|
||||
QFETCH(SymmetricCipher::Direction, direction);
|
||||
QFETCH(QByteArray, key);
|
||||
QFETCH(QByteArray, iv);
|
||||
QFETCH(QByteArray, plainText);
|
||||
QFETCH(QByteArray, cipherText);
|
||||
|
||||
QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d");
|
||||
cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
SymmetricCipher cipher(algorithm, mode, direction);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
QCOMPARE(cipher.process(plainText, &ok), cipherText);
|
||||
QVERIFY(ok);
|
||||
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
if (mode == SymmetricCipher::Cbc) {
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, algorithm, mode, direction);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
QCOMPARE(buffer.data().size(), 32);
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
QCOMPARE(buffer.data().size(), 32);
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes128CbcDecryption()
|
||||
void TestSymmetricCipher::testAesCbcPadding_data()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d");
|
||||
cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
bool ok;
|
||||
QTest::addColumn<QByteArray>("key");
|
||||
QTest::addColumn<QByteArray>("iv");
|
||||
QTest::addColumn<QByteArray>("cipherText");
|
||||
QTest::addColumn<QByteArray>("plainText");
|
||||
QTest::addColumn<QByteArray>("padding");
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
QCOMPARE(cipher.process(cipherText, &ok), plainText);
|
||||
QVERIFY(ok);
|
||||
QTest::newRow("AES128")
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538");
|
||||
|
||||
QTest::newRow("AES256")
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAesCbcPadding()
|
||||
{
|
||||
QFETCH(QByteArray, key);
|
||||
QFETCH(QByteArray, iv);
|
||||
QFETCH(QByteArray, cipherText);
|
||||
QFETCH(QByteArray, plainText);
|
||||
QFETCH(QByteArray, padding);
|
||||
|
||||
// padded with 16 0x10 bytes
|
||||
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538");
|
||||
QByteArray cipherTextPadded = cipherText + padding;
|
||||
|
||||
QBuffer buffer(&cipherTextPadded);
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
@ -114,126 +209,48 @@ void TestSymmetricCipher::testAes128CbcDecryption()
|
||||
QCOMPARE(stream.read(100), plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CbcEncryption()
|
||||
void TestSymmetricCipher::testInplaceEcb_data()
|
||||
{
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
QTest::addColumn<QByteArray>("key");
|
||||
QTest::addColumn<QByteArray>("plainText");
|
||||
QTest::addColumn<QByteArray>("cipherText");
|
||||
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(plainText, &ok), cipherText);
|
||||
QVERIFY(ok);
|
||||
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
QCOMPARE(buffer.data().size(), 32);
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
QTest::newRow("AES128")
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a")
|
||||
<< QByteArray::fromHex("3ad77bb40d7a3660a89ecaf32466ef97");
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CbcDecryption()
|
||||
void TestSymmetricCipher::testInplaceEcb()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
bool ok;
|
||||
QFETCH(QByteArray, key);
|
||||
QFETCH(QByteArray, plainText);
|
||||
QFETCH(QByteArray, cipherText);
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
SymmetricCipher cipherInPlaceEnc(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipherInPlaceEnc.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceEnc.blockSize(), 16);
|
||||
auto data = QByteArray(plainText);
|
||||
QVERIFY(cipherInPlaceEnc.processInPlace(data));
|
||||
QCOMPARE(data, cipherText);
|
||||
|
||||
QCOMPARE(cipher.process(cipherText, &ok), plainText);
|
||||
QVERIFY(ok);
|
||||
SymmetricCipher cipherInPlaceDec(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipherInPlaceDec.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceDec.blockSize(), 16);
|
||||
QVERIFY(cipherInPlaceDec.processInPlace(data));
|
||||
QCOMPARE(data, plainText);
|
||||
|
||||
// padded with 16 0x16 bytes
|
||||
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||
QBuffer buffer(&cipherTextPadded);
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QVERIFY(stream.open(QIODevice::ReadOnly));
|
||||
SymmetricCipher cipherInPlaceEnc2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipherInPlaceEnc2.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceEnc2.blockSize(), 16);
|
||||
data = QByteArray(plainText);
|
||||
QVERIFY(cipherInPlaceEnc2.processInPlace(data, 100));
|
||||
|
||||
QCOMPARE(stream.read(10), plainText.left(10));
|
||||
buffer.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(20), plainText.left(20));
|
||||
buffer.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(16), plainText.left(16));
|
||||
buffer.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(100), plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CtrEncryption()
|
||||
{
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228");
|
||||
cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, ctr));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(plainText, &ok), cipherText);
|
||||
QVERIFY(ok);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CtrDecryption()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228");
|
||||
cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipher.init(key, ctr));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(cipherText, &ok), plainText);
|
||||
QVERIFY(ok);
|
||||
SymmetricCipher cipherInPlaceDec2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipherInPlaceDec2.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceDec2.blockSize(), 16);
|
||||
QVERIFY(cipherInPlaceDec2.processInPlace(data, 100));
|
||||
QCOMPARE(data, plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testTwofish256CbcEncryption()
|
||||
|
@ -27,12 +27,13 @@ class TestSymmetricCipher : public QObject
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testAes128CbcEncryption();
|
||||
void testAes128CbcDecryption();
|
||||
void testAes256CbcEncryption();
|
||||
void testAes256CbcDecryption();
|
||||
void testAes256CtrEncryption();
|
||||
void testAes256CtrDecryption();
|
||||
void testAlgorithmToCipher();
|
||||
void testEncryptionDecryption_data();
|
||||
void testEncryptionDecryption();
|
||||
void testAesCbcPadding_data();
|
||||
void testAesCbcPadding();
|
||||
void testInplaceEcb_data();
|
||||
void testInplaceEcb();
|
||||
void testTwofish256CbcEncryption();
|
||||
void testTwofish256CbcDecryption();
|
||||
void testSalsa20();
|
||||
|
@ -15,6 +15,6 @@
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
add_unit_test(NAME testgui SOURCES TestGui.cpp TemporaryFile.cpp LIBS ${TEST_LIBRARIES})
|
||||
add_unit_test(NAME testgui SOURCES TestGui.cpp LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES})
|
||||
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Danny Su <contact@dannysu.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TemporaryFile.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const QString TemporaryFile::SUFFIX = ".win";
|
||||
|
||||
TemporaryFile::~TemporaryFile()
|
||||
{
|
||||
if (m_tempFile.autoRemove()) {
|
||||
m_file.remove();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool TemporaryFile::open()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Still call QTemporaryFile::open() so that it figures out the temporary
|
||||
// file name to use. Assuming that by appending the SUFFIX to whatever
|
||||
// QTemporaryFile chooses is also an available file.
|
||||
bool tempFileOpened = m_tempFile.open();
|
||||
if (tempFileOpened) {
|
||||
m_file.setFileName(filePath());
|
||||
return m_file.open(QIODevice::WriteOnly);
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return m_tempFile.open();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TemporaryFile::close()
|
||||
{
|
||||
m_tempFile.close();
|
||||
#ifdef Q_OS_WIN
|
||||
m_file.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
qint64 TemporaryFile::write(const char* data, qint64 maxSize)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return m_file.write(data, maxSize);
|
||||
#else
|
||||
return m_tempFile.write(data, maxSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
qint64 TemporaryFile::write(const QByteArray& byteArray)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return m_file.write(byteArray);
|
||||
#else
|
||||
return m_tempFile.write(byteArray);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString TemporaryFile::fileName() const
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return QFileInfo(m_tempFile).fileName() + TemporaryFile::SUFFIX;
|
||||
#else
|
||||
return QFileInfo(m_tempFile).fileName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString TemporaryFile::filePath() const
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return m_tempFile.fileName() + TemporaryFile::SUFFIX;
|
||||
#else
|
||||
return m_tempFile.fileName();
|
||||
#endif
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Danny Su <contact@dannysu.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_TEMPORARYFILE_H
|
||||
#define KEEPASSX_TEMPORARYFILE_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
/**
|
||||
* QTemporaryFile::close() doesn't actually close the file according to
|
||||
* http://doc.qt.io/qt-5/qtemporaryfile.html: "For as long as the
|
||||
* QTemporaryFile object itself is not destroyed, the unique temporary file
|
||||
* will exist and be kept open internally by QTemporaryFile."
|
||||
*
|
||||
* This behavior causes issues when running tests on Windows. If the file is
|
||||
* not closed, the testSave test will fail due to Access Denied. The
|
||||
* auto-reload test also fails from Windows not triggering file change
|
||||
* notification because the file isn't actually closed by QTemporaryFile.
|
||||
*
|
||||
* This class isolates the Windows specific logic that uses QFile to really
|
||||
* close the test file when requested to.
|
||||
*/
|
||||
class TemporaryFile : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
#ifdef Q_OS_WIN
|
||||
~TemporaryFile();
|
||||
#endif
|
||||
|
||||
bool open();
|
||||
void close();
|
||||
qint64 write(const char* data, qint64 maxSize);
|
||||
qint64 write(const QByteArray& byteArray);
|
||||
|
||||
QString fileName() const;
|
||||
QString filePath() const;
|
||||
|
||||
private:
|
||||
QTemporaryFile m_tempFile;
|
||||
#ifdef Q_OS_WIN
|
||||
QFile m_file;
|
||||
static const QString SUFFIX;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TEMPORARYFILE_H
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "TestGui.h"
|
||||
#include "TestGlobal.h"
|
||||
#include "gui/Application.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
@ -33,12 +34,12 @@
|
||||
#include <QPushButton>
|
||||
#include <QSignalSpy>
|
||||
#include <QSpinBox>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "config-keepassx-tests.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
@ -59,7 +60,6 @@
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/PasswordEdit.h"
|
||||
#include "gui/SearchWidget.h"
|
||||
@ -74,22 +74,23 @@
|
||||
#include "gui/masterkey/KeyComponentWidget.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
QTEST_MAIN(TestGui)
|
||||
|
||||
void TestGui::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
Config::createTempFileInstance();
|
||||
// Disable autosave so we can test the modified file indicator
|
||||
config()->set("AutoSaveAfterEveryChange", false);
|
||||
// Enable the tray icon so we can test hiding/restoring the window
|
||||
// Enable the tray icon so we can test hiding/restoring the windowQByteArray
|
||||
config()->set("GUI/ShowTrayIcon", true);
|
||||
// Disable advanced settings mode (activate within individual tests to test advanced settings)
|
||||
config()->set("GUI/AdvancedSettings", false);
|
||||
|
||||
m_mainWindow = new MainWindow();
|
||||
m_mainWindow.reset(new MainWindow());
|
||||
Bootstrap::restoreMainWindowState(*m_mainWindow);
|
||||
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
|
||||
m_mainWindow->show();
|
||||
m_mainWindow->activateWindow();
|
||||
Tools::wait(50);
|
||||
|
||||
// Load the NewDatabase.kdbx file into temporary storage
|
||||
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
|
||||
@ -101,29 +102,32 @@ void TestGui::initTestCase()
|
||||
// Every test starts with opening the temp database
|
||||
void TestGui::init()
|
||||
{
|
||||
m_dbFile.reset(new QTemporaryFile());
|
||||
// Write the temp storage to a temp database file for use in our tests
|
||||
QVERIFY(m_dbFile.open());
|
||||
QCOMPARE(m_dbFile.write(m_dbData), static_cast<qint64>((m_dbData.size())));
|
||||
m_dbFile.close();
|
||||
|
||||
m_dbFileName = m_dbFile.fileName();
|
||||
m_dbFilePath = m_dbFile.filePath();
|
||||
QVERIFY(m_dbFile->open());
|
||||
QCOMPARE(m_dbFile->write(m_dbData), static_cast<qint64>((m_dbData.size())));
|
||||
m_dbFileName = QFileInfo(m_dbFile->fileName()).fileName();
|
||||
m_dbFilePath = m_dbFile->fileName();
|
||||
m_dbFile->close();
|
||||
|
||||
fileDialog()->setNextFileName(m_dbFilePath);
|
||||
triggerAction("actionDatabaseOpen");
|
||||
|
||||
QWidget* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget");
|
||||
QLineEdit* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
|
||||
auto* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget");
|
||||
auto* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
|
||||
QVERIFY(editPassword);
|
||||
|
||||
QTest::keyClicks(editPassword, "a");
|
||||
QTest::keyClick(editPassword, Qt::Key_Enter);
|
||||
Tools::wait(100);
|
||||
|
||||
QVERIFY(m_tabWidget->currentDatabaseWidget());
|
||||
QTRY_VERIFY(m_tabWidget->currentDatabaseWidget());
|
||||
|
||||
m_dbWidget = m_tabWidget->currentDatabaseWidget();
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
// make sure window is activated or focus tests may fail
|
||||
m_mainWindow->activateWindow();
|
||||
QApplication::processEvents();
|
||||
}
|
||||
|
||||
// Every test ends with closing the temp database without saving
|
||||
@ -132,17 +136,21 @@ void TestGui::cleanup()
|
||||
// DO NOT save the database
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
QApplication::processEvents();
|
||||
|
||||
if (m_db) {
|
||||
delete m_db;
|
||||
}
|
||||
m_db = nullptr;
|
||||
|
||||
if (m_dbWidget) {
|
||||
delete m_dbWidget;
|
||||
}
|
||||
m_dbWidget = nullptr;
|
||||
|
||||
m_dbFile->remove();
|
||||
}
|
||||
|
||||
void TestGui::cleanupTestCase()
|
||||
{
|
||||
m_dbFile->remove();
|
||||
}
|
||||
|
||||
void TestGui::testSettingsDefaultTabOrder()
|
||||
@ -187,8 +195,9 @@ void TestGui::testCreateDatabase()
|
||||
|
||||
// check key and encryption
|
||||
QCOMPARE(m_db->key()->keys().size(), 2);
|
||||
QCOMPARE(m_db->kdf()->rounds(), 2);
|
||||
QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2);
|
||||
QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES);
|
||||
QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES256);
|
||||
auto compositeKey = QSharedPointer<CompositeKey>::create();
|
||||
compositeKey->addKey(QSharedPointer<PasswordKey>::create("test"));
|
||||
auto fileKey = QSharedPointer<FileKey>::create();
|
||||
@ -213,7 +222,40 @@ void TestGui::createDatabaseCallback()
|
||||
QTest::keyClick(wizard, Qt::Key_Enter);
|
||||
QCOMPARE(wizard->currentId(), 1);
|
||||
|
||||
QTest::keyClick(wizard, Qt::Key_Enter);
|
||||
auto decryptionTimeSlider = wizard->currentPage()->findChild<QSlider*>("decryptionTimeSlider");
|
||||
auto algorithmComboBox = wizard->currentPage()->findChild<QComboBox*>("algorithmComboBox");
|
||||
QTRY_VERIFY(decryptionTimeSlider->isVisible());
|
||||
QVERIFY(!algorithmComboBox->isVisible());
|
||||
auto advancedToggle = wizard->currentPage()->findChild<QPushButton*>("advancedSettingsButton");
|
||||
QTest::mouseClick(advancedToggle, Qt::MouseButton::LeftButton);
|
||||
QTRY_VERIFY(!decryptionTimeSlider->isVisible());
|
||||
QVERIFY(algorithmComboBox->isVisible());
|
||||
|
||||
auto rounds = wizard->currentPage()->findChild<QSpinBox*>("transformRoundsSpinBox");
|
||||
QVERIFY(rounds);
|
||||
QVERIFY(rounds->isVisible());
|
||||
QTest::mouseClick(rounds, Qt::MouseButton::LeftButton);
|
||||
QTest::keyClick(rounds, Qt::Key_A, Qt::ControlModifier);
|
||||
QTest::keyClicks(rounds, "2");
|
||||
QTest::keyClick(rounds, Qt::Key_Tab);
|
||||
QTest::keyClick(rounds, Qt::Key_Tab);
|
||||
|
||||
auto memory = wizard->currentPage()->findChild<QSpinBox*>("memorySpinBox");
|
||||
QVERIFY(memory);
|
||||
QVERIFY(memory->isVisible());
|
||||
QTest::mouseClick(memory, Qt::MouseButton::LeftButton);
|
||||
QTest::keyClick(memory, Qt::Key_A, Qt::ControlModifier);
|
||||
QTest::keyClicks(memory, "50");
|
||||
QTest::keyClick(memory, Qt::Key_Tab);
|
||||
|
||||
auto parallelism = wizard->currentPage()->findChild<QSpinBox*>("parallelismSpinBox");
|
||||
QVERIFY(parallelism);
|
||||
QVERIFY(parallelism->isVisible());
|
||||
QTest::mouseClick(parallelism, Qt::MouseButton::LeftButton);
|
||||
QTest::keyClick(parallelism, Qt::Key_A, Qt::ControlModifier);
|
||||
QTest::keyClicks(parallelism, "1");
|
||||
QTest::keyClick(parallelism, Qt::Key_Enter);
|
||||
|
||||
QCOMPARE(wizard->currentId(), 2);
|
||||
|
||||
// enter password
|
||||
@ -222,7 +264,7 @@ void TestGui::createDatabaseCallback()
|
||||
auto* passwordEdit = passwordWidget->findChild<QLineEdit*>("enterPasswordEdit");
|
||||
auto* passwordRepeatEdit = passwordWidget->findChild<QLineEdit*>("repeatPasswordEdit");
|
||||
QTRY_VERIFY(passwordEdit->isVisible());
|
||||
QVERIFY(passwordEdit->hasFocus());
|
||||
QTRY_VERIFY(passwordEdit->hasFocus());
|
||||
QTest::keyClicks(passwordEdit, "test");
|
||||
QTest::keyClick(passwordEdit, Qt::Key::Key_Tab);
|
||||
QTest::keyClicks(passwordRepeatEdit, "test");
|
||||
@ -247,25 +289,26 @@ void TestGui::createDatabaseCallback()
|
||||
QCOMPARE(fileCombo->currentText(), QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key"));
|
||||
|
||||
// save database to temporary file
|
||||
TemporaryFile tmpFile;
|
||||
QTemporaryFile tmpFile;
|
||||
QVERIFY(tmpFile.open());
|
||||
tmpFile.close();
|
||||
fileDialog()->setNextFileName(tmpFile.filePath());
|
||||
fileDialog()->setNextFileName(tmpFile.fileName());
|
||||
|
||||
QTest::keyClick(fileCombo, Qt::Key::Key_Enter);
|
||||
tmpFile.remove();
|
||||
}
|
||||
|
||||
void TestGui::testMergeDatabase()
|
||||
{
|
||||
// It is safe to ignore the warning this line produces
|
||||
QSignalSpy dbMergeSpy(m_dbWidget, SIGNAL(databaseMerged(Database*)));
|
||||
QSignalSpy dbMergeSpy(m_dbWidget.data(), SIGNAL(databaseMerged(Database*)));
|
||||
|
||||
// set file to merge from
|
||||
fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx"));
|
||||
triggerAction("actionDatabaseMerge");
|
||||
|
||||
QWidget* databaseOpenMergeWidget = m_mainWindow->findChild<QWidget*>("databaseOpenMergeWidget");
|
||||
QLineEdit* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("editPassword");
|
||||
auto* databaseOpenMergeWidget = m_mainWindow->findChild<QWidget*>("databaseOpenMergeWidget");
|
||||
auto* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("editPassword");
|
||||
QVERIFY(editPasswordMerge->isVisible());
|
||||
|
||||
m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseOpenMergeWidget);
|
||||
@ -300,11 +343,11 @@ void TestGui::testAutoreloadDatabase()
|
||||
// Test accepting new file in autoreload
|
||||
MessageBox::setNextAnswer(QMessageBox::Yes);
|
||||
// Overwrite the current database with the temp data
|
||||
QVERIFY(m_dbFile.open());
|
||||
QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile.close();
|
||||
Tools::wait(1500);
|
||||
QVERIFY(m_dbFile->open());
|
||||
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile->close();
|
||||
|
||||
Tools::wait(800);
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
// the General group contains one entry from the new db data
|
||||
@ -318,10 +361,10 @@ void TestGui::testAutoreloadDatabase()
|
||||
// Test rejecting new file in autoreload
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
// Overwrite the current temp database with a new file
|
||||
m_dbFile.open();
|
||||
QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile.close();
|
||||
Tools::wait(1500);
|
||||
m_dbFile->open();
|
||||
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile->close();
|
||||
Tools::wait(800);
|
||||
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
@ -342,10 +385,10 @@ void TestGui::testAutoreloadDatabase()
|
||||
// This is saying yes to merging the entries
|
||||
MessageBox::setNextAnswer(QMessageBox::Yes);
|
||||
// Overwrite the current database with the temp data
|
||||
QVERIFY(m_dbFile.open());
|
||||
QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile.close();
|
||||
Tools::wait(1500);
|
||||
QVERIFY(m_dbFile->open());
|
||||
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile->close();
|
||||
Tools::wait(800);
|
||||
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
@ -361,17 +404,17 @@ void TestGui::testTabs()
|
||||
|
||||
void TestGui::testEditEntry()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
int editCount = 0;
|
||||
|
||||
// Select the first entry in the database
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
QModelIndex entryItem = entryView->model()->index(0, 1);
|
||||
Entry* entry = entryView->entryFromIndex(entryItem);
|
||||
clickIndex(entryItem, entryView, Qt::LeftButton);
|
||||
|
||||
// Confirm the edit action button is enabled
|
||||
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
auto* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
QVERIFY(entryEditAction->isEnabled());
|
||||
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||
QVERIFY(entryEditWidget->isVisible());
|
||||
@ -380,12 +423,12 @@ void TestGui::testEditEntry()
|
||||
// Edit the first entry ("Sample Entry")
|
||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "_test");
|
||||
|
||||
// Apply the edit
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
QCOMPARE(entry->title(), QString("Sample Entry_test"));
|
||||
@ -410,7 +453,7 @@ void TestGui::testEditEntry()
|
||||
|
||||
// Test protected attributes
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
QPlainTextEdit* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("addAttributeButton"), Qt::LeftButton);
|
||||
QString attrText = "TEST TEXT";
|
||||
QTest::keyClicks(attrTextEdit, attrText);
|
||||
@ -422,11 +465,11 @@ void TestGui::testEditEntry()
|
||||
editEntryWidget->setCurrentPage(0);
|
||||
|
||||
// Test mismatch passwords
|
||||
QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
QString originalPassword = passwordEdit->text();
|
||||
passwordEdit->setText("newpass");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
MessageWidget* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
|
||||
auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
|
||||
QTRY_VERIFY(messageWiget->isVisible());
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
QCOMPARE(passwordEdit->text(), QString("newpass"));
|
||||
@ -469,9 +512,9 @@ void TestGui::testSearchEditEntry()
|
||||
// Regression test for Issue #1447 -- Uses example from issue description
|
||||
|
||||
// Find buttons for group creation
|
||||
EditGroupWidget* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
|
||||
QLineEdit* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
|
||||
QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
|
||||
auto* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
|
||||
auto* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
|
||||
// Add groups "Good" and "Bad"
|
||||
m_dbWidget->createGroup();
|
||||
@ -484,11 +527,11 @@ void TestGui::testSearchEditEntry()
|
||||
m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup());
|
||||
|
||||
// Find buttons for entry creation
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
|
||||
// Create "Doggy" in "Good"
|
||||
Group* goodGroup = m_dbWidget->currentGroup()->findChildByName(QString("Good"));
|
||||
@ -501,8 +544,8 @@ void TestGui::testSearchEditEntry()
|
||||
m_dbWidget->groupView()->setCurrentGroup(badGroup);
|
||||
|
||||
// Search for "Doggy" entry
|
||||
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||
QTest::keyClicks(searchTextEdit, "Doggy");
|
||||
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
||||
@ -518,11 +561,11 @@ void TestGui::testSearchEditEntry()
|
||||
|
||||
void TestGui::testAddEntry()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@ -535,10 +578,10 @@ void TestGui::testAddEntry()
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
|
||||
// Add entry "test" and confirm added
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
@ -551,28 +594,12 @@ void TestGui::testAddEntry()
|
||||
// Add entry "something 2"
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 2");
|
||||
QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
QLineEdit* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
auto* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
QTest::keyClicks(passwordEdit, "something 2");
|
||||
QTest::keyClicks(passwordRepeatEdit, "something 2");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
/* All apply tests disabled due to data loss workaround
|
||||
* that disables apply button on new entry creation
|
||||
*
|
||||
// Add entry "something 3" using the apply button then click ok
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 3");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
// Add entry "something 4" using the apply button then click cancel
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 4");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton);
|
||||
*/
|
||||
|
||||
// Add entry "something 5" but click cancel button (does NOT add entry)
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 5");
|
||||
@ -587,10 +614,10 @@ void TestGui::testAddEntry()
|
||||
|
||||
void TestGui::testPasswordEntryEntropy()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@ -603,18 +630,18 @@ void TestGui::testPasswordEntryEntropy()
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
|
||||
// Add entry "test" and confirm added
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
|
||||
// Open the password generator
|
||||
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
auto* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
QTest::mouseClick(generatorButton, Qt::LeftButton);
|
||||
|
||||
// Type in some password
|
||||
QLineEdit* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword");
|
||||
QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
auto* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword");
|
||||
auto* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
auto* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
|
||||
editNewPassword->setText("");
|
||||
QTest::keyClicks(editNewPassword, "hello");
|
||||
@ -659,10 +686,10 @@ void TestGui::testPasswordEntryEntropy()
|
||||
|
||||
void TestGui::testDicewareEntryEntropy()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@ -675,27 +702,27 @@ void TestGui::testDicewareEntryEntropy()
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
|
||||
// Add entry "test" and confirm added
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
|
||||
// Open the password generator
|
||||
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
auto* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
QTest::mouseClick(generatorButton, Qt::LeftButton);
|
||||
|
||||
// Select Diceware
|
||||
QTabWidget* tabWidget = editEntryWidget->findChild<QTabWidget*>("tabWidget");
|
||||
QWidget* dicewareWidget = editEntryWidget->findChild<QWidget*>("dicewareWidget");
|
||||
auto* tabWidget = editEntryWidget->findChild<QTabWidget*>("tabWidget");
|
||||
auto* dicewareWidget = editEntryWidget->findChild<QWidget*>("dicewareWidget");
|
||||
tabWidget->setCurrentWidget(dicewareWidget);
|
||||
|
||||
QComboBox* comboBoxWordList = dicewareWidget->findChild<QComboBox*>("comboBoxWordList");
|
||||
auto* comboBoxWordList = dicewareWidget->findChild<QComboBox*>("comboBoxWordList");
|
||||
comboBoxWordList->setCurrentText("eff_large.wordlist");
|
||||
QSpinBox* spinBoxWordCount = dicewareWidget->findChild<QSpinBox*>("spinBoxWordCount");
|
||||
auto* spinBoxWordCount = dicewareWidget->findChild<QSpinBox*>("spinBoxWordCount");
|
||||
spinBoxWordCount->setValue(6);
|
||||
|
||||
// Type in some password
|
||||
QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
auto* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
auto* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
|
||||
QCOMPARE(entropyLabel->text(), QString("Entropy: 77.55 bit"));
|
||||
QCOMPARE(strengthLabel->text(), QString("Password Quality: Good"));
|
||||
@ -703,8 +730,8 @@ void TestGui::testDicewareEntryEntropy()
|
||||
|
||||
void TestGui::testTotp()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 1);
|
||||
|
||||
@ -716,36 +743,36 @@ void TestGui::testTotp()
|
||||
|
||||
triggerAction("actionEntrySetupTotp");
|
||||
|
||||
TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
|
||||
auto* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
|
||||
|
||||
Tools::wait(100);
|
||||
QApplication::processEvents();
|
||||
|
||||
QLineEdit* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
|
||||
auto* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
|
||||
|
||||
QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||
QTest::keyClicks(seedEdit, exampleSeed);
|
||||
|
||||
QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
auto* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
QPlainTextEdit* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("revealAttributeButton"), Qt::LeftButton);
|
||||
QCOMPARE(attrTextEdit->toPlainText(), exampleSeed);
|
||||
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
triggerAction("actionEntryTotp");
|
||||
|
||||
TotpDialog* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
|
||||
QLabel* totpLabel = totpDialog->findChild<QLabel*>("totpLabel");
|
||||
auto* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
|
||||
auto* totpLabel = totpDialog->findChild<QLabel*>("totpLabel");
|
||||
|
||||
QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp());
|
||||
}
|
||||
@ -755,16 +782,16 @@ void TestGui::testSearch()
|
||||
// Add canned entries for consistent testing
|
||||
Q_UNUSED(addCannedEntries());
|
||||
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
|
||||
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
QVERIFY(searchWidget->isEnabled());
|
||||
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
QVERIFY(entryView->isVisible());
|
||||
|
||||
QAction* clearButton = searchWidget->findChild<QAction*>("clearIcon");
|
||||
auto* clearButton = searchWidget->findChild<QAction*>("clearIcon");
|
||||
QVERIFY(!clearButton->isVisible());
|
||||
|
||||
// Enter search
|
||||
@ -801,7 +828,7 @@ void TestGui::testSearch()
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
||||
QTRY_VERIFY(entryView->hasFocus());
|
||||
// Restore focus and search text selection
|
||||
QTest::keyClick(m_mainWindow, Qt::Key_F, Qt::ControlModifier);
|
||||
QTest::keyClick(m_mainWindow.data(), Qt::Key_F, Qt::ControlModifier);
|
||||
QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING"));
|
||||
// Ensure Down focuses on entry view when search text is selected
|
||||
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
||||
@ -862,7 +889,7 @@ void TestGui::testSearch()
|
||||
QCOMPARE(entry->title(), origTitle.append("_edited"));
|
||||
|
||||
// Cancel search, should return to normal view
|
||||
QTest::keyClick(m_mainWindow, Qt::Key_Escape);
|
||||
QTest::keyClick(m_mainWindow.data(), Qt::Key_Escape);
|
||||
QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
}
|
||||
|
||||
@ -871,10 +898,10 @@ void TestGui::testDeleteEntry()
|
||||
// Add canned entries for consistent testing
|
||||
Q_UNUSED(addCannedEntries());
|
||||
|
||||
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
|
||||
auto* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
|
||||
QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
@ -934,7 +961,7 @@ void TestGui::testDeleteEntry()
|
||||
|
||||
void TestGui::testCloneEntry()
|
||||
{
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 1);
|
||||
|
||||
@ -944,8 +971,8 @@ void TestGui::testCloneEntry()
|
||||
|
||||
triggerAction("actionEntryClone");
|
||||
|
||||
CloneDialog* cloneDialog = m_dbWidget->findChild<CloneDialog*>("CloneDialog");
|
||||
QDialogButtonBox* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* cloneDialog = m_dbWidget->findChild<CloneDialog*>("CloneDialog");
|
||||
auto* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(cloneButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
@ -956,11 +983,11 @@ void TestGui::testCloneEntry()
|
||||
|
||||
void TestGui::testEntryPlaceholders()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@ -973,14 +1000,14 @@ void TestGui::testEntryPlaceholders()
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
|
||||
// Add entry "test" and confirm added
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
QLineEdit* usernameEdit = editEntryWidget->findChild<QLineEdit*>("usernameEdit");
|
||||
QTest::keyClicks(usernameEdit, "john");
|
||||
QLineEdit* urlEdit = editEntryWidget->findChild<QLineEdit*>("urlEdit");
|
||||
QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
@ -1000,8 +1027,8 @@ void TestGui::testEntryPlaceholders()
|
||||
|
||||
void TestGui::testDragAndDropEntry()
|
||||
{
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
QAbstractItemModel* groupModel = groupView->model();
|
||||
|
||||
QModelIndex sourceIndex = entryView->model()->index(0, 1);
|
||||
@ -1029,11 +1056,7 @@ void TestGui::testDragAndDropGroup()
|
||||
|
||||
// dropping parent on child is supposed to fail
|
||||
dragAndDropGroup(groupModel->index(0, 0, rootIndex),
|
||||
groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)),
|
||||
-1,
|
||||
false,
|
||||
"NewDatabase",
|
||||
0);
|
||||
groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)), -1, false, "NewDatabase", 0);
|
||||
|
||||
dragAndDropGroup(groupModel->index(1, 0, rootIndex), rootIndex, 0, true, "NewDatabase", 0);
|
||||
|
||||
@ -1063,6 +1086,7 @@ void TestGui::testSaveAs()
|
||||
|
||||
fileInfo.refresh();
|
||||
QCOMPARE(fileInfo.lastModified(), lastModified);
|
||||
tmpFile.remove();
|
||||
}
|
||||
|
||||
void TestGui::testSave()
|
||||
@ -1123,7 +1147,7 @@ void TestGui::testKeePass1Import()
|
||||
// Close the KeePass1 Database
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
QApplication::processEvents();
|
||||
}
|
||||
|
||||
void TestGui::testDatabaseLocking()
|
||||
@ -1135,13 +1159,13 @@ void TestGui::testDatabaseLocking()
|
||||
|
||||
QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]");
|
||||
|
||||
QAction* actionDatabaseMerge = m_mainWindow->findChild<QAction*>("actionDatabaseMerge", Qt::FindChildrenRecursively);
|
||||
auto* actionDatabaseMerge = m_mainWindow->findChild<QAction*>("actionDatabaseMerge", Qt::FindChildrenRecursively);
|
||||
QCOMPARE(actionDatabaseMerge->isEnabled(), false);
|
||||
QAction* actionDatabaseSave = m_mainWindow->findChild<QAction*>("actionDatabaseSave", Qt::FindChildrenRecursively);
|
||||
auto* actionDatabaseSave = m_mainWindow->findChild<QAction*>("actionDatabaseSave", Qt::FindChildrenRecursively);
|
||||
QCOMPARE(actionDatabaseSave->isEnabled(), false);
|
||||
|
||||
QWidget* dbWidget = m_tabWidget->currentDatabaseWidget();
|
||||
QWidget* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
|
||||
auto* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
|
||||
QWidget* editPassword = unlockDatabaseWidget->findChild<QLineEdit*>("editPassword");
|
||||
QVERIFY(editPassword);
|
||||
|
||||
@ -1162,11 +1186,11 @@ void TestGui::testDragAndDropKdbxFiles()
|
||||
QMimeData badMimeData;
|
||||
badMimeData.setUrls({QUrl::fromLocalFile(badDatabaseFilePath)});
|
||||
QDragEnterEvent badDragEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &badDragEvent);
|
||||
qApp->notify(m_mainWindow.data(), &badDragEvent);
|
||||
QCOMPARE(badDragEvent.isAccepted(), false);
|
||||
|
||||
QDropEvent badDropEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &badDropEvent);
|
||||
qApp->notify(m_mainWindow.data(), &badDropEvent);
|
||||
QCOMPARE(badDropEvent.isAccepted(), false);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
@ -1175,20 +1199,19 @@ void TestGui::testDragAndDropKdbxFiles()
|
||||
QMimeData goodMimeData;
|
||||
goodMimeData.setUrls({QUrl::fromLocalFile(goodDatabaseFilePath)});
|
||||
QDragEnterEvent goodDragEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &goodDragEvent);
|
||||
qApp->notify(m_mainWindow.data(), &goodDragEvent);
|
||||
QCOMPARE(goodDragEvent.isAccepted(), true);
|
||||
|
||||
QDropEvent goodDropEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &goodDropEvent);
|
||||
qApp->notify(m_mainWindow.data(), &goodDropEvent);
|
||||
QCOMPARE(goodDropEvent.isAccepted(), true);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1);
|
||||
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
QTRY_COMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
}
|
||||
|
||||
void TestGui::testTrayRestoreHide()
|
||||
@ -1197,29 +1220,20 @@ void TestGui::testTrayRestoreHide()
|
||||
QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test...");
|
||||
}
|
||||
|
||||
QSystemTrayIcon* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
|
||||
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
|
||||
QVERIFY(m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(!m_mainWindow->isVisible());
|
||||
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(m_mainWindow->isVisible());
|
||||
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(!m_mainWindow->isVisible());
|
||||
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(m_mainWindow->isVisible());
|
||||
}
|
||||
|
||||
void TestGui::cleanupTestCase()
|
||||
{
|
||||
delete m_mainWindow;
|
||||
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||
}
|
||||
|
||||
int TestGui::addCannedEntries()
|
||||
@ -1227,17 +1241,17 @@ int TestGui::addCannedEntries()
|
||||
int entries_added = 0;
|
||||
|
||||
// Find buttons
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
QLineEdit* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
auto* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
|
||||
// Add entry "test" and confirm added
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
++entries_added;
|
||||
|
||||
@ -1274,7 +1288,7 @@ void TestGui::checkDatabase(QString dbFileName)
|
||||
|
||||
void TestGui::triggerAction(const QString& name)
|
||||
{
|
||||
QAction* action = m_mainWindow->findChild<QAction*>(name);
|
||||
auto* action = m_mainWindow->findChild<QAction*>(name);
|
||||
QVERIFY(action);
|
||||
QVERIFY(action->isEnabled());
|
||||
action->trigger();
|
||||
@ -1312,5 +1326,3 @@ void TestGui::clickIndex(const QModelIndex& index,
|
||||
{
|
||||
QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestGui)
|
||||
|
@ -19,17 +19,18 @@
|
||||
#ifndef KEEPASSX_TESTGUI_H
|
||||
#define KEEPASSX_TESTGUI_H
|
||||
|
||||
#include "TemporaryFile.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
class Database;
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
class QAbstractItemView;
|
||||
class MainWindow;
|
||||
|
||||
class TestGui : public QObject
|
||||
{
|
||||
@ -84,12 +85,12 @@ private:
|
||||
Qt::MouseButton button,
|
||||
Qt::KeyboardModifiers stateKey = 0);
|
||||
|
||||
QPointer<MainWindow> m_mainWindow;
|
||||
QScopedPointer<MainWindow> m_mainWindow;
|
||||
QPointer<DatabaseTabWidget> m_tabWidget;
|
||||
QPointer<DatabaseWidget> m_dbWidget;
|
||||
QPointer<Database> m_db;
|
||||
QByteArray m_dbData;
|
||||
TemporaryFile m_dbFile;
|
||||
QScopedPointer<QTemporaryFile> m_dbFile;
|
||||
QString m_dbFileName;
|
||||
QString m_dbFilePath;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user