CLI : basic entry manipulation commands. (#919)

* CLI : basic entry manipulation commands.

* Code review.
This commit is contained in:
louib 2017-09-06 09:14:41 -04:00 committed by GitHub
parent 1220b7d501
commit 6e1fd0694f
16 changed files with 661 additions and 9 deletions

150
src/cli/Add.cpp Normal file
View File

@ -0,0 +1,150 @@
/*
* 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 <cstdlib>
#include <stdio.h>
#include "Add.h"
#include <QCommandLineParser>
#include <QTextStream>
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/PasswordGenerator.h"
Add::Add()
{
this->name = QString("add");
this->description = QObject::tr("Add a new entry to a database.");
}
Add::~Add()
{
}
int Add::execute(QStringList arguments)
{
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(this->description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
QCommandLineOption keyFile(QStringList() << "k"
<< "key-file",
QObject::tr("Key file of the database."),
QObject::tr("path"));
parser.addOption(keyFile);
QCommandLineOption username(QStringList() << "u"
<< "username",
QObject::tr("Username for the entry."),
QObject::tr("username"));
parser.addOption(username);
QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
parser.addOption(url);
QCommandLineOption prompt(QStringList() << "p"
<< "password-prompt",
QObject::tr("Prompt for the entry's password."));
parser.addOption(prompt);
QCommandLineOption generate(QStringList() << "g"
<< "generate",
QObject::tr("Generate a password for the entry."));
parser.addOption(generate);
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.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add");
return EXIT_FAILURE;
}
QString databasePath = args.at(0);
QString entryPath = args.at(1);
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile));
if (db == nullptr) {
return EXIT_FAILURE;
}
// Validating the password length here, before we actually create
// the entry.
QString passwordLength = parser.value(length);
if (!passwordLength.isEmpty() && !passwordLength.toInt()) {
qCritical("Invalid value for password length %s.", qPrintable(passwordLength));
return EXIT_FAILURE;
}
Entry* entry = db->rootGroup()->addEntryWithPath(entryPath);
if (!entry) {
qCritical("Could not create entry with path %s.", qPrintable(entryPath));
return EXIT_FAILURE;
}
if (!parser.value("username").isEmpty()) {
entry->setUsername(parser.value("username"));
}
if (!parser.value("url").isEmpty()) {
entry->setUrl(parser.value("url"));
}
if (parser.isSet(prompt)) {
outputTextStream << "Enter password for new entry: ";
outputTextStream.flush();
QString password = Utils::getPassword();
entry->setPassword(password);
} else if (parser.isSet(generate)) {
PasswordGenerator passwordGenerator;
if (passwordLength.isEmpty()) {
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
} else {
passwordGenerator.setLength(passwordLength.toInt());
}
passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters |
PasswordGenerator::Numbers);
QString password = passwordGenerator.generatePassword();
entry->setPassword(password);
}
QString errorMessage = db->saveToFile(databasePath);
if (!errorMessage.isEmpty()) {
qCritical("Writing the database failed %s.", qPrintable(errorMessage));
return EXIT_FAILURE;
}
outputTextStream << "Successfully added entry " << entry->title() << "." << endl;
return EXIT_SUCCESS;
}

31
src/cli/Add.h Normal file
View File

@ -0,0 +1,31 @@
/*
* 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 KEEPASSXC_ADD_H
#define KEEPASSXC_ADD_H
#include "Command.h"
class Add : public Command
{
public:
Add();
~Add();
int execute(QStringList arguments);
};
#endif // KEEPASSXC_ADD_H

View File

@ -14,10 +14,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(cli_SOURCES
Add.cpp
Add.h
Clip.cpp
Clip.h
Command.cpp
Command.h
Edit.cpp
Edit.h
EntropyMeter.cpp
EntropyMeter.h
Extract.cpp
@ -28,6 +32,8 @@ set(cli_SOURCES
Locate.h
Merge.cpp
Merge.h
Remove.cpp
Remove.h
Show.cpp
Show.h)

View File

@ -22,12 +22,15 @@
#include "Command.h"
#include "Add.h"
#include "Edit.h"
#include "Clip.h"
#include "EntropyMeter.h"
#include "Extract.h"
#include "List.h"
#include "Locate.h"
#include "Merge.h"
#include "Remove.h"
#include "Show.h"
QMap<QString, Command*> commands;
@ -56,12 +59,15 @@ QString Command::getDescriptionLine()
void populateCommands()
{
if (commands.isEmpty()) {
commands.insert(QString("add"), new Add());
commands.insert(QString("clip"), new Clip());
commands.insert(QString("edit"), new Edit());
commands.insert(QString("entropy-meter"), new EntropyMeter());
commands.insert(QString("extract"), new Extract());
commands.insert(QString("locate"), new Locate());
commands.insert(QString("ls"), new List());
commands.insert(QString("merge"), new Merge());
commands.insert(QString("rm"), new Remove());
commands.insert(QString("show"), new Show());
}
}

168
src/cli/Edit.cpp Normal file
View File

@ -0,0 +1,168 @@
/*
* 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 <cstdlib>
#include <stdio.h>
#include "Edit.h"
#include <QCommandLineParser>
#include <QTextStream>
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/PasswordGenerator.h"
Edit::Edit()
{
this->name = QString("edit");
this->description = QObject::tr("Edit an entry.");
}
Edit::~Edit()
{
}
int Edit::execute(QStringList arguments)
{
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(this->description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
QCommandLineOption keyFile(QStringList() << "k"
<< "key-file",
QObject::tr("Key file of the database."),
QObject::tr("path"));
parser.addOption(keyFile);
QCommandLineOption username(QStringList() << "u"
<< "username",
QObject::tr("Username for the entry."),
QObject::tr("username"));
parser.addOption(username);
QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
parser.addOption(url);
QCommandLineOption title(QStringList() << "t"
<< "title",
QObject::tr("Title for the entry."),
QObject::tr("title"));
parser.addOption(title);
QCommandLineOption prompt(QStringList() << "p"
<< "password-prompt",
QObject::tr("Prompt for the entry's password."));
parser.addOption(prompt);
QCommandLineOption generate(QStringList() << "g"
<< "generate",
QObject::tr("Generate a password for the entry."));
parser.addOption(generate);
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.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit");
return EXIT_FAILURE;
}
QString databasePath = args.at(0);
QString entryPath = args.at(1);
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile));
if (db == nullptr) {
return EXIT_FAILURE;
}
QString passwordLength = parser.value(length);
if (!passwordLength.isEmpty() && !passwordLength.toInt()) {
qCritical("Invalid value for password length %s.", qPrintable(passwordLength));
return EXIT_FAILURE;
}
Entry* entry = db->rootGroup()->findEntryByPath(entryPath);
if (!entry) {
qCritical("Could not find entry with path %s.", qPrintable(entryPath));
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));
return EXIT_FAILURE;
}
entry->beginUpdate();
if (!parser.value("title").isEmpty()) {
entry->setTitle(parser.value("title"));
}
if (!parser.value("username").isEmpty()) {
entry->setUsername(parser.value("username"));
}
if (!parser.value("url").isEmpty()) {
entry->setUrl(parser.value("url"));
}
if (parser.isSet(prompt)) {
outputTextStream << "Enter new password for entry: ";
outputTextStream.flush();
QString password = Utils::getPassword();
entry->setPassword(password);
} else if (parser.isSet(generate)) {
PasswordGenerator passwordGenerator;
if (passwordLength.isEmpty()) {
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
} else {
passwordGenerator.setLength(passwordLength.toInt());
}
passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters |
PasswordGenerator::Numbers);
QString password = passwordGenerator.generatePassword();
entry->setPassword(password);
}
entry->endUpdate();
QString errorMessage = db->saveToFile(databasePath);
if (!errorMessage.isEmpty()) {
qCritical("Writing the database failed %s.", qPrintable(errorMessage));
return EXIT_FAILURE;
}
outputTextStream << "Successfully edited entry " << entry->title() << "." << endl;
return EXIT_SUCCESS;
}

31
src/cli/Edit.h Normal file
View File

@ -0,0 +1,31 @@
/*
* 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 KEEPASSXC_EDIT_H
#define KEEPASSXC_EDIT_H
#include "Command.h"
class Edit : public Command
{
public:
Edit();
~Edit();
int execute(QStringList arguments);
};
#endif // KEEPASSXC_EDIT_H

106
src/cli/Remove.cpp Normal file
View File

@ -0,0 +1,106 @@
/*
* 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 <cstdlib>
#include <stdio.h>
#include "Remove.h"
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QStringList>
#include <QTextStream>
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
Remove::Remove()
{
this->name = QString("rm");
this->description = QString("Remove an entry from the database.");
}
Remove::~Remove()
{
}
int Remove::execute(QStringList arguments)
{
QTextStream outputTextStream(stdout, 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",
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.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm");
return EXIT_FAILURE;
}
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
if (db == nullptr) {
return EXIT_FAILURE;
}
return this->removeEntry(db, args.at(0), args.at(1));
}
int Remove::removeEntry(Database* database, QString databasePath, QString entryPath)
{
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) {
qCritical("Entry %s not found.", qPrintable(entryPath));
return EXIT_FAILURE;
}
QString entryTitle = entry->title();
bool recycled = true;
if (Tools::hasChild(database->metadata()->recycleBin(), entry) || !database->metadata()->recycleBinEnabled()) {
delete entry;
recycled = false;
} else {
database->recycleEntry(entry);
};
QString errorMessage = database->saveToFile(databasePath);
if (!errorMessage.isEmpty()) {
qCritical("Unable to save database to file : %s", qPrintable(errorMessage));
return EXIT_FAILURE;
}
if (recycled) {
outputTextStream << "Successfully recycled entry " << entryTitle << "." << endl;
} else {
outputTextStream << "Successfully deleted entry " << entryTitle << "." << endl;
}
return EXIT_SUCCESS;
}

34
src/cli/Remove.h Normal file
View File

@ -0,0 +1,34 @@
/*
* 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 KEEPASSXC_REMOVE_H
#define KEEPASSXC_REMOVE_H
#include "Command.h"
#include "core/Database.h"
class Remove : public Command
{
public:
Remove();
~Remove();
int execute(QStringList arguments);
int removeEntry(Database* database, QString databasePath, QString entryPath);
};
#endif // KEEPASSXC_REMOVE_H

View File

@ -9,13 +9,19 @@ keepassxc-cli \- command line interface for the \fBKeePassXC\fP password manager
.I command
.SH DESCRIPTION
\fBkeepassxc-cli\fP is the command line interface for the \fBKeePassXC\fP password manager. It provides the ability of listing the entries of a database, displaying the contents of an entry and many more, directly from the command line.
\fBkeepassxc-cli\fP is the command line interface for the \fBKeePassXC\fP password manager. It provides the ability to query and modify the entries of a KeePass database, directly from the command line.
.SH COMMANDS
.IP "add [options] <database> <entry>"
Adds a new entry to a database. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option).
.IP "clip [options] <database> <entry> [timeout]"
Copies the password of a database entry to the clipboard. If multiple entries with the same name exist in different groups, only the password for the first one is going to be copied. For copying the password of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name. Optionally, a timeout in seconds can be specified to automatically clear the clipboard.
.IP "edit [options] <database> <entry>"
Edits a database entry. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option).
.IP "entropy-meter [-a pwd1 pwd2 ...]"
Calculates the entropy of a single, or multiple passwords specified using the \fI-a\fP option. If no passwords are specified, the program will run in interactive mode and prompt the user to enter a password.
@ -31,26 +37,59 @@ Lists the contents of a group in a database. If no group is specified, it will d
.IP "merge [options] <database1> <database2>"
Merges two databases together. The first database file is going to be replaced by the result of the merge, for that reason it is advisable to keep a backup of the two database files before attempting a merge. In the case that both databases make use of the same credentials, the \fI--same-credentials\fP or \fI-s\fP option can be used.
.IP "rm [options] <database> <entry>"
Removes an entry from a database. If the database has a recycle bin, the entry will be moved there. If the entry is already in the recycle bin, it will be removed permanently.
.IP "show [options] <database> <entry>"
Shows the title, username, password, URL and notes of a database entry. Regarding the occurrence of multiple entries with the same name in different groups, everything stated in the \fIclip\fP command section also applies here.
.SH OPTIONS
.SS "General options"
.IP "-k, --key-file <path>"
Specifies a path to a key file for unlocking the database. In a merge operation this option is used to specify the key file path for the first database.
.IP "-f, --key-file-from <path>"
Specifies a path to a key file for the second database in a merge operation.
.IP "-s, --same-credentials"
Tells the program to use the same credentials for unlocking both of the database files in a merge operation.
.IP "-h, --help"
Displays help information.
.IP "-v, --version"
Shows the program version.
.SS "Merge options"
.IP "-f, --key-file-from <path>"
Path of the key file for the second database.
.IP "-s, --same-credentials"
Use the same credentials for unlocking both database.
.SS "Add and edit options"
.IP "-u, --username <username>"
Specify the username of the entry.
.IP "--url <url>"
Specify the URL of the entry.
.IP "-p, --password-prompt"
Use a password prompt for the entry's password.
.IP "-g, --generate"
Generate a new password for the entry.
.IP "-l, --password-length"
Specify the length of the password to generate.
.SS "Edit options"
.IP "-t, --title <title>"
Specify the title of the entry.
.SH REPORTING BUGS
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues.

View File

@ -949,3 +949,32 @@ QStringList Group::locate(QString locateTerm, QString currentPath)
return response;
}
Entry* Group::addEntryWithPath(QString entryPath)
{
Q_ASSERT(!entryPath.isNull());
if (this->findEntryByPath(entryPath)) {
return nullptr;
}
QStringList groups = entryPath.split("/");
QString entryTitle = groups.takeLast();
QString groupPath = groups.join("/");
if (groupPath.isNull()) {
groupPath = QString("");
}
Q_ASSERT(!groupPath.isNull());
Group* group = this->findGroupByPath(groupPath);
if (!group) {
return nullptr;
}
Entry* entry = new Entry();
entry->setTitle(entryTitle);
entry->setUuid(Uuid::random());
entry->setGroup(group);
return entry;
}

View File

@ -85,6 +85,7 @@ public:
Entry* findEntryByPath(QString entryPath, QString basePath = QString(""));
Group* findGroupByPath(QString groupPath, QString basePath = QString("/"));
QStringList locate(QString locateTerm, QString currentPath = QString("/"));
Entry* addEntryWithPath(QString entryPath);
void setUuid(const Uuid& uuid);
void setName(const QString& name);
void setNotes(const QString& notes);

View File

@ -58,6 +58,8 @@ public:
QString generatePassword() const;
int getbits() const;
static const int DefaultLength = 16;
private:
QVector<PasswordGroup> passwordGroups() const;
int numCharClasses() const;

View File

@ -98,7 +98,7 @@ void PasswordGeneratorWidget::loadSettings()
m_ui->checkBoxExtASCII->setChecked(config()->get("generator/EASCII", false).toBool());
m_ui->checkBoxExcludeAlike->setChecked(config()->get("generator/ExcludeAlike", true).toBool());
m_ui->checkBoxEnsureEvery->setChecked(config()->get("generator/EnsureEvery", true).toBool());
m_ui->spinBoxLength->setValue(config()->get("generator/Length", 16).toInt());
m_ui->spinBoxLength->setValue(config()->get("generator/Length", PasswordGenerator::DefaultLength).toInt());
// Diceware config
m_ui->spinBoxWordCount->setValue(config()->get("generator/WordCount", 6).toInt());

View File

@ -56,7 +56,7 @@ void HttpPasswordGeneratorWidget::loadSettings()
m_ui->checkBoxExcludeAlike->setChecked(config()->get("Http/generator/ExcludeAlike", true).toBool());
m_ui->checkBoxEnsureEvery->setChecked(config()->get("Http/generator/EnsureEvery", true).toBool());
m_ui->spinBoxLength->setValue(config()->get("Http/generator/Length", 16).toInt());
m_ui->spinBoxLength->setValue(config()->get("Http/generator/Length", PasswordGenerator::DefaultLength).toInt());
}
void HttpPasswordGeneratorWidget::saveSettings()

View File

@ -698,3 +698,51 @@ void TestGroup::testLocate()
delete db;
}
void TestGroup::testAddEntryWithPath()
{
Database* db = new Database();
Group* group1 = new Group();
group1->setName("group1");
group1->setParent(db->rootGroup());
Group* group2 = new Group();
group2->setName("group2");
group2->setParent(group1);
Entry* entry = db->rootGroup()->addEntryWithPath("entry1");
QVERIFY(entry != nullptr);
QVERIFY(!entry->uuid().isNull());
entry = db->rootGroup()->addEntryWithPath("entry1");
QVERIFY(entry == nullptr);
entry = db->rootGroup()->addEntryWithPath("/entry1");
QVERIFY(entry == nullptr);
entry = db->rootGroup()->addEntryWithPath("entry2");
QVERIFY(entry != nullptr);
QVERIFY(entry->title() == "entry2");
QVERIFY(!entry->uuid().isNull());
entry = db->rootGroup()->addEntryWithPath("/entry3");
QVERIFY(entry != nullptr);
QVERIFY(entry->title() == "entry3");
QVERIFY(!entry->uuid().isNull());
entry = db->rootGroup()->addEntryWithPath("/group1/entry4");
QVERIFY(entry != nullptr);
QVERIFY(entry->title() == "entry4");
QVERIFY(!entry->uuid().isNull());
entry = db->rootGroup()->addEntryWithPath("/group1/group2/entry5");
QVERIFY(entry != nullptr);
QVERIFY(entry->title() == "entry5");
QVERIFY(!entry->uuid().isNull());
entry = db->rootGroup()->addEntryWithPath("/group1/invalid_group/entry6");
QVERIFY(entry == nullptr);
delete db;
}

View File

@ -39,6 +39,7 @@ private slots:
void testFindGroupByPath();
void testPrint();
void testLocate();
void testAddEntryWithPath();
};
#endif // KEEPASSX_TESTGROUP_H