diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 7ea56b093..3604750bc 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -66,10 +66,6 @@ Remember - - Allow access to entries - - Allow Selected @@ -78,6 +74,30 @@ Deny All + + Non-existing/inaccessible executable path. Please double-check the client is legit. + + + + Name + Name + + + PID + + + + Executable + + + + Command Line + + + + Details + + AccessControlDialog::DenyButton @@ -3773,28 +3793,16 @@ Error: %1 FdoSecrets::SettingsClientModel - Application - + Unknown + Unknown - Manage + Non-existing/inaccessible executable path. Please double-check the client is legit. FdoSecrets::SettingsDatabaseModel - - File Name - - - - Group - Group - - - Manage - - Unlock to show @@ -7249,6 +7257,14 @@ Please consider generating a new key file. Please present or touch your YubiKey to continue… + + unknown executable (DBus address %1) + + + + %1 (invalid executable path) + + QtIOCompressor @@ -7724,6 +7740,40 @@ Please consider generating a new key file. + + SettingsClientModel + + Application + + + + PID + + + + DBus Address + + + + Manage + + + + + SettingsDatabaseModel + + File Name + + + + Group + Group + + + Manage + + + SettingsWidgetFdoSecrets diff --git a/src/fdosecrets/dbus/DBusClient.cpp b/src/fdosecrets/dbus/DBusClient.cpp index 4fa47465f..6e0f63486 100644 --- a/src/fdosecrets/dbus/DBusClient.cpp +++ b/src/fdosecrets/dbus/DBusClient.cpp @@ -22,14 +22,53 @@ #include "fdosecrets/dbus/DBusMgr.h" #include "fdosecrets/objects/SessionCipher.h" +#include + namespace FdoSecrets { - DBusClient::DBusClient(DBusMgr* dbus, const QString& address, uint pid, const QString& name) - : m_dbus(dbus) - , m_address(address) - , m_pid(pid) - , m_name(name) + bool ProcInfo::operator==(const ProcInfo& other) const { + return this->pid == other.pid && this->ppid == other.ppid && this->exePath == other.exePath + && this->name == other.name && this->command == other.command; + } + + bool ProcInfo::operator!=(const ProcInfo& other) const + { + return !(*this == other); + } + + bool PeerInfo::operator==(const PeerInfo& other) const + { + return this->address == other.address && this->pid == other.pid && this->valid == other.valid + && this->hierarchy == other.hierarchy; + } + + bool PeerInfo::operator!=(const PeerInfo& other) const + { + return !(*this == other); + } + + DBusClient::DBusClient(DBusMgr* dbus, PeerInfo process) + : m_dbus(dbus) + , m_process(std::move(process)) + { + } + + DBusMgr* DBusClient::dbus() const + { + return m_dbus; + } + + QString DBusClient::name() const + { + auto exePath = m_process.exePath(); + if (exePath.isEmpty()) { + return QObject::tr("unknown executable (DBus address %1)").arg(m_process.address); + } + if (!m_process.valid) { + return QObject::tr("%1 (invalid executable path)").arg(exePath); + } + return exePath; } bool DBusClient::itemKnown(const QUuid& uuid) const diff --git a/src/fdosecrets/dbus/DBusClient.h b/src/fdosecrets/dbus/DBusClient.h index 60a72b5fa..4fc333e1c 100644 --- a/src/fdosecrets/dbus/DBusClient.h +++ b/src/fdosecrets/dbus/DBusClient.h @@ -30,6 +30,56 @@ namespace FdoSecrets class DBusMgr; class CipherPair; + struct ProcInfo + { + uint pid; + uint ppid; + QString exePath; + QString name; + QString command; + + bool operator==(const ProcInfo& other) const; + bool operator!=(const ProcInfo& other) const; + }; + + /** + * Contains info representing a process. + * This can be obtained by DBusMgr::serviceInfo given a dbus address. + */ + struct PeerInfo + { + /** + * @brief DBus address + */ + QString address; + + uint pid; + /** + * @brief Whether current process' exePath points to a valid executable file. + * + * Note that an empty exePath is not valid. + */ + bool valid; + + /** + * @brief List of parents of the process. + * + * The first element is the current process. The last element is usually PID 1. + * + * This is for showing to the user only and is intentionally simple. + * Getting detailed process info is beyond the scope of KPXC. + */ + QList hierarchy; + + QString exePath() const + { + return hierarchy.front().exePath; + } + + bool operator==(const PeerInfo& other) const; + bool operator!=(const PeerInfo& other) const; + }; + /** * Represent a client that has made requests to our service. A client is identified by its * DBus address, which is guaranteed to be unique by the DBus protocol. @@ -48,30 +98,23 @@ namespace FdoSecrets /** * @brief Given peer's service address, construct a client object * @param address obtained from `QDBusMessage::service()` - * @param pid the process PID - * @param name the process name + * @param process the process info */ - explicit DBusClient(DBusMgr* dbus, const QString& address, uint pid, const QString& name); + explicit DBusClient(DBusMgr* dbus, PeerInfo process); - DBusMgr* dbus() const - { - return m_dbus; - } + DBusMgr* dbus() const; /** * @return The human readable client name, usually the process name */ - QString name() const - { - return m_name; - } + QString name() const; /** * @return The unique DBus address of the client */ QString address() const { - return m_address; + return m_process.address; } /** @@ -79,7 +122,15 @@ namespace FdoSecrets */ uint pid() const { - return m_pid; + return m_process.pid; + } + + /** + * @return The process info + */ + const PeerInfo& processInfo() const + { + return m_process; } QSharedPointer @@ -123,10 +174,7 @@ namespace FdoSecrets private: QPointer m_dbus; - QString m_address; - - uint m_pid{0}; - QString m_name{}; + PeerInfo m_process; bool m_authorizedAll{false}; diff --git a/src/fdosecrets/dbus/DBusMgr.cpp b/src/fdosecrets/dbus/DBusMgr.cpp index 2b9b038ae..e60c068aa 100644 --- a/src/fdosecrets/dbus/DBusMgr.cpp +++ b/src/fdosecrets/dbus/DBusMgr.cpp @@ -167,6 +167,46 @@ namespace FdoSecrets )xml"; + // read /proc, each field except pid may be empty + ProcInfo readProc(uint pid) + { + ProcInfo info{}; + info.pid = pid; + + // The /proc/pid/exe link is more reliable than /proc/pid/cmdline + // It's still weak and if the application does a prctl(PR_SET_DUMPABLE, 0) this link cannot be accessed. + QFileInfo exe(QStringLiteral("/proc/%1/exe").arg(pid)); + info.exePath = exe.canonicalFilePath(); + qDebug() << "process" << pid << info.exePath; + + // /proc/pid/cmdline gives full command line + QFile cmdline(QStringLiteral("/proc/%1/cmdline").arg(pid)); + if (cmdline.open(QFile::ReadOnly)) { + info.command = QString::fromLocal8Bit(cmdline.readAll().replace('\0', ' ')).trimmed(); + } + qDebug() << "process" << pid << info.command; + + // /proc/pid/stat gives ppid, name + QFile stat(QStringLiteral("/proc/%1/stat").arg(pid)); + if (stat.open(QIODevice::ReadOnly)) { + auto line = stat.readAll(); + // find comm field without looking in what's inside as it's user controlled + auto commStart = line.indexOf('('); + auto commEnd = line.lastIndexOf(')'); + if (commStart != -1 && commEnd != -1 && commStart < commEnd) { + // start past '(', and subtract 2 from total length + info.name = QString::fromLocal8Bit(line.mid(commStart + 1, commEnd + 1 - commStart - 2)); + + auto parts = line.mid(commEnd + 1).trimmed().split(' '); + if (parts.size() >= 2) { + info.ppid = parts[1].toUInt(); + } + } + } + + return info; + } + DBusMgr::DBusMgr() : m_conn(QDBusConnection::sessionBus()) { @@ -198,18 +238,33 @@ namespace FdoSecrets return m_clients.values(); } - bool DBusMgr::serviceInfo(const QString& addr, ProcessInfo& info) const + bool DBusMgr::serviceInfo(const QString& addr, PeerInfo& info) const { + info.address = addr; auto pid = m_conn.interface()->servicePid(addr); if (!pid.isValid()) { return false; } info.pid = pid.value(); - // The /proc/pid/exe link is more reliable than /proc/pid/cmdline - // It's still weak and if the application does a prctl(PR_SET_DUMPABLE, 0) this link cannot be accessed. - QFileInfo proc(QStringLiteral("/proc/%1/exe").arg(pid.value())); - info.exePath = proc.canonicalFilePath(); + // get the whole hierarchy + uint ppid = info.pid; + while (ppid != 0) { + auto proc = readProc(ppid); + info.hierarchy.append(proc); + ppid = proc.ppid; + } + + // check if the exe file is valid + // (assuming for now that an accessible file is valid) + info.valid = QFileInfo::exists(info.exePath()); + + // ask again to double-check and protect against pid reuse + auto newPid = m_conn.interface()->servicePid(addr); + if (!newPid.isValid() || newPid.value() != pid.value()) { + // either the peer already gone or the pid changed to something else + return false; + } return true; } @@ -321,11 +376,11 @@ namespace FdoSecrets auto pidStr = tr("Unknown", "Unknown PID"); auto exeStr = tr("Unknown", "Unknown executable path"); - ProcessInfo info{}; + PeerInfo info{}; if (serviceInfo(DBUS_SERVICE_SECRET, info)) { pidStr = QString::number(info.pid); - if (!info.exePath.isEmpty()) { - exeStr = info.exePath; + if (!info.exePath().isEmpty()) { + exeStr = info.exePath(); } } @@ -560,23 +615,17 @@ namespace FdoSecrets } it = m_clients.insert(addr, client); } - // double check the client - ProcessInfo info{}; - if (!serviceInfo(addr, info) || info.pid != it.value()->pid()) { - dbusServiceUnregistered(addr); - return {}; - } return it.value(); } DBusClientPtr DBusMgr::createClient(const QString& addr) { - ProcessInfo info{}; + PeerInfo info{}; if (!serviceInfo(addr, info)) { return {}; } - auto client = DBusClientPtr(new DBusClient(this, addr, info.pid, info.exePath.isEmpty() ? addr : info.exePath)); + auto client = DBusClientPtr(new DBusClient(this, info)); emit clientConnected(client); m_watcher.addWatchedService(addr); @@ -612,6 +661,7 @@ namespace FdoSecrets } auto client = it.value(); + // client will notify DBusMgr and call DBusMgr::removeClient client->disconnectDBus(); } } // namespace FdoSecrets diff --git a/src/fdosecrets/dbus/DBusMgr.h b/src/fdosecrets/dbus/DBusMgr.h index 918e5e87d..bc32e6fe6 100644 --- a/src/fdosecrets/dbus/DBusMgr.h +++ b/src/fdosecrets/dbus/DBusMgr.h @@ -219,12 +219,7 @@ namespace FdoSecrets private: QDBusConnection m_conn; - struct ProcessInfo - { - uint pid; - QString exePath; - }; - bool serviceInfo(const QString& addr, ProcessInfo& info) const; + bool serviceInfo(const QString& addr, PeerInfo& info) const; bool sendDBusSignal(const QString& path, const QString& interface, diff --git a/src/fdosecrets/objects/Prompt.cpp b/src/fdosecrets/objects/Prompt.cpp index 6ffbce000..9fa06c25e 100644 --- a/src/fdosecrets/objects/Prompt.cpp +++ b/src/fdosecrets/objects/Prompt.cpp @@ -291,7 +291,8 @@ namespace FdoSecrets } if (!entries.isEmpty()) { QString app = tr("%1 (PID: %2)").arg(client->name()).arg(client->pid()); - auto ac = new AccessControlDialog(findWindow(m_windowId), entries, app, AuthOption::Remember); + auto ac = new AccessControlDialog( + findWindow(m_windowId), entries, app, client->processInfo(), AuthOption::Remember); connect(ac, &AccessControlDialog::finished, this, &UnlockPrompt::itemUnlockFinished); connect(ac, &AccessControlDialog::finished, ac, &AccessControlDialog::deleteLater); ac->open(); diff --git a/src/fdosecrets/widgets/AccessControlDialog.cpp b/src/fdosecrets/widgets/AccessControlDialog.cpp index c6ee32a5a..af741ea0f 100644 --- a/src/fdosecrets/widgets/AccessControlDialog.cpp +++ b/src/fdosecrets/widgets/AccessControlDialog.cpp @@ -20,6 +20,7 @@ #include "AccessControlDialog.h" #include "ui_AccessControlDialog.h" +#include "fdosecrets/dbus/DBusClient.h" #include "fdosecrets/widgets/RowButtonHelper.h" #include "core/Entry.h" @@ -31,9 +32,12 @@ AccessControlDialog::AccessControlDialog(QWindow* parent, const QList& entries, const QString& app, + const FdoSecrets::PeerInfo& info, AuthOptions authOptions) : m_ui(new Ui::AccessControlDialog()) + , m_rememberCheck() , m_model(new EntryModel(entries)) + , m_decisions() { if (parent) { // Force the creation of the QWindow, without this windowHandle() will return nullptr @@ -44,19 +48,13 @@ AccessControlDialog::AccessControlDialog(QWindow* parent, } setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); - m_ui->setupUi(this); - - connect(m_ui->cancelButton, &QPushButton::clicked, this, [this]() { done(DenyAll); }); - connect(m_ui->allowButton, &QPushButton::clicked, this, [this]() { done(AllowSelected); }); - connect(m_ui->itemsTable, &QTableView::clicked, m_model.data(), &EntryModel::toggleCheckState); - connect(m_ui->rememberCheck, &QCheckBox::clicked, this, &AccessControlDialog::rememberChecked); connect(this, &QDialog::finished, this, &AccessControlDialog::dialogFinished); - m_ui->rememberMsg->setCloseButtonVisible(false); - m_ui->rememberMsg->setMessageType(MessageWidget::Information); - + m_ui->setupUi(this); m_ui->appLabel->setText(m_ui->appLabel->text().arg(app)); + // items table + connect(m_ui->itemsTable, &QTableView::clicked, m_model.data(), &EntryModel::toggleCheckState); m_ui->itemsTable->setModel(m_model.data()); installWidgetItemDelegate(m_ui->itemsTable, 2, [this](QWidget* p, const QModelIndex& idx) { auto btn = new DenyButton(p, idx); @@ -68,19 +66,85 @@ AccessControlDialog::AccessControlDialog(QWindow* parent, m_ui->itemsTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); m_ui->itemsTable->resizeColumnsToContents(); + // the info widget + m_ui->rememberMsg->setMessageType(MessageWidget::Information); + m_ui->rememberMsg->show(); // sync with m_rememberCheck->setChecked(true) + m_ui->exePathWarn->setMessageType(MessageWidget::Warning); + m_ui->exePathWarn->hide(); + + setupDetails(info); + + // the button box + QString detailsButtonText = tr("Details"); + auto detailsButton = + m_ui->buttonBox->addButton(detailsButtonText + QStringLiteral(" >>"), QDialogButtonBox::HelpRole); + detailsButton->setCheckable(true); + + m_rememberCheck = new QCheckBox(tr("Remember"), this); + m_rememberCheck->setObjectName("rememberCheck"); // for testing + m_rememberCheck->setChecked(true); + m_ui->buttonBox->addButton(m_rememberCheck, QDialogButtonBox::ActionRole); + + auto allowButton = m_ui->buttonBox->addButton(tr("Allow Selected"), QDialogButtonBox::AcceptRole); + allowButton->setDefault(true); + + auto cancelButton = m_ui->buttonBox->addButton(tr("Deny All"), QDialogButtonBox::RejectRole); + + connect(cancelButton, &QPushButton::clicked, this, [this]() { done(DenyAll); }); + connect(allowButton, &QPushButton::clicked, this, [this]() { done(AllowSelected); }); + connect(m_rememberCheck, &QCheckBox::clicked, this, &AccessControlDialog::rememberChecked); + connect(detailsButton, &QPushButton::clicked, this, [=](bool checked) { + m_ui->detailsContainer->setVisible(checked); + if (checked) { + detailsButton->setText(detailsButtonText + QStringLiteral(" <<")); + } else { + detailsButton->setText(detailsButtonText + QStringLiteral(" >>")); + } + adjustSize(); + }); + + // tune the UI according to options if (!authOptions.testFlag(AuthOption::Remember)) { - m_ui->rememberCheck->setHidden(true); - m_ui->rememberCheck->setChecked(false); + m_rememberCheck->hide(); + m_rememberCheck->setChecked(false); } if (!authOptions.testFlag(AuthOption::PerEntryDeny)) { m_ui->itemsTable->horizontalHeader()->setSectionHidden(2, true); } - m_ui->allowButton->setFocus(); + // show warning and details if not valid + if (!info.valid) { + m_ui->exePathWarn->show(); + detailsButton->click(); + } + + allowButton->setFocus(); } AccessControlDialog::~AccessControlDialog() = default; +void AccessControlDialog::setupDetails(const FdoSecrets::PeerInfo& info) +{ + QTreeWidgetItem* item = nullptr; + for (auto it = info.hierarchy.crbegin(); it != info.hierarchy.crend(); ++it) { + QStringList columns = { + it->name, + QString::number(it->pid), + it->exePath, + it->command, + }; + if (item) { + item = new QTreeWidgetItem(item, columns); + } else { + item = new QTreeWidgetItem(m_ui->procTree, columns); + } + } + m_ui->procTree->expandAll(); + m_ui->procTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_ui->procTree->scrollToBottom(); + m_ui->detailsContainer->hide(); +} + void AccessControlDialog::rememberChecked(bool checked) { if (checked) { @@ -101,8 +165,8 @@ void AccessControlDialog::denyEntryClicked(Entry* entry, const QModelIndex& inde void AccessControlDialog::dialogFinished(int result) { - auto allow = m_ui->rememberCheck->isChecked() ? AuthDecision::Allowed : AuthDecision::AllowedOnce; - auto deny = m_ui->rememberCheck->isChecked() ? AuthDecision::Denied : AuthDecision::DeniedOnce; + auto allow = m_rememberCheck->isChecked() ? AuthDecision::Allowed : AuthDecision::AllowedOnce; + auto deny = m_rememberCheck->isChecked() ? AuthDecision::Denied : AuthDecision::DeniedOnce; for (int row = 0; row != m_model->rowCount({}); ++row) { auto entry = m_model->data(m_model->index(row, 2), Qt::EditRole).value(); diff --git a/src/fdosecrets/widgets/AccessControlDialog.h b/src/fdosecrets/widgets/AccessControlDialog.h index 894413dd0..7f491f7ce 100644 --- a/src/fdosecrets/widgets/AccessControlDialog.h +++ b/src/fdosecrets/widgets/AccessControlDialog.h @@ -21,6 +21,7 @@ #define KEEPASSXC_FDOSECRETS_ACCESSCONTROLDIALOG_H #include +#include #include #include #include @@ -35,6 +36,11 @@ namespace Ui class AccessControlDialog; } +namespace FdoSecrets +{ + struct PeerInfo; +} + enum class AuthOption { None = 0, @@ -52,6 +58,7 @@ public: explicit AccessControlDialog(QWindow* parent, const QList& entries, const QString& app, + const FdoSecrets::PeerInfo& info, AuthOptions authOptions = AuthOption::Remember | AuthOption::PerEntryDeny); ~AccessControlDialog() override; @@ -76,7 +83,10 @@ private: class EntryModel; class DenyButton; + void setupDetails(const FdoSecrets::PeerInfo& info); + QScopedPointer m_ui; + QPointer m_rememberCheck; QScopedPointer m_model; QHash m_decisions; }; diff --git a/src/fdosecrets/widgets/AccessControlDialog.ui b/src/fdosecrets/widgets/AccessControlDialog.ui index 2de17d5b8..3e4785273 100644 --- a/src/fdosecrets/widgets/AccessControlDialog.ui +++ b/src/fdosecrets/widgets/AccessControlDialog.ui @@ -14,6 +14,16 @@ KeePassXC - Access Request + + + + Non-existing/inaccessible executable path. Please double-check the client is legit. + + + false + + + @@ -30,8 +40,62 @@ + + + + + + + + 0 + 0 + + + + true + + + true + + + true + + + false + + + + Name + + + + + PID + + + + + Executable + + + + + Command Line + + + + + + + + + + 0 + 0 + + QAbstractItemView::NoEditTriggers @@ -63,56 +127,17 @@ Your decision for above entries will be remembered for the duration the requesting client is running. + + false + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Remember - - - true - - - - - - - Allow access to entries - - - Allow Selected - - - true - - - true - - - - - - - Deny All - - - true + + + QDialogButtonBox::NoButton diff --git a/src/fdosecrets/widgets/SettingsModels.cpp b/src/fdosecrets/widgets/SettingsModels.cpp index d99079abd..6f318b7bb 100644 --- a/src/fdosecrets/widgets/SettingsModels.cpp +++ b/src/fdosecrets/widgets/SettingsModels.cpp @@ -28,6 +28,8 @@ namespace FdoSecrets { + // static constexpr still requires definition before c++17 + constexpr const char* SettingsDatabaseModel::ColumnNames[]; SettingsDatabaseModel::SettingsDatabaseModel(DatabaseTabWidget* dbTabs, QObject* parent) : QAbstractTableModel(parent) @@ -58,7 +60,7 @@ namespace FdoSecrets if (parent.isValid()) { return 0; } - return 3; + return sizeof(ColumnNames) / sizeof(ColumnNames[0]); } QVariant SettingsDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -71,16 +73,11 @@ namespace FdoSecrets return {}; } - switch (section) { - case 0: - return tr("File Name"); - case 1: - return tr("Group"); - case 2: - return tr("Manage"); - default: + if (section < 0 || section >= columnCount({})) { return {}; } + + return qApp->translate(metaObject()->className(), ColumnNames[section]); } QVariant SettingsDatabaseModel::data(const QModelIndex& index, int role) const @@ -88,17 +85,24 @@ namespace FdoSecrets if (!index.isValid()) { return {}; } + if (index.model() != this) { + return {}; + } + if (index.row() >= rowCount({}) || index.column() >= columnCount({})) { + return {}; + } + const auto& dbWidget = m_dbs[index.row()]; if (!dbWidget) { return {}; } switch (index.column()) { - case 0: + case ColumnFileName: return dataForName(dbWidget, role); - case 1: + case ColumnGroup: return dataForExposedGroup(dbWidget, role); - case 2: + case ColumnManage: return dataForManage(dbWidget, role); default: return {}; @@ -240,6 +244,9 @@ namespace FdoSecrets } } + // static constexpr still requires definition before c++17 + constexpr const char* SettingsClientModel::ColumnNames[]; + SettingsClientModel::SettingsClientModel(DBusMgr& dbus, QObject* parent) : QAbstractTableModel(parent) , m_dbus(dbus) @@ -260,7 +267,7 @@ namespace FdoSecrets if (parent.isValid()) { return 0; } - return 2; + return sizeof(ColumnNames) / sizeof(ColumnNames[0]); } QVariant SettingsClientModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -273,14 +280,11 @@ namespace FdoSecrets return {}; } - switch (section) { - case 0: - return tr("Application"); - case 1: - return tr("Manage"); - default: + if (section < 0 || section >= columnCount({})) { return {}; } + + return qApp->translate(metaObject()->className(), ColumnNames[section]); } QVariant SettingsClientModel::data(const QModelIndex& index, int role) const @@ -288,15 +292,26 @@ namespace FdoSecrets if (!index.isValid()) { return {}; } + if (index.model() != this) { + return {}; + } + if (index.row() >= rowCount({}) || index.column() >= columnCount({})) { + return {}; + } + const auto& client = m_clients[index.row()]; if (!client) { return {}; } switch (index.column()) { - case 0: + case ColumnApplication: return dataForApplication(client, role); - case 1: + case ColumnPID: + return dataForPID(client, role); + case ColumnDBus: + return dataForDBus(client, role); + case ColumnManage: return dataForManage(client, role); default: return {}; @@ -305,9 +320,44 @@ namespace FdoSecrets QVariant SettingsClientModel::dataForApplication(const DBusClientPtr& client, int role) const { + const auto& info = client->processInfo(); switch (role) { case Qt::DisplayRole: - return client->name(); + if (info.exePath().isEmpty()) { + return tr("Unknown"); + } + return info.exePath(); + case Qt::ToolTipRole: + if (!info.valid) { + return tr("Non-existing/inaccessible executable path. Please double-check the client is legit."); + } + return {}; + case Qt::DecorationRole: + // give some visual clues if the path is invalid + if (!info.valid) { + return icons()->icon(QStringLiteral("dialog-warning")); + } + return {}; + default: + return {}; + } + } + + QVariant SettingsClientModel::dataForPID(const DBusClientPtr& client, int role) const + { + switch (role) { + case Qt::DisplayRole: + return client->pid(); + default: + return {}; + } + } + + QVariant SettingsClientModel::dataForDBus(const DBusClientPtr& client, int role) const + { + switch (role) { + case Qt::DisplayRole: + return client->address(); default: return {}; } diff --git a/src/fdosecrets/widgets/SettingsModels.h b/src/fdosecrets/widgets/SettingsModels.h index 1482f5093..a6cdf41de 100644 --- a/src/fdosecrets/widgets/SettingsModels.h +++ b/src/fdosecrets/widgets/SettingsModels.h @@ -40,6 +40,18 @@ namespace FdoSecrets QVariant data(const QModelIndex& index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + enum Column + { + ColumnFileName, + ColumnGroup, + ColumnManage, + }; + static constexpr const char* ColumnNames[] = { + QT_TRANSLATE_NOOP("SettingsDatabaseModel", "File Name"), + QT_TRANSLATE_NOOP("SettingsDatabaseModel", "Group"), + QT_TRANSLATE_NOOP("SettingsDatabaseModel", "Manage"), + }; + private: QVariant dataForName(DatabaseWidget* db, int role) const; static QVariant dataForExposedGroup(DatabaseWidget* db, int role); @@ -71,8 +83,24 @@ namespace FdoSecrets QVariant data(const QModelIndex& index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + enum Column + { + ColumnApplication, + ColumnPID, + ColumnDBus, + ColumnManage, + }; + static constexpr const char* ColumnNames[] = { + QT_TRANSLATE_NOOP("SettingsClientModel", "Application"), + QT_TRANSLATE_NOOP("SettingsClientModel", "PID"), + QT_TRANSLATE_NOOP("SettingsClientModel", "DBus Address"), + QT_TRANSLATE_NOOP("SettingsClientModel", "Manage"), + }; + private: QVariant dataForApplication(const DBusClientPtr& client, int role) const; + QVariant dataForPID(const DBusClientPtr& client, int role) const; + QVariant dataForDBus(const DBusClientPtr& client, int role) const; QVariant dataForManage(const DBusClientPtr& client, int role) const; private slots: diff --git a/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp b/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp index 251f18ed9..5ab22749b 100644 --- a/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp +++ b/src/fdosecrets/widgets/SettingsWidgetFdoSecrets.cpp @@ -209,28 +209,30 @@ SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWi auto clientModel = new SettingsClientModel(*plugin->dbus(), this); m_ui->tableClients->setModel(clientModel); - installWidgetItemDelegate( - m_ui->tableClients, 1, [](QWidget* p, const QModelIndex&) { return new ManageSession(p); }); + installWidgetItemDelegate(m_ui->tableClients, + SettingsClientModel::ColumnManage, + [](QWidget* p, const QModelIndex&) { return new ManageSession(p); }); // config header after setting model, otherwise the header doesn't have enough sections auto clientViewHeader = m_ui->tableClients->horizontalHeader(); clientViewHeader->setSelectionMode(QAbstractItemView::NoSelection); clientViewHeader->setSectionsClickable(false); - clientViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application - clientViewHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button + clientViewHeader->setSectionResizeMode(QHeaderView::ResizeToContents); + clientViewHeader->setSectionResizeMode(SettingsClientModel::ColumnApplication, QHeaderView::Stretch); auto dbModel = new SettingsDatabaseModel(plugin->dbTabs(), this); m_ui->tableDatabases->setModel(dbModel); installWidgetItemDelegate( - m_ui->tableDatabases, 2, [plugin](QWidget* p, const QModelIndex&) { return new ManageDatabase(plugin, p); }); + m_ui->tableDatabases, SettingsDatabaseModel::ColumnManage, [plugin](QWidget* p, const QModelIndex&) { + return new ManageDatabase(plugin, p); + }); // config header after setting model, otherwise the header doesn't have enough sections auto dbViewHeader = m_ui->tableDatabases->horizontalHeader(); dbViewHeader->setSelectionMode(QAbstractItemView::NoSelection); dbViewHeader->setSectionsClickable(false); - dbViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // file name - dbViewHeader->setSectionResizeMode(1, QHeaderView::Stretch); // group - dbViewHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); // manage button + dbViewHeader->setSectionResizeMode(QHeaderView::Stretch); + dbViewHeader->setSectionResizeMode(SettingsDatabaseModel::ColumnManage, QHeaderView::ResizeToContents); // prompt the user to save settings before the sections are enabled connect(m_plugin, &FdoSecretsPlugin::secretServiceStarted, this, &SettingsWidgetFdoSecrets::updateServiceState); diff --git a/tests/gui/TestGuiFdoSecrets.cpp b/tests/gui/TestGuiFdoSecrets.cpp index a8dae4476..5e8bd01cd 100644 --- a/tests/gui/TestGuiFdoSecrets.cpp +++ b/tests/gui/TestGuiFdoSecrets.cpp @@ -117,7 +117,9 @@ class FakeClient : public DBusClient { public: explicit FakeClient(DBusMgr* dbus) - : DBusClient(dbus, QStringLiteral("local"), 0, "fake-client") + : DBusClient( + dbus, + {QStringLiteral("local"), 0, true, {ProcInfo{0, 0, QStringLiteral("fake-client"), QString{}, QString{}}}}) { } };