certificateselectiondialog.cpp 14.9 KB
Newer Older
Frank Osterfeld's avatar
Frank Osterfeld committed
1
2
3
4
/* -*- mode: c++; c-basic-offset:4 -*-
    dialogs/certificateselectiondialog.cpp

    This file is part of Kleopatra, the KDE keymanager
5
    SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
Frank Osterfeld's avatar
Frank Osterfeld committed
6

7
    SPDX-License-Identifier: GPL-2.0-or-later
Frank Osterfeld's avatar
Frank Osterfeld committed
8
9
*/

Marc Mutz's avatar
Marc Mutz committed
10
11
#include <config-kleopatra.h>

12
13
#include "certificateselectiondialog.h"

14
15
#include "conf/groupsconfigdialog.h"

16
#include <view/keytreeview.h>
17
18
19
#include <view/searchbar.h>
#include <view/tabwidget.h>

20
21
#include "utils/tags.h"

22
#include <Libkleo/KeyGroup>
23
24
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyCache>
25

26
#include <commands/reloadkeyscommand.h>
27
28
#include <commands/lookupcertificatescommand.h>
#include <commands/newcertificatecommand.h>
29
#include <commands/importcertificatefromfilecommand.h>
30
31
32

#include <gpgme++/key.h>

Laurent Montel's avatar
Laurent Montel committed
33
#include <KLocalizedString>
34
#include <KConfigDialog>
35
36
#include <KConfigGroup>
#include <KSharedConfig>
Laurent Montel's avatar
Laurent Montel committed
37

38
39
40
41
42
#include <QLabel>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QItemSelectionModel>
#include <QAbstractItemView>
43
#include <QPointer>
Laurent Montel's avatar
Laurent Montel committed
44
#include <QVBoxLayout>
45
46
47

#include <algorithm>

48
49
50
51
52
#include <gpgme++/gpgmepp_version.h>
#if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
# define GPGME_HAS_REMARKS
#endif

53
54
using namespace Kleo;
using namespace Kleo::Dialogs;
55
using namespace Kleo::Commands;
56
57
using namespace GpgME;

Laurent Montel's avatar
Laurent Montel committed
58
59
class CertificateSelectionDialog::Private
{
60
    friend class ::Kleo::Dialogs::CertificateSelectionDialog;
Laurent Montel's avatar
Laurent Montel committed
61
    CertificateSelectionDialog *const q;
62
public:
Laurent Montel's avatar
Laurent Montel committed
63
    explicit Private(CertificateSelectionDialog *qq);
64
65

private:
Laurent Montel's avatar
Laurent Montel committed
66
67
    void reload()
    {
Laurent Montel's avatar
Laurent Montel committed
68
        Command *const cmd = new ReloadKeysCommand(nullptr);
Laurent Montel's avatar
Laurent Montel committed
69
        cmd->setParentWidget(q);
70
        cmd->start();
71
    }
Laurent Montel's avatar
Laurent Montel committed
72
73
    void create()
    {
Laurent Montel's avatar
Laurent Montel committed
74
        NewCertificateCommand *cmd = new NewCertificateCommand(nullptr);
Laurent Montel's avatar
Laurent Montel committed
75
76
77
78
        cmd->setParentWidget(q);
        if ((options & AnyFormat) != AnyFormat) {
            cmd->setProtocol((options & OpenPGPFormat) ? OpenPGP : CMS);
        }
79
80
        cmd->start();
    }
Laurent Montel's avatar
Laurent Montel committed
81
82
    void lookup()
    {
Laurent Montel's avatar
Laurent Montel committed
83
        Command *const cmd = new LookupCertificatesCommand(nullptr);
Laurent Montel's avatar
Laurent Montel committed
84
        cmd->setParentWidget(q);
85
        cmd->start();
86
    }
87
88
89
90
91
92
93
94
95
96
97
    void manageGroups()
    {
        KConfigDialog *dialog = KConfigDialog::exists(GroupsConfigDialog::dialogName());
        if (dialog) {
            // reparent the dialog to ensure it's shown on top of the modal CertificateSelectionDialog
            dialog->setParent(q, Qt::Dialog);
        } else {
            dialog = new GroupsConfigDialog(q);
        }
        dialog->show();
    }
98
    void slotKeysMayHaveChanged();
Laurent Montel's avatar
Laurent Montel committed
99
    void slotCurrentViewChanged(QAbstractItemView *newView);
100
    void slotSelectionChanged();
Laurent Montel's avatar
Laurent Montel committed
101
    void slotDoubleClicked(const QModelIndex &idx);
102
103

private:
104
    bool acceptable(const std::vector<Key> &keys, const std::vector<KeyGroup> &groups)
Laurent Montel's avatar
Laurent Montel committed
105
    {
106
        return !keys.empty() || !groups.empty();
107
    }
Laurent Montel's avatar
Laurent Montel committed
108
109
110
111
112
113
    void updateLabelText()
    {
        ui.label.setText(!customLabelText.isEmpty() ? customLabelText :
                         (options & MultiSelection)
                         ? i18n("Please select one or more of the following certificates:")
                         : i18n("Please select one of the following certificates:"));
114
115
116
    }

private:
117
    QSet<QAbstractItemView *> connectedViews;
118
119
120
121
122
123
124
125
126
    QString customLabelText;
    Options options;

    struct UI {
        QLabel label;
        SearchBar searchBar;
        TabWidget tabWidget;
        QDialogButtonBox buttonBox;
    } ui;
127
128
129
130
131
132
133
134
135
136
137
138
139
140

    void setUpUI(CertificateSelectionDialog *q)
    {
        KDAB_SET_OBJECT_NAME(ui.label);
        KDAB_SET_OBJECT_NAME(ui.searchBar);
        KDAB_SET_OBJECT_NAME(ui.tabWidget);
        KDAB_SET_OBJECT_NAME(ui.buttonBox);

        auto *vlay = new QVBoxLayout(q);
        vlay->addWidget(&ui.label);
        vlay->addWidget(&ui.searchBar);
        vlay->addWidget(&ui.tabWidget, 1);
        vlay->addWidget(&ui.buttonBox);

141
142
143
144
145
146
147
148
149
150
151
152
153
154
        QPushButton *const okButton = ui.buttonBox.addButton(QDialogButtonBox::Ok);
        okButton->setEnabled(false);
        ui.buttonBox.addButton(QDialogButtonBox::Close);
        QPushButton *const reloadButton = ui.buttonBox.addButton(i18n("Reload"), QDialogButtonBox::ActionRole);
        QPushButton *const importButton = ui.buttonBox.addButton(i18n("Import..."), QDialogButtonBox::ActionRole);
        QPushButton *const lookupButton = ui.buttonBox.addButton(i18n("Lookup..."), QDialogButtonBox::ActionRole);
        QPushButton *const createButton = ui.buttonBox.addButton(i18n("New..."), QDialogButtonBox::ActionRole);
        QPushButton *const groupsButton = ui.buttonBox.addButton(i18n("Groups..."), QDialogButtonBox::ActionRole);

        importButton->setToolTip(i18nc("@info:tooltip", "Import certificate from file"));
        lookupButton->setToolTip(i18nc("@info:tooltip", "Lookup certificates on server"));
        reloadButton->setToolTip(i18nc("@info:tooltip", "Refresh certificate list"));
        createButton->setToolTip(i18nc("@info:tooltip", "Create a new certificate"));
        groupsButton->setToolTip(i18nc("@info:tooltip", "Manage certificate groups"));
155
156
157

        connect(&ui.buttonBox, &QDialogButtonBox::accepted, q, &CertificateSelectionDialog::accept);
        connect(&ui.buttonBox, &QDialogButtonBox::rejected, q, &CertificateSelectionDialog::reject);
158
159
160
161
162
163
164
165
166
        connect(reloadButton, &QPushButton::clicked, q, [this] () { reload(); });
        connect(lookupButton, &QPushButton::clicked, q, [this] () { lookup(); });
        connect(createButton, &QPushButton::clicked, q, [this] () { create(); });
        connect(groupsButton, &QPushButton::clicked, q, [this] () { manageGroups(); });
        connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged,
                q, [this] () { slotKeysMayHaveChanged(); });

        connect(importButton, &QPushButton::clicked, q, [importButton, q] () {
            importButton->setEnabled(false);
167
168
            auto cmd = new Kleo::ImportCertificateFromFileCommand();
            connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished,
169
170
                    q, [importButton]() {
                importButton->setEnabled(true);
171
172
173
174
175
            });
            cmd->setParentWidget(q);
            cmd->start();
        });
    }
176
177
};

Laurent Montel's avatar
Laurent Montel committed
178
CertificateSelectionDialog::Private::Private(CertificateSelectionDialog *qq)
179
    : q(qq)
180
{
181
    setUpUI(q);
Laurent Montel's avatar
Laurent Montel committed
182
183
    ui.tabWidget.setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q));
    ui.tabWidget.setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(q));
184
185
186
187
188
#ifdef GPGME_HAS_REMARKS
    const auto tagKeys = Tags::tagKeys();
    ui.tabWidget.flatModel()->setRemarkKeys(tagKeys);
    ui.tabWidget.hierarchicalModel()->setRemarkKeys(tagKeys);
#endif
Laurent Montel's avatar
Laurent Montel committed
189
    ui.tabWidget.connectSearchBar(&ui.searchBar);
190

191
192
    connect(&ui.tabWidget, &TabWidget::currentViewChanged,
            q, [this] (QAbstractItemView *view) { slotCurrentViewChanged(view); });
193

194
    updateLabelText();
195
    q->setWindowTitle(i18nc("@title:window", "Certificate Selection"));
196
197
}

Laurent Montel's avatar
Laurent Montel committed
198
199
CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent)
    : QDialog(parent), d(new Private(this))
200
{
Laurent Montel's avatar
Laurent Montel committed
201
    const KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc"));
Laurent Montel's avatar
Laurent Montel committed
202
203
204
    d->ui.tabWidget.loadViews(config.data());
    const KConfigGroup geometry(config, "Geometry");
    resize(geometry.readEntry("size", size()));
205
    d->slotKeysMayHaveChanged();
206
207
208
209
}

CertificateSelectionDialog::~CertificateSelectionDialog() {}

Laurent Montel's avatar
Laurent Montel committed
210
211
212
void CertificateSelectionDialog::setCustomLabelText(const QString &txt)
{
    if (txt == d->customLabelText) {
213
        return;
Laurent Montel's avatar
Laurent Montel committed
214
    }
215
216
217
218
    d->customLabelText = txt;
    d->updateLabelText();
}

Laurent Montel's avatar
Laurent Montel committed
219
220
QString CertificateSelectionDialog::customLabelText() const
{
221
222
223
    return d->customLabelText;
}

Laurent Montel's avatar
Laurent Montel committed
224
225
226
void CertificateSelectionDialog::setOptions(Options options)
{
    if (d->options == options) {
227
        return;
Laurent Montel's avatar
Laurent Montel committed
228
    }
229
230
    d->options = options;

Laurent Montel's avatar
Laurent Montel committed
231
    d->ui.tabWidget.setMultiSelection(options & MultiSelection);
232

233
    d->slotKeysMayHaveChanged();
234
235
}

Laurent Montel's avatar
Laurent Montel committed
236
237
CertificateSelectionDialog::Options CertificateSelectionDialog::options() const
{
238
239
240
    return d->options;
}

Laurent Montel's avatar
Laurent Montel committed
241
242
243
void CertificateSelectionDialog::setStringFilter(const QString &filter)
{
    d->ui.tabWidget.setStringFilter(filter);
244
245
}

246
void CertificateSelectionDialog::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
Laurent Montel's avatar
Laurent Montel committed
247
248
{
    d->ui.tabWidget.setKeyFilter(filter);
249
250
}

251
252
253
254
namespace
{

void selectRows(const QAbstractItemView *view, const QModelIndexList &indexes)
Laurent Montel's avatar
Laurent Montel committed
255
256
{
    if (!view) {
257
        return;
Laurent Montel's avatar
Laurent Montel committed
258
259
    }
    QItemSelectionModel *const sm = view->selectionModel();
260
    Q_ASSERT(sm);
Laurent Montel's avatar
Laurent Montel committed
261

262
    for (const QModelIndex &idx : qAsConst(indexes)) {
Laurent Montel's avatar
Laurent Montel committed
263
264
265
        if (idx.isValid()) {
            sm->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
        }
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
    }
}

QModelIndexList getGroupIndexes(const KeyListModelInterface *model, const std::vector<KeyGroup> &groups)
{
    QModelIndexList indexes;
    indexes.reserve(groups.size());
    std::transform(groups.begin(), groups.end(),
                   std::back_inserter(indexes),
                   [model] (const KeyGroup &group) {
                       return model->index(group);
                   });
    indexes.erase(std::remove_if(indexes.begin(), indexes.end(),
                                 [] (const QModelIndex &index) { return !index.isValid(); }),
                  indexes.end());
    return indexes;
}

}

void CertificateSelectionDialog::selectCertificates(const std::vector<Key> &keys)
{
    const auto *const model = d->ui.tabWidget.currentModel();
    Q_ASSERT(model);
    selectRows(d->ui.tabWidget.currentView(), model->indexes(keys));
291
292
}

Laurent Montel's avatar
Laurent Montel committed
293
294
295
void CertificateSelectionDialog::selectCertificate(const Key &key)
{
    selectCertificates(std::vector<Key>(1, key));
296
297
}

298
void CertificateSelectionDialog::selectGroups(const std::vector<KeyGroup> &groups)
Laurent Montel's avatar
Laurent Montel committed
299
{
300
    const auto *const model = d->ui.tabWidget.currentModel();
301
    Q_ASSERT(model);
302
303
304
305
306
307
308
309
310
311
312
    selectRows(d->ui.tabWidget.currentView(), getGroupIndexes(model, groups));
}

namespace
{

QModelIndexList getSelectedRows(const QAbstractItemView *view)
{
    if (!view) {
        return {};
    }
Laurent Montel's avatar
Laurent Montel committed
313
    const QItemSelectionModel *const sm = view->selectionModel();
314
    Q_ASSERT(sm);
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    return sm->selectedRows();
}

std::vector<KeyGroup> getGroups(const KeyListModelInterface *model, const QModelIndexList &indexes)
{
    std::vector<KeyGroup> groups;
    groups.reserve(indexes.size());
    std::transform(indexes.begin(), indexes.end(),
                   std::back_inserter(groups),
                   [model](const QModelIndex &idx) {
                       return model->group(idx);
                   });
    groups.erase(std::remove_if(groups.begin(), groups.end(), std::mem_fn(&Kleo::KeyGroup::isNull)), groups.end());
    return groups;
}

}

std::vector<Key> CertificateSelectionDialog::selectedCertificates() const
{
    const KeyListModelInterface *const model = d->ui.tabWidget.currentModel();
    Q_ASSERT(model);
    return model->keys(getSelectedRows(d->ui.tabWidget.currentView()));
338
339
}

Laurent Montel's avatar
Laurent Montel committed
340
341
Key CertificateSelectionDialog::selectedCertificate() const
{
342
    const std::vector<Key> keys = selectedCertificates();
Laurent Montel's avatar
Laurent Montel committed
343
    return keys.empty() ? Key() : keys.front();
344
345
}

346
347
348
349
350
351
352
std::vector<KeyGroup> CertificateSelectionDialog::selectedGroups() const
{
    const KeyListModelInterface *const model = d->ui.tabWidget.currentModel();
    Q_ASSERT(model);
    return getGroups(model, getSelectedRows(d->ui.tabWidget.currentView()));
}

Laurent Montel's avatar
Laurent Montel committed
353
354
void CertificateSelectionDialog::hideEvent(QHideEvent *e)
{
Laurent Montel's avatar
Laurent Montel committed
355
    KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc"));
Laurent Montel's avatar
Laurent Montel committed
356
357
358
359
    d->ui.tabWidget.saveViews(config.data());
    KConfigGroup geometry(config, "Geometry");
    geometry.writeEntry("size", size());
    QDialog::hideEvent(e);
360
361
}

Laurent Montel's avatar
Laurent Montel committed
362
363
364
void CertificateSelectionDialog::Private::slotKeysMayHaveChanged()
{
    q->setEnabled(true);
Laurent Montel's avatar
Laurent Montel committed
365
    std::vector<Key> keys = (options & SecretKeys) ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys();
366
    q->filterAllowedKeys(keys, options);
367
368
369
370
    const std::vector<KeyGroup> groups = (options & IncludeGroups) ? KeyCache::instance()->groups() : std::vector<KeyGroup>();

    const std::vector<Key> selectedKeys = q->selectedCertificates();
    const std::vector<KeyGroup> selectedGroups = q->selectedGroups();
Laurent Montel's avatar
Laurent Montel committed
371
372
    if (AbstractKeyListModel *const model = ui.tabWidget.flatModel()) {
        model->setKeys(keys);
373
        model->setGroups(groups);
Laurent Montel's avatar
Laurent Montel committed
374
375
376
    }
    if (AbstractKeyListModel *const model = ui.tabWidget.hierarchicalModel()) {
        model->setKeys(keys);
377
        model->setGroups(groups);
Laurent Montel's avatar
Laurent Montel committed
378
    }
379
380
    q->selectCertificates(selectedKeys);
    q->selectGroups(selectedGroups);
381
382
}

383
void CertificateSelectionDialog::filterAllowedKeys(std::vector<Key> &keys, int options)
Laurent Montel's avatar
Laurent Montel committed
384
{
385
386
    std::vector<Key>::iterator end = keys.end();

Laurent Montel's avatar
Laurent Montel committed
387
    switch (options & AnyFormat) {
388
    case OpenPGPFormat:
389
        end = std::remove_if(keys.begin(), end, [](const Key &key) { return key.protocol() != OpenPGP; });
390
391
        break;
    case CMSFormat:
392
        end = std::remove_if(keys.begin(), end, [](const Key &key) { return key.protocol() != CMS; });
393
394
395
396
397
398
        break;
    default:
    case AnyFormat:
        ;
    }

Laurent Montel's avatar
Laurent Montel committed
399
    switch (options & AnyCertificate) {
400
    case SignOnly:
401
        end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.canReallySign(); });
402
403
        break;
    case EncryptOnly:
404
        end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.canEncrypt(); });
405
406
407
408
409
410
        break;
    default:
    case AnyCertificate:
        ;
    }

Laurent Montel's avatar
Laurent Montel committed
411
    if (options & SecretKeys) {
412
        end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.hasSecret(); });
Laurent Montel's avatar
Laurent Montel committed
413
    }
414

Laurent Montel's avatar
Laurent Montel committed
415
    keys.erase(end, keys.end());
416
417
}

Laurent Montel's avatar
Laurent Montel committed
418
419
void CertificateSelectionDialog::Private::slotCurrentViewChanged(QAbstractItemView *newView)
{
420
421
422
423
    if (!connectedViews.contains(newView)) {
        connectedViews.insert(newView);
        connect(newView, &QAbstractItemView::doubleClicked,
                q, [this] (const QModelIndex &index) { slotDoubleClicked(index); });
424
        Q_ASSERT(newView->selectionModel());
425
426
        connect(newView->selectionModel(), &QItemSelectionModel::selectionChanged,
                q, [this] (const QItemSelection &, const QItemSelection &) { slotSelectionChanged(); });
427
428
429
430
    }
    slotSelectionChanged();
}

Laurent Montel's avatar
Laurent Montel committed
431
432
433
void CertificateSelectionDialog::Private::slotSelectionChanged()
{
    if (QPushButton *const pb = ui.buttonBox.button(QDialogButtonBox::Ok)) {
434
        pb->setEnabled(acceptable(q->selectedCertificates(), q->selectedGroups()));
Laurent Montel's avatar
Laurent Montel committed
435
    }
436
437
}

Laurent Montel's avatar
Laurent Montel committed
438
439
440
void CertificateSelectionDialog::Private::slotDoubleClicked(const QModelIndex &idx)
{
    QAbstractItemView *const view = ui.tabWidget.currentView();
441
    Q_ASSERT(view);
442
    const auto *const model = ui.tabWidget.currentModel();
443
    Q_ASSERT(model);
Laurent Montel's avatar
Laurent Montel committed
444
    Q_UNUSED(model)
Laurent Montel's avatar
Laurent Montel committed
445
    QItemSelectionModel *const sm = view->selectionModel();
446
    Q_ASSERT(sm);
Laurent Montel's avatar
Laurent Montel committed
447
    sm->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
448
    QMetaObject::invokeMethod(q, [this]() {q->accept();}, Qt::QueuedConnection);
449
450
}

Laurent Montel's avatar
Laurent Montel committed
451
452
void CertificateSelectionDialog::accept()
{
453
    if (d->acceptable(selectedCertificates(), selectedGroups())) {
454
        QDialog::accept();
Laurent Montel's avatar
Laurent Montel committed
455
    }
456
457
}

458
#include "moc_certificateselectiondialog.cpp"