mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-05-13 03:52:33 -04:00
Merge pull request #4232 from Aetf/feature/fdo-secrets-unittests
FdoSecrets: Add unit tests for secret service integration
This commit is contained in:
commit
229a756d84
29 changed files with 1706 additions and 65 deletions
|
@ -21,8 +21,9 @@
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
|
|
||||||
EntrySearcher::EntrySearcher(bool caseSensitive)
|
EntrySearcher::EntrySearcher(bool caseSensitive, bool skipProtected)
|
||||||
: m_caseSensitive(caseSensitive)
|
: m_caseSensitive(caseSensitive)
|
||||||
|
, m_skipProtected(skipProtected)
|
||||||
, m_termParser(R"re(([-!*+]+)?(?:(\w*):)?(?:(?=")"((?:[^"\\]|\\.)*)"|([^ ]*))( |$))re")
|
, m_termParser(R"re(([-!*+]+)?(?:(\w*):)?(?:(?=")"((?:[^"\\]|\\.)*)"|([^ ]*))( |$))re")
|
||||||
// Group 1 = modifiers, Group 2 = field, Group 3 = quoted string, Group 4 = unquoted string
|
// Group 1 = modifiers, Group 2 = field, Group 3 = quoted string, Group 4 = unquoted string
|
||||||
{
|
{
|
||||||
|
@ -136,7 +137,7 @@ void EntrySearcher::setCaseSensitive(bool state)
|
||||||
m_caseSensitive = state;
|
m_caseSensitive = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntrySearcher::isCaseSensitive()
|
bool EntrySearcher::isCaseSensitive() const
|
||||||
{
|
{
|
||||||
return m_caseSensitive;
|
return m_caseSensitive;
|
||||||
}
|
}
|
||||||
|
@ -150,7 +151,9 @@ bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||||
// Build a group hierarchy to allow searching for e.g. /group1/subgroup*
|
// Build a group hierarchy to allow searching for e.g. /group1/subgroup*
|
||||||
auto hierarchy = entry->group()->hierarchy().join('/').prepend("/");
|
auto hierarchy = entry->group()->hierarchy().join('/').prepend("/");
|
||||||
|
|
||||||
bool found;
|
// By default, empty term matches every entry.
|
||||||
|
// However when skipping protected fields, we will recject everything instead
|
||||||
|
bool found = !m_skipProtected;
|
||||||
for (const auto& term : m_searchTerms) {
|
for (const auto& term : m_searchTerms) {
|
||||||
switch (term.field) {
|
switch (term.field) {
|
||||||
case Field::Title:
|
case Field::Title:
|
||||||
|
@ -160,6 +163,9 @@ bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||||
found = term.regex.match(entry->resolvePlaceholder(entry->username())).hasMatch();
|
found = term.regex.match(entry->resolvePlaceholder(entry->username())).hasMatch();
|
||||||
break;
|
break;
|
||||||
case Field::Password:
|
case Field::Password:
|
||||||
|
if (m_skipProtected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
found = term.regex.match(entry->resolvePlaceholder(entry->password())).hasMatch();
|
found = term.regex.match(entry->resolvePlaceholder(entry->password())).hasMatch();
|
||||||
break;
|
break;
|
||||||
case Field::Url:
|
case Field::Url:
|
||||||
|
@ -175,8 +181,7 @@ bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||||
found = !attachments.filter(term.regex).empty();
|
found = !attachments.filter(term.regex).empty();
|
||||||
break;
|
break;
|
||||||
case Field::AttributeValue:
|
case Field::AttributeValue:
|
||||||
// skip protected attributes
|
if (m_skipProtected && entry->attributes()->isProtected(term.word)) {
|
||||||
if (entry->attributes()->isProtected(term.word)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
found = entry->attributes()->contains(term.word)
|
found = entry->attributes()->contains(term.word)
|
||||||
|
@ -198,13 +203,18 @@ bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||||
|| term.regex.match(entry->notes()).hasMatch();
|
|| term.regex.match(entry->notes()).hasMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short circuit if we failed to match or we matched and are excluding this term
|
// negate the result if exclude:
|
||||||
if ((!found && !term.exclude) || (found && term.exclude)) {
|
// * if found and not excluding, the entry matches
|
||||||
|
// * if didn't found but excluding, the entry also matches
|
||||||
|
found = (found && !term.exclude) || (!found && term.exclude);
|
||||||
|
|
||||||
|
// short circuit if we failed the match
|
||||||
|
if (!found) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntrySearcher::parseSearchTerms(const QString& searchString)
|
void EntrySearcher::parseSearchTerms(const QString& searchString)
|
||||||
|
|
|
@ -51,7 +51,7 @@ public:
|
||||||
bool exclude;
|
bool exclude;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit EntrySearcher(bool caseSensitive = false);
|
explicit EntrySearcher(bool caseSensitive = false, bool skipProtected = false);
|
||||||
|
|
||||||
QList<Entry*> search(const QList<SearchTerm>& searchTerms, const Group* baseGroup, bool forceSearch = false);
|
QList<Entry*> search(const QList<SearchTerm>& searchTerms, const Group* baseGroup, bool forceSearch = false);
|
||||||
QList<Entry*> search(const QString& searchString, const Group* baseGroup, bool forceSearch = false);
|
QList<Entry*> search(const QString& searchString, const Group* baseGroup, bool forceSearch = false);
|
||||||
|
@ -62,13 +62,14 @@ public:
|
||||||
QList<Entry*> repeatEntries(const QList<Entry*>& entries);
|
QList<Entry*> repeatEntries(const QList<Entry*>& entries);
|
||||||
|
|
||||||
void setCaseSensitive(bool state);
|
void setCaseSensitive(bool state);
|
||||||
bool isCaseSensitive();
|
bool isCaseSensitive() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool searchEntryImpl(const Entry* entry);
|
bool searchEntryImpl(const Entry* entry);
|
||||||
void parseSearchTerms(const QString& searchString);
|
void parseSearchTerms(const QString& searchString);
|
||||||
|
|
||||||
bool m_caseSensitive;
|
bool m_caseSensitive;
|
||||||
|
bool m_skipProtected;
|
||||||
QRegularExpression m_termParser;
|
QRegularExpression m_termParser;
|
||||||
QList<SearchTerm> m_searchTerms;
|
QList<SearchTerm> m_searchTerms;
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
using FdoSecrets::Service;
|
using FdoSecrets::Service;
|
||||||
|
|
||||||
FdoSecretsPlugin::FdoSecretsPlugin(DatabaseTabWidget* tabWidget)
|
FdoSecretsPlugin::FdoSecretsPlugin(DatabaseTabWidget* tabWidget)
|
||||||
: m_dbTabs(tabWidget)
|
: QObject(tabWidget)
|
||||||
|
, m_dbTabs(tabWidget)
|
||||||
{
|
{
|
||||||
FdoSecrets::registerDBusTypes();
|
FdoSecrets::registerDBusTypes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,8 @@ namespace FdoSecrets
|
||||||
m_registered = false;
|
m_registered = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(m_backend);
|
||||||
|
|
||||||
// make sure we have updated copy of the filepath, which is used to identify the database.
|
// make sure we have updated copy of the filepath, which is used to identify the database.
|
||||||
m_backendPath = m_backend->database()->filePath();
|
m_backendPath = m_backend->database()->filePath();
|
||||||
|
|
||||||
|
@ -245,19 +247,11 @@ namespace FdoSecrets
|
||||||
|
|
||||||
QList<EntrySearcher::SearchTerm> terms;
|
QList<EntrySearcher::SearchTerm> terms;
|
||||||
for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
|
for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
|
||||||
if (it.key() == EntryAttributes::PasswordKey) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
terms << attributeToTerm(it.key(), it.value());
|
terms << attributeToTerm(it.key(), it.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty terms causes EntrySearcher returns everything
|
|
||||||
if (terms.isEmpty()) {
|
|
||||||
return QList<Item*>{};
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<Item*> items;
|
QList<Item*> items;
|
||||||
const auto foundEntries = EntrySearcher().search(terms, m_exposedGroup);
|
const auto foundEntries = EntrySearcher(false, true).search(terms, m_exposedGroup);
|
||||||
items.reserve(foundEntries.size());
|
items.reserve(foundEntries.size());
|
||||||
for (const auto& entry : foundEntries) {
|
for (const auto& entry : foundEntries) {
|
||||||
items << m_entryToItem.value(entry);
|
items << m_entryToItem.value(entry);
|
||||||
|
@ -310,13 +304,13 @@ namespace FdoSecrets
|
||||||
QString itemPath;
|
QString itemPath;
|
||||||
StringStringMap attributes;
|
StringStringMap attributes;
|
||||||
|
|
||||||
// check existing item using attributes
|
|
||||||
auto iterAttr = properties.find(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes"));
|
auto iterAttr = properties.find(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes"));
|
||||||
if (iterAttr != properties.end()) {
|
if (iterAttr != properties.end()) {
|
||||||
attributes = qdbus_cast<StringStringMap>(iterAttr.value().value<QDBusArgument>());
|
attributes = iterAttr.value().value<StringStringMap>();
|
||||||
|
|
||||||
itemPath = attributes.value(ItemAttributes::PathKey);
|
itemPath = attributes.value(ItemAttributes::PathKey);
|
||||||
|
|
||||||
|
// check existing item using attributes
|
||||||
auto existings = searchItems(attributes);
|
auto existings = searchItems(attributes);
|
||||||
if (existings.isError()) {
|
if (existings.isError()) {
|
||||||
return existings;
|
return existings;
|
||||||
|
@ -440,6 +434,16 @@ namespace FdoSecrets
|
||||||
|
|
||||||
QString Collection::name() const
|
QString Collection::name() const
|
||||||
{
|
{
|
||||||
|
if (m_backendPath.isEmpty()) {
|
||||||
|
// This is a newly created db without saving to file.
|
||||||
|
// This name is also used to register dbus path.
|
||||||
|
// For simplicity, we don't monitor the name change.
|
||||||
|
// So the dbus object path is not updated if the db name
|
||||||
|
// changes. This should not be a problem because once the database
|
||||||
|
// gets saved, the dbus path will be updated to use filename and
|
||||||
|
// everything back to normal.
|
||||||
|
return m_backend->database()->metadata()->name();
|
||||||
|
}
|
||||||
return QFileInfo(m_backendPath).baseName();
|
return QFileInfo(m_backendPath).baseName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,8 +548,9 @@ namespace FdoSecrets
|
||||||
}
|
}
|
||||||
|
|
||||||
// repopulate
|
// repopulate
|
||||||
Q_ASSERT(!backendLocked());
|
if (!backendLocked()) {
|
||||||
populateContents();
|
populateContents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Collection::onEntryAdded(Entry* entry, bool emitSignal)
|
void Collection::onEntryAdded(Entry* entry, bool emitSignal)
|
||||||
|
@ -598,11 +603,11 @@ namespace FdoSecrets
|
||||||
return qobject_cast<Service*>(parent());
|
return qobject_cast<Service*>(parent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Collection::doLock()
|
bool Collection::doLock()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_backend);
|
Q_ASSERT(m_backend);
|
||||||
|
|
||||||
m_backend->lock();
|
return m_backend->lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Collection::doUnlock()
|
void Collection::doUnlock()
|
||||||
|
@ -614,6 +619,11 @@ namespace FdoSecrets
|
||||||
|
|
||||||
void Collection::doDelete()
|
void Collection::doDelete()
|
||||||
{
|
{
|
||||||
|
if (!m_backend) {
|
||||||
|
// I'm already deleted
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
emit collectionAboutToDelete();
|
emit collectionAboutToDelete();
|
||||||
|
|
||||||
unregisterCurrentPath();
|
unregisterCurrentPath();
|
||||||
|
@ -623,7 +633,11 @@ namespace FdoSecrets
|
||||||
removeAlias(a).okOrDie();
|
removeAlias(a).okOrDie();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanup connection on Database
|
||||||
cleanupConnections();
|
cleanupConnections();
|
||||||
|
// cleanup connection on Backend itself
|
||||||
|
m_backend->disconnect(this);
|
||||||
|
parent()->disconnect(this);
|
||||||
|
|
||||||
m_exposedGroup = nullptr;
|
m_exposedGroup = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -59,9 +59,9 @@ namespace FdoSecrets
|
||||||
createItem(const QVariantMap& properties, const SecretStruct& secret, bool replace, PromptBase*& prompt);
|
createItem(const QVariantMap& properties, const SecretStruct& secret, bool replace, PromptBase*& prompt);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void itemCreated(const Item* item);
|
void itemCreated(Item* item);
|
||||||
void itemDeleted(const Item* item);
|
void itemDeleted(Item* item);
|
||||||
void itemChanged(const Item* item);
|
void itemChanged(Item* item);
|
||||||
|
|
||||||
void collectionChanged();
|
void collectionChanged();
|
||||||
void collectionAboutToDelete();
|
void collectionAboutToDelete();
|
||||||
|
@ -102,7 +102,7 @@ namespace FdoSecrets
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// expose some methods for Prmopt to use
|
// expose some methods for Prmopt to use
|
||||||
void doLock();
|
bool doLock();
|
||||||
void doUnlock();
|
void doUnlock();
|
||||||
// will remove self
|
// will remove self
|
||||||
void doDelete();
|
void doDelete();
|
||||||
|
|
|
@ -51,6 +51,11 @@ namespace FdoSecrets
|
||||||
return m_objectPath;
|
return m_objectPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDBusAbstractAdaptor& dbusAdaptor() const
|
||||||
|
{
|
||||||
|
return *m_dbusAdaptor;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void registerWithPath(const QString& path, QDBusAbstractAdaptor* adaptor);
|
void registerWithPath(const QString& path, QDBusAbstractAdaptor* adaptor);
|
||||||
|
|
||||||
|
@ -74,11 +79,6 @@ namespace FdoSecrets
|
||||||
|
|
||||||
QString callingPeerName() const;
|
QString callingPeerName() const;
|
||||||
|
|
||||||
template <typename Adaptor> Adaptor& dbusAdaptor() const
|
|
||||||
{
|
|
||||||
return *static_cast<Adaptor*>(m_dbusAdaptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
DBusObject* p() const
|
DBusObject* p() const
|
||||||
{
|
{
|
||||||
return qobject_cast<DBusObject*>(parent());
|
return qobject_cast<DBusObject*>(parent());
|
||||||
|
|
|
@ -35,6 +35,16 @@ namespace FdoSecrets
|
||||||
qRegisterMetaType<ObjectPathSecretMap>();
|
qRegisterMetaType<ObjectPathSecretMap>();
|
||||||
qDBusRegisterMetaType<ObjectPathSecretMap>();
|
qDBusRegisterMetaType<ObjectPathSecretMap>();
|
||||||
|
|
||||||
|
QMetaType::registerConverter<QDBusArgument, StringStringMap>([](const QDBusArgument& arg) {
|
||||||
|
if (arg.currentSignature() != "a{ss}") {
|
||||||
|
return StringStringMap{};
|
||||||
|
}
|
||||||
|
// QDBusArgument is COW and qdbus_cast modifies it by detaching even it is const.
|
||||||
|
// we don't want to modify the instance (arg) stored in the qvariant so we create a copy
|
||||||
|
const auto copy = arg; // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
return qdbus_cast<StringStringMap>(copy);
|
||||||
|
});
|
||||||
|
|
||||||
// NOTE: this is already registered by Qt in qtextratypes.h
|
// NOTE: this is already registered by Qt in qtextratypes.h
|
||||||
// qRegisterMetaType<QList<QDBusObjectPath > >();
|
// qRegisterMetaType<QList<QDBusObjectPath > >();
|
||||||
// qDBusRegisterMetaType<QList<QDBusObjectPath> >();
|
// qDBusRegisterMetaType<QList<QDBusObjectPath> >();
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
#include "core/EntryAttributes.h"
|
#include "core/EntryAttributes.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
|
#include "core/Metadata.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
|
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
|
@ -274,8 +275,8 @@ namespace FdoSecrets
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attributes = qdbus_cast<StringStringMap>(
|
auto attributes =
|
||||||
properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes")).value<QDBusArgument>());
|
properties.value(QStringLiteral(DBUS_INTERFACE_SECRET_ITEM ".Attributes")).value<StringStringMap>();
|
||||||
ret = setAttributes(attributes);
|
ret = setAttributes(attributes);
|
||||||
if (ret.isError()) {
|
if (ret.isError()) {
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -350,6 +351,13 @@ namespace FdoSecrets
|
||||||
return pathComponents.join('/');
|
return pathComponents.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Item::isDeletePermanent() const
|
||||||
|
{
|
||||||
|
auto recycleBin = backend()->database()->metadata()->recycleBin();
|
||||||
|
return (recycleBin && recycleBin->findEntryByUuid(backend()->uuid()))
|
||||||
|
|| !backend()->database()->metadata()->recycleBinEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType)
|
void setEntrySecret(Entry* entry, const QByteArray& data, const QString& contentType)
|
||||||
{
|
{
|
||||||
auto mimeName = contentType.split(';').takeFirst().trimmed();
|
auto mimeName = contentType.split(';').takeFirst().trimmed();
|
||||||
|
@ -369,7 +377,8 @@ namespace FdoSecrets
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mimeType.isValid() || !mimeType.inherits(QStringLiteral("text/plain")) || !codec) {
|
if (!mimeType.isValid() || !mimeType.inherits(QStringLiteral("text/plain")) || !codec) {
|
||||||
// we can't handle this content type, save the data as attachment
|
// 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->attachments()->set(FDO_SECRETS_DATA, data);
|
||||||
entry->attributes()->set(FDO_SECRETS_CONTENT_TYPE, contentType);
|
entry->attributes()->set(FDO_SECRETS_CONTENT_TYPE, contentType);
|
||||||
return;
|
return;
|
||||||
|
@ -393,8 +402,13 @@ namespace FdoSecrets
|
||||||
|
|
||||||
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
if (entry->attachments()->hasKey(FDO_SECRETS_DATA)) {
|
||||||
ss.value = entry->attachments()->value(FDO_SECRETS_DATA);
|
ss.value = entry->attachments()->value(FDO_SECRETS_DATA);
|
||||||
Q_ASSERT(entry->attributes()->hasKey(FDO_SECRETS_CONTENT_TYPE));
|
if (entry->attributes()->hasKey(FDO_SECRETS_CONTENT_TYPE)) {
|
||||||
ss.contentType = entry->attributes()->value(FDO_SECRETS_CONTENT_TYPE);
|
ss.contentType = entry->attributes()->value(FDO_SECRETS_CONTENT_TYPE);
|
||||||
|
} else {
|
||||||
|
// the entry is somehow corrupted, maybe the user deleted it.
|
||||||
|
// set to binary and hope for the best...
|
||||||
|
ss.contentType = QStringLiteral("application/octet-stream");
|
||||||
|
}
|
||||||
return ss;
|
return ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,14 @@ namespace FdoSecrets
|
||||||
*/
|
*/
|
||||||
QString path() const;
|
QString path() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the containing db does not have recycle bin enabled,
|
||||||
|
* or the entry is already in the recycle bin (not possible for item, though),
|
||||||
|
* the delete is permanent
|
||||||
|
* @return true if delete is permanent
|
||||||
|
*/
|
||||||
|
bool isDeletePermanent() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void doDelete();
|
void doDelete();
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ namespace FdoSecrets
|
||||||
|
|
||||||
DBusReturn<void> PromptBase::dismiss()
|
DBusReturn<void> PromptBase::dismiss()
|
||||||
{
|
{
|
||||||
emit completed(true, {});
|
emit completed(true, "");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ namespace FdoSecrets
|
||||||
// only need to delete in backend, collection will react itself.
|
// only need to delete in backend, collection will react itself.
|
||||||
auto accepted = service()->doCloseDatabase(m_collection->backend());
|
auto accepted = service()->doCloseDatabase(m_collection->backend());
|
||||||
|
|
||||||
emit completed(!accepted, {});
|
emit completed(!accepted, "");
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -125,11 +125,17 @@ namespace FdoSecrets
|
||||||
}
|
}
|
||||||
|
|
||||||
emit collectionCreated(coll);
|
emit collectionCreated(coll);
|
||||||
emit completed(false, coll->objectPath().path());
|
emit completed(false, QVariant::fromValue(coll->objectPath()));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBusReturn<void> CreateCollectionPrompt::dismiss()
|
||||||
|
{
|
||||||
|
emit completed(true, QVariant::fromValue(QDBusObjectPath{"/"}));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
LockCollectionsPrompt::LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
LockCollectionsPrompt::LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
||||||
: PromptBase(parent)
|
: PromptBase(parent)
|
||||||
{
|
{
|
||||||
|
@ -153,19 +159,26 @@ namespace FdoSecrets
|
||||||
|
|
||||||
MessageBox::OverrideParent override(findWindow(windowId));
|
MessageBox::OverrideParent override(findWindow(windowId));
|
||||||
|
|
||||||
QList<QDBusObjectPath> locked;
|
|
||||||
for (const auto& c : asConst(m_collections)) {
|
for (const auto& c : asConst(m_collections)) {
|
||||||
if (c) {
|
if (c) {
|
||||||
c->doLock();
|
if (!c->doLock()) {
|
||||||
locked << c->objectPath();
|
return dismiss();
|
||||||
|
}
|
||||||
|
m_locked << c->objectPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit completed(false, QVariant::fromValue(locked));
|
emit completed(false, QVariant::fromValue(m_locked));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBusReturn<void> LockCollectionsPrompt::dismiss()
|
||||||
|
{
|
||||||
|
emit completed(true, QVariant::fromValue(m_locked));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
UnlockCollectionsPrompt::UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
UnlockCollectionsPrompt::UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& colls)
|
||||||
: PromptBase(parent)
|
: PromptBase(parent)
|
||||||
{
|
{
|
||||||
|
@ -191,6 +204,7 @@ namespace FdoSecrets
|
||||||
|
|
||||||
for (const auto& c : asConst(m_collections)) {
|
for (const auto& c : asConst(m_collections)) {
|
||||||
if (c) {
|
if (c) {
|
||||||
|
// doUnlock is nonblocking
|
||||||
connect(c, &Collection::doneUnlockCollection, this, &UnlockCollectionsPrompt::collectionUnlockFinished);
|
connect(c, &Collection::doneUnlockCollection, this, &UnlockCollectionsPrompt::collectionUnlockFinished);
|
||||||
c->doUnlock();
|
c->doUnlock();
|
||||||
}
|
}
|
||||||
|
@ -226,6 +240,11 @@ namespace FdoSecrets
|
||||||
emit completed(m_unlocked.isEmpty(), QVariant::fromValue(m_unlocked));
|
emit completed(m_unlocked.isEmpty(), QVariant::fromValue(m_unlocked));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DBusReturn<void> UnlockCollectionsPrompt::dismiss()
|
||||||
|
{
|
||||||
|
emit completed(true, QVariant::fromValue(m_unlocked));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item)
|
DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item)
|
||||||
: PromptBase(parent)
|
: PromptBase(parent)
|
||||||
|
@ -250,14 +269,18 @@ namespace FdoSecrets
|
||||||
// delete item's backend. Item will be notified after the backend is deleted.
|
// delete item's backend. Item will be notified after the backend is deleted.
|
||||||
if (m_item) {
|
if (m_item) {
|
||||||
if (FdoSecrets::settings()->noConfirmDeleteItem()) {
|
if (FdoSecrets::settings()->noConfirmDeleteItem()) {
|
||||||
MessageBox::setNextAnswer(MessageBox::Move);
|
// based on permanent or not, different button is used
|
||||||
|
if (m_item->isDeletePermanent()) {
|
||||||
|
MessageBox::setNextAnswer(MessageBox::Delete);
|
||||||
|
} else {
|
||||||
|
MessageBox::setNextAnswer(MessageBox::Move);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_item->collection()->doDeleteEntries({m_item->backend()});
|
m_item->collection()->doDeleteEntries({m_item->backend()});
|
||||||
}
|
}
|
||||||
|
|
||||||
emit completed(false, {});
|
emit completed(false, "");
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace FdoSecrets
|
} // namespace FdoSecrets
|
||||||
|
|
|
@ -73,6 +73,7 @@ namespace FdoSecrets
|
||||||
explicit CreateCollectionPrompt(Service* parent);
|
explicit CreateCollectionPrompt(Service* parent);
|
||||||
|
|
||||||
DBusReturn<void> prompt(const QString& windowId) override;
|
DBusReturn<void> prompt(const QString& windowId) override;
|
||||||
|
DBusReturn<void> dismiss() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void collectionCreated(Collection* coll);
|
void collectionCreated(Collection* coll);
|
||||||
|
@ -85,9 +86,11 @@ namespace FdoSecrets
|
||||||
explicit LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls);
|
explicit LockCollectionsPrompt(Service* parent, const QList<Collection*>& colls);
|
||||||
|
|
||||||
DBusReturn<void> prompt(const QString& windowId) override;
|
DBusReturn<void> prompt(const QString& windowId) override;
|
||||||
|
DBusReturn<void> dismiss() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<QPointer<Collection>> m_collections;
|
QList<QPointer<Collection>> m_collections;
|
||||||
|
QList<QDBusObjectPath> m_locked;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnlockCollectionsPrompt : public PromptBase
|
class UnlockCollectionsPrompt : public PromptBase
|
||||||
|
@ -97,6 +100,7 @@ namespace FdoSecrets
|
||||||
explicit UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& coll);
|
explicit UnlockCollectionsPrompt(Service* parent, const QList<Collection*>& coll);
|
||||||
|
|
||||||
DBusReturn<void> prompt(const QString& windowId) override;
|
DBusReturn<void> prompt(const QString& windowId) override;
|
||||||
|
DBusReturn<void> dismiss() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void collectionUnlockFinished(bool accepted);
|
void collectionUnlockFinished(bool accepted);
|
||||||
|
|
|
@ -68,9 +68,9 @@ namespace FdoSecrets
|
||||||
incomplete = false;
|
incomplete = false;
|
||||||
|
|
||||||
std::unique_ptr<CipherPair> cipher{};
|
std::unique_ptr<CipherPair> cipher{};
|
||||||
if (algorithm == QLatin1Literal("plain")) {
|
if (algorithm == QLatin1String(PlainCipher::Algorithm)) {
|
||||||
cipher.reset(new PlainCipher);
|
cipher.reset(new PlainCipher);
|
||||||
} else if (algorithm == QLatin1Literal("dh-ietf1024-sha256-aes128-cbc-pkcs7")) {
|
} else if (algorithm == QLatin1String(DhIetf1024Sha256Aes128CbcPkcs7::Algorithm)) {
|
||||||
QByteArray clientPublicKey = input.toByteArray();
|
QByteArray clientPublicKey = input.toByteArray();
|
||||||
cipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7(clientPublicKey));
|
cipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7(clientPublicKey));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -43,6 +43,9 @@ namespace
|
||||||
|
|
||||||
namespace FdoSecrets
|
namespace FdoSecrets
|
||||||
{
|
{
|
||||||
|
// XXX: remove the redundant definitions once we are at C++17
|
||||||
|
constexpr char PlainCipher::Algorithm[];
|
||||||
|
constexpr char DhIetf1024Sha256Aes128CbcPkcs7::Algorithm[];
|
||||||
|
|
||||||
DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes)
|
DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes)
|
||||||
: m_valid(false)
|
: m_valid(false)
|
||||||
|
@ -51,13 +54,24 @@ namespace FdoSecrets
|
||||||
auto clientPub = MpiFromBytes(clientPublicKeyBytes, false);
|
auto clientPub = MpiFromBytes(clientPublicKeyBytes, false);
|
||||||
|
|
||||||
// generate server side private, 128 bytes
|
// generate server side private, 128 bytes
|
||||||
GcryptMPI serverPrivate(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
GcryptMPI serverPrivate = nullptr;
|
||||||
gcry_mpi_randomize(serverPrivate.get(), KEY_SIZE_BYTES * 8, GCRY_STRONG_RANDOM);
|
if (NextPrivKey) {
|
||||||
|
serverPrivate = std::move(NextPrivKey);
|
||||||
|
} else {
|
||||||
|
serverPrivate.reset(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
||||||
|
gcry_mpi_randomize(serverPrivate.get(), KEY_SIZE_BYTES * 8, GCRY_STRONG_RANDOM);
|
||||||
|
}
|
||||||
|
|
||||||
// generate server side public key
|
// generate server side public key
|
||||||
GcryptMPI serverPublic(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
GcryptMPI serverPublic = nullptr;
|
||||||
// the generator of Second Oakley Group is 2
|
if (NextPubKey) {
|
||||||
gcry_mpi_powm(serverPublic.get(), GCRYMPI_CONST_TWO, serverPrivate.get(), IETF1024_SECOND_OAKLEY_GROUP_P.get());
|
serverPublic = std::move(NextPubKey);
|
||||||
|
} else {
|
||||||
|
serverPublic.reset(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
|
||||||
|
// the generator of Second Oakley Group is 2
|
||||||
|
gcry_mpi_powm(
|
||||||
|
serverPublic.get(), GCRYMPI_CONST_TWO, serverPrivate.get(), IETF1024_SECOND_OAKLEY_GROUP_P.get());
|
||||||
|
}
|
||||||
|
|
||||||
initialize(std::move(clientPub), std::move(serverPublic), std::move(serverPrivate));
|
initialize(std::move(clientPub), std::move(serverPublic), std::move(serverPrivate));
|
||||||
}
|
}
|
||||||
|
@ -216,4 +230,13 @@ namespace FdoSecrets
|
||||||
return m_publicKey;
|
return m_publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DhIetf1024Sha256Aes128CbcPkcs7::fixNextServerKeys(GcryptMPI priv, GcryptMPI pub)
|
||||||
|
{
|
||||||
|
NextPrivKey = std::move(priv);
|
||||||
|
NextPubKey = std::move(pub);
|
||||||
|
}
|
||||||
|
|
||||||
|
GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPrivKey = nullptr;
|
||||||
|
GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPubKey = nullptr;
|
||||||
|
|
||||||
} // namespace FdoSecrets
|
} // namespace FdoSecrets
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "fdosecrets/objects/Session.h"
|
#include "fdosecrets/objects/Session.h"
|
||||||
|
|
||||||
class TestFdoSecrets;
|
class TestFdoSecrets;
|
||||||
|
class TestGuiFdoSecrets;
|
||||||
|
|
||||||
namespace FdoSecrets
|
namespace FdoSecrets
|
||||||
{
|
{
|
||||||
|
@ -42,6 +43,8 @@ namespace FdoSecrets
|
||||||
{
|
{
|
||||||
Q_DISABLE_COPY(PlainCipher)
|
Q_DISABLE_COPY(PlainCipher)
|
||||||
public:
|
public:
|
||||||
|
static constexpr const char Algorithm[] = "plain";
|
||||||
|
|
||||||
PlainCipher() = default;
|
PlainCipher() = default;
|
||||||
SecretStruct encrypt(const SecretStruct& input) override
|
SecretStruct encrypt(const SecretStruct& input) override
|
||||||
{
|
{
|
||||||
|
@ -113,6 +116,8 @@ namespace FdoSecrets
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static constexpr const char Algorithm[] = "dh-ietf1024-sha256-aes128-cbc-pkcs7";
|
||||||
|
|
||||||
explicit DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes);
|
explicit DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes);
|
||||||
|
|
||||||
SecretStruct encrypt(const SecretStruct& input) override;
|
SecretStruct encrypt(const SecretStruct& input) override;
|
||||||
|
@ -123,9 +128,18 @@ namespace FdoSecrets
|
||||||
|
|
||||||
QVariant negotiationOutput() const override;
|
QVariant negotiationOutput() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* For test only, fix the server side private and public key.
|
||||||
|
*/
|
||||||
|
static void fixNextServerKeys(GcryptMPI priv, GcryptMPI pub);
|
||||||
|
static GcryptMPI NextPrivKey;
|
||||||
|
static GcryptMPI NextPubKey;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DISABLE_COPY(DhIetf1024Sha256Aes128CbcPkcs7);
|
Q_DISABLE_COPY(DhIetf1024Sha256Aes128CbcPkcs7);
|
||||||
friend class ::TestFdoSecrets;
|
friend class ::TestFdoSecrets;
|
||||||
|
friend class ::TestGuiFdoSecrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace FdoSecrets
|
} // namespace FdoSecrets
|
||||||
|
|
|
@ -335,9 +335,7 @@ namespace FdoSecrets
|
||||||
{
|
{
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::EditRole: {
|
case Qt::EditRole: {
|
||||||
auto v = QVariant::fromValue(sess);
|
return QVariant::fromValue(sess);
|
||||||
qDebug() << v << v.type() << v.userType();
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -49,7 +49,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||||
: QTabWidget(parent)
|
: QTabWidget(parent)
|
||||||
, m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
|
, m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
|
||||||
, m_dbWidgetPendingLock(nullptr)
|
, m_dbWidgetPendingLock(nullptr)
|
||||||
, m_databaseOpenDialog(new DatabaseOpenDialog())
|
, m_databaseOpenDialog(new DatabaseOpenDialog(this))
|
||||||
{
|
{
|
||||||
auto* tabBar = new DragTabBar(this);
|
auto* tabBar = new DragTabBar(this);
|
||||||
setTabBar(tabBar);
|
setTabBar(tabBar);
|
||||||
|
|
|
@ -108,7 +108,7 @@ private:
|
||||||
|
|
||||||
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
|
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
|
||||||
QPointer<DatabaseWidget> m_dbWidgetPendingLock;
|
QPointer<DatabaseWidget> m_dbWidgetPendingLock;
|
||||||
QScopedPointer<DatabaseOpenDialog> m_databaseOpenDialog;
|
QPointer<DatabaseOpenDialog> m_databaseOpenDialog;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_DATABASETABWIDGET_H
|
#endif // KEEPASSX_DATABASETABWIDGET_H
|
||||||
|
|
|
@ -57,8 +57,9 @@ macro(parse_arguments prefix arg_names option_names)
|
||||||
endmacro(parse_arguments)
|
endmacro(parse_arguments)
|
||||||
|
|
||||||
macro(add_unit_test)
|
macro(add_unit_test)
|
||||||
parse_arguments(TEST "NAME;SOURCES;LIBS" "" ${ARGN})
|
parse_arguments(TEST "NAME;SOURCES;LIBS;LAUNCHER" "" ${ARGN})
|
||||||
set(_test_NAME ${TEST_NAME})
|
set(_test_NAME ${TEST_NAME})
|
||||||
|
set(_test_LAUNCHER ${TEST_LAUNCHER})
|
||||||
set(_srcList ${TEST_SOURCES})
|
set(_srcList ${TEST_SOURCES})
|
||||||
add_executable(${_test_NAME} ${_srcList})
|
add_executable(${_test_NAME} ${_srcList})
|
||||||
target_link_libraries(${_test_NAME} ${TEST_LIBS})
|
target_link_libraries(${_test_NAME} ${TEST_LIBS})
|
||||||
|
@ -69,9 +70,9 @@ macro(add_unit_test)
|
||||||
set(TEST_OUTPUT ${TEST_OUTPUT} CACHE STRING "The output to generate when running the QTest unit tests")
|
set(TEST_OUTPUT ${TEST_OUTPUT} CACHE STRING "The output to generate when running the QTest unit tests")
|
||||||
|
|
||||||
if(KDE4_TEST_OUTPUT STREQUAL "xml")
|
if(KDE4_TEST_OUTPUT STREQUAL "xml")
|
||||||
add_test(${_test_NAME} ${_test_NAME} -xml -o ${_test_NAME}.tml)
|
add_test(${_test_NAME} ${_test_LAUNCHER} ${_test_NAME} -xml -o ${_test_NAME}.tml)
|
||||||
else(KDE4_TEST_OUTPUT STREQUAL "xml")
|
else(KDE4_TEST_OUTPUT STREQUAL "xml")
|
||||||
add_test(${_test_NAME} ${_test_NAME})
|
add_test(${_test_NAME} ${_test_LAUNCHER} ${_test_NAME})
|
||||||
endif(KDE4_TEST_OUTPUT STREQUAL "xml")
|
endif(KDE4_TEST_OUTPUT STREQUAL "xml")
|
||||||
|
|
||||||
if(NOT MSVC_IDE) #not needed for the ide
|
if(NOT MSVC_IDE) #not needed for the ide
|
||||||
|
|
|
@ -23,6 +23,7 @@ QTEST_GUILESS_MAIN(TestEntrySearcher)
|
||||||
void TestEntrySearcher::init()
|
void TestEntrySearcher::init()
|
||||||
{
|
{
|
||||||
m_rootGroup = new Group();
|
m_rootGroup = new Group();
|
||||||
|
m_entrySearcher = EntrySearcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestEntrySearcher::cleanup()
|
void TestEntrySearcher::cleanup()
|
||||||
|
@ -259,6 +260,7 @@ void TestEntrySearcher::testCustomAttributesAreSearched()
|
||||||
QCOMPARE(m_searchResult.count(), 2);
|
QCOMPARE(m_searchResult.count(), 2);
|
||||||
|
|
||||||
// protected attributes are ignored
|
// protected attributes are ignored
|
||||||
|
m_entrySearcher = EntrySearcher(false, true);
|
||||||
m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
|
m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
|
||||||
QCOMPARE(m_searchResult.count(), 2);
|
QCOMPARE(m_searchResult.count(), 2);
|
||||||
}
|
}
|
||||||
|
@ -319,3 +321,52 @@ void TestEntrySearcher::testGroup()
|
||||||
m_searchResult = m_entrySearcher.search("g:/group1 search", m_rootGroup);
|
m_searchResult = m_entrySearcher.search("g:/group1 search", m_rootGroup);
|
||||||
QCOMPARE(m_searchResult.count(), 1);
|
QCOMPARE(m_searchResult.count(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestEntrySearcher::testSkipProtected()
|
||||||
|
{
|
||||||
|
QScopedPointer<Entry> e1(new Entry());
|
||||||
|
e1->setGroup(m_rootGroup);
|
||||||
|
|
||||||
|
e1->attributes()->set("testAttribute", "testE1");
|
||||||
|
e1->attributes()->set("testProtected", "apple", true);
|
||||||
|
|
||||||
|
QScopedPointer<Entry> e2(new Entry());
|
||||||
|
e2->setGroup(m_rootGroup);
|
||||||
|
e2->attributes()->set("testAttribute", "testE2");
|
||||||
|
e2->attributes()->set("testProtected", "banana", true);
|
||||||
|
|
||||||
|
const QList<Entry*> expectE1{e1.data()};
|
||||||
|
const QList<Entry*> expectE2{e2.data()};
|
||||||
|
const QList<Entry*> expectBoth{e1.data(), e2.data()};
|
||||||
|
|
||||||
|
// when not skipping protected, empty term matches everything
|
||||||
|
m_searchResult = m_entrySearcher.search("", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, expectBoth);
|
||||||
|
|
||||||
|
// now test the searcher with skipProtected = true
|
||||||
|
m_entrySearcher = EntrySearcher(false, true);
|
||||||
|
|
||||||
|
// when skipping protected, empty term matches nothing
|
||||||
|
m_searchResult = m_entrySearcher.search("", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, {});
|
||||||
|
|
||||||
|
// having a protected entry in terms should not affect the results in anyways
|
||||||
|
m_searchResult = m_entrySearcher.search("_testProtected:apple", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, {});
|
||||||
|
m_searchResult = m_entrySearcher.search("_testProtected:apple _testAttribute:testE2", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, expectE2);
|
||||||
|
m_searchResult = m_entrySearcher.search("_testProtected:apple _testAttribute:testE1", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, expectE1);
|
||||||
|
m_searchResult =
|
||||||
|
m_entrySearcher.search("_testProtected:apple _testAttribute:testE1 _testAttribute:testE2", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, {});
|
||||||
|
|
||||||
|
// also move the protected term around to execurise the short-circut logic
|
||||||
|
m_searchResult = m_entrySearcher.search("_testAttribute:testE2 _testProtected:apple", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, expectE2);
|
||||||
|
m_searchResult = m_entrySearcher.search("_testAttribute:testE1 _testProtected:apple", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, expectE1);
|
||||||
|
m_searchResult =
|
||||||
|
m_entrySearcher.search("_testAttribute:testE1 _testProtected:apple _testAttribute:testE2", m_rootGroup);
|
||||||
|
QCOMPARE(m_searchResult, {});
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ private slots:
|
||||||
void testSearchTermParser();
|
void testSearchTermParser();
|
||||||
void testCustomAttributesAreSearched();
|
void testCustomAttributesAreSearched();
|
||||||
void testGroup();
|
void testGroup();
|
||||||
|
void testSkipProtected();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Group* m_rootGroup;
|
Group* m_rootGroup;
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<interface name="org.freedesktop.Secret.Collection">
|
||||||
|
<property name="Items" type="ao" access="read"/>
|
||||||
|
<property name="Label" type="s" access="readwrite"/>
|
||||||
|
<property name="Locked" type="b" access="read"/>
|
||||||
|
<property name="Created" type="t" access="read"/>
|
||||||
|
<property name="Modified" type="t" access="read"/>
|
||||||
|
<signal name="ItemCreated">
|
||||||
|
<arg name="item" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="ItemDeleted">
|
||||||
|
<arg name="item" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="ItemChanged">
|
||||||
|
<arg name="item" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<method name="Delete">
|
||||||
|
<arg type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="SearchItems">
|
||||||
|
<arg type="ao" direction="out"/>
|
||||||
|
<arg name="attributes" type="a{ss}" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="StringStringMap"/>
|
||||||
|
</method>
|
||||||
|
<method name="CreateItem">
|
||||||
|
<arg type="o" direction="out"/>
|
||||||
|
<arg name="properties" type="a{sv}" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||||
|
<arg name="secret" type="(oayays)" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="FdoSecrets::SecretStruct"/>
|
||||||
|
<arg name="replace" type="b" direction="in"/>
|
||||||
|
<arg name="prompt" type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
</interface>
|
21
tests/data/dbus/interfaces/org.freedesktop.Secret.Item.xml
Normal file
21
tests/data/dbus/interfaces/org.freedesktop.Secret.Item.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<interface name="org.freedesktop.Secret.Item">
|
||||||
|
<property name="Locked" type="b" access="read"/>
|
||||||
|
<property name="Attributes" type="a{ss}" access="readwrite">
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName" value="StringStringMap"/>
|
||||||
|
</property>
|
||||||
|
<property name="Label" type="s" access="readwrite"/>
|
||||||
|
<property name="Created" type="t" access="read"/>
|
||||||
|
<property name="Modified" type="t" access="read"/>
|
||||||
|
<method name="Delete">
|
||||||
|
<arg type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="GetSecret">
|
||||||
|
<arg type="(oayays)" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="FdoSecrets::SecretStruct"/>
|
||||||
|
<arg name="session" type="o" direction="in"/>
|
||||||
|
</method>
|
||||||
|
<method name="SetSecret">
|
||||||
|
<arg name="secret" type="(oayays)" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="FdoSecrets::SecretStruct"/>
|
||||||
|
</method>
|
||||||
|
</interface>
|
11
tests/data/dbus/interfaces/org.freedesktop.Secret.Prompt.xml
Normal file
11
tests/data/dbus/interfaces/org.freedesktop.Secret.Prompt.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<interface name="org.freedesktop.Secret.Prompt">
|
||||||
|
<signal name="Completed">
|
||||||
|
<arg name="dismissed" type="b" direction="out"/>
|
||||||
|
<arg name="result" type="v" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<method name="Prompt">
|
||||||
|
<arg name="windowId" type="s" direction="in"/>
|
||||||
|
</method>
|
||||||
|
<method name="Dismiss">
|
||||||
|
</method>
|
||||||
|
</interface>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<interface name="org.freedesktop.Secret.Service">
|
||||||
|
<property name="Collections" type="ao" access="read"/>
|
||||||
|
<signal name="CollectionCreated">
|
||||||
|
<arg name="collection" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="CollectionDeleted">
|
||||||
|
<arg name="collection" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="CollectionChanged">
|
||||||
|
<arg name="collection" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<method name="OpenSession">
|
||||||
|
<arg type="v" direction="out"/>
|
||||||
|
<arg name="algorithm" type="s" direction="in"/>
|
||||||
|
<arg name="input" type="v" direction="in"/>
|
||||||
|
<arg name="result" type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="CreateCollection">
|
||||||
|
<arg type="o" direction="out"/>
|
||||||
|
<arg name="properties" type="a{sv}" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||||
|
<arg name="alias" type="s" direction="in"/>
|
||||||
|
<arg name="prompt" type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="SearchItems">
|
||||||
|
<arg type="ao" direction="out"/>
|
||||||
|
<arg name="attributes" type="a{ss}" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="StringStringMap"/>
|
||||||
|
<arg name="locked" type="ao" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="Unlock">
|
||||||
|
<arg type="ao" direction="out"/>
|
||||||
|
<arg name="paths" type="ao" direction="in"/>
|
||||||
|
<arg name="prompt" type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="Lock">
|
||||||
|
<arg type="ao" direction="out"/>
|
||||||
|
<arg name="paths" type="ao" direction="in"/>
|
||||||
|
<arg name="prompt" type="o" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<method name="GetSecrets">
|
||||||
|
<arg type="a{o(oayays)}" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ObjectPathSecretMap"/>
|
||||||
|
<arg name="items" type="ao" direction="in"/>
|
||||||
|
<arg name="session" type="o" direction="in"/>
|
||||||
|
</method>
|
||||||
|
<method name="ReadAlias">
|
||||||
|
<arg type="o" direction="out"/>
|
||||||
|
<arg name="name" type="s" direction="in"/>
|
||||||
|
</method>
|
||||||
|
<method name="SetAlias">
|
||||||
|
<arg name="name" type="s" direction="in"/>
|
||||||
|
<arg name="collection" type="o" direction="in"/>
|
||||||
|
</method>
|
||||||
|
</interface>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<interface name="org.freedesktop.Secret.Session">
|
||||||
|
<method name="Close">
|
||||||
|
</method>
|
||||||
|
</interface>
|
39
tests/data/dbus/session.conf
Normal file
39
tests/data/dbus/session.conf
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||||
|
<busconfig>
|
||||||
|
<type>session</type>
|
||||||
|
<keep_umask/>
|
||||||
|
<listen>unix:tmpdir=/tmp</listen>
|
||||||
|
<auth>EXTERNAL</auth>
|
||||||
|
<standard_session_servicedirs />
|
||||||
|
<policy context="default">
|
||||||
|
<allow send_destination="*" eavesdrop="true"/>
|
||||||
|
<allow eavesdrop="true"/>
|
||||||
|
<allow own="*"/>
|
||||||
|
</policy>
|
||||||
|
<include ignore_missing="yes">/etc/dbus-1/session.conf</include>
|
||||||
|
<includedir>session.d</includedir>
|
||||||
|
<includedir>/etc/dbus-1/session.d</includedir>
|
||||||
|
<include ignore_missing="yes">/etc/dbus-1/session-local.conf</include>
|
||||||
|
<include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
|
||||||
|
<limit name="max_incoming_bytes">1000000000</limit>
|
||||||
|
<limit name="max_incoming_unix_fds">250000000</limit>
|
||||||
|
<limit name="max_outgoing_bytes">1000000000</limit>
|
||||||
|
<limit name="max_outgoing_unix_fds">250000000</limit>
|
||||||
|
<limit name="max_message_size">1000000000</limit>
|
||||||
|
<limit name="auth_timeout">240000</limit>
|
||||||
|
<limit name="pending_fd_timeout">150000</limit>
|
||||||
|
<limit name="max_completed_connections">100000</limit>
|
||||||
|
<limit name="max_incomplete_connections">10000</limit>
|
||||||
|
<limit name="max_connections_per_user">100000</limit>
|
||||||
|
<limit name="max_pending_service_starts">10000</limit>
|
||||||
|
<limit name="max_names_per_connection">50000</limit>
|
||||||
|
<limit name="max_match_rules_per_connection">50000</limit>
|
||||||
|
<limit name="max_replies_per_connection">50000</limit>
|
||||||
|
<!-- The above is copied from session bus conf.
|
||||||
|
Our only intent here is to set a low service_start_timeout,
|
||||||
|
such that ctest can exit sooner when dbus-run-session is used
|
||||||
|
to launch tests and some service fails to start.
|
||||||
|
-->
|
||||||
|
<limit name="service_start_timeout">500</limit>
|
||||||
|
</busconfig>
|
|
@ -21,3 +21,12 @@ add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARI
|
||||||
if(WITH_XC_BROWSER)
|
if(WITH_XC_BROWSER)
|
||||||
add_unit_test(NAME testguibrowser SOURCES TestGuiBrowser.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES})
|
add_unit_test(NAME testguibrowser SOURCES TestGuiBrowser.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WITH_XC_FDOSECRETS)
|
||||||
|
add_unit_test(NAME testguifdosecrets
|
||||||
|
SOURCES TestGuiFdoSecrets.cpp ../util/TemporaryFile.cpp
|
||||||
|
LIBS ${TEST_LIBRARIES}
|
||||||
|
# The following doesn't work because dbus-run-session expects execname to be in PATH
|
||||||
|
# dbus-run-session -- execname
|
||||||
|
LAUNCHER dbus-run-session --config-file ${CMAKE_CURRENT_SOURCE_DIR}/../data/dbus/session.conf -- sh -c "exec ./$0")
|
||||||
|
endif()
|
||||||
|
|
1175
tests/gui/TestGuiFdoSecrets.cpp
Normal file
1175
tests/gui/TestGuiFdoSecrets.cpp
Normal file
File diff suppressed because it is too large
Load diff
121
tests/gui/TestGuiFdoSecrets.h
Normal file
121
tests/gui/TestGuiFdoSecrets.h
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSXC_TESTGUIFDOSECRETS_H
|
||||||
|
#define KEEPASSXC_TESTGUIFDOSECRETS_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "fdosecrets/GcryptMPI.h"
|
||||||
|
#include "fdosecrets/objects/DBusTypes.h"
|
||||||
|
|
||||||
|
class MainWindow;
|
||||||
|
class Database;
|
||||||
|
class DatabaseTabWidget;
|
||||||
|
class DatabaseWidget;
|
||||||
|
class TemporaryFile;
|
||||||
|
class FdoSecretsPlugin;
|
||||||
|
namespace FdoSecrets
|
||||||
|
{
|
||||||
|
class Service;
|
||||||
|
class Session;
|
||||||
|
class Collection;
|
||||||
|
class Item;
|
||||||
|
class Prompt;
|
||||||
|
class DhIetf1024Sha256Aes128CbcPkcs7;
|
||||||
|
} // namespace FdoSecrets
|
||||||
|
|
||||||
|
class QAbstractItemView;
|
||||||
|
|
||||||
|
class TestGuiFdoSecrets : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
~TestGuiFdoSecrets() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase();
|
||||||
|
void init();
|
||||||
|
void cleanup();
|
||||||
|
void cleanupTestCase();
|
||||||
|
|
||||||
|
void testDBusSpec();
|
||||||
|
|
||||||
|
void testServiceEnable();
|
||||||
|
void testServiceEnableNoExposedDatabase();
|
||||||
|
void testServiceSearch();
|
||||||
|
void testServiceUnlock();
|
||||||
|
void testServiceLock();
|
||||||
|
|
||||||
|
void testSessionOpen();
|
||||||
|
void testSessionClose();
|
||||||
|
|
||||||
|
void testCollectionCreate();
|
||||||
|
void testCollectionDelete();
|
||||||
|
|
||||||
|
void testItemCreate();
|
||||||
|
void testItemReplace();
|
||||||
|
void testItemSecret();
|
||||||
|
void testItemDelete();
|
||||||
|
|
||||||
|
void testAlias();
|
||||||
|
void testDefaultAliasAlwaysPresent();
|
||||||
|
|
||||||
|
void testExposeSubgroup();
|
||||||
|
void testModifiyingExposedGroup();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void createDatabaseCallback();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void lockDatabaseInBackend();
|
||||||
|
void unlockDatabaseInBackend();
|
||||||
|
QPointer<FdoSecrets::Service> enableService();
|
||||||
|
QPointer<FdoSecrets::Session> openSession(FdoSecrets::Service* service, const QString& algo);
|
||||||
|
QPointer<FdoSecrets::Collection> getDefaultCollection(FdoSecrets::Service* service);
|
||||||
|
QPointer<FdoSecrets::Item> getFirstItem(FdoSecrets::Collection* coll);
|
||||||
|
QPointer<FdoSecrets::Item> createItem(FdoSecrets::Session* sess,
|
||||||
|
FdoSecrets::Collection* coll,
|
||||||
|
const QString& label,
|
||||||
|
const QString& pass,
|
||||||
|
const StringStringMap& attr,
|
||||||
|
bool replace);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<MainWindow> m_mainWindow;
|
||||||
|
QPointer<DatabaseTabWidget> m_tabWidget;
|
||||||
|
QPointer<DatabaseWidget> m_dbWidget;
|
||||||
|
QSharedPointer<Database> m_db;
|
||||||
|
|
||||||
|
QPointer<FdoSecretsPlugin> m_plugin;
|
||||||
|
|
||||||
|
// For DH session tests
|
||||||
|
GcryptMPI m_serverPrivate;
|
||||||
|
GcryptMPI m_serverPublic;
|
||||||
|
std::unique_ptr<FdoSecrets::DhIetf1024Sha256Aes128CbcPkcs7> m_cipher;
|
||||||
|
|
||||||
|
QByteArray m_dbData;
|
||||||
|
QScopedPointer<TemporaryFile> m_dbFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_TESTGUIFDOSECRETS_H
|
Loading…
Add table
Add a link
Reference in a new issue