Select group when adding credentials from browser extension (#2637)

This commit is contained in:
Sami Vänttinen 2019-01-30 16:48:22 +02:00 committed by Jonathan White
parent 779b529da2
commit 891f67a1cd
10 changed files with 178 additions and 11 deletions

View File

@ -85,6 +85,8 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json)
return handleSetLogin(json, action); return handleSetLogin(json, action);
} else if (action.compare("lock-database", Qt::CaseSensitive) == 0) { } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
return handleLockDatabase(json, action); return handleLockDatabase(json, action);
} else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) {
return handleGetDatabaseGroups(json, action);
} }
// Action was not recognized // Action was not recognized
@ -320,10 +322,12 @@ QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString
const QString password = decrypted.value("password").toString(); const QString password = decrypted.value("password").toString();
const QString submitUrl = decrypted.value("submitUrl").toString(); const QString submitUrl = decrypted.value("submitUrl").toString();
const QString uuid = decrypted.value("uuid").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; const QString realm;
if (uuid.isEmpty()) { if (uuid.isEmpty()) {
m_browserService.addEntry(id, login, password, url, submitUrl, realm); m_browserService.addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
} else { } else {
m_browserService.updateEntry(id, uuid, login, password, url, submitUrl); m_browserService.updateEntry(id, uuid, login, password, url, submitUrl);
} }
@ -368,6 +372,40 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED); 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);
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 BrowserAction::getErrorReply(const QString& action, const int errorCode) const
{ {
QJsonObject response; QJsonObject response;
@ -427,6 +465,8 @@ QString BrowserAction::getErrorMessage(const int errorCode) const
return QObject::tr("No URL provided"); return QObject::tr("No URL provided");
case ERROR_KEEPASS_NO_LOGINS_FOUND: case ERROR_KEEPASS_NO_LOGINS_FOUND:
return QObject::tr("No logins found"); return QObject::tr("No logins found");
case ERROR_KEEPASS_NO_GROUPS_FOUND:
return QObject::tr("No groups found");
default: default:
return QObject::tr("Unknown error"); return QObject::tr("Unknown error");
} }

View File

@ -45,7 +45,8 @@ class BrowserAction : public QObject
ERROR_KEEPASS_INCORRECT_ACTION = 12, ERROR_KEEPASS_INCORRECT_ACTION = 12,
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13, ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
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
}; };
public: public:
@ -64,6 +65,7 @@ private:
QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action); QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action);
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action); QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
QJsonObject handleLockDatabase(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 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

@ -56,7 +56,7 @@ BrowserService::BrowserService(DatabaseTabWidget* parent)
, m_bringToFrontRequested(false) , m_bringToFrontRequested(false)
, m_wasMinimized(false) , m_wasMinimized(false)
, m_wasHidden(false) , m_wasHidden(false)
, m_keepassBrowserUUID(QUuid::fromRfc4122(QByteArray::fromHex("de887cc3036343b8974b5911b8816224"))) , m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
{ {
// Don't connect the signals when used from DatabaseSettingsWidgetBrowser (parent is nullptr) // Don't connect the signals when used from DatabaseSettingsWidgetBrowser (parent is nullptr)
if (m_dbTabWidget) { if (m_dbTabWidget) {
@ -151,6 +151,54 @@ QString BrowserService::getDatabaseRecycleBinUuid()
return recycleBin->uuidToHex(); 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 BrowserService::storeKey(const QString& key)
{ {
QString id; QString id;
@ -298,6 +346,8 @@ void BrowserService::addEntry(const QString& id,
const QString& url, const QString& url,
const QString& submitUrl, const QString& submitUrl,
const QString& realm, const QString& realm,
const QString& group,
const QString& groupUuid,
QSharedPointer<Database> selectedDb) QSharedPointer<Database> selectedDb)
{ {
if (thread() != QThread::currentThread()) { if (thread() != QThread::currentThread()) {
@ -310,6 +360,8 @@ void BrowserService::addEntry(const QString& id,
Q_ARG(QString, url), Q_ARG(QString, url),
Q_ARG(QString, submitUrl), Q_ARG(QString, submitUrl),
Q_ARG(QString, realm), Q_ARG(QString, realm),
Q_ARG(QString, group),
Q_ARG(QString, groupUuid),
Q_ARG(QSharedPointer<Database>, selectedDb)); Q_ARG(QSharedPointer<Database>, selectedDb));
} }
@ -318,8 +370,8 @@ void BrowserService::addEntry(const QString& id,
return; return;
} }
Group* group = findCreateAddEntryGroup(db); auto* addEntryGroup = findCreateAddEntryGroup(db);
if (!group) { if (!addEntryGroup) {
return; return;
} }
@ -330,7 +382,17 @@ void BrowserService::addEntry(const QString& id,
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON); entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
entry->setUsername(login); entry->setUsername(login);
entry->setPassword(password); entry->setPassword(password);
entry->setGroup(group); entry->setGroup(addEntryGroup);
// Select a group for the entry
if (!group.isEmpty()) {
if (db->rootGroup()) {
auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid));
if (selectedGroup && selectedGroup->name() == group) {
entry->setGroup(selectedGroup);
}
}
}
const QString host = QUrl(url).host(); const QString host = QUrl(url).host();
const QString submitHost = QUrl(submitUrl).host(); const QString submitHost = QUrl(submitUrl).host();
@ -370,10 +432,10 @@ void BrowserService::updateEntry(const QString& id,
return; return;
} }
Entry* entry = db->rootGroup()->findEntryByUuid(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1()))); Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
if (!entry) { if (!entry) {
// If entry is not found for update, add a new one to the selected database // 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; return;
} }
@ -715,7 +777,7 @@ Group* BrowserService::findCreateAddEntryGroup(QSharedPointer<Database> selected
return nullptr; return nullptr;
} }
Group* rootGroup = db->rootGroup(); auto* rootGroup = db->rootGroup();
if (!rootGroup) { if (!rootGroup) {
return nullptr; return nullptr;
} }
@ -723,8 +785,8 @@ Group* BrowserService::findCreateAddEntryGroup(QSharedPointer<Database> selected
const QString groupName = const QString groupName =
QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); // TODO: setting to decide where new keys are created QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); // TODO: setting to decide where new keys are created
for (const Group* g : rootGroup->groupsRecursive(true)) { for (auto* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName) { if (g->name() == groupName && !g->isRecycled()) {
return db->rootGroup()->findGroupByUuid(g->uuid()); return db->rootGroup()->findGroupByUuid(g->uuid());
} }
} }

View File

@ -44,6 +44,7 @@ public:
bool openDatabase(bool triggerUnlock); bool openDatabase(bool triggerUnlock);
QString getDatabaseRootUuid(); QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid(); QString getDatabaseRecycleBinUuid();
QJsonObject getDatabaseGroups();
QString getKey(const QString& id); QString getKey(const QString& id);
void addEntry(const QString& id, void addEntry(const QString& id,
const QString& login, const QString& login,
@ -51,6 +52,8 @@ public:
const QString& url, const QString& url,
const QString& submitUrl, const QString& submitUrl,
const QString& realm, const QString& realm,
const QString& group,
const QString& groupUuid,
QSharedPointer<Database> selectedDb = {}); QSharedPointer<Database> selectedDb = {});
QList<Entry*> searchEntries(QSharedPointer<Database> db, const QString& hostname, const QString& url); QList<Entry*> searchEntries(QSharedPointer<Database> db, const QString& hostname, const QString& url);
QList<Entry*> searchEntries(const QString& url, const StringPairList& keyList); QList<Entry*> searchEntries(const QString& url, const StringPairList& keyList);
@ -111,6 +114,7 @@ private:
QString baseDomain(const QString& url) const; QString baseDomain(const QString& url) const;
QSharedPointer<Database> getDatabase(); QSharedPointer<Database> getDatabase();
QSharedPointer<Database> selectedDatabase(); QSharedPointer<Database> selectedDatabase();
QJsonArray addChildrenToGroup(Group* group);
bool moveSettingsToCustomData(Entry* entry, const QString& name) const; bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const; int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const;
bool checkLegacySettings(); bool checkLegacySettings();

View File

@ -257,6 +257,25 @@ Entry* Group::lastTopVisibleEntry() const
return m_lastTopVisibleEntry; return m_lastTopVisibleEntry;
} }
bool Group::isRecycled()
{
Group* group = this;
if (!group->database()) {
return false;
}
do {
if (group->m_parent && group->m_db->metadata()) {
if (group->m_parent == group->m_db->metadata()->recycleBin()) {
return true;
}
}
group = group->m_parent;
} while (group && group->m_parent && group->m_parent != group->m_db->rootGroup());
return false;
}
bool Group::isExpired() const bool Group::isExpired() const
{ {
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTimeUtc(); return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTimeUtc();

View File

@ -102,6 +102,7 @@ public:
bool resolveAutoTypeEnabled() const; bool resolveAutoTypeEnabled() const;
Entry* lastTopVisibleEntry() const; Entry* lastTopVisibleEntry() const;
bool isExpired() const; bool isExpired() const;
bool isRecycled();
CustomData* customData(); CustomData* customData();
const CustomData* customData() const; const CustomData* customData() const;

View File

@ -213,6 +213,11 @@ namespace Tools
return QString::fromLatin1(uuid.toRfc4122().toHex()); return QString::fromLatin1(uuid.toRfc4122().toHex());
} }
QUuid hexToUuid(const QString& uuid)
{
return QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1()));
}
Buffer::Buffer() Buffer::Buffer()
: raw(nullptr) : raw(nullptr)
, size(0) , size(0)

View File

@ -41,6 +41,7 @@ namespace Tools
void sleep(int ms); void sleep(int ms);
void wait(int ms); void wait(int ms);
QString uuidToHex(const QUuid& uuid); QString uuidToHex(const QUuid& uuid);
QUuid hexToUuid(const QString& uuid);
QRegularExpression convertToRegex(const QString& string, QRegularExpression convertToRegex(const QString& string,
bool useWildcards = false, bool useWildcards = false,
bool exactMatch = false, bool exactMatch = false,

View File

@ -766,3 +766,35 @@ void TestGroup::testAddEntryWithPath()
delete db; delete db;
} }
void TestGroup::testIsRecycled()
{
Database* db = new Database();
db->rootGroup()->createRecycleBin();
Group* group1 = new Group();
group1->setName("group1");
group1->setParent(db->rootGroup());
Group* group2 = new Group();
group2->setName("group2");
group2->setParent(db->rootGroup());
Group* group3 = new Group();
group3->setName("group3");
group3->setParent(group2);
Group* group4 = new Group();
group4->setName("group4");
group4->setParent(db->rootGroup());
db->recycleGroup(group2);
QVERIFY(!group1->isRecycled());
QVERIFY(group2->isRecycled());
QVERIFY(group3->isRecycled());
QVERIFY(!group4->isRecycled());
db->recycleGroup(group4);
QVERIFY(group4->isRecycled());
}

View File

@ -42,6 +42,7 @@ private slots:
void testPrint(); void testPrint();
void testLocate(); void testLocate();
void testAddEntryWithPath(); void testAddEntryWithPath();
void testIsRecycled();
}; };
#endif // KEEPASSX_TESTGROUP_H #endif // KEEPASSX_TESTGROUP_H