certificatelineedit.cpp 11.3 KB
Newer Older
1
2
3
/*  crypto/gui/certificatelineedit.cpp

    This file is part of Kleopatra, the KDE keymanager
4
5
    SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
    SPDX-FileContributor: Intevation GmbH
6
7
    SPDX-FileCopyrightText: 2021 g10 Code GmbH
    SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9

    SPDX-License-Identifier: GPL-2.0-or-later
10
11
12
13
14
15
16
17
18
19
20
*/

#include "certificatelineedit.h"

#include <QCompleter>
#include <QPushButton>
#include <QAction>
#include <QSignalBlocker>

#include "kleopatra_debug.h"

21
#include <Libkleo/KeyCache>
22
#include <Libkleo/KeyFilter>
23
#include <Libkleo/KeyList>
24
25
26
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Formatting>
27
28
29
30
31

#include <KLocalizedString>

#include <gpgme++/key.h>

32
33
34
#include <QGpgME/KeyForMailboxJob>
#include <QGpgME/Protocol>

35
36
37
using namespace Kleo;
using namespace GpgME;

38
Q_DECLARE_METATYPE(GpgME::Key)
39
Q_DECLARE_METATYPE(KeyGroup)
40

41
42
static QStringList s_lookedUpKeys;

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
namespace
{
class ProxyModel : public KeyListSortFilterProxyModel
{
    Q_OBJECT

public:
    ProxyModel(QObject *parent = nullptr)
        : KeyListSortFilterProxyModel(parent)
    {
    }

    QVariant data(const QModelIndex &index, int role) const override
    {
        if (!index.isValid()) {
            return QVariant();
        }

        switch (role) {
        case Qt::DecorationRole: {
63
            const auto key = KeyListSortFilterProxyModel::data(index, KeyList::KeyRole).value<GpgME::Key>();
64
65
            if (!key.isNull()) {
                return Kleo::Formatting::iconForUid(key.userID(0));
66
            }
67
68
69
70
71
72
73
74

            const auto group = KeyListSortFilterProxyModel::data(index, KeyList::GroupRole).value<KeyGroup>();
            if (!group.isNull()) {
                return QIcon::fromTheme(QStringLiteral("group"));
            }

            Q_ASSERT(!key.isNull() || !group.isNull());
            return QVariant();
75
76
77
78
79
80
81
82
        }
        default:
            return KeyListSortFilterProxyModel::data(index, role);
        }
    }
};
} // namespace

83
84
85
CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model,
                                         QWidget *parent,
                                         KeyFilter *filter)
86
    : QLineEdit(parent),
87
      mFilterModel(new KeyListSortFilterProxyModel(this)),
88
      mCompleterFilterModel(new ProxyModel(this)),
89
      mCompleter(new QCompleter(this)),
90
      mFilter(std::shared_ptr<KeyFilter>(filter)),
Laurent Montel's avatar
Laurent Montel committed
91
      mLineAction(new QAction(this))
92
{
93
94
95
    setPlaceholderText(i18n("Please enter a name or email address..."));
    setClearButtonEnabled(true);
    addAction(mLineAction, QLineEdit::LeadingPosition);
96

97
98
    mCompleterFilterModel->setKeyFilter(mFilter);
    mCompleterFilterModel->setSourceModel(model);
99
100
101
102
103
    mCompleter->setModel(mCompleterFilterModel);
    mCompleter->setCompletionColumn(KeyList::Summary);
    mCompleter->setFilterMode(Qt::MatchContains);
    mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
    setCompleter(mCompleter);
104
    mFilterModel->setSourceModel(model);
105
    mFilterModel->setFilterKeyColumn(KeyList::Summary);
106
107
108
109
    if (filter) {
        mFilterModel->setKeyFilter(mFilter);
    }

110
    connect(KeyCache::instance().get(), &Kleo::KeyCache::keyListingDone,
111
            this, &CertificateLineEdit::updateKey);
112
113
    connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated,
            this, [this] (const KeyGroup &group) {
114
                if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
115
                    QSignalBlocker blocky(this);
116
117
118
                    setText(Formatting::summaryLine(group));
                    // queue the update to ensure that the model has been updated
                    QMetaObject::invokeMethod(this, &CertificateLineEdit::updateKey, Qt::QueuedConnection);
119
120
                }
            });
121
122
123
    connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved,
            this, [this] (const KeyGroup &group) {
                if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
124
125
126
                    mGroup = KeyGroup();
                    QSignalBlocker blocky(this);
                    clear();
127
128
129
130
                    // queue the update to ensure that the model has been updated
                    QMetaObject::invokeMethod(this, &CertificateLineEdit::updateKey, Qt::QueuedConnection);
                }
            });
131
    connect(this, &QLineEdit::editingFinished,
132
133
134
135
            this, [this] () {
                // queue the call of editFinished() to ensure that QCompleter::activated is handled first
                QMetaObject::invokeMethod(this, &CertificateLineEdit::editFinished, Qt::QueuedConnection);
            });
136
    connect(this, &QLineEdit::textChanged,
137
138
139
            this, &CertificateLineEdit::editChanged);
    connect(mLineAction, &QAction::triggered,
            this, &CertificateLineEdit::dialogRequested);
140
141
142
143
144
145
146
147
148
149
150
151
    connect(mCompleter, QOverload<const QModelIndex &>::of(&QCompleter::activated),
            this, [this] (const QModelIndex &index) {
                Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value<Key>();
                KeyGroup group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value<KeyGroup>();
                if (!key.isNull()) {
                    setKey(key);
                } else if (!group.isNull()) {
                    setGroup(group);
                } else {
                    qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group";
                }
            });
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    updateKey();

    /* Take ownership of the model to prevent double deletion when the
     * filter models are deleted */
    model->setParent(parent ? parent : this);
}

void CertificateLineEdit::editChanged()
{
    updateKey();
    if (!mEditStarted) {
        Q_EMIT editingStarted();
        mEditStarted = true;
    }
    mEditFinished = false;
}

169
170
171
172
173
void CertificateLineEdit::editFinished()
{
    mEditStarted = false;
    mEditFinished = true;
    updateKey();
174
    checkLocate();
175
176
}

177
178
void CertificateLineEdit::checkLocate()
{
179
180
    if (!key().isNull() || !group().isNull()) {
        // Already have a key or group
181
182
183
184
185
186
187
188
189
190
191
192
193
194
        return;
    }

    // Only check once per mailbox
    const auto mailText = text();
    if (s_lookedUpKeys.contains(mailText)) {
        return;
    }
    s_lookedUpKeys << mailText;
    qCDebug(KLEOPATRA_LOG) << "Lookup job for" << mailText;
    QGpgME::KeyForMailboxJob *job = QGpgME::openpgp()->keyForMailboxJob();
    job->start(mailText);
}

195
196
void CertificateLineEdit::updateKey()
{
197
198
    static const _detail::ByFingerprint<std::equal_to> keysHaveSameFingerprint;

199
    const auto mailText = text();
200
    auto newKey = Key();
201
    auto newGroup = KeyGroup();
202
    if (mailText.isEmpty()) {
203
204
        mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
        mLineAction->setToolTip(i18n("Open selection dialog."));
205
206
207
    } else {
        mFilterModel->setFilterFixedString(mailText);
        if (mFilterModel->rowCount() > 1) {
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
            // keep current key or group if they still match
            if (!mKey.isNull()) {
                for (int row = 0; row < mFilterModel->rowCount(); ++row) {
                    const QModelIndex index = mFilterModel->index(row, 0);
                    Key key = mFilterModel->key(index);
                    if (!key.isNull() && keysHaveSameFingerprint(key, mKey)) {
                        newKey = mKey;
                        break;
                    }
                }
            } else if (!mGroup.isNull()) {
                newGroup = mGroup;
                for (int row = 0; row < mFilterModel->rowCount(); ++row) {
                    const QModelIndex index = mFilterModel->index(row, 0);
                    KeyGroup group = mFilterModel->group(index);
                    if (!group.isNull() && group.source() == mGroup.source() && group.id() == mGroup.id()) {
                        newGroup = mGroup;
                        break;
                    }
                }
            }
            if (newKey.isNull() && newGroup.isNull()) {
                if (mEditFinished) {
231
                    mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("question")));
232
233
234
235
236
                    mLineAction->setToolTip(i18n("Multiple certificates"));
                } else {
                    mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
                    mLineAction->setToolTip(i18n("Open selection dialog."));
                }
237
238
            }
        } else if (mFilterModel->rowCount() == 1) {
239
240
            const auto index = mFilterModel->index(0, 0);
            newKey = mFilterModel->data(index, KeyList::KeyRole).value<Key>();
241
242
            newGroup = mFilterModel->data(index, KeyList::GroupRole).value<KeyGroup>();
            Q_ASSERT(!newKey.isNull() || !newGroup.isNull());
243
            if (newKey.isNull() && newGroup.isNull()) {
244
245
246
                mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")));
                mLineAction->setToolTip(i18n("No matching certificates found.<br/>Click here to import a certificate."));
            }
247
248
249
250
251
252
        } else {
            mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")));
            mLineAction->setToolTip(i18n("No matching certificates found.<br/>Click here to import a certificate."));
        }
    }
    mKey = newKey;
253
    mGroup = newGroup;
254

255
    if (!mKey.isNull()) {
256
257
258
259
        /* FIXME: This needs to be solved by a multiple UID supporting model */
        mLineAction->setIcon(Formatting::iconForUid(mKey.userID(0)));
        mLineAction->setToolTip(Formatting::validity(mKey.userID(0)) +
                                QLatin1String("<br/>") + i18n("Click for details."));
260
261
        setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions));
    } else if (!mGroup.isNull()) {
262
263
264
        mLineAction->setIcon(Formatting::validityIcon(mGroup));
        mLineAction->setToolTip(Formatting::validity(mGroup) +
                                QLatin1String("<br/>") + i18n("Click for details."));
265
        setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions));
266
    } else {
267
        setToolTip(QString());
268
269
270
    }

    Q_EMIT keyChanged();
271
272
273
274

    if (mailText.isEmpty()) {
        Q_EMIT wantsRemoval(this);
    }
275
276
277
278
}

Key CertificateLineEdit::key() const
{
279
280
281
282
283
    if (isEnabled()) {
        return mKey;
    } else {
        return Key();
    }
284
285
}

286
287
288
289
290
291
292
293
294
KeyGroup CertificateLineEdit::group() const
{
    if (isEnabled()) {
        return mGroup;
    } else {
        return KeyGroup();
    }
}

295
void CertificateLineEdit::setKey(const Key &key)
296
{
297
298
    mKey = key;
    mGroup = KeyGroup();
299
    QSignalBlocker blocky(this);
300
301
    qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key);
    setText(Formatting::summaryLine(key));
302
    updateKey();
303
304
}

305
306
void CertificateLineEdit::setGroup(const KeyGroup &group)
{
307
308
    mGroup = group;
    mKey = Key();
309
310
311
312
313
314
315
    QSignalBlocker blocky(this);
    const QString summary = Formatting::summaryLine(group);
    qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary;
    setText(summary);
    updateKey();
}

316
317
bool CertificateLineEdit::isEmpty() const
{
318
    return text().isEmpty();
319
}
320

321
322
323
324
void CertificateLineEdit::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
    mFilter = filter;
    mFilterModel->setKeyFilter(filter);
325
326
    mCompleterFilterModel->setKeyFilter(mFilter);
    updateKey();
327
328
}

329
#include "certificatelineedit.moc"