mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
Merge a5531a0350 into ada379fddd
This commit is contained in:
commit
261bed8d98
16 changed files with 853 additions and 2 deletions
|
|
@ -4616,6 +4616,25 @@ This will leave your passwords and sensitive information vulnerable!
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FdoSecrets::SettingsAliasesModel</name>
|
||||
<message>
|
||||
<source>No database selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database with this UUID is not currently opened.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Collection Alias</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FdoSecrets::SettingsClientModel</name>
|
||||
<message>
|
||||
|
|
@ -10227,6 +10246,23 @@ This option is deprecated, use --set-key-file instead.</source>
|
|||
<source><html><head/><body><p>This improves compatibility with certain applications which search for password without unlocking the database first.</p><p>But enabling this may also crash the client if the database can not be unlocked within a certain timeout. (Usually 25s, but may be a different value set in applications.) </p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Collection Aliases</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Expose a database's exposed group under a specific alias. If the “default”-alias has not been configured here, it will point to the currently active database tab.
|
||||
A database needs to be unlocked and have an exposed group configured to be selectable here.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsWidgetKeeShare</name>
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
|||
{Config::FdoSecrets_ConfirmDeleteItem, {QS("FdoSecrets/ConfirmDeleteItem"), Roaming, true}},
|
||||
{Config::FdoSecrets_ConfirmAccessItem, {QS("FdoSecrets/ConfirmAccessItem"), Roaming, true}},
|
||||
{Config::FdoSecrets_UnlockBeforeSearch, {QS("FdoSecrets/UnlockBeforeSearch"), Roaming, true}},
|
||||
{Config::FdoSecrets_CollectionAliasDatabaseUUIDs, {QS("FdoSecrets/CollectionAliasDatabaseUUIDs"), Roaming, QVariantMap()}},
|
||||
|
||||
// KeeShare
|
||||
{Config::KeeShare_QuietSuccess, {QS("KeeShare/QuietSuccess"), Roaming, false}},
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ public:
|
|||
FdoSecrets_ConfirmDeleteItem,
|
||||
FdoSecrets_ConfirmAccessItem,
|
||||
FdoSecrets_UnlockBeforeSearch,
|
||||
FdoSecrets_CollectionAliasDatabaseUUIDs,
|
||||
|
||||
KeeShare_QuietSuccess,
|
||||
KeeShare_Own,
|
||||
|
|
|
|||
|
|
@ -104,4 +104,36 @@ namespace FdoSecrets
|
|||
db->metadata()->customData()->set(CustomData::FdoSecretsExposedGroup, group.toString());
|
||||
}
|
||||
|
||||
QVariantMap FdoSecretsSettings::collectionAliases() const
|
||||
{
|
||||
return config()->get(Config::FdoSecrets_CollectionAliasDatabaseUUIDs).toMap();
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setCollectionAliases(const QVariantMap& aliases)
|
||||
{
|
||||
config()->set(Config::FdoSecrets_CollectionAliasDatabaseUUIDs, aliases);
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::setCollectionAlias(QString alias, QUuid publicUuid)
|
||||
{
|
||||
auto aliases = collectionAliases();
|
||||
auto it = std::as_const(aliases).lowerBound(alias);
|
||||
if (it != aliases.cend() && *it == publicUuid)
|
||||
return;
|
||||
aliases.insert(std::move(it), std::move(alias), std::move(publicUuid));
|
||||
// always signals collectionAliasesChanged(), so return above if nothing changed
|
||||
setCollectionAliases(std::move(aliases));
|
||||
}
|
||||
|
||||
void FdoSecretsSettings::removeCollectionAlias(const QString& alias, const QUuid& publicUuid)
|
||||
{
|
||||
auto aliases = collectionAliases();
|
||||
auto it = aliases.find(alias);
|
||||
if (it == aliases.end() || it->toUuid() != publicUuid)
|
||||
return;
|
||||
aliases.erase(std::move(it));
|
||||
// always signals collectionAliasesChanged(), so return above if nothing changed
|
||||
setCollectionAliases(std::move(aliases));
|
||||
}
|
||||
|
||||
} // namespace FdoSecrets
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
#include <QVariantMap>
|
||||
|
||||
class Database;
|
||||
|
||||
|
|
@ -47,6 +48,11 @@ namespace FdoSecrets
|
|||
bool unlockBeforeSearch() const;
|
||||
void setUnlockBeforeSearch(bool unlockBeforeSearch);
|
||||
|
||||
QVariantMap collectionAliases() const;
|
||||
void setCollectionAliases(const QVariantMap& aliases);
|
||||
void setCollectionAlias(QString alias, QUuid publicUuuid);
|
||||
void removeCollectionAlias(const QString& alias, const QUuid& publicUuid);
|
||||
|
||||
// Per db settings
|
||||
|
||||
QUuid exposedGroup(const QSharedPointer<Database>& db) const;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,11 @@ namespace FdoSecrets
|
|||
onDatabaseTabOpened(dbWidget, true);
|
||||
});
|
||||
|
||||
// when a new database is opened, apply it's aliases
|
||||
connect(m_databases.data(), &DatabaseTabWidget::databaseOpened, this, &Service::applyCollectionAliasSettings);
|
||||
// apply aliases from settings, when they change
|
||||
connect(config(), &Config::changed, this, &Service::handleSettingsChanged);
|
||||
|
||||
// make default alias track current activated database
|
||||
connect(m_databases.data(), &DatabaseTabWidget::activeDatabaseChanged, this, &Service::ensureDefaultAlias);
|
||||
|
||||
|
|
@ -163,7 +168,7 @@ namespace FdoSecrets
|
|||
|
||||
void Service::ensureDefaultAlias()
|
||||
{
|
||||
if (m_insideEnsureDefaultAlias) {
|
||||
if (m_insideEnsureDefaultAlias || m_explicitlyDefinedDefaultAlias) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -463,6 +468,51 @@ namespace FdoSecrets
|
|||
return collection->addAlias(name);
|
||||
}
|
||||
|
||||
void Service::handleSettingsChanged(Config::ConfigKey key)
|
||||
{
|
||||
if (key == Config::FdoSecrets_CollectionAliasDatabaseUUIDs) {
|
||||
applyCollectionAliasSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Service::applyCollectionAliasSettings()
|
||||
{
|
||||
auto aliases = settings()->collectionAliases();
|
||||
// add missing aliases
|
||||
m_explicitlyDefinedDefaultAlias = false; // is there still an explicit default alias?
|
||||
for (auto it = aliases.cbegin(); it != aliases.cend(); ++it) {
|
||||
auto& alias = it.key();
|
||||
if (alias == DEFAULT_ALIAS) {
|
||||
m_explicitlyDefinedDefaultAlias = true; // will disable this->ensureDefaultAlias()
|
||||
}
|
||||
|
||||
auto database = m_databases->databaseWidgetFromPublicUuid(it->toUuid());
|
||||
if (!database)
|
||||
continue; // cannot add alias to database not currently opened
|
||||
auto collection = m_dbToCollection.value(database, nullptr);
|
||||
if (!collection)
|
||||
continue;
|
||||
|
||||
collection->addAlias(alias).okOrDie(); // is idempotent
|
||||
}
|
||||
// remove unexpected aliases
|
||||
QList<std::pair<QString, Collection*>> aliasesToRemove = {};
|
||||
for (auto it = m_aliases.cbegin(); it != m_aliases.cend(); ++it) {
|
||||
auto& alias = it.key();
|
||||
Collection* const isCollection = *it; // the collection this alias currently points to
|
||||
auto shouldUuid = aliases.value(alias, {}).toUuid(); // the UUID this alias should point to
|
||||
if (shouldUuid.isNull() || isCollection->backend()->database()->publicUuid() != shouldUuid) {
|
||||
// don't call isCollection->removeAlias, to avoid invalidating the iterator it
|
||||
aliasesToRemove.append(std::make_pair(alias, isCollection));
|
||||
}
|
||||
}
|
||||
for (auto toRemove : aliasesToRemove) {
|
||||
toRemove.second->removeAlias(toRemove.first).okOrDie();
|
||||
}
|
||||
// select default alias, if not explicitly defined
|
||||
ensureDefaultAlias();
|
||||
}
|
||||
|
||||
Collection* Service::findCollection(const QString& alias) const
|
||||
{
|
||||
if (alias.isEmpty()) {
|
||||
|
|
@ -495,11 +545,23 @@ namespace FdoSecrets
|
|||
{
|
||||
auto coll = qobject_cast<Collection*>(sender());
|
||||
m_aliases[alias] = coll;
|
||||
if (!(m_insideEnsureDefaultAlias && alias == DEFAULT_ALIAS)) {
|
||||
// store dynamically created aliases to settings
|
||||
settings()->setCollectionAlias(alias, coll->backend()->database()->publicUuid());
|
||||
}
|
||||
}
|
||||
|
||||
void Service::onCollectionAliasRemoved(const QString& alias)
|
||||
{
|
||||
m_aliases.remove(alias);
|
||||
auto coll = m_aliases.take(alias);
|
||||
if (coll
|
||||
&& !(m_insideEnsureDefaultAlias && alias == DEFAULT_ALIAS)
|
||||
// don't persistently remove alias after collectionAboutToDelete() was emitted
|
||||
&& m_dbToCollection.contains(coll->backend())) {
|
||||
// pass publicUuid, to avoid removing alias x to database A, when
|
||||
// applyCollectionAliasSettings() removes x->B while applying this very x->A
|
||||
settings()->removeCollectionAlias(alias, coll->backend()->database()->publicUuid());
|
||||
}
|
||||
ensureDefaultAlias();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#ifndef KEEPASSXC_FDOSECRETS_SERVICE_H
|
||||
#define KEEPASSXC_FDOSECRETS_SERVICE_H
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
#include "fdosecrets/dbus/DBusObject.h"
|
||||
|
||||
|
|
@ -143,6 +144,9 @@ namespace FdoSecrets
|
|||
|
||||
void onCollectionAliasRemoved(const QString& alias);
|
||||
|
||||
void handleSettingsChanged(Config::ConfigKey key);
|
||||
void applyCollectionAliasSettings();
|
||||
|
||||
private:
|
||||
bool initialize();
|
||||
|
||||
|
|
@ -173,6 +177,7 @@ namespace FdoSecrets
|
|||
QList<Session*> m_sessions{};
|
||||
|
||||
bool m_insideEnsureDefaultAlias{false};
|
||||
bool m_explicitlyDefinedDefaultAlias{false};
|
||||
bool m_unlockingAnyDatabase{false};
|
||||
// list of db currently has unlock dialog shown
|
||||
QHash<const DatabaseWidget*, QMetaObject::Connection> m_unlockingDb{};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "gui/Icons.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <iterator>
|
||||
|
||||
namespace FdoSecrets
|
||||
{
|
||||
|
|
@ -244,6 +245,273 @@ namespace FdoSecrets
|
|||
}
|
||||
}
|
||||
|
||||
// static constexpr still requires definition before c++17
|
||||
constexpr const char* SettingsAliasesModel::ColumnNames[];
|
||||
|
||||
SettingsAliasesModel::SettingsAliasesModel(const DatabaseTabWidget* dbTabs, QObject* parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_databases(dbTabs)
|
||||
{
|
||||
}
|
||||
|
||||
void SettingsAliasesModel::setAliases(QVariantMap aliases)
|
||||
{
|
||||
beginResetModel();
|
||||
m_aliases = aliases;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
const QVariantMap& SettingsAliasesModel::aliases() const
|
||||
{
|
||||
return m_aliases;
|
||||
}
|
||||
|
||||
int SettingsAliasesModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return newRowIndex() + extraNewRow;
|
||||
}
|
||||
|
||||
int SettingsAliasesModel::newRowIndex() const
|
||||
{
|
||||
return m_aliases.size();
|
||||
}
|
||||
|
||||
int SettingsAliasesModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return sizeof(ColumnNames) / sizeof(ColumnNames[0]);
|
||||
}
|
||||
|
||||
QVariant SettingsAliasesModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (section < 0 || section >= columnCount({})) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return qApp->translate(metaObject()->className(), ColumnNames[section]);
|
||||
}
|
||||
|
||||
QVariantMap::const_iterator SettingsAliasesModel::rowAlias(const QModelIndex& index) const
|
||||
{
|
||||
return std::next(m_aliases.cbegin(), index.row());
|
||||
}
|
||||
|
||||
QVariantMap::iterator SettingsAliasesModel::rowAlias(const QModelIndex& index)
|
||||
{
|
||||
return std::next(m_aliases.begin(), index.row());
|
||||
}
|
||||
|
||||
QVariant SettingsAliasesModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
if (index.model() != this) {
|
||||
return {};
|
||||
}
|
||||
if (index.row() >= rowCount({}) || index.column() >= columnCount({})) {
|
||||
return {};
|
||||
}
|
||||
if (index.row() == newRowIndex()) { // final empty row, to allow adding more aliases
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (index.column()) {
|
||||
case ColumnAlias:
|
||||
return dataForCollectionAlias(rowAlias(index).key(), role);
|
||||
case ColumnDatabase:
|
||||
return dataForDatabase(rowAlias(index)->toUuid(), role);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsAliasesModel::dataForCollectionAlias(const QString& alias, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole: {
|
||||
return alias;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsAliasesModel::dataForDatabase(const QUuid& publicUuid, int role) const
|
||||
{
|
||||
if (role == Qt::EditRole) {
|
||||
return publicUuid; // initial value for editor for this cell
|
||||
}
|
||||
|
||||
auto dbWidget = m_databases->databaseWidgetFromPublicUuid(publicUuid);
|
||||
if (dbWidget) {
|
||||
auto db = dbWidget->database();
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
return dbWidget->displayName();
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
return db->filePath();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
} else if (publicUuid.isNull()) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
return tr("No database selected.");
|
||||
}
|
||||
case Qt::FontRole: {
|
||||
QFont font;
|
||||
font.setItalic(true);
|
||||
return font;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
return publicUuid;
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
return tr("The database with this UUID is not currently opened.");
|
||||
case Qt::FontRole: {
|
||||
QFont font;
|
||||
font.setItalic(true);
|
||||
return font;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsAliasesModel::moveAlias(const QModelIndex& index,
|
||||
const QString& prevAlias,
|
||||
QString nextAlias,
|
||||
QVariant database)
|
||||
{
|
||||
const auto destIt = std::as_const(m_aliases).lowerBound(nextAlias);
|
||||
// beginMoveRows takes that element's current index, in front of which we insert
|
||||
auto destRowIdx = std::distance(m_aliases.cbegin(), destIt);
|
||||
// inserting directly above or below the element to be removed doesn't move
|
||||
const bool willMove = destRowIdx != index.row() && destRowIdx - 1 != index.row();
|
||||
if (willMove) {
|
||||
// move index.row() to destRowIdx
|
||||
beginMoveRows({}, index.row(), index.row(), {}, destRowIdx);
|
||||
}
|
||||
const bool newRow = index.row() == newRowIndex();
|
||||
m_aliases.insert(std::move(destIt), std::move(nextAlias), std::move(database));
|
||||
if (newRow) {
|
||||
// remove the special "newRow", which does not correspond to m_aliases
|
||||
extraNewRow = false; // cannot nest beginMoveRows and beginInsertRows
|
||||
} else {
|
||||
// remove a normal row, which does correspond to an m_aliases entry
|
||||
m_aliases.remove(prevAlias);
|
||||
}
|
||||
if (willMove) {
|
||||
// one insert and one remove -> we moved a row
|
||||
endMoveRows();
|
||||
}
|
||||
// during this move, we might have also changed the cell values
|
||||
if (index.row() < destRowIdx) {
|
||||
--destRowIdx; // shifted up, when we removed index.row()
|
||||
}
|
||||
emit dataChanged(this->index(destRowIdx, ColumnAlias), this->index(destRowIdx, ColumnDatabase));
|
||||
if (newRow) {
|
||||
// create a new "newRow", if we (re)moved the old one
|
||||
beginInsertRows({}, newRowIndex(), newRowIndex());
|
||||
extraNewRow = true; // increases this->rowCount() by one
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsAliasesModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
switch (index.column()) {
|
||||
case ColumnAlias: {
|
||||
QString prevAlias = "";
|
||||
QString nextAlias = value.toString();
|
||||
QVariant database = QUuid();
|
||||
if (index.row() == newRowIndex()) {
|
||||
if (nextAlias.isEmpty())
|
||||
return false; // don't add empty alias
|
||||
if (m_aliases.contains(nextAlias))
|
||||
return false; // don't override existing alias
|
||||
} else {
|
||||
auto prevRow = rowAlias(index);
|
||||
prevAlias = prevRow.key();
|
||||
database = prevRow.value();
|
||||
if (nextAlias.isEmpty()) {
|
||||
beginRemoveRows({}, index.row(), index.row());
|
||||
m_aliases.erase(prevRow);
|
||||
endRemoveRows();
|
||||
return true; // edit to empty -> remove row
|
||||
}
|
||||
if (nextAlias == prevAlias) {
|
||||
return true; // editor didn't change this value
|
||||
}
|
||||
if (m_aliases.contains(nextAlias)) {
|
||||
return false; // refuse duplicate alias
|
||||
}
|
||||
}
|
||||
moveAlias(index, prevAlias, std::move(nextAlias), std::move(database));
|
||||
return true;
|
||||
}
|
||||
case ColumnDatabase: {
|
||||
auto nextDatabase = value.toUuid();
|
||||
QString alias = "default";
|
||||
if (index.row() == newRowIndex()) {
|
||||
// find a new unique alias name
|
||||
int i = 1;
|
||||
while (m_aliases.contains(alias)) {
|
||||
alias = QString("alias%1").arg(i++);
|
||||
}
|
||||
// insert new alias for selected database
|
||||
moveAlias(index, "", std::move(alias), std::move(nextDatabase));
|
||||
} else {
|
||||
// just update this value (won't move/insert/remove any rows)
|
||||
*rowAlias(index) = nextDatabase;
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return QAbstractTableModel::setData(index, value, role);
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags SettingsAliasesModel::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid() || index.model() != this || index.row() >= rowCount({})
|
||||
|| index.column() >= columnCount({})) {
|
||||
return {};
|
||||
}
|
||||
// all table cells are editable (see SettingsAliasesModel::setData())
|
||||
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
void SettingsAliasesModel::removeRow(int row)
|
||||
{
|
||||
beginRemoveRows({}, row, row);
|
||||
m_aliases.erase(std::next(m_aliases.begin(), row));
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
// static constexpr still requires definition before c++17
|
||||
constexpr const char* SettingsClientModel::ColumnNames[];
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "fdosecrets/dbus/DBusClient.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QVariantMap>
|
||||
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
|
|
@ -72,6 +73,48 @@ namespace FdoSecrets
|
|||
|
||||
class DBusMgr;
|
||||
|
||||
class SettingsAliasesModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsAliasesModel(const DatabaseTabWidget* dbTabs, QObject* parent = nullptr);
|
||||
|
||||
void setAliases(QVariantMap aliases);
|
||||
const QVariantMap& aliases() const;
|
||||
void removeRow(int row);
|
||||
|
||||
int newRowIndex() const;
|
||||
int rowCount(const QModelIndex& parent) const override;
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
enum Column
|
||||
{
|
||||
ColumnAlias,
|
||||
ColumnDatabase,
|
||||
};
|
||||
static constexpr const char* ColumnNames[] = {
|
||||
QT_TRANSLATE_NOOP("FdoSecrets::SettingsAliasesModel", "Collection Alias"),
|
||||
QT_TRANSLATE_NOOP("FdoSecrets::SettingsAliasesModel", "Database"),
|
||||
};
|
||||
|
||||
private:
|
||||
QVariantMap::const_iterator rowAlias(const QModelIndex&) const;
|
||||
QVariantMap::iterator rowAlias(const QModelIndex&);
|
||||
void moveAlias(const QModelIndex& index, const QString& prevAlias, QString nextAlias, QVariant database);
|
||||
QVariant dataForCollectionAlias(const QString& alias, int role) const;
|
||||
QVariant dataForDatabase(const QUuid& publicUuid, int role) const;
|
||||
|
||||
private:
|
||||
const DatabaseTabWidget* m_databases;
|
||||
QVariantMap m_aliases;
|
||||
// whether to include an empty row at the bottom, for new alias entry
|
||||
bool extraNewRow = true;
|
||||
};
|
||||
|
||||
class SettingsClientModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include "SettingsWidgetFdoSecrets.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "ui_SettingsWidgetFdoSecrets.h"
|
||||
|
||||
#include "fdosecrets/FdoSecretsPlugin.h"
|
||||
|
|
@ -27,9 +28,11 @@
|
|||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QComboBox>
|
||||
#include <QToolButton>
|
||||
|
||||
using FdoSecrets::DBusClientPtr;
|
||||
using FdoSecrets::SettingsAliasesModel;
|
||||
using FdoSecrets::SettingsClientModel;
|
||||
using FdoSecrets::SettingsDatabaseModel;
|
||||
|
||||
|
|
@ -215,6 +218,62 @@ namespace
|
|||
};
|
||||
} // namespace
|
||||
|
||||
class DatabaseUuidDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DatabaseUuidDelegate(DatabaseTabWidget* dbTabs, QObject* parent = nullptr)
|
||||
: QStyledItemDelegate(parent)
|
||||
, m_dbTabs(dbTabs)
|
||||
{
|
||||
}
|
||||
|
||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const override
|
||||
{
|
||||
QComboBox* e = new QComboBox(parent);
|
||||
// Add existing database tabs
|
||||
for (auto i = 0; i < m_dbTabs->count(); ++i) {
|
||||
auto dbWidget = m_dbTabs->databaseWidgetFromIndex(i);
|
||||
auto db = dbWidget->database();
|
||||
if (!FdoSecrets::settings()->exposedGroup(db).isNull()) {
|
||||
auto idx = e->count();
|
||||
e->insertItem(idx, dbWidget->displayName(), db->publicUuid());
|
||||
e->setItemData(idx, db->filePath(), Qt::ToolTipRole);
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
void setEditorData(QWidget* editor, const QModelIndex& index) const override
|
||||
{
|
||||
const QVariant current = index.model()->data(index, Qt::EditRole);
|
||||
if (current.toUuid().isNull())
|
||||
return; // no previously selected db
|
||||
|
||||
auto e = widget(editor);
|
||||
auto idx = e->findData(current);
|
||||
if (idx < 0) { // UUID of not currently opened db in model, add to editor
|
||||
idx = e->count();
|
||||
e->insertItem(idx, current.toUuid().toString(), current);
|
||||
}
|
||||
e->setCurrentIndex(idx);
|
||||
}
|
||||
|
||||
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override
|
||||
{
|
||||
model->setData(index, widget(editor)->currentData());
|
||||
}
|
||||
|
||||
private:
|
||||
static QComboBox* widget(QWidget* editor)
|
||||
{
|
||||
return static_cast<QComboBox*>(editor);
|
||||
}
|
||||
|
||||
const DatabaseTabWidget* const m_dbTabs;
|
||||
};
|
||||
|
||||
SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::SettingsWidgetFdoSecrets())
|
||||
|
|
@ -224,6 +283,33 @@ SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWi
|
|||
m_ui->warningMsg->setHidden(true);
|
||||
m_ui->warningMsg->setCloseButtonVisible(false);
|
||||
|
||||
m_aliasesModel = new SettingsAliasesModel(plugin->dbTabs(), this);
|
||||
m_ui->tableAliases->setModel(m_aliasesModel);
|
||||
|
||||
auto databaseDelegate = new DatabaseUuidDelegate(plugin->dbTabs(), this);
|
||||
m_ui->tableAliases->setItemDelegateForColumn(SettingsAliasesModel::ColumnDatabase, databaseDelegate);
|
||||
|
||||
connect(m_ui->addAliasButton, &QPushButton::clicked, [&]() {
|
||||
auto index =
|
||||
m_aliasesModel->index(m_aliasesModel->newRowIndex(), FdoSecrets::SettingsAliasesModel::ColumnAlias);
|
||||
m_ui->tableAliases->setCurrentIndex(index);
|
||||
m_ui->tableAliases->edit(index);
|
||||
});
|
||||
connect(m_ui->removeAliasButton, &QPushButton::clicked, [&]() {
|
||||
m_aliasesModel->removeRow(m_ui->tableAliases->currentIndex().row());
|
||||
});
|
||||
connect(m_ui->tableAliases->selectionModel(),
|
||||
&QItemSelectionModel::currentChanged,
|
||||
[&](const QModelIndex& current, const QModelIndex&) {
|
||||
m_ui->removeAliasButton->setEnabled(current.isValid());
|
||||
});
|
||||
|
||||
auto aliasesViewHeader = m_ui->tableAliases->horizontalHeader();
|
||||
aliasesViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
aliasesViewHeader->setSectionsClickable(false);
|
||||
aliasesViewHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
aliasesViewHeader->setSectionResizeMode(SettingsAliasesModel::ColumnDatabase, QHeaderView::Stretch);
|
||||
|
||||
auto clientModel = new SettingsClientModel(*plugin->dbus(), this);
|
||||
m_ui->tableClients->setModel(clientModel);
|
||||
installWidgetItemDelegate<ManageSession>(m_ui->tableClients,
|
||||
|
|
@ -273,6 +359,7 @@ void SettingsWidgetFdoSecrets::loadSettings()
|
|||
m_ui->confirmDeleteItem->setChecked(FdoSecrets::settings()->confirmDeleteItem());
|
||||
m_ui->confirmAccessItem->setChecked(FdoSecrets::settings()->confirmAccessItem());
|
||||
m_ui->unlockBeforeSearch->setChecked(FdoSecrets::settings()->unlockBeforeSearch());
|
||||
m_aliasesModel->setAliases(FdoSecrets::settings()->collectionAliases());
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::saveSettings()
|
||||
|
|
@ -282,6 +369,7 @@ void SettingsWidgetFdoSecrets::saveSettings()
|
|||
FdoSecrets::settings()->setConfirmDeleteItem(m_ui->confirmDeleteItem->isChecked());
|
||||
FdoSecrets::settings()->setConfirmAccessItem(m_ui->confirmAccessItem->isChecked());
|
||||
FdoSecrets::settings()->setUnlockBeforeSearch(m_ui->unlockBeforeSearch->isChecked());
|
||||
FdoSecrets::settings()->setCollectionAliases(m_aliasesModel->aliases());
|
||||
}
|
||||
|
||||
void SettingsWidgetFdoSecrets::showEvent(QShowEvent* event)
|
||||
|
|
|
|||
|
|
@ -20,11 +20,16 @@
|
|||
|
||||
#include "gui/MessageWidget.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
class QAbstractItemView;
|
||||
|
||||
class FdoSecretsPlugin;
|
||||
namespace FdoSecrets
|
||||
{
|
||||
class SettingsAliasesModel;
|
||||
}
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
|
@ -52,6 +57,7 @@ protected:
|
|||
private:
|
||||
QScopedPointer<Ui::SettingsWidgetFdoSecrets> m_ui;
|
||||
FdoSecretsPlugin* m_plugin;
|
||||
QPointer<FdoSecrets::SettingsAliasesModel> m_aliasesModel;
|
||||
QTimer m_checkTimer;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,78 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Collection Aliases</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Expose a database's exposed group under a specific alias. If the “default”-alias has not been configured here, it will point to the currently active database tab.
|
||||
A database needs to be unlocked and have an exposed group configured to be selectable here.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableAliases">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="aliasesButtonsLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addAliasButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeAliasButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Authorization</string>
|
||||
|
|
|
|||
|
|
@ -225,6 +225,13 @@ void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackgrou
|
|||
{
|
||||
Q_ASSERT(dbWidget->database());
|
||||
|
||||
// register public UUID before emiting databaseOpened() signal
|
||||
auto publicUuid = dbWidget->database()->publicUuid();
|
||||
if (!m_publicUuidMap.contains(publicUuid)) {
|
||||
// if two DBs have identical publicUuid, ignore the second one
|
||||
m_publicUuidMap.insert(dbWidget->database()->publicUuid(), dbWidget);
|
||||
}
|
||||
|
||||
// emit before index change
|
||||
emit databaseOpened(dbWidget);
|
||||
|
||||
|
|
@ -235,6 +242,7 @@ void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackgrou
|
|||
setCurrentIndex(index);
|
||||
}
|
||||
|
||||
connect(dbWidget, &DatabaseWidget::databaseReplaced, this, &DatabaseTabWidget::updatePublicUuid);
|
||||
connect(dbWidget,
|
||||
SIGNAL(requestOpenDatabase(QString, bool, QString, QString)),
|
||||
SLOT(addDatabaseTab(QString, bool, QString, QString)));
|
||||
|
|
@ -385,6 +393,11 @@ bool DatabaseTabWidget::closeDatabaseTab(DatabaseWidget* dbWidget)
|
|||
return false;
|
||||
}
|
||||
|
||||
auto publicUuid = dbWidget->database()->publicUuid();
|
||||
if (m_publicUuidMap.value(publicUuid) == dbWidget) {
|
||||
m_publicUuidMap.remove(publicUuid);
|
||||
}
|
||||
|
||||
QString filePath = dbWidget->database()->filePath();
|
||||
if (!dbWidget->close()) {
|
||||
return false;
|
||||
|
|
@ -685,6 +698,11 @@ void DatabaseTabWidget::updateTabName(int index)
|
|||
emit tabNameChanged();
|
||||
}
|
||||
|
||||
DatabaseWidget* DatabaseTabWidget::databaseWidgetFromPublicUuid(const QUuid& publicUuid) const
|
||||
{
|
||||
return m_publicUuidMap.value(publicUuid, nullptr);
|
||||
}
|
||||
|
||||
DatabaseWidget* DatabaseTabWidget::databaseWidgetFromIndex(int index) const
|
||||
{
|
||||
return qobject_cast<DatabaseWidget*>(widget(index));
|
||||
|
|
@ -886,6 +904,16 @@ void DatabaseTabWidget::updateLastDatabases()
|
|||
}
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::updatePublicUuid(const QSharedPointer<Database>& oldDb, const QSharedPointer<Database>& newDb)
|
||||
{
|
||||
if (!oldDb.isNull()) {
|
||||
auto widget = m_publicUuidMap.take(oldDb->publicUuid());
|
||||
if (widget == sender()) {
|
||||
m_publicUuidMap.insert(newDb->publicUuid(), widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::emitActiveDatabaseChanged()
|
||||
{
|
||||
emit activeDatabaseChanged(currentDatabaseWidget());
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ public:
|
|||
QString tabName(int index);
|
||||
DatabaseWidget* currentDatabaseWidget();
|
||||
DatabaseWidget* databaseWidgetFromIndex(int index) const;
|
||||
DatabaseWidget* databaseWidgetFromPublicUuid(const QUuid& publicUuid) const;
|
||||
|
||||
bool canSave(int index = -1) const;
|
||||
bool isModified(int index = -1) const;
|
||||
|
|
@ -114,6 +115,7 @@ private slots:
|
|||
void handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
||||
void handleExportError(const QString& reason);
|
||||
void updateLastDatabases();
|
||||
void updatePublicUuid(const QSharedPointer<Database>& oldDb, const QSharedPointer<Database>& newDb);
|
||||
|
||||
private:
|
||||
QSharedPointer<Database> execNewDatabaseWizard();
|
||||
|
|
@ -127,6 +129,7 @@ private:
|
|||
QPointer<ImportWizard> m_importWizard;
|
||||
QTimer m_lockDelayTimer;
|
||||
bool m_databaseOpenInProgress;
|
||||
QHash<QUuid, DatabaseWidget*> m_publicUuidMap;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASETABWIDGET_H
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "core/Global.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "fdosecrets/widgets/SettingsModels.h"
|
||||
#include "gui/Application.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/FileDialog.h"
|
||||
|
|
@ -43,6 +44,7 @@
|
|||
#include <QLineEdit>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
#include <qabstractitemmodeltester.h>
|
||||
#include <utility>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
|
|
@ -202,6 +204,7 @@ void TestGuiFdoSecrets::init()
|
|||
VERIFY(m_dbWidget->save());
|
||||
|
||||
// enforce consistent default settings at the beginning
|
||||
FdoSecrets::settings()->setCollectionAliases({});
|
||||
FdoSecrets::settings()->setUnlockBeforeSearch(false);
|
||||
FdoSecrets::settings()->setShowNotification(false);
|
||||
FdoSecrets::settings()->setConfirmAccessItem(false);
|
||||
|
|
@ -212,6 +215,7 @@ void TestGuiFdoSecrets::init()
|
|||
void TestGuiFdoSecrets::cleanup()
|
||||
{
|
||||
// restore to default settings
|
||||
FdoSecrets::settings()->setCollectionAliases({});
|
||||
FdoSecrets::settings()->setUnlockBeforeSearch(false);
|
||||
FdoSecrets::settings()->setShowNotification(false);
|
||||
FdoSecrets::settings()->setConfirmAccessItem(false);
|
||||
|
|
@ -1602,6 +1606,196 @@ void TestGuiFdoSecrets::testDefaultAliasAlwaysPresent()
|
|||
DBUS_COMPARE(coll->locked(), false);
|
||||
}
|
||||
|
||||
void TestGuiFdoSecrets::testConfiguredAlias()
|
||||
{
|
||||
|
||||
const QString newalias = "newalias";
|
||||
|
||||
auto service = enableService();
|
||||
VERIFY(service);
|
||||
|
||||
DBUS_GET(prePath, service->ReadAlias(newalias));
|
||||
COMPARE(prePath, QDBusObjectPath("/"));
|
||||
|
||||
FdoSecrets::settings()->setCollectionAlias(newalias, m_db->publicUuid());
|
||||
|
||||
DBUS_GET(collPath, service->ReadAlias(newalias));
|
||||
auto coll = getProxy<CollectionProxy>(collPath);
|
||||
VERIFY(coll);
|
||||
|
||||
FdoSecrets::settings()->removeCollectionAlias(newalias, m_db->publicUuid());
|
||||
|
||||
DBUS_GET(postPath, service->ReadAlias(newalias));
|
||||
COMPARE(postPath, QDBusObjectPath("/"));
|
||||
}
|
||||
|
||||
void TestGuiFdoSecrets::testConfiguredDefaultAlias()
|
||||
{
|
||||
const QString defaultAlias = "default";
|
||||
|
||||
auto service = enableService();
|
||||
VERIFY(service);
|
||||
|
||||
// read original default alias
|
||||
DBUS_GET(oldCollPath, service->ReadAlias(defaultAlias));
|
||||
// create a second collection (selects the new db)
|
||||
QDBusObjectPath newCollPath;
|
||||
{
|
||||
QSignalSpy spyCollectionCreated(service.data(), SIGNAL(CollectionCreated(QDBusObjectPath)));
|
||||
VERIFY(spyCollectionCreated.isValid());
|
||||
|
||||
DBUS_GET2(collPath,
|
||||
promptPath,
|
||||
service->CreateCollection({{DBUS_INTERFACE_SECRET_COLLECTION + ".Label", "Test NewDB"}}, "mydatadb"));
|
||||
auto prompt = getProxy<PromptProxy>(promptPath);
|
||||
DBUS_VERIFY(prompt->Prompt(""));
|
||||
VERIFY(driveNewDatabaseWizard());
|
||||
|
||||
VERIFY(waitForSignal(spyCollectionCreated, 1));
|
||||
auto args = spyCollectionCreated.takeFirst();
|
||||
COMPARE(args.size(), 1);
|
||||
newCollPath = args.at(0).value<QDBusObjectPath>();
|
||||
}
|
||||
VERIFY2(oldCollPath.path() != newCollPath.path(), oldCollPath.path().toStdString().data());
|
||||
COMPARE(m_tabWidget->count(), 2);
|
||||
COMPARE(m_tabWidget->currentIndex(), 1);
|
||||
|
||||
// read original default alias
|
||||
DBUS_GET(origDefaultPath, service->ReadAlias(defaultAlias));
|
||||
COMPARE(origDefaultPath, newCollPath);
|
||||
|
||||
// change default alias (back to the old db)
|
||||
FdoSecrets::settings()->setCollectionAlias(defaultAlias, m_db->publicUuid());
|
||||
|
||||
// verify configuration had effect
|
||||
DBUS_GET(configuredDefaultPath, service->ReadAlias(defaultAlias));
|
||||
COMPARE(configuredDefaultPath, oldCollPath);
|
||||
auto configuredDefaultColl = getProxy<CollectionProxy>(configuredDefaultPath);
|
||||
VERIFY(configuredDefaultColl);
|
||||
// verify, that the default alias no longer follows the active tab
|
||||
m_tabWidget->setCurrentIndex(0);
|
||||
DBUS_GET(configuredDefaultPath0, service->ReadAlias(defaultAlias));
|
||||
COMPARE(configuredDefaultPath0, oldCollPath);
|
||||
m_tabWidget->setCurrentIndex(1);
|
||||
DBUS_GET(configuredDefaultPath1, service->ReadAlias(defaultAlias));
|
||||
COMPARE(configuredDefaultPath1, oldCollPath);
|
||||
|
||||
// remove default alias configuration
|
||||
FdoSecrets::settings()->removeCollectionAlias(defaultAlias, m_db->publicUuid());
|
||||
|
||||
// verify that original default alias got restored
|
||||
DBUS_GET(restoredDefaultPath, service->ReadAlias(defaultAlias));
|
||||
COMPARE(restoredDefaultPath, origDefaultPath);
|
||||
// ... and changes with the active tab
|
||||
m_tabWidget->setCurrentIndex(0);
|
||||
DBUS_GET(restoredDefaultPath0, service->ReadAlias(defaultAlias));
|
||||
COMPARE(restoredDefaultPath0, oldCollPath);
|
||||
m_tabWidget->setCurrentIndex(1);
|
||||
DBUS_GET(restoredDefaultPath1, service->ReadAlias(defaultAlias));
|
||||
COMPARE(restoredDefaultPath1, newCollPath);
|
||||
}
|
||||
|
||||
void TestGuiFdoSecrets::testConfiguredUnavailableAlias()
|
||||
{
|
||||
const QString alias = "unavailableAlias";
|
||||
auto service = enableService();
|
||||
VERIFY(service);
|
||||
// configure an alias, for which the database is not currently opened
|
||||
FdoSecrets::settings()->setCollectionAlias(alias, QUuid::createUuid());
|
||||
// check that this alias is not exposed
|
||||
DBUS_GET(path, service->ReadAlias(alias));
|
||||
COMPARE(path, QDBusObjectPath("/"));
|
||||
}
|
||||
|
||||
void TestGuiFdoSecrets::testSettingsAliasesModelDisplay()
|
||||
{
|
||||
FdoSecrets::SettingsAliasesModel model{m_tabWidget};
|
||||
QAbstractItemModelTester tester{&model};
|
||||
|
||||
model.setAliases({{"alias", m_db->publicUuid()}});
|
||||
QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), m_dbWidget->displayName());
|
||||
QCOMPARE(model.data(model.index(0, 1), Qt::ToolTipRole), m_db->filePath());
|
||||
QCOMPARE(model.data(model.index(0, 1), Qt::EditRole), m_db->publicUuid());
|
||||
}
|
||||
|
||||
void TestGuiFdoSecrets::testSettingsAliasesModel()
|
||||
{
|
||||
FdoSecrets::SettingsAliasesModel model{m_tabWidget};
|
||||
QAbstractItemModelTester tester{&model};
|
||||
|
||||
const QVariant defaultUuid = QUuid::createUuid();
|
||||
const QVariant otherUuid = QUuid::createUuid();
|
||||
const QVariant newUuid = QUuid::createUuid();
|
||||
const QVariantMap origAliases{
|
||||
{"default", defaultUuid},
|
||||
{"otherAlias", otherUuid},
|
||||
};
|
||||
model.setAliases(origAliases);
|
||||
QCOMPARE(model.aliases(), origAliases);
|
||||
// last row should be empty (to be filled with new entries)
|
||||
QCOMPARE(model.rowCount({}), origAliases.size() + 1);
|
||||
const QModelIndex nextAliasIdx = model.index(origAliases.size(), 0);
|
||||
const QModelIndex nextUuidIdx = model.index(origAliases.size(), 1);
|
||||
QCOMPARE(model.data(nextAliasIdx, Qt::DisplayRole), QVariant());
|
||||
QCOMPARE(model.data(nextUuidIdx, Qt::DisplayRole), QVariant());
|
||||
// last row should be editable (to insert new entries)
|
||||
QVERIFY(model.flags(nextAliasIdx) & Qt::ItemIsEditable);
|
||||
QVERIFY(model.setData(nextAliasIdx, "newAlias"));
|
||||
QVERIFY(model.aliases().contains("newAlias"));
|
||||
// aliases sorted, so this should be newAlias
|
||||
const QModelIndex newAliasIdx = model.index(1, 0);
|
||||
const QModelIndex newUuidIdx = model.index(1, 1);
|
||||
QCOMPARE(model.data(newAliasIdx, Qt::DisplayRole), "newAlias");
|
||||
QVERIFY(model.flags(newUuidIdx) & Qt::ItemIsEditable);
|
||||
QVERIFY(model.setData(newUuidIdx, newUuid));
|
||||
QCOMPARE(model.aliases()["newAlias"], newUuid);
|
||||
// entries can be renamed
|
||||
QVERIFY(model.flags(newAliasIdx) & Qt::ItemIsEditable);
|
||||
QCOMPARE(model.data(newAliasIdx, Qt::EditRole), "newAlias");
|
||||
QVERIFY(model.setData(newAliasIdx, "renamedAlias"));
|
||||
QVERIFY(!model.aliases().contains("newAlias"));
|
||||
// updates data model.aliases()
|
||||
QCOMPARE(model.aliases()["default"], defaultUuid);
|
||||
QCOMPARE(model.aliases()["otherAlias"], otherUuid);
|
||||
QCOMPARE(model.aliases()["renamedAlias"], newUuid);
|
||||
// updates sorted display
|
||||
QCOMPARE(model.data(model.index(0, 0), Qt::DisplayRole), "default");
|
||||
QCOMPARE(model.data(model.index(1, 0), Qt::DisplayRole), "otherAlias");
|
||||
QCOMPARE(model.data(model.index(2, 0), Qt::DisplayRole), "renamedAlias");
|
||||
QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), defaultUuid);
|
||||
QCOMPARE(model.data(model.index(1, 1), Qt::DisplayRole), otherUuid);
|
||||
QCOMPARE(model.data(model.index(2, 1), Qt::DisplayRole), newUuid);
|
||||
|
||||
// automatically generate names for newly inserted databases
|
||||
const QVariant unnamedUuid = QUuid::createUuid();
|
||||
QVERIFY(model.setData(model.index(3, 1), unnamedUuid));
|
||||
QCOMPARE(model.aliases()["alias1"], unnamedUuid); // "default" already exists
|
||||
// updates sorted display
|
||||
QCOMPARE(model.data(model.index(0, 0), Qt::DisplayRole), "alias1");
|
||||
QCOMPARE(model.data(model.index(1, 0), Qt::DisplayRole), "default");
|
||||
QCOMPARE(model.data(model.index(2, 0), Qt::DisplayRole), "otherAlias");
|
||||
QCOMPARE(model.data(model.index(3, 0), Qt::DisplayRole), "renamedAlias");
|
||||
QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), unnamedUuid);
|
||||
QCOMPARE(model.data(model.index(1, 1), Qt::DisplayRole), defaultUuid);
|
||||
QCOMPARE(model.data(model.index(2, 1), Qt::DisplayRole), otherUuid);
|
||||
QCOMPARE(model.data(model.index(3, 1), Qt::DisplayRole), newUuid);
|
||||
|
||||
// remove rows
|
||||
model.removeRow(1);
|
||||
QCOMPARE(model.rowCount({}), 4);
|
||||
QVERIFY(!model.aliases().contains("default"));
|
||||
// other way of removing rows
|
||||
QVERIFY(model.setData(model.index(1, 0), ""));
|
||||
QCOMPARE(model.rowCount({}), 3);
|
||||
QVERIFY(!model.aliases().contains("otherAlias"));
|
||||
QCOMPARE(model.data(model.index(0, 0), Qt::DisplayRole), "alias1");
|
||||
QCOMPARE(model.data(model.index(1, 0), Qt::DisplayRole), "renamedAlias");
|
||||
QCOMPARE(model.data(model.index(2, 0), Qt::DisplayRole), {});
|
||||
QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), unnamedUuid);
|
||||
QCOMPARE(model.data(model.index(1, 1), Qt::DisplayRole), newUuid);
|
||||
QCOMPARE(model.data(model.index(2, 1), Qt::DisplayRole), {});
|
||||
}
|
||||
|
||||
void TestGuiFdoSecrets::testExposeSubgroup()
|
||||
{
|
||||
auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup");
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ private slots:
|
|||
|
||||
void testAlias();
|
||||
void testDefaultAliasAlwaysPresent();
|
||||
void testConfiguredAlias();
|
||||
void testConfiguredDefaultAlias();
|
||||
void testConfiguredUnavailableAlias();
|
||||
|
||||
void testSettingsAliasesModelDisplay();
|
||||
void testSettingsAliasesModel();
|
||||
|
||||
void testExposeSubgroup();
|
||||
void testModifyingExposedGroup();
|
||||
|
|
|
|||
Loading…
Reference in a new issue