mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-22 23:49:58 -05:00
Add create command to keepassxc-cli (#2540)
* Add tests for CLI::Create
This commit is contained in:
parent
a070f1bce7
commit
8ac9d0a131
@ -16,6 +16,7 @@
|
||||
set(cli_SOURCES
|
||||
Add.cpp
|
||||
Clip.cpp
|
||||
Create.cpp
|
||||
Command.cpp
|
||||
Diceware.cpp
|
||||
Edit.cpp
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include "Add.h"
|
||||
#include "Clip.h"
|
||||
#include "Create.h"
|
||||
#include "Diceware.h"
|
||||
#include "Edit.h"
|
||||
#include "Estimate.h"
|
||||
@ -69,6 +70,7 @@ void populateCommands()
|
||||
if (commands.isEmpty()) {
|
||||
commands.insert(QString("add"), new Add());
|
||||
commands.insert(QString("clip"), new Clip());
|
||||
commands.insert(QString("create"), new Create());
|
||||
commands.insert(QString("diceware"), new Diceware());
|
||||
commands.insert(QString("edit"), new Edit());
|
||||
commands.insert(QString("estimate"), new Estimate());
|
||||
|
175
src/cli/Create.cpp
Normal file
175
src/cli/Create.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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 <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "Create.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "keys/Key.h"
|
||||
|
||||
Create::Create()
|
||||
{
|
||||
name = QString("create");
|
||||
description = QObject::tr("Create a new database.");
|
||||
}
|
||||
|
||||
Create::~Create()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a database file using the command line. A key file and/or
|
||||
* password can be specified to encrypt the password. If none is
|
||||
* specified the function will fail.
|
||||
*
|
||||
* If a key file is specified but it can't be loaded, the function will
|
||||
* fail.
|
||||
*
|
||||
* If the database is being saved in a non existant directory, the
|
||||
* function will fail.
|
||||
*
|
||||
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure
|
||||
*/
|
||||
int Create::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
|
||||
parser.setApplicationDescription(description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
parser.addOption(Command::KeyFileOption);
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() < 1) {
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString databaseFilename = args.at(0);
|
||||
if (QFileInfo::exists(databaseFilename)) {
|
||||
err << QObject::tr("File %1 already exists.").arg(databaseFilename) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
auto key = QSharedPointer<CompositeKey>::create();
|
||||
|
||||
auto password = getPasswordFromStdin();
|
||||
if (!password.isNull()) {
|
||||
key->addKey(password);
|
||||
}
|
||||
|
||||
QSharedPointer<FileKey> fileKey;
|
||||
if(parser.isSet(Command::KeyFileOption)) {
|
||||
if (!loadFileKey(parser.value(Command::KeyFileOption), fileKey)) {
|
||||
err << QObject::tr("Loading the key file failed") << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileKey.isNull()) {
|
||||
key->addKey(fileKey);
|
||||
}
|
||||
|
||||
if (key->isEmpty()) {
|
||||
err << QObject::tr("No key is set. Aborting database creation.") << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Database db;
|
||||
db.setKey(key);
|
||||
|
||||
QString errorMessage;
|
||||
if (!db.save(databaseFilename, &errorMessage, true, false)) {
|
||||
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << QObject::tr("Successfully created new database.") << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read optional password from stdin.
|
||||
*
|
||||
* @return Pointer to the PasswordKey or null if passwordkey is skipped
|
||||
* by user
|
||||
*/
|
||||
QSharedPointer<PasswordKey> Create::getPasswordFromStdin()
|
||||
{
|
||||
QSharedPointer<PasswordKey> passwordKey;
|
||||
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
out << QObject::tr("Insert password to encrypt database (Press enter to leave blank): ");
|
||||
out.flush();
|
||||
QString password = Utils::getPassword();
|
||||
|
||||
if (!password.isEmpty()) {
|
||||
passwordKey = QSharedPointer<PasswordKey>(new PasswordKey(password));
|
||||
}
|
||||
|
||||
return passwordKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a key file from disk. When the path specified does not exist a
|
||||
* new file will be generated. No folders will be generated so the parent
|
||||
* folder of the specified file nees to exist
|
||||
*
|
||||
* If the key file cannot be loaded or created the function will fail.
|
||||
*
|
||||
* @param path Path to the key file to be loaded
|
||||
* @param fileKey Resulting fileKey
|
||||
* @return true if the key file was loaded succesfully
|
||||
*/
|
||||
bool Create::loadFileKey(QString path, QSharedPointer<FileKey>& fileKey)
|
||||
{
|
||||
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
|
||||
|
||||
QString error;
|
||||
fileKey = QSharedPointer<FileKey>(new FileKey());
|
||||
|
||||
if (!QFileInfo::exists(path)) {
|
||||
fileKey->create(path, &error);
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
err << QObject::tr("Creating KeyFile %1 failed: %2").arg(path, error) << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileKey->load(path, &error)) {
|
||||
err << QObject::tr("Loading KeyFile %1 failed: %2").arg(path, error) << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
39
src/cli/Create.h
Normal file
39
src/cli/Create.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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_CREATE_H
|
||||
#define KEEPASSXC_CREATE_H
|
||||
|
||||
#include "Command.h"
|
||||
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
class Create : public Command
|
||||
{
|
||||
public:
|
||||
Create();
|
||||
~Create();
|
||||
int execute(const QStringList& arguments);
|
||||
|
||||
private:
|
||||
QSharedPointer<PasswordKey> getPasswordFromStdin();
|
||||
QSharedPointer<FileKey> getFileKeyFromStdin();
|
||||
bool loadFileKey(QString path, QSharedPointer<FileKey>& fileKey);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_CREATE_H
|
@ -19,6 +19,9 @@ Adds a new entry to a database. A password can be generated (\fI-g\fP option), o
|
||||
.IP "clip [options] <database> <entry> [timeout]"
|
||||
Copies the password or the current TOTP (\fI-t\fP option) 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 "create [options] <database>"
|
||||
Creates a new database with a key file and/or password. The key file will be created if the file that is referred to does not exist. If both the key file and password are empty, no database will be created.
|
||||
|
||||
.IP "diceware [options]"
|
||||
Generate a random diceware passphrase.
|
||||
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "cli/Add.h"
|
||||
#include "cli/Clip.h"
|
||||
#include "cli/Command.h"
|
||||
#include "cli/Create.h"
|
||||
#include "cli/Diceware.h"
|
||||
#include "cli/Edit.h"
|
||||
#include "cli/Estimate.h"
|
||||
@ -137,9 +138,10 @@ QSharedPointer<Database> TestCli::readTestDatabase() const
|
||||
|
||||
void TestCli::testCommand()
|
||||
{
|
||||
QCOMPARE(Command::getCommands().size(), 12);
|
||||
QCOMPARE(Command::getCommands().size(), 13);
|
||||
QVERIFY(Command::getCommand("add"));
|
||||
QVERIFY(Command::getCommand("clip"));
|
||||
QVERIFY(Command::getCommand("create"));
|
||||
QVERIFY(Command::getCommand("diceware"));
|
||||
QVERIFY(Command::getCommand("edit"));
|
||||
QVERIFY(Command::getCommand("estimate"));
|
||||
@ -274,7 +276,7 @@ void TestCli::testClip()
|
||||
// clang-format off
|
||||
QFuture<void> future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"});
|
||||
// clang-format on
|
||||
|
||||
|
||||
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500);
|
||||
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500);
|
||||
|
||||
@ -296,6 +298,76 @@ void TestCli::testClip()
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry with path /Sample Entry has no TOTP set up.\n"));
|
||||
}
|
||||
|
||||
void TestCli::testCreate()
|
||||
{
|
||||
Create createCmd;
|
||||
QVERIFY(!createCmd.name.isEmpty());
|
||||
QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name));
|
||||
|
||||
QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir());
|
||||
|
||||
QString databaseFilename = testDir->path() + "testCreate1.kdbx";
|
||||
// Password
|
||||
Utils::Test::setNextPassword("a");
|
||||
createCmd.execute({"create", databaseFilename});
|
||||
|
||||
m_stderrFile->reset();
|
||||
m_stdoutFile->reset();
|
||||
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Insert password to encrypt database (Press enter to leave blank): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
auto db = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename, "", Utils::DEVNULL));
|
||||
QVERIFY(db);
|
||||
|
||||
// Should refuse to create the database if it already exists.
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
qint64 errPos = m_stderrFile->pos();
|
||||
createCmd.execute({"create", databaseFilename});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stderrFile->seek(errPos);
|
||||
// Output should be empty when there is an error.
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QString errorMessage = QString("File " + databaseFilename + " already exists.\n");
|
||||
QCOMPARE(m_stderrFile->readAll(), errorMessage.toUtf8());
|
||||
|
||||
|
||||
// Testing with keyfile creation
|
||||
QString databaseFilename2 = testDir->path() + "testCreate2.kdbx";
|
||||
QString keyfilePath = testDir->path() + "keyfile.txt";
|
||||
pos = m_stdoutFile->pos();
|
||||
errPos = m_stderrFile->pos();
|
||||
Utils::Test::setNextPassword("a");
|
||||
createCmd.execute({"create", databaseFilename2, "-k", keyfilePath});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stderrFile->seek(errPos);
|
||||
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Insert password to encrypt database (Press enter to leave blank): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
auto db2 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename2, keyfilePath, Utils::DEVNULL));
|
||||
QVERIFY(db2);
|
||||
|
||||
|
||||
// Testing with existing keyfile
|
||||
QString databaseFilename3 = testDir->path() + "testCreate3.kdbx";
|
||||
pos = m_stdoutFile->pos();
|
||||
errPos = m_stderrFile->pos();
|
||||
Utils::Test::setNextPassword("a");
|
||||
createCmd.execute({"create", databaseFilename3, "-k", keyfilePath});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stderrFile->seek(errPos);
|
||||
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Insert password to encrypt database (Press enter to leave blank): \n"));
|
||||
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
|
||||
|
||||
Utils::Test::setNextPassword("a");
|
||||
auto db3 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename3, keyfilePath, Utils::DEVNULL));
|
||||
QVERIFY(db3);
|
||||
}
|
||||
|
||||
void TestCli::testDiceware()
|
||||
{
|
||||
Diceware dicewareCmd;
|
||||
|
@ -43,6 +43,7 @@ private slots:
|
||||
void testCommand();
|
||||
void testAdd();
|
||||
void testClip();
|
||||
void testCreate();
|
||||
void testDiceware();
|
||||
void testEdit();
|
||||
void testEstimate_data();
|
||||
|
Loading…
x
Reference in New Issue
Block a user