diff options
author | louib <code@louib.net> | 2022-10-05 14:30:15 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-05 14:30:15 +0300 |
commit | db98f114f99e9abf317f80c93a607b142690df67 (patch) | |
tree | 2cca52c554469d5fe546b1a3b546ddb05bac5fd9 | |
parent | b1e7c34b82452773e0a7291462fef60a40a4ea72 (diff) |
[CLI] Add a db-edit command (#8400)
-rw-r--r-- | .github/CONTRIBUTING.md | 6 | ||||
-rw-r--r-- | docs/man/keepassxc-cli.1.adoc | 21 | ||||
-rw-r--r-- | share/translations/keepassxc_en.ts | 53 | ||||
-rw-r--r-- | src/cli/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/cli/Command.cpp | 10 | ||||
-rw-r--r-- | src/cli/DatabaseCreate.cpp (renamed from src/cli/Create.cpp) | 47 | ||||
-rw-r--r-- | src/cli/DatabaseCreate.h (renamed from src/cli/Create.h) | 11 | ||||
-rw-r--r-- | src/cli/DatabaseEdit.cpp | 174 | ||||
-rw-r--r-- | src/cli/DatabaseEdit.h | 41 | ||||
-rw-r--r-- | src/cli/DatabaseInfo.cpp (renamed from src/cli/Info.cpp) | 6 | ||||
-rw-r--r-- | src/cli/DatabaseInfo.h (renamed from src/cli/Info.h) | 10 | ||||
-rw-r--r-- | src/cli/Import.cpp | 11 | ||||
-rw-r--r-- | src/keys/CompositeKey.cpp | 30 | ||||
-rw-r--r-- | src/keys/CompositeKey.h | 2 | ||||
-rw-r--r-- | tests/TestCli.cpp | 153 | ||||
-rw-r--r-- | tests/TestCli.h | 1 |
16 files changed, 529 insertions, 52 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9300cc846..6de956328 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -85,6 +85,12 @@ All pull requests must comply with the above requirements and with the [stylegui Translations are managed on [Transifex](https://www.transifex.com/keepassxc/keepassxc/) which offers a web interface. Please join an existing language team or request a new one if there is none. +If you open a Pull Request with new strings that require translations, you will need to run the following: +``` +./release-tool i18n lupdate +``` +This will make the new strings available for translation in Transifex. + ## Styleguides ### Git branch strategy diff --git a/docs/man/keepassxc-cli.1.adoc b/docs/man/keepassxc-cli.1.adoc index 8ae0f4693..f5a5ac55a 100644 --- a/docs/man/keepassxc-cli.1.adoc +++ b/docs/man/keepassxc-cli.1.adoc @@ -66,6 +66,11 @@ It provides the ability to query and modify the entries of a KeePass database, d 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. +*db-edit* [_options_] <__database__>:: + Edits a database. + When setting a key file, the key file will be created if the file that is referred to + does not exist. + *db-info* [_options_] <__database__>:: Show a database's information. @@ -154,7 +159,7 @@ It provides the ability to query and modify the entries of a KeePass database, d *--no-password*:: Deactivates the password key for the database. -*-y*, *--yubikey* <__slot__>:: +*-y*, *--yubikey* <__slot[:serial]__>:: Specifies a yubikey slot for unlocking the database. In a merge operation this option is used to specify the YubiKey slot for the first database. @@ -177,7 +182,7 @@ It provides the ability to query and modify the entries of a KeePass database, d *--no-password-from*:: Deactivates password key for the database to merge from. -*--yubikey-from* <__slot__>:: +*--yubikey-from* <__slot[:serial]__>:: YubiKey slot for the second database. *-s*, *--same-credentials*:: @@ -235,16 +240,24 @@ The same password generation options as documented for the generate command can 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 and Import options -*-k*, *--set-key-file* <__path__>:: +=== Db-create, Db-edit and Import options +*--set-key-file* <__path__>:: Set the key file for the database. *-p*, *--set-password*:: Set a password for the database. +=== Db-create, Import options *-t*, *--decryption-time* <__time__>:: Target decryption time in MS for the database. +=== Db-edit options +*--unset-password* <__path__>:: + Removes the password for the database. + +*--unset-key-file* <__path__>:: + Removes the key file for the database. + === Show options *-a*, *--attributes* <__attribute__>...:: Shows the named attributes. diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 7c4521579..2701f7b05 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -7871,6 +7871,59 @@ Kernel: %3 %4</source> <source>Show all the attributes of the entry.</source> <translation type="unfinished"></translation> </message> + <message> + <source>Edit a database.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Could not change the database key.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Database was not modified.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Successfully edited the database.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Loading the new key file failed: %1</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Unset the password for the database.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Unset the key file for the database.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cannot use %1 and %2 at the same time.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cannot remove all the keys from a database.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cannot remove password: The database does not have a password.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cannot remove file key: The database does not have a file key.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Found unexpected Key type %1</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Set the key file for the database. +This options is deprecated, use --set-key-file instead.</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>QtIOCompressor</name> diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 984427084..8f0b2854a 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -22,9 +22,11 @@ set(cli_SOURCES AttachmentRemove.cpp Clip.cpp Close.cpp - Create.cpp Command.cpp DatabaseCommand.cpp + DatabaseCreate.cpp + DatabaseEdit.cpp + DatabaseInfo.cpp Diceware.cpp Edit.cpp Estimate.cpp @@ -33,7 +35,6 @@ set(cli_SOURCES Generate.cpp Help.cpp Import.cpp - Info.cpp List.cpp Merge.cpp Move.cpp diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index 2eab320a3..4bba8fff9 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -23,7 +23,9 @@ #include "AttachmentRemove.h"
#include "Clip.h"
#include "Close.h"
-#include "Create.h"
+#include "DatabaseCreate.h"
+#include "DatabaseEdit.h"
+#include "DatabaseInfo.h"
#include "Diceware.h"
#include "Edit.h"
#include "Estimate.h"
@@ -32,7 +34,6 @@ #include "Generate.h"
#include "Help.h"
#include "Import.h"
-#include "Info.h"
#include "List.h"
#include "Merge.h"
#include "Move.h"
@@ -172,8 +173,9 @@ namespace Commands s_commands.insert(QStringLiteral("attachment-rm"), QSharedPointer<Command>(new AttachmentRemove()));
s_commands.insert(QStringLiteral("clip"), QSharedPointer<Command>(new Clip()));
s_commands.insert(QStringLiteral("close"), QSharedPointer<Command>(new Close()));
- s_commands.insert(QStringLiteral("db-create"), QSharedPointer<Command>(new Create()));
- s_commands.insert(QStringLiteral("db-info"), QSharedPointer<Command>(new Info()));
+ s_commands.insert(QStringLiteral("db-create"), QSharedPointer<Command>(new DatabaseCreate()));
+ s_commands.insert(QStringLiteral("db-edit"), QSharedPointer<Command>(new DatabaseEdit()));
+ s_commands.insert(QStringLiteral("db-info"), QSharedPointer<Command>(new DatabaseInfo()));
s_commands.insert(QStringLiteral("diceware"), QSharedPointer<Command>(new Diceware()));
s_commands.insert(QStringLiteral("edit"), QSharedPointer<Command>(new Edit()));
s_commands.insert(QStringLiteral("estimate"), QSharedPointer<Command>(new Estimate()));
diff --git a/src/cli/Create.cpp b/src/cli/DatabaseCreate.cpp index 760bf5368..10b424825 100644 --- a/src/cli/Create.cpp +++ b/src/cli/DatabaseCreate.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "Create.h" +#include "DatabaseCreate.h" #include "Utils.h" #include "keys/FileKey.h" @@ -23,34 +23,39 @@ #include <QCommandLineParser> #include <QFileInfo> -const QCommandLineOption Create::DecryptionTimeOption = +const QCommandLineOption DatabaseCreate::DecryptionTimeOption = QCommandLineOption(QStringList() << "t" << "decryption-time", QObject::tr("Target decryption time in MS for the database."), QObject::tr("time")); -const QCommandLineOption Create::SetKeyFileOption = - QCommandLineOption(QStringList() << "k" - << "set-key-file", +const QCommandLineOption DatabaseCreate::SetKeyFileShortOption = QCommandLineOption( + QStringList() << "k", + QObject::tr("Set the key file for the database.\nThis options is deprecated, use --set-key-file instead."), + QObject::tr("path")); + +const QCommandLineOption DatabaseCreate::SetKeyFileOption = + QCommandLineOption(QStringList() << "set-key-file", QObject::tr("Set the key file for the database."), QObject::tr("path")); -const QCommandLineOption Create::SetPasswordOption = +const QCommandLineOption DatabaseCreate::SetPasswordOption = QCommandLineOption(QStringList() << "p" << "set-password", QObject::tr("Set a password for the database.")); -Create::Create() +DatabaseCreate::DatabaseCreate() { name = QString("db-create"); description = QObject::tr("Create a new database."); positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")}); - options.append(Create::SetKeyFileOption); - options.append(Create::SetPasswordOption); - options.append(Create::DecryptionTimeOption); + options.append(DatabaseCreate::SetKeyFileOption); + options.append(DatabaseCreate::SetKeyFileShortOption); + options.append(DatabaseCreate::SetPasswordOption); + options.append(DatabaseCreate::DecryptionTimeOption); } -QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser) +QSharedPointer<Database> DatabaseCreate::initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser) { if (parser.isNull()) { return {}; @@ -60,7 +65,7 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin auto& err = Utils::STDERR; // Validate the decryption time before asking for a password. - QString decryptionTimeValue = parser->value(Create::DecryptionTimeOption); + QString decryptionTimeValue = parser->value(DatabaseCreate::DecryptionTimeOption); int decryptionTime = 0; if (decryptionTimeValue.length() != 0) { decryptionTime = decryptionTimeValue.toInt(); @@ -78,7 +83,7 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin auto key = QSharedPointer<CompositeKey>::create(); - if (parser->isSet(Create::SetPasswordOption)) { + if (parser->isSet(DatabaseCreate::SetPasswordOption)) { auto passwordKey = Utils::getConfirmedPassword(); if (passwordKey.isNull()) { err << QObject::tr("Failed to set database password.") << endl; @@ -87,10 +92,18 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin key->addKey(passwordKey); } - if (parser->isSet(Create::SetKeyFileOption)) { + if (parser->isSet(DatabaseCreate::SetKeyFileOption) || parser->isSet(DatabaseCreate::SetKeyFileShortOption)) { QSharedPointer<FileKey> fileKey; - if (!Utils::loadFileKey(parser->value(Create::SetKeyFileOption), fileKey)) { + QString keyFilePath; + if (parser->isSet(DatabaseCreate::SetKeyFileShortOption)) { + qWarning("The -k option will be deprecated. Please use the --set-key-file option instead."); + keyFilePath = parser->value(DatabaseCreate::SetKeyFileShortOption); + } else { + keyFilePath = parser->value(DatabaseCreate::SetKeyFileOption); + } + + if (!Utils::loadFileKey(keyFilePath, fileKey)) { err << QObject::tr("Loading the key file failed") << endl; return {}; } @@ -141,7 +154,7 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin * * @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure */ -int Create::execute(const QStringList& arguments) +int DatabaseCreate::execute(const QStringList& arguments) { QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments); if (parser.isNull()) { @@ -159,7 +172,7 @@ int Create::execute(const QStringList& arguments) return EXIT_FAILURE; } - QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser); + QSharedPointer<Database> db = DatabaseCreate::initializeDatabaseFromOptions(parser); if (!db) { return EXIT_FAILURE; } diff --git a/src/cli/Create.h b/src/cli/DatabaseCreate.h index 3b91dca10..30db16ed9 100644 --- a/src/cli/Create.h +++ b/src/cli/DatabaseCreate.h @@ -15,22 +15,23 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef KEEPASSXC_CREATE_H -#define KEEPASSXC_CREATE_H +#ifndef KEEPASSXC_DATABASECREATE_H +#define KEEPASSXC_DATABASECREATE_H #include "Command.h" -class Create : public Command +class DatabaseCreate : public Command { public: - Create(); + DatabaseCreate(); int execute(const QStringList& arguments) override; static QSharedPointer<Database> initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser); static const QCommandLineOption SetKeyFileOption; + static const QCommandLineOption SetKeyFileShortOption; static const QCommandLineOption SetPasswordOption; static const QCommandLineOption DecryptionTimeOption; }; -#endif // KEEPASSXC_CREATE_H +#endif // KEEPASSXC_DATABASECREATE_H diff --git a/src/cli/DatabaseEdit.cpp b/src/cli/DatabaseEdit.cpp new file mode 100644 index 000000000..f5ca4ef2e --- /dev/null +++ b/src/cli/DatabaseEdit.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2022 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 "DatabaseEdit.h" + +#include "Utils.h" +#include "cli/DatabaseCreate.h" +#include "keys/ChallengeResponseKey.h" +#include "keys/FileKey.h" +#include "keys/PasswordKey.h" + +#include <QCommandLineParser> +#include <QFileInfo> + +const QCommandLineOption DatabaseEdit::UnsetPasswordOption = + QCommandLineOption(QStringList() << "unset-password", QObject::tr("Unset the password for the database.")); +const QCommandLineOption DatabaseEdit::UnsetKeyFileOption = + QCommandLineOption(QStringList() << "unset-key-file", QObject::tr("Unset the key file for the database.")); + +DatabaseEdit::DatabaseEdit() +{ + name = QString("db-edit"); + description = QObject::tr("Edit a database."); + options.append(DatabaseCreate::SetKeyFileOption); + options.append(DatabaseCreate::SetPasswordOption); + options.append(DatabaseEdit::UnsetKeyFileOption); + options.append(DatabaseEdit::UnsetPasswordOption); +} + +int DatabaseEdit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) +{ + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; + + const QStringList args = parser->positionalArguments(); + bool databaseWasChanged = false; + + if (parser->isSet(DatabaseCreate::SetPasswordOption) && parser->isSet(DatabaseEdit::UnsetPasswordOption)) { + err << QObject::tr("Cannot use %1 and %2 at the same time.") + .arg(DatabaseCreate::SetPasswordOption.names().at(0)) + .arg(DatabaseEdit::UnsetPasswordOption.names().at(0)) + << endl; + return EXIT_FAILURE; + } + + if (parser->isSet(DatabaseCreate::SetKeyFileOption) && parser->isSet(DatabaseEdit::UnsetKeyFileOption)) { + err << QObject::tr("Cannot use %1 and %2 at the same time.") + .arg(DatabaseCreate::SetKeyFileOption.names().at(0)) + .arg(DatabaseEdit::UnsetKeyFileOption.names().at(0)) + << endl; + return EXIT_FAILURE; + } + + bool hasKeyChange = + (parser->isSet(DatabaseCreate::SetPasswordOption) || parser->isSet(DatabaseCreate::SetKeyFileOption) + || parser->isSet(DatabaseEdit::UnsetPasswordOption) || parser->isSet(DatabaseEdit::UnsetKeyFileOption)); + + if (hasKeyChange) { + auto newDatabaseKey = getNewDatabaseKey(database, + parser->isSet(DatabaseCreate::SetPasswordOption), + parser->isSet(DatabaseEdit::UnsetPasswordOption), + parser->value(DatabaseCreate::SetKeyFileOption), + parser->isSet(DatabaseEdit::UnsetKeyFileOption)); + if (newDatabaseKey.isNull()) { + err << QObject::tr("Could not change the database key.") << endl; + return EXIT_FAILURE; + } + database->setKey(newDatabaseKey); + databaseWasChanged = true; + } + + if (!databaseWasChanged) { + out << QObject::tr("Database was not modified.") << endl; + return EXIT_SUCCESS; + } + + QString errorMessage; + if (!database->save(Database::Atomic, {}, &errorMessage)) { + err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl; + return EXIT_FAILURE; + } + + out << QObject::tr("Successfully edited the database.") << endl; + return EXIT_SUCCESS; +} + +QSharedPointer<CompositeKey> DatabaseEdit::getNewDatabaseKey(QSharedPointer<Database> database, + bool updatePassword, + bool removePassword, + QString newFileKeyPath, + bool removeKeyFile) +{ + auto& err = Utils::STDERR; + auto newDatabaseKey = QSharedPointer<CompositeKey>::create(); + bool updateKeyFile = !newFileKeyPath.isEmpty(); + + auto currentPasswordKey = database->key()->getKey(PasswordKey::UUID); + auto currentFileKey = database->key()->getKey(FileKey::UUID); + auto currentChallengeResponseKey = database->key()->getChallengeResponseKey(ChallengeResponseKey::UUID); + + if (removePassword && currentPasswordKey.isNull()) { + err << QObject::tr("Cannot remove password: The database does not have a password.") << endl; + return {}; + } + + if (removeKeyFile && currentFileKey.isNull()) { + err << QObject::tr("Cannot remove file key: The database does not have a file key.") << endl; + return {}; + } + + if (updatePassword) { + QSharedPointer<PasswordKey> newPasswordKey = Utils::getConfirmedPassword(); + if (newPasswordKey.isNull()) { + err << QObject::tr("Failed to set database password.") << endl; + return {}; + } + newDatabaseKey->addKey(newPasswordKey); + } else if (!removePassword && !currentPasswordKey.isNull()) { + newDatabaseKey->addKey(currentPasswordKey); + } + + if (updateKeyFile) { + QSharedPointer<FileKey> newFileKey = QSharedPointer<FileKey>::create(); + QString errorMessage; + if (!Utils::loadFileKey(newFileKeyPath, newFileKey)) { + err << QObject::tr("Loading the new key file failed: %1").arg(errorMessage) << endl; + return {}; + } + newDatabaseKey->addKey(newFileKey); + } else if (!removeKeyFile && !currentFileKey.isNull()) { + newDatabaseKey->addKey(currentFileKey); + } + + // This is a sanity check to make sure that this function is not used if + // new key types are introduced. Otherwise, those key types would be + // silently removed from the database. + for (const QSharedPointer<Key>& key : database->key()->keys()) { + if (key->uuid() != PasswordKey::UUID && key->uuid() != FileKey::UUID) { + err << QObject::tr("Found unexpected Key type %1").arg(key->uuid().toString()) << endl; + return {}; + } + } + for (const QSharedPointer<ChallengeResponseKey>& key : database->key()->challengeResponseKeys()) { + if (key->uuid() != ChallengeResponseKey::UUID) { + err << QObject::tr("Found unexpected Key type %1").arg(key->uuid().toString()) << endl; + return {}; + } + } + + if (!currentChallengeResponseKey.isNull()) { + newDatabaseKey->addChallengeResponseKey(currentChallengeResponseKey); + } + + if (newDatabaseKey->keys().isEmpty() && newDatabaseKey->challengeResponseKeys().isEmpty()) { + err << QObject::tr("Cannot remove all the keys from a database.") << endl; + return {}; + } + + return newDatabaseKey; +} diff --git a/src/cli/DatabaseEdit.h b/src/cli/DatabaseEdit.h new file mode 100644 index 000000000..2d77b206f --- /dev/null +++ b/src/cli/DatabaseEdit.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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_DATABASEEDIT_H +#define KEEPASSXC_DATABASEEDIT_H + +#include "DatabaseCommand.h" + +class DatabaseEdit : public DatabaseCommand +{ +public: + DatabaseEdit(); + + int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override; + + static const QCommandLineOption UnsetKeyFileOption; + static const QCommandLineOption UnsetPasswordOption; + +private: + QSharedPointer<CompositeKey> getNewDatabaseKey(QSharedPointer<Database> database, + bool updatePassword, + bool removePassword, + QString newFileKeyPath, + bool removeKeyFile); +}; + +#endif // KEEPASSXC_DATABASEEDIT_H diff --git a/src/cli/Info.cpp b/src/cli/DatabaseInfo.cpp index bf093664a..b5569ae84 100644 --- a/src/cli/Info.cpp +++ b/src/cli/DatabaseInfo.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "Info.h" +#include "DatabaseInfo.h" #include "Utils.h" #include "core/DatabaseStats.h" @@ -25,13 +25,13 @@ #include <QCommandLineParser> -Info::Info() +DatabaseInfo::DatabaseInfo() { name = QString("db-info"); description = QObject::tr("Show a database's information."); } -int Info::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser>) +int DatabaseInfo::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser>) { auto& out = Utils::STDOUT; diff --git a/src/cli/Info.h b/src/cli/DatabaseInfo.h index 386dc62eb..42a48c86f 100644 --- a/src/cli/Info.h +++ b/src/cli/DatabaseInfo.h @@ -15,17 +15,17 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef KEEPASSXC_INFO_H -#define KEEPASSXC_INFO_H +#ifndef KEEPASSXC_DATABASEINFO_H +#define KEEPASSXC_DATABASEINFO_H #include "DatabaseCommand.h" -class Info : public DatabaseCommand +class DatabaseInfo : public DatabaseCommand { public: - Info(); + DatabaseInfo(); int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override; }; -#endif // KEEPASSXC_INFO_H +#endif // KEEPASSXC_DATABASEINFO_H diff --git a/src/cli/Import.cpp b/src/cli/Import.cpp index 3732f0cf6..cc74df767 100644 --- a/src/cli/Import.cpp +++ b/src/cli/Import.cpp @@ -17,7 +17,7 @@ #include "Import.h" -#include "Create.h" +#include "DatabaseCreate.h" #include "Utils.h" #include <QCommandLineParser> @@ -40,9 +40,10 @@ Import::Import() description = QObject::tr("Import the contents of an XML database."); positionalArguments.append({QString("xml"), QObject::tr("Path of the XML database export."), QString("")}); positionalArguments.append({QString("database"), QObject::tr("Path of the new database."), QString("")}); - options.append(Create::SetKeyFileOption); - options.append(Create::SetPasswordOption); - options.append(Create::DecryptionTimeOption); + options.append(DatabaseCreate::SetKeyFileOption); + options.append(DatabaseCreate::SetKeyFileShortOption); + options.append(DatabaseCreate::SetPasswordOption); + options.append(DatabaseCreate::DecryptionTimeOption); } int Import::execute(const QStringList& arguments) @@ -64,7 +65,7 @@ int Import::execute(const QStringList& arguments) return EXIT_FAILURE; } - QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser); + QSharedPointer<Database> db = DatabaseCreate::initializeDatabaseFromOptions(parser); if (!db) { return EXIT_FAILURE; } diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 0fdb2b32f..705491667 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -170,6 +170,36 @@ void CompositeKey::addKey(const QSharedPointer<Key>& key) } /** + * Get the \link Key with the specified ID. + * + * @param keyId the ID of the key to get. + */ +QSharedPointer<Key> CompositeKey::getKey(const QUuid keyId) const +{ + for (const QSharedPointer<Key>& key : m_keys) { + if (key->uuid() == keyId) { + return key; + } + } + return {}; +} + +/** + * Get the \link ChallengeResponseKey with the specified ID. + * + * @param keyId the ID of the key to get. + */ +QSharedPointer<ChallengeResponseKey> CompositeKey::getChallengeResponseKey(const QUuid keyId) const +{ + for (const QSharedPointer<ChallengeResponseKey>& key : m_challengeResponseKeys) { + if (key->uuid() == keyId) { + return key; + } + } + return {}; +} + +/** * @return list of Keys which are part of this CompositeKey */ const QList<QSharedPointer<Key>>& CompositeKey::keys() const diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 8db385c2c..451f88e4f 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -43,6 +43,8 @@ public: bool challenge(const QByteArray& seed, QByteArray& result, QString* error = nullptr) const; void addKey(const QSharedPointer<Key>& key); + QSharedPointer<Key> getKey(const QUuid keyType) const; + QSharedPointer<ChallengeResponseKey> getChallengeResponseKey(const QUuid keyType) const; const QList<QSharedPointer<Key>>& keys() const; void addChallengeResponseKey(const QSharedPointer<ChallengeResponseKey>& key); diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 785121032..2ccda2e24 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -34,7 +34,9 @@ #include "cli/AttachmentImport.h" #include "cli/AttachmentRemove.h" #include "cli/Clip.h" -#include "cli/Create.h" +#include "cli/DatabaseCreate.h" +#include "cli/DatabaseEdit.h" +#include "cli/DatabaseInfo.h" #include "cli/Diceware.h" #include "cli/Edit.h" #include "cli/Estimate.h" @@ -42,7 +44,6 @@ #include "cli/Generate.h" #include "cli/Help.h" #include "cli/Import.h" -#include "cli/Info.h" #include "cli/List.h" #include "cli/Merge.h" #include "cli/Move.h" @@ -242,7 +243,7 @@ void TestCli::testBatchCommands() QVERIFY(Commands::getCommand("show")); QVERIFY(Commands::getCommand("search")); QVERIFY(!Commands::getCommand("doesnotexist")); - QCOMPARE(Commands::getCommands().size(), 25); + QCOMPARE(Commands::getCommands().size(), 26); } void TestCli::testInteractiveCommands() @@ -274,7 +275,7 @@ void TestCli::testInteractiveCommands() QVERIFY(Commands::getCommand("show")); QVERIFY(Commands::getCommand("search")); QVERIFY(!Commands::getCommand("doesnotexist")); - QCOMPARE(Commands::getCommands().size(), 25); + QCOMPARE(Commands::getCommands().size(), 26); } void TestCli::testAdd() @@ -732,7 +733,7 @@ void TestCli::testClip() void TestCli::testCreate() { - Create createCmd; + DatabaseCreate createCmd; QVERIFY(!createCmd.name.isEmpty()); QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name)); @@ -848,9 +849,147 @@ void TestCli::testCreate() QVERIFY(db); } +void TestCli::testDatabaseEdit() +{ + TemporaryFile firstKeyFile; + firstKeyFile.open(); + firstKeyFile.write(QString("keyFilePassword").toLatin1()); + firstKeyFile.close(); + + TemporaryFile secondKeyFile; + secondKeyFile.open(); + secondKeyFile.write(QString("newKeyFilePassword").toLatin1()); + secondKeyFile.close(); + + QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir()); + + DatabaseCreate createCmd; + DatabaseEdit editCmd; + QVERIFY(!editCmd.name.isEmpty()); + QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name)); + + QString dbFilename; + dbFilename = testDir->path() + "/testDatabaseEdit.kdbx"; + + // Creating a database for testing + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); + + // Sanity check. + auto db = readDatabase(dbFilename, "a"); + QVERIFY(!db.isNull()); + + setInput("a"); + execCmd(editCmd, {"db-edit", dbFilename, "-p", "--unset-password"}); + QCOMPARE(m_stdout->readAll(), QByteArray("")); + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("Cannot use p and unset-password at the same time.\n")); + + setInput("a"); + execCmd(editCmd, {"db-edit", dbFilename, "--set-key-file", "/key/file/path", "--unset-key-file"}); + QCOMPARE(m_stdout->readAll(), QByteArray("")); + // Skipping the password prompt. + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("Cannot use set-key-file and unset-key-file at the same time.\n")); + + // Sanity check. + db = readDatabase(dbFilename, "a"); + QVERIFY(!db.isNull()); + + setInput({"a", "b", "b"}); + execCmd(editCmd, {"db-edit", dbFilename, "-p"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + // Sanity check + db = readDatabase(dbFilename, "b"); + QVERIFY(!db.isNull()); + + setInput("b"); + execCmd(editCmd, {"db-edit", dbFilename, "--set-key-file", firstKeyFile.fileName()}); + // Skipping the password prompt. + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + // Sanity check + db = readDatabase(dbFilename, "b"); + QVERIFY(db.isNull()); + db = readDatabase(dbFilename, "b", firstKeyFile.fileName()); + QVERIFY(!db.isNull()); + + setInput("b"); + execCmd(editCmd, + {"db-edit", dbFilename, "-k", firstKeyFile.fileName(), "--set-key-file", secondKeyFile.fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + // Sanity check + db = readDatabase(dbFilename, "b", firstKeyFile.fileName()); + QVERIFY(db.isNull()); + db = readDatabase(dbFilename, "b", secondKeyFile.fileName()); + QVERIFY(!db.isNull()); + + setInput("b"); + execCmd(editCmd, {"db-edit", dbFilename, "-k", secondKeyFile.fileName(), "--unset-password"}); + // Skipping the password prompt. + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + execCmd(editCmd, + {"db-edit", + dbFilename, + "--no-password", + "-k", + secondKeyFile.fileName(), + "--set-key-file", + firstKeyFile.fileName()}); + // Skipping the password prompt. + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + setInput({"b", "b"}); + execCmd(editCmd, {"db-edit", dbFilename, "-k", firstKeyFile.fileName(), "--no-password", "--set-password"}); + // Skipping over the password setting prompts. + m_stderr->readLine(); + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + setInput("b"); + execCmd(editCmd, {"db-edit", dbFilename, "-k", firstKeyFile.fileName(), "--unset-key-file"}); + // Skipping the password prompt. + m_stderr->readLine(); + QCOMPARE(m_stderr->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited the database.\n")); + + // Sanity check + db = readDatabase(dbFilename, "b", firstKeyFile.fileName()); + QVERIFY(db.isNull()); + db = readDatabase(dbFilename, "b"); + QVERIFY(!db.isNull()); + + // Trying to remove the key file when there is none set should + // raise an error. + setInput("b"); + execCmd(editCmd, {"db-edit", dbFilename, "-p", "--unset-key-file"}); + QCOMPARE(m_stdout->readAll(), QByteArray("")); + m_stderr->readLine(); + QCOMPARE(m_stderr->readLine(), QByteArray("Cannot remove file key: The database does not have a file key.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Could not change the database key.\n")); + + setInput("b"); + execCmd(editCmd, {"db-edit", dbFilename, "--unset-password"}); + QCOMPARE(m_stdout->readAll(), QByteArray("")); + // Skipping the password prompt. + m_stderr->readLine(); + QCOMPARE(m_stderr->readLine(), QByteArray("Cannot remove all the keys from a database.\n")); +} + void TestCli::testInfo() { - Info infoCmd; + DatabaseInfo infoCmd; QVERIFY(!infoCmd.name.isEmpty()); QVERIFY(infoCmd.getDescriptionLine().contains(infoCmd.name)); @@ -1613,7 +1752,7 @@ void TestCli::testMerge() void TestCli::testMergeWithKeys() { - Create createCmd; + DatabaseCreate createCmd; QVERIFY(!createCmd.name.isEmpty()); QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name)); diff --git a/tests/TestCli.h b/tests/TestCli.h index bbe80a4b3..3beb80724 100644 --- a/tests/TestCli.h +++ b/tests/TestCli.h @@ -54,6 +54,7 @@ private slots: void testCommandParsing_data(); void testCommandParsing(); void testCreate(); + void testDatabaseEdit(); void testDiceware(); void testEdit(); void testEstimate_data(); |