From 8d5d88009e04217076c70384456fadba87ad3972 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 23 Jan 2019 09:38:01 +0200 Subject: [PATCH] Select group when adding credentials from browser extension --- src/browser/BrowserAction.cpp | 42 +++++++++++++++++++- src/browser/BrowserAction.h | 4 +- src/browser/BrowserService.cpp | 71 ++++++++++++++++++++++++++++++++-- src/browser/BrowserService.h | 4 ++ src/core/Group.cpp | 15 +++++++ src/core/Group.h | 1 + tests/TestGroup.cpp | 55 ++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 8 files changed, 187 insertions(+), 6 deletions(-) diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 4357718b36..0759104eb9 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -88,6 +88,8 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json) return handleSetLogin(json, action); } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) { return handleLockDatabase(json, action); + } else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) { + return handleGetDatabaseGroups(json, action); } // Action was not recognized @@ -319,10 +321,12 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString const QString password = decrypted.value("password").toString(); const QString submitUrl = decrypted.value("submitUrl").toString(); const QString uuid = decrypted.value("uuid").toString(); + const QString group = decrypted.value("group").toString(); + const QString groupUuid = decrypted.value("groupUuid").toString(); const QString realm; if (uuid.isEmpty()) { - m_browserService.addEntry(id, login, password, url, submitUrl, realm); + m_browserService.addEntry(id, login, password, url, submitUrl, realm, group, groupUuid); } else { m_browserService.updateEntry(id, uuid, login, password, url, submitUrl); } @@ -367,6 +371,40 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED); } +QJsonObject BrowserAction::handleGetDatabaseGroups(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, action); + if (decrypted.isEmpty()) { + return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE); + } + + QString command = decrypted.value("action").toString(); + if (command.isEmpty() || command.compare("get-database-groups", Qt::CaseSensitive) != 0) { + return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); + } + + const QJsonObject groups = m_browserService.getDatabaseGroups(); + if (groups.isEmpty()) { + return getErrorReply(action, ERROR_KEEPASS_NO_GROUPS_FOUND); + } + + const QString newNonce = incrementNonce(nonce); + + QJsonObject message = buildMessage(newNonce); + message["groups"] = groups; + + return buildResponse(action, message, newNonce); +} + QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const { QJsonObject response; @@ -427,6 +465,8 @@ QString BrowserAction::getErrorMessage(const int errorCode) const return QObject::tr("No URL provided"); case ERROR_KEEPASS_NO_LOGINS_FOUND: return QObject::tr("No logins found"); + case ERROR_KEEPASS_NO_GROUPS_FOUND: + return QObject::tr("No groups found"); default: return QObject::tr("Unknown error"); } diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index c82af2c176..d65dfc5bfa 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -45,7 +45,8 @@ class BrowserAction : public QObject ERROR_KEEPASS_INCORRECT_ACTION = 12, ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13, ERROR_KEEPASS_NO_URL_PROVIDED = 14, - ERROR_KEEPASS_NO_LOGINS_FOUND = 15 + ERROR_KEEPASS_NO_LOGINS_FOUND = 15, + ERROR_KEEPASS_NO_GROUPS_FOUND = 16 }; public: @@ -64,6 +65,7 @@ class BrowserAction : public QObject QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action); 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 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 a1ea5d6849..5b1debb09e 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -148,6 +148,54 @@ QString BrowserService::getDatabaseRecycleBinUuid() return recycleBin->uuidToHex(); } +QJsonArray BrowserService::addChildrenToGroup(Group* group) +{ + QJsonArray groupList; + + if (!group) { + return groupList; + } + + for (const auto& c : group->children()) { + if (c == group->database()->metadata()->recycleBin()) { + continue; + } + + QJsonObject jsonGroup; + jsonGroup["name"] = c->name(); + jsonGroup["uuid"] = Tools::uuidToHex(c->uuid()); + jsonGroup["children"] = addChildrenToGroup(c); + groupList.push_back(jsonGroup); + } + return groupList; +} + +QJsonObject BrowserService::getDatabaseGroups() +{ + auto db = getDatabase(); + if (!db) { + return {}; + } + + Group* rootGroup = db->rootGroup(); + if (!rootGroup) { + return {}; + } + + QJsonObject root; + root["name"] = rootGroup->name(); + root["uuid"] = Tools::uuidToHex(rootGroup->uuid()); + root["children"] = addChildrenToGroup(rootGroup); + + QJsonArray groups; + groups.push_back(root); + + QJsonObject result; + result["groups"] = groups; + + return result; +} + QString BrowserService::storeKey(const QString& key) { QString id; @@ -292,6 +340,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id, void BrowserService::addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm, + const QString& group, const QString& groupUuid, QSharedPointer selectedDb) { if (thread() != QThread::currentThread()) { @@ -304,6 +353,8 @@ void BrowserService::addEntry(const QString& id, const QString& login, Q_ARG(QString, url), Q_ARG(QString, submitUrl), Q_ARG(QString, realm), + Q_ARG(QString, group), + Q_ARG(QString, groupUuid), Q_ARG(QSharedPointer, selectedDb)); } @@ -312,8 +363,8 @@ void BrowserService::addEntry(const QString& id, const QString& login, return; } - Group* group = findCreateAddEntryGroup(db); - if (!group) { + auto* addEntryGroup = findCreateAddEntryGroup(db); + if (!addEntryGroup) { return; } @@ -324,7 +375,19 @@ void BrowserService::addEntry(const QString& id, const QString& login, entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON); entry->setUsername(login); entry->setPassword(password); - entry->setGroup(group); + entry->setGroup(addEntryGroup); + + // Select a group for the entry + if (!group.isEmpty()) { + if (db->rootGroup()) { + auto groupList = db->rootGroup()->findGroupsByName(group); + for (const auto& g : groupList) { + if (g->name() == group && g->uuidToHex() == groupUuid) { + entry->setGroup(g); + } + } + } + } const QString host = QUrl(url).host(); const QString submitHost = QUrl(submitUrl).host(); @@ -367,7 +430,7 @@ void BrowserService::updateEntry(const QString& id, Entry* entry = db->rootGroup()->findEntryByUuid(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1()))); if (!entry) { // If entry is not found for update, add a new one to the selected database - addEntry(id, login, password, url, submitUrl, "", db); + addEntry(id, login, password, url, submitUrl, "", "", "", db); return; } diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index 8e1c45f4a4..39b00ad536 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -44,6 +44,7 @@ class BrowserService : public QObject bool openDatabase(bool triggerUnlock); QString getDatabaseRootUuid(); QString getDatabaseRecycleBinUuid(); + QJsonObject getDatabaseGroups(); QString getKey(const QString& id); void addEntry(const QString& id, const QString& login, @@ -51,6 +52,8 @@ class BrowserService : public QObject const QString& url, const QString& submitUrl, const QString& realm, + const QString& group, + const QString& groupUuid, QSharedPointer selectedDb = {}); QList searchEntries(QSharedPointer db, const QString& hostname, const QString& url); QList searchEntries(const QString& url, const StringPairList& keyList); @@ -114,6 +117,7 @@ public slots: QString baseDomain(const QString& url) const; QSharedPointer getDatabase(); QSharedPointer selectedDatabase(); + QJsonArray addChildrenToGroup(Group* group); bool moveSettingsToCustomData(Entry* entry, const QString& name) const; int moveKeysToCustomData(Entry* entry, QSharedPointer db) const; bool checkLegacySettings(); diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 6c4adbad7f..3c6f0d43cf 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -785,6 +785,21 @@ Group* Group::findGroupByUuid(const QUuid& uuid) return nullptr; } +QList Group::findGroupsByName(const QString& name) { + QList groupList; + if (name.isEmpty()) { + return groupList; + } + + for (Group* group : groupsRecursive(true)) { + if (group->name() == name) { + groupList.push_back(group); + } + } + + return groupList; +} + Group* Group::findChildByName(const QString& name) { for (Group* group : asConst(m_children)) { diff --git a/src/core/Group.h b/src/core/Group.h index d0074d5d3a..68b94b6257 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -119,6 +119,7 @@ class Group : public QObject Entry* findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType); Group* findGroupByUuid(const QUuid& uuid); Group* findGroupByPath(const QString& groupPath); + QList findGroupsByName(const QString& name); QStringList locate(const QString& locateTerm, const QString& currentPath = {"/"}) const; Entry* addEntryWithPath(const QString& entryPath); void setUuid(const QUuid& uuid); diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 7abb808d1a..689bf1e98f 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -615,6 +615,61 @@ void TestGroup::testFindGroupByPath() QVERIFY(!group); } +void TestGroup::testFindGroupsByName() +{ + QScopedPointer db(new Database()); + + Group* group1 = new Group(); + group1->setName("KeePassXC-Browser Passwords"); + group1->setParent(db->rootGroup()); + + Group* group2 = new Group(); + group2->setName("SecondRoot"); + group2->setParent(db->rootGroup()); + + Group* group3 = new Group(); + group3->setName("Child"); + group3->setParent(group2); + + Group* group4 = new Group(); + group4->setName("Grandchild"); + group4->setParent(group3); + + Group* group5 = new Group(); + group5->setName("ThirdRoot"); + group5->setParent(db->rootGroup()); + + Group* group6 = new Group(); + group6->setName("Child2"); + group6->setParent(group5); + + Group* group7 = new Group(); + group7->setName("Child2"); + group7->setParent(db->rootGroup()); + + QList groupList; + + groupList = db->rootGroup()->findGroupsByName("SecondRoot"); + QCOMPARE(groupList.size(), 1); + QCOMPARE(groupList.first()->name(), group2->name()); + QCOMPARE(groupList.first()->uuid(), group2->uuid()); + + groupList = db->rootGroup()->findGroupsByName("Grandchild"); + QCOMPARE(groupList.size(), 1); + QCOMPARE(groupList.first()->name(), group4->name()); + QCOMPARE(groupList.first()->uuid(), group4->uuid()); + + groupList = db->rootGroup()->findGroupsByName("Child2"); + QCOMPARE(groupList.size(), 2); + QCOMPARE(groupList[0]->name(), group6->name()); + QCOMPARE(groupList[0]->uuid(), group6->uuid()); + QCOMPARE(groupList[1]->name(), group7->name()); + QCOMPARE(groupList[1]->uuid(), group7->uuid()); + + groupList = db->rootGroup()->findGroupsByName("invalid"); + QCOMPARE(groupList.size(), 0); +} + void TestGroup::testPrint() { QScopedPointer db(new Database()); diff --git a/tests/TestGroup.h b/tests/TestGroup.h index c6ccb21f90..bc4759cd06 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -39,6 +39,7 @@ private slots: void testCopyCustomIcons(); void testFindEntry(); void testFindGroupByPath(); + void testFindGroupsByName(); void testPrint(); void testLocate(); void testAddEntryWithPath();