Skip to content

Commit

Permalink
Add a db-edit CLI command
Browse files Browse the repository at this point in the history
  • Loading branch information
louib committed Sep 13, 2022
1 parent d181f80 commit 3911b5d
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7847,6 +7847,42 @@ 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>
</context>
<context>
<name>QtIOCompressor</name>
Expand Down
1 change: 1 addition & 0 deletions src/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(cli_SOURCES
Create.cpp
Command.cpp
DatabaseCommand.cpp
DatabaseEdit.cpp
Diceware.cpp
Edit.cpp
Estimate.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/cli/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "Clip.h"
#include "Close.h"
#include "Create.h"
#include "DatabaseEdit.h"
#include "Diceware.h"
#include "Edit.h"
#include "Estimate.h"
Expand Down Expand Up @@ -173,6 +174,7 @@ namespace Commands
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-edit"), QSharedPointer<Command>(new DatabaseEdit()));
s_commands.insert(QStringLiteral("db-info"), QSharedPointer<Command>(new Info()));
s_commands.insert(QStringLiteral("diceware"), QSharedPointer<Command>(new Diceware()));
s_commands.insert(QStringLiteral("edit"), QSharedPointer<Command>(new Edit()));
Expand Down
19 changes: 15 additions & 4 deletions src/cli/Create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ const QCommandLineOption Create::DecryptionTimeOption =
QObject::tr("Target decryption time in MS for the database."),
QObject::tr("time"));

const QCommandLineOption Create::SetKeyFileShortOption =
QCommandLineOption(QStringList() << "k", QObject::tr("Set the key file for the database."), QObject::tr("path"));

const QCommandLineOption Create::SetKeyFileOption =
QCommandLineOption(QStringList() << "k"
<< "set-key-file",
QCommandLineOption(QStringList() << "set-key-file",
QObject::tr("Set the key file for the database."),
QObject::tr("path"));

Expand All @@ -46,6 +48,7 @@ Create::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::SetKeyFileShortOption);
options.append(Create::SetPasswordOption);
options.append(Create::DecryptionTimeOption);
}
Expand Down Expand Up @@ -87,10 +90,18 @@ QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPoin
key->addKey(passwordKey);
}

if (parser->isSet(Create::SetKeyFileOption)) {
if (parser->isSet(Create::SetKeyFileOption) || parser->isSet(Create::SetKeyFileShortOption)) {
QSharedPointer<FileKey> fileKey;

if (!Utils::loadFileKey(parser->value(Create::SetKeyFileOption), fileKey)) {
QString keyFilePath;
if (parser->isSet(Create::SetKeyFileShortOption)) {
qWarning("The -k option will be deprecated. Please use the --set-key-file option instead.");
keyFilePath = parser->value(Create::SetKeyFileShortOption);
} else {
keyFilePath = parser->value(Create::SetKeyFileOption);
}

if (!Utils::loadFileKey(keyFilePath, fileKey)) {
err << QObject::tr("Loading the key file failed") << endl;
return {};
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/Create.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Create : public Command
static QSharedPointer<Database> initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser);

static const QCommandLineOption SetKeyFileOption;
static const QCommandLineOption SetKeyFileShortOption;
static const QCommandLineOption SetPasswordOption;
static const QCommandLineOption DecryptionTimeOption;
};
Expand Down
172 changes: 172 additions & 0 deletions src/cli/DatabaseEdit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright (C) 2022 KeePassXC Team <[email protected]>
*
* 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/Create.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(Create::SetKeyFileOption);
options.append(Create::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(Create::SetPasswordOption) && parser->isSet(DatabaseEdit::UnsetPasswordOption)) {
err << QObject::tr("Cannot use %1 and %2 at the same time.")
.arg(Create::SetPasswordOption.names().at(0))
.arg(DatabaseEdit::UnsetPasswordOption.names().at(0))
<< endl;
return EXIT_FAILURE;
}

if (parser->isSet(Create::SetKeyFileOption) && parser->isSet(DatabaseEdit::UnsetKeyFileOption)) {
err << QObject::tr("Cannot use %1 and %2 at the same time.")
.arg(Create::SetKeyFileOption.names().at(0))
.arg(DatabaseEdit::UnsetKeyFileOption.names().at(0))
<< endl;
return EXIT_FAILURE;
}

bool hasKeyChange =
(parser->isSet(Create::SetPasswordOption) || parser->isSet(Create::SetKeyFileOption)
|| parser->isSet(DatabaseEdit::UnsetPasswordOption) || parser->isSet(DatabaseEdit::UnsetKeyFileOption));

if (hasKeyChange) {
auto newDatabaseKey = getNewDatabaseKey(database,
parser->isSet(Create::SetPasswordOption),
parser->isSet(DatabaseEdit::UnsetPasswordOption),
parser->value(Create::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();

for (const auto& key : database->key()->keys()) {
if (key->uuid() == PasswordKey::UUID) {
if (removePassword) {
continue;
}

if (!updatePassword) {
newDatabaseKey->addKey(key);
continue;
}

continue;
}

if (key->uuid() == FileKey::UUID) {
if (removeKeyFile) {
continue;
}

if (newFileKeyPath.isEmpty()) {
newDatabaseKey->addKey(key);
continue;
}

continue;
}

// Not sure that we should ever get here.
newDatabaseKey->addKey(key);
}

for (const auto& key : database->key()->challengeResponseKeys()) {
if (key->uuid() == ChallengeResponseKey::UUID) {
newDatabaseKey->addKey(key);
}
}

if (updatePassword) {
auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl;
return {};
}
newDatabaseKey->addKey(passwordKey);
}

if (!newFileKeyPath.isEmpty()) {
auto newFileKey = QSharedPointer<FileKey>::create();
QString errorMessage;
if (!newFileKey->load(newFileKeyPath, &errorMessage)) {
err << QObject::tr("Loading the new key file failed: %1").arg(errorMessage) << endl;
return {};
}
newDatabaseKey->addKey(newFileKey);
}

if (newDatabaseKey->keys().isEmpty()) {
err << QObject::tr("Cannot remove all the keys from a database.") << endl;
return {};
}

return newDatabaseKey;
}
41 changes: 41 additions & 0 deletions src/cli/DatabaseEdit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 KeePassXC Team <[email protected]>
*
* 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
1 change: 1 addition & 0 deletions src/cli/Import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Import::Import()
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::SetKeyFileShortOption);
options.append(Create::SetPasswordOption);
options.append(Create::DecryptionTimeOption);
}
Expand Down
Loading

0 comments on commit 3911b5d

Please sign in to comment.