mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-11-29 10:56:48 -05:00
FdoSecrets: reject setting refs via the API (#7043)
* FdoSecrets: add TOTP as a readonly attribute * FdoSecrets: reject setting fields containing refs, fixes #6802 It is still possible to set refs using KPXC UI.
This commit is contained in:
parent
c8f135aaed
commit
2a9d92faeb
5 changed files with 144 additions and 53 deletions
|
|
@ -9,8 +9,9 @@ can connect and access the exposed database in KeePassXC.
|
|||
## Configurable settings
|
||||
|
||||
* The user can specify if a database is exposed on DBus, and which group is exposed.
|
||||
* Whether to show desktop notification is shown when an entry is retrieved.
|
||||
* Whether to skip confirmation for entries deleted from DBus
|
||||
* Whether to show desktop notification is shown when an entry's secret is retrieved.
|
||||
* Whether to confirm for entries deleted from DBus
|
||||
* Whether to confirm each entry's access
|
||||
|
||||
## Implemented Attributes on Item Object
|
||||
|
||||
|
|
@ -22,10 +23,11 @@ The following attributes are exposed:
|
|||
|UserName|The entry user name|
|
||||
|URL|The entry URL|
|
||||
|Notes|The entry notes|
|
||||
|TOTP|The TOTP code if the entry has one|
|
||||
|
||||
In addition, all non-protected custom attributes are also exposed.
|
||||
|
||||
## Architecture
|
||||
## Implementation
|
||||
|
||||
* `FdoSecrets::Service` is the top level DBus service
|
||||
* There is one and only one `FdoSecrets::Collection` per opened database tab
|
||||
|
|
@ -33,8 +35,11 @@ In addition, all non-protected custom attributes are also exposed.
|
|||
|
||||
### Signal connections
|
||||
|
||||
Collection here means the `Collection` object in code. Not the logical concept "collection"
|
||||
that the user interacts with.
|
||||
|
||||
- Collections are created when a corresponding database tab opened
|
||||
- If the database is locked, a collection is created
|
||||
- If the database is locked, a collection is still created
|
||||
- When the database is unlocked, collection populates its children
|
||||
- If the unlocked database's exposed group is none, collection deletes itself
|
||||
- If the database's exposed group changes, collection repopulates
|
||||
|
|
|
|||
|
|
@ -32,10 +32,25 @@
|
|||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
struct EntryUpdater
|
||||
{
|
||||
Entry* entry;
|
||||
explicit EntryUpdater(Entry* entry)
|
||||
: entry(entry)
|
||||
{
|
||||
entry->beginUpdate();
|
||||
}
|
||||
|
||||
const QSet<QString> Item::ReadOnlyAttributes(QSet<QString>() << ItemAttributes::UuidKey << ItemAttributes::PathKey);
|
||||
~EntryUpdater()
|
||||
{
|
||||
entry->endUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
static void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType);
|
||||
const QSet<QString> Item::ReadOnlyAttributes(QSet<QString>() << ItemAttributes::UuidKey << ItemAttributes::PathKey
|
||||
<< ItemAttributes::TotpKey);
|
||||
|
||||
static DBusResult setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType);
|
||||
static Secret getEntrySecret(Entry* entry);
|
||||
|
||||
namespace
|
||||
|
|
@ -110,6 +125,9 @@ namespace FdoSecrets
|
|||
// add some informative and readonly attributes
|
||||
attrs[ItemAttributes::UuidKey] = m_backend->uuidToHex();
|
||||
attrs[ItemAttributes::PathKey] = path();
|
||||
if (m_backend->hasTotp()) {
|
||||
attrs[ItemAttributes::TotpKey] = m_backend->totp();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -124,23 +142,28 @@ namespace FdoSecrets
|
|||
return ret;
|
||||
}
|
||||
|
||||
m_backend->beginUpdate();
|
||||
|
||||
auto entryAttrs = m_backend->attributes();
|
||||
// set on a temp variable first and check for errors to avoid partial updates
|
||||
EntryAttributes tempAttrs;
|
||||
tempAttrs.copyDataFrom(m_backend->attributes());
|
||||
for (auto it = attrs.constBegin(); it != attrs.constEnd(); ++it) {
|
||||
if (entryAttrs->isProtected(it.key()) || it.key() == EntryAttributes::PasswordKey) {
|
||||
continue;
|
||||
if (tempAttrs.isProtected(it.key()) || it.key() == EntryAttributes::PasswordKey) {
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
|
||||
if (ReadOnlyAttributes.contains(it.key())) {
|
||||
continue;
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
|
||||
entryAttrs->set(it.key(), it.value());
|
||||
if (EntryAttributes::matchReference(it.value()).hasMatch()) {
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
|
||||
tempAttrs.set(it.key(), it.value());
|
||||
}
|
||||
|
||||
m_backend->endUpdate();
|
||||
|
||||
// actually update the backend
|
||||
EntryUpdater eu(m_backend);
|
||||
m_backend->attributes()->copyDataFrom(&tempAttrs);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -170,10 +193,12 @@ namespace FdoSecrets
|
|||
return ret;
|
||||
}
|
||||
|
||||
m_backend->beginUpdate();
|
||||
m_backend->setTitle(label);
|
||||
m_backend->endUpdate();
|
||||
if (EntryAttributes::matchReference(label).hasMatch()) {
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
|
||||
EntryUpdater eu(m_backend);
|
||||
m_backend->setTitle(label);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -278,12 +303,14 @@ namespace FdoSecrets
|
|||
// decode using session
|
||||
auto decoded = secret.session->decode(secret);
|
||||
|
||||
// set in backend
|
||||
m_backend->beginUpdate();
|
||||
setEntrySecret(m_backend, decoded.value, decoded.contentType);
|
||||
m_backend->endUpdate();
|
||||
// block references
|
||||
if (EntryAttributes::matchReference(decoded.value).hasMatch()) {
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
|
||||
return {};
|
||||
EntryUpdater eu(m_backend);
|
||||
// set in backend
|
||||
return setEntrySecret(m_backend, decoded.value, decoded.contentType);
|
||||
}
|
||||
|
||||
DBusResult Item::setProperties(const QVariantMap& properties)
|
||||
|
|
@ -378,7 +405,7 @@ namespace FdoSecrets
|
|||
return pathComponents.join('/');
|
||||
}
|
||||
|
||||
void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType)
|
||||
DBusResult setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType)
|
||||
{
|
||||
auto mimeName = contentType.split(';').takeFirst().trimmed();
|
||||
|
||||
|
|
@ -397,23 +424,28 @@ namespace FdoSecrets
|
|||
}
|
||||
|
||||
if (!mimeType.isValid() || !mimeType.inherits(QStringLiteral("text/plain")) || !codec) {
|
||||
if (EntryAttributes::matchReference(contentType).hasMatch()) {
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
// we can't handle this content type, save the data as attachment, and clear the password field
|
||||
entry->setPassword("");
|
||||
entry->attachments()->set(FDO_SECRETS_DATA, data);
|
||||
entry->attributes()->set(FDO_SECRETS_CONTENT_TYPE, contentType);
|
||||
return;
|
||||
} else {
|
||||
auto password = codec->toUnicode(data);
|
||||
if (EntryAttributes::matchReference(password).hasMatch()) {
|
||||
return QDBusError::InvalidArgs;
|
||||
}
|
||||
// save the data to password field
|
||||
entry->setPassword(password);
|
||||
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
||||
entry->attachments()->remove(FDO_SECRETS_DATA);
|
||||
}
|
||||
if (entry->attributes()->hasKey(FDO_SECRETS_CONTENT_TYPE)) {
|
||||
entry->attributes()->remove(FDO_SECRETS_CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
// save the data to password field
|
||||
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
||||
entry->attachments()->remove(FDO_SECRETS_DATA);
|
||||
}
|
||||
if (entry->attributes()->hasKey(FDO_SECRETS_CONTENT_TYPE)) {
|
||||
entry->attributes()->remove(FDO_SECRETS_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
Q_ASSERT(codec);
|
||||
entry->setPassword(codec->toUnicode(data));
|
||||
return {};
|
||||
}
|
||||
|
||||
Secret getEntrySecret(Entry* entry)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace FdoSecrets
|
|||
{
|
||||
constexpr const auto UuidKey = "Uuid";
|
||||
constexpr const auto PathKey = "Path";
|
||||
constexpr const auto TotpKey = "TOTP";
|
||||
} // namespace ItemAttributes
|
||||
|
||||
class Session;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue