12#include "ui_usersdialog.h"
14#include <QApplication>
16#include <QDialogButtonBox>
26#include <QRegularExpression>
28#include <QSignalBlocker>
48 connect(
ui->buttonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject);
59 connect(
ui->buttonBox, &QDialogButtonBox::accepted,
this,
61 connect(
ui->buttonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject);
62 connect(
ui->listWidget, &QListWidget::itemChanged,
this,
67 ui->lineEdit->setClearButtonEnabled(
true);
75 QList<UserInfo> users =
m_pass->listKeys();
76 if (users.isEmpty()) {
77 QMessageBox::critical(parentWidget(), tr(
"Keylist missing"),
78 tr(
"Could not fetch list of available GPG keys"));
91 QSet<QString> secretKeyIds;
92 for (
const UserInfo &sec : secret_keys) {
93 secretKeyIds.insert(sec.key_id);
95 for (
auto &user : users) {
96 if (secretKeyIds.contains(user.key_id)) {
97 user.have_secret =
true;
105 auto *banner =
new QLabel(
this);
106 banner->setTextFormat(Qt::PlainText);
107 banner->setText(text);
108 banner->setObjectName(QStringLiteral(
"recipientWarning"));
109 banner->setWordWrap(
true);
112 QFont bold = banner->font();
114 banner->setFont(bold);
115 QPalette palette = banner->palette();
116 palette.setColor(QPalette::WindowText, QColor(Qt::red));
117 banner->setPalette(palette);
118 banner->setContentsMargins(4, 4, 4, 4);
119 ui->verticalLayout->insertWidget(1, banner);
125auto normalisedKeyId(
const QString &
id) -> QString {
126 const QString trimmed =
id.trimmed();
127 return (trimmed.startsWith(QLatin1String(
"0x"), Qt::CaseInsensitive)
139auto answersTo(
const UserInfo &key,
const QString &recipient) ->
bool {
140 static const QRegularExpression hexId(
141 QStringLiteral(
"^(0x)?[0-9A-Fa-f]{8,}$"));
142 if (hexId.match(recipient.trimmed()).hasMatch()) {
143 const QString
id = normalisedKeyId(recipient);
144 const QString keyId = normalisedKeyId(key.key_id);
145 return keyId.endsWith(
id) || (keyId.size() >= 8 &&
id.endsWith(keyId));
147 QString term = recipient.trimmed();
148 if (term.startsWith(u
'=') || term.startsWith(u
'@') || term.startsWith(u
'*')) {
150 }
else if (term.startsWith(u
'<') && term.endsWith(u
'>')) {
151 term = term.mid(1, term.size() - 2);
153 return !term.isEmpty() && key.name.contains(term, Qt::CaseInsensitive);
163 constexpr int kMaxLookups = 10;
167 QHash<QString, bool> perLineHasKey;
171 if (resolved.isEmpty() && recipients.size() > 1) {
177 for (
const QString &recipient : recipients) {
178 if (lookups++ >= kMaxLookups) {
182 perLineHasKey.insert(recipient, !keys.isEmpty());
187 if (std::any_of(resolved.cbegin(), resolved.cend(),
189 return key.key_id == user.key_id;
202 for (
const QString &recipient : recipients) {
204 const auto cached = perLineHasKey.constFind(recipient);
205 if (cached != perLineHasKey.constEnd()) {
206 found = cached.value();
208 found = std::any_of(resolved.cbegin(), resolved.cend(),
210 return answersTo(key, recipient);
212 if (!found && lookups++ < kMaxLookups) {
219 missing.
key_id = recipient;
220 missing.
name =
" ?? " + tr(
"Key not found in keyring");
237 if (!loaded.
warning.isEmpty()) {
265 [](
const UserInfo &user) { return user.enabled; });
269 if (
auto *ok =
ui->buttonBox->button(QDialogButtonBox::Ok)) {
275 switch (event->key()) {
277 ui->lineEdit->clear();
289 const int index = item->data(Qt::UserRole).toInt(&ok);
292 <<
"UsersDialog::itemChange: invalid user index data for item";
295 if (index < 0 || index >=
m_userList.size()) {
296 qCWarning(lcQtPass) <<
"UsersDialog::itemChange: user index out of range:"
297 << index <<
"valid range is [0,"
301 m_userList[index].enabled = item->checkState() == Qt::Checked;
309 QString patternString =
"*" + filter +
"*";
311 QRegularExpression re(
312 QRegularExpression::wildcardToRegularExpression(patternString),
313 QRegularExpression::CaseInsensitiveOption);
320 ui->listWidget->clear();
330 item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
331 item->setData(Qt::UserRole, QVariant::fromValue(i));
332 ui->listWidget->addItem(item);
338 const QRegularExpression &nameFilter)
const {
339 if (!filter.isEmpty() && !nameFilter.match(user.
name).hasMatch()) {
345 if (user.
enabled ||
ui->checkBox->isChecked()) {
357 return user.validity ==
'e' ||
362 QString text = user.
name +
"\n" + user.
key_id;
364 text +=
" " + tr(
"created") +
" " +
365 QLocale::system().toString(user.
created, QLocale::ShortFormat);
367 if (user.
expiry.isValid()) {
368 text +=
" " + tr(
"expires") +
" " +
369 QLocale::system().toString(user.
expiry, QLocale::ShortFormat);
376 const QString originalText = item->text();
385 item->setBackground(Qt::darkRed);
386 item->setForeground(Qt::white);
387 item->setText(tr(
"[EXPIRED] ") + originalText);
389 item->setBackground(Qt::darkRed);
390 item->setForeground(Qt::white);
391 item->setText(tr(
"[INVALID] ") + originalText);
393 item->setBackground(Qt::darkYellow);
394 item->setForeground(Qt::white);
395 item->setText(tr(
"[PARTIAL] ") + originalText);
397 item->setText(originalText);
402 item->setForeground(QApplication::palette().color(QPalette::Link));
404 QFont font = item->font();
418 const auto sameKey = [&importedKey](
const QString &keyId) {
419 return (keyId.length() >= 16 &&
420 importedKey.endsWith(keyId, Qt::CaseInsensitive)) ||
421 (importedKey.length() >= 16 &&
422 keyId.endsWith(importedKey, Qt::CaseInsensitive));
425 for (
int i = 0; i <
ui->listWidget->count(); ++i) {
426 QListWidgetItem *item =
ui->listWidget->item(i);
428 const int idx = item !=
nullptr ? item->data(Qt::UserRole).toInt(&ok) : -1;
429 if (ok && idx >= 0 && idx <
m_userList.size() &&
431 ui->listWidget->setCurrentItem(item);
439 if (dialog.exec() != QDialog::Accepted) {
446 if (importedKey.isEmpty()) {
454 QStringList selected;
457 selected.append(user.key_id);
473 QStringList unresolved;
474 for (
const QString &keyId : std::as_const(selected)) {
475 bool matched =
false;
477 if (user.key_id == keyId) {
483 unresolved.append(keyId);
486 if (!unresolved.isEmpty()) {
493 const QSignalBlocker blocker(
ui->lineEdit);
494 ui->lineEdit->clear();
Dialog for importing GPG keys from file or clipboard.
auto importedKeyId() const -> QString
Get the imported key id on success.
Abstract base class for password store operations.
auto listKeys(QStringList keystrings, bool secret=false) -> QList< UserInfo >
List GPG keys matching patterns.
auto recipientsForEditing(const QString &dir, const QString &passStore) -> RecipientsForEditing
The recipients a dialog may preselect for editing the list of dir.
virtual void Init(QString path, const QList< UserInfo > &users)=0
Initialize new password store.
QString m_cachedPatternString
~UsersDialog() override
Destroys the dialog and its generated form.
void updateOkButton()
Enable OK only while at least one key is ticked (hasSelection()).
void itemChange(QListWidgetItem *item)
Handle user selection change.
QRegularExpression m_cachedNameFilter
void on_importKeyButton_clicked()
Import a GPG key and refresh the list.
void on_lineEdit_textChanged(const QString &filter)
Filter user list.
void showRecipientWarning(const QString &text)
Show text above the key list in bold red, for a recipient list that could not be verified.
auto isUserExpired(const UserInfo &user) const -> bool
Check if GPG key is expired.
void keyPressEvent(QKeyEvent *event) override
Handle key press.
void applyUserStyling(QListWidgetItem *item, const UserInfo &user) const
Apply visual styling to user item.
Preselect
Whether the recipients on disk are ticked when it opens.
@ Nothing
None: for a store that had no list when it was chosen.
QList< UserInfo > m_userList
void connectSignals()
Connect dialog signals.
QDateTime m_cachedCurrentDateTime
void restoreDialogState()
QString buildUserText(const UserInfo &user) const
Build display text for user.
void populateList(const QString &filter=QString())
Populate user list.
void selectImportedKey(const QString &importedKey)
Make the key gpg just imported the current row. gpg reports a 16-character long ID (IMPORTED) or a 40...
void selectRecipients(const QStringList &recipients)
Tick the keys the folder is encrypted to, and list recipients no key in the keyring answers to,...
void loadRecipients()
Load recipients and handle missing keys.
void accept() override
Handle dialog acceptance.
bool m_cachedDateTimeValid
UsersDialog(Pass *pass, const AppSettings &s, QString dir, QWidget *parent=nullptr)
Construct users dialog.
void markSecretKeys(QList< UserInfo > &users)
Mark which keys have secret counterparts.
bool passesFilter(const UserInfo &user, const QString &filter, const QRegularExpression &nameFilter) const
Check if user passes filter.
auto hasSelection() const -> bool
Whether at least one key is ticked.
void on_checkBox_clicked()
Handle select all checkbox.
QScopedPointer< Ui::UsersDialog > ui
The widgets from usersdialog.ui.
auto loadGpgKeys() -> bool
Load GPG keys and determine secret key status.
Dialog that imports an ASCII-armored GPG public key from a file or the clipboard by running gpg --imp...
void attach(QDialog &dialog, const QString &key)
restore() now, save() whenever the dialog finishes.
Pass, the abstract password-store backend that RealPass and ImitatePass implement,...
The "qtpass" logging category.
QtPassSettings, the QSettings singleton behind every saved setting, the profiles and the Pass backend...
Plain value-object holding all flat QtPass configuration.
What a dialog may do with the list of a folder (recipientsForEditing()).
QStringList recipients
The recipients to preselect; empty when Rejected.
State state
How the list on disk stood up.
QString warning
What the dialog should show, if anything.
@ Rejected
Not to be used: signature invalid, written for another folder or for no folder in particular while a ...
Stores GPG key info including validity, creation date, and more.
bool have_secret
Whether secret key is available (can decrypt with this key).
bool enabled
Whether this user/key is enabled for normal use.
QString key_id
Hexadecimal representation of the GnuPG key identifier.
auto fullyValid() const -> bool
Check full validity per GnuPG validity codes.
auto isValid() const -> bool
Check whether the key has any usable validity level.
QDateTime created
Date/time when key was created.
QString name
GPG user ID / full name.
QDateTime expiry
Date/time when key expires.
UsersDialog, the recipient picker for a folder's .gpg-id.
WindowStateStore: saves and restores top-level window geometry through QtPassSettings,...