Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a best option to clip to copy a password of the best match #4489

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.