diff --git a/client/QCNG.cpp b/client/QCNG.cpp index 7da8c4b1a..eedacd510 100644 --- a/client/QCNG.cpp +++ b/client/QCNG.cpp @@ -52,7 +52,7 @@ QCNG::QCNG() noexcept = default; QCNG::~QCNG() noexcept = default; -QCNG::Status QCNG::login(const TokenData &token) +QCNG::Status QCNG::login(const TokenData &token, const QString & /*pin*/) { std::unique_ptr p = std::make_unique(); if(FAILED(NCryptOpenStorageProvider(&p->prov, LPCWSTR(token.data(u"provider"_s).toString().utf16()), 0))) diff --git a/client/QCNG.h b/client/QCNG.h index 7b060e9e7..e6c66bb47 100644 --- a/client/QCNG.h +++ b/client/QCNG.h @@ -31,7 +31,7 @@ class QCNG final: public QCryptoBackend explicit QCNG() noexcept; ~QCNG() noexcept final; - Status login(const TokenData &token) final; + Status login(const TokenData &token, const QString &pin) final; QByteArray decrypt(const QByteArray &data, bool oaep) const final; QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, diff --git a/client/QCryptoBackend.cpp b/client/QCryptoBackend.cpp index 56f5a12c2..dd15c5c93 100644 --- a/client/QCryptoBackend.cpp +++ b/client/QCryptoBackend.cpp @@ -66,7 +66,7 @@ QCryptoBackend::~QCryptoBackend() } std::expected -QCryptoBackend::getBackend(const TokenData& token) { +QCryptoBackend::getBackend(const TokenData& token, const QString &pin) { if(!qApp->cryptoManager()->d->operationLock.tryLockForWrite(10 * 1000)) return std::unexpected(InProgress); #ifdef Q_OS_WIN @@ -79,9 +79,14 @@ QCryptoBackend::getBackend(const TokenData& token) { return backend.release(); Status status; - do { - status = backend->login(token); - } while (status == PinIncorrect); + if(pin.isEmpty()) + { + do { + status = backend->login(token, {}); + } while (status == PinIncorrect); + } + else + status = backend->login(token, pin); if (status != PinOK) return std::unexpected(status); diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index 820fceb76..023de026e 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -67,7 +67,7 @@ class QCryptoBackend * @param token the token to use * @return the new backend object or an error code */ - static std::expected getBackend(const TokenData &token); + static std::expected getBackend(const TokenData &token, const QString &pin = {}); /** * @brief The status of the last operation @@ -77,7 +77,7 @@ class QCryptoBackend static QString errorString(Status error); protected: - virtual Status login(const TokenData &cert) = 0; + virtual Status login(const TokenData &cert, const QString &pin) = 0; private: TokenData token; diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 28479d0a7..8f8248515 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -361,7 +361,7 @@ QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteAr return out; } -QPKCS11::Status QPKCS11::login(const TokenData &t) +QPKCS11::Status QPKCS11::login(const TokenData &t, const QString &pin) { if(!d->l) { d->l = QPKCS11Library::current(); @@ -401,12 +401,17 @@ QPKCS11::Status QPKCS11::login(const TokenData &t) p.startTimer(); return waitFor(d->l->f->C_Login, d->session, CKU_USER, nullptr, 0); } else { - PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); - p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); - if(!p.exec()) - return CKR_FUNCTION_CANCELED; - QByteArray pin = p.pin().toUtf8(); - return d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); + QByteArray pinData; + if(pin.isEmpty()) { + PinPopup p(isSign ? QSmartCardData::Pin2Type : QSmartCardData::Pin1Type, f, cert, Application::mainWindow()); + p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); + if(!p.exec()) + return CKR_FUNCTION_CANCELED; + pinData = p.pin().toUtf8(); + } else { + pinData = pin.toUtf8(); + } + return d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pinData.constData()), CK_ULONG(pinData.size())); } }); @@ -471,6 +476,9 @@ QList QPKCS11::tokens() t.setData(QStringLiteral("slot"), QVariant::fromValue(slot)); t.setData(QStringLiteral("id"), id); t.setData(QStringLiteral("blocked"), (token.flags & (CKF_USER_PIN_LOCKED|CKF_USER_PIN_TO_BE_CHANGED)) > 0); + t.setData(QStringLiteral("pinpad"), (token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) > 0); + t.setData(QStringLiteral("pinMin"), int(token.ulMinPinLen)); + t.setData(QStringLiteral("pinMax"), int(token.ulMaxPinLen)); CK_KEY_TYPE keyType = CKK_RSA; CK_ATTRIBUTE attribute { CKA_KEY_TYPE, &keyType, sizeof(keyType) }; diff --git a/client/QPKCS11.h b/client/QPKCS11.h index c01994018..1c9e11f33 100644 --- a/client/QPKCS11.h +++ b/client/QPKCS11.h @@ -36,7 +36,7 @@ class QPKCS11 final: public QCryptoBackend QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; QByteArray sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const final; - Status login(const TokenData &t) final; + Status login(const TokenData &t, const QString &pin) final; static QList tokens(); diff --git a/client/dialogs/SigningDialog.cpp b/client/dialogs/SigningDialog.cpp index c4778bfc0..a5f8f1f7f 100644 --- a/client/dialogs/SigningDialog.cpp +++ b/client/dialogs/SigningDialog.cpp @@ -22,12 +22,18 @@ #include +static void setError(LineEdit *input, QLabel *error, const QString &msg) +{ + input->setLabel(msg.isEmpty() ? QString() : QStringLiteral("error")); + error->setText(msg); + error->setHidden(msg.isEmpty()); +} + SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) : QDialog(parent) , digiDoc(doc) , ui(new Ui::SigningDialog) { - static const QStringList countryCodes {QStringLiteral("372"), QStringLiteral("370")}; new Overlay(this); ui->setupUi(this); ui->progressPage->hide(); @@ -46,6 +52,11 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) updateCards(); connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &SigningDialog::updateCards); connect(qApp->cryptoManager()->smartcard(), &QSmartCard::dataChanged, this, &SigningDialog::updateCards); + connect(ui->pin, &QLineEdit::textEdited, this, [this] { + setError(ui->pin, ui->errorPin, {}); + ui->sign->setEnabled(ui->pin->hasAcceptableInput()); + }); + connect(ui->pin, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); connect(ui->tabs, &QTabBar::currentChanged, this, [this](int index) { if(index == IDCard) updateCards(); @@ -53,12 +64,6 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) ui->sign->setEnabled(true); }); - auto setError = [](LineEdit *input, QLabel *error, const QString &msg) { - input->setLabel(msg.isEmpty() ? QString() : QStringLiteral("error")); - error->setText(msg); - error->setHidden(msg.isEmpty()); - }; - // Mobile-ID tab ui->idCodeMID->setValidator(new NumberValidator(ui->idCodeMID)); ui->idCodeMID->setText(Settings::MOBILEID_CODE); @@ -78,12 +83,12 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) }; connect(ui->idCodeMID, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); connect(ui->idCodeMID, &QLineEdit::textEdited, this, saveMIDSettings); - connect(ui->idCodeMID, &QLineEdit::textEdited, this, [this, setError] { + connect(ui->idCodeMID, &QLineEdit::textEdited, this, [this] { setError(ui->idCodeMID, ui->errorCodeMID, {}); }); connect(ui->phoneNo, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); connect(ui->phoneNo, &QLineEdit::textEdited, this, saveMIDSettings); - connect(ui->phoneNo, &QLineEdit::textEdited, this, [this, setError] { + connect(ui->phoneNo, &QLineEdit::textEdited, this, [this] { setError(ui->phoneNo, ui->errorPhone, {}); }); connect(ui->cbRememberMID, &QCheckBox::clicked, this, saveMIDSettings); @@ -108,7 +113,7 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) }; connect(ui->idCodeSID, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); connect(ui->idCodeSID, &QLineEdit::textEdited, this, saveSIDSettings); - connect(ui->idCodeSID, &QLineEdit::textEdited, this, [this, setError] { + connect(ui->idCodeSID, &QLineEdit::textEdited, this, [this] { setError(ui->idCodeSID, ui->errorCodeSID, {}); }); connect(ui->idCountry, &QComboBox::currentTextChanged, this, [this, ik, saveSIDSettings] { @@ -118,43 +123,7 @@ SigningDialog::SigningDialog(DigiDoc *doc, QWidget *parent) }); connect(ui->cbRememberSID, &QCheckBox::clicked, this, saveSIDSettings); - // Sign button — validates per-tab then delegates to performSign() - connect(ui->sign, &QPushButton::clicked, this, [this, setError] { - switch(currentMethod()) { - case IDCard: - if(performSign()) accept(); - else reject(); - break; - case MobileID: { - const QString code = ui->idCodeMID->text(); - const QString phone = ui->phoneNo->text(); - if(!IKValidator::isValid(code)) - setError(ui->idCodeMID, ui->errorCodeMID, tr("Personal code is not valid")); - else - setError(ui->idCodeMID, ui->errorCodeMID, {}); - if(phone.size() < 8 || countryCodes.contains(phone)) - setError(ui->phoneNo, ui->errorPhone, tr("Phone number is not entered")); - else if(!countryCodes.contains(phone.left(3))) - setError(ui->phoneNo, ui->errorPhone, tr("Invalid country code")); - else - setError(ui->phoneNo, ui->errorPhone, {}); - if(ui->errorCodeMID->text().isEmpty() && ui->errorPhone->text().isEmpty()) { - if(performSign()) accept(); - else reject(); - } - break; - } - case SmartID: - if(ui->idCodeSID->validator() && !IKValidator::isValid(ui->idCodeSID->text())) - setError(ui->idCodeSID, ui->errorCodeSID, tr("Personal code is not valid")); - else { - setError(ui->idCodeSID, ui->errorCodeSID, {}); - if(performSign()) accept(); - else reject(); - } - break; - } - }); + connect(ui->sign, &QPushButton::clicked, this, &SigningDialog::performSign); connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject); connect(this, &QDialog::finished, this, &QDialog::close); } @@ -200,11 +169,19 @@ void SigningDialog::updateCards() { qDeleteAll(ui->cardListContainer->findChildren()); + auto select = [this](const TokenData &t) { + if(!(t == m_selectedToken)) { + ui->pin->clear(); + setError(ui->pin, ui->errorPin, {}); + } + m_selectedToken = t; + }; + QList tokens = qApp->cryptoManager()->cache(); bool localValid = std::any_of(tokens.cbegin(), tokens.cend(), [this](const TokenData &t) { return t == m_selectedToken; }); if(!localValid) - m_selectedToken = qApp->cryptoManager()->tokensign(); + select(qApp->cryptoManager()->tokensign()); const QSmartCardData data = qApp->cryptoManager()->smartcard()->data(); bool any = false; @@ -220,8 +197,8 @@ void SigningDialog::updateCards() bool usable = c.isValid() && !blocked; auto *w = new CardListItem(ui->cardListContainer); w->update(token, token.card() == m_selectedToken.card()); - connect(w, &CardListItem::selected, this, [this](const TokenData &t) { - m_selectedToken = t; + connect(w, &CardListItem::selected, this, [this, select](const TokenData &t) { + select(t); updateSelection(true); }); ui->cardListContainer->layout()->addWidget(w); @@ -245,20 +222,33 @@ void SigningDialog::updateCards() void SigningDialog::updateSelection(bool usable) { - ui->infoPin->setVisible(usable); - if(usable) - ui->infoPin->setText(tr("Please enter %1 code in the new window that will appear shortly").arg( - SslCertificate(m_selectedToken.cert()).type() & SslCertificate::TempelType ? - QStringLiteral("PIN") : QSmartCardData::typeString(QSmartCardData::Pin2Type))); + bool isSeal = SslCertificate(m_selectedToken.cert()).type() & SslCertificate::TempelType; + QString pinName = isSeal ? QStringLiteral("PIN") : QSmartCardData::typeString(QSmartCardData::Pin2Type); + bool showPin = false; +#ifndef Q_OS_WIN + if(usable && !m_selectedToken.data(QStringLiteral("pinpad")).toBool()) { + showPin = true; + int minLen = m_selectedToken.data(QStringLiteral("pinMin")).toInt(); + int maxLen = m_selectedToken.data(QStringLiteral("pinMax")).toInt(); + ui->pin->setPinLen(std::max(minLen, 5), std::max(maxLen, 12), !isSeal); + ui->labelPin->setText(tr("Enter %1 code").arg(pinName)); + } +#endif + ui->labelPin->setVisible(showPin); + ui->pin->setVisible(showPin); + ui->errorPin->setVisible(showPin && !ui->errorPin->text().isEmpty()); + if(!showPin) + ui->pin->clear(); + + ui->infoPin->setVisible(usable && !showPin); + if(usable && !showPin) + ui->infoPin->setText(tr("Please enter %1 code in the new window that will appear shortly").arg(pinName)); if(currentMethod() == IDCard) - ui->sign->setEnabled(usable); + ui->sign->setEnabled(usable && (!showPin || ui->pin->hasAcceptableInput())); } -bool SigningDialog::performSign() +void SigningDialog::performSign() { - if(!confirmAlreadySigned()) - return false; - auto showPage = [this](QWidget *page) { ui->formPage->setVisible(page == ui->formPage); @@ -267,23 +257,70 @@ bool SigningDialog::performSign() switch(currentMethod()) { case IDCard: { - QSigner signer(m_selectedToken); - return digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &signer); + if(!confirmAlreadySigned()) + return reject(); + QString pin = ui->pin->isVisible() ? ui->pin->text() : QString(); + auto val = QCryptoBackend::getBackend(m_selectedToken, pin); + if(!val) { + switch(val.error()) { + case QCryptoBackend::PinIncorrect: { + QString msg = QCryptoBackend::errorString(QCryptoBackend::PinIncorrect); + if(QSmartCardData data = qApp->cryptoManager()->smartcard()->data(); data.card() == m_selectedToken.card()) + msg += QStringLiteral(". ") + tr("Remaining attempts: %1").arg(data.retryCount(QSmartCardData::Pin2Type)); + ui->pin->clear(); + setError(ui->pin, ui->errorPin, msg); + ui->pin->setFocus(); + ui->sign->setEnabled(false); + return; + } + case QCryptoBackend::PinCanceled: + return reject(); + default: + WarningDialog::create(this) + ->withTitle(tr("Failed to sign document")) + ->withText(QCryptoBackend::errorString(val.error())) + ->exec(); + return reject(); + } + } + QSigner signer(std::unique_ptr(val.value()), m_selectedToken); + return digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &signer) ? accept() : reject(); } case MobileID: { + static const QStringList countryCodes {QStringLiteral("372"), QStringLiteral("370")}; + const QString code = ui->idCodeMID->text(); + const QString phone = ui->phoneNo->text(); + setError(ui->idCodeMID, ui->errorCodeMID, IKValidator::isValid(code) ? QString() : tr("Personal code is not valid")); + if(phone.size() < 8 || countryCodes.contains(phone)) + setError(ui->phoneNo, ui->errorPhone, tr("Phone number is not entered")); + else if(!countryCodes.contains(phone.left(3))) + setError(ui->phoneNo, ui->errorPhone, tr("Invalid country code")); + else + setError(ui->phoneNo, ui->errorPhone, {}); + if(!ui->errorCodeMID->text().isEmpty() || !ui->errorPhone->text().isEmpty()) + return; + if(!confirmAlreadySigned()) + return reject(); SigningProgressUI pui { ui->progressInfo, ui->progressCode, ui->progressLabel, ui->progressBar, ui->progressCancel, this }; showPage(ui->progressPage); MobileProgress m(pui); - return m.init(ui->idCodeMID->text(), ui->phoneNo->text()) && - digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &m); + return m.init(code, phone) && + digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &m) ? accept() : reject(); } case SmartID: { + const QString code = ui->idCodeSID->text(); + if(ui->idCodeSID->validator() && !IKValidator::isValid(code)) { + setError(ui->idCodeSID, ui->errorCodeSID, tr("Personal code is not valid")); + return; + } + setError(ui->idCodeSID, ui->errorCodeSID, {}); + if(!confirmAlreadySigned()) + return reject(); SigningProgressUI pui { ui->progressInfo, ui->progressCode, ui->progressLabel, ui->progressBar, ui->progressCancel, this }; showPage(ui->progressPage); SmartIDProgress s(pui); - return s.init(ui->idCountry->currentData().toString(), ui->idCodeSID->text(), digiDoc->fileName()) && - digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &s); + return s.init(ui->idCountry->currentData().toString(), code, digiDoc->fileName()) && + digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &s) ? accept() : reject(); } } - return false; } diff --git a/client/dialogs/SigningDialog.h b/client/dialogs/SigningDialog.h index 1073e0d7e..26d3f40ae 100644 --- a/client/dialogs/SigningDialog.h +++ b/client/dialogs/SigningDialog.h @@ -13,8 +13,6 @@ class SigningDialog final : public QDialog { Q_OBJECT public: - enum Method { IDCard, MobileID, SmartID }; - explicit SigningDialog(DigiDoc *doc, QWidget *parent = nullptr); ~SigningDialog() final; @@ -22,9 +20,11 @@ class SigningDialog final : public QDialog const QString &state, const QString &zip, const QString &role); private: + enum Method { IDCard, MobileID, SmartID }; + void updateCards(); void updateSelection(bool usable); - bool performSign(); + void performSign(); Method currentMethod() const; bool confirmAlreadySigned(); diff --git a/client/dialogs/SigningDialog.ui b/client/dialogs/SigningDialog.ui index ad91feff8..db9411b02 100644 --- a/client/dialogs/SigningDialog.ui +++ b/client/dialogs/SigningDialog.ui @@ -308,6 +308,45 @@ background-color: #EDF1F6; + + + + 6 + + + + + + 0 + 20 + + + + Enter PIN2 code + + + pin + + + + + + + + + + + 0 + 20 + + + + Qt::TabFocus + + + + + @@ -709,6 +748,11 @@ background-color: #EDF1F6; QLineEdit
widgets/LineEdit.h
+ + PinLineEdit + LineEdit +
widgets/PinLineEdit.h
+
NoCardInfo QWidget diff --git a/client/signer/QSigner.cpp b/client/signer/QSigner.cpp index 30151df65..f732ea504 100644 --- a/client/signer/QSigner.cpp +++ b/client/signer/QSigner.cpp @@ -19,9 +19,7 @@ #include "QSigner.h" -#include "Application.h" #include "QCryptoBackend.h" -#include "QSmartCard.h" #include "TokenData.h" #include "Utils.h" @@ -33,8 +31,9 @@ using namespace digidoc; -QSigner::QSigner(const TokenData &token) - : m_token(token) +QSigner::QSigner(std::unique_ptr backend, const TokenData &token) + : m_backend(std::move(backend)) + , m_token(token) { if(m_token.data(QStringLiteral("PSS")).toBool()) { @@ -51,6 +50,8 @@ QSigner::QSigner(const TokenData &token) } } +QSigner::~QSigner() = default; + X509Cert QSigner::cert() const { if(m_token.cert().isNull()) @@ -88,27 +89,13 @@ std::vector QSigner::sign(const std::string &method, const std::v throw e; \ } - auto val = QCryptoBackend::getBackend(m_token); - if(!val) - { - auto err = tr("Failed to login token") + ' ' + - QCryptoBackend::errorString(val.error()); - switch(val.error()) { - case QCryptoBackend::PinCanceled: throwException(err, Exception::PINCanceled); - case QCryptoBackend::PinLocked: throwException(err, Exception::PINLocked); - case QCryptoBackend::InProgress: throwException(err, Exception::General); - default: throwException(err, Exception::PINFailed); - } - } - std::unique_ptr backend(val.value()); - - QByteArray sig = waitFor(&QCryptoBackend::sign, backend.get(), + QByteArray sig = waitFor(&QCryptoBackend::sign, m_backend.get(), methodToNID(method), QByteArray::fromRawData((const char*)digest.data(), int(digest.size()))); if(sig.isEmpty()) { auto err = tr("Failed to login token") + ' ' + - QCryptoBackend::errorString(backend->status); - switch(backend->status) { + QCryptoBackend::errorString(m_backend->status); + switch(m_backend->status) { case QCryptoBackend::PinCanceled: throwException(err, Exception::PINCanceled); case QCryptoBackend::PinLocked: throwException(err, Exception::PINLocked); default: break; diff --git a/client/signer/QSigner.h b/client/signer/QSigner.h index 6f9620742..36328587d 100644 --- a/client/signer/QSigner.h +++ b/client/signer/QSigner.h @@ -26,14 +26,17 @@ #include #include -class QSmartCard; +#include + +class QCryptoBackend; class TokenData; class QSigner final : public digidoc::Signer { Q_DECLARE_TR_FUNCTIONS(QSigner); public: - explicit QSigner(const TokenData &token); + QSigner(std::unique_ptr backend, const TokenData &token); + ~QSigner() final; digidoc::X509Cert cert() const final; std::vector sign(const std::string &method, @@ -42,5 +45,6 @@ class QSigner final : public digidoc::Signer private: static QCryptographicHash::Algorithm methodToNID(const std::string &method); + std::unique_ptr m_backend; TokenData m_token; }; diff --git a/client/translations/en.ts b/client/translations/en.ts index 496dee821..b519335e2 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -2768,6 +2768,10 @@ Additional licenses and components Invalid country code Invalid country code + + Enter %1 code + Enter %1 code + Please enter %1 code in the new window that will appear shortly Please enter %1 code in the new window that will appear shortly @@ -2780,6 +2784,14 @@ Additional licenses and components Continue signing Continue signing + + Remaining attempts: %1 + Remaining attempts: %1 + + + Failed to sign document + Failed to sign document + SslCertificate diff --git a/client/translations/et.ts b/client/translations/et.ts index 704acf724..41a2f3dba 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -2768,6 +2768,10 @@ Täiendavad litsentsid ja komponendid Invalid country code Vigane riigikood + + Enter %1 code + Sisesta %1-kood + Please enter %1 code in the new window that will appear shortly Palun sisesta %1-kood peatselt ilmuvas uues aknas @@ -2780,6 +2784,14 @@ Täiendavad litsentsid ja komponendid Continue signing Jätka allkirjastamisega + + Remaining attempts: %1 + Katseid jäänud: %1 + + + Failed to sign document + Dokumendi allkirjastamine ebaõnnestus + SslCertificate diff --git a/client/widgets/PinLineEdit.cpp b/client/widgets/PinLineEdit.cpp new file mode 100644 index 000000000..286d9eba2 --- /dev/null +++ b/client/widgets/PinLineEdit.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "widgets/PinLineEdit.h" + +#include + +PinLineEdit::PinLineEdit(QWidget *parent) + : LineEdit(parent) + , m_validator(new QRegularExpressionValidator(this)) +{ + setEchoMode(QLineEdit::Password); + setValidator(m_validator); + setPinLen(4); +} + +void PinLineEdit::setPinLen(int min, int max, bool numeric) +{ + setMaxLength(max); + m_validator->setRegularExpression(QRegularExpression(QStringLiteral("^%1{%2,%3}$") + .arg(numeric ? QStringLiteral("\\d") : QStringLiteral(".")) + .arg(min).arg(max))); +} diff --git a/client/widgets/PinLineEdit.h b/client/widgets/PinLineEdit.h new file mode 100644 index 000000000..cbf769e7d --- /dev/null +++ b/client/widgets/PinLineEdit.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "widgets/LineEdit.h" + +class QRegularExpressionValidator; + +class PinLineEdit final: public LineEdit +{ + Q_OBJECT +public: + explicit PinLineEdit(QWidget *parent = nullptr); + + void setPinLen(int min, int max = 12, bool numeric = true); + +private: + QRegularExpressionValidator *m_validator; +};