From 14e868d2f7bb64dc590b54710ba897c878caba91 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sat, 16 Mar 2019 10:34:50 +0200 Subject: [PATCH] Allow creating new groups with Browser Integration Added a confirmation dialog --- src/browser/BrowserAction.cpp | 40 +++++++++++++++++ src/browser/BrowserAction.h | 4 +- src/browser/BrowserService.cpp | 80 ++++++++++++++++++++++++++++++++-- src/browser/BrowserService.h | 3 +- 4 files changed, 122 insertions(+), 5 deletions(-) diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 8c96568a..59db5dc7 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -88,6 +88,8 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json) return handleLockDatabase(json, action); } else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) { return handleGetDatabaseGroups(json, action); + } else if (action.compare("create-new-group", Qt::CaseSensitive) == 0) { + return handleCreateNewGroup(json, action); } // Action was not recognized @@ -407,6 +409,42 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons return buildResponse(action, message, newNonce); } +QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action) +{ + const QString hash = getDatabaseHash(); + const QString nonce = json.value("nonce").toString(); + const QString encrypted = json.value("message").toString(); + + QMutexLocker locker(&m_mutex); + 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("create-new-group", Qt::CaseSensitive) != 0) { + return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); + } + + QString group = decrypted.value("groupName").toString(); + const QJsonObject newGroup = m_browserService.createNewGroup(group); + if (newGroup.isEmpty() || newGroup["name"].toString().isEmpty() || newGroup["uuid"].toString().isEmpty()) { + return getErrorReply(action, ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP); + } + + const QString newNonce = incrementNonce(nonce); + + QJsonObject message = buildMessage(newNonce); + message["name"] = newGroup["name"]; + message["uuid"] = newGroup["uuid"]; + + return buildResponse(action, message, newNonce); +} + QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const { QJsonObject response; @@ -468,6 +506,8 @@ QString BrowserAction::getErrorMessage(const int errorCode) const return QObject::tr("No logins found"); case ERROR_KEEPASS_NO_GROUPS_FOUND: return QObject::tr("No groups found"); + case ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP: + return QObject::tr("Cannot create new group"); default: return QObject::tr("Unknown error"); } diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index 29736ab4..53517093 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -46,7 +46,8 @@ class BrowserAction : public QObject ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13, ERROR_KEEPASS_NO_URL_PROVIDED = 14, 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 }; public: @@ -66,6 +67,7 @@ private: QJsonObject handleSetLogin(const QJsonObject& json, const QString& action); QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action); QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action); + QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action); QJsonObject buildMessage(const QString& nonce) const; QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce); diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 807f61cc..59989fcc 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -150,7 +150,7 @@ QString BrowserService::getDatabaseRecycleBinUuid() return recycleBin->uuidToHex(); } -QJsonArray BrowserService::addChildrenToGroup(Group* group) +QJsonArray BrowserService::getChildrenFromGroup(Group* group) { QJsonArray groupList; @@ -166,7 +166,7 @@ QJsonArray BrowserService::addChildrenToGroup(Group* group) QJsonObject jsonGroup; jsonGroup["name"] = c->name(); jsonGroup["uuid"] = Tools::uuidToHex(c->uuid()); - jsonGroup["children"] = addChildrenToGroup(c); + jsonGroup["children"] = getChildrenFromGroup(c); groupList.push_back(jsonGroup); } return groupList; @@ -187,7 +187,7 @@ QJsonObject BrowserService::getDatabaseGroups() QJsonObject root; root["name"] = rootGroup->name(); root["uuid"] = Tools::uuidToHex(rootGroup->uuid()); - root["children"] = addChildrenToGroup(rootGroup); + root["children"] = getChildrenFromGroup(rootGroup); QJsonArray groups; groups.push_back(root); @@ -198,6 +198,80 @@ QJsonObject BrowserService::getDatabaseGroups() return result; } +QJsonObject BrowserService::createNewGroup(const QString& groupName) +{ + QJsonObject result; + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "createNewGroup", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QJsonObject, result), Q_ARG(QString, groupName)); + return result; + } + + auto db = getDatabase(); + if (!db) { + return {}; + } + + Group* rootGroup = db->rootGroup(); + if (!rootGroup) { + return {}; + } + + auto group = rootGroup->findGroupByPath(groupName); + + // Group already exists + if (group) { + result["name"] = group->name(); + result["uuid"] = Tools::uuidToHex(group->uuid()); + return result; + } + + auto dialogResult = MessageBox::warning(nullptr, + tr("KeePassXC: Create a new group"), + tr("A request for creating a new group \"%1\" has been received.\n" + "Do you want to create this group?\n").arg(groupName), + MessageBox::Yes | MessageBox::No); + + if (dialogResult != MessageBox::Yes) { + return result; + } + + QString name, uuid; + Group* previousGroup = rootGroup; + auto groups = groupName.split("/"); + + // Returns the group name based on depth + auto getGroupName = [&](int depth) { + QString gName; + for (int i = 0; i < depth+1; ++i) { + gName.append((i == 0 ? "" : "/") + groups[i]); + } + return gName; + }; + + // Create new group(s) always when the path is not found + for (int i = 0; i < groups.length(); ++i) { + QString gName = getGroupName(i); + auto tempGroup = rootGroup->findGroupByPath(gName); + if (!tempGroup) { + Group* newGroup = new Group(); + newGroup->setName(groups[i]); + newGroup->setUuid(QUuid::createUuid()); + newGroup->setParent(previousGroup); + name = newGroup->name(); + uuid = Tools::uuidToHex(newGroup->uuid()); + previousGroup = newGroup; + continue; + } + + previousGroup = tempGroup; + } + + result["name"] = name; + result["uuid"] = uuid; + return result; +} + QString BrowserService::storeKey(const QString& key) { QString id; diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index b002a9b9..a8f04262 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -45,6 +45,7 @@ public: QString getDatabaseRootUuid(); QString getDatabaseRecycleBinUuid(); QJsonObject getDatabaseGroups(); + QJsonObject createNewGroup(const QString& groupName); QString getKey(const QString& id); void addEntry(const QString& id, const QString& login, @@ -121,7 +122,7 @@ private: QString baseDomain(const QString& url) const; QSharedPointer getDatabase(); QSharedPointer selectedDatabase(); - QJsonArray addChildrenToGroup(Group* group); + QJsonArray getChildrenFromGroup(Group* group); bool moveSettingsToCustomData(Entry* entry, const QString& name) const; int moveKeysToCustomData(Entry* entry, const QSharedPointer& db) const; bool checkLegacySettings();