mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-22 20:51:23 -05:00
Select group when adding credentials from browser extension (#2637)
This commit is contained in:
parent
779b529da2
commit
891f67a1cd
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user