Add --username option to Clip command. (#3947)

* make Clip accept an attribute name

This allows users to copy arbitrary attributes (e.g. username, notes,
URL) to the clipboard in addition to the password and TOTP values.

* update Clip manpage

* Add findAttributes to CLI utils

* Use case-insensitive search in Show command.

* Use case-insensitive search in Clip command.

Co-authored-by: louib <L0U13@protonmail.com>
This commit is contained in:
James Ring 2020-01-30 12:46:48 -08:00 committed by GitHub
parent 06e0f38523
commit 71a39c37ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 26 deletions

View file

@ -17,7 +17,6 @@
#include <chrono>
#include <cstdlib>
#include <stdio.h>
#include <thread>
#include "Clip.h"
@ -28,14 +27,23 @@
#include "core/Entry.h"
#include "core/Group.h"
const QCommandLineOption Clip::TotpOption = QCommandLineOption(QStringList() << "t"
<< "totp",
QObject::tr("Copy the current TOTP to the clipboard."));
const QCommandLineOption Clip::AttributeOption = QCommandLineOption(
QStringList() << "a"
<< "attribute",
QObject::tr("Copy the given attribute to the clipboard. Defaults to \"password\" if not specified."),
"attr",
"password");
const QCommandLineOption Clip::TotpOption =
QCommandLineOption(QStringList() << "t"
<< "totp",
QObject::tr("Copy the current TOTP to the clipboard (equivalent to \"-a totp\")."));
Clip::Clip()
{
name = QString("clip");
description = QObject::tr("Copy an entry's password to the clipboard.");
description = QObject::tr("Copy an entry's attribute to the clipboard.");
options.append(Clip::AttributeOption);
options.append(Clip::TotpOption);
positionalArguments.append(
{QString("entry"), QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"), QString("")});
@ -51,7 +59,6 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
if (args.size() == 3) {
timeout = args.at(2);
}
bool clipTotp = parser->isSet(Clip::TotpOption);
TextStream errorTextStream(Utils::STDERR);
int timeoutSeconds = 0;
@ -70,16 +77,39 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
return EXIT_FAILURE;
}
if (parser->isSet(AttributeOption) && parser->isSet(TotpOption)) {
errorTextStream << QObject::tr("ERROR: Please specify one of --attribute or --totp, not both.") << endl;
return EXIT_FAILURE;
}
QString selectedAttribute = parser->value(AttributeOption);
QString value;
if (clipTotp) {
bool found = false;
if (parser->isSet(TotpOption) || selectedAttribute == "totp") {
if (!entry->hasTotp()) {
errorTextStream << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << endl;
return EXIT_FAILURE;
}
found = true;
value = entry->totp();
} else {
value = entry->password();
QStringList attrs = Utils::findAttributes(*entry->attributes(), selectedAttribute);
if (attrs.size() > 1) {
errorTextStream << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.")
.arg(selectedAttribute, QLocale().createSeparatedList(attrs))
<< endl;
return EXIT_FAILURE;
} else if (attrs.size() == 1) {
found = true;
selectedAttribute = attrs[0];
value = entry->attributes()->value(selectedAttribute);
}
}
if (!found) {
outputTextStream << QObject::tr("Attribute \"%1\" not found.").arg(selectedAttribute) << endl;
return EXIT_FAILURE;
}
int exitCode = Utils::clipText(value);
@ -87,11 +117,7 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
return exitCode;
}
if (clipTotp) {
outputTextStream << QObject::tr("Entry's current TOTP copied to the clipboard!") << endl;
} else {
outputTextStream << QObject::tr("Entry's password copied to the clipboard!") << endl;
}
outputTextStream << QObject::tr("Entry's \"%1\" attribute copied to the clipboard!").arg(selectedAttribute) << endl;
if (!timeoutSeconds) {
return exitCode;

View file

@ -27,6 +27,7 @@ public:
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
static const QCommandLineOption AttributeOption;
static const QCommandLineOption TotpOption;
};

View file

@ -27,6 +27,8 @@
#include "core/Global.h"
#include "core/Group.h"
#include <QLocale>
const QCommandLineOption Show::TotpOption = QCommandLineOption(QStringList() << "t"
<< "totp",
QObject::tr("Show the entry's current TOTP."));
@ -79,25 +81,33 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
// If no attributes specified, output the default attribute set.
bool showDefaultAttributes = attributes.isEmpty() && !showTotp;
if (attributes.isEmpty() && !showTotp) {
if (showDefaultAttributes) {
attributes = EntryAttributes::DefaultAttributes;
}
// Iterate over the attributes and output them line-by-line.
bool sawUnknownAttribute = false;
bool encounteredError = false;
for (const QString& attributeName : asConst(attributes)) {
if (!entry->attributes()->contains(attributeName)) {
sawUnknownAttribute = true;
QStringList attrs = Utils::findAttributes(*entry->attributes(), attributeName);
if (attrs.isEmpty()) {
encounteredError = true;
errorTextStream << QObject::tr("ERROR: unknown attribute %1.").arg(attributeName) << endl;
continue;
} else if (attrs.size() > 1) {
encounteredError = true;
errorTextStream << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.")
.arg(attributeName, QLocale().createSeparatedList(attrs))
<< endl;
continue;
}
QString canonicalName = attrs[0];
if (showDefaultAttributes) {
outputTextStream << attributeName << ": ";
outputTextStream << canonicalName << ": ";
}
if (entry->attributes()->isProtected(attributeName) && showDefaultAttributes && !showProtectedAttributes) {
if (entry->attributes()->isProtected(canonicalName) && showDefaultAttributes && !showProtectedAttributes) {
outputTextStream << "PROTECTED" << endl;
} else {
outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attributeName)) << endl;
outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(canonicalName)) << endl;
}
}
@ -105,5 +115,5 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
outputTextStream << entry->totp() << endl;
}
return sawUnknownAttribute ? EXIT_FAILURE : EXIT_SUCCESS;
return encounteredError ? EXIT_FAILURE : EXIT_SUCCESS;
}

View file

@ -331,4 +331,21 @@ namespace Utils
return result;
}
QStringList findAttributes(const EntryAttributes& attributes, const QString& name)
{
QStringList result;
if (attributes.hasKey(name)) {
result.append(name);
return result;
}
for (const QString& key : attributes.keys()) {
if (key.compare(name, Qt::CaseSensitivity::CaseInsensitive) == 0) {
result.append(key);
}
}
return result;
}
} // namespace Utils

View file

@ -20,6 +20,7 @@
#include "cli/TextStream.h"
#include "core/Database.h"
#include "core/EntryAttributes.h"
#include "keys/CompositeKey.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
@ -51,6 +52,14 @@ namespace Utils
QStringList splitCommandString(const QString& command);
/**
* If `attributes` contains an attribute named `name` (case-sensitive),
* returns a list containing only `name`. Otherwise, returns the list of
* all attribute names in `attributes` matching the given name
* (case-insensitive).
*/
QStringList findAttributes(const EntryAttributes& attributes, const QString& name);
namespace Test
{
void setNextPassword(const QString& password);