Add delete-entry command to Browser Integration API

This commit is contained in:
varjolintu 2021-09-06 18:38:25 +03:00 committed by Jonathan White
parent e3c7b570ae
commit 4c10e516c3
8 changed files with 116 additions and 9 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -22,6 +22,7 @@
#include "BrowserShared.h" #include "BrowserShared.h"
#include "config-keepassx.h" #include "config-keepassx.h"
#include "core/Global.h" #include "core/Global.h"
#include "core/Tools.h"
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -51,7 +52,8 @@ namespace
ERROR_KEEPASS_NO_URL_PROVIDED = 14, ERROR_KEEPASS_NO_URL_PROVIDED = 14,
ERROR_KEEPASS_NO_LOGINS_FOUND = 15, ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
ERROR_KEEPASS_NO_GROUPS_FOUND = 16, ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17 ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17,
ERROR_KEEPASS_NO_VALID_UUID_PROVIDED = 18
}; };
} }
@ -112,6 +114,8 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json)
return handleCreateNewGroup(json, action); return handleCreateNewGroup(json, action);
} else if (action.compare("get-totp", Qt::CaseSensitive) == 0) { } else if (action.compare("get-totp", Qt::CaseSensitive) == 0) {
return handleGetTotp(json, action); return handleGetTotp(json, action);
} else if (action.compare("delete-entry", Qt::CaseSensitive) == 0) {
return handleDeleteEntry(json, action);
} }
// Action was not recognized // Action was not recognized
@ -360,6 +364,10 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
if (uuid.isEmpty()) { if (uuid.isEmpty()) {
browserService()->addEntry(id, login, password, url, submitUrl, realm, group, groupUuid); browserService()->addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
} else { } else {
if (!Tools::isValidUuid(uuid)) {
return getErrorReply(action, ERROR_KEEPASS_NO_VALID_UUID_PROVIDED);
}
result = browserService()->updateEntry(id, uuid, login, password, url, submitUrl); result = browserService()->updateEntry(id, uuid, login, password, url, submitUrl);
} }
@ -490,6 +498,9 @@ QJsonObject BrowserAction::handleGetTotp(const QJsonObject& json, const QString&
} }
const QString uuid = decrypted.value("uuid").toString(); const QString uuid = decrypted.value("uuid").toString();
if (!Tools::isValidUuid(uuid)) {
return getErrorReply(action, ERROR_KEEPASS_NO_VALID_UUID_PROVIDED);
}
// Get the current TOTP // Get the current TOTP
const auto totp = browserService()->getCurrentTotp(uuid); const auto totp = browserService()->getCurrentTotp(uuid);
@ -501,6 +512,39 @@ QJsonObject BrowserAction::handleGetTotp(const QJsonObject& json, const QString&
return buildResponse(action, message, newNonce); return buildResponse(action, message, newNonce);
} }
QJsonObject BrowserAction::handleDeleteEntry(const QJsonObject& json, const QString& action)
{
const QString nonce = json.value("nonce").toString();
const QString encrypted = json.value("message").toString();
if (!m_associated) {
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
}
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
if (decrypted.isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
}
QString command = decrypted.value("action").toString();
if (command.isEmpty() || command.compare("delete-entry", Qt::CaseSensitive) != 0) {
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
}
const auto uuid = decrypted.value("uuid").toString();
if (!Tools::isValidUuid(uuid)) {
return getErrorReply(action, ERROR_KEEPASS_NO_VALID_UUID_PROVIDED);
}
const auto result = browserService()->deleteEntry(uuid);
const QString newNonce = incrementNonce(nonce);
QJsonObject message = buildMessage(newNonce);
message["success"] = result ? TRUE_STR : FALSE_STR;
return buildResponse(action, message, newNonce);
}
QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
{ {
QJsonObject response; QJsonObject response;
@ -564,6 +608,8 @@ QString BrowserAction::getErrorMessage(const int errorCode) const
return QObject::tr("No groups found"); return QObject::tr("No groups found");
case ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP: case ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP:
return QObject::tr("Cannot create new group"); return QObject::tr("Cannot create new group");
case ERROR_KEEPASS_NO_VALID_UUID_PROVIDED:
return QObject::tr("No valid UUID provided");
default: default:
return QObject::tr("Unknown error"); return QObject::tr("Unknown error");
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -43,6 +43,7 @@ private:
QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action); QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action);
QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action); QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action);
QJsonObject handleGetTotp(const QJsonObject& json, const QString& action); QJsonObject handleGetTotp(const QJsonObject& json, const QString& action);
QJsonObject handleDeleteEntry(const QJsonObject& json, const QString& action);
QJsonObject buildMessage(const QString& nonce) const; QJsonObject buildMessage(const QString& nonce) const;
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce); QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2013 Francois Ferrand * Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com> * Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -578,6 +578,32 @@ bool BrowserService::updateEntry(const QString& dbid,
return result; return result;
} }
bool BrowserService::deleteEntry(const QString& uuid)
{
auto db = selectedDatabase();
if (!db) {
return false;
}
auto* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
if (!entry) {
return false;
}
auto dialogResult = MessageBox::warning(nullptr,
tr("KeePassXC: Delete entry"),
tr("A request for deleting entry \"%1\" has been received.\n"
"Do you want to delete the entry?\n")
.arg(entry->title()),
MessageBox::Yes | MessageBox::No);
if (dialogResult != MessageBox::Yes) {
return false;
}
db->recycleEntry(entry);
return true;
}
QList<Entry*> QList<Entry*>
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& siteUrlStr, const QString& formUrlStr) BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& siteUrlStr, const QString& formUrlStr)
{ {

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2013 Francois Ferrand * Copyright (C) 2013 Francois Ferrand
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com> * Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -71,6 +71,7 @@ public:
const QString& password, const QString& password,
const QString& siteUrlStr, const QString& siteUrlStr,
const QString& formUrlStr); const QString& formUrlStr);
bool deleteEntry(const QString& uuid);
QJsonArray findMatchingEntries(const QString& dbid, QJsonArray findMatchingEntries(const QString& dbid,
const QString& siteUrlStr, const QString& siteUrlStr,

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de> * Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 Lennart Glauer <mail@lennart-glauer.de> * Copyright (C) 2017 Lennart Glauer <mail@lennart-glauer.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -317,6 +317,20 @@ namespace Tools
return QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1())); return QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1()));
} }
bool isValidUuid(const QString& uuidStr)
{
if (uuidStr.isEmpty() || uuidStr.length() != 32 || !isHex(uuidStr.toLatin1())) {
return false;
}
const auto uuid = hexToUuid(uuidStr);
if (uuid.isNull() || uuid.version() == QUuid::VerUnknown) {
return false;
}
return true;
}
QString envSubstitute(const QString& filepath, QProcessEnvironment environment) QString envSubstitute(const QString& filepath, QProcessEnvironment environment)
{ {
QString subbed = filepath; QString subbed = filepath;

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de> * Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -40,6 +40,7 @@ namespace Tools
bool checkUrlValid(const QString& urlField); bool checkUrlValid(const QString& urlField);
QString uuidToHex(const QUuid& uuid); QString uuidToHex(const QUuid& uuid);
QUuid hexToUuid(const QString& uuid); QUuid hexToUuid(const QString& uuid);
bool isValidUuid(const QString& uuidStr);
QRegularExpression convertToRegex(const QString& string, QRegularExpression convertToRegex(const QString& string,
bool useWildcards = false, bool useWildcards = false,
bool exactMatch = false, bool exactMatch = false,

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -88,3 +88,20 @@ void TestTools::testEnvSubstitute()
QCOMPARE(Tools::envSubstitute("start/$EMPTY$$EMPTY$HOME/end", environment), QString("start/$/home/user/end")); QCOMPARE(Tools::envSubstitute("start/$EMPTY$$EMPTY$HOME/end", environment), QString("start/$/home/user/end"));
#endif #endif
} }
void TestTools::testValidUuid()
{
auto validUuid = Tools::uuidToHex(QUuid::createUuid());
auto nonValidUuid = "1234567890abcdef1234567890abcdef";
auto emptyUuid = QString();
auto shortUuid = validUuid.left(10);
auto longUuid = validUuid + "baddata";
auto nonHexUuid = Tools::uuidToHex(QUuid::createUuid()).replace(0, 1, 'p');
QVERIFY(Tools::isValidUuid(validUuid));
QVERIFY(not Tools::isValidUuid(nonValidUuid));
QVERIFY(not Tools::isValidUuid(emptyUuid));
QVERIFY(not Tools::isValidUuid(shortUuid));
QVERIFY(not Tools::isValidUuid(longUuid));
QVERIFY(not Tools::isValidUuid(nonHexUuid));
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -28,6 +28,7 @@ private slots:
void testIsHex(); void testIsHex();
void testIsBase64(); void testIsBase64();
void testEnvSubstitute(); void testEnvSubstitute();
void testValidUuid();
}; };
#endif // KEEPASSX_TESTTOOLS_H #endif // KEEPASSX_TESTTOOLS_H