Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select group when adding credentials from browser extension #2637

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/browser/BrowserAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,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
Expand Down Expand Up @@ -320,10 +322,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);
}
Expand Down Expand Up @@ -368,6 +372,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);
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;
Expand Down Expand Up @@ -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");
}
Expand Down
4 changes: 3 additions & 1 deletion src/browser/BrowserAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);
Expand Down
80 changes: 71 additions & 9 deletions src/browser/BrowserService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ BrowserService::BrowserService(DatabaseTabWidget* parent)
, m_bringToFrontRequested(false)
, m_wasMinimized(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)
if (m_dbTabWidget) {
Expand Down Expand Up @@ -151,6 +151,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;
Expand Down Expand Up @@ -298,6 +346,8 @@ void BrowserService::addEntry(const QString& id,
const QString& url,
const QString& submitUrl,
const QString& realm,
const QString& group,
const QString& groupUuid,
QSharedPointer<Database> selectedDb)
{
if (thread() != QThread::currentThread()) {
Expand All @@ -310,6 +360,8 @@ void BrowserService::addEntry(const QString& id,
Q_ARG(QString, url),
Q_ARG(QString, submitUrl),
Q_ARG(QString, realm),
Q_ARG(QString, group),
Q_ARG(QString, groupUuid),
Q_ARG(QSharedPointer<Database>, selectedDb));
}

Expand All @@ -318,8 +370,8 @@ void BrowserService::addEntry(const QString& id,
return;
}

Group* group = findCreateAddEntryGroup(db);
if (!group) {
auto* addEntryGroup = findCreateAddEntryGroup(db);
if (!addEntryGroup) {
return;
}

Expand All @@ -330,7 +382,17 @@ void BrowserService::addEntry(const QString& id,
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 selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid));
if (selectedGroup && selectedGroup->name() == group) {
entry->setGroup(selectedGroup);
}
}
}

const QString host = QUrl(url).host();
const QString submitHost = QUrl(submitUrl).host();
Expand Down Expand Up @@ -370,10 +432,10 @@ void BrowserService::updateEntry(const QString& id,
return;
}

Entry* entry = db->rootGroup()->findEntryByUuid(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1())));
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
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;
}

Expand Down Expand Up @@ -715,16 +777,16 @@ Group* BrowserService::findCreateAddEntryGroup(QSharedPointer<Database> selected
return nullptr;
}

Group* rootGroup = db->rootGroup();
auto* rootGroup = db->rootGroup();
if (!rootGroup) {
return nullptr;
}

const QString groupName =
QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); // TODO: setting to decide where new keys are created

for (const Group* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName) {
for (auto* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName && !g->isRecycled()) {
return db->rootGroup()->findGroupByUuid(g->uuid());
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/browser/BrowserService.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ 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,
const QString& password,
const QString& url,
const QString& submitUrl,
const QString& realm,
const QString& group,
const QString& groupUuid,
QSharedPointer<Database> selectedDb = {});
QList<Entry*> searchEntries(QSharedPointer<Database> db, const QString& hostname, const QString& url);
QList<Entry*> searchEntries(const QString& url, const StringPairList& keyList);
Expand Down Expand Up @@ -111,6 +114,7 @@ public slots:
QString baseDomain(const QString& url) const;
QSharedPointer<Database> getDatabase();
QSharedPointer<Database> selectedDatabase();
QJsonArray addChildrenToGroup(Group* group);
bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const;
bool checkLegacySettings();
Expand Down
19 changes: 19 additions & 0 deletions src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,25 @@ Entry* Group::lastTopVisibleEntry() const
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
{
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTimeUtc();
Expand Down
1 change: 1 addition & 0 deletions src/core/Group.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class Group : public QObject
bool resolveAutoTypeEnabled() const;
Entry* lastTopVisibleEntry() const;
bool isExpired() const;
bool isRecycled();
CustomData* customData();
const CustomData* customData() const;

Expand Down
5 changes: 5 additions & 0 deletions src/core/Tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ namespace Tools
return QString::fromLatin1(uuid.toRfc4122().toHex());
}

QUuid hexToUuid(const QString& uuid)
{
return QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1()));
}

Buffer::Buffer()
: raw(nullptr)
, size(0)
Expand Down
1 change: 1 addition & 0 deletions src/core/Tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ namespace Tools
void sleep(int ms);
void wait(int ms);
QString uuidToHex(const QUuid& uuid);
QUuid hexToUuid(const QString& uuid);
QRegularExpression convertToRegex(const QString& string,
bool useWildcards = false,
bool exactMatch = false,
Expand Down
32 changes: 32 additions & 0 deletions tests/TestGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,3 +766,35 @@ void TestGroup::testAddEntryWithPath()

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());
}
1 change: 1 addition & 0 deletions tests/TestGroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private slots:
void testPrint();
void testLocate();
void testAddEntryWithPath();
void testIsRecycled();
};

#endif // KEEPASSX_TESTGROUP_H