[CLI] Add a db-edit command (#8400)

This commit is contained in:
louib 2022-10-05 07:30:15 -04:00 committed by GitHub
parent b1e7c34b82
commit db98f114f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 529 additions and 52 deletions

View File

@ -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. 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. 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 ## Styleguides
### Git branch strategy ### Git branch strategy

View File

@ -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. 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. 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__>:: *db-info* [_options_] <__database__>::
Show a database's information. 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*:: *--no-password*::
Deactivates the password key for the database. Deactivates the password key for the database.
*-y*, *--yubikey* <__slot__>:: *-y*, *--yubikey* <__slot[:serial]__>::
Specifies a yubikey slot for unlocking the database. 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. 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*:: *--no-password-from*::
Deactivates password key for the database to merge from. Deactivates password key for the database to merge from.
*--yubikey-from* <__slot__>:: *--yubikey-from* <__slot[:serial]__>::
YubiKey slot for the second database. YubiKey slot for the second database.
*-s*, *--same-credentials*:: *-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 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) If multiple entries are found they will be listed to refine the search. (no clip performed)
=== Create and Import options === Db-create, Db-edit and Import options
*-k*, *--set-key-file* <__path__>:: *--set-key-file* <__path__>::
Set the key file for the database. Set the key file for the database.
*-p*, *--set-password*:: *-p*, *--set-password*::
Set a password for the database. Set a password for the database.
=== Db-create, Import options
*-t*, *--decryption-time* <__time__>:: *-t*, *--decryption-time* <__time__>::
Target decryption time in MS for the database. 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 === Show options
*-a*, *--attributes* <__attribute__>...:: *-a*, *--attributes* <__attribute__>...::
Shows the named attributes. Shows the named attributes.

View File

@ -7871,6 +7871,59 @@ Kernel: %3 %4</source>
<source>Show all the attributes of the entry.</source> <source>Show all the attributes of the entry.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </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>
<context> <context>
<name>QtIOCompressor</name> <name>QtIOCompressor</name>

View File

@ -22,9 +22,11 @@ set(cli_SOURCES
AttachmentRemove.cpp AttachmentRemove.cpp
Clip.cpp Clip.cpp
Close.cpp Close.cpp
Create.cpp
Command.cpp Command.cpp
DatabaseCommand.cpp DatabaseCommand.cpp
DatabaseCreate.cpp
DatabaseEdit.cpp
DatabaseInfo.cpp
Diceware.cpp Diceware.cpp
Edit.cpp Edit.cpp
Estimate.cpp Estimate.cpp
@ -33,7 +35,6 @@ set(cli_SOURCES
Generate.cpp Generate.cpp
Help.cpp Help.cpp
Import.cpp Import.cpp
Info.cpp
List.cpp List.cpp
Merge.cpp Merge.cpp
Move.cpp Move.cpp

View File

@ -23,7 +23,9 @@
#include "AttachmentRemove.h" #include "AttachmentRemove.h"
#include "Clip.h" #include "Clip.h"
#include "Close.h" #include "Close.h"
#include "Create.h" #include "DatabaseCreate.h"
#include "DatabaseEdit.h"
#include "DatabaseInfo.h"
#include "Diceware.h" #include "Diceware.h"
#include "Edit.h" #include "Edit.h"
#include "Estimate.h" #include "Estimate.h"
@ -32,7 +34,6 @@
#include "Generate.h" #include "Generate.h"
#include "Help.h" #include "Help.h"
#include "Import.h" #include "Import.h"
#include "Info.h"
#include "List.h" #include "List.h"
#include "Merge.h" #include "Merge.h"
#include "Move.h" #include "Move.h"
@ -172,8 +173,9 @@ namespace Commands
s_commands.insert(QStringLiteral("attachment-rm"), QSharedPointer<Command>(new AttachmentRemove())); s_commands.insert(QStringLiteral("attachment-rm"), QSharedPointer<Command>(new AttachmentRemove()));
s_commands.insert(QStringLiteral("clip"), QSharedPointer<Command>(new Clip())); s_commands.insert(QStringLiteral("clip"), QSharedPointer<Command>(new Clip()));
s_commands.insert(QStringLiteral("close"), QSharedPointer<Command>(new Close())); s_commands.insert(QStringLiteral("close"), QSharedPointer<Command>(new Close()));
s_commands.insert(QStringLiteral("db-create"), QSharedPointer<Command>(new Create())); s_commands.insert(QStringLiteral("db-create"), QSharedPointer<Command>(new DatabaseCreate()));
s_commands.insert(QStringLiteral("db-info"), QSharedPointer<Command>(new Info())); 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("diceware"), QSharedPointer<Command>(new Diceware()));
s_commands.insert(QStringLiteral("edit"), QSharedPointer<Command>(new Edit())); s_commands.insert(QStringLiteral("edit"), QSharedPointer<Command>(new Edit()));
s_commands.insert(QStringLiteral("estimate"), QSharedPointer<Command>(new Estimate())); s_commands.insert(QStringLiteral("estimate"), QSharedPointer<Command>(new Estimate()));

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Create.h" #include "DatabaseCreate.h"
#include "Utils.h" #include "Utils.h"
#include "keys/FileKey.h" #include "keys/FileKey.h"
@ -23,34 +23,39 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QFileInfo> #include <QFileInfo>
const QCommandLineOption Create::DecryptionTimeOption = const QCommandLineOption DatabaseCreate::DecryptionTimeOption =
QCommandLineOption(QStringList() << "t" QCommandLineOption(QStringList() << "t"
<< "decryption-time", << "decryption-time",
QObject::tr("Target decryption time in MS for the database."), QObject::tr("Target decryption time in MS for the database."),
QObject::tr("time")); QObject::tr("time"));
const QCommandLineOption Create::SetKeyFileOption = const QCommandLineOption DatabaseCreate::SetKeyFileShortOption = QCommandLineOption(
QCommandLineOption(QStringList() << "k" QStringList() << "k",
<< "set-key-file", 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("Set the key file for the database."),
QObject::tr("path")); QObject::tr("path"));
const QCommandLineOption Create::SetPasswordOption = const QCommandLineOption DatabaseCreate::SetPasswordOption =
QCommandLineOption(QStringList() << "p" QCommandLineOption(QStringList() << "p"
<< "set-password", << "set-password",
QObject::tr("Set a password for the database.")); QObject::tr("Set a password for the database."));
Create::Create() DatabaseCreate::DatabaseCreate()
{ {
name = QString("db-create"); name = QString("db-create");
description = QObject::tr("Create a new database."); description = QObject::tr("Create a new database.");
positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")}); positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")});
options.append(Create::SetKeyFileOption); options.append(DatabaseCreate::SetKeyFileOption);
options.append(Create::SetPasswordOption); options.append(DatabaseCreate::SetKeyFileShortOption);
options.append(Create::DecryptionTimeOption); 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()) { if (parser.isNull()) {
return {}; return {};
@ -60,7 +65,7 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin
auto& err = Utils::STDERR; auto& err = Utils::STDERR;
// Validate the decryption time before asking for a password. // Validate the decryption time before asking for a password.
QString decryptionTimeValue = parser->value(Create::DecryptionTimeOption); QString decryptionTimeValue = parser->value(DatabaseCreate::DecryptionTimeOption);
int decryptionTime = 0; int decryptionTime = 0;
if (decryptionTimeValue.length() != 0) { if (decryptionTimeValue.length() != 0) {
decryptionTime = decryptionTimeValue.toInt(); decryptionTime = decryptionTimeValue.toInt();
@ -78,7 +83,7 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
if (parser->isSet(Create::SetPasswordOption)) { if (parser->isSet(DatabaseCreate::SetPasswordOption)) {
auto passwordKey = Utils::getConfirmedPassword(); auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) { if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl; err << QObject::tr("Failed to set database password.") << endl;
@ -87,10 +92,18 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin
key->addKey(passwordKey); key->addKey(passwordKey);
} }
if (parser->isSet(Create::SetKeyFileOption)) { if (parser->isSet(DatabaseCreate::SetKeyFileOption) || parser->isSet(DatabaseCreate::SetKeyFileShortOption)) {
QSharedPointer<FileKey> fileKey; 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; err << QObject::tr("Loading the key file failed") << endl;
return {}; return {};
} }
@ -141,7 +154,7 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin
* *
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure * @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); QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) { if (parser.isNull()) {
@ -159,7 +172,7 @@ int Create::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser); QSharedPointer<Database> db = DatabaseCreate::initializeDatabaseFromOptions(parser);
if (!db) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -15,22 +15,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KEEPASSXC_CREATE_H #ifndef KEEPASSXC_DATABASECREATE_H
#define KEEPASSXC_CREATE_H #define KEEPASSXC_DATABASECREATE_H
#include "Command.h" #include "Command.h"
class Create : public Command class DatabaseCreate : public Command
{ {
public: public:
Create(); DatabaseCreate();
int execute(const QStringList& arguments) override; int execute(const QStringList& arguments) override;
static QSharedPointer<Database> initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser); static QSharedPointer<Database> initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser);
static const QCommandLineOption SetKeyFileOption; static const QCommandLineOption SetKeyFileOption;
static const QCommandLineOption SetKeyFileShortOption;
static const QCommandLineOption SetPasswordOption; static const QCommandLineOption SetPasswordOption;
static const QCommandLineOption DecryptionTimeOption; static const QCommandLineOption DecryptionTimeOption;
}; };
#endif // KEEPASSXC_CREATE_H #endif // KEEPASSXC_DATABASECREATE_H

174
src/cli/DatabaseEdit.cpp Normal file
View File

@ -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;
}

41
src/cli/DatabaseEdit.h Normal file
View File

@ -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

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Info.h" #include "DatabaseInfo.h"
#include "Utils.h" #include "Utils.h"
#include "core/DatabaseStats.h" #include "core/DatabaseStats.h"
@ -25,13 +25,13 @@
#include <QCommandLineParser> #include <QCommandLineParser>
Info::Info() DatabaseInfo::DatabaseInfo()
{ {
name = QString("db-info"); name = QString("db-info");
description = QObject::tr("Show a database's information."); 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; auto& out = Utils::STDOUT;

View File

@ -15,17 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KEEPASSXC_INFO_H #ifndef KEEPASSXC_DATABASEINFO_H
#define KEEPASSXC_INFO_H #define KEEPASSXC_DATABASEINFO_H
#include "DatabaseCommand.h" #include "DatabaseCommand.h"
class Info : public DatabaseCommand class DatabaseInfo : public DatabaseCommand
{ {
public: public:
Info(); DatabaseInfo();
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override; int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
}; };
#endif // KEEPASSXC_INFO_H #endif // KEEPASSXC_DATABASEINFO_H

View File

@ -17,7 +17,7 @@
#include "Import.h" #include "Import.h"
#include "Create.h" #include "DatabaseCreate.h"
#include "Utils.h" #include "Utils.h"
#include <QCommandLineParser> #include <QCommandLineParser>
@ -40,9 +40,10 @@ Import::Import()
description = QObject::tr("Import the contents of an XML database."); 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("xml"), QObject::tr("Path of the XML database export."), QString("")});
positionalArguments.append({QString("database"), QObject::tr("Path of the new database."), QString("")}); positionalArguments.append({QString("database"), QObject::tr("Path of the new database."), QString("")});
options.append(Create::SetKeyFileOption); options.append(DatabaseCreate::SetKeyFileOption);
options.append(Create::SetPasswordOption); options.append(DatabaseCreate::SetKeyFileShortOption);
options.append(Create::DecryptionTimeOption); options.append(DatabaseCreate::SetPasswordOption);
options.append(DatabaseCreate::DecryptionTimeOption);
} }
int Import::execute(const QStringList& arguments) int Import::execute(const QStringList& arguments)
@ -64,7 +65,7 @@ int Import::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser); QSharedPointer<Database> db = DatabaseCreate::initializeDatabaseFromOptions(parser);
if (!db) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -169,6 +169,36 @@ void CompositeKey::addKey(const QSharedPointer<Key>& key)
m_keys.append(key); m_keys.append(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 * @return list of Keys which are part of this CompositeKey
*/ */

View File

@ -43,6 +43,8 @@ public:
bool challenge(const QByteArray& seed, QByteArray& result, QString* error = nullptr) const; bool challenge(const QByteArray& seed, QByteArray& result, QString* error = nullptr) const;
void addKey(const QSharedPointer<Key>& key); 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; const QList<QSharedPointer<Key>>& keys() const;
void addChallengeResponseKey(const QSharedPointer<ChallengeResponseKey>& key); void addChallengeResponseKey(const QSharedPointer<ChallengeResponseKey>& key);

View File

@ -34,7 +34,9 @@
#include "cli/AttachmentImport.h" #include "cli/AttachmentImport.h"
#include "cli/AttachmentRemove.h" #include "cli/AttachmentRemove.h"
#include "cli/Clip.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/Diceware.h"
#include "cli/Edit.h" #include "cli/Edit.h"
#include "cli/Estimate.h" #include "cli/Estimate.h"
@ -42,7 +44,6 @@
#include "cli/Generate.h" #include "cli/Generate.h"
#include "cli/Help.h" #include "cli/Help.h"
#include "cli/Import.h" #include "cli/Import.h"
#include "cli/Info.h"
#include "cli/List.h" #include "cli/List.h"
#include "cli/Merge.h" #include "cli/Merge.h"
#include "cli/Move.h" #include "cli/Move.h"
@ -242,7 +243,7 @@ void TestCli::testBatchCommands()
QVERIFY(Commands::getCommand("show")); QVERIFY(Commands::getCommand("show"));
QVERIFY(Commands::getCommand("search")); QVERIFY(Commands::getCommand("search"));
QVERIFY(!Commands::getCommand("doesnotexist")); QVERIFY(!Commands::getCommand("doesnotexist"));
QCOMPARE(Commands::getCommands().size(), 25); QCOMPARE(Commands::getCommands().size(), 26);
} }
void TestCli::testInteractiveCommands() void TestCli::testInteractiveCommands()
@ -274,7 +275,7 @@ void TestCli::testInteractiveCommands()
QVERIFY(Commands::getCommand("show")); QVERIFY(Commands::getCommand("show"));
QVERIFY(Commands::getCommand("search")); QVERIFY(Commands::getCommand("search"));
QVERIFY(!Commands::getCommand("doesnotexist")); QVERIFY(!Commands::getCommand("doesnotexist"));
QCOMPARE(Commands::getCommands().size(), 25); QCOMPARE(Commands::getCommands().size(), 26);
} }
void TestCli::testAdd() void TestCli::testAdd()
@ -732,7 +733,7 @@ void TestCli::testClip()
void TestCli::testCreate() void TestCli::testCreate()
{ {
Create createCmd; DatabaseCreate createCmd;
QVERIFY(!createCmd.name.isEmpty()); QVERIFY(!createCmd.name.isEmpty());
QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name)); QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name));
@ -848,9 +849,147 @@ void TestCli::testCreate()
QVERIFY(db); 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() void TestCli::testInfo()
{ {
Info infoCmd; DatabaseInfo infoCmd;
QVERIFY(!infoCmd.name.isEmpty()); QVERIFY(!infoCmd.name.isEmpty());
QVERIFY(infoCmd.getDescriptionLine().contains(infoCmd.name)); QVERIFY(infoCmd.getDescriptionLine().contains(infoCmd.name));
@ -1613,7 +1752,7 @@ void TestCli::testMerge()
void TestCli::testMergeWithKeys() void TestCli::testMergeWithKeys()
{ {
Create createCmd; DatabaseCreate createCmd;
QVERIFY(!createCmd.name.isEmpty()); QVERIFY(!createCmd.name.isEmpty());
QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name)); QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name));

View File

@ -54,6 +54,7 @@ private slots:
void testCommandParsing_data(); void testCommandParsing_data();
void testCommandParsing(); void testCommandParsing();
void testCreate(); void testCreate();
void testDatabaseEdit();
void testDiceware(); void testDiceware();
void testEdit(); void testEdit();
void testEstimate_data(); void testEstimate_data();