File indexing completed on 2024-06-16 04:56:17

0001 /*  view/smartcardwidget.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
0005     SPDX-FileContributor: Intevation GmbH
0006     SPDX-FileCopyrightText: 2020 g10 Code GmbH
0007     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "smartcardwidget.h"
0013 
0014 #include "smartcard/netkeycard.h"
0015 #include "smartcard/openpgpcard.h"
0016 #include "smartcard/p15card.h"
0017 #include "smartcard/pivcard.h"
0018 #include "smartcard/readerstatus.h"
0019 #include "smartcard/utils.h"
0020 
0021 #include "view/netkeywidget.h"
0022 #include "view/p15cardwidget.h"
0023 #include "view/pgpcardwidget.h"
0024 #include "view/pivcardwidget.h"
0025 
0026 #include "kleopatra_debug.h"
0027 
0028 #include <KLocalizedString>
0029 
0030 #include <QHBoxLayout>
0031 #include <QLabel>
0032 #include <QPointer>
0033 #include <QPushButton>
0034 #include <QStackedWidget>
0035 #include <QTabWidget>
0036 #include <QVBoxLayout>
0037 
0038 using namespace Kleo;
0039 using namespace Kleo::SmartCard;
0040 
0041 namespace
0042 {
0043 class PlaceHolderWidget : public QWidget
0044 {
0045     Q_OBJECT
0046 public:
0047     explicit PlaceHolderWidget(QWidget *parent = nullptr)
0048         : QWidget{parent}
0049     {
0050         auto lay = new QVBoxLayout;
0051         lay->addStretch(-1);
0052 
0053         const QStringList supported = QStringList() << i18nc("OpenPGP refers to a smartcard protocol", "OpenPGP v2.0 - v3.3")
0054                                                     << i18nc("Gnuk is a cryptographic token for GnuPG", "Gnuk")
0055                                                     << i18nc("NetKey refers to a smartcard protocol", "NetKey v3")
0056                                                     << i18nc("PIV refers to a smartcard protocol", "PIV (requires GnuPG 2.3 or later)")
0057                                                     << i18nc("CardOS is a smartcard operating system", "CardOS 5 (various apps)");
0058         lay->addWidget(new QLabel(QStringLiteral("\t\t<h3>") + i18n("Please insert a compatible smartcard.") + QStringLiteral("</h3>"), this));
0059         lay->addSpacing(10);
0060         lay->addWidget(new QLabel(QStringLiteral("\t\t") + i18n("Kleopatra currently supports the following card types:") + QStringLiteral("<ul><li>")
0061                                       + supported.join(QLatin1StringView("</li><li>")) + QStringLiteral("</li></ul>"),
0062                                   this));
0063         lay->addSpacing(10);
0064         {
0065             auto hbox = new QHBoxLayout;
0066             hbox->addStretch(1);
0067             mReloadButton = new QPushButton{i18n("Reload"), this};
0068             hbox->addWidget(mReloadButton);
0069             hbox->addStretch(1);
0070             lay->addLayout(hbox);
0071         }
0072         lay->addStretch(-1);
0073 
0074         auto hLay = new QHBoxLayout(this);
0075         hLay->addStretch(-1);
0076         hLay->addLayout(lay);
0077         hLay->addStretch(-1);
0078         lay->addStretch(-1);
0079 
0080         connect(mReloadButton, &QPushButton::clicked, this, &PlaceHolderWidget::reload);
0081     }
0082 
0083 Q_SIGNALS:
0084     void reload();
0085 
0086 private:
0087     QPushButton *mReloadButton = nullptr;
0088 };
0089 } // namespace
0090 
0091 class SmartCardWidget::Private
0092 {
0093     friend class ::Kleo::SmartCardWidget;
0094 
0095 public:
0096     Private(SmartCardWidget *qq);
0097 
0098     void cardAddedOrChanged(const std::string &serialNumber, const std::string &appName);
0099     void cardRemoved(const std::string &serialNumber, const std::string &appName);
0100 
0101 private:
0102     template<typename C, typename W>
0103     void cardAddedOrChanged(const std::string &serialNumber);
0104 
0105 private:
0106     SmartCardWidget *const q;
0107     QMap<std::pair<std::string, std::string>, QPointer<QWidget>> mCardWidgets;
0108     PlaceHolderWidget *mPlaceHolderWidget;
0109     QStackedWidget *mStack;
0110     QTabWidget *mTabWidget;
0111 };
0112 
0113 SmartCardWidget::Private::Private(SmartCardWidget *qq)
0114     : q{qq}
0115 {
0116     auto vLay = new QVBoxLayout(q);
0117 
0118     vLay->addWidget(new QLabel(QStringLiteral("<h2>") + i18n("Smartcard Management") + QStringLiteral("</h2>")));
0119 
0120     mStack = new QStackedWidget;
0121     vLay->addWidget(mStack);
0122 
0123     mPlaceHolderWidget = new PlaceHolderWidget;
0124     mStack->addWidget(mPlaceHolderWidget);
0125 
0126     mTabWidget = new QTabWidget;
0127     mStack->addWidget(mTabWidget);
0128 
0129     mStack->setCurrentWidget(mPlaceHolderWidget);
0130 
0131     connect(mPlaceHolderWidget, &PlaceHolderWidget::reload, q, &SmartCardWidget::reload);
0132     connect(ReaderStatus::instance(), &ReaderStatus::cardAdded, q, [this](const std::string &serialNumber, const std::string &appName) {
0133         cardAddedOrChanged(serialNumber, appName);
0134     });
0135     connect(ReaderStatus::instance(), &ReaderStatus::cardChanged, q, [this](const std::string &serialNumber, const std::string &appName) {
0136         cardAddedOrChanged(serialNumber, appName);
0137     });
0138     connect(ReaderStatus::instance(), &ReaderStatus::cardRemoved, q, [this](const std::string &serialNumber, const std::string &appName) {
0139         cardRemoved(serialNumber, appName);
0140     });
0141 }
0142 
0143 void SmartCardWidget::Private::cardAddedOrChanged(const std::string &serialNumber, const std::string &appName)
0144 {
0145     if (appName == SmartCard::NetKeyCard::AppName) {
0146         cardAddedOrChanged<NetKeyCard, NetKeyWidget>(serialNumber);
0147     } else if (appName == SmartCard::OpenPGPCard::AppName) {
0148         cardAddedOrChanged<OpenPGPCard, PGPCardWidget>(serialNumber);
0149     } else if (appName == SmartCard::PIVCard::AppName) {
0150         cardAddedOrChanged<PIVCard, PIVCardWidget>(serialNumber);
0151     } else if (appName == SmartCard::P15Card::AppName) {
0152         cardAddedOrChanged<P15Card, P15CardWidget>(serialNumber);
0153     } else {
0154         qCWarning(KLEOPATRA_LOG) << "SmartCardWidget::Private::cardAddedOrChanged:"
0155                                  << "App" << appName.c_str() << "is not supported";
0156     }
0157 }
0158 
0159 namespace
0160 {
0161 static QString getCardLabel(const std::shared_ptr<Card> &card)
0162 {
0163     if (!card->cardHolder().isEmpty()) {
0164         return i18nc("@title:tab smartcard application - name of card holder - serial number of smartcard",
0165                      "%1 - %2 - %3",
0166                      displayAppName(card->appName()),
0167                      card->cardHolder(),
0168                      card->displaySerialNumber());
0169     } else {
0170         return i18nc("@title:tab smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber());
0171     }
0172 }
0173 }
0174 
0175 template<typename C, typename W>
0176 void SmartCardWidget::Private::cardAddedOrChanged(const std::string &serialNumber)
0177 {
0178     const auto card = ReaderStatus::instance()->getCard<C>(serialNumber);
0179     if (!card) {
0180         qCWarning(KLEOPATRA_LOG) << "SmartCardWidget::Private::cardAddedOrChanged:"
0181                                  << "New or changed card" << serialNumber.c_str() << "with app" << C::AppName.c_str() << "not found";
0182         return;
0183     }
0184     W *cardWidget = dynamic_cast<W *>(mCardWidgets.value({serialNumber, C::AppName}).data());
0185     if (!cardWidget) {
0186         cardWidget = new W;
0187         mCardWidgets.insert({serialNumber, C::AppName}, cardWidget);
0188         mTabWidget->addTab(cardWidget, getCardLabel(card));
0189         if (mCardWidgets.size() == 1) {
0190             mStack->setCurrentWidget(mTabWidget);
0191         }
0192     }
0193     cardWidget->setCard(card.get());
0194 }
0195 
0196 void SmartCardWidget::Private::cardRemoved(const std::string &serialNumber, const std::string &appName)
0197 {
0198     QWidget *cardWidget = mCardWidgets.take({serialNumber, appName});
0199     if (cardWidget) {
0200         const int index = mTabWidget->indexOf(cardWidget);
0201         if (index != -1) {
0202             mTabWidget->removeTab(index);
0203         }
0204         delete cardWidget;
0205     }
0206     if (mCardWidgets.empty()) {
0207         mStack->setCurrentWidget(mPlaceHolderWidget);
0208     }
0209 }
0210 
0211 SmartCardWidget::SmartCardWidget(QWidget *parent)
0212     : QWidget{parent}
0213     , d{std::make_unique<Private>(this)}
0214 {
0215 }
0216 
0217 Kleo::SmartCardWidget::~SmartCardWidget() = default;
0218 
0219 QWidget *getFirstEnabledFocusWidget(QWidget *parent)
0220 {
0221     for (auto w = parent->nextInFocusChain(); w != parent; w = w->nextInFocusChain()) {
0222         if (w->isEnabled() && (w->focusPolicy() != Qt::NoFocus)) {
0223             return w;
0224         }
0225     }
0226     return nullptr;
0227 }
0228 
0229 void SmartCardWidget::focusFirstChild(Qt::FocusReason reason)
0230 {
0231     if (d->mStack->currentWidget() == d->mPlaceHolderWidget) {
0232         if (auto w = getFirstEnabledFocusWidget(d->mPlaceHolderWidget)) {
0233             w->setFocus(reason);
0234         }
0235     } else if (auto cardWidget = d->mTabWidget->currentWidget()) {
0236         if (auto w = getFirstEnabledFocusWidget(cardWidget)) {
0237             w->setFocus(reason);
0238         }
0239     }
0240 }
0241 
0242 void SmartCardWidget::reload()
0243 {
0244     ReaderStatus::mutableInstance()->updateStatus();
0245 }
0246 
0247 #include "smartcardwidget.moc"
0248 
0249 #include "moc_smartcardwidget.cpp"