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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
QtIOCompressor
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(new AttachmentRemove()));
s_commands.insert(QStringLiteral("clip"), QSharedPointer(new Clip()));
s_commands.insert(QStringLiteral("close"), QSharedPointer(new Close()));
- s_commands.insert(QStringLiteral("db-create"), QSharedPointer(new Create()));
- s_commands.insert(QStringLiteral("db-info"), QSharedPointer(new Info()));
+ s_commands.insert(QStringLiteral("db-create"), QSharedPointer(new DatabaseCreate()));
+ s_commands.insert(QStringLiteral("db-edit"), QSharedPointer(new DatabaseEdit()));
+ s_commands.insert(QStringLiteral("db-info"), QSharedPointer(new DatabaseInfo()));
s_commands.insert(QStringLiteral("diceware"), QSharedPointer(new Diceware()));
s_commands.insert(QStringLiteral("edit"), QSharedPointer(new Edit()));
s_commands.insert(QStringLiteral("estimate"), QSharedPointer(new Estimate()));
diff --git a/src/cli/Create.cpp b/src/cli/DatabaseCreate.cpp
similarity index 74%
rename from src/cli/Create.cpp
rename to 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 .
*/
-#include "Create.h"
+#include "DatabaseCreate.h"
#include "Utils.h"
#include "keys/FileKey.h"
@@ -23,34 +23,39 @@
#include
#include
-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 Create::initializeDatabaseFromOptions(const QSharedPointer& parser)
+QSharedPointer DatabaseCreate::initializeDatabaseFromOptions(const QSharedPointer& parser)
{
if (parser.isNull()) {
return {};
@@ -60,7 +65,7 @@ QSharedPointer 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 Create::initializeDatabaseFromOptions(const QSharedPoin
auto key = QSharedPointer::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 Create::initializeDatabaseFromOptions(const QSharedPoin
key->addKey(passwordKey);
}
- if (parser->isSet(Create::SetKeyFileOption)) {
+ if (parser->isSet(DatabaseCreate::SetKeyFileOption) || parser->isSet(DatabaseCreate::SetKeyFileShortOption)) {
QSharedPointer 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 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 parser = getCommandLineParser(arguments);
if (parser.isNull()) {
@@ -159,7 +172,7 @@ int Create::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
- QSharedPointer db = Create::initializeDatabaseFromOptions(parser);
+ QSharedPointer db = DatabaseCreate::initializeDatabaseFromOptions(parser);
if (!db) {
return EXIT_FAILURE;
}
diff --git a/src/cli/Create.h b/src/cli/DatabaseCreate.h
similarity index 82%
rename from src/cli/Create.h
rename to 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 .
*/
-#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 initializeDatabaseFromOptions(const QSharedPointer& 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
+ *
+ * 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 .
+ */
+
+#include "DatabaseEdit.h"
+
+#include "Utils.h"
+#include "cli/DatabaseCreate.h"
+#include "keys/ChallengeResponseKey.h"
+#include "keys/FileKey.h"
+#include "keys/PasswordKey.h"
+
+#include
+#include
+
+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, QSharedPointer 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 DatabaseEdit::getNewDatabaseKey(QSharedPointer database,
+ bool updatePassword,
+ bool removePassword,
+ QString newFileKeyPath,
+ bool removeKeyFile)
+{
+ auto& err = Utils::STDERR;
+ auto newDatabaseKey = QSharedPointer::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 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 newFileKey = QSharedPointer::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 : 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& 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
+ *
+ * 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 .
+ */
+
+#ifndef KEEPASSXC_DATABASEEDIT_H
+#define KEEPASSXC_DATABASEEDIT_H
+
+#include "DatabaseCommand.h"
+
+class DatabaseEdit : public DatabaseCommand
+{
+public:
+ DatabaseEdit();
+
+ int executeWithDatabase(QSharedPointer db, QSharedPointer parser) override;
+
+ static const QCommandLineOption UnsetKeyFileOption;
+ static const QCommandLineOption UnsetPasswordOption;
+
+private:
+ QSharedPointer getNewDatabaseKey(QSharedPointer 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
similarity index 95%
rename from src/cli/Info.cpp
rename to 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 .
*/
-#include "Info.h"
+#include "DatabaseInfo.h"
#include "Utils.h"
#include "core/DatabaseStats.h"
@@ -25,13 +25,13 @@
#include
-Info::Info()
+DatabaseInfo::DatabaseInfo()
{
name = QString("db-info");
description = QObject::tr("Show a database's information.");
}
-int Info::executeWithDatabase(QSharedPointer database, QSharedPointer)
+int DatabaseInfo::executeWithDatabase(QSharedPointer database, QSharedPointer)
{
auto& out = Utils::STDOUT;
diff --git a/src/cli/Info.h b/src/cli/DatabaseInfo.h
similarity index 84%
rename from src/cli/Info.h
rename to 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 .
*/
-#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 db, QSharedPointer 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
@@ -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 db = Create::initializeDatabaseFromOptions(parser);
+ QSharedPointer 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
@@ -169,6 +169,36 @@ void CompositeKey::addKey(const QSharedPointer& key)
m_keys.append(key);
}
+/**
+ * Get the \link Key with the specified ID.
+ *
+ * @param keyId the ID of the key to get.
+ */
+QSharedPointer CompositeKey::getKey(const QUuid keyId) const
+{
+ for (const QSharedPointer& 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 CompositeKey::getChallengeResponseKey(const QUuid keyId) const
+{
+ for (const QSharedPointer& key : m_challengeResponseKeys) {
+ if (key->uuid() == keyId) {
+ return key;
+ }
+ }
+ return {};
+}
+
/**
* @return list of Keys which are part of this CompositeKey
*/
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);
+ QSharedPointer getKey(const QUuid keyType) const;
+ QSharedPointer getChallengeResponseKey(const QUuid keyType) const;
const QList>& keys() const;
void addChallengeResponseKey(const QSharedPointer& 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 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();