Skip to content

Commit

Permalink
Add a best option to CLI command clip (#4489)
Browse files Browse the repository at this point in the history
The best option copy the password from the best match if only one matching entry exists.

Adding clip best option documentation

Adding unit tests on the new clip --best option
  • Loading branch information
lerignoux authored and droidmonkey committed Sep 1, 2020
1 parent e538506 commit 8de2e12
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 1 deletion.
5 changes: 5 additions & 0 deletions docs/man/keepassxc-cli.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ The same password generation options as documented for the generate command can
Copies the current TOTP instead of the specified attribute to the clipboard.
Will report an error if no TOTP is configured for the entry.

*-b*, *--best*::
Try to find and copy to clipboard a unique entry matching the input (similar to *-locate*)
If a unique matching entry is found it will be copied to the clipboard.
If multiple entries are found they will be listed to refine the search. (no clip performed)

=== Create options
*-k*, *--set-key-file* <__path__>::
Set the key file for the database.
Expand Down
28 changes: 27 additions & 1 deletion src/cli/Clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Global.h"
#include "core/Group.h"

const QCommandLineOption Clip::AttributeOption = QCommandLineOption(
Expand All @@ -39,12 +40,18 @@ const QCommandLineOption Clip::TotpOption =
<< "totp",
QObject::tr("Copy the current TOTP to the clipboard (equivalent to \"-a totp\")."));

const QCommandLineOption Clip::BestMatchOption = QCommandLineOption(
QStringList() << "b"
<< "best-match",
QObject::tr("Try to find the unique entry matching, will fail and display the list of matches otherwise."));

Clip::Clip()
{
name = QString("clip");
description = QObject::tr("Copy an entry's attribute to the clipboard.");
options.append(Clip::AttributeOption);
options.append(Clip::TotpOption);
options.append(Clip::BestMatchOption);
positionalArguments.append(
{QString("entry"), QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"), QString("")});
optionalArguments.append(
Expand All @@ -57,12 +64,31 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
auto& err = Utils::STDERR;

const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1);
QString bestEntryPath;

QString timeout;
if (args.size() == 3) {
timeout = args.at(2);
}

if (parser->isSet(Clip::BestMatchOption)) {
QStringList results = database->rootGroup()->locate(args.at(1));
if (results.count() > 1) {
err << QObject::tr("Multiple entries matching:") << endl;
for (const QString& result : asConst(results)) {
err << result << endl;
}
return EXIT_FAILURE;
} else {
bestEntryPath = (results.isEmpty()) ? args.at(1) : results[0];
out << QObject::tr("Matching \"%1\" entry used.").arg(bestEntryPath) << endl;
}
} else {
bestEntryPath = args.at(1);
}

const QString& entryPath = bestEntryPath;

int timeoutSeconds = 0;
if (!timeout.isEmpty() && timeout.toInt() <= 0) {
err << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl;
Expand Down
1 change: 1 addition & 0 deletions src/cli/Clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Clip : public DatabaseCommand

static const QCommandLineOption AttributeOption;
static const QCommandLineOption TotpOption;
static const QCommandLineOption BestMatchOption;
};

#endif // KEEPASSXC_CLIP_H
15 changes: 15 additions & 0 deletions tests/TestCli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ void TestCli::init()
m_dbFile2.reset(new TemporaryFile());
m_dbFile2->copyFromFile(file.arg("NewDatabase2.kdbx"));

m_dbFileMulti.reset(new TemporaryFile());
m_dbFileMulti->copyFromFile(file.arg("NewDatabaseMulti.kdbx"));

m_xmlFile.reset(new TemporaryFile());
m_xmlFile->copyFromFile(file.arg("NewDatabase.xml"));

Expand Down Expand Up @@ -115,6 +118,7 @@ void TestCli::cleanup()
{
m_dbFile.reset();
m_dbFile2.reset();
m_dbFileMulti.reset();
m_keyFileProtectedDbFile.reset();
m_keyFileProtectedNoPasswordDbFile.reset();
m_yubiKeyProtectedDbFile.reset();
Expand Down Expand Up @@ -504,6 +508,17 @@ void TestCli::testClip()
setInput("a");
execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"});
QVERIFY(m_stderr->readAll().contains("ERROR: Please specify one of --attribute or --totp, not both.\n"));

// Best option
setInput("a");
execCmd(clipCmd, {"clip", m_dbFileMulti->fileName(), "Multi", "-b"});
QByteArray errorChoices = m_stderr->readAll();
QVERIFY(errorChoices.contains("Multi Entry 1"));
QVERIFY(errorChoices.contains("Multi Entry 2"));

setInput("a");
execCmd(clipCmd, {"clip", m_dbFileMulti->fileName(), "Entry 2", "-b"});
QTRY_COMPARE(clipboard->text(), QString("Password2"));
}

void TestCli::testCreate()
Expand Down
1 change: 1 addition & 0 deletions tests/TestCli.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ private slots:
private:
QScopedPointer<TemporaryFile> m_dbFile;
QScopedPointer<TemporaryFile> m_dbFile2;
QScopedPointer<TemporaryFile> m_dbFileMulti;
QScopedPointer<TemporaryFile> m_xmlFile;
QScopedPointer<TemporaryFile> m_keyFileProtectedDbFile;
QScopedPointer<TemporaryFile> m_keyFileProtectedNoPasswordDbFile;
Expand Down
Binary file added tests/data/NewDatabaseMulti.kdbx
Binary file not shown.

0 comments on commit 8de2e12

Please sign in to comment.