mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Allow groups to restrict by browser integration key (#6437)
This commit is contained in:
parent
7bfe9065cf
commit
884386c924
@ -953,7 +953,7 @@ Do you want to overwrite the Passkey in %1 - %2?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC - New key association request</source>
|
||||
<source>Disable</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -972,6 +972,10 @@ Do you want to overwrite the Passkey in %1 - %2?</source>
|
||||
<source>KeePassXC - Delete entry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC - New key association request</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>BrowserSettingsWidget</name>
|
||||
@ -3209,6 +3213,14 @@ Would you like to correct it?</source>
|
||||
<source>Omit WWW subdomain from matching toggle for this and sub groups</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Restrict matching to given browser key:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Restrict matching to given browser key toggle for this and sub groups</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditGroupWidgetKeeShare</name>
|
||||
|
@ -64,6 +64,7 @@ const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEnt
|
||||
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
|
||||
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
|
||||
const QString BrowserService::OPTION_OMIT_WWW = QStringLiteral("BrowserOmitWww");
|
||||
const QString BrowserService::OPTION_RESTRICT_KEY = QStringLiteral("BrowserRestrictKey");
|
||||
|
||||
Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
||||
|
||||
@ -947,6 +948,7 @@ bool BrowserService::deleteEntry(const QString& uuid)
|
||||
QList<Entry*> BrowserService::searchEntries(const QSharedPointer<Database>& db,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const QStringList& keys,
|
||||
bool passkey)
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
@ -961,6 +963,12 @@ QList<Entry*> BrowserService::searchEntries(const QSharedPointer<Database>& db,
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a key restriction is specified and not contained in the keys list then skip this group.
|
||||
auto restrictKey = group->resolveCustomDataString(BrowserService::OPTION_RESTRICT_KEY);
|
||||
if (!restrictKey.isEmpty() && !keys.contains(restrictKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto omitWwwSubdomain =
|
||||
group->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW) == Group::Enable;
|
||||
|
||||
@ -997,30 +1005,35 @@ QList<Entry*> BrowserService::searchEntries(const QString& siteUrl,
|
||||
const StringPairList& keyList,
|
||||
bool passkey)
|
||||
{
|
||||
// Check if database is connected with KeePassXC-Browser
|
||||
// Check if database is connected with KeePassXC-Browser. If so, return browser key (otherwise empty)
|
||||
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
||||
for (const StringPair& keyPair : keyList) {
|
||||
QString key = db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + keyPair.first);
|
||||
if (!key.isEmpty() && keyPair.second == key) {
|
||||
return true;
|
||||
return keyPair.first;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return QString();
|
||||
};
|
||||
|
||||
// Get the list of databases to search
|
||||
QList<QSharedPointer<Database>> databases;
|
||||
QStringList keys;
|
||||
if (browserSettings()->searchInAllDatabases()) {
|
||||
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
||||
auto db = dbWidget->database();
|
||||
if (db && databaseConnected(dbWidget->database())) {
|
||||
auto key = databaseConnected(dbWidget->database());
|
||||
if (db && !key.isEmpty()) {
|
||||
databases << db;
|
||||
keys << key;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto& db = getDatabase();
|
||||
if (databaseConnected(db)) {
|
||||
auto key = databaseConnected(db);
|
||||
if (!key.isEmpty()) {
|
||||
databases << db;
|
||||
keys << key;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1029,13 +1042,18 @@ QList<Entry*> BrowserService::searchEntries(const QString& siteUrl,
|
||||
QList<Entry*> entries;
|
||||
do {
|
||||
for (const auto& db : databases) {
|
||||
entries << searchEntries(db, siteUrl, formUrl, passkey);
|
||||
entries << searchEntries(db, siteUrl, formUrl, keys, passkey);
|
||||
}
|
||||
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
QString BrowserService::decodeCustomDataRestrictKey(const QString& key)
|
||||
{
|
||||
return key.isEmpty() ? tr("Disable") : key;
|
||||
}
|
||||
|
||||
void BrowserService::requestGlobalAutoType(const QString& search)
|
||||
{
|
||||
emit osUtils->globalShortcutTriggered("autotype", search);
|
||||
|
@ -119,6 +119,8 @@ public:
|
||||
QJsonArray findEntries(const EntryParameters& entryParameters, const StringPairList& keyList, bool* entriesFound);
|
||||
void requestGlobalAutoType(const QString& search);
|
||||
|
||||
static QString decodeCustomDataRestrictKey(const QString& key);
|
||||
|
||||
static const QString KEEPASSXCBROWSER_NAME;
|
||||
static const QString KEEPASSXCBROWSER_OLD_NAME;
|
||||
static const QString OPTION_SKIP_AUTO_SUBMIT;
|
||||
@ -126,6 +128,7 @@ public:
|
||||
static const QString OPTION_ONLY_HTTP_AUTH;
|
||||
static const QString OPTION_NOT_HTTP_AUTH;
|
||||
static const QString OPTION_OMIT_WWW;
|
||||
static const QString OPTION_RESTRICT_KEY;
|
||||
|
||||
signals:
|
||||
void requestUnlock();
|
||||
@ -157,6 +160,7 @@ private:
|
||||
QList<Entry*> searchEntries(const QSharedPointer<Database>& db,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const QStringList& keys = {},
|
||||
bool passkey = false);
|
||||
QList<Entry*>
|
||||
searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList, bool passkey = false);
|
||||
|
@ -288,6 +288,21 @@ void Group::setCustomDataTriState(const QString& key, const Group::TriState& val
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this returns an empty string both if the key is missing *or* if the key is present but value is empty.
|
||||
QString Group::resolveCustomDataString(const QString& key, bool checkParent) const
|
||||
{
|
||||
// If not defined, check our parent up to the root group
|
||||
if (!m_customData->contains(key)) {
|
||||
if (!m_parent || !checkParent) {
|
||||
return QString();
|
||||
} else {
|
||||
return m_parent->resolveCustomDataString(key);
|
||||
}
|
||||
}
|
||||
|
||||
return m_customData->value(key);
|
||||
}
|
||||
|
||||
bool Group::equals(const Group* other, CompareItemOptions options) const
|
||||
{
|
||||
if (!other) {
|
||||
|
@ -102,6 +102,7 @@ public:
|
||||
const CustomData* customData() const;
|
||||
Group::TriState resolveCustomDataTriState(const QString& key, bool checkParent = true) const;
|
||||
void setCustomDataTriState(const QString& key, const Group::TriState& value);
|
||||
QString resolveCustomDataString(const QString& key, bool checkParent = true) const;
|
||||
const Group* previousParentGroup() const;
|
||||
QUuid previousParentGroupUuid() const;
|
||||
|
||||
|
@ -196,6 +196,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
|
||||
auto inheritOnlyHttp = false;
|
||||
auto inheritNoHttp = false;
|
||||
auto inheritOmitWww = false;
|
||||
auto inheritRestrictKey = QString();
|
||||
|
||||
auto parent = group->parentGroup();
|
||||
if (parent) {
|
||||
@ -204,6 +205,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
|
||||
inheritOnlyHttp = parent->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH);
|
||||
inheritNoHttp = parent->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH);
|
||||
inheritOmitWww = parent->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW);
|
||||
inheritRestrictKey = parent->resolveCustomDataString(BrowserService::OPTION_RESTRICT_KEY);
|
||||
}
|
||||
|
||||
// If the page has not been created at all, some of the elements are null
|
||||
@ -219,6 +221,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
|
||||
addTriStateItems(m_browserUi->browserIntegrationOnlyHttpAuthComboBox, inheritOnlyHttp);
|
||||
addTriStateItems(m_browserUi->browserIntegrationNotHttpAuthComboBox, inheritNoHttp);
|
||||
addTriStateItems(m_browserUi->browserIntegrationOmitWwwCombobox, inheritOmitWww);
|
||||
addRestrictKeyComboBoxItems(m_db->metadata()->customData()->keys(), inheritRestrictKey);
|
||||
|
||||
m_browserUi->browserIntegrationHideEntriesComboBox->setCurrentIndex(
|
||||
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY, false)));
|
||||
@ -230,6 +233,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
|
||||
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH, false)));
|
||||
m_browserUi->browserIntegrationOmitWwwCombobox->setCurrentIndex(
|
||||
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW, false)));
|
||||
setRestrictKeyComboBoxIndex(group);
|
||||
} else if (hasPage(m_browserWidget)) {
|
||||
setPageHidden(m_browserWidget, true);
|
||||
}
|
||||
@ -303,6 +307,7 @@ void EditGroupWidget::apply()
|
||||
m_temporaryGroup->setCustomDataTriState(
|
||||
BrowserService::OPTION_OMIT_WWW,
|
||||
triStateFromIndex(m_browserUi->browserIntegrationOmitWwwCombobox->currentIndex()));
|
||||
setRestrictKeyCustomData(m_temporaryGroup->customData());
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -444,3 +449,58 @@ Group::TriState EditGroupWidget::triStateFromIndex(int index)
|
||||
return Group::Inherit;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
void EditGroupWidget::addRestrictKeyComboBoxItems(QStringList const& keyList, QString inheritValue)
|
||||
{
|
||||
auto comboBox = m_browserUi->browserIntegrationRestrictKeyCombobox;
|
||||
|
||||
comboBox->clear();
|
||||
comboBox->addItem(
|
||||
tr("Inherit from parent group (%1)").arg(BrowserService::decodeCustomDataRestrictKey(inheritValue)));
|
||||
comboBox->addItem(tr("Disable"));
|
||||
|
||||
comboBox->insertSeparator(2);
|
||||
|
||||
// Add all the browser keys to the combobox
|
||||
for (const QString& key : keyList) {
|
||||
if (key.startsWith(CustomData::BrowserKeyPrefix)) {
|
||||
auto strippedKey = key;
|
||||
strippedKey.remove(CustomData::BrowserKeyPrefix);
|
||||
comboBox->addItem(strippedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditGroupWidget::setRestrictKeyComboBoxIndex(const Group* group)
|
||||
{
|
||||
auto comboBox = m_browserUi->browserIntegrationRestrictKeyCombobox;
|
||||
|
||||
if (!group || !group->customData()->contains(BrowserService::OPTION_RESTRICT_KEY)) {
|
||||
comboBox->setCurrentIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto key = group->customData()->value(BrowserService::OPTION_RESTRICT_KEY);
|
||||
if (key.isEmpty()) {
|
||||
comboBox->setCurrentIndex(1);
|
||||
} else {
|
||||
comboBox->setCurrentText(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the customData regarding OPTION_RESTRICT_KEY
|
||||
void EditGroupWidget::setRestrictKeyCustomData(CustomData* customData)
|
||||
{
|
||||
auto comboBox = m_browserUi->browserIntegrationRestrictKeyCombobox;
|
||||
auto key = BrowserService::OPTION_RESTRICT_KEY;
|
||||
auto idx = comboBox->currentIndex();
|
||||
if (idx == 0) {
|
||||
customData->remove(key);
|
||||
} else if (idx == 1) {
|
||||
customData->set(key, QString());
|
||||
} else {
|
||||
customData->set(key, comboBox->currentText());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -81,6 +81,10 @@ private:
|
||||
Group::TriState triStateFromIndex(int index);
|
||||
void setupModifiedTracking();
|
||||
|
||||
void addRestrictKeyComboBoxItems(QStringList const& keyList, QString inheritValue);
|
||||
void setRestrictKeyComboBoxIndex(const Group* group);
|
||||
void setRestrictKeyCustomData(CustomData* customData);
|
||||
|
||||
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
|
||||
|
||||
QPointer<QScrollArea> m_editGroupWidgetMain;
|
||||
|
@ -136,6 +136,23 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="browserIntegrationRestrictKey">
|
||||
<property name="text">
|
||||
<string>Restrict matching to given browser key:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="browserIntegrationRestrictKeyCombobox">
|
||||
<property name="accessibleName">
|
||||
<string>Restrict matching to given browser key toggle for this and sub groups</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer_1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -158,6 +175,7 @@
|
||||
<tabstop>browserIntegrationOnlyHttpAuthComboBox</tabstop>
|
||||
<tabstop>browserIntegrationNotHttpAuthComboBox</tabstop>
|
||||
<tabstop>browserIntegrationOmitWwwCombobox</tabstop>
|
||||
<tabstop>browserIntegrationRestrictKeyCombobox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -675,3 +675,68 @@ void TestBrowser::testBestMatchingWithAdditionalURLs()
|
||||
QCOMPARE(sorted.length(), 1);
|
||||
QCOMPARE(sorted[0]->url(), urls[0]);
|
||||
}
|
||||
|
||||
void TestBrowser::testRestrictBrowserKey()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
auto* root = db->rootGroup();
|
||||
|
||||
// Group 0 (root): No browser key restriction given
|
||||
QStringList urlsRoot = {"https://example.com/0"};
|
||||
auto entriesRoot = createEntries(urlsRoot, root);
|
||||
|
||||
// Group 1: restricted to browser with 'key1'
|
||||
auto* group1 = new Group();
|
||||
group1->setParent(root);
|
||||
group1->setName("TestGroup1");
|
||||
group1->customData()->set(BrowserService::OPTION_RESTRICT_KEY, "key1");
|
||||
QStringList urls1 = {"https://example.com/1"};
|
||||
auto entries1 = createEntries(urls1, group1);
|
||||
|
||||
// Group 2: restricted to browser with 'key2'
|
||||
auto* group2 = new Group();
|
||||
group2->setParent(root);
|
||||
group2->setName("TestGroup2");
|
||||
group2->customData()->set(BrowserService::OPTION_RESTRICT_KEY, "key2");
|
||||
QStringList urls2 = {"https://example.com/2"};
|
||||
auto entries2 = createEntries(urls2, group2);
|
||||
|
||||
// Group 2b: inherits parent group (2) restriction
|
||||
auto* group2b = new Group();
|
||||
group2b->setParent(group2);
|
||||
group2b->setName("TestGroup2b");
|
||||
QStringList urls2b = {"https://example.com/2b"};
|
||||
auto entries2b = createEntries(urls2b, group2b);
|
||||
|
||||
// Group 3: inherits parent group (root) - any browser can see
|
||||
auto* group3 = new Group();
|
||||
group3->setParent(root);
|
||||
group3->setName("TestGroup3");
|
||||
QStringList urls3 = {"https://example.com/3"};
|
||||
auto entries3 = createEntries(urls3, group3);
|
||||
|
||||
// Browser 'key0': Groups 1 and 2 are excluded, so entries 0 and 3 will be found
|
||||
auto siteUrl = QString("https://example.com");
|
||||
auto result = m_browserService->searchEntries(db, siteUrl, siteUrl, {"key0"});
|
||||
auto sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||
QCOMPARE(sorted.size(), 2);
|
||||
QCOMPARE(sorted[0]->url(), QString("https://example.com/3"));
|
||||
QCOMPARE(sorted[1]->url(), QString("https://example.com/0"));
|
||||
|
||||
// Browser 'key1': Group 2 will be excluded, so entries 0, 1, and 3 will be found
|
||||
result = m_browserService->searchEntries(db, siteUrl, siteUrl, {"key1"});
|
||||
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||
QCOMPARE(sorted.size(), 3);
|
||||
QCOMPARE(sorted[0]->url(), QString("https://example.com/3"));
|
||||
QCOMPARE(sorted[1]->url(), QString("https://example.com/1"));
|
||||
QCOMPARE(sorted[2]->url(), QString("https://example.com/0"));
|
||||
|
||||
// Browser 'key2': Group 1 will be excluded, so entries 0, 2, 2b, 3 will be found
|
||||
result = m_browserService->searchEntries(db, siteUrl, siteUrl, {"key2"});
|
||||
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||
QCOMPARE(sorted.size(), 4);
|
||||
QCOMPARE(sorted[0]->url(), QString("https://example.com/3"));
|
||||
QCOMPARE(sorted[1]->url(), QString("https://example.com/2b"));
|
||||
QCOMPARE(sorted[2]->url(), QString("https://example.com/2"));
|
||||
QCOMPARE(sorted[3]->url(), QString("https://example.com/0"));
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ private slots:
|
||||
void testSubdomainsAndPaths();
|
||||
void testBestMatchingCredentials();
|
||||
void testBestMatchingWithAdditionalURLs();
|
||||
void testRestrictBrowserKey();
|
||||
|
||||
private:
|
||||
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
||||
|
Loading…
Reference in New Issue
Block a user