From 592d2a548d9020b8b3fece3555981a4d946a655a Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 17 Jul 2026 20:05:31 +0300 Subject: [PATCH 1/5] Event based polling IB-8767 Signed-off-by: Raul Metsma --- client/Application.cpp | 10 +- client/Application.h | 4 +- client/CDocSupport.cpp | 15 +- client/CDocSupport.h | 2 +- client/CryptoDoc.cpp | 11 +- client/DigiDoc.cpp | 20 ++- client/MainWindow.cpp | 40 ++--- client/QCryptoBackend.cpp | 250 ++++++++++++++++++++++-------- client/QCryptoBackend.h | 54 ++++--- client/QSigner.cpp | 227 +++++---------------------- client/QSigner.h | 34 ++-- client/dialogs/AddRecipients.cpp | 8 +- client/dialogs/SettingsDialog.cpp | 4 +- client/translations/en.ts | 12 +- client/translations/et.ts | 12 +- client/widgets/ContainerPage.cpp | 4 +- 16 files changed, 341 insertions(+), 366 deletions(-) diff --git a/client/Application.cpp b/client/Application.cpp index 66b4107d5..75f246650 100644 --- a/client/Application.cpp +++ b/client/Application.cpp @@ -25,7 +25,7 @@ #include "Configuration.h" #include "CDocSupport.h" #include "MainWindow.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "QSmartCard.h" #include "DigiDoc.h" #include "Settings.h" @@ -296,7 +296,7 @@ class Application::Private Configuration *conf {}; QAction *closeAction {}, *newClientAction {}, *helpAction {}; std::unique_ptr bar; - QSigner *signer {}; + QCryptoManager *cryptoManager {}; QTranslator appTranslator, qtTranslator; QString lang; @@ -307,7 +307,7 @@ class Application::Private #endif // Q_OS_WIN ~Private() { - delete signer; + delete cryptoManager; } }; @@ -462,7 +462,7 @@ Application::Application( int &argc, char **argv ) try { digidoc::Conf::init( new DigidocConf ); - d->signer = new QSigner(this); + d->cryptoManager = new QCryptoManager(); updateTSLCache(QDateTime::currentDateTimeUtc().addDays(-7)); digidoc::initialize(applicationName().toUtf8().constData(), QStringLiteral("%1/%2 (%3)") @@ -968,7 +968,7 @@ void Application::showWarning(const QString &title, const digidoc::Exception &e) WarningDialog::create()->withTitle(title)->withDetails(causes.join('\n'))->open(); } -QSigner* Application::signer() const { return d->signer; } +QCryptoManager* Application::cryptoManager() const { return d->cryptoManager; } void Application::updateTSLCache(const QDateTime &tslTime) { diff --git a/client/Application.h b/client/Application.h index c458af40a..4118e7352 100644 --- a/client/Application.h +++ b/client/Application.h @@ -37,7 +37,7 @@ using BaseApplication = QtSingleApplication; namespace digidoc { class Exception; } class Configuration; class QAction; -class QSigner; +class QCryptoManager; class Application final: public BaseApplication { Q_OBJECT @@ -61,7 +61,7 @@ class Application final: public BaseApplication Configuration *conf(); void loadTranslation( const QString &lang ); bool notify(QObject *object, QEvent *event ) final; - QSigner* signer() const; + QCryptoManager* cryptoManager() const; int run(); void waitForTSL( const QString &file ); diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 19cf47812..8cd01aede 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -32,7 +32,6 @@ #include "Application.h" #include "CheckConnection.h" #include "QCryptoBackend.h" -#include "QSigner.h" #include "Settings.h" #include "TokenData.h" #include "Utils.h" @@ -124,7 +123,7 @@ libcdoc::result_t DDCryptoBackend::decryptRSA(std::vector& dst, const std::vector &data, bool oaep, unsigned int idx) { if (!backend) { - auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -144,7 +143,7 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vectorsigner()->tokenauth()); + auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -159,7 +158,7 @@ libcdoc::result_t DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector &key_material, const std::vector &salt, unsigned int idx) { if (!backend) { - auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -189,7 +188,7 @@ DDCryptoBackend::getLastErrorStr(libcdoc::result_t code) const case IN_PROGRESS: return "Signing/decrypting is already in progress another window."; case BACKEND_ERROR: - return qApp->signer()->getLastErrorStr().toStdString(); + return "Backend error"; } return libcdoc::CryptoBackend::getLastErrorStr(code); } @@ -321,8 +320,8 @@ DDNetworkBackend::fetchKey(std::vector &result, const std::string &url, return BACKEND_ERROR; } - TokenData auth = qApp->signer()->tokenauth(); - auto val = QCryptoBackend::getBackend(qApp->signer()->tokenauth()); + TokenData auth = qApp->cryptoManager()->tokenauth(); + auto val = QCryptoBackend::getBackend(auth); if (!val) return getDecryptStatus(val.error()); std::unique_ptr backend(val.value()); @@ -333,7 +332,7 @@ DDNetworkBackend::fetchKey(std::vector &result, const std::string &url, return BACKEND_ERROR; } QScopedPointer nam( - CheckConnection::setupNAM(req, qApp->signer()->tokenauth().cert(), authKey, Settings::CDOC2_GET_CERT)); + CheckConnection::setupNAM(req, auth.cert(), authKey, Settings::CDOC2_GET_CERT)); QEventLoop e; QNetworkReply *reply = nam->get(req); connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 7a9fe5573..d0d0f2b72 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -52,7 +52,7 @@ struct DDConfiguration : public libcdoc::Configuration { // // CryptoBackend // -// Bridges to qApp->signer() +// Bridges to qApp->cryptoManager() // struct DDCryptoBackend final : public libcdoc::CryptoBackend { diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a83b10d91..99b1a805d 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -23,7 +23,6 @@ #include "CDocSupport.h" #include "TokenData.h" #include "QCryptoBackend.h" -#include "QSigner.h" #include "Settings.h" #include "SslCertificate.h" #include "Utils.h" @@ -283,7 +282,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) if(!d->reader) { WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(tr("Container is not open")) ->open(); return false; @@ -292,12 +291,12 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) int lock_idx = -1; const std::vector &locks = d->reader->getLocks(); if (lock == nullptr) { - QByteArray der = qApp->signer()->tokenauth().cert().toDer(); + QByteArray der = qApp->cryptoManager()->tokenauth().cert().toDer(); lock_idx = d->reader->getLockForCert( std::vector(der.cbegin(), der.cend())); if (lock_idx < 0) { WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(tr("You do not have the key to decrypt this document")) ->open(); return false; @@ -315,7 +314,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) } if (!lock || (lock->isSymmetric() && secret.isEmpty())) { WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(tr("You do not have the key to decrypt this document")) ->open(); return false; @@ -368,7 +367,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) break; } WarningDialog::create() - ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withTitle(tr("Failed to decrypt document")) ->withText(str) ->withDetails(QString::fromStdString(msg)) ->open(); diff --git a/client/DigiDoc.cpp b/client/DigiDoc.cpp index 8409b55b4..8c3748cc9 100644 --- a/client/DigiDoc.cpp +++ b/client/DigiDoc.cpp @@ -24,7 +24,6 @@ #include "Common.h" #include "MainWindow.h" #include "QCryptoBackend.h" -#include "QSigner.h" #include "Settings.h" #include "TokenData.h" #include "Utils.h" @@ -33,6 +32,7 @@ #include #include +#include #include #include @@ -45,6 +45,16 @@ using namespace digidoc; using namespace ria::qdigidoc4; +struct ExtendSigner final: public Signer +{ + X509Cert cert() const final { return X509Cert(); } + std::vector sign(const std::string & /*method*/, + const std::vector & /*digest*/) const final + { + throw Exception(__FILE__, __LINE__, "Not implemented"); + } +}; + static std::string to(const QString &str) { return str.toStdString(); } static QString from(const std::string &str) { return FileDialog::normalized(QString::fromStdString(str)); } @@ -424,8 +434,8 @@ bool DigiDoc::extend() { QWidget *parent = parentWidget(); try { - auto *signer = qApp->signer(); - signer->setUserAgent(QStringLiteral("%1/%2 (%3) Devices: %4").arg( + ExtendSigner signer; + signer.setUserAgent(QStringLiteral("%1/%2 (%3) Devices: %4").arg( QCoreApplication::applicationName(), QCoreApplication::applicationVersion(), Common::applicationOs(), @@ -434,12 +444,10 @@ bool DigiDoc::extend() ServiceConfirmation cb(parent); QString current = m_fileName; size_t extendCount = 0; - bool wrapped = false; if(std::unique_ptr extended = waitFor([&] { - return Container::extendContainerValidity(*b, signer, extendCount); + return Container::extendContainerValidity(*b, &signer, extendCount); })) { - wrapped = true; const QString asics = QCoreApplication::translate("MainWindow", "Documents (%1)").arg(QLatin1String("*.asics *.scs")); QFileInfo f(current); QString name = f.absolutePath() + '/' + f.completeBaseName() + QStringLiteral(".asics"); diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 683ad6b99..cab64a0a3 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -24,6 +24,7 @@ #include "CheckConnection.h" #include "CryptoDoc.h" #include "DigiDoc.h" +#include "QCryptoBackend.h" #include "QPCSC.h" #include "QSigner.h" #include "Settings.h" @@ -92,13 +93,13 @@ MainWindow::MainWindow( QWidget *parent ) #endif // Refresh ID card info in card widget - connect(qApp->signer(), &QSigner::cacheChanged, this, &MainWindow::updateSelector); - connect(qApp->signer(), &QSigner::signDataChanged, ui->signContainerPage, &ContainerPage::tokenChanged); - connect(qApp->signer(), &QSigner::authDataChanged, ui->cryptoContainerPage, &ContainerPage::tokenChanged); + connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &MainWindow::updateSelector); + connect(qApp->cryptoManager(), &QCryptoManager::signDataChanged, ui->signContainerPage, &ContainerPage::tokenChanged); + connect(qApp->cryptoManager(), &QCryptoManager::authDataChanged, ui->cryptoContainerPage, &ContainerPage::tokenChanged); // Refresh card info on "My EID" page - connect(qApp->signer()->smartcard(), &QSmartCard::tokenChanged, this, &MainWindow::updateMyEID); - connect(qApp->signer()->smartcard(), &QSmartCard::dataChanged, this, &MainWindow::updateMyEid); + connect(qApp->cryptoManager()->smartcard(), &QSmartCard::tokenChanged, this, &MainWindow::updateMyEID); + connect(qApp->cryptoManager()->smartcard(), &QSmartCard::dataChanged, this, &MainWindow::updateMyEid); connect(ui->signIntroButton, &QPushButton::clicked, this, [this] { openContainer(true); }); connect(ui->cryptoIntroButton, &QPushButton::clicked, this, [this] { openContainer(false); }); @@ -119,10 +120,10 @@ MainWindow::MainWindow( QWidget *parent ) connect(ui->accordion, &Accordion::changePinClicked, this, &MainWindow::changePinClicked); connect(ui->cardInfo, &CardWidget::selected, ui->selector, &QToolButton::toggle); - ui->signContainerPage->tokenChanged(qApp->signer()->tokensign()); - ui->cryptoContainerPage->tokenChanged(qApp->signer()->tokenauth()); - updateMyEID(qApp->signer()->smartcard()->tokenData()); - updateMyEid(qApp->signer()->smartcard()->data()); + ui->signContainerPage->tokenChanged(qApp->cryptoManager()->tokensign()); + ui->cryptoContainerPage->tokenChanged(qApp->cryptoManager()->tokenauth()); + updateMyEID(qApp->cryptoManager()->smartcard()->tokenData()); + updateMyEid(qApp->cryptoManager()->smartcard()->data()); } MainWindow::~MainWindow() noexcept = default; @@ -157,8 +158,8 @@ void MainWindow::changeEvent(QEvent* event) void MainWindow::changePinClicked(QSmartCardData::PinType type, QSmartCard::PinAction action) { - if(qApp->signer()->smartcard()->pinChange(type, action, ui->topBar)) - updateMyEid(qApp->signer()->smartcard()->data()); + if(qApp->cryptoManager()->smartcard()->pinChange(type, action, ui->topBar)) + updateMyEid(qApp->cryptoManager()->smartcard()->data()); } void MainWindow::closeEvent(QCloseEvent * /*event*/) @@ -274,7 +275,7 @@ void MainWindow::navigateToPage( Pages page, const QStringList &files, bool crea if(navigate) { cryptoDoc = std::move(cryptoContainer); - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->signer()->tokenauth().cert()); + ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->cryptoManager()->tokenauth().cert()); } } @@ -289,7 +290,8 @@ void MainWindow::onSignAction(int action, const QString &idCode, const QString & case SignatureAdd: case SignatureToken: sign([this](const QString &city, const QString &state, const QString &zip, const QString &country, const QString &role) { - return digiDoc->sign(city, state, zip, country, role, qApp->signer()); + QSigner signer(qApp->cryptoManager()->tokensign()); + return digiDoc->sign(city, state, zip, country, role, &signer); }); break; case SignatureMobile: @@ -344,7 +346,7 @@ void MainWindow::convertToCDoc() else cryptoContainer->documentModel()->copyModel(digiDoc->documentModel()); - auto cardData = qApp->signer()->tokenauth(); + auto cardData = qApp->cryptoManager()->tokenauth(); if (!cardData.cert().isNull()) { cryptoContainer->addEncryptionKey(CKey(cardData.cert())); } @@ -733,22 +735,22 @@ void MainWindow::updateSelector() { case SignIntro: case SignDetails: - selected = qApp->signer()->tokensign(); + selected = qApp->cryptoManager()->tokensign(); filter = Signing; break; case CryptoIntro: case CryptoDetails: - selected = qApp->signer()->tokenauth(); + selected = qApp->cryptoManager()->tokenauth(); filter = Decrypting; break; case MyEid: default: - selected = qApp->signer()->smartcard()->tokenData(); + selected = qApp->cryptoManager()->smartcard()->tokenData(); filter = MyEID; break; } QVector list; - for(const TokenData &token: qApp->signer()->cache()) + for(const TokenData &token: qApp->cryptoManager()->cache()) { if(token.card() == selected.card()) continue; @@ -783,7 +785,7 @@ void MainWindow::updateSelector() if(show) { auto *cardPopup = new CardPopup(list, this); - connect(cardPopup, &CardPopup::activated, qApp->signer(), &QSigner::selectCard); + connect(cardPopup, &CardPopup::activated, qApp->cryptoManager(), &QCryptoManager::selectCard); connect(cardPopup, &CardPopup::activated, this, [this] { ui->selector->setChecked(false); }); cardPopup->show(); } diff --git a/client/QCryptoBackend.cpp b/client/QCryptoBackend.cpp index 4c7640fe8..36ba71de8 100644 --- a/client/QCryptoBackend.cpp +++ b/client/QCryptoBackend.cpp @@ -20,18 +20,21 @@ #include "QCryptoBackend.h" #include "Application.h" -#include "TokenData.h" #ifdef Q_OS_WIN #include "QCNG.h" #endif +#include "QPCSC.h" #include "QPKCS11.h" -#include "QSigner.h" #include "QSmartCard.h" +#include "SslCertificate.h" +#include #include -#include +#include #include +static Q_LOGGING_CATEGORY(CryptoLog, "qdigidoc4.QCryptoManager") + // TODO: Port everything to the new OpenSSL API #define OPENSSL_SUPPRESS_DEPRECATED @@ -39,15 +42,35 @@ #include #include +struct QCryptoManager::Private +{ + QSmartCard smartcard; + TokenData auth, sign; + QList cache; + QSemaphore operationLock {1}; + QReadWriteLock lock; + + RSA_METHOD *rsa_method = RSA_meth_dup(RSA_get_default_method()); + EC_KEY_METHOD *ec_method = EC_KEY_METHOD_new(EC_KEY_get_default_method()); +}; + QCryptoBackend::~QCryptoBackend() { - qApp->signer()->sessionLock().unlock(); - qApp->signer()->smartcard()->reloadCard(token, true); + auto *manager = qApp->cryptoManager(); + // Reload counters while the operation semaphore is still held, otherwise + // operationLock.release() would release it and queue refresh() first, letting the + // manager thread enumerate tokens concurrently with this PCSC reload. + manager->smartcard()->reloadCard(token, true); + + manager->d->operationLock.release(); + // A card change may have been skipped by refresh() while the operation held + // the lock; re-sync on the main thread now that the card session is free. + QMetaObject::invokeMethod(manager, [manager] { manager->refresh(); }, Qt::QueuedConnection); } std::expected QCryptoBackend::getBackend(const TokenData& token) { - if(!qApp->signer()->sessionLock().tryLockForWrite(10 * 1000)) + if(!qApp->cryptoManager()->d->operationLock.tryAcquire(1, (10 * 1000))) return std::unexpected(InProgress); #ifdef Q_OS_WIN auto backend = std::make_unique(); @@ -73,28 +96,18 @@ QSslCertificate QCryptoBackend::cert() const return token.cert(); } -QList -QCryptoBackend::getTokens() -{ -#ifdef Q_OS_WIN - return QCNG::tokens(); -#else - return QPKCS11::tokens(); -#endif -} - QString QCryptoBackend::errorString(Status error) { switch( error ) { case PinOK: return QString(); - case PinCanceled: return QCoreApplication::translate("QCryptoBackend", "PIN entry canceled"); - case PinLocked: return QCoreApplication::translate("QCryptoBackend", "PIN locked"); - case PinIncorrect: return QCoreApplication::translate("QCryptoBackend", "PIN incorrect"); - case InProgress: return QCoreApplication::translate("QCryptoBackend", "Signing/decrypting is already in progress another window."); - case GeneralError: return QCoreApplication::translate("QCryptoBackend", "PKCS11 general error"); - case DeviceError: return QCoreApplication::translate("QCryptoBackend", "PKCS11 device error"); - default: return QCoreApplication::translate("QCryptoBackend", "Unknown error"); + case PinCanceled: return tr("PIN entry canceled"); + case PinLocked: return tr("PIN locked"); + case PinIncorrect: return tr("PIN incorrect"); + case InProgress: return tr("Signing/decrypting is already in progress another window."); + case GeneralError: return tr("PKCS11 general error"); + case DeviceError: return tr("PKCS11 device error"); + default: return tr("Unknown error"); } } @@ -133,39 +146,6 @@ ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM * /*inv*/, c return sig; } -static RSA_METHOD *get_rsa_method(bool release = false) -{ - static RSA_METHOD *method = nullptr; - if (!method && !release) { - method = RSA_meth_dup(RSA_get_default_method()); - RSA_meth_set1_name(method, "QSmartCard"); - RSA_meth_set_sign(method, rsa_sign); - } else if (method && release) { - RSA_meth_free(method); - method = nullptr; - } - return method; -} - -static EC_KEY_METHOD *get_ec_method(bool release = false) -{ - static EC_KEY_METHOD *method = nullptr; - if(!method && !release) { - method = EC_KEY_METHOD_new(EC_KEY_get_default_method()); - using EC_KEY_sign = int (*)(int type, const unsigned char *dgst, int dlen, unsigned char *sig, - unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey); - using EC_KEY_sign_setup = int (*)(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp); - EC_KEY_sign sign = nullptr; - EC_KEY_sign_setup sign_setup = nullptr; - EC_KEY_METHOD_get_sign(method, &sign, &sign_setup, nullptr); - EC_KEY_METHOD_set_sign(method, sign, sign_setup, ecdsa_do_sign); - } else if (method && release) { - EC_KEY_METHOD_free(method); - method = nullptr; - } - return method; -} - QSslKey QCryptoBackend::getKey() const { @@ -174,24 +154,170 @@ QCryptoBackend::getKey() const status = GeneralError; return {}; } + auto *manager = qApp->cryptoManager(); if(key.algorithm() == QSsl::Ec) { auto *ec = (EC_KEY*)key.handle(); - EC_KEY_set_method(ec, get_ec_method()); + EC_KEY_set_method(ec, manager->d->ec_method); EC_KEY_set_ex_data(ec, 0, (void *) this); } else { RSA *rsa = (RSA*)key.handle(); - RSA_set_method(rsa, get_rsa_method()); + RSA_set_method(rsa, manager->d->rsa_method); RSA_set_ex_data(rsa, 0, (void *) this); } return key; } -void -QCryptoBackend::shutDown() +QCryptoManager::QCryptoManager() + : d(new Private) +{ + RSA_meth_set1_name(d->rsa_method, "QSmartCard"); + RSA_meth_set_sign(d->rsa_method, rsa_sign); + using EC_KEY_sign = int (*)(int type, const unsigned char *dgst, int dlen, unsigned char *sig, + unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey); + using EC_KEY_sign_setup = int (*)(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp); + EC_KEY_sign sign = nullptr; + EC_KEY_sign_setup sign_setup = nullptr; + EC_KEY_METHOD_get_sign(d->ec_method, &sign, &sign_setup, nullptr); + EC_KEY_METHOD_set_sign(d->ec_method, sign, sign_setup, ecdsa_do_sign); + + // Run the manager on its own thread with an event loop, so token enumeration + // and reloadCard happen off the UI thread. cardChanged/selectCard are delivered + // via queued connections and processed by run()'s exec() loop. + moveToThread(this); + connect(&QPCSC::instance(), &QPCSC::cardChanged, this, &QCryptoManager::refresh); + QPCSC::instance().start(); + start(); +} + +QCryptoManager::~QCryptoManager() +{ + quit(); + wait(); + EC_KEY_METHOD_free(d->ec_method); + RSA_meth_free(d->rsa_method); + delete d; +} + +void QCryptoManager::run() +{ + refresh(); + exec(); +} + +QList QCryptoManager::cache() const { QReadLocker locker(&d->lock); return d->cache; } +QSmartCard *QCryptoManager::smartcard() const { return &d->smartcard; } +TokenData QCryptoManager::tokenauth() const { QReadLocker locker(&d->lock); return d->auth; } +TokenData QCryptoManager::tokensign() const { QReadLocker locker(&d->lock); return d->sign; } + +void QCryptoManager::selectCard(const TokenData &token) +{ + bool isSign = SslCertificate(token.cert()).keyUsage().contains(SslCertificate::NonRepudiation); + TokenData other; + { + QWriteLocker locker(&d->lock); + if(isSign) + d->sign = token; + else + d->auth = token; + for(const TokenData &t: d->cache) + { + if(t == token || + t.card() != token.card() || + isSign == SslCertificate(t.cert()).keyUsage().contains(SslCertificate::NonRepudiation)) + continue; + if(isSign) + d->auth = t; + else + d->sign = t; + other = t; + break; + } + } + if(isSign) + Q_EMIT signDataChanged(token); + else + Q_EMIT authDataChanged(token); + if(!other.isNull()) + { + if(isSign) + Q_EMIT authDataChanged(other); + else + Q_EMIT signDataChanged(other); + } + d->smartcard.reloadCard(token, false); +} + +void QCryptoManager::refresh() { - get_rsa_method(true); - get_ec_method(true); + // Don't enumerate tokens while a sign/decrypt operation holds the card + // session — the operation runs on a worker thread and PKCS11/PCSC access + // from two threads at once is unsafe. unlockOperation() queues a catch-up + // refresh when the operation finishes, so a skip here is not lost. + if(!d->operationLock.tryAcquire()) + return; + +#ifdef Q_OS_WIN + QList newCache = QCNG::tokens(); +#else + QList newCache = QPKCS11::tokens(); +#endif + + QList acards, scards; + for(const TokenData &t: newCache) + { + SslCertificate c(t.cert()); + if(c.keyUsage().contains(SslCertificate::KeyEncipherment) || + c.keyUsage().contains(SslCertificate::KeyAgreement)) + acards.append(t); + if(c.keyUsage().contains(SslCertificate::NonRepudiation)) + scards.append(t); + } + + bool cacheChangedFlag = false; + TokenData aold, sold, anew, snew; + { + QWriteLocker locker(&d->lock); + if(newCache != d->cache) + { + d->cache = std::move(newCache); + cacheChangedFlag = true; + } + + aold = d->auth; + sold = d->sign; + + if(!d->auth.isNull() && !acards.contains(d->auth)) + { + qCDebug(CryptoLog) << "Disconnected from auth card" << d->auth.card(); + d->auth.clear(); + } + if(!d->sign.isNull() && !scards.contains(d->sign)) + { + qCDebug(CryptoLog) << "Disconnected from sign card" << d->sign.card(); + d->sign.clear(); + } + + if(d->sign.isNull() && !scards.isEmpty()) + d->sign = scards.first(); + if(d->auth.isNull() && !acards.isEmpty()) + d->auth = acards.first(); + + anew = d->auth; + snew = d->sign; + } + + if(cacheChangedFlag) + Q_EMIT cacheChanged(); + TokenData update; + if(aold != anew) + Q_EMIT authDataChanged(update = anew); + if(sold != snew) + Q_EMIT signDataChanged(update = snew); + if(aold != anew || sold != snew) + d->smartcard.reloadCard(update, false); + + d->operationLock.release(); } diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index cac9b42ca..0e6616733 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -21,15 +21,18 @@ #include "TokenData.h" +#include #include +#include #include -class TokenData; +class QSmartCard; class QSslKey; class QCryptoBackend { + Q_DECLARE_TR_FUNCTIONS(QCryptoBackend); public: enum Status : quint8 { @@ -53,41 +56,56 @@ class QCryptoBackend /** * @brief Get the SSL key for the certificate - * + * * @return the Qt SSL key */ QSslKey getKey() const; QSslCertificate cert() const; /** * @brief Get a new Backend object and log in with the given token - * + * * @param token the token to use - * @return the new backend object or an error code + * @return the new backend object or an error code */ - static std::expected getBackend(const TokenData& token); - /** - * @brief Shut down all backends - * - * This should be called when the application is about to exit. It releases all static data held by backend(s) (e.g. PKCS11 library) - */ - static void shutDown(); + static std::expected getBackend(const TokenData &token); /** * @brief The status of the last operation */ mutable Status status = PinOK; - /** - * @brief Get a list of all available tokens - * - * @return list of all available tokens - */ - static QList getTokens(); - static QString errorString(Status error); + protected: virtual Status login(const TokenData &cert) = 0; private: TokenData token; }; + +class QCryptoManager final : public QThread +{ + Q_OBJECT +public: + explicit QCryptoManager(); + ~QCryptoManager() final; + + QList cache() const; + QSmartCard *smartcard() const; + void selectCard(const TokenData &token); + TokenData tokenauth() const; + TokenData tokensign() const; + +Q_SIGNALS: + void cacheChanged(); + void authDataChanged(const TokenData &token); + void signDataChanged(const TokenData &token); + +private: + friend class QCryptoBackend; + void refresh(); + void run() final; + + struct Private; + Private *d; +}; diff --git a/client/QSigner.cpp b/client/QSigner.cpp index 0ccd17fc0..e7c8811e9 100644 --- a/client/QSigner.cpp +++ b/client/QSigner.cpp @@ -20,92 +20,43 @@ #include "QSigner.h" #include "Application.h" -#include "QPCSC.h" +#include "QCryptoBackend.h" #include "QSmartCard.h" #include "TokenData.h" -#include "QCryptoBackend.h" -#include "SslCertificate.h" #include "Utils.h" -#include "dialogs/WarningDialog.h" #include +#include #include -#include -#include -#include -#include -#include - -#include -#include -#include - -static Q_LOGGING_CATEGORY(SLog, "qdigidoc4.QSigner") - -class QSigner::Private final -{ -public: - QSmartCard *smartcard {}; - TokenData auth, sign; - QList cache; - QReadWriteLock lock; - QMutex sleepMutex; - QWaitCondition sleepCond; -}; +#include using namespace digidoc; -QSigner::QSigner(QObject *parent) - : QThread(parent) - , d(new Private) +QSigner::QSigner(const TokenData &token) + : m_token(token) { - d->smartcard = new QSmartCard(parent); - connect(this, &QSigner::error, this, [](const QString &title, const QString &msg) { - WarningDialog::create() - ->withTitle(title) - ->withText(msg) - ->open(); - }); - connect(this, &QSigner::signDataChanged, this, [this](const TokenData &token) { + if(m_token.data(QStringLiteral("PSS")).toBool()) + { std::string method; - if(token.data(QStringLiteral("PSS")).toBool()) + switch(methodToNID(CONF(signatureDigestUri))) { - switch(methodToNID(CONF(signatureDigestUri))) - { - case QCryptographicHash::Sha224: method = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"; break; - case QCryptographicHash::Sha256: method = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"; break; - case QCryptographicHash::Sha384: method = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"; break; - case QCryptographicHash::Sha512: method = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"; break; - default: break; - } + case QCryptographicHash::Sha224: method = "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1"; break; + case QCryptographicHash::Sha256: method = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"; break; + case QCryptographicHash::Sha384: method = "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1"; break; + case QCryptographicHash::Sha512: method = "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1"; break; + default: break; } setMethod(method); - }); - connect(&QPCSC::instance(), &QPCSC::cardChanged, this, [this] { - qCDebug(SLog) << "Card change detected"; - d->sleepCond.wakeAll(); - }); - start(); - QPCSC::instance().start(); -} - -QSigner::~QSigner() -{ - requestInterruption(); - d->sleepCond.wakeAll(); - wait(); - delete d->smartcard; - delete d; + } } -QList QSigner::cache() const { return d->cache; } - X509Cert QSigner::cert() const { - if( d->sign.cert().isNull() ) - throw Exception(__FILE__, __LINE__, QSigner::tr("Sign certificate is not selected").toStdString()); - QByteArray der = d->sign.cert().toDer(); + if(m_token.cert().isNull()) + throw Exception(__FILE__, __LINE__, + tr("Sign certificate is not selected").toStdString()); + QByteArray der = m_token.cert().toDer(); return X509Cert((const unsigned char*)der.constData(), size_t(der.size()), X509Cert::Der); } @@ -125,98 +76,12 @@ QCryptographicHash::Algorithm QSigner::methodToNID(const std::string &method) method == "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384") return QCryptographicHash::Sha384; if(method == "http://www.w3.org/2001/04/xmlenc#sha512" || method == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" || - method == "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1" || + method == "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1" || method == "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512") return QCryptographicHash::Sha512; return QCryptographicHash::Sha256; } -void QSigner::run() -{ - d->auth.clear(); - d->sign.clear(); - - while(!isInterruptionRequested()) { - if(d->lock.tryLockForRead()) { - QList acards, scards; - QList cache = QCryptoBackend::getTokens(); - if(cache != d->cache) - { - d->cache = std::move(cache); - Q_EMIT cacheChanged(); - } - for(const TokenData &t: d->cache) - { - SslCertificate c(t.cert()); - if(c.keyUsage().contains(SslCertificate::KeyEncipherment) || - c.keyUsage().contains(SslCertificate::KeyAgreement)) - acards.append(t); - if(c.keyUsage().contains(SslCertificate::NonRepudiation)) - scards.append(t); - } - - TokenData aold = d->auth; - TokenData sold = d->sign; - // check if selected card is still in slot - if(!d->auth.isNull() && !acards.contains(d->auth)) - { - qCDebug(SLog) << "Disconnected from auth card" << d->auth.card(); - d->auth.clear(); - } - if(!d->sign.isNull() && !scards.contains(d->sign)) - { - qCDebug(SLog) << "Disconnected from sign card" << d->sign.card(); - d->sign.clear(); - } - - // if none is selected then pick first card with signing cert; - // if no signing certs then pick first card with auth cert - if(d->sign.isNull() && !scards.isEmpty()) - d->sign = scards.first(); - if(d->auth.isNull() && !acards.isEmpty()) - d->auth = acards.first(); - - // update data if something has changed - TokenData update; - if(aold != d->auth) - Q_EMIT authDataChanged(update = d->auth); - if(sold != d->sign) - Q_EMIT signDataChanged(update = d->sign); - if(aold != d->auth || sold != d->sign) - d->smartcard->reloadCard(update, false); - d->lock.unlock(); - } - - QMutexLocker locker(&d->sleepMutex); - if (isInterruptionRequested()) - break; - d->sleepCond.wait(&d->sleepMutex, 5000); - } - QCryptoBackend::shutDown(); -} - -void QSigner::selectCard(const TokenData &token) -{ - bool isSign = SslCertificate(token.cert()).keyUsage().contains(SslCertificate::NonRepudiation); - if(isSign) - Q_EMIT signDataChanged(d->sign = token); - else - Q_EMIT authDataChanged(d->auth = token); - for(const TokenData &other: cache()) - { - if(other == token || - other.card() != token.card() || - isSign == SslCertificate(other.cert()).keyUsage().contains(SslCertificate::NonRepudiation)) - continue; - if(isSign) // Select other cert if they are on same card - Q_EMIT authDataChanged(d->auth = other); - else - Q_EMIT signDataChanged(d->sign = other); - break; - } - d->smartcard->reloadCard(token, false); -} - -std::vector QSigner::sign(const std::string &method, const std::vector &digest ) const +std::vector QSigner::sign(const std::string &method, const std::vector &digest) const { #define throwException(msg, code) { \ Exception e(__FILE__, __LINE__, (msg).toStdString()); \ @@ -224,50 +89,32 @@ std::vector QSigner::sign(const std::string &method, const std::v throw e; \ } - auto val = QCryptoBackend::getBackend(d->sign); - if (!val) { + 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((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINCanceled); - case QCryptoBackend::PinLocked: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINLocked); - case QCryptoBackend::InProgress: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::General); - default: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(val.error())), Exception::PINFailed); + 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()); - if(backend->cert().isNull()) - throwException(tr("Signing certificate is not selected."), Exception::General) QByteArray sig = waitFor(&QCryptoBackend::sign, backend.get(), methodToNID(method), QByteArray::fromRawData((const char*)digest.data(), int(digest.size()))); - if (sig.isEmpty()) { + if(sig.isEmpty()) + { + auto err = tr("Failed to login token") + ' ' + + QCryptoBackend::errorString(backend->status); switch(backend->status) { - case QCryptoBackend::PinCanceled: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(backend->status)), Exception::PINCanceled); - case QCryptoBackend::PinLocked: - throwException((tr("Failed to login token") + ' ' + QCryptoBackend::errorString(backend->status)), Exception::PINLocked) - default: - break; + case QCryptoBackend::PinCanceled: throwException(err, Exception::PINCanceled); + case QCryptoBackend::PinLocked: throwException(err, Exception::PINLocked); + default: break; } - throwException(tr("Failed to sign document"), Exception::General) + throwException(tr("Failed to sign document"), Exception::General); } return {sig.constBegin(), sig.constEnd()}; } - -QReadWriteLock& QSigner::sessionLock() const -{ - return d->lock; -} - -QSmartCard * QSigner::smartcard() const { return d->smartcard; } -TokenData QSigner::tokenauth() const { return d->auth; } -TokenData QSigner::tokensign() const { return d->sign; } - -QString -QSigner::getLastErrorStr() const -{ - return "Backend error"; -} diff --git a/client/QSigner.h b/client/QSigner.h index 1b341f3ef..6f9620742 100644 --- a/client/QSigner.h +++ b/client/QSigner.h @@ -19,44 +19,28 @@ #pragma once -#include +#include "TokenData.h" + #include -#include +#include +#include -class QReadWriteLock; class QSmartCard; class TokenData; -class QSigner final: public QThread, public digidoc::Signer +class QSigner final : public digidoc::Signer { - Q_OBJECT - + Q_DECLARE_TR_FUNCTIONS(QSigner); public: - explicit QSigner(QObject *parent = nullptr); - ~QSigner() final; + explicit QSigner(const TokenData &token); - QList cache() const; digidoc::X509Cert cert() const final; - void selectCard(const TokenData &token); - std::vector sign( const std::string &method, + std::vector sign(const std::string &method, const std::vector &digest) const final; - QReadWriteLock& sessionLock() const; - QSmartCard * smartcard() const; - TokenData tokenauth() const; - TokenData tokensign() const; - QString getLastErrorStr() const; - -Q_SIGNALS: - void cacheChanged(); - void authDataChanged( const TokenData &token ); - void signDataChanged( const TokenData &token ); - void error(const QString &title, const QString &text); private: static QCryptographicHash::Algorithm methodToNID(const std::string &method); - void run() final; - class Private; - Private *d; + TokenData m_token; }; diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index a81ecbfd5..55b0cfd9a 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -26,7 +26,7 @@ #include "FileDialog.h" #include "IKValidator.h" #include "LdapSearch.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "Settings.h" #include "TokenData.h" #include "dialogs/WarningDialog.h" @@ -92,13 +92,13 @@ AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent) connect(ui->rightPane, &ItemList::removed, ui->rightPane, &ItemList::removeItem ); connect(ui->fromCard, &QPushButton::clicked, this, [this] { - addRecipient(qApp->signer()->tokenauth().cert()); + addRecipient(qApp->cryptoManager()->tokenauth().cert()); }); auto enableRecipientFromCard = [this] { - ui->fromCard->setDisabled(qApp->signer()->tokenauth().cert().isNull()); + ui->fromCard->setDisabled(qApp->cryptoManager()->tokenauth().cert().isNull()); }; enableRecipientFromCard(); - connect(qApp->signer(), &QSigner::authDataChanged, this, std::move(enableRecipientFromCard)); + connect(qApp->cryptoManager(), &QCryptoManager::authDataChanged, this, std::move(enableRecipientFromCard)); connect(ui->fromFile, &QPushButton::clicked, this, &AddRecipients::addRecipientFromFile); connect(ui->fromHistory, &QPushButton::clicked, this, &AddRecipients::addRecipientFromHistory); diff --git a/client/dialogs/SettingsDialog.cpp b/client/dialogs/SettingsDialog.cpp index 9ad9e6a4f..306e20c6c 100644 --- a/client/dialogs/SettingsDialog.cpp +++ b/client/dialogs/SettingsDialog.cpp @@ -25,7 +25,7 @@ #include "Configuration.h" #include "Diagnostics.h" #include "FileDialog.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "Settings.h" #include "SslCertificate.h" #include "TokenData.h" @@ -376,7 +376,7 @@ SettingsDialog::SettingsDialog(int page, QWidget *parent) #ifdef Q_OS_WIN connect(ui->btnNavFromHistory, &QPushButton::clicked, this, [this] { // remove certificates from browsing history of Edge and Google Chrome, and do it for all users. - QList cache = qApp->signer()->cache(); + QList cache = qApp->cryptoManager()->cache(); HCERTSTORE s = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); if(!s) diff --git a/client/translations/en.ts b/client/translations/en.ts index e0ee8edf5..0079f693c 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -608,6 +608,10 @@ Failed to open document Failed to open document + + Failed to decrypt document + Failed to decrypt document + Wrong password. Wrong password. @@ -2190,10 +2194,6 @@ ID-Card QSigner - - Signing certificate is not selected. - Signing certificate is not selected. - Failed to login token Failed to login token @@ -2206,10 +2206,6 @@ ID-Card Sign certificate is not selected Signing certificate is not selected - - Failed to decrypt document - Failed to decrypt document - QSmartCard diff --git a/client/translations/et.ts b/client/translations/et.ts index b8052a14a..0a6e6af88 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -608,6 +608,10 @@ Failed to open document Dokumendi avamine ebaõnnestus + + Failed to decrypt document + Dokumendi dekrüpteerimine ebaõnnestus + Wrong password. Vale parool. @@ -2190,10 +2194,6 @@ ID-kaardiga QSigner - - Signing certificate is not selected. - Allkirjastamise sertifikaat ei ole valitud. - Failed to login token PIN-koodi valideerimine ebaõnnestus @@ -2206,10 +2206,6 @@ ID-kaardiga Sign certificate is not selected Allkirjastamise sertifikaat ei ole valitud - - Failed to decrypt document - Dokumendi dekrüpteerimine ebaõnnestus - QSmartCard diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index b10c37130..6b5d36628 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -24,7 +24,7 @@ #include "CryptoDoc.h" #include "DigiDoc.h" #include "PrintSheet.h" -#include "QSigner.h" +#include "QCryptoBackend.h" #include "Settings.h" #include "SslCertificate.h" #include "TokenData.h" @@ -244,7 +244,7 @@ void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) WaitDialogHolder waitDialog(this, tr("Encrypting")); if(!container->encrypt(container->fileName(), {}, {})) return; - transition(container, qApp->signer()->tokenauth().cert()); + transition(container, qApp->cryptoManager()->tokenauth().cert()); emit action(EncryptContainerSuccess, {}, {}); return; } From 5c745fd982a6ee72a9a31cf93788065a5720e114 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 31 Jul 2026 14:30:07 +0300 Subject: [PATCH 2/5] Update My EID page style IB-8893 Signed-off-by: Raul Metsma --- client/MainWindow.cpp | 65 ++-- client/MainWindow.h | 3 +- client/MainWindow.ui | 22 +- client/common_enums.h | 3 +- client/dialogs/SettingsDialog.cpp | 1 + client/widgets/Accordion.cpp | 89 ------ client/widgets/Accordion.h | 53 ---- client/widgets/Accordion.ui | 115 ------- client/widgets/ContainerPage.cpp | 8 +- client/widgets/InfoStack.ui | 248 --------------- client/widgets/Label.cpp | 102 ++++++ client/widgets/Label.h | 23 +- .../widgets/{InfoStack.cpp => MyEidInfo.cpp} | 54 +++- client/widgets/{InfoStack.h => MyEidInfo.h} | 15 +- client/widgets/MyEidInfo.ui | 300 ++++++++++++++++++ client/widgets/VerifyCert.cpp | 6 + client/widgets/VerifyCert.ui | 284 +++++++---------- 17 files changed, 633 insertions(+), 758 deletions(-) delete mode 100644 client/widgets/Accordion.cpp delete mode 100644 client/widgets/Accordion.h delete mode 100644 client/widgets/Accordion.ui delete mode 100644 client/widgets/InfoStack.ui rename client/widgets/{InfoStack.cpp => MyEidInfo.cpp} (67%) rename client/widgets/{InfoStack.h => MyEidInfo.h} (81%) create mode 100644 client/widgets/MyEidInfo.ui diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index cab64a0a3..d44981ce9 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -101,8 +101,11 @@ MainWindow::MainWindow( QWidget *parent ) connect(qApp->cryptoManager()->smartcard(), &QSmartCard::tokenChanged, this, &MainWindow::updateMyEID); connect(qApp->cryptoManager()->smartcard(), &QSmartCard::dataChanged, this, &MainWindow::updateMyEid); - connect(ui->signIntroButton, &QPushButton::clicked, this, [this] { openContainer(true); }); - connect(ui->cryptoIntroButton, &QPushButton::clicked, this, [this] { openContainer(false); }); + connect(ui->signIntroButton, &QPushButton::clicked, this, [this] { + openContainer(QStringLiteral("*.bdoc *.ddoc *.asice *.sce *.asics *.scs *.edoc *.adoc%1") + .arg(Application::confValue(Application::SiVaUrl).toString().isEmpty() ? QLatin1String() : QLatin1String(" *.pdf"))); + }); + connect(ui->cryptoIntroButton, &QPushButton::clicked, this, [this] { openContainer(QLatin1String("*.cdoc *.cdoc2")); }); connect(ui->signContainerPage, &ContainerPage::action, this, &MainWindow::onSignAction); connect(ui->signContainerPage, &ContainerPage::addFiles, this, [this](const QStringList &files) { openFiles(files); } ); connect(ui->signContainerPage, &ContainerPage::warning, this, [this](WarningText warningText) { @@ -117,7 +120,7 @@ MainWindow::MainWindow( QWidget *parent ) ui->crypto->warningIcon(true); }); - connect(ui->accordion, &Accordion::changePinClicked, this, &MainWindow::changePinClicked); + connect(ui->infoStack, &MyEidInfo::changePinClicked, this, &MainWindow::changePinClicked); connect(ui->cardInfo, &CardWidget::selected, ui->selector, &QToolButton::toggle); ui->signContainerPage->tokenChanged(qApp->cryptoManager()->tokensign()); @@ -308,7 +311,7 @@ void MainWindow::onSignAction(int action, const QString &idCode, const QString & digiDoc->sign(city, state, zip, country, role, &s); }); break; - case ClearSignatureWarning: + case ContainerClearWarning: ui->signature->warningIcon(false); ui->warnings->closeWarnings(SignDetails); break; @@ -378,7 +381,7 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString case EncryptContainerSuccess: FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); break; - case ClearCryptoWarning: + case ContainerClearWarning: ui->crypto->warningIcon(false); ui->warnings->closeWarnings(CryptoDetails); break; @@ -450,13 +453,20 @@ void MainWindow::openFiles(QStringList files, bool addFile, bool forceCreate) default: if(addFile) { - bool crypto = state & CryptoContainers; - if(wrapContainer(!crypto)) + page = (state & CryptoContainers) ? CryptoDetails : SignDetails; + if(WarningDialog::create(this) + ->withTitle(page == CryptoDetails ? + tr("Files can not be added to the cryptocontainer") : + tr("Files can not be added to the signed container")) + ->withText(page == CryptoDetails ? + tr("The system will create a new container which shall contain the cypto-document and the files you wish to add.") : + tr("The system will create a new container which shall contain the signed document and the files you wish to add.")) + ->setCancelText(WarningDialog::Cancel) + ->addButton(tr("Continue"), QMessageBox::Ok) + ->exec() == QMessageBox::Ok) files.insert(files.begin(), digiDoc->fileName()); else create = false; - - page = crypto ? CryptoDetails : SignDetails; } else { @@ -471,15 +481,10 @@ void MainWindow::openFiles(QStringList files, bool addFile, bool forceCreate) navigateToPage(page, files, create); } -void MainWindow::openContainer(bool signature) +void MainWindow::openContainer(const QString &filter) { - QString filter = QFileDialog::tr("All Files (*)") + QStringLiteral(";;") + FileDialog::tr("Documents (%1)"); - if(signature) - filter = filter.arg(QStringLiteral("*.bdoc *.ddoc *.asice *.sce *.asics *.scs *.edoc *.adoc%1") - .arg(Application::confValue(Application::SiVaUrl).toString().isEmpty() ? QLatin1String() : QLatin1String(" *.pdf"))); - else - filter = filter.arg(QLatin1String("*.cdoc *.cdoc2")); - QStringList files = FileDialog::getOpenFileNames(this, tr("Select documents"), {}, filter); + QStringList files = FileDialog::getOpenFileNames(this, tr("Select documents"), {}, + QFileDialog::tr("All Files (*)") + QStringLiteral(";;") + FileDialog::tr("Documents (%1)").arg(filter)); if(!files.isEmpty()) openFiles(std::move(files)); } @@ -545,9 +550,9 @@ void MainWindow::showSettings(int page) settings->show(); return; } - SettingsDialog dlg(page, this); - connect(&dlg, &SettingsDialog::togglePrinting, ui->signContainerPage, &ContainerPage::togglePrinting); - dlg.exec(); + auto *dlg = new SettingsDialog(page, this); + connect(dlg, &SettingsDialog::togglePrinting, ui->signContainerPage, &ContainerPage::togglePrinting); + dlg->open(); } template @@ -611,18 +616,6 @@ bool MainWindow::wrap(const QString& wrappedFile, bool pdf) return true; } -bool MainWindow::wrapContainer(bool signing) -{ - return WarningDialog::create(this) - ->withTitle(signing ? tr("Files can not be added to the signed container") : tr("Files can not be added to the cryptocontainer")) - ->withText(signing ? - tr("The system will create a new container which shall contain the signed document and the files you wish to add.") : - tr("The system will create a new container which shall contain the cypto-document and the files you wish to add.")) - ->setCancelText(WarningDialog::Cancel) - ->addButton(tr("Continue"), QMessageBox::Ok) - ->exec() == QMessageBox::Ok; -} - void MainWindow::updateMyEID(const TokenData &t) { updateSelector(); @@ -632,7 +625,6 @@ void MainWindow::updateMyEID(const TokenData &t) SslCertificate cert(t.cert()); auto type = cert.type(); ui->infoStack->setHidden(type == SslCertificate::UnknownType); - ui->accordion->setHidden(type == SslCertificate::UnknownType); ui->noReaderInfo->setVisible(type == SslCertificate::UnknownType); auto setText = [this](const char *text) { @@ -642,18 +634,12 @@ void MainWindow::updateMyEID(const TokenData &t) if(!t.isNull()) { setText(QT_TR_NOOP("The card in the card reader is not an Estonian ID-card")); - if(ui->cardInfo->token().card() != t.card()) - ui->accordion->clear(); if(type & SslCertificate::TempelType) - { ui->infoStack->update(cert); - ui->accordion->updateInfo(cert); - } } else { ui->infoStack->clearData(); - ui->accordion->clear(); setText(QT_TR_NOOP("Connect the card reader to your computer and insert your ID card into the reader")); } } @@ -661,7 +647,6 @@ void MainWindow::updateMyEID(const TokenData &t) void MainWindow::updateMyEid(const QSmartCardData &data) { ui->infoStack->update(data); - ui->accordion->updateInfo(data); ui->myEid->warningIcon(false); ui->myEid->invalidIcon(false); ui->warnings->closeWarnings(MyEid); diff --git a/client/MainWindow.h b/client/MainWindow.h index 39de44426..2ac313310 100644 --- a/client/MainWindow.h +++ b/client/MainWindow.h @@ -66,7 +66,7 @@ class MainWindow final : public QWidget void navigateToPage(Pages page, const QStringList &files = QStringList(), bool create = true); void onCryptoAction(int action, const QString &id, const QString &phone); void onSignAction(int action, const QString &idCode, const QString &info2); - void openContainer(bool signature); + void openContainer(const QString &filter); void resetDigiDoc(std::unique_ptr &&doc); template void sign(F &&sign); @@ -74,7 +74,6 @@ class MainWindow final : public QWidget void updateMyEID(const TokenData &t); void updateMyEid(const QSmartCardData &data); bool wrap(const QString& wrappedFile, bool pdf); - bool wrapContainer(bool signing); static QStringList dropEventFiles(QDropEvent *event); diff --git a/client/MainWindow.ui b/client/MainWindow.ui index 480ccb97c..0ae7af938 100644 --- a/client/MainWindow.ui +++ b/client/MainWindow.ui @@ -6,14 +6,14 @@ 0 0 - 1025 - 600 + 1280 + 710 - 1024 - 600 + 1280 + 710 @@ -683,10 +683,7 @@ color: #BFD3E8; 0 - - - - + @@ -790,14 +787,9 @@ color: #BFD3E8; 1 - InfoStack - QWidget -
widgets/InfoStack.h
-
- - Accordion + MyEidInfo QWidget -
widgets/Accordion.h
+
widgets/MyEidInfo.h
NoCardInfo diff --git a/client/common_enums.h b/client/common_enums.h index a6f4b2342..7ed4422b8 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -38,6 +38,7 @@ enum ContainerState : unsigned char { enum Actions : unsigned char { ContainerClose, ContainerCancel, + ContainerClearWarning, ContainerConvert, ContainerEncrypt, @@ -51,8 +52,6 @@ enum Actions : unsigned char { SignatureMobile, SignatureSmartID, SignatureToken, - ClearSignatureWarning, - ClearCryptoWarning, EncryptLT }; diff --git a/client/dialogs/SettingsDialog.cpp b/client/dialogs/SettingsDialog.cpp index 306e20c6c..30793afdd 100644 --- a/client/dialogs/SettingsDialog.cpp +++ b/client/dialogs/SettingsDialog.cpp @@ -65,6 +65,7 @@ SettingsDialog::SettingsDialog(int page, QWidget *parent) ui->setupUi(this); setWindowFlag(Qt::FramelessWindowHint); + setAttribute(Qt::WA_DeleteOnClose, true); move(parent->geometry().center() - geometry().center()); for(QLineEdit *w: findChildren()) w->setAttribute(Qt::WA_MacShowFocusRect, false); diff --git a/client/widgets/Accordion.cpp b/client/widgets/Accordion.cpp deleted file mode 100644 index 2c23eeab3..000000000 --- a/client/widgets/Accordion.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "Accordion.h" -#include "ui_Accordion.h" - -Accordion::Accordion(QWidget *parent) - : QWidget(parent) - , ui(new Ui::Accordion) -{ - ui->setupUi( this ); - connect(ui->titleVerifyCert, &AccordionTitle::toggled, ui->contentVerifyCert, &QWidget::setVisible); - connect(ui->authBox, &VerifyCert::changePinClicked, this, [this](QSmartCard::PinAction action) { - emit changePinClicked(QSmartCardData::Pin1Type, action); - }); - connect(ui->signBox, &VerifyCert::changePinClicked, this,[this](QSmartCard::PinAction action) { - emit changePinClicked(QSmartCardData::Pin2Type, action); - }); - connect(ui->pukBox, &VerifyCert::changePinClicked, this, [this](QSmartCard::PinAction action) { - emit changePinClicked(QSmartCardData::PukType, action); - }); - clear(); -} - -Accordion::~Accordion() -{ - delete ui; -} - -void Accordion::clear() -{ - ui->authBox->clear(); - ui->signBox->clear(); - ui->pukBox->clear(); - ui->titleVerifyCert->setChecked(true); -} - -void Accordion::updateInfo(const SslCertificate &c) -{ - clear(); - bool isSign = c.keyUsage().contains(SslCertificate::NonRepudiation); - ui->authBox->setHidden(isSign); - ui->signBox->setVisible(isSign); - if(isSign) - ui->signBox->update(QSmartCardData::Pin2Type, c); - else - ui->authBox->update(QSmartCardData::Pin1Type, c); - - ui->pukBox->hide(); -} - -void Accordion::updateInfo(const QSmartCardData &data) -{ - if(data.isNull()) - return clear(); - ui->authBox->setVisible(!data.authCert().isNull()); - if (!data.authCert().isNull()) - ui->authBox->update(QSmartCardData::Pin1Type, data); - - ui->signBox->setVisible(!data.signCert().isNull()); - if (!data.signCert().isNull()) - ui->signBox->update(QSmartCardData::Pin2Type, data); - - ui->pukBox->show(); - ui->pukBox->update(QSmartCardData::PukType, data); -} - -void Accordion::changeEvent(QEvent* event) -{ - if (event->type() == QEvent::LanguageChange) - ui->retranslateUi(this); - QWidget::changeEvent(event); -} diff --git a/client/widgets/Accordion.h b/client/widgets/Accordion.h deleted file mode 100644 index 83cf26454..000000000 --- a/client/widgets/Accordion.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#pragma once - -#include - -#include "QSmartCard.h" - -class SslCertificate; - -namespace Ui { -class Accordion; -} - -class AccordionTitle; - -class Accordion final : public QWidget -{ - Q_OBJECT - -public: - explicit Accordion( QWidget *parent = nullptr ); - ~Accordion() final; - - void clear(); - void updateInfo(const SslCertificate &info); - void updateInfo(const QSmartCardData &data); - -Q_SIGNALS: - void changePinClicked(QSmartCardData::PinType, QSmartCard::PinAction); - -private: - void changeEvent(QEvent* event) override; - - Ui::Accordion *ui; -}; diff --git a/client/widgets/Accordion.ui b/client/widgets/Accordion.ui deleted file mode 100644 index 8c70254c0..000000000 --- a/client/widgets/Accordion.ui +++ /dev/null @@ -1,115 +0,0 @@ - - - Accordion - - - - 0 - 0 - 554 - 780 - - - - #titleVerifyCert { -border-top: 1px solid #E7EAEF; -border-bottom: 1px solid #E7EAEF; -} -#authBox, #signBox { -border-right: 1px solid #E7EAEF; -} - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 40 - - - - PointingHandCursor - - - PIN/PUK codes and certificates - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - AccordionTitle - QCheckBox -
widgets/AccordionTitle.h
- 1 -
- - VerifyCert - QWidget -
widgets/VerifyCert.h
- 1 -
-
- - -
diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 6b5d36628..d6c8cd0ee 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -361,7 +361,7 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert }); disconnect(container, &CryptoDoc::destroyed, this, nullptr); connect(container, &CryptoDoc::destroyed, this, [this] { - clear(ClearCryptoWarning); + clear(ContainerClearWarning); }); disconnect(mainAction, &MainAction::action, container, nullptr); connect(mainAction, &MainAction::action, container, [container, this](int action) { @@ -382,7 +382,7 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert } }); - clear(ClearCryptoWarning); + clear(ContainerClearWarning); isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); setHeader(container->fileName()); ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Encrypted files")); @@ -481,10 +481,10 @@ void ContainerPage::transition(DigiDoc* container) }); disconnect(container, &DigiDoc::destroyed, this, nullptr); connect(container, &DigiDoc::destroyed, this, [this] { - clear(ClearSignatureWarning); + clear(ContainerClearWarning); }); - clear(ClearSignatureWarning); + clear(ContainerClearWarning); std::map errors; setHeader(container->fileName()); ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Container files")); diff --git a/client/widgets/InfoStack.ui b/client/widgets/InfoStack.ui deleted file mode 100644 index b326ba138..000000000 --- a/client/widgets/InfoStack.ui +++ /dev/null @@ -1,248 +0,0 @@ - - - InfoStack - - - - 0 - 0 - 882 - 186 - - - - - 0 - 186 - - - - - 16777215 - 186 - - - - QLabel { -color: #607496; -font-family: Roboto, Helvetica; -font-size: 12px; -font-weight: 400; -} -#valueGivenNames, #valueSurname, -#valuePersonalCode, #valueCitizenship, -#valueDocument, #valueExpiryDate { -color: #003168; -font-size: 14px; -font-weight: 700; -} -#valueExpiryDate[label="error"] { -padding: 2px 10px; -border-radius: 8px; -color: #AD2A45; -background: #F5EBED; -font-weight: 400; -} -#valueExpiryDate[label="good"] { -padding: 2px 10px; -border-radius: 8px; -color: #1A641B; -background: #EAF8EA; -font-weight: 400; -} - - - - 20 - - - 16 - - - 20 - - - 20 - - - 40 - - - 0 - - - - - Qt::TabFocus - - - Given names - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - Qt::TabFocus - - - Surname - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - Qt::TabFocus - - - valueGivenNames - - - true - - - - - - - Qt::TabFocus - - - valueSurname - - - - - - - Qt::TabFocus - - - Personal code - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - Qt::TabFocus - - - Citizenship - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - Qt::TabFocus - - - valuePersonalCode - - - - - - - Qt::TabFocus - - - valueCitizenship - - - - - - - Qt::TabFocus - - - Expiry date - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - Qt::TabFocus - - - Document - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - - 0 - 0 - - - - Qt::TabFocus - - - valueExpiryDate - - - good - - - - - - - Qt::TabFocus - - - valueSerialNumber - - - - - - - - Label - QLabel -
widgets/Label.h
-
-
- - labelGivenNames - valueGivenNames - labelSurname - valueSurname - labelPersonalCode - valuePersonalCode - labelCitizenship - valueCitizenship - labelExpiryDate - valueExpiryDate - labelDocument - valueDocument - - - -
diff --git a/client/widgets/Label.cpp b/client/widgets/Label.cpp index 75cc4f432..aa80b70a2 100644 --- a/client/widgets/Label.cpp +++ b/client/widgets/Label.cpp @@ -19,8 +19,12 @@ #include "Label.h" +#include +#include #include +#include + Label::Label(QWidget *parent) : QLabel(parent) {} @@ -35,6 +39,104 @@ void Label::setLabel(QString label) if (label == _label) return; _label = std::move(label); + naturalWidth = -1; parentWidget()->style()->unpolish(this); parentWidget()->style()->polish(this); + updateFit(); +} + +bool Label::fitToParentWidth() const +{ + return fitParentWidth; +} + +void Label::setFitToParentWidth(bool enabled) +{ + if(fitParentWidth == enabled) + return; + fitParentWidth = enabled; + updateParentEventFilter(); + updateFit(); +} + +int Label::wrapAtWidth() const +{ + return maximumWrapWidth; +} + +void Label::setWrapAtWidth(int width) +{ + width = std::max(0, width); + if(maximumWrapWidth == width) + return; + maximumWrapWidth = width; + updateParentEventFilter(); + updateFit(); +} + +void Label::fitToWidth(int availableWidth) +{ + const bool previousWrap = wordWrap(); + if(naturalWidth < 0 || measuredText != text()) + { + setMinimumWidth(0); + setMaximumWidth(QWIDGETSIZE_MAX); + setWordWrap(false); + ensurePolished(); + naturalWidth = QLabel::sizeHint().width(); + measuredText = text(); + } + + const bool fits = naturalWidth <= availableWidth; + setFixedWidth(fits ? naturalWidth : availableWidth); + const bool wrap = !fits; + setWordWrap(wrap); + if(previousWrap != wrap) + emit wordWrapChanged(wrap); +} + +void Label::changeEvent(QEvent *event) +{ + const bool sizeChanged = event->type() == QEvent::FontChange || event->type() == QEvent::StyleChange; + if(sizeChanged) + naturalWidth = -1; + QLabel::changeEvent(event); + if(sizeChanged) + updateFit(); +} + +bool Label::eventFilter(QObject *watched, QEvent *event) +{ + if(watched == parentWidget() && + (event->type() == QEvent::Resize || event->type() == QEvent::LayoutRequest)) + updateFit(); + return QLabel::eventFilter(watched, event); +} + +void Label::updateFit() +{ + QWidget *parent = parentWidget(); + QLayout *layout = parent ? parent->layout() : nullptr; + if(text().isEmpty()) + return; + + int availableWidth = maximumWrapWidth; + if(fitParentWidth && layout) + { + const QMargins margins = layout->contentsMargins(); + const int parentWidth = parent->contentsRect().width() - margins.left() - margins.right(); + availableWidth = availableWidth > 0 ? std::min(availableWidth, parentWidth) : parentWidth; + } + if(availableWidth > 0) + fitToWidth(availableWidth); +} + +void Label::updateParentEventFilter() +{ + if(!parentWidget()) + return; + if(fitParentWidth || maximumWrapWidth > 0) + parentWidget()->installEventFilter(this); + else + parentWidget()->removeEventFilter(this); } diff --git a/client/widgets/Label.h b/client/widgets/Label.h index 0a83086eb..b06caa755 100644 --- a/client/widgets/Label.h +++ b/client/widgets/Label.h @@ -25,12 +25,33 @@ class Label : public QLabel { Q_OBJECT public: Q_PROPERTY(QString label READ label WRITE setLabel FINAL) + Q_PROPERTY(bool fitToParentWidth READ fitToParentWidth WRITE setFitToParentWidth FINAL) + Q_PROPERTY(int wrapAtWidth READ wrapAtWidth WRITE setWrapAtWidth FINAL) explicit Label(QWidget *parent = {}); QString label() const; - void setLabel(QString _label); + void setLabel(QString label); + bool fitToParentWidth() const; + void setFitToParentWidth(bool enabled); + int wrapAtWidth() const; + void setWrapAtWidth(int width); + +signals: + void wordWrapChanged(bool wordWrap); + +protected: + void changeEvent(QEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; private: + void fitToWidth(int availableWidth); + void updateFit(); + void updateParentEventFilter(); + QString _label; + QString measuredText; + int naturalWidth = -1; + int maximumWrapWidth = 0; + bool fitParentWidth = false; }; diff --git a/client/widgets/InfoStack.cpp b/client/widgets/MyEidInfo.cpp similarity index 67% rename from client/widgets/InfoStack.cpp rename to client/widgets/MyEidInfo.cpp index d182057df..45eaea5d8 100644 --- a/client/widgets/InfoStack.cpp +++ b/client/widgets/MyEidInfo.cpp @@ -17,28 +17,36 @@ * */ -#include "InfoStack.h" -#include "ui_InfoStack.h" +#include "MyEidInfo.h" +#include "ui_MyEidInfo.h" -#include "QSmartCard.h" #include "SslCertificate.h" #include -InfoStack::InfoStack( QWidget *parent ) +MyEidInfo::MyEidInfo( QWidget *parent ) : QWidget(parent) - , ui( new Ui::InfoStack ) + , ui(new Ui::MyEidInfo) { ui->setupUi( this ); + connect(ui->authBox, &VerifyCert::changePinClicked, this, [this](QSmartCard::PinAction action) { + emit changePinClicked(QSmartCardData::Pin1Type, action); + }); + connect(ui->signBox, &VerifyCert::changePinClicked, this,[this](QSmartCard::PinAction action) { + emit changePinClicked(QSmartCardData::Pin2Type, action); + }); + connect(ui->pukBox, &VerifyCert::changePinClicked, this, [this](QSmartCard::PinAction action) { + emit changePinClicked(QSmartCardData::PukType, action); + }); clearData(); } -InfoStack::~InfoStack() +MyEidInfo::~MyEidInfo() { delete ui; } -void InfoStack::clearData() +void MyEidInfo::clearData() { certType = 0; expiry = {}; @@ -48,9 +56,12 @@ void InfoStack::clearData() ui->valueCitizenship->clear(); ui->valueExpiryDate->clear(); ui->valueDocument->clear(); + ui->authBox->clear(); + ui->signBox->clear(); + ui->pukBox->clear(); } -void InfoStack::changeEvent(QEvent* event) +void MyEidInfo::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { @@ -61,7 +72,7 @@ void InfoStack::changeEvent(QEvent* event) QWidget::changeEvent(event); } -void InfoStack::update() +void MyEidInfo::update() { ui->valueExpiryDate->setText(expiry.toString(QStringLiteral("dd.MM.yyyy"))); if(certType & SslCertificate::DigiIDType) @@ -92,19 +103,28 @@ void InfoStack::update() } } -void InfoStack::update(const SslCertificate &cert) +void MyEidInfo::update(const SslCertificate &cert) { certType = cert.type(); expiry = cert.expiryDate(); + bool isSign = cert.keyUsage().contains(SslCertificate::NonRepudiation); ui->valueGivenNames->setText(cert.toString(QStringLiteral("CN"))); ui->valueSurname->setText(cert.toString(QStringLiteral("O"))); ui->valuePersonalCode->setText(cert.personalCode()); ui->valueCitizenship->setText(cert.toString(QStringLiteral("C"))); ui->valueDocument->clear(); + ui->authBox->setHidden(isSign); + ui->signBox->setVisible(isSign); + if(isSign) + ui->signBox->update(QSmartCardData::Pin2Type, cert); + else + ui->authBox->update(QSmartCardData::Pin1Type, cert); + ui->pukBox->hide(); + update(); } -void InfoStack::update(const QSmartCardData &t) +void MyEidInfo::update(const QSmartCardData &t) { if(t.isNull()) return clearData(); @@ -115,5 +135,17 @@ void InfoStack::update(const QSmartCardData &t) ui->valuePersonalCode->setText(t.data(QSmartCardData::Id).toString()); ui->valueCitizenship->setText(t.data(QSmartCardData::Citizen).toString()); ui->valueDocument->setText(t.data(QSmartCardData::DocumentId).toString()); + + ui->authBox->setVisible(!t.authCert().isNull()); + if(!t.authCert().isNull()) + ui->authBox->update(QSmartCardData::Pin1Type, t); + + ui->signBox->setVisible(!t.signCert().isNull()); + if(!t.signCert().isNull()) + ui->signBox->update(QSmartCardData::Pin2Type, t); + + ui->pukBox->show(); + ui->pukBox->update(QSmartCardData::PukType, t); + update(); } diff --git a/client/widgets/InfoStack.h b/client/widgets/MyEidInfo.h similarity index 81% rename from client/widgets/InfoStack.h rename to client/widgets/MyEidInfo.h index 7c5442c19..618acf767 100644 --- a/client/widgets/InfoStack.h +++ b/client/widgets/MyEidInfo.h @@ -19,34 +19,39 @@ #pragma once +#include "QSmartCard.h" + #include #include namespace Ui { -class InfoStack; +class MyEidInfo; } class SslCertificate; class QSmartCardData; -class InfoStack final: public QWidget +class MyEidInfo final: public QWidget { Q_OBJECT public: - explicit InfoStack( QWidget *parent = nullptr ); - ~InfoStack() final; + explicit MyEidInfo( QWidget *parent = nullptr ); + ~MyEidInfo() final; void clearData(); void update(const SslCertificate &cert); void update(const QSmartCardData &t); +Q_SIGNALS: + void changePinClicked(QSmartCardData::PinType, QSmartCard::PinAction); + private: void changeEvent(QEvent* event) final; void update(); - Ui::InfoStack *ui; + Ui::MyEidInfo *ui; QDateTime expiry; int certType = 0; diff --git a/client/widgets/MyEidInfo.ui b/client/widgets/MyEidInfo.ui new file mode 100644 index 000000000..ba356c179 --- /dev/null +++ b/client/widgets/MyEidInfo.ui @@ -0,0 +1,300 @@ + + + MyEidInfo + + + + 0 + 0 + 882 + 387 + + + + #authBox, #signBox, #pukBox { +border: 1px solid #E7EAEF; +border-radius: 8px; +} + + + + 24 + + + 24 + + + 24 + + + 26 + + + 24 + + + 40 + + + + + QLabel { +color: #607496; +font-family: Roboto, Helvetica; +font-size: 12px; +font-weight: 400; +} +#valueGivenNames, #valueSurname, +#valuePersonalCode, #valueCitizenship, +#valueDocument, #valueExpiryDate { +color: #003168; +font-size: 14px; +font-weight: 700; +} +#valueExpiryDate[label="error"] { +padding: 2px 10px; +border-radius: 8px; +color: #AD2A45; +background: #F5EBED; +font-weight: 400; +} +#valueExpiryDate[label="good"] { +padding: 2px 10px; +border-radius: 8px; +color: #1A641B; +background: #EAF8EA; +font-weight: 400; +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 56 + + + 32 + + + + + 2 + + + + + Qt::TabFocus + + + Given names + + + + + + + 392 + + + Qt::TabFocus + + + valueGivenNames + + + true + + + + + + + + + 2 + + + + + Qt::TabFocus + + + Surname + + + + + + + 392 + + + Qt::TabFocus + + + valueSurname + + + true + + + + + + + + + 2 + + + + + Qt::TabFocus + + + Personal code + + + + + + + Qt::TabFocus + + + valuePersonalCode + + + + + + + + + 2 + + + + + Qt::TabFocus + + + Citizenship + + + + + + + Qt::TabFocus + + + valueCitizenship + + + + + + + + + 2 + + + + + Qt::TabFocus + + + Expiry date + + + + + + + + 0 + 0 + + + + Qt::TabFocus + + + valueExpiryDate + + + good + + + + + + + + + 2 + + + + + Qt::TabFocus + + + Document + + + + + + + Qt::TabFocus + + + valueSerialNumber + + + + + + + + + + + + + + + + + + + + + + Label + QLabel +
widgets/Label.h
+
+ + VerifyCert + QWidget +
widgets/VerifyCert.h
+ 1 +
+
+ + +
diff --git a/client/widgets/VerifyCert.cpp b/client/widgets/VerifyCert.cpp index 29b95ff94..601afa7ff 100644 --- a/client/widgets/VerifyCert.cpp +++ b/client/widgets/VerifyCert.cpp @@ -30,6 +30,12 @@ VerifyCert::VerifyCert(QWidget *parent) , ui(new Ui::VerifyCert) { ui->setupUi( this ); + connect(ui->info, &Label::wordWrapChanged, this, [this](bool wordWrap) { + Qt::Alignment alignment = Qt::AlignTop; + if(!wordWrap) + alignment |= Qt::AlignLeft; + ui->nameLayout->setAlignment(ui->info, alignment); + }); connect(ui->changePIN, &QToolButton::clicked, this, [this] { if(cardData.retryCount(pinType) == 0 && cardData.pinLocked(pinType)) diff --git a/client/widgets/VerifyCert.ui b/client/widgets/VerifyCert.ui index 53b1c5e20..7557fa12c 100644 --- a/client/widgets/VerifyCert.ui +++ b/client/widgets/VerifyCert.ui @@ -7,15 +7,9 @@ 0 0 305 - 264 + 342 - - - 305 - 0 - - #name { color: #003168; @@ -26,7 +20,7 @@ font-weight: 700; #info, #validUntil { color: #091A36; font-family: Roboto, Helvetica; -font-size: 12px; +font-size: 14px; padding: 2px 10px; border-radius: 8px; background-color: #F3F5F7; @@ -85,158 +79,114 @@ background-color: #EAF1F8; background-color: #BFD3E8; } - + - 5 + 32 - 5 + 32 - 20 + 32 - 5 + 32 - 20 + 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - - - - - - - Qt::TabFocus - - - name - - - Qt::RichText - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 0 - 0 - - - - - - - - - - Qt::TabFocus - - - Certificate is valid until %1 - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - + + - 20 - 6 + 0 + 92 - - - - - - Qt::TabFocus - - - PIN%1 has been blocked because PIN%1 code has been entered incorrectly 3 times. - - - Qt::AlignCenter - - - true - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - + + + 0 + + + 0 + + + 0 + + + 0 + + + 4 + + + 12 + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + + + + Qt::TabFocus + + + name + + + Qt::RichText + + + + + + + Qt::TabFocus + + + Certificate is valid until %1 + + + + + + + Qt::TabFocus + + + PIN%1 has been blocked because PIN%1 code has been entered incorrectly 3 times. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + true + + + + - + PointingHandCursor @@ -249,36 +199,11 @@ background-color: #BFD3E8; - - - - Qt::Vertical - - - - 20 - 6 - - - - - - - 0 - 57 - - - - - 16777215 - 57 - - - 0 + 15 0 @@ -292,7 +217,7 @@ background-color: #BFD3E8; 0 - + PointingHandCursor @@ -302,7 +227,7 @@ background-color: #BFD3E8; - + PointingHandCursor @@ -312,7 +237,7 @@ background-color: #BFD3E8; - + PointingHandCursor @@ -325,6 +250,19 @@ background-color: #BFD3E8; + + + + Qt::Vertical + + + + 20 + 32 + + + +
From 903cf8c977dce157ac13616c0312aeed60f2d8a7 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 14 Jul 2026 12:07:50 +0300 Subject: [PATCH 3/5] New signing dialog IB-7974 Signed-off-by: Raul Metsma --- client/Application.cpp | 1 + client/CDocSupport.cpp | 7 +- client/CMakeLists.txt | 3 +- client/CheckConnection.cpp | 2 +- client/MainWindow.cpp | 58 +- client/MainWindow.h | 7 +- client/Utils.h | 1 + client/common_enums.h | 6 +- client/dialogs/MobileDialog.cpp | 105 --- client/dialogs/MobileDialog.h | 40 - client/dialogs/MobileDialog.ui | 318 -------- client/dialogs/MobileProgress.ui | 179 ----- client/dialogs/SigningDialog.cpp | 283 +++++++ client/dialogs/SigningDialog.h | 34 + client/dialogs/SigningDialog.ui | 710 ++++++++++++++++++ client/dialogs/SmartIDDialog.cpp | 98 --- client/dialogs/SmartIDDialog.h | 39 - client/dialogs/SmartIDDialog.ui | 355 --------- client/{dialogs => signer}/MobileProgress.cpp | 79 +- client/{dialogs => signer}/MobileProgress.h | 14 +- client/{ => signer}/QSigner.cpp | 0 client/{ => signer}/QSigner.h | 0 .../{dialogs => signer}/SmartIDProgress.cpp | 96 +-- client/{dialogs => signer}/SmartIDProgress.h | 6 +- client/translations/en.ts | 175 ++--- client/translations/et.ts | 177 ++--- client/widgets/CardListItem.cpp | 82 ++ client/widgets/CardListItem.h | 33 + client/widgets/CardListItem.ui | 151 ++++ client/widgets/ContainerPage.cpp | 120 +-- client/widgets/ContainerPage.h | 8 +- client/widgets/MainAction.cpp | 154 +--- client/widgets/MainAction.h | 15 +- client/widgets/MainAction.ui | 130 ---- client/widgets/ShrinkingStack.h | 17 + 35 files changed, 1616 insertions(+), 1887 deletions(-) delete mode 100644 client/dialogs/MobileDialog.cpp delete mode 100644 client/dialogs/MobileDialog.h delete mode 100644 client/dialogs/MobileDialog.ui delete mode 100644 client/dialogs/MobileProgress.ui create mode 100644 client/dialogs/SigningDialog.cpp create mode 100644 client/dialogs/SigningDialog.h create mode 100644 client/dialogs/SigningDialog.ui delete mode 100644 client/dialogs/SmartIDDialog.cpp delete mode 100644 client/dialogs/SmartIDDialog.h delete mode 100644 client/dialogs/SmartIDDialog.ui rename client/{dialogs => signer}/MobileProgress.cpp (85%) rename client/{dialogs => signer}/MobileProgress.h (81%) rename client/{ => signer}/QSigner.cpp (100%) rename client/{ => signer}/QSigner.h (100%) rename client/{dialogs => signer}/SmartIDProgress.cpp (83%) rename client/{dialogs => signer}/SmartIDProgress.h (93%) create mode 100644 client/widgets/CardListItem.cpp create mode 100644 client/widgets/CardListItem.h create mode 100644 client/widgets/CardListItem.ui delete mode 100644 client/widgets/MainAction.ui create mode 100644 client/widgets/ShrinkingStack.h diff --git a/client/Application.cpp b/client/Application.cpp index 75f246650..66781ff28 100644 --- a/client/Application.cpp +++ b/client/Application.cpp @@ -432,6 +432,7 @@ Application::Application( int &argc, char **argv ) // Clear obsolete registriy settings Settings::CDOC2_NOTIFICATION.clear(); + Settings::MOBILEID_ORDER.clear(); #ifndef Q_OS_DARWIN Settings::DEFAULT_DIR.clear(); #endif diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 8cd01aede..0dfe46625 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -196,12 +196,13 @@ DDCryptoBackend::getLastErrorStr(libcdoc::result_t code) const static bool checkConnection() { - if(CheckConnection().check()) { + CheckConnection check; + if(check.check()) { return true; } - return dispatchToMain([] { + return dispatchToMain([check] { FadeInNotification::error(Application::mainWindow()->findChild(QStringLiteral("topBar")), - QCoreApplication::translate("MainWindow", "Check internet connection")); + check.errorString()); return false; }); } diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 475f57341..668a58961 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(libcdoc EXCLUDE_FROM_ALL) file(GLOB WIDGETS CONFIGURE_DEPENDS "dialogs/*.cpp" "dialogs/*.h" "dialogs/*.ui" "effects/*.cpp" "effects/*.h" "effects/*.ui" + "signer/*.cpp" "signer/*.h" "widgets/*.cpp" "widgets/*.h" "widgets/*.ui" ) add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE @@ -66,8 +67,6 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE QPCSC.h QPKCS11.cpp QPKCS11.h - QSigner.cpp - QSigner.h QSmartCard.cpp QSmartCard_p.h QSmartCard.h diff --git a/client/CheckConnection.cpp b/client/CheckConnection.cpp index ee6da2d30..ee8cd7d48 100644 --- a/client/CheckConnection.cpp +++ b/client/CheckConnection.cpp @@ -57,7 +57,7 @@ QString CheckConnection::errorString() const case QNetworkReply::ProxyAuthenticationRequiredError: return QCoreApplication::translate("CheckConnection", "Check proxy username and password"); default: - return QCoreApplication::translate("CheckConnection", "Cannot connect to certificate status service!"); + return QCoreApplication::translate("CheckConnection", "Check internet connection"); } } diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index d44981ce9..72a209830 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -26,17 +26,15 @@ #include "DigiDoc.h" #include "QCryptoBackend.h" #include "QPCSC.h" -#include "QSigner.h" #include "Settings.h" #include "SslCertificate.h" #include "TokenData.h" #include "effects/FadeInNotification.h" #include "effects/Overlay.h" #include "dialogs/FileDialog.h" -#include "dialogs/MobileProgress.h" #include "dialogs/RoleAddressDialog.h" #include "dialogs/SettingsDialog.h" -#include "dialogs/SmartIDProgress.h" +#include "dialogs/SigningDialog.h" #include "dialogs/WaitDialog.h" #include "dialogs/WarningDialog.h" #include "widgets/CardPopup.h" @@ -221,7 +219,6 @@ void MainWindow::mouseReleaseEvent(QMouseEvent *event) { if(auto *cardPopup = findChild()) cardPopup->deleteLater(); - ui->signContainerPage->clearPopups(); QWidget::mouseReleaseEvent(event); } @@ -286,30 +283,12 @@ void MainWindow::navigateToPage( Pages page, const QStringList &files, bool crea selectPage(page); } -void MainWindow::onSignAction(int action, const QString &idCode, const QString &info2) +void MainWindow::onSignAction(int action) { switch(action) { case SignatureAdd: - case SignatureToken: - sign([this](const QString &city, const QString &state, const QString &zip, const QString &country, const QString &role) { - QSigner signer(qApp->cryptoManager()->tokensign()); - return digiDoc->sign(city, state, zip, country, role, &signer); - }); - break; - case SignatureMobile: - sign([this, idCode, info2](const QString &city, const QString &state, const QString &zip, const QString &country, const QString &role) { - MobileProgress m(this); - return m.init(idCode, info2) && - digiDoc->sign(city, state, zip, country, role, &m); - }); - break; - case SignatureSmartID: - sign([this, idCode, info2](const QString &city, const QString &state, const QString &zip, const QString &country, const QString &role) { - SmartIDProgress s(this); - return s.init(info2, idCode, digiDoc->fileName()) && - digiDoc->sign(city, state, zip, country, role, &s); - }); + sign(); break; case ContainerClearWarning: ui->signature->warningIcon(false); @@ -362,7 +341,7 @@ void MainWindow::convertToCDoc() FadeInNotification::success(ui->topBar, tr("Converted to crypto container!")); } -void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString &/*phone*/) +void MainWindow::onCryptoAction(int action) { switch(action) { @@ -555,12 +534,11 @@ void MainWindow::showSettings(int page) dlg->open(); } -template -void MainWindow::sign(F &&sign) +void MainWindow::sign() { - if(!CheckConnection().check()) + if(CheckConnection check; !check.check()) { - FadeInNotification::error(ui->topBar, tr("Check internet connection")); + FadeInNotification::error(ui->topBar, check.errorString()); return; } @@ -568,28 +546,34 @@ void MainWindow::sign(F &&sign) if(RoleAddressDialog(this).get(city, country, state, zip, role) == QDialog::Rejected) return; - WaitDialogHolder waitDialog(this, tr("Signing")); if(digiDoc->isPDF()) { - QString wrappedFile = digiDoc->fileName(); - if(!wrap(wrappedFile, true)) + QString pdfFile = digiDoc->fileName(); + if(!wrap(pdfFile, true)) return; - if(!sign(city, state, zip, country, role)) + SigningDialog dlg(digiDoc.get(), ui->signContainerPage); + dlg.setRoleAddress(city, country, state, zip, role); + if(dlg.exec() != QDialog::Accepted) { digiDoc.reset(); - openFiles({std::move(wrappedFile)}); + openFiles({std::move(pdfFile)}); return; } } - else if(!sign(city, state, zip, country, role)) - return; + else + { + SigningDialog dlg(digiDoc.get(), ui->signContainerPage); + dlg.setRoleAddress(city, country, state, zip, role); + if(dlg.exec() != QDialog::Accepted) + return; + } + WaitDialogHolder waitDialog(this, tr("Signing")); if(!digiDoc->save()) return; ui->signContainerPage->transition(digiDoc.get()); - FadeInNotification::success(ui->topBar, tr("The container has been successfully signed!")); } diff --git a/client/MainWindow.h b/client/MainWindow.h index 2ac313310..b8c9e296d 100644 --- a/client/MainWindow.h +++ b/client/MainWindow.h @@ -64,12 +64,11 @@ class MainWindow final : public QWidget void changePinClicked(QSmartCardData::PinType type, QSmartCard::PinAction action); void convertToCDoc(); void navigateToPage(Pages page, const QStringList &files = QStringList(), bool create = true); - void onCryptoAction(int action, const QString &id, const QString &phone); - void onSignAction(int action, const QString &idCode, const QString &info2); + void onCryptoAction(int action); + void onSignAction(int action); void openContainer(const QString &filter); void resetDigiDoc(std::unique_ptr &&doc); - template - void sign(F &&sign); + void sign(); void updateSelector(); void updateMyEID(const TokenData &t); void updateMyEid(const QSmartCardData &data); diff --git a/client/Utils.h b/client/Utils.h index c8c317fe7..035a953cd 100644 --- a/client/Utils.h +++ b/client/Utils.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include diff --git a/client/common_enums.h b/client/common_enums.h index 7ed4422b8..ae8d733ab 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -43,16 +43,12 @@ enum Actions : unsigned char { ContainerEncrypt, EncryptContainer, + EncryptLT, EncryptContainerSuccess, DecryptContainer, - DecryptToken, DecryptContainerSuccess, SignatureAdd, - SignatureMobile, - SignatureSmartID, - SignatureToken, - EncryptLT }; } diff --git a/client/dialogs/MobileDialog.cpp b/client/dialogs/MobileDialog.cpp deleted file mode 100644 index b19606d01..000000000 --- a/client/dialogs/MobileDialog.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "MobileDialog.h" -#include "ui_MobileDialog.h" - -#include "IKValidator.h" -#include "Settings.h" -#include "effects/Overlay.h" - -MobileDialog::MobileDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::MobileDialog) -{ - static const QStringList countryCodes {QStringLiteral("372"), QStringLiteral("370")}; - new Overlay(this); - - ui->setupUi(this); - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); -#ifdef Q_OS_WIN - ui->buttonLayout->setDirection(QBoxLayout::RightToLeft); -#endif - - // Mobile - ui->idCode->setValidator(new NumberValidator(ui->idCode)); - ui->idCode->setText(Settings::MOBILEID_CODE); - ui->idCode->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->errorCode->hide(); - ui->phoneNo->setValidator(new NumberValidator(ui->phoneNo)); - ui->phoneNo->setText(Settings::MOBILEID_NUMBER); - ui->phoneNo->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->phoneNo->setFocus(); - ui->errorPhone->hide(); - ui->cbRemember->setChecked(Settings::MOBILEID_REMEMBER); - ui->cbRemember->setAttribute(Qt::WA_MacShowFocusRect, false); - auto saveSettings = [this] { - bool checked = ui->cbRemember->isChecked(); - Settings::MOBILEID_REMEMBER = checked; - Settings::MOBILEID_CODE = checked ? ui->idCode->text() : QString(); - Settings::MOBILEID_NUMBER = checked ? ui->phoneNo->text() : QString(); - }; - auto setError = [](LineEdit *input, QLabel *error, const QString &msg) { - input->setLabel(msg.isEmpty() ? QString() : QStringLiteral("error")); - error->setText(msg); - error->setHidden(msg.isEmpty()); - }; - connect(ui->idCode, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); - connect(ui->idCode, &QLineEdit::textEdited, this, saveSettings); - connect(ui->idCode, &QLineEdit::textEdited, ui->errorCode, [this, setError] { - setError(ui->idCode, ui->errorCode, {}); - }); - connect(ui->phoneNo, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); - connect(ui->phoneNo, &QLineEdit::textEdited, this, saveSettings); - connect(ui->phoneNo, &QLineEdit::textEdited, ui->errorPhone, [this, setError] { - setError(ui->phoneNo, ui->errorPhone, {}); - }); - connect(ui->cbRemember, &QCheckBox::clicked, this, saveSettings); - connect(ui->sign, &QPushButton::clicked, this, [this, setError] { - if(!IKValidator::isValid(idCode())) - setError(ui->idCode, ui->errorCode, tr("Personal code is not valid")); - else - setError(ui->idCode, ui->errorCode, {}); - if(phoneNo().size() < 8 || countryCodes.contains(phoneNo())) - setError(ui->phoneNo, ui->errorPhone, tr("Phone number is not entered")); - else if(!countryCodes.contains(phoneNo().left(3))) - setError(ui->phoneNo, ui->errorPhone, tr("Invalid country code")); - else - setError(ui->phoneNo, ui->errorPhone, {}); - if(ui->errorCode->text().isEmpty() && ui->errorPhone->text().isEmpty()) - accept(); - }); - connect(ui->cancel, &QPushButton::clicked, this, &MobileDialog::reject); - connect(this, &MobileDialog::finished, this, &MobileDialog::close); -} - -MobileDialog::~MobileDialog() -{ - delete ui; -} - -QString MobileDialog::idCode() -{ - return ui->idCode->text(); -} - -QString MobileDialog::phoneNo() -{ - return ui->phoneNo->text(); -} diff --git a/client/dialogs/MobileDialog.h b/client/dialogs/MobileDialog.h deleted file mode 100644 index 7875dba7d..000000000 --- a/client/dialogs/MobileDialog.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#pragma once - -#include - -namespace Ui { class MobileDialog; } - -class MobileDialog final : public QDialog -{ - Q_OBJECT - -public: - explicit MobileDialog(QWidget *parent = nullptr); - ~MobileDialog() final; - - QString idCode(); - QString phoneNo(); - -private: - Ui::MobileDialog *ui; -}; - diff --git a/client/dialogs/MobileDialog.ui b/client/dialogs/MobileDialog.ui deleted file mode 100644 index 5fb98339b..000000000 --- a/client/dialogs/MobileDialog.ui +++ /dev/null @@ -1,318 +0,0 @@ - - - MobileDialog - - - Qt::WindowModal - - - - 0 - 0 - 430 - 481 - - - - Mobile-ID - - - QWidget { -color: #07142A; -font-family: Roboto, Helvetica; -font-size: 14px; -} -#MobileDialog { -background-color: #FFFFFF; -border-radius: 4px; -} -#label { -color: #003168; -font-size: 20px; -font-weight: 700; -} -#errorCode, #errorPhone { -color: #AD2A45; -} -QLineEdit { -padding: 10px 14px; -border: 1px solid #C4CBD8; -border-radius: 4px; -background-color: white; -placeholder-text-color: #607496; -font-size: 16px; -} -QLineEdit[label="error"] { -border-color: #BE7884; -} -QCheckBox { -spacing: 8px; -border-right: none; /*Workaround for right padding*/ -} -QCheckBox:disabled { -color: #C4CBD8; -} -QCheckBox::indicator { -width: 16px; -height: 16px; -} -QCheckBox::indicator:unchecked { -image: url(:/images/icon_checkbox.svg); -} -QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus { -image: url(:/images/icon_checkbox_active.svg); -} -QCheckBox::indicator:unchecked:disabled { -image: url(:/images/icon_checkbox_disabled.svg); -} -QCheckBox::indicator:checked { -image: url(:/images/icon_checkbox_check.svg); -} -QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus { -image: url(:/images/icon_checkbox_check_active.svg); -} -QCheckBox::indicator:checked:disabled { -image: url(:/images/icon_checkbox_check_disabled.svg); -} -QPushButton { -padding: 12px 12px; -border-radius: 4px; -border: 1px solid #AD2A45; -color: #AD2A45; -font-weight: 700; -} -QPushButton:hover, QPushButton:focus { -background-color: #F5EBED; -} -QPushButton:pressed { -background-color: #E1C1C6; -} -QPushButton:default { -color: #ffffff; -border-color: #2F70B6; -background-color: #2F70B6; -} -QPushButton:default:hover, QPushButton:default:focus { -border-color: #2B66A6; -background-color: #2B66A6; -} -QPushButton:default:pressed { -border-color: #215081; -background-color: #215081; -} -QPushButton:default:disabled { -border-color: #82A9D3; -background-color: #82A9D3; -} - - - - 40 - - - QLayout::SetDefaultConstraint - - - 40 - - - 32 - - - 40 - - - 32 - - - - - - 350 - 0 - - - - Qt::TabFocus - - - Enter your phone number to sign with mobile-ID - - - Qt::AlignCenter - - - true - - - - - - - 24 - - - - - 6 - - - - - - 0 - 20 - - - - Country code and phone number - - - phoneNo - - - - - - - 37254321 - - - - - - - - - - - 0 - 20 - - - - Qt::TabFocus - - - - - - - - - 6 - - - - - - 0 - 20 - - - - Personal code - - - idCode - - - - - - - 47101010033 - - - - - - - - - - - 0 - 20 - - - - Qt::TabFocus - - - - - - - - - - 0 - 17 - - - - Remember me - - - - - - - - - 0 - - - - - PointingHandCursor - - - Cancel - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - PointingHandCursor - - - Sign - - - true - - - - - - - - - - LineEdit - QLineEdit -
widgets/LineEdit.h
-
-
- - -
diff --git a/client/dialogs/MobileProgress.ui b/client/dialogs/MobileProgress.ui deleted file mode 100644 index b9edf5b73..000000000 --- a/client/dialogs/MobileProgress.ui +++ /dev/null @@ -1,179 +0,0 @@ - - - MobileProgress - - - Qt::WindowModal - - - - 0 - 0 - 432 - 322 - - - - QWidget { -color: #07142A; -font-family: Roboto, Helvetica; -font-size: 14px; -} -#MobileProgress { -background-color: #FFFFFF; -border-radius: 4px; -} -#code { -color: #003168; -font-size: 32px; -font-weight: 700; -} -QPushButton { -padding: 12px 12px; -border-radius: 4px; -border: 1px solid #AD2A45; -color: #AD2A45; -font-weight: 700; -} -QPushButton:hover, QPushButton:focus { -background-color: #F5EBED; -} -QPushButton:pressed { -background-color: #E1C1C6; -} -QPushButton:default { -color: #ffffff; -border-color: #2F70B6; -background-color: #2F70B6; -} -QPushButton:default:hover, QPushButton:default:focus { -border-color: #2B66A6; -background-color: #2B66A6; -} -QPushButton:default:pressed { -border-color: #215081; -background-color: #215081; -} -QPushButton:default:disabled { -border-color: #82A9D3; -background-color: #82A9D3; -} - - - - 30 - - - QLayout::SetFixedSize - - - 40 - - - 32 - - - 40 - - - 32 - - - - - 24 - - - - - - 350 - 0 - - - - Control code: - - - Qt::AlignCenter - - - - - - - Qt::TabFocus - - - 1234 - - - Qt::AlignCenter - - - - - - - Make sure control code matches with one in phone screen and enter mobile-ID PIN2-code. - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - 0 - 40 - - - - 75 - - - 0 - - - Qt::AlignCenter - - - false - - - %v sec - - - - - - - - - - - PointingHandCursor - - - Cancel - - - false - - - - - - - - diff --git a/client/dialogs/SigningDialog.cpp b/client/dialogs/SigningDialog.cpp new file mode 100644 index 000000000..c7a41934f --- /dev/null +++ b/client/dialogs/SigningDialog.cpp @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "SigningDialog.h" +#include "ui_SigningDialog.h" + +#include "Application.h" +#include "DigiDoc.h" +#include "IKValidator.h" +#include "QPCSC.h" +#include "QCryptoBackend.h" +#include "Settings.h" +#include "SslCertificate.h" +#include "dialogs/WarningDialog.h" +#include "effects/Overlay.h" +#include "signer/QSigner.h" +#include "signer/MobileProgress.h" +#include "signer/SmartIDProgress.h" +#include "widgets/CardListItem.h" +#include "widgets/NoCardInfo.h" + +#include + +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(); + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); +#ifdef Q_OS_WIN + ui->buttonLayout->setDirection(QBoxLayout::RightToLeft); +#endif + + // Tab bar + ui->tabs->addTab(tr("ID-card")); + ui->tabs->addTab(tr("Mobile-ID")); + ui->tabs->addTab(tr("Smart-ID")); + connect(ui->tabs, &QTabBar::currentChanged, ui->tabStack, &QStackedWidget::setCurrentIndex); + + // ID-card tab + updateCards(); + connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &SigningDialog::updateCards); + connect(ui->tabs, &QTabBar::currentChanged, this, [this](int index) { + if(index == IDCard) + updateCards(); + else + 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); + ui->idCodeMID->setAttribute(Qt::WA_MacShowFocusRect, false); + ui->errorCodeMID->hide(); + ui->phoneNo->setValidator(new NumberValidator(ui->phoneNo)); + ui->phoneNo->setText(Settings::MOBILEID_NUMBER); + ui->phoneNo->setAttribute(Qt::WA_MacShowFocusRect, false); + ui->errorPhone->hide(); + ui->cbRememberMID->setChecked(Settings::MOBILEID_REMEMBER); + ui->cbRememberMID->setAttribute(Qt::WA_MacShowFocusRect, false); + auto saveMIDSettings = [this] { + bool checked = ui->cbRememberMID->isChecked(); + Settings::MOBILEID_REMEMBER = checked; + Settings::MOBILEID_CODE = checked ? ui->idCodeMID->text() : QString(); + Settings::MOBILEID_NUMBER = checked ? ui->phoneNo->text() : QString(); + }; + connect(ui->idCodeMID, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); + connect(ui->idCodeMID, &QLineEdit::textEdited, this, saveMIDSettings); + connect(ui->idCodeMID, &QLineEdit::textEdited, this, [this, setError] { + 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] { + setError(ui->phoneNo, ui->errorPhone, {}); + }); + connect(ui->cbRememberMID, &QCheckBox::clicked, this, saveMIDSettings); + + // Smart-ID tab + static const QString &EE = Settings::SMARTID_COUNTRY_LIST.first(); + auto *ik = new NumberValidator(ui->idCodeSID); + ui->idCodeSID->setValidator(Settings::SMARTID_COUNTRY == EE ? ik : nullptr); + ui->idCodeSID->setText(Settings::SMARTID_CODE); + ui->idCodeSID->setAttribute(Qt::WA_MacShowFocusRect, false); + ui->errorCodeSID->hide(); + ui->cbRememberSID->setAttribute(Qt::WA_MacShowFocusRect, false); + for(int i = 0, count = Settings::SMARTID_COUNTRY_LIST.size(); i < count; ++i) + ui->idCountry->setItemData(i, Settings::SMARTID_COUNTRY_LIST[i]); + ui->idCountry->setCurrentIndex(ui->idCountry->findData(Settings::SMARTID_COUNTRY)); + ui->cbRememberSID->setChecked(Settings::SMARTID_REMEMBER); + auto saveSIDSettings = [this] { + bool checked = ui->cbRememberSID->isChecked(); + Settings::SMARTID_REMEMBER = checked; + Settings::SMARTID_CODE = checked ? ui->idCodeSID->text() : QString(); + Settings::SMARTID_COUNTRY = checked ? ui->idCountry->currentData().toString() : Settings::SMARTID_COUNTRY_LIST.first(); + }; + connect(ui->idCodeSID, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); + connect(ui->idCodeSID, &QLineEdit::textEdited, this, saveSIDSettings); + connect(ui->idCodeSID, &QLineEdit::textEdited, this, [this, setError] { + setError(ui->idCodeSID, ui->errorCodeSID, {}); + }); + connect(ui->idCountry, &QComboBox::currentTextChanged, this, [this, ik, saveSIDSettings] { + static const QString &EE2 = Settings::SMARTID_COUNTRY_LIST.first(); + ui->idCodeSID->setValidator(ui->idCountry->currentData().toString() == EE2 ? ik : nullptr); + saveSIDSettings(); + }); + 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->cancel, &QPushButton::clicked, this, &QDialog::reject); + connect(this, &QDialog::finished, this, &QDialog::close); +} + +SigningDialog::~SigningDialog() +{ + delete ui; +} + +void SigningDialog::setRoleAddress(const QString &city, const QString &country, + const QString &state, const QString &zip, const QString &role) +{ + m_city = city; + m_country = country; + m_state = state; + m_zip = zip; + m_role = role; +} + +SigningDialog::Method SigningDialog::currentMethod() const +{ + return static_cast(ui->tabs->currentIndex()); +} + +QString SigningDialog::currentCode() const +{ + switch(currentMethod()) { + case IDCard: + return SslCertificate(m_selectedToken.cert()).personalCode(); + case MobileID: + return ui->idCodeMID->text(); + case SmartID: + return ui->idCodeSID->text(); + } + return {}; +} + +void SigningDialog::updateCards() +{ + qDeleteAll(ui->cardListContainer->findChildren()); + + // Fall back to global selection if our local token was removed + const 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(); + + bool any = false; + bool selectedUsable = false; + for(const TokenData &token : tokens) { + SslCertificate c(token.cert()); + if(!c.keyUsage().contains(SslCertificate::NonRepudiation)) + continue; + bool usable = c.isValid() && !token.data(QStringLiteral("blocked")).toBool(); + auto *w = new CardListItem(ui->cardListContainer); + w->update(token, token.card() == m_selectedToken.card(), usable); + connect(w, &CardListItem::selected, this, [this](const TokenData &t) { + m_selectedToken = t; + updateCards(); + }); + ui->cardListContainer->layout()->addWidget(w); + any = true; + if(token == m_selectedToken) + selectedUsable = usable; + } + + if(!any) { + if(!QPCSC::instance().serviceRunning()) + ui->noCard->update(NoCardInfo::NoPCSC); + else if(QPCSC::instance().readers().isEmpty()) + ui->noCard->update(NoCardInfo::NoReader); + else + ui->noCard->update(NoCardInfo::NoCard); + } + ui->noCard->setVisible(!any); + ui->cardListContainer->setVisible(any); + if(currentMethod() == IDCard) + ui->sign->setEnabled(selectedUsable); +} + +bool SigningDialog::performSign() +{ + // Warn if this personal code has already signed the document + const QString code = currentCode(); + if(!code.isEmpty()) { + const bool alreadySigned = std::any_of( + digiDoc->signatures().cbegin(), digiDoc->signatures().cend(), + [&code](const DigiDocSignature &sig) { + return SslCertificate(sig.cert()).personalCode() == code; + }); + if(alreadySigned) { + auto *dlg = WarningDialog::create(this) + ->withTitle(tr("The document has already been signed by you")) + ->addButton(tr("Continue signing"), QMessageBox::Ok); + if(dlg->exec() != QMessageBox::Ok) + return false; + } + } + + auto showPage = [this](QWidget *page) + { + ui->formPage->setVisible(page == ui->formPage); + ui->progressPage->setVisible(page == ui->progressPage); + }; + + switch(currentMethod()) { + case IDCard: { + QSigner signer(m_selectedToken); + return digiDoc->sign(m_city, m_state, m_zip, m_country, m_role, &signer); + } + case MobileID: { + 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); + } + case SmartID: { + 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 false; +} diff --git a/client/dialogs/SigningDialog.h b/client/dialogs/SigningDialog.h new file mode 100644 index 000000000..624d81816 --- /dev/null +++ b/client/dialogs/SigningDialog.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#include + +#include "TokenData.h" + +class DigiDoc; +namespace Ui { class SigningDialog; } + +class SigningDialog final : public QDialog +{ + Q_OBJECT +public: + enum Method { IDCard, MobileID, SmartID }; + + explicit SigningDialog(DigiDoc *doc, QWidget *parent = nullptr); + ~SigningDialog() final; + + void setRoleAddress(const QString &city, const QString &country, + const QString &state, const QString &zip, const QString &role); + +private: + void updateCards(); + bool performSign(); + Method currentMethod() const; + QString currentCode() const; + + DigiDoc *digiDoc; + Ui::SigningDialog *ui; + TokenData m_selectedToken; + QString m_role, m_city, m_state, m_country, m_zip; +}; diff --git a/client/dialogs/SigningDialog.ui b/client/dialogs/SigningDialog.ui new file mode 100644 index 000000000..b5f6636d1 --- /dev/null +++ b/client/dialogs/SigningDialog.ui @@ -0,0 +1,710 @@ + + + + + SigningDialog + + + Qt::WindowModal + + + + 0 + 0 + 380 + 756 + + + + Sign document + + + QWidget { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +} +#SigningDialog { +background-color: #FFFFFF; +border-radius: 4px; +} +#signingTitle { +color: #003168; +font-size: 20px; +font-weight: 700; +} +#progressCode { +color: #003168; +font-size: 32px; +font-weight: 700; +} +QProgressBar { +background-color: #F3F5F7; +border: 1px solid #C4CBD8; +border-radius: 4px; +min-height: 6px; +max-height: 6px; +margin: 15px 5px; +} +QProgressBar::chunk { +border-radius: 3px; +background-color: #2F70B6; +} +#errorCodeMID, #errorPhone, #errorCodeSID, #errorCountry { +color: #AD2A45; +} +QLineEdit, QComboBox { +padding: 10px 14px; +border: 1px solid #C4CBD8; +border-radius: 4px; +background-color: #FFFFFF; +placeholder-text-color: #607496; +font-size: 16px; +} +QLineEdit[label="error"] { +border-color: #BE7884; +} +QComboBox QWidget#popup { +background-color: transparent; +} +QComboBox QWidget#content { +border: 1px solid #C4CBD8; +border-radius: 4px; +background-color: #FFFFFF; +} +QComboBox QPushButton { +margin: 3px; +padding: 0px 12px 0px 4px; +border: 0px; +color: #07142A; +text-align: left; +font-weight: normal; +font-size: 16px; +qproperty-iconSize: 14px 9px; +qproperty-layoutDirection: RightToLeft; +} +QComboBox QPushButton#selected { +qproperty-icon: url(:/images/arrow_up.svg); +} +QComboBox QPushButton:hover#selected, QComboBox QPushButton:focus#selected { +qproperty-icon: url(:/images/arrow_up_white.svg); +} +QComboBox::drop-down { +background-color: #FFFFFF; +width: 25px; +} +QComboBox::down-arrow { +image: url(:/images/arrow_down.svg); +} +QComboBox::down-arrow:on { +top: 1px; +left: 1px; +} +QCheckBox { +spacing: 8px; +border: none; +} +QCheckBox:disabled { +color: #C4CBD8; +} +QCheckBox::indicator { +width: 16px; +height: 16px; +} +QCheckBox::indicator:unchecked { +image: url(:/images/icon_checkbox.svg); +} +QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus { +image: url(:/images/icon_checkbox_active.svg); +} +QCheckBox::indicator:unchecked:disabled { +image: url(:/images/icon_checkbox_disabled.svg); +} +QCheckBox::indicator:checked { +image: url(:/images/icon_checkbox_check.svg); +} +QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus { +image: url(:/images/icon_checkbox_check_active.svg); +} +QCheckBox::indicator:checked:disabled { +image: url(:/images/icon_checkbox_check_disabled.svg); +} +QPushButton { +padding: 12px 12px; +border-radius: 4px; +border: 1px solid #AD2A45; +color: #AD2A45; +font-weight: 700; +} +QPushButton:hover, QPushButton:focus { +background-color: #F5EBED; +} +QPushButton:pressed { +background-color: #E1C1C6; +} +QPushButton:default { +color: #ffffff; +border-color: #2F70B6; +background-color: #2F70B6; +} +QPushButton:default:hover, QPushButton:default:focus { +border-color: #2B66A6; +background-color: #2B66A6; +} +QPushButton:default:pressed { +border-color: #215081; +background-color: #215081; +} +QPushButton:default:disabled { +border-color: #82A9D3; +background-color: #82A9D3; +} +QTabBar::tab { +padding: 11px 19px; +color: #003168; +border: none; +font-weight: 700; +background-color: #FFFFFF; +border: 1px solid #003168; +border-width: 1px 0px 1px 1px; +} +QTabBar::tab:first { +border-top-left-radius: 4px; +border-bottom-left-radius: 4px; +} +QTabBar::tab:last { +border-top-right-radius: 4px; +border-bottom-right-radius: 4px; +border-right-width: 1px; +} +QTabBar::tab:selected { +background-color: #003168; +color: #FFFFFF; +} +QTabBar::tab:hover:!selected { +background-color: #EDF1F6; +} + + + + 0 + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 40 + + + 40 + + + 32 + + + 40 + + + 32 + + + + + + 300 + 0 + + + + Choose signing method + + + Qt::AlignCenter + + + + + + + 16 + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 8 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + 24 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + + + + 0 + 20 + + + + Country code and phone number + + + phoneNo + + + + + + + 37254321 + + + + + + + + 0 + 20 + + + + Qt::TabFocus + + + + + + + + + 6 + + + + + + 0 + 20 + + + + Personal code + + + idCodeMID + + + + + + + 47101010033 + + + + + + + + 0 + 20 + + + + Qt::TabFocus + + + + + + + + + + 0 + 17 + + + + Remember me + + + + + + + + + 24 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + + + + 0 + 20 + + + + Country + + + idCountry + + + + + + + + Estonia + + + + + Lithuania + + + + + Latvia + + + + + + + + + + 6 + + + + + + 0 + 20 + + + + Personal code + + + idCodeSID + + + + + + + 47101010033 + + + + + + + + 0 + 20 + + + + Qt::TabFocus + + + + + + + + + + 0 + 17 + + + + Remember me + + + + + + + + + + + + + 0 + + + + + PointingHandCursor + + + Cancel + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PointingHandCursor + + + Sign + + + true + + + + + + + + + + + + + 24 + + + 40 + + + 32 + + + 40 + + + 32 + + + + + + 300 + 0 + + + + Control code: + + + Qt::AlignCenter + + + + + + + Qt::TabFocus + + + Qt::AlignCenter + + + + + + + Qt::AlignCenter + + + true + + + true + + + + + + + + 0 + 40 + + + + 75 + + + 0 + + + false + + + + + + + PointingHandCursor + + + Cancel + + + false + + + + + + + + + + + ShrinkingStack + QStackedWidget +
widgets/ShrinkingStack.h
+ 1 +
+ + ComboBox + QComboBox +
widgets/ComboBox.h
+
+ + LineEdit + QLineEdit +
widgets/LineEdit.h
+
+ + NoCardInfo + QWidget +
widgets/NoCardInfo.h
+ 1 +
+ + QTabBar + QWidget +
qtabbar.h
+
+
+ + +
diff --git a/client/dialogs/SmartIDDialog.cpp b/client/dialogs/SmartIDDialog.cpp deleted file mode 100644 index eea9c1251..000000000 --- a/client/dialogs/SmartIDDialog.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "SmartIDDialog.h" -#include "ui_SmartIDDialog.h" - -#include "IKValidator.h" -#include "Settings.h" -#include "effects/Overlay.h" - -SmartIDDialog::SmartIDDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::SmartIDDialog) -{ - static const QString &EE = Settings::SMARTID_COUNTRY_LIST.first(); - new Overlay(this); - - ui->setupUi(this); - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint); -#ifdef Q_OS_WIN - ui->buttonLayout->setDirection(QBoxLayout::RightToLeft); -#endif - - ui->idCode->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->idCode->setFocus(); - ui->cbRemember->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->errorCode->hide(); - - auto *ik = new NumberValidator(ui->idCode); - ui->idCode->setValidator(Settings::SMARTID_COUNTRY == EE ? ik : nullptr); - ui->idCode->setText(Settings::SMARTID_CODE); - for(int i = 0, count = Settings::SMARTID_COUNTRY_LIST.size(); i < count; ++i) - ui->idCountry->setItemData(i, Settings::SMARTID_COUNTRY_LIST[i]); - ui->idCountry->setCurrentIndex(ui->idCountry->findData(Settings::SMARTID_COUNTRY)); - ui->cbRemember->setChecked(Settings::SMARTID_REMEMBER); - auto saveSettings = [this]{ - bool checked = ui->cbRemember->isChecked(); - Settings::SMARTID_REMEMBER = checked; - Settings::SMARTID_CODE = checked ? idCode() : QString(); - Settings::SMARTID_COUNTRY = checked ? country() : EE; - }; - auto setError = [](LineEdit *input, QLabel *error, const QString &msg) { - input->setLabel(msg.isEmpty() ? QString() : QStringLiteral("error")); - error->setText(msg); - error->setHidden(msg.isEmpty()); - }; - connect(ui->idCode, &QLineEdit::returnPressed, ui->sign, &QPushButton::click); - connect(ui->idCode, &QLineEdit::textEdited, this, saveSettings); - connect(ui->idCode, &QLineEdit::textEdited, ui->errorCode, [this, setError] { - setError(ui->idCode, ui->errorCode, {}); - }); - connect(ui->idCountry, &QComboBox::currentTextChanged, this, [this, ik, saveSettings] { - ui->idCode->setValidator(country() == EE ? ik : nullptr); - saveSettings(); - }); - connect(ui->cbRemember, &QCheckBox::clicked, this, saveSettings); - connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject); - connect(ui->sign, &QPushButton::clicked, this, [this, setError] { - if(ui->idCode->validator() && !IKValidator::isValid(idCode())) - setError(ui->idCode, ui->errorCode, tr("Personal code is not valid")); - else - { - setError(ui->idCode, ui->errorCode, {}); - accept(); - } - }); -} - -SmartIDDialog::~SmartIDDialog() -{ - delete ui; -} - -QString SmartIDDialog::country() const -{ - return ui->idCountry->currentData().toString(); -} - -QString SmartIDDialog::idCode() const -{ - return ui->idCode->text(); -} diff --git a/client/dialogs/SmartIDDialog.h b/client/dialogs/SmartIDDialog.h deleted file mode 100644 index 45cb090fe..000000000 --- a/client/dialogs/SmartIDDialog.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * QDigiDoc4 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#pragma once - -#include - -namespace Ui { class SmartIDDialog; } - -class SmartIDDialog final : public QDialog -{ - Q_OBJECT - -public: - explicit SmartIDDialog(QWidget *parent = nullptr); - ~SmartIDDialog() final; - - QString country() const; - QString idCode() const; - -private: - Ui::SmartIDDialog *ui; -}; diff --git a/client/dialogs/SmartIDDialog.ui b/client/dialogs/SmartIDDialog.ui deleted file mode 100644 index 2afbc4495..000000000 --- a/client/dialogs/SmartIDDialog.ui +++ /dev/null @@ -1,355 +0,0 @@ - - - SmartIDDialog - - - Qt::WindowModal - - - - 0 - 0 - 430 - 455 - - - - Smart-ID - - - QWidget { -color: #07142A; -font-family: Roboto, Helvetica; -font-size: 14px; -} -#SmartIDDialog { -background-color: #FFFFFF; -border-radius: 4px; -} -#label { -color: #003168; -font-size: 20px; -font-weight: 700; -} -#errorCode, #errorCountry { -color: #AD2A45; -} -QLineEdit, QComboBox { -padding: 10px 14px; -border: 1px solid #C4CBD8; -border-radius: 4px; -background-color: #FFFFFF; -placeholder-text-color: #607496; -font-size: 16px; -} -QLineEdit[label="error"] { -border-color: #BE7884; -} -QComboBox QWidget#popup { -background-color: transparent; -} -QComboBox QWidget#content { -border: 1px solid #C4CBD8; -border-radius: 4px; -background-color: #FFFFFF; -} -QComboBox QPushButton { -margin: 3px; -padding: 0px 12px 0px 4px; -border: 0px; -color: #07142A; -text-align: left; -font-weight: normal; -font-size: 16px; -qproperty-iconSize: 14px 9px; -qproperty-layoutDirection: RightToLeft; -} -QComboBox QPushButton#selected { -qproperty-icon: url(:/images/arrow_up.svg); -} -QComboBox QPushButton:hover#selected, QComboBox QPushButton:focus#selected { -qproperty-icon: url(:/images/arrow_up_white.svg); -} -QComboBox::drop-down { -background-color: #FFFFFF; -width: 25px; -} -QComboBox::down-arrow { -image: url(:/images/arrow_down.svg); -} -QComboBox::down-arrow:on { -top: 1px; -left: 1px; -} -QCheckBox { -spacing: 8px; -border: none; /*Workaround for right padding*/ -} -QCheckBox:disabled { -color: #C4CBD8; -} -QCheckBox::indicator { -width: 16px; -height: 16px; -} -QCheckBox::indicator:unchecked { -image: url(:/images/icon_checkbox.svg); -} -QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus { -image: url(:/images/icon_checkbox_active.svg); -} -QCheckBox::indicator:unchecked:disabled { -image: url(:/images/icon_checkbox_disabled.svg); -} -QCheckBox::indicator:checked { -image: url(:/images/icon_checkbox_check.svg); -} -QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus { -image: url(:/images/icon_checkbox_check_active.svg); -} -QCheckBox::indicator:checked:disabled { -image: url(:/images/icon_checkbox_check_disabled.svg); -} -QPushButton { -padding: 12px 12px; -border-radius: 4px; -border: 1px solid #AD2A45; -color: #AD2A45; -font-weight: 700; -} -QPushButton:hover, QPushButton:focus { -background-color: #F5EBED; -} -QPushButton:pressed { -background-color: #E1C1C6; -} -QPushButton:default { -color: #ffffff; -border-color: #2F70B6; -background-color: #2F70B6; -} -QPushButton:default:hover, QPushButton:default:focus { -border-color: #2B66A6; -background-color: #2B66A6; -} -QPushButton:default:pressed { -border-color: #215081; -background-color: #215081; -} -QPushButton:default:disabled { -border-color: #82A9D3; -background-color: #82A9D3; -} - - - - 40 - - - QLayout::SetFixedSize - - - 40 - - - 32 - - - 40 - - - 32 - - - - - - 350 - 0 - - - - Qt::TabFocus - - - Enter your personal code to sign with Smart-ID - - - Qt::AlignCenter - - - true - - - - - - - 24 - - - - - 6 - - - - - - 0 - 20 - - - - Country - - - idCountry - - - - - - - - Estonia - - - - - Lithuania - - - - - Latvia - - - - - - - - - - 6 - - - - - - 0 - 20 - - - - Personal code - - - idCode - - - - - - - 47101010033 - - - - - - - - - - - 0 - 20 - - - - Qt::TabFocus - - - - - - - - - - 0 - 17 - - - - Remember me - - - - - - - - - 0 - - - - - PointingHandCursor - - - Cancel - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - PointingHandCursor - - - Sign - - - true - - - - - - - - - - ComboBox - QComboBox -
widgets/ComboBox.h
-
- - LineEdit - QLineEdit -
widgets/LineEdit.h
-
-
- - -
diff --git a/client/dialogs/MobileProgress.cpp b/client/signer/MobileProgress.cpp similarity index 85% rename from client/dialogs/MobileProgress.cpp rename to client/signer/MobileProgress.cpp index d78131636..8c3d9fafd 100644 --- a/client/dialogs/MobileProgress.cpp +++ b/client/signer/MobileProgress.cpp @@ -18,7 +18,6 @@ */ #include "MobileProgress.h" -#include "ui_MobileProgress.h" #include "Application.h" #include "CheckConnection.h" @@ -38,18 +37,27 @@ #include #include #include +#include +#include +#include +#include Q_LOGGING_CATEGORY(MIDLog,"RIA.MID") using namespace digidoc; -class MobileProgress::Private final: public QDialog, public Ui::MobileProgress +class MobileProgress::Private final: public QObject { Q_OBJECT public: - using QDialog::QDialog; - void reject() final { l.exit(QDialog::Rejected); } - QTimeLine *statusTimer{}; + explicit Private(const SigningProgressUI &pui, QObject *parent = nullptr) + : QObject(parent) + , ui(pui) + {} + + const SigningProgressUI &ui; + + QTimeLine *statusTimer {}; QNetworkAccessManager *manager {}; QNetworkRequest req; QString ssid, cell, sessionID; @@ -62,38 +70,25 @@ class MobileProgress::Private final: public QDialog, public Ui::MobileProgress QString URL = !UUID.isNull() && useCustomUUID ? Settings::MID_SK_URL : Settings::MID_PROXY_URL; }; -MobileProgress::MobileProgress(QWidget *parent) - : d(new Private(parent)) +MobileProgress::MobileProgress(const SigningProgressUI &pui) + : d(new Private(pui)) { const_cast(MIDLog()).setEnabled(QtDebugMsg, QFile::exists(QStringLiteral("%1/%2.log").arg(QDir::tempPath(), QApplication::applicationName()))); - d->setWindowFlags(Qt::Dialog|Qt::CustomizeWindowHint); - d->setupUi(d); - d->code->setBuddy(d->signProgressBar); - d->code->clear(); -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) -const auto styleSheet = R"(QProgressBar { -background-color: #d3d3d3; -border-style: solid; -border-radius: 3px; -min-height: 6px; -max-height: 6px; -margin: 15px 5px; -} -QProgressBar::chunk { -border-style: solid; -border-radius: 3px; -background-color: #007aff; -})"; - d->signProgressBar->setStyleSheet(styleSheet); -#endif - QObject::connect(d->cancel, &QPushButton::clicked, d, &QDialog::reject); + d->ui.bar->setMaximum(75); + d->ui.bar->setValue(0); + d->ui.code->setBuddy(d->ui.bar); + d->ui.code->clear(); + d->ui.info->clear(); + QObject::connect(pui.cancel, &QAbstractButton::clicked, [this]() { + d->l.exit(QDialog::Rejected); + }); - d->statusTimer = new QTimeLine(d->signProgressBar->maximum() * 1000, d); + d->statusTimer = new QTimeLine(d->ui.bar->maximum() * 1000, d); d->statusTimer->setEasingCurve(QEasingCurve::Linear); - d->statusTimer->setFrameRange(d->signProgressBar->minimum(), d->signProgressBar->maximum()); - QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->signProgressBar, &QProgressBar::setValue); - QObject::connect(d->statusTimer, &QTimeLine::finished, d, &QDialog::reject); + d->statusTimer->setFrameRange(d->ui.bar->minimum(), d->ui.bar->maximum()); + QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->ui.bar, &QProgressBar::setValue); + QObject::connect(d->statusTimer, &QTimeLine::finished, pui.cancel, &QAbstractButton::click); d->req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); d->manager = CheckConnection::setupNAM(d->req); @@ -103,8 +98,7 @@ background-color: #007aff; auto returnError = [=, this](const QString &err, const QString &details = {}) { qCWarning(MIDLog) << err; d->statusTimer->stop(); - d->hide(); - auto *dlg = WarningDialog::create(d->parentWidget())->withText(err)->withDetails(details) + auto *dlg = WarningDialog::create(d->ui.errorParent)->withText(err)->withDetails(details) ->withTitle(QCoreApplication::translate("DigiDoc", "Failed to sign container")); QObject::connect(dlg, &WarningDialog::finished, &d->l, &QEventLoop::exit); dlg->open(); @@ -169,7 +163,6 @@ background-color: #007aff; try { QByteArray b64 = QByteArray::fromBase64(result.value(QLatin1String("cert")).toString().toUtf8()); d->cert = X509Cert((const unsigned char*)b64.constData(), size_t(b64.size()), X509Cert::Der); - d->hide(); d->l.exit(QDialog::Accepted); } catch(const Exception &e) { returnError(tr("Failed to parse certificate: ") + QString::fromStdString(e.msg())); @@ -186,7 +179,6 @@ background-color: #007aff; QByteArray b64 = QByteArray::fromBase64( result.value(QLatin1String("signature")).toObject().value(QLatin1String("value")).toString().toUtf8()); d->signature.assign(b64.cbegin(), b64.cend()); - d->hide(); d->l.exit(QDialog::Accepted); } else if(endResult == QLatin1String("NOT_MID_CLIENT") || endResult == QLatin1String("NOT_FOUND") || endResult == QLatin1String("NOT_ACTIVE")) @@ -227,7 +219,7 @@ bool MobileProgress::init(const QString &ssid, const QString &cell) { if(!d->UUID.isEmpty() && QUuid(d->UUID).isNull()) { - WarningDialog::create(d->parentWidget()) + WarningDialog::create(d->ui.errorParent) ->withText(tr("Failed to send request. Check your %1 service access settings.").arg(tr("mobile-ID"))) ->withTitle(QCoreApplication::translate("DigiDoc", "Failed to sign container")) ->open(); @@ -235,8 +227,8 @@ bool MobileProgress::init(const QString &ssid, const QString &cell) } d->ssid = ssid; d->cell = '+' + cell; - d->info->setText(tr("Signing in process")); - d->code->setAccessibleName(d->info->text()); + d->ui.info->setText(tr("Signing in process")); + d->ui.code->setAccessibleName(d->ui.info->text()); d->sessionID.clear(); QByteArray data = QJsonDocument(QJsonObject::fromVariantHash(QVariantHash{ {"relyingPartyUUID", d->UUID.isEmpty() ? QStringLiteral("00000000-0000-0000-0000-000000000000") : d->UUID}, @@ -268,9 +260,9 @@ std::vector MobileProgress::sign(const std::string &method, const else throw Exception(__FILE__, __LINE__, "Unsupported digest method"); - d->code->setText(QStringLiteral("%1").arg((digest.front() >> 2) << 7 | (digest.back() & 0x7F), 4, 10, QChar('0'))); - d->info->setText(tr("Make sure control code matches with one in phone screen and enter mobile-ID PIN2-code.")); - d->code->setAccessibleName(QStringLiteral("%1 %2. %3").arg(d->label->text(), d->code->text(), d->info->text())); + d->ui.code->setText(QStringLiteral("%1").arg((digest.front() >> 2) << 7 | (digest.back() & 0x7F), 4, 10, QChar('0'))); + d->ui.info->setText(tr("Make sure control code matches with one in phone screen and enter mobile-ID PIN2-code.")); + d->ui.code->setAccessibleName(QStringLiteral("%1 %2. %3").arg(d->ui.label->text(), d->ui.code->text(), d->ui.info->text())); QByteArray data = QJsonDocument(QJsonObject::fromVariantHash({ {"relyingPartyUUID", d->UUID.isEmpty() ? QStringLiteral("00000000-0000-0000-0000-000000000000") : d->UUID}, @@ -289,9 +281,6 @@ std::vector MobileProgress::sign(const std::string &method, const qCDebug(MIDLog).noquote() << d->req.url() << data; d->manager->post(d->req, data); d->statusTimer->start(); - d->adjustSize(); - WaitDialogHider hider; - d->show(); switch(d->l.exec()) { case QDialog::Accepted: return d->signature; diff --git a/client/dialogs/MobileProgress.h b/client/signer/MobileProgress.h similarity index 81% rename from client/dialogs/MobileProgress.h rename to client/signer/MobileProgress.h index d8db2e232..00c6a5bbb 100644 --- a/client/dialogs/MobileProgress.h +++ b/client/signer/MobileProgress.h @@ -23,13 +23,25 @@ #include +class QAbstractButton; +class QLabel; +class QProgressBar; class QWidget; +struct SigningProgressUI { + QLabel *info {}; + QLabel *code {}; + QLabel *label {}; + QProgressBar *bar {}; + QAbstractButton *cancel {}; + QWidget *errorParent {}; +}; + class MobileProgress final: public digidoc::Signer { Q_DECLARE_TR_FUNCTIONS(MobileProgress) public: - explicit MobileProgress(QWidget *parent = nullptr); + explicit MobileProgress(const SigningProgressUI &pui); ~MobileProgress() final; bool init(const QString &ssid, const QString &cell); diff --git a/client/QSigner.cpp b/client/signer/QSigner.cpp similarity index 100% rename from client/QSigner.cpp rename to client/signer/QSigner.cpp diff --git a/client/QSigner.h b/client/signer/QSigner.h similarity index 100% rename from client/QSigner.h rename to client/signer/QSigner.h diff --git a/client/dialogs/SmartIDProgress.cpp b/client/signer/SmartIDProgress.cpp similarity index 83% rename from client/dialogs/SmartIDProgress.cpp rename to client/signer/SmartIDProgress.cpp index 97afb60fd..cb4748e1e 100644 --- a/client/dialogs/SmartIDProgress.cpp +++ b/client/signer/SmartIDProgress.cpp @@ -18,7 +18,6 @@ */ #include "SmartIDProgress.h" -#include "ui_MobileProgress.h" #include "Application.h" #include "CheckConnection.h" @@ -39,26 +38,27 @@ #include #include #include - -#include +#include +#include +#include +#include Q_LOGGING_CATEGORY(SIDLog,"RIA.SmartID") using namespace digidoc; -class SmartIDProgress::Private final: public QDialog, public Ui::MobileProgress +class SmartIDProgress::Private final: public QObject { Q_OBJECT public: - using QDialog::QDialog; - void reject() final { l.exit(QDialog::Rejected); } - void setVisible(bool visible) final { - if(visible && !hider) hider = std::make_unique(); - QDialog::setVisible(visible); - if(!visible && hider) hider.reset(); - } - QTimer *timer{}; - QTimeLine *statusTimer{}; + explicit Private(const SigningProgressUI &pui, QObject *parent = nullptr) + : QObject(parent) + , ui(pui) + {} + + const SigningProgressUI &ui; + + QTimeLine *statusTimer {}; QNetworkAccessManager *manager {}; QNetworkRequest req; QString documentNumber, sessionID, fileName; @@ -69,43 +69,26 @@ class SmartIDProgress::Private final: public QDialog, public Ui::MobileProgress QString UUID = useCustomUUID ? Settings::SID_UUID : QString(); QString NAME = Settings::SID_NAME; QString URL = !UUID.isNull() && useCustomUUID ? Settings::SID_SK_URL : Settings::SID_PROXY_URL; - std::unique_ptr hider; }; - - -SmartIDProgress::SmartIDProgress(QWidget *parent) - : d(new Private(parent)) +SmartIDProgress::SmartIDProgress(const SigningProgressUI &pui) + : d(new Private(pui)) { const_cast(SIDLog()).setEnabled(QtDebugMsg, QFile::exists(QStringLiteral("%1/%2.log").arg(QDir::tempPath(), QApplication::applicationName()))); - d->setWindowFlags(Qt::Dialog|Qt::CustomizeWindowHint); - d->setupUi(d); - d->signProgressBar->setMaximum(100); - d->code->setBuddy(d->signProgressBar); - d->code->clear(); -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) -const auto styleSheet = R"(QProgressBar { -background-color: #d3d3d3;; -border-style: solid; -border-radius: 3px; -min-height: 6px; -max-height: 6px; -margin: 15px 5px; -} -QProgressBar::chunk { -border-style: solid; -border-radius: 3px; -background-color: #007aff; -})"; - d->signProgressBar->setStyleSheet(styleSheet); -#endif - QObject::connect(d->cancel, &QPushButton::clicked, d, &QDialog::reject); + d->ui.bar->setMaximum(100); + d->ui.bar->setValue(0); + d->ui.code->setBuddy(d->ui.bar); + d->ui.code->clear(); + d->ui.info->clear(); + QObject::connect(pui.cancel, &QAbstractButton::clicked, [this] { + d->l.exit(QDialog::Rejected); + }); - d->statusTimer = new QTimeLine(d->signProgressBar->maximum() * 1000, d); + d->statusTimer = new QTimeLine(d->ui.bar->maximum() * 1000, d); d->statusTimer->setEasingCurve(QEasingCurve::Linear); - d->statusTimer->setFrameRange(d->signProgressBar->minimum(), d->signProgressBar->maximum()); - QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->signProgressBar, &QProgressBar::setValue); + d->statusTimer->setFrameRange(d->ui.bar->minimum(), d->ui.bar->maximum()); + QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->ui.bar, &QProgressBar::setValue); d->req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); d->manager = CheckConnection::setupNAM(d->req); @@ -115,10 +98,7 @@ background-color: #007aff; auto returnError = [=, this](const QString &err, const QString &details = {}) { qCWarning(SIDLog) << err; d->statusTimer->stop(); - delete d->timer; - d->timer = nullptr; - d->hide(); - auto *dlg = WarningDialog::create(d->parentWidget())->withText(err)->withDetails(details) + auto *dlg = WarningDialog::create(d->ui.errorParent)->withText(err)->withDetails(details) ->withTitle(QCoreApplication::translate("DigiDoc", "Failed to sign container")); QObject::connect(dlg, &WarningDialog::finished, &d->l, &QEventLoop::exit); dlg->open(); @@ -203,7 +183,6 @@ background-color: #007aff; QByteArray b64 = QByteArray::fromBase64( result.value(QLatin1String("signature")).toObject().value(QLatin1String("value")).toString().toUtf8()); d->signature.assign(b64.cbegin(), b64.cend()); - d->hide(); d->l.exit(QDialog::Accepted); } else if(result.contains(QLatin1String("cert"))) @@ -212,7 +191,6 @@ background-color: #007aff; QByteArray b64 = QByteArray::fromBase64( result.value(QLatin1String("cert")).toObject().value(QLatin1String("value")).toString().toUtf8()); d->cert = X509Cert((const unsigned char*)b64.constData(), size_t(b64.size()), X509Cert::Der); - d->hide(); d->l.exit(QDialog::Accepted); } catch(const Exception &e) { returnError(tr("Failed to parse certificate: ") + QString::fromStdString(e.msg())); @@ -240,7 +218,7 @@ bool SmartIDProgress::init(const QString &country, const QString &idCode, const { if(!d->UUID.isEmpty() && QUuid(d->UUID).isNull()) { - WarningDialog::create(d->parentWidget()) + WarningDialog::create(d->ui.errorParent) ->withText(tr("Failed to send request. Check your %1 service access settings.").arg(tr("Smart-ID"))) ->withTitle(QCoreApplication::translate("DigiDoc", "Failed to sign container")) ->open(); @@ -262,15 +240,9 @@ bool SmartIDProgress::init(const QString &country, const QString &idCode, const d->req.setUrl(QUrl(QStringLiteral("%1/certificatechoice/etsi/PNO%2-%3").arg(d->URL, country, idCode))); qCDebug(SIDLog).noquote() << d->req.url() << data; d->manager->post(d->req, data); - d->info->setText(tr("Open the Smart-ID application on your smart device and confirm device for signing.")); - d->code->setAccessibleName(d->info->text()); + d->ui.info->setText(tr("Open the Smart-ID application on your smart device and confirm device for signing.")); + d->ui.code->setAccessibleName(d->ui.info->text()); d->statusTimer->start(); - d->adjustSize(); - d->timer = new QTimer(d); - d->timer->setSingleShot(true); - QObject::connect(d->timer, &QTimer::timeout, d, &SmartIDProgress::Private::show); - using namespace std::chrono; - d->timer->start(3s); return d->l.exec() == QDialog::Accepted; } @@ -291,9 +263,9 @@ std::vector SmartIDProgress::sign(const std::string &method, cons QByteArray codeDiest = QCryptographicHash::hash(QByteArray::fromRawData((const char*)digest.data(), int(digest.size())), QCryptographicHash::Sha256); uint code = codeDiest.right(2).toHex().toUInt(nullptr, 16) % 10000; - d->code->setText(QStringLiteral("%1").arg(code, 4, 10, QChar('0'))); - d->info->setText(tr("Make sure control code matches with one in phone screen and enter Smart-ID PIN2-code.")); - d->code->setAccessibleName(QStringLiteral("%1 %2. %3").arg(d->label->text(), d->code->text(), d->info->text())); + d->ui.code->setText(QStringLiteral("%1").arg(code, 4, 10, QChar('0'))); + d->ui.info->setText(tr("Make sure control code matches with one in phone screen and enter Smart-ID PIN2-code.")); + d->ui.code->setAccessibleName(QStringLiteral("%1 %2. %3").arg(d->ui.label->text(), d->ui.code->text(), d->ui.info->text())); QJsonObject req{ {"relyingPartyUUID", (d->UUID.isEmpty() ? QStringLiteral("00000000-0000-0000-0000-000000000000") : d->UUID)}, @@ -313,8 +285,6 @@ std::vector SmartIDProgress::sign(const std::string &method, cons qCDebug(SIDLog).noquote() << d->req.url() << data; d->manager->post(d->req, data); d->statusTimer->start(); - d->adjustSize(); - d->show(); switch(d->l.exec()) { case QDialog::Accepted: return d->signature; diff --git a/client/dialogs/SmartIDProgress.h b/client/signer/SmartIDProgress.h similarity index 93% rename from client/dialogs/SmartIDProgress.h rename to client/signer/SmartIDProgress.h index 18202fd5e..80d371aec 100644 --- a/client/dialogs/SmartIDProgress.h +++ b/client/signer/SmartIDProgress.h @@ -19,17 +19,17 @@ #pragma once +#include "MobileProgress.h" + #include #include -class QWidget; - class SmartIDProgress final: public digidoc::Signer { Q_DECLARE_TR_FUNCTIONS(MobileProgress) public: - explicit SmartIDProgress(QWidget *parent = nullptr); + explicit SmartIDProgress(const SigningProgressUI &pui); ~SmartIDProgress() final; digidoc::X509Cert cert() const final; bool init(const QString &country, const QString &idCode, const QString &fileName); diff --git a/client/translations/en.ts b/client/translations/en.ts index 0079f693c..51c0f8c31 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -248,6 +248,17 @@ Added file(s) exceeds the maximum size limit of the container (∼120MB). <a href='https://www.id.ee/en/article/encrypting-large-120-mb-files/'>Read more about it</a>
+ + CardListItem + + Issuer + Issuer + + + Valid to + Valid to + + CardWidget @@ -456,8 +467,8 @@ Check proxy username and password - Cannot connect to certificate status service! - Cannot connect to certificate status service! + Check internet connection + Check internet connection @@ -477,10 +488,6 @@ Container: Container: - - The document has already been signed by you - The document has already been signed by you - DigiDoc4 Client DigiDoc4 Client @@ -553,10 +560,6 @@ Sign Sign - - Continue signing - Continue signing - Encrypting Encrypting @@ -1347,29 +1350,6 @@ LDAP server is unavailable. MainAction - - Token selection - accessible - Token selection - - - Sign with -Mobile-ID - Sign with -Mobile-ID - - - Sign with -Smart-ID - Sign with -Smart-ID - - - Sign with -E-Seal - Sign with -E-Seal - Encrypt Encrypt @@ -1379,10 +1359,8 @@ E-Seal Decrypt - Decrypt with -ID-Card - Decrypt with -ID-Card + Sign + Sign Encrypt @@ -1390,12 +1368,6 @@ long-term Encrypt long-term - - Sign with -ID-Card - Sign with -ID-Card - MainWindow @@ -1483,10 +1455,6 @@ ID-Card Signing Signing - - Check internet connection - Check internet connection - Load file from disk for signing or verifying accessible @@ -1561,49 +1529,6 @@ ID-Card Continue - - MobileDialog - - Enter your phone number to sign with mobile-ID - <b>Enter your phone number to sign<br/>with mobile-ID</b> - - - Remember me - Remember me - - - Personal code is not valid - Personal code is not valid - - - Phone number is not entered - Phone number is not entered - - - Cancel - Cancel - - - Sign - Sign - - - Country code and phone number - Country code and phone number - - - Personal code - Personal code - - - Invalid country code - Invalid country code - - - Mobile-ID - Mobile-ID - - MobileProgress @@ -1630,22 +1555,10 @@ ID-Card Make sure control code matches with one in phone screen and enter mobile-ID PIN2-code. Make sure control code matches with one in phone screen and enter mobile-ID PIN2-code. - - Control code: - Control code: - SSL handshake failed. Check the proxy settings of your computer or software upgrades. SSL handshake failed. Check the proxy settings of your computer or software upgrades. - - %v sec - %v sec - - - Cancel - Cancel - %1 service has encountered technical errors. Please try again later. %1 service has encountered technical errors. Please try again later. @@ -2781,22 +2694,26 @@ Additional licenses and components - SmartIDDialog + SigningDialog - Personal code - Personal code + Sign document + Sign document - Enter your personal code to sign with Smart-ID - Enter your personal code to sign with Smart-ID + Choose signing method + Choose signing method - Remember me - Remember me + Country code and phone number + Country code and phone number - Personal code is not valid - Personal code is not valid + Personal code + Personal code + + + Remember me + Remember me Country @@ -2822,6 +2739,42 @@ Additional licenses and components Sign Sign + + Control code: + Control code: + + + ID-card + ID-card + + + Mobile-ID + Mobile-ID + + + Smart-ID + Smart-ID + + + Personal code is not valid + Personal code is not valid + + + Phone number is not entered + Phone number is not entered + + + Invalid country code + Invalid country code + + + The document has already been signed by you + The document has already been signed by you + + + Continue signing + Continue signing + SslCertificate diff --git a/client/translations/et.ts b/client/translations/et.ts index 0a6e6af88..03fd270de 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -248,6 +248,17 @@ Lisatud fail(id) ületab turvaümbriku maksimaalset suurust (~120MB). <a href='https://www.id.ee/artikkel/suuremahuliste-120-mb-failide-krupteerimine/'>Loe täpsemalt siit</a> + + CardListItem + + Issuer + Väljaandja + + + Valid to + Kehtib kuni + + CardWidget @@ -456,8 +467,8 @@ Kontrolli proksi kasutajanime ja parooli - Cannot connect to certificate status service! - Kehtivuskinnitusteenus ei ole kättesaadav! + Check internet connection + Kontrolli internetiühendust @@ -477,10 +488,6 @@ Container: Ümbrik: - - The document has already been signed by you - Dokument on Sinu poolt juba allkirjastatud - DigiDoc4 Client DigiDoc4 klient @@ -553,10 +560,6 @@ Sign Allkirjasta - - Continue signing - Jätka allkirjastamisega - Encrypting Krüpteerin @@ -1347,29 +1350,6 @@ LDAP serveriga ei saa ühendust. MainAction - - Token selection - accessible - Vali vahend - - - Sign with -Mobile-ID - Allkirjasta -Mobiil-ID’ga - - - Sign with -Smart-ID - Allkirjasta -Smart-ID’ga - - - Sign with -E-Seal - Allkirjasta -E-templiga - Encrypt Krüpteeri @@ -1379,10 +1359,8 @@ E-templiga Dekrüpteeri - Decrypt with -ID-Card - Dekrüpteeri -ID-kaardiga + Sign + Allkirjasta Encrypt @@ -1390,12 +1368,6 @@ long-term Krüpteeri säilitamiseks - - Sign with -ID-Card - Allkirjasta -ID-kaardiga - MainWindow @@ -1483,10 +1455,6 @@ ID-kaardiga Signing Allkirjastamine - - Check internet connection - Kontrolli internetiühendust - Load file from disk for signing or verifying accessible @@ -1561,49 +1529,6 @@ ID-kaardiga Edasi - - MobileDialog - - Enter your phone number to sign with mobile-ID - <b>Sisesta oma telefoninumber<br/>mobiil-IDga allkirjastamiseks</b> - - - Remember me - Pea mind meeles - - - Personal code is not valid - Isikukood pole kehtiv - - - Phone number is not entered - Telefoninumber pole sisestatud - - - Cancel - Katkesta - - - Sign - Allkirjastan - - - Country code and phone number - Riigikood ja telefoninumber - - - Personal code - Isikukood - - - Invalid country code - Vigane riigikood - - - Mobile-ID - Mobiil-ID - - MobileProgress @@ -1630,22 +1555,10 @@ ID-kaardiga Make sure control code matches with one in phone screen and enter mobile-ID PIN2-code. Veendu kontrollkoodi õigsuses ja sisesta telefonil mobiil-ID PIN2-kood. - - Control code: - Kontrollkood: - SSL handshake failed. Check the proxy settings of your computer or software upgrades. SSL ühenduskanali loomine ebaõnnestus. Kontrolli arvuti puhverserveri seadeid või tarkvara uuendusi. - - %v sec - %v sek - - - Cancel - Katkesta - %1 service has encountered technical errors. Please try again later. %1 teenuses esinevad tehnilised tõrked. Palun proovi mõne aja pärast uuesti. @@ -2781,22 +2694,26 @@ Täiendavad litsentsid ja komponendid - SmartIDDialog + SigningDialog - Personal code - Isikukood + Sign document + Allkirjasta dokument - Enter your personal code to sign with Smart-ID - Sisesta oma isikukood Smart-IDga allkirjastamiseks + Choose signing method + Vali allkirjastamise meetod - Remember me - Pea mind meeles + Country code and phone number + Riigikood ja telefoninumber - Personal code is not valid - Isikukood pole kehtiv + Personal code + Isikukood + + + Remember me + Pea mind meeles Country @@ -2820,7 +2737,43 @@ Täiendavad litsentsid ja komponendid Sign - Allkirjastan + Allkirjasta + + + Control code: + Kontrollkood: + + + ID-card + ID-kaart + + + Mobile-ID + Mobiil-ID + + + Smart-ID + Smart-ID + + + Personal code is not valid + Isikukood pole kehtiv + + + Phone number is not entered + Telefoninumber pole sisestatud + + + Invalid country code + Vigane riigikood + + + The document has already been signed by you + Dokument on Sinu poolt juba allkirjastatud + + + Continue signing + Jätka allkirjastamisega diff --git a/client/widgets/CardListItem.cpp b/client/widgets/CardListItem.cpp new file mode 100644 index 000000000..355c4d9d4 --- /dev/null +++ b/client/widgets/CardListItem.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "CardListItem.h" +#include "ui_CardListItem.h" + +#include "SslCertificate.h" + +#include +#include +#include + +CardListItem::CardListItem(QWidget *parent) + : QAbstractButton(parent) + , ui(new Ui::CardListItem) +{ + ui->setupUi(this); + ui->cardIcon->load(QStringLiteral(":/images/icon_Minu_eID_hover.svg")); + setAutoExclusive(true); + connect(this, &QAbstractButton::toggled, this, [this](bool checked) { + if(checked) + emit selected(t); + }); +} + +CardListItem::~CardListItem() +{ + delete ui; +} + +TokenData CardListItem::token() const +{ + return t; +} + +void CardListItem::paintEvent(QPaintEvent*) +{ + QStyleOptionButton opt; + opt.initFrom(this); + if(isChecked()) + opt.state |= QStyle::State_On; + if(isDown()) + opt.state |= QStyle::State_Sunken; + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} + +void CardListItem::changeEvent(QEvent *ev) +{ + QAbstractButton::changeEvent(ev); + if(ev->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + update(t, isChecked(), isCheckable()); + } +} + +void CardListItem::update(const TokenData &token, bool selected, bool usable) +{ + t = token; + SslCertificate c(t.cert()); + QString name = !c.subjectInfo("GN").isEmpty() || !c.subjectInfo("SN").isEmpty() ? + c.toString(QStringLiteral("GN SN")) : c.toString(QStringLiteral("CN")); + ui->cardName->setText(QStringLiteral("%1, %2").arg(name, c.personalCode()).toHtmlEscaped()); + ui->cardName->setAccessibleName(ui->cardName->text().toLower()); + ui->cardIssuer->setText(QStringLiteral("%1: %2").arg(tr("Issuer"), c.issuerInfo(QSslCertificate::CommonName))); + ui->cardValidUntil->setText(QStringLiteral("%1: %2").arg(tr("Valid to"), + c.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy")))); + + qint64 leftDays = std::max(0, QDateTime::currentDateTime().daysTo(c.expiryDate().toLocalTime())); + if(!usable || !c.isValid()) + ui->cardValidUntil->setLabel(QStringLiteral("error")); + else if(leftDays <= 105) + ui->cardValidUntil->setLabel(QStringLiteral("warning")); + else + ui->cardValidUntil->setLabel(QString()); + + setCheckable(usable); + setEnabled(usable); + setCursor(usable ? Qt::PointingHandCursor : Qt::ArrowCursor); + setChecked(usable && selected); +} diff --git a/client/widgets/CardListItem.h b/client/widgets/CardListItem.h new file mode 100644 index 000000000..757a84d0f --- /dev/null +++ b/client/widgets/CardListItem.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "TokenData.h" + +#include + +namespace Ui { class CardListItem; } + +class CardListItem final: public QAbstractButton +{ + Q_OBJECT + +public: + explicit CardListItem(QWidget *parent = nullptr); + ~CardListItem() final; + + TokenData token() const; + void update(const TokenData &token, bool selected, bool usable); + +signals: + void selected(const TokenData &token); + +protected: + void paintEvent(QPaintEvent *ev) final; + void changeEvent(QEvent *ev) final; + +private: + Ui::CardListItem *ui; + TokenData t; +}; diff --git a/client/widgets/CardListItem.ui b/client/widgets/CardListItem.ui new file mode 100644 index 000000000..006ea0c0f --- /dev/null +++ b/client/widgets/CardListItem.ui @@ -0,0 +1,151 @@ + + + CardListItem + + + + 0 + 0 + 420 + 114 + + + + #CardListItem { +border: 1px solid #E7EAEF; +border-radius: 5px; +} +#CardListItem:hover, #CardListItem:focus { +border-color: #EAF1F8; +background-color: #EAF1F8; +} +#CardListItem:checked { +border-color: #003168; +background-color: #EAF1F8; +} +#cardName { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +font-weight: 700; +} +#cardIssuer { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 12px; +font-weight: 400; +} +#cardValidUntil { +color: #091A36; +font-family: Roboto, Helvetica; +font-size: 12px; +font-weight: 400; +padding: 2px 8px; +border-radius: 8px; +background-color: #F3F5F7; +} +#cardValidUntil[label="error"] { +color: #AD2A45; +background-color: #F5EBED; +} +#cardValidUntil[label="warning"] { +color: #83612D; +background-color: #FDF7EE; +} +#cardValidUntil[label="good"] { +color: #1A641B; +background-color: #EAF8EA; +} + + + + 16 + + + 16 + + + 16 + + + 16 + + + 11 + + + 2 + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + Qt::FocusPolicy::TabFocus + + + MARI MAASIKAS, 48405050123 + + + Qt::NoTextInteraction + + + + + + + Qt::FocusPolicy::TabFocus + + + Issuer: ESTEID-SK 2023 + + + Qt::NoTextInteraction + + + + + + + Qt::FocusPolicy::TabFocus + + + Valid to: 02.02.2030 + + + Qt::NoTextInteraction + + + + + + + + Label + QLabel +
widgets/Label.h
+
+ + QSvgWidget + QWidget +
QSvgWidget
+ 1 +
+
+ + +
diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index d6c8cd0ee..c2067be59 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -30,9 +30,7 @@ #include "TokenData.h" #include "dialogs/AddRecipients.h" #include "dialogs/FileDialog.h" -#include "dialogs/MobileDialog.h" #include "dialogs/PasswordDialog.h" -#include "dialogs/SmartIDDialog.h" #include "dialogs/WarningDialog.h" #include "widgets/AddressItem.h" #include "widgets/MainAction.h" @@ -67,7 +65,7 @@ ContainerPage::ContainerPage(QWidget *parent) connect(btn, &QAbstractButton::clicked, this, [this,code] { emit action(code); }); }; - connect(mainAction, &MainAction::action, this, &ContainerPage::handleAction); + connect(mainAction, &MainAction::action, this, &ContainerPage::action); connect(ui->cancel, &QPushButton::clicked, this, [this] { window()->setWindowFilePath({}); window()->setWindowTitle(tr("DigiDoc4 Client")); @@ -105,10 +103,7 @@ ContainerPage::~ContainerPage() void ContainerPage::cardChanged(const SslCertificate &cert, bool isBlocked) { emit ui->rightPane->idChanged(cert); - isSeal = cert.type() & SslCertificate::TempelType; - isExpired = !cert.isValid(); this->isBlocked = isBlocked; - idCode = cert.personalCode(); emit certChanged(cert); } @@ -124,11 +119,6 @@ void ContainerPage::clear(int code) emit action(code); } -void ContainerPage::clearPopups() -{ - mainAction->hideDropdown(); -} - void ContainerPage::elideFileName() { ui->containerFile->setText(QStringLiteral("%1") @@ -150,52 +140,6 @@ bool ContainerPage::eventFilter(QObject *o, QEvent *e) return QWidget::eventFilter(o, e); } -void ContainerPage::handleAction(int type) -{ - QString code; - QString info2; - switch(type) - { - case SignatureAdd: - case SignatureToken: - code = idCode; - break; - case SignatureMobile: - { - MobileDialog dlg(this); - if(dlg.exec() != QDialog::Accepted) - return; - code = dlg.idCode(); - info2 = dlg.phoneNo(); - break; - } - case SignatureSmartID: - { - SmartIDDialog dlg(this); - if(dlg.exec() != QDialog::Accepted) - return; - code = dlg.idCode(); - info2 = dlg.country(); - break; - } - default: - emit action(type, code, info2); - return; - } - if(auto items = ui->rightPane->findChildren(); - std::any_of(items.cbegin(), items.cend(), [code](auto *signatureItem) { - return signatureItem->isSelfSigned(code); - })) - { - auto *dlg = WarningDialog::create(this) - ->withTitle(tr("The document has already been signed by you")) - ->addButton(tr("Continue signing"), QMessageBox::Ok); - if(dlg->exec() != QMessageBox::Ok) - return; - } - emit action(type, code, info2); -} - void ContainerPage::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) @@ -212,7 +156,7 @@ void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, con if (!container->decrypt(lock, secret)) return; transition(container, QSslCertificate{}); - emit action(DecryptContainerSuccess, {}, {}); + emit action(DecryptContainerSuccess); } template @@ -245,7 +189,7 @@ void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) if(!container->encrypt(container->fileName(), {}, {})) return; transition(container, qApp->cryptoManager()->tokenauth().cert()); - emit action(EncryptContainerSuccess, {}, {}); + emit action(EncryptContainerSuccess); return; } @@ -257,7 +201,7 @@ void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) if(!container->encrypt(container->fileName(), p.label(), p.secret())) return; transition(container, QSslCertificate{}); - emit action(EncryptContainerSuccess, {}, {}); + emit action(EncryptContainerSuccess); } void ContainerPage::setHeader(const QString &file) @@ -268,33 +212,17 @@ void ContainerPage::setHeader(const QString &file) elideFileName(); } -void ContainerPage::showMainAction(const QList &actions) -{ - mainAction->showActions(actions); - bool isSignCard = actions.contains(SignatureAdd) || actions.contains(SignatureToken); - bool isSignMobile = !isSignCard && (actions.contains(SignatureMobile) || actions.contains(SignatureSmartID)); - bool isEncrypt = actions.contains(EncryptContainer) && !ui->rightPane->findChildren().isEmpty(); - bool isEncryptLT = actions.contains(EncryptLT); - bool isDecrypt = !isBlocked && (actions.contains(DecryptContainer) || actions.contains(DecryptToken)); - mainAction->setButtonEnabled(isSupported && - (isEncrypt || isEncryptLT || isDecrypt || isSignMobile || (isSignCard && !isBlocked && !isExpired))); - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - ui->navigationArea->layout()->invalidate(); -} - void ContainerPage::showEncryptAction(CryptoDoc *container) { if (!container->keys().empty()) { - mainAction->showActions({EncryptContainer}); - mainAction->setButtonEnabled(true); + mainAction->showAction(EncryptContainer); + mainAction->setEnabled(true); + } else if (container->supportsSymmetricKeys()) { + mainAction->showAction(EncryptLT); + mainAction->setEnabled(true); } else { - if (container->supportsSymmetricKeys()) { - mainAction->showActions({EncryptLT}); - mainAction->setButtonEnabled(true); - } else { - mainAction->showActions({EncryptContainer}); - mainAction->setButtonEnabled(false); - } + mainAction->showAction(EncryptContainer); + mainAction->setEnabled(false); } ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); ui->navigationArea->layout()->invalidate(); @@ -306,14 +234,14 @@ void ContainerPage::showSigningButton() { mainAction->hide(); ui->mainActionSpacer->changeSize(1, 20, QSizePolicy::Fixed); - ui->navigationArea->layout()->invalidate(); } - else if(idCode.isEmpty()) - showMainAction({ SignatureMobile, SignatureSmartID }); - else if(isSeal) - showMainAction({ SignatureToken, SignatureMobile, SignatureSmartID }); else - showMainAction({ SignatureAdd, SignatureMobile, SignatureSmartID }); + { + mainAction->showAction(SignatureAdd); + mainAction->setEnabled(isSupported); + ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); + } + ui->navigationArea->layout()->invalidate(); } void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert) @@ -373,7 +301,6 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert case EncryptLT: encrypt(container, true); break; - case DecryptToken: case DecryptContainer: decrypt(container, nullptr, {}); break; @@ -416,10 +343,6 @@ void ContainerPage::transition(DigiDoc* container) connect(ui->leftPane, &ItemList::removed, container, [this, container](int index) { deleteConfirm(container, index); }); - disconnect(this, &ContainerPage::certChanged, container, nullptr); - connect(this, &ContainerPage::certChanged, container, [this](const SslCertificate &) { - showSigningButton(); - }); disconnect(ui->summary, &QAbstractButton::clicked, container, nullptr); connect(ui->summary, &QAbstractButton::clicked, container, [this,container] { #ifdef Q_OS_WIN @@ -531,15 +454,16 @@ void ContainerPage::transition(DigiDoc* container) } } - showSigningButton(); - ui->leftPane->setModel(container->documentModel()); updatePanes(container->state(), nullptr); } void ContainerPage::updateDecryptionButton() { - showMainAction({ isSeal ? DecryptToken : DecryptContainer }); + mainAction->showAction(DecryptContainer); + mainAction->setEnabled(isSupported && !isBlocked); + ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); + ui->navigationArea->layout()->invalidate(); } void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container) @@ -568,6 +492,7 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->show(); ui->rightPane->init(ItemList::ItemSignature, QT_TRANSLATE_NOOP("ItemList", "Container is not signed")); ui->summary->setVisible(Settings::SHOW_PRINT_SUMMARY); + showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email }, true); ui->extend->hide(); break; @@ -577,6 +502,7 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->hide(); ui->rightPane->init(ItemList::ItemSignature, QT_TRANSLATE_NOOP("ItemList", "Container signatures")); ui->summary->setVisible(Settings::SHOW_PRINT_SUMMARY); + showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email, ui->extend }, true); break; case UnencryptedContainer: diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index e301dfae7..8519c1dec 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -45,14 +45,13 @@ class ContainerPage final : public QWidget void cardChanged(const SslCertificate &cert, bool isBlocked = false); void tokenChanged(const TokenData &token); - void clearPopups(); void setHeader(const QString &file); void togglePrinting(bool enable); void transition(CryptoDoc *container, const QSslCertificate &cert); void transition(DigiDoc* container); Q_SIGNALS: - void action(int code, const QString &idCode = {}, const QString &info2 = {}); + void action(int code); void addFiles(const QStringList &files); void certChanged(const SslCertificate &cert); void warning(const WarningText &warningText); @@ -66,23 +65,18 @@ class ContainerPage final : public QWidget void elideFileName(); void encrypt(CryptoDoc *container, bool longTerm); bool eventFilter(QObject *o, QEvent *e) final; - void showMainAction(const QList &actions); void showEncryptAction(CryptoDoc *container); void showSigningButton(); - void handleAction(int type); void updateDecryptionButton(); void updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container); void translateLabels(); Ui::ContainerPage *ui; MainAction *mainAction {}; - QString idCode; QString fileName; const char *cancelText = QT_TR_NOOP("Cancel"); const char *convertText = QT_TR_NOOP("Encrypt"); bool isSupported = false; - bool isSeal = false; - bool isExpired = false; bool isBlocked = false; }; diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index d4f92f216..708685207 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -18,48 +18,37 @@ */ #include "MainAction.h" -#include "ui_MainAction.h" -#include "Settings.h" -#include -#include -#include +#include using namespace ria::qdigidoc4; -class MainAction::Private: public Ui::MainAction -{ -public: - QList actions; - QList list; -}; - MainAction::MainAction(QWidget *parent) - : QWidget(parent) - , ui(new Private) + : QPushButton(parent) { - ui->setupUi(this); - ui->otherCards->hide(); - ui->otherCards->installEventFilter(this); + setFixedSize(QSize(200, 65)); + setCursor(QCursor(Qt::CursorShape::PointingHandCursor)); + setStyleSheet(QString::fromUtf8(R"(QPushButton { +border: 0px; +color: #ffffff; +background-color: #2F70B6; +font-family: Roboto, Helvetica; +font-size: 16px; +font-weight: 700; +border-top-left-radius: 4px; +} +QPushButton:hover, QPushButton:focus { +background-color: #2B66A6; +} +QPushButton:pressed { +background-color: #215081; +} +QPushButton:disabled { +background-color: #82A9D3; +})")); parent->installEventFilter(this); move(parent->width() - width(), parent->height() - height()); - - connect(ui->mainAction, &QPushButton::clicked, this, [&]{ - if (ui->actions.value(0) == Actions::SignatureMobile) - Settings::MOBILEID_ORDER = true; - if (ui->actions.value(0) == Actions::SignatureSmartID) - Settings::MOBILEID_ORDER = false; - }); - connect(ui->mainAction, &QPushButton::clicked, this, [this]{ emit action(ui->actions.value(0)); }); - connect(ui->mainAction, &QPushButton::clicked, this, &MainAction::hideDropdown); - connect(ui->otherCards, &QToolButton::clicked, this, &MainAction::showDropdown); - adjustSize(); -} - -MainAction::~MainAction() -{ - hideDropdown(); - delete ui; + connect(this, &QPushButton::clicked, this, [this]{ emit action(_action); }); } void MainAction::changeEvent(QEvent* event) @@ -69,104 +58,27 @@ void MainAction::changeEvent(QEvent* event) QWidget::changeEvent(event); } -void MainAction::hideDropdown() -{ - for(QPushButton *other: ui->list) - other->deleteLater(); - ui->list.clear(); - setStyleSheet(QStringLiteral("QPushButton { border-top-left-radius: 4px; }")); -} - bool MainAction::eventFilter(QObject *watched, QEvent *event) { - switch(event->type()) - { - case QEvent::Resize: - if(watched == parentWidget()) - { - move(parentWidget()->width() - width(), parentWidget()->height() - height()); - QWidget* prev = this; - for(QPushButton *other: std::as_const(ui->list)) - { - other->move(prev->pos() + QPoint(0, -height() - 1)); - prev = other; - } - } - break; - default: break; - } + if(event->type() == QEvent::Resize && watched == parentWidget()) + move(parentWidget()->width() - width(), parentWidget()->height() - height()); return QWidget::eventFilter(watched, event); } - -QString MainAction::label(Actions action) -{ - switch(action) - { - case SignatureMobile: return tr("Sign with\nMobile-ID"); - case SignatureSmartID: return tr("Sign with\nSmart-ID"); - case SignatureToken: return tr("Sign with\nE-Seal"); - case EncryptContainer: return tr("Encrypt"); - case EncryptLT: return tr("Encrypt\nlong-term"); - case DecryptContainer: return tr("Decrypt with\nID-Card"); - case DecryptToken: return tr("Decrypt"); - default: return tr("Sign with\nID-Card"); - } -} - -void MainAction::setButtonEnabled(bool enabled) -{ - ui->mainAction->setEnabled(enabled); -} - -void MainAction::showActions(QList actions) +void MainAction::showAction(Actions action) { - if(actions.size() == 2 && - std::all_of(actions.cbegin(), actions.cend(), [] (Actions action) { - return action == SignatureMobile || action == SignatureSmartID; - }) && - !Settings::MOBILEID_ORDER) - std::reverse(actions.begin(), actions.end()); - ui->actions = std::move(actions); + _action = action; update(); - ui->otherCards->setVisible(ui->actions.size() > 1); show(); } -void MainAction::showDropdown() +void MainAction::update() { - if(ui->actions.size() > 1 && ui->list.isEmpty()) + switch(_action) { - QWidget* prev = this; - for(auto i = std::next(ui->actions.cbegin()); i != ui->actions.cend(); ++i) - { - auto *other = new QPushButton(label(*i), parentWidget()); - other->setCursor(ui->mainAction->cursor()); - other->resize(size()); - other->move(prev->pos() + QPoint(0, -height() - 1)); - prev = other; - other->show(); - other->setStyleSheet(ui->mainAction->styleSheet() + - (i + 1 == ui->actions.cend() ? QStringLiteral("\nQPushButton { border-top-left-radius: 4px; }") : QString())); - connect(other, &QPushButton::clicked, this, [i, this]{ - hideDropdown(); - if (*i == Actions::SignatureMobile) - Settings::MOBILEID_ORDER = true; - if (*i == Actions::SignatureSmartID) - Settings::MOBILEID_ORDER = false; - emit action(*i); - }); - ui->list.push_back(other); - } - setStyleSheet({}); + case EncryptContainer: return setText(tr("Encrypt")); + case EncryptLT: return setText(tr("Encrypt\nlong-term")); + case DecryptContainer: return setText(tr("Decrypt")); + default: return setText(tr("Sign")); } - else - hideDropdown(); -} - -void MainAction::update() -{ - hideDropdown(); - if(!ui->actions.isEmpty()) - ui->mainAction->setText(label(ui->actions[0])); } diff --git a/client/widgets/MainAction.h b/client/widgets/MainAction.h index b558e15b1..ac56446e3 100644 --- a/client/widgets/MainAction.h +++ b/client/widgets/MainAction.h @@ -21,19 +21,16 @@ #include "common_enums.h" -#include +#include -class MainAction final : public QWidget +class MainAction final : public QPushButton { Q_OBJECT public: explicit MainAction(QWidget *parent); - ~MainAction() final; - void hideDropdown(); - void setButtonEnabled(bool enabled); - void showActions(QList actions); + void showAction(ria::qdigidoc4::Actions action); signals: void action(ria::qdigidoc4::Actions action); @@ -41,11 +38,7 @@ class MainAction final : public QWidget private: void changeEvent(QEvent* event) override; bool eventFilter(QObject *watched, QEvent *event) override; - void showDropdown(); void update(); - static QString label(ria::qdigidoc4::Actions action); - - class Private; - Private *ui; + ria::qdigidoc4::Actions _action; }; diff --git a/client/widgets/MainAction.ui b/client/widgets/MainAction.ui deleted file mode 100644 index 8d3ce0ab4..000000000 --- a/client/widgets/MainAction.ui +++ /dev/null @@ -1,130 +0,0 @@ - - - MainAction - - - - 0 - 0 - 200 - 65 - - - - - 200 - 65 - - - - QPushButton { border-top-left-radius: 4px; } - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 65 - - - - - 16777215 - 65 - - - - PointingHandCursor - - - QPushButton { -border: 0px; -color: #ffffff; -background-color: #2F70B6; -font-family: Roboto, Helvetica; -font-size: 16px; -font-weight: 700; -} -QPushButton:hover, QPushButton:focus { -background-color: #2B66A6; -} -QPushButton:pressed { -background-color: #215081; -} -QPushButton:disabled { -background-color: #82A9D3; -} - - - Allkirjasta -ID-Kaardiga - - - - - - - - 34 - 65 - - - - - 34 - 65 - - - - PointingHandCursor - - - Token selection - - - QToolButton { -border: 0px solid #E7EAEF; -border-width: 0px 0px 0px 1px; -background-color: #2F70B6; -} -QToolButton:hover, QToolButton:focus { -background-color: #2B66A6; -} -QToolButton:pressed { -background-color: #215081; -} - - - - :/images/arrow_up_white.svg - - - - - 14 - 8 - - - - - - - - - diff --git a/client/widgets/ShrinkingStack.h b/client/widgets/ShrinkingStack.h new file mode 100644 index 000000000..e4e471f1b --- /dev/null +++ b/client/widgets/ShrinkingStack.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#include + +class ShrinkingStack final : public QStackedWidget +{ +public: + using QStackedWidget::QStackedWidget; + QSize sizeHint() const final { + return currentWidget() ? currentWidget()->sizeHint() : QStackedWidget::sizeHint(); + } + QSize minimumSizeHint() const final { + return currentWidget() ? currentWidget()->minimumSizeHint() : QStackedWidget::minimumSizeHint(); + } +}; From 41d7ce08edc0bb92a10ef9f7145290f8227961e7 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 11 Sep 2026 15:15:45 +0300 Subject: [PATCH 4/5] Update encryption flow UI IB-8830 Signed-off-by: Raul Metsma --- client/common_enums.h | 1 - client/dialogs/PasswordDialog.cpp | 5 +- client/dialogs/PasswordDialog.ui | 62 ++++++----- client/images/icon_check_white.svg | 3 + client/images/images.qrc | 1 + client/translations/en.ts | 54 ++++++---- client/translations/et.ts | 56 ++++++---- client/widgets/ContainerPage.cpp | 147 +++++++++++++------------ client/widgets/ContainerPage.h | 9 +- client/widgets/ContainerPage.ui | 165 ++++++++++++++++++++++++++++- client/widgets/MainAction.cpp | 1 - 11 files changed, 359 insertions(+), 145 deletions(-) create mode 100644 client/images/icon_check_white.svg diff --git a/client/common_enums.h b/client/common_enums.h index ae8d733ab..183c1f227 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -43,7 +43,6 @@ enum Actions : unsigned char { ContainerEncrypt, EncryptContainer, - EncryptLT, EncryptContainerSuccess, DecryptContainer, DecryptContainerSuccess, diff --git a/client/dialogs/PasswordDialog.cpp b/client/dialogs/PasswordDialog.cpp index 818e7c641..09257c6e4 100644 --- a/client/dialogs/PasswordDialog.cpp +++ b/client/dialogs/PasswordDialog.cpp @@ -36,8 +36,9 @@ PasswordDialog::PasswordDialog(Mode mode, QWidget *parent) ui->password2Line->setHidden(mode == Mode::DECRYPT); ui->password2Error->hide(); if(mode == DECRYPT) { - ui->title->setText(tr("Decrypt with password")); - ui->passwordLabel->setText(tr("Enter password to decrypt the document")); + ui->title->setText(tr("Decrypt")); + ui->labelLabel->setText(tr("Envelope name")); + ui->passwordLabel->setText(tr("Envelope password")); ui->ok->setText(tr("Decrypt")); ui->passwordLine->setFocus(); } diff --git a/client/dialogs/PasswordDialog.ui b/client/dialogs/PasswordDialog.ui index d43d7f401..7597ed751 100644 --- a/client/dialogs/PasswordDialog.ui +++ b/client/dialogs/PasswordDialog.ui @@ -37,6 +37,12 @@ font-size: 16px; QLineEdit[label="error"] { border-color: #BE7884; } +QLineEdit[readOnly="true"] { +border: none; +padding: 0px; +background-color: transparent; +font-weight: 700; +} QLineEdit::disabled { color: #607496; background-color: #F3F5F7; @@ -114,30 +120,6 @@ margin-left: 6px; - - - - - - Key label (recipient name or id) - - - labelLine - - - - - - - - 400 - 0 - - - - - - @@ -174,6 +156,36 @@ margin-left: 6px; + + + + 6 + + + + + Create a name for the envelope + + + labelLine + + + + + + + + 400 + 0 + + + + E.g. Contracts + + + + + @@ -182,7 +194,7 @@ margin-left: 6px; - Enter a password to encrypt the document + Create a password for the envelope Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter diff --git a/client/images/icon_check_white.svg b/client/images/icon_check_white.svg new file mode 100644 index 000000000..b94b204fa --- /dev/null +++ b/client/images/icon_check_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/images.qrc b/client/images/images.qrc index 667f1a4e9..2537f5f19 100644 --- a/client/images/images.qrc +++ b/client/images/images.qrc @@ -23,6 +23,7 @@ icon_checkbox_active.svg icon_checkbox_disabled.svg icon_checkbox_hover.svg + icon_check_white.svg icon_checkbox_check.svg icon_checkbox_check_active.svg icon_checkbox_check_disabled.svg diff --git a/client/translations/en.ts b/client/translations/en.ts index 51c0f8c31..6dd450dce 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -492,6 +492,14 @@ DigiDoc4 Client DigiDoc4 Client + + Encrypt for recipients + Encrypt for recipients + + + Encrypt with password + Encrypt with password + Decrypting Decrypting @@ -528,6 +536,10 @@ Change Change + + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + Cancel Cancel @@ -1310,6 +1322,10 @@ Failed to init ldap Failed to init ldap + + Failed to set ldap CA cert + + Failed to init ldap search Failed to init ldap search @@ -1362,12 +1378,6 @@ LDAP server is unavailable. Sign Sign - - Encrypt -long-term - Encrypt -long-term -
MainWindow @@ -1713,16 +1723,24 @@ long-term Encrypt with password Encrypt with password - - Key label (recipient name or id) - Key label (recipient name or id) - Be sure to save the password in a secure place - without the password, you won’t be able to open the file again. Be sure to save the password in a secure place - without the password, you won’t be able to open the file again. + + Create a name for the envelope + Create a name for the envelope + + + E.g. Contracts + E.g. Contracts + + + Create a password for the envelope + Create a password for the envelope + • Length: 20–64 characters • Contains at least one number (0–9) @@ -1746,16 +1764,16 @@ long-term Encrypt - Decrypt with password - Decrypt with password + Decrypt + Decrypt - Enter password to decrypt the document - Enter password to decrypt the document + Envelope name + Envelope name - Decrypt - Decrypt + Envelope password + Envelope password Password is empty @@ -1769,10 +1787,6 @@ long-term Passwords do not match Passwords do not match - - Enter a password to encrypt the document - Enter a password to encrypt the document - PinPopup diff --git a/client/translations/et.ts b/client/translations/et.ts index 03fd270de..110d2e3b6 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -492,6 +492,14 @@ DigiDoc4 Client DigiDoc4 klient + + Encrypt for recipients + Krüpteeri adressaadi alusel + + + Encrypt with password + Krüpteeri parooliga + Decrypting Dekrüpteerin @@ -528,6 +536,10 @@ Change Muuda + + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + Parooliga krüpteerimine on mõeldud pikaajaliseks salvestamiseks. Parooli ei saa muuta ega taastada. + Cancel Katkesta @@ -1310,6 +1322,10 @@ Failed to init ldap LDAP initsialiseerimine ebaõnnestus + + Failed to set ldap CA cert + + Failed to init ldap search LDAP otsingu initsialiseerimine ebaõnnestus @@ -1362,12 +1378,6 @@ LDAP serveriga ei saa ühendust. Sign Allkirjasta - - Encrypt -long-term - Krüpteeri -säilitamiseks - MainWindow @@ -1713,16 +1723,24 @@ säilitamiseks Encrypt with password Krüpteeri parooliga - - Key label (recipient name or id) - Võtme nimi (saaja nimi või kood) - Be sure to save the password in a secure place - without the password, you won’t be able to open the file again. Salvesta parool kindlasti turvalisse kohta - ilma paroolita ei saa faili enam avada. + + Create a name for the envelope + Loo ümbrikule nimi + + + E.g. Contracts + Nt. Lepingud + + + Create a password for the envelope + Loo ümbrikule parool + • Length: 20–64 characters • Contains at least one number (0–9) @@ -1746,16 +1764,16 @@ säilitamiseks Krüpteeri - Decrypt with password - Dekrüpteeri parooliga + Decrypt + Dekrüpteeri - Enter password to decrypt the document - Sisestage parool dokumendi dekrüpteerimiseks + Envelope name + Ümbriku nimi - Decrypt - Dekrüpteeri + Envelope password + Ümbriku parool Password is empty @@ -1767,11 +1785,7 @@ säilitamiseks Passwords do not match - Paroolid ei ühti - - - Enter a password to encrypt the document - Loo ümbrikule parool + Paroolid ei kattu diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index c2067be59..468c676c4 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,19 @@ ContainerPage::ContainerPage(QWidget *parent) connect(ui->leftPane, &FileList::addFiles, this, &ContainerPage::addFiles); connect(ui->leftPane, &ItemList::addItem, this, [this](int code) { emit action(code); }); connect(ui->rightPane, &ItemList::addItem, this, [this](int code) { emit action(code); }); + ui->encryptMethodArea->hide(); + ui->encryptMethod->setExpanding(true); + ui->encryptMethod->setDrawBase(false); + ui->encryptMethod->setIconSize({16, 16}); + ui->encryptMethod->addTab(tr("Encrypt for recipients")); + ui->encryptMethod->addTab(tr("Encrypt with password")); + connect(ui->encryptMethod, &QTabBar::currentChanged, this, [this](int index) { + for(int i = 0; i < ui->encryptMethod->count(); ++i) + ui->encryptMethod->setTabIcon(i, i == index ? + QIcon(QStringLiteral(":/images/icon_check_white.svg")) : QIcon()); + ui->rightPaneStack->setCurrentIndex(index); + }); + ui->encryptMethod->setTabIcon(0, QIcon(QStringLiteral(":/images/icon_check_white.svg"))); connect(ui->email, &QAbstractButton::clicked, this, [this] { if(!QFileInfo::exists(fileName)) return; @@ -160,12 +174,12 @@ void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, con } template -void ContainerPage::deleteConfirm(C *c, int index) +bool ContainerPage::deleteConfirm(C *c, int index) { if(c->documentModel()->rowCount() > 1) { ui->leftPane->removeItem(index); - return; + return true; } auto *dlg = WarningDialog::create(this) ->withTitle(tr("You are about to delete the last file in the container")) @@ -174,33 +188,34 @@ void ContainerPage::deleteConfirm(C *c, int index) ->resetCancelStyle(false) ->addButton(WarningDialog::Remove, QMessageBox::Ok, true); if (dlg->exec() != QMessageBox::Ok) - return; + return false; window()->setWindowFilePath({}); window()->setWindowTitle(QCoreApplication::translate("MainWindow", "DigiDoc4 Client")); if(QFile::exists(c->fileName())) QFile::remove(c->fileName()); emit action(ContainerClose); + return false; } -void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) +void ContainerPage::encrypt(CryptoDoc *container) { - if(!longTerm) { - WaitDialogHolder waitDialog(this, tr("Encrypting")); - if(!container->encrypt(container->fileName(), {}, {})) + QString label; + QByteArray secret; + QSslCertificate cert; + if(isPasswordEncryption()) { + PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); + if(!p.exec()) return; - transition(container, qApp->cryptoManager()->tokenauth().cert()); - emit action(EncryptContainerSuccess); - return; + label = p.label(); + secret = p.secret(); + } else { + cert = qApp->cryptoManager()->tokenauth().cert(); } - PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); - if(!p.exec()) - return; - WaitDialogHolder waitDialog(this, tr("Encrypting")); - if(!container->encrypt(container->fileName(), p.label(), p.secret())) + if(!container->encrypt(container->fileName(), label, secret)) return; - transition(container, QSslCertificate{}); + transition(container, cert); emit action(EncryptContainerSuccess); } @@ -212,36 +227,9 @@ void ContainerPage::setHeader(const QString &file) elideFileName(); } -void ContainerPage::showEncryptAction(CryptoDoc *container) -{ - if (!container->keys().empty()) { - mainAction->showAction(EncryptContainer); - mainAction->setEnabled(true); - } else if (container->supportsSymmetricKeys()) { - mainAction->showAction(EncryptLT); - mainAction->setEnabled(true); - } else { - mainAction->showAction(EncryptContainer); - mainAction->setEnabled(false); - } - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - ui->navigationArea->layout()->invalidate(); -} - -void ContainerPage::showSigningButton() +bool ContainerPage::isPasswordEncryption() const { - if (!isSupported) - { - mainAction->hide(); - ui->mainActionSpacer->changeSize(1, 20, QSizePolicy::Fixed); - } - else - { - mainAction->showAction(SignatureAdd); - mainAction->setEnabled(isSupported); - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - } - ui->navigationArea->layout()->invalidate(); + return !ui->encryptMethodArea->isHidden() && ui->encryptMethod->currentIndex() == 1; } void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert) @@ -262,19 +250,19 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert container->addEncryptionKey(key); ui->rightPane->addWidget(new AddressItem(key, AddressItem::Icon, ui->rightPane)); } - showEncryptAction(container); + mainAction->setEnabled(isEncryptEnabled(container)); }); disconnect(ui->rightPane, &ItemList::removed, container, nullptr); connect(ui->rightPane, &ItemList::removed, container, [this, container](int index) { container->removeKey(index); ui->rightPane->removeItem(index); - showEncryptAction(container); + mainAction->setEnabled(isEncryptEnabled(container)); }); disconnect(this, &ContainerPage::certChanged, container, nullptr); connect(this, &ContainerPage::certChanged, container, [this, container](const SslCertificate &cert) { isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); if(container->state() & EncryptedContainer) - updateDecryptionButton(); + mainAction->setEnabled(isSupported && !isBlocked); }); disconnect(ui->changeLocation, &QPushButton::clicked, container, nullptr); connect(ui->changeLocation, &QPushButton::clicked, container, [container, this] { @@ -291,15 +279,16 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert connect(container, &CryptoDoc::destroyed, this, [this] { clear(ContainerClearWarning); }); + disconnect(ui->encryptMethod, &QTabBar::currentChanged, container, nullptr); + connect(ui->encryptMethod, &QTabBar::currentChanged, container, [this, container] { + mainAction->setEnabled(isEncryptEnabled(container)); + }); disconnect(mainAction, &MainAction::action, container, nullptr); connect(mainAction, &MainAction::action, container, [container, this](int action) { switch (action) { case EncryptContainer: - encrypt(container, false); - break; - case EncryptLT: - encrypt(container, true); + encrypt(container); break; case DecryptContainer: decrypt(container, nullptr, {}); @@ -310,6 +299,7 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert }); clear(ContainerClearWarning); + ui->encryptMethod->setCurrentIndex(0); isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); setHeader(container->fileName()); ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Encrypted files")); @@ -341,7 +331,8 @@ void ContainerPage::transition(DigiDoc* container) using enum WarningText::WarningType; disconnect(ui->leftPane, &ItemList::removed, container, nullptr); connect(ui->leftPane, &ItemList::removed, container, [this, container](int index) { - deleteConfirm(container, index); + if(deleteConfirm(container, index)) + transition(container); }); disconnect(ui->summary, &QAbstractButton::clicked, container, nullptr); connect(ui->summary, &QAbstractButton::clicked, container, [this,container] { @@ -458,21 +449,21 @@ void ContainerPage::transition(DigiDoc* container) updatePanes(container->state(), nullptr); } -void ContainerPage::updateDecryptionButton() +bool ContainerPage::isEncryptEnabled(CryptoDoc *container) const { - mainAction->showAction(DecryptContainer); - mainAction->setEnabled(isSupported && !isBlocked); - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - ui->navigationArea->layout()->invalidate(); + return isPasswordEncryption() || (container && !container->keys().empty()); } void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container) { ui->leftPane->stateChange(state); ui->rightPane->stateChange(state); + ui->encryptMethodArea->setVisible(state == UnencryptedContainer && crypto_container && crypto_container->supportsSymmetricKeys()); + if(ui->encryptMethodArea->isHidden()) + ui->encryptMethod->setCurrentIndex(0); ui->save->setVisible(state == UnsignedContainer); - ui->rightPane->setHidden(state == UnsignedContainer); - auto setButtonsVisible = [](const QVector &buttons, bool visible) { + ui->rightPaneArea->setHidden(state == UnsignedContainer); + auto setButtonsVisible = [](std::initializer_list buttons, bool visible) { for(QWidget *button: buttons) button->setVisible(visible); }; @@ -483,7 +474,6 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->show(); ui->rightPane->clear(); - showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email, ui->summary, ui->extend }, false); break; case UnsignedSavedContainer: @@ -492,7 +482,6 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->show(); ui->rightPane->init(ItemList::ItemSignature, QT_TRANSLATE_NOOP("ItemList", "Container is not signed")); ui->summary->setVisible(Settings::SHOW_PRINT_SUMMARY); - showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email }, true); ui->extend->hide(); break; @@ -502,29 +491,53 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->hide(); ui->rightPane->init(ItemList::ItemSignature, QT_TRANSLATE_NOOP("ItemList", "Container signatures")); ui->summary->setVisible(Settings::SHOW_PRINT_SUMMARY); - showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email, ui->extend }, true); break; case UnencryptedContainer: cancelText = QT_TR_NOOP("Start"); convertText = QT_TR_NOOP("Sign"); - showEncryptAction(crypto_container); setButtonsVisible({ ui->changeLocation, ui->convert }, true); setButtonsVisible({ ui->saveAs, ui->email, ui->extend }, false); break; case EncryptedContainer: cancelText = QT_TR_NOOP("Start"); convertText = QT_TR_NOOP("Sign"); - updateDecryptionButton(); - setButtonsVisible({ ui->changeLocation, ui->convert }, false); + setButtonsVisible({ ui->changeLocation, ui->convert, ui->extend }, false); setButtonsVisible({ ui->saveAs, ui->email }, true); - ui->extend->hide(); break; default: // Uninitialized cannot be shown on container page break; } + switch(state) + { + case UnsignedContainer: + case UnsignedSavedContainer: + case SignedContainer: + if(isSupported) + { + mainAction->showAction(SignatureAdd); + mainAction->setEnabled(true); + } + else + mainAction->hide(); + break; + case UnencryptedContainer: + mainAction->showAction(EncryptContainer); + mainAction->setEnabled(isEncryptEnabled(crypto_container)); + break; + case EncryptedContainer: + mainAction->showAction(DecryptContainer); + mainAction->setEnabled(isSupported && !isBlocked); + break; + default: + mainAction->hide(); + break; + } + ui->mainActionSpacer->changeSize(mainAction->isHidden() ? 1 : 198, 20, QSizePolicy::Fixed); + ui->navigationArea->layout()->invalidate(); + translateLabels(); } diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 8519c1dec..e9f051425 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -61,13 +61,12 @@ class ContainerPage final : public QWidget void clear(int code); void decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret); template - void deleteConfirm(C *c, int index); + bool deleteConfirm(C *c, int index); void elideFileName(); - void encrypt(CryptoDoc *container, bool longTerm); + void encrypt(CryptoDoc *container); bool eventFilter(QObject *o, QEvent *e) final; - void showEncryptAction(CryptoDoc *container); - void showSigningButton(); - void updateDecryptionButton(); + bool isPasswordEncryption() const; + bool isEncryptEnabled(CryptoDoc *container) const; void updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container); void translateLabels(); diff --git a/client/widgets/ContainerPage.ui b/client/widgets/ContainerPage.ui index 4314ac3eb..ac172db3a 100644 --- a/client/widgets/ContainerPage.ui +++ b/client/widgets/ContainerPage.ui @@ -11,9 +11,38 @@ - #leftPane, #rightPane { + #leftPane, #rightPaneArea { background-color: #ffffff; } +QTabBar::tab { +padding: 11px 19px; +color: #003168; +font-weight: 700; +background-color: #FFFFFF; +border: 1px solid #003168; +border-width: 1px 0px 1px 1px; +} +QTabBar::tab:first { +border-top-left-radius: 4px; +border-bottom-left-radius: 4px; +} +QTabBar::tab:last { +border-top-right-radius: 4px; +border-bottom-right-radius: 4px; +border-right-width: 1px; +} +QTabBar::tab:selected { +background-color: #003168; +color: #FFFFFF; +} +QTabBar::tab:hover:!selected { +background-color: #EDF1F6; +} +#passwordMethodInfo { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +} #leftPane { border: solid #F3F5F7; border-width: 0px 1px 0px 0px; @@ -149,7 +178,7 @@ background-color: #BFD3E8; - + 0 @@ -157,7 +186,132 @@ background-color: #BFD3E8; - + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 32 + + + 32 + + + 32 + + + 0 + + + + + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 32 + + + 24 + + + 32 + + + 0 + + + + + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + @@ -331,6 +485,11 @@ background-color: #E1C1C6; + + QTabBar + QWidget +
QTabBar
+
ItemList QScrollArea diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index 708685207..19c6cbc1e 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -77,7 +77,6 @@ void MainAction::update() switch(_action) { case EncryptContainer: return setText(tr("Encrypt")); - case EncryptLT: return setText(tr("Encrypt\nlong-term")); case DecryptContainer: return setText(tr("Decrypt")); default: return setText(tr("Sign")); } From 6b36bd24e7cc5cf372b2f3dee729719b513c5f58 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 11 Sep 2026 16:39:31 +0300 Subject: [PATCH 5/5] Update decryption flow UI IB-8830 Signed-off-by: Raul Metsma --- client/CDocSupport.cpp | 8 ++--- client/CDocSupport.h | 1 + client/CryptoDoc.cpp | 44 +++++-------------------- client/CryptoDoc.h | 4 +-- client/MainWindow.cpp | 10 ++---- client/QCryptoBackend.cpp | 16 +++------- client/QCryptoBackend.h | 1 - client/common_enums.h | 1 - client/translations/en.ts | 8 ----- client/translations/et.ts | 8 ----- client/widgets/AddressItem.cpp | 41 +++++++++++++++--------- client/widgets/AddressItem.h | 6 ++-- client/widgets/ContainerPage.cpp | 55 +++++++------------------------- client/widgets/ContainerPage.h | 9 ++---- client/widgets/Item.cpp | 1 - client/widgets/Item.h | 1 - client/widgets/ItemList.cpp | 3 -- client/widgets/ItemList.h | 2 -- client/widgets/MainAction.cpp | 1 - 19 files changed, 65 insertions(+), 155 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 0dfe46625..53a752269 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -123,7 +123,7 @@ libcdoc::result_t DDCryptoBackend::decryptRSA(std::vector& dst, const std::vector &data, bool oaep, unsigned int idx) { if (!backend) { - auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); + auto val = QCryptoBackend::getBackend(token); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -143,7 +143,7 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vectorcryptoManager()->tokenauth()); + auto val = QCryptoBackend::getBackend(token); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -158,7 +158,7 @@ libcdoc::result_t DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector &key_material, const std::vector &salt, unsigned int idx) { if (!backend) { - auto val = QCryptoBackend::getBackend(qApp->cryptoManager()->tokenauth()); + auto val = QCryptoBackend::getBackend(token); if (!val) return getDecryptStatus(val.error()); backend.reset(val.value()); @@ -321,7 +321,7 @@ DDNetworkBackend::fetchKey(std::vector &result, const std::string &url, return BACKEND_ERROR; } - TokenData auth = qApp->cryptoManager()->tokenauth(); + const TokenData &auth = crypto.token; auto val = QCryptoBackend::getBackend(auth); if (!val) return getDecryptStatus(val.error()); diff --git a/client/CDocSupport.h b/client/CDocSupport.h index d0d0f2b72..bfb830221 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -81,6 +81,7 @@ struct DDCryptoBackend final : public libcdoc::CryptoBackend { std::unique_ptr backend; std::vector secret; + TokenData token; explicit DDCryptoBackend() = default; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 99b1a805d..4bf156278 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -253,16 +253,6 @@ bool CryptoDoc::addEncryptionKey(const CKey& key) { return true; } -bool CryptoDoc::canDecrypt(const QSslCertificate &cert) { - if (!d->reader) - return false; - if (cert.isNull()) - return false; - QByteArray der = cert.toDer(); - return d->reader->getLockForCert( - std::vector(der.cbegin(), der.cend())) >= 0; -} - void CryptoDoc::clear(const QString &file, int version) { d->documents->clearTempFolder(); @@ -277,7 +267,7 @@ ContainerState CryptoDoc::state() const return d->isEncrypted() ? EncryptedContainer : UnencryptedContainer; } -bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) +bool CryptoDoc::decrypt(const libcdoc::Lock &lock, const QByteArray& secret, const TokenData &token) { if(!d->reader) { @@ -288,38 +278,20 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) return false; } - int lock_idx = -1; const std::vector &locks = d->reader->getLocks(); - if (lock == nullptr) { - QByteArray der = qApp->cryptoManager()->tokenauth().cert().toDer(); - lock_idx = d->reader->getLockForCert( - std::vector(der.cbegin(), der.cend())); - if (lock_idx < 0) { - WarningDialog::create() - ->withTitle(tr("Failed to decrypt document")) - ->withText(tr("You do not have the key to decrypt this document")) - ->open(); - return false; - } - lock = &locks.at(lock_idx); - } else { - for (lock_idx = 0; lock_idx < locks.size(); lock_idx++) { - if (lock->label == locks[lock_idx].label) { - lock = &locks.at(lock_idx); - break; - } - } - if (lock_idx >= locks.size()) - lock_idx = -1; - } - if (!lock || (lock->isSymmetric() && secret.isEmpty())) { + auto found = std::find_if(locks.cbegin(), locks.cend(), + [&lock](const libcdoc::Lock &l) { return l.label == lock.label; }); + if (found == locks.cend() || (found->isSymmetric() && secret.isEmpty())) { WarningDialog::create() ->withTitle(tr("Failed to decrypt document")) ->withText(tr("You do not have the key to decrypt this document")) ->open(); return false; } + int lock_idx = int(std::distance(locks.cbegin(), found)); + d->crypto.setBackend({}); + d->crypto.token = token; d->crypto.secret.assign(secret.cbegin(), secret.cend()); TempListConsumer cons; @@ -341,7 +313,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) const std::string &msg = d->reader->getLastErrorStr(); switch (result) { case libcdoc::WRONG_KEY: - str = (lock->type == libcdoc::Lock::PASSWORD) ? tr("Wrong password.") : tr("Wrong key."); + str = (found->type == libcdoc::Lock::PASSWORD) ? tr("Wrong password.") : tr("Wrong key."); break; case libcdoc::HASH_MISMATCH: case libcdoc::DATA_FORMAT_ERROR: diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 3a0c2da71..577b3627c 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -32,6 +32,7 @@ #include class QSslKey; +class TokenData; Q_DECLARE_LOGGING_CATEGORY(CRYPTO) @@ -61,9 +62,8 @@ class CryptoDoc final: public QObject bool supportsSymmetricKeys() const; bool addEncryptionKey(const CKey& key); - bool canDecrypt(const QSslCertificate &cert); void clear(const QString &file = {}, int version = -1); - bool decrypt(const libcdoc::Lock *lock, const QByteArray& secret); + bool decrypt(const libcdoc::Lock &lock, const QByteArray& secret, const TokenData &token); bool encrypt(const QString &filename = {}, const QString& label = {}, const QByteArray& secret = {}); DocumentModel* documentModel() const; QString fileName() const; diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index 72a209830..15d719f4e 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -92,8 +92,6 @@ MainWindow::MainWindow( QWidget *parent ) // Refresh ID card info in card widget connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &MainWindow::updateSelector); - connect(qApp->cryptoManager(), &QCryptoManager::signDataChanged, ui->signContainerPage, &ContainerPage::tokenChanged); - connect(qApp->cryptoManager(), &QCryptoManager::authDataChanged, ui->cryptoContainerPage, &ContainerPage::tokenChanged); // Refresh card info on "My EID" page connect(qApp->cryptoManager()->smartcard(), &QSmartCard::tokenChanged, this, &MainWindow::updateMyEID); @@ -121,8 +119,6 @@ MainWindow::MainWindow( QWidget *parent ) connect(ui->infoStack, &MyEidInfo::changePinClicked, this, &MainWindow::changePinClicked); connect(ui->cardInfo, &CardWidget::selected, ui->selector, &QToolButton::toggle); - ui->signContainerPage->tokenChanged(qApp->cryptoManager()->tokensign()); - ui->cryptoContainerPage->tokenChanged(qApp->cryptoManager()->tokenauth()); updateMyEID(qApp->cryptoManager()->smartcard()->tokenData()); updateMyEid(qApp->cryptoManager()->smartcard()->data()); } @@ -275,7 +271,7 @@ void MainWindow::navigateToPage( Pages page, const QStringList &files, bool crea if(navigate) { cryptoDoc = std::move(cryptoContainer); - ui->cryptoContainerPage->transition(cryptoDoc.get(), qApp->cryptoManager()->tokenauth().cert()); + ui->cryptoContainerPage->transition(cryptoDoc.get()); } } @@ -335,7 +331,7 @@ void MainWindow::convertToCDoc() cryptoDoc = std::move(cryptoContainer); digiDoc.reset(); - ui->cryptoContainerPage->transition(cryptoDoc.get(), cardData.cert()); + ui->cryptoContainerPage->transition(cryptoDoc.get()); selectPage(CryptoDetails); FadeInNotification::success(ui->topBar, tr("Converted to crypto container!")); @@ -646,8 +642,6 @@ void MainWindow::updateMyEid(const QSmartCardData &data) pin1Blocked || pin1Locked || pin2Blocked || pin2Locked || pukBlocked); - ui->signContainerPage->cardChanged(data.signCert(), pin2Blocked || pin2Locked); - ui->cryptoContainerPage->cardChanged(data.authCert(), pin1Blocked || pin1Locked); using enum WarningText::WarningType; if(pin1Locked) diff --git a/client/QCryptoBackend.cpp b/client/QCryptoBackend.cpp index 36ba71de8..5a98bc925 100644 --- a/client/QCryptoBackend.cpp +++ b/client/QCryptoBackend.cpp @@ -236,17 +236,10 @@ void QCryptoManager::selectCard(const TokenData &token) break; } } - if(isSign) - Q_EMIT signDataChanged(token); - else + if(!isSign) Q_EMIT authDataChanged(token); - if(!other.isNull()) - { - if(isSign) - Q_EMIT authDataChanged(other); - else - Q_EMIT signDataChanged(other); - } + else if(!other.isNull()) + Q_EMIT authDataChanged(other); d->smartcard.reloadCard(token, false); } @@ -314,8 +307,9 @@ void QCryptoManager::refresh() TokenData update; if(aold != anew) Q_EMIT authDataChanged(update = anew); + // reloadCard() reports the sign token when that is the one that changed if(sold != snew) - Q_EMIT signDataChanged(update = snew); + update = snew; if(aold != anew || sold != snew) d->smartcard.reloadCard(update, false); diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index 0e6616733..820fceb76 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -99,7 +99,6 @@ class QCryptoManager final : public QThread Q_SIGNALS: void cacheChanged(); void authDataChanged(const TokenData &token); - void signDataChanged(const TokenData &token); private: friend class QCryptoBackend; diff --git a/client/common_enums.h b/client/common_enums.h index 183c1f227..c89278483 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -44,7 +44,6 @@ enum Actions : unsigned char { EncryptContainer, EncryptContainerSuccess, - DecryptContainer, DecryptContainerSuccess, SignatureAdd, diff --git a/client/translations/en.ts b/client/translations/en.ts index 6dd450dce..42a0fd219 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -77,10 +77,6 @@
AddressItem - - (Yourself) - (Yourself) - digi-ID digi-ID @@ -1370,10 +1366,6 @@ LDAP server is unavailable. Encrypt Encrypt - - Decrypt - Decrypt - Sign Sign diff --git a/client/translations/et.ts b/client/translations/et.ts index 110d2e3b6..eeba29ac7 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -77,10 +77,6 @@ AddressItem - - (Yourself) - (Sina ise) - digi-ID digi-ID @@ -1370,10 +1366,6 @@ LDAP serveriga ei saa ühendust. Encrypt Krüpteeri - - Decrypt - Dekrüpteeri - Sign Allkirjasta diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index ebeb6d029..9d67c39f5 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -20,8 +20,11 @@ #include "AddressItem.h" #include "ui_AddressItem.h" +#include "Application.h" #include "CryptoDoc.h" +#include "QCryptoBackend.h" #include "SslCertificate.h" +#include "TokenData.h" #include "dialogs/KeyDialog.h" #include @@ -37,7 +40,7 @@ class AddressItem::Private: public Ui::AddressItem CKey key; QString label; QDateTime expireDate; - bool yourself = false; + TokenData token; }; AddressItem::AddressItem(const CKey &key, Type type, QWidget *parent) @@ -89,14 +92,13 @@ AddressItem::AddressItem(const CKey &key, Type type, QWidget *parent) connect(ui->add, &QToolButton::clicked, this, [this]{ emit add(this);}); connect(ui->remove, &QToolButton::clicked, this, [this]{ emit remove(this);}); - connect(ui->decrypt, &QToolButton::clicked, this, [this] { - emit decrypt(&ui->key.lock); - }); + connect(ui->decrypt, &QToolButton::clicked, this, [this] { emit decrypt(ui->token); }); + connect(qApp->cryptoManager(), &QCryptoManager::cacheChanged, this, &AddressItem::setDecryptVisible); setIdType(); ui->add->setVisible(type == Add); ui->remove->setVisible(type != Add); - ui->decrypt->setVisible(ui->key.lock.type == libcdoc::Lock::PASSWORD); + setDecryptVisible(); } AddressItem::~AddressItem() @@ -122,15 +124,6 @@ const CKey& AddressItem::getKey() const return ui->key; } -void AddressItem::idChanged(const SslCertificate &cert) { - ui->yourself = false; - if (ui->key.lock.isPKI()) { - const auto &key = ui->key.lock.getBytes(libcdoc::Lock::RCPT_KEY); - ui->yourself = cert.publicKey().toDer() == QByteArray::fromRawData((const char*)key.data(), key.size()); - } - setName(); -} - void AddressItem::initTabOrder(QWidget *item) { setTabOrder(item, ui->name); @@ -149,10 +142,28 @@ void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { (new KeyDialog(ui->key, this))->open(); } +TokenData AddressItem::tokenForLock(const libcdoc::Lock &lock) +{ + if(!lock.isPKI()) + return {}; + const auto &key = lock.getBytes(libcdoc::Lock::RCPT_KEY); + QByteArray rcpt = QByteArray::fromRawData((const char*)key.data(), key.size()); + for(const TokenData &token: qApp->cryptoManager()->cache()) + if(SslCertificate(token.cert()).publicKey().toDer() == rcpt) + return token; + return {}; +} + +void AddressItem::setDecryptVisible() +{ + ui->token = tokenForLock(ui->key.lock); + ui->decrypt->setVisible(ui->key.lock.type == libcdoc::Lock::PASSWORD || !ui->token.isNull()); +} + void AddressItem::setName() { ui->name->setText(QStringLiteral("%1 %2") - .arg(ui->label.toHtmlEscaped(), (ui->yourself ? ui->code + tr(" (Yourself)") : ui->code).toHtmlEscaped())); + .arg(ui->label.toHtmlEscaped(), ui->code.toHtmlEscaped())); if(ui->name->text().isEmpty()) ui->name->hide(); } diff --git a/client/widgets/AddressItem.h b/client/widgets/AddressItem.h index 265782648..50d188d9f 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -22,6 +22,7 @@ #include "widgets/Item.h" struct CKey; +class TokenData; namespace libcdoc { struct Lock; } @@ -41,17 +42,18 @@ class AddressItem final : public Item ~AddressItem() final; const CKey& getKey() const; - void idChanged(const SslCertificate &cert) final; void initTabOrder(QWidget *item) final; QWidget* lastTabWidget() final; void stateChange(ria::qdigidoc4::ContainerState state) final; signals: - void decrypt(const libcdoc::Lock *lock); + void decrypt(const TokenData &token); private: void changeEvent(QEvent *event) final; void mouseReleaseEvent(QMouseEvent *event) final; + void setDecryptVisible(); + static TokenData tokenForLock(const libcdoc::Lock &lock); void setName(); void setIdType(); void setIdType(const SslCertificate& cert); diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 468c676c4..778ee02c7 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -114,18 +114,6 @@ ContainerPage::~ContainerPage() delete ui; } -void ContainerPage::cardChanged(const SslCertificate &cert, bool isBlocked) -{ - emit ui->rightPane->idChanged(cert); - this->isBlocked = isBlocked; - emit certChanged(cert); -} - -void ContainerPage::tokenChanged(const TokenData &token) -{ - cardChanged(token.cert(), token.data(QStringLiteral("blocked")).toBool()); -} - void ContainerPage::clear(int code) { ui->leftPane->clear(); @@ -165,11 +153,11 @@ void ContainerPage::changeEvent(QEvent* event) QWidget::changeEvent(event); } -void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret) { +void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock &lock, const QByteArray &secret, const TokenData &token) { WaitDialogHolder waitDialog(this, tr("Decrypting")); - if (!container->decrypt(lock, secret)) + if (!container->decrypt(lock, secret, token)) return; - transition(container, QSslCertificate{}); + transition(container); emit action(DecryptContainerSuccess); } @@ -201,21 +189,18 @@ void ContainerPage::encrypt(CryptoDoc *container) { QString label; QByteArray secret; - QSslCertificate cert; if(isPasswordEncryption()) { PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); if(!p.exec()) return; label = p.label(); secret = p.secret(); - } else { - cert = qApp->cryptoManager()->tokenauth().cert(); } WaitDialogHolder waitDialog(this, tr("Encrypting")); if(!container->encrypt(container->fileName(), label, secret)) return; - transition(container, cert); + transition(container); emit action(EncryptContainerSuccess); } @@ -232,7 +217,7 @@ bool ContainerPage::isPasswordEncryption() const return !ui->encryptMethodArea->isHidden() && ui->encryptMethod->currentIndex() == 1; } -void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert) +void ContainerPage::transition(CryptoDoc *container) { disconnect(ui->leftPane, &ItemList::removed, container, nullptr); connect(ui->leftPane, &ItemList::removed, container, [this, container](int index) { @@ -258,12 +243,6 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert ui->rightPane->removeItem(index); mainAction->setEnabled(isEncryptEnabled(container)); }); - disconnect(this, &ContainerPage::certChanged, container, nullptr); - connect(this, &ContainerPage::certChanged, container, [this, container](const SslCertificate &cert) { - isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); - if(container->state() & EncryptedContainer) - mainAction->setEnabled(isSupported && !isBlocked); - }); disconnect(ui->changeLocation, &QPushButton::clicked, container, nullptr); connect(ui->changeLocation, &QPushButton::clicked, container, [container, this] { QString to = FileDialog::getSaveFileName(this, FileDialog::tr("Move file"), container->fileName()); @@ -285,22 +264,12 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert }); disconnect(mainAction, &MainAction::action, container, nullptr); connect(mainAction, &MainAction::action, container, [container, this](int action) { - switch (action) - { - case EncryptContainer: + if(action == EncryptContainer) encrypt(container); - break; - case DecryptContainer: - decrypt(container, nullptr, {}); - break; - default: - break; - } }); clear(ContainerClearWarning); ui->encryptMethod->setCurrentIndex(0); - isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); setHeader(container->fileName()); ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Encrypted files")); ui->rightPane->init(ItemList::ItemAddress, QT_TRANSLATE_NOOP("ItemList", "Recipients")); @@ -309,15 +278,17 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert hasUnsupported = hasUnsupported || (key.rcpt_cert.isNull() && !key.lock.isValid()); auto *addr = new AddressItem(key, AddressItem::Icon, ui->rightPane); ui->rightPane->addWidget(addr); - connect(addr, &AddressItem::decrypt, container, [container, key, this] { - if (key.lock.type != libcdoc::Lock::Type::PASSWORD) + connect(addr, &AddressItem::decrypt, container, [container, key, this](const TokenData &token) { + if (key.lock.type != libcdoc::Lock::Type::PASSWORD) { + decrypt(container, key.lock, {}, token); return; + } PasswordDialog p(PasswordDialog::Mode::DECRYPT, this); auto params = libcdoc::Lock::parseLabel(key.lock.label); p.setLabel(QString::fromStdString(params.contains("label") ? params["label"] : key.lock.label)); if (!p.exec()) return; - decrypt(container, &key.lock, p.secret()); + decrypt(container, key.lock, p.secret(), token); }); } if(hasUnsupported) @@ -527,10 +498,6 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc mainAction->showAction(EncryptContainer); mainAction->setEnabled(isEncryptEnabled(crypto_container)); break; - case EncryptedContainer: - mainAction->showAction(DecryptContainer); - mainAction->setEnabled(isSupported && !isBlocked); - break; default: mainAction->hide(); break; diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index e9f051425..55428e039 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -29,7 +29,6 @@ namespace Ui { class ContainerPage; } class CryptoDoc; class DigiDoc; class MainAction; -class QSslCertificate; class SignatureItem; class SslCertificate; class TokenData; @@ -43,23 +42,20 @@ class ContainerPage final : public QWidget explicit ContainerPage( QWidget *parent = nullptr ); ~ContainerPage() final; - void cardChanged(const SslCertificate &cert, bool isBlocked = false); - void tokenChanged(const TokenData &token); void setHeader(const QString &file); void togglePrinting(bool enable); - void transition(CryptoDoc *container, const QSslCertificate &cert); + void transition(CryptoDoc *container); void transition(DigiDoc* container); Q_SIGNALS: void action(int code); void addFiles(const QStringList &files); - void certChanged(const SslCertificate &cert); void warning(const WarningText &warningText); private: void changeEvent(QEvent* event) final; void clear(int code); - void decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret); + void decrypt(CryptoDoc *container, const libcdoc::Lock &lock, const QByteArray &secret, const TokenData &token); template bool deleteConfirm(C *c, int index); void elideFileName(); @@ -77,5 +73,4 @@ class ContainerPage final : public QWidget const char *cancelText = QT_TR_NOOP("Cancel"); const char *convertText = QT_TR_NOOP("Encrypt"); bool isSupported = false; - bool isBlocked = false; }; diff --git a/client/widgets/Item.cpp b/client/widgets/Item.cpp index d20755a5a..7441d42b6 100644 --- a/client/widgets/Item.cpp +++ b/client/widgets/Item.cpp @@ -23,7 +23,6 @@ #include #include -void Item::idChanged(const SslCertificate & /* cert */) {} void Item::initTabOrder(QWidget * /* item */) {} QWidget* Item::lastTabWidget() { return this; } diff --git a/client/widgets/Item.h b/client/widgets/Item.h index 3c540e381..49e433dad 100644 --- a/client/widgets/Item.h +++ b/client/widgets/Item.h @@ -31,7 +31,6 @@ class Item : public StyledWidget public: using StyledWidget::StyledWidget; - virtual void idChanged(const SslCertificate &cert); virtual void initTabOrder(QWidget *item); virtual QWidget* lastTabWidget(); diff --git a/client/widgets/ItemList.cpp b/client/widgets/ItemList.cpp index 395bc5be4..0156bc5a0 100644 --- a/client/widgets/ItemList.cpp +++ b/client/widgets/ItemList.cpp @@ -39,7 +39,6 @@ ItemList::ItemList(QWidget *parent) ui->add->hide(); ui->txtFind->setAttribute(Qt::WA_MacShowFocusRect, false); connect(ui->add, &QToolButton::clicked, this, &ItemList::add); - connect(this, &ItemList::idChanged, this, [this](const SslCertificate &cert){ this->cert = cert; }); ui->txtFind->installEventFilter(this); } @@ -76,9 +75,7 @@ void ItemList::addWidget(Item *widget, int index, QWidget *tabIndex) } ui->itemLayout->insertWidget(index, widget); connect(widget, &Item::remove, this, &ItemList::remove); - connect(this, &ItemList::idChanged, widget, &Item::idChanged); widget->stateChange(state); - widget->idChanged(cert); widget->show(); items.push_back(widget); widget->initTabOrder(tabIndex); diff --git a/client/widgets/ItemList.h b/client/widgets/ItemList.h index 51bc701a5..bca28bbeb 100644 --- a/client/widgets/ItemList.h +++ b/client/widgets/ItemList.h @@ -59,7 +59,6 @@ class ItemList : public QScrollArea signals: void add(); void addItem(int code); - void idChanged(const SslCertificate &cert); void keysSelected(QList keys); void removed(int row); void search(const QString &term); @@ -82,7 +81,6 @@ class ItemList : public QScrollArea const char *title = ""; const char *addTitle = ""; const char *headerText = ""; - SslCertificate cert; friend class AddRecipients; }; diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index 19c6cbc1e..3acd1b613 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -77,7 +77,6 @@ void MainAction::update() switch(_action) { case EncryptContainer: return setText(tr("Encrypt")); - case DecryptContainer: return setText(tr("Decrypt")); default: return setText(tr("Sign")); } }