diff --git a/CMakeLists.txt b/CMakeLists.txt index 258722df0..feb4a1045 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -374,6 +374,13 @@ install(FILES core/okularGenerator.desktop DESTINATION ${KDE_INSTALL_KSERVICETYP if(BUILD_DESKTOP) # okularpart set(okularpart_SRCS + gui/certificatemodel.cpp + gui/debug_ui.cpp + gui/guiutils.cpp + gui/pagepainter.cpp + gui/signatureguiutils.cpp + gui/signaturemodel.cpp + gui/tocmodel.cpp part/preferencesdialog.cpp part/dlgaccessibility.cpp part/dlgdebug.cpp @@ -404,18 +411,15 @@ if(BUILD_DESKTOP) part/certificateviewer.cpp part/colormodemenu.cpp part/cursorwraphelper.cpp - part/debug_ui.cpp part/drawingtoolactions.cpp part/fileprinterpreview.cpp part/findbar.cpp part/formwidgets.cpp - part/guiutils.cpp part/ktreeviewsearchline.cpp part/latexrenderer.cpp part/minibar.cpp part/okmenutitle.cpp part/pageitemdelegate.cpp - part/pagepainter.cpp part/pagesizelabel.cpp part/pageviewannotator.cpp part/pageviewmouseannotation.cpp @@ -433,13 +437,11 @@ if(BUILD_DESKTOP) part/snapshottaker.cpp part/thumbnaillist.cpp part/toc.cpp - part/tocmodel.cpp part/toggleactionmenu.cpp part/videowidget.cpp part/layers.cpp - part/signatureguiutils.cpp + part/signaturepartutils.cpp part/signaturepropertiesdialog.cpp - part/signaturemodel.cpp part/signaturepanel.cpp ) diff --git a/generators/kimgio/CMakeLists.txt b/generators/kimgio/CMakeLists.txt index 77009a0a3..cd664178e 100644 --- a/generators/kimgio/CMakeLists.txt +++ b/generators/kimgio/CMakeLists.txt @@ -13,7 +13,7 @@ endif() if(BUILD_TESTING AND BUILD_DESKTOP AND KF5KExiv2_FOUND) add_definitions( -DKDESRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" ) - set( kimgiotest_SRCS tests/kimgiotest.cpp ${CMAKE_SOURCE_DIR}/part/pagepainter.cpp ${CMAKE_SOURCE_DIR}/part/guiutils.cpp ${CMAKE_SOURCE_DIR}/part/debug_ui.cpp ) + set( kimgiotest_SRCS tests/kimgiotest.cpp ${CMAKE_SOURCE_DIR}/gui/pagepainter.cpp ${CMAKE_SOURCE_DIR}/gui/guiutils.cpp ${CMAKE_SOURCE_DIR}/gui/debug_ui.cpp ) ecm_add_test(${kimgiotest_SRCS} TEST_NAME "kimgiotest" LINK_LIBRARIES okularcore okularpart Qt5::Svg Qt5::Test) target_compile_definitions(kimgiotest PRIVATE -DGENERATOR_PATH="$") endif() diff --git a/generators/kimgio/tests/kimgiotest.cpp b/generators/kimgio/tests/kimgiotest.cpp index 417bdd0f7..a37153a15 100644 --- a/generators/kimgio/tests/kimgiotest.cpp +++ b/generators/kimgio/tests/kimgiotest.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include diff --git a/gui/certificatemodel.cpp b/gui/certificatemodel.cpp new file mode 100644 index 000000000..582b48bd0 --- /dev/null +++ b/gui/certificatemodel.cpp @@ -0,0 +1,163 @@ +/* + SPDX-FileCopyrightText: 2018 Chinmoy Ranjan Pradhan + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "certificatemodel.h" + +#include "signatureguiutils.h" + +#include + +#include +#include +#include +#include + +CertificateModel::CertificateModel(const Okular::CertificateInfo &certInfo, QObject *parent) + : QAbstractTableModel(parent) + , m_certificateInfo(certInfo) +{ + m_certificateProperties = {Version, SerialNumber, Issuer, IssuedOn, ExpiresOn, Subject, PublicKey, KeyUsage}; +} + +int CertificateModel::columnCount(const QModelIndex &) const +{ + return 2; +} + +int CertificateModel::rowCount(const QModelIndex &) const +{ + return m_certificateProperties.size(); +} + +static QString propertyVisibleName(CertificateModel::Property p) +{ + switch (p) { + case CertificateModel::Version: + return i18n("Version"); + case CertificateModel::SerialNumber: + return i18n("Serial Number"); + case CertificateModel::Issuer: + return i18n("Issuer"); + case CertificateModel::IssuedOn: + return i18n("Issued On"); + case CertificateModel::ExpiresOn: + return i18n("Expires On"); + case CertificateModel::Subject: + return i18nc("The person/company that made the signature", "Subject"); + case CertificateModel::PublicKey: + return i18n("Public Key"); + case CertificateModel::KeyUsage: + return i18n("Key Usage"); + case CertificateModel::IssuerName: + case CertificateModel::IssuerEmail: + case CertificateModel::IssuerOrganization: + case CertificateModel::SubjectName: + case CertificateModel::SubjectEmail: + case CertificateModel::SubjectOrganization: + case CertificateModel::Sha1: + case CertificateModel::Sha256: + Q_ASSERT(false); + qWarning() << "Unimplemented"; + } + return QString(); +} + +QString CertificateModel::propertyVisibleValue(CertificateModel::Property p) const +{ + switch (p) { + case CertificateModel::Version: + return i18n("V%1", QString::number(m_certificateInfo.version())); + case CertificateModel::SerialNumber: + return m_certificateInfo.serialNumber().toHex(' '); + case CertificateModel::Issuer: + return m_certificateInfo.issuerInfo(Okular::CertificateInfo::DistinguishedName); + case CertificateModel::IssuedOn: + return m_certificateInfo.validityStart().toString(Qt::DefaultLocaleLongDate); + case CertificateModel::ExpiresOn: + return m_certificateInfo.validityEnd().toString(Qt::DefaultLocaleLongDate); + case CertificateModel::Subject: + return m_certificateInfo.subjectInfo(Okular::CertificateInfo::DistinguishedName); + case CertificateModel::PublicKey: + return i18n("%1 (%2 bits)", SignatureGuiUtils::getReadablePublicKeyType(m_certificateInfo.publicKeyType()), m_certificateInfo.publicKeyStrength()); + case CertificateModel::KeyUsage: + return SignatureGuiUtils::getReadableKeyUsageCommaSeparated(m_certificateInfo.keyUsageExtensions()); + case CertificateModel::IssuerName: + return m_certificateInfo.issuerInfo(Okular::CertificateInfo::CommonName); + case CertificateModel::IssuerEmail: + return m_certificateInfo.issuerInfo(Okular::CertificateInfo::EmailAddress); + case CertificateModel::IssuerOrganization: + return m_certificateInfo.issuerInfo(Okular::CertificateInfo::Organization); + case CertificateModel::SubjectName: + return m_certificateInfo.subjectInfo(Okular::CertificateInfo::CommonName); + case CertificateModel::SubjectEmail: + return m_certificateInfo.subjectInfo(Okular::CertificateInfo::EmailAddress); + case CertificateModel::SubjectOrganization: + return m_certificateInfo.subjectInfo(Okular::CertificateInfo::Organization); + case CertificateModel::Sha1: + return QCryptographicHash::hash(m_certificateInfo.certificateData(), QCryptographicHash::Sha1).toHex(' '); + case CertificateModel::Sha256: + return QCryptographicHash::hash(m_certificateInfo.certificateData(), QCryptographicHash::Sha256).toHex(' '); + } + return QString(); +} + +QVariant CertificateModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + if (!index.isValid() || row < 0 || row >= m_certificateProperties.count()) + return QVariant(); + + switch (role) { + case Qt::DisplayRole: + case Qt::ToolTipRole: + switch (index.column()) { + case 0: + return propertyVisibleName(m_certificateProperties[row]); + case 1: + return propertyVisibleValue(m_certificateProperties[row]); + default: + return QString(); + } + case PropertyKeyRole: + return m_certificateProperties[row]; + case PropertyVisibleValueRole: + return propertyVisibleValue(m_certificateProperties[row]); + } + + return QVariant(); +} + +QVariant CertificateModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::TextAlignmentRole) + return QVariant(Qt::AlignLeft); + + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + + switch (section) { + case 0: + return i18n("Property"); + case 1: + return i18n("Value"); + default: + return QVariant(); + } +} + +bool CertificateModel::exportCertificateTo(const QString &path) +{ + const QUrl url = QUrl::fromUserInput(path); + if (!url.isLocalFile()) { + return false; + } + QFile targetFile(url.toLocalFile()); + if (!targetFile.open(QIODevice::WriteOnly)) { + return false; + } + const QByteArray data = m_certificateInfo.certificateData(); + return targetFile.write(data) == data.size(); +} diff --git a/gui/certificatemodel.h b/gui/certificatemodel.h new file mode 100644 index 000000000..a3897f287 --- /dev/null +++ b/gui/certificatemodel.h @@ -0,0 +1,40 @@ +/* + SPDX-FileCopyrightText: 2018 Chinmoy Ranjan Pradhan + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef OKULAR_CERTIFICATEMODEL_H +#define OKULAR_CERTIFICATEMODEL_H + +#include +#include + +#include "core/signatureutils.h" + +class CertificateModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit CertificateModel(const Okular::CertificateInfo &certInfo, QObject *parent = nullptr); + + enum { PropertyKeyRole = Qt::UserRole, PropertyVisibleValueRole }; + + enum Property { Version, SerialNumber, Issuer, IssuedOn, ExpiresOn, Subject, PublicKey, KeyUsage, IssuerName, IssuerEmail, IssuerOrganization, SubjectName, SubjectEmail, SubjectOrganization, Sha1, Sha256 }; + Q_ENUM(Property) + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + Q_INVOKABLE QString propertyVisibleValue(CertificateModel::Property p) const; + Q_INVOKABLE bool exportCertificateTo(const QString &path); + +private: + QVector m_certificateProperties; + const Okular::CertificateInfo &m_certificateInfo; +}; + +#endif diff --git a/part/debug_ui.cpp b/gui/debug_ui.cpp similarity index 100% rename from part/debug_ui.cpp rename to gui/debug_ui.cpp diff --git a/part/debug_ui.h b/gui/debug_ui.h similarity index 100% rename from part/debug_ui.h rename to gui/debug_ui.h diff --git a/part/guiutils.cpp b/gui/guiutils.cpp similarity index 100% rename from part/guiutils.cpp rename to gui/guiutils.cpp diff --git a/part/guiutils.h b/gui/guiutils.h similarity index 100% rename from part/guiutils.h rename to gui/guiutils.h diff --git a/part/pagepainter.cpp b/gui/pagepainter.cpp similarity index 100% rename from part/pagepainter.cpp rename to gui/pagepainter.cpp diff --git a/part/pagepainter.h b/gui/pagepainter.h similarity index 100% rename from part/pagepainter.h rename to gui/pagepainter.h diff --git a/part/priorities.h b/gui/priorities.h similarity index 100% rename from part/priorities.h rename to gui/priorities.h diff --git a/part/signatureguiutils.cpp b/gui/signatureguiutils.cpp similarity index 53% rename from part/signatureguiutils.cpp rename to gui/signatureguiutils.cpp index 4206e8390..c6cc89bb4 100644 --- a/part/signatureguiutils.cpp +++ b/gui/signatureguiutils.cpp @@ -6,22 +6,15 @@ #include "signatureguiutils.h" -#include -#include -#include -#include - #include -#include #include "core/document.h" #include "core/form.h" #include "core/page.h" -#include "pageview.h" namespace SignatureGuiUtils { -QVector getSignatureFormFields(Okular::Document *doc) +QVector getSignatureFormFields(const Okular::Document *doc) { uint curPage = 0; const uint endPage = doc->pages() - 1; @@ -152,106 +145,65 @@ QString getReadableKeyUsageNewLineSeparated(Okular::CertificateInfo::KeyUsageExt return getReadableKeyUsage(kuExtensions, QStringLiteral("\n")); } -std::unique_ptr getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword) +QString getReadableModificationSummary(const Okular::SignatureInfo &signatureInfo) { - const Okular::CertificateStore *certStore = doc->certificateStore(); - bool userCancelled, nonDateValidCerts; - QList certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts); - if (userCancelled) { - return nullptr; - } - - if (certs.isEmpty()) { - pageView->showNoSigningCertificatesDialog(nonDateValidCerts); - return nullptr; - } - - QStringList items; - QHash nickToCert; - for (auto cert : qAsConst(certs)) { - items.append(cert->nickName()); - nickToCert[cert->nickName()] = cert; - } - - bool resok = false; - const QString certNicknameToUse = QInputDialog::getItem(pageView, i18n("Select certificate to sign with"), i18n("Certificates:"), items, 0, false, &resok); - - if (!resok) { - qDeleteAll(certs); - return nullptr; - } - - // I could not find any case in which i need to enter a password to use the certificate, seems that once you unlcok the firefox/NSS database - // you don't need a password anymore, but still there's code to do that in NSS so we have code to ask for it if needed. What we do is - // ask if the empty password is fine, if it is we don't ask the user anything, if it's not, we ask for a password - Okular::CertificateInfo *cert = nickToCert.value(certNicknameToUse); - bool passok = cert->checkPassword(*password); - while (!passok) { - const QString title = i18n("Enter password (if any) to unlock certificate: %1", certNicknameToUse); - bool ok; - *password = QInputDialog::getText(pageView, i18n("Enter certificate password"), title, QLineEdit::Password, QString(), &ok); - if (ok) { - passok = cert->checkPassword(*password); + const Okular::SignatureInfo::SignatureStatus signatureStatus = signatureInfo.signatureStatus(); + // signature validation status + if (signatureStatus == Okular::SignatureInfo::SignatureValid) { + if (signatureInfo.signsTotalDocument()) { + return i18n("The document has not been modified since it was signed."); } else { - passok = false; - break; + return i18n( + "The revision of the document that was covered by this signature has not been modified;\n" + "however there have been subsequent changes to the document."); } + } else if (signatureStatus == Okular::SignatureInfo::SignatureDigestMismatch) { + return i18n("The document has been modified in a way not permitted by a previous signer."); + } else { + return i18n("The document integrity verification could not be completed."); } - - if (doc->metaData(QStringLiteral("DocumentHasPassword")).toString() == QLatin1String("yes")) { - *documentPassword = QInputDialog::getText(pageView, i18n("Enter document password"), i18n("Enter document password"), QLineEdit::Password, QString(), &passok); - } - - if (passok) { - certs.removeOne(cert); - } - qDeleteAll(certs); - - return passok ? std::unique_ptr(cert) : std::unique_ptr(); -} - -QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc) -{ - QMimeDatabase db; - const QString typeName = doc->documentInfo().get(Okular::DocumentInfo::MimeType); - const QMimeType mimeType = db.mimeTypeForName(typeName); - const QString mimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' '))); - - const QUrl currentFileUrl = doc->currentDocument(); - const QFileInfo currentFileInfo(currentFileUrl.fileName()); - const QString localFilePathIfAny = currentFileUrl.isLocalFile() ? QFileInfo(currentFileUrl.path()).canonicalPath() + QLatin1Char('/') : QString(); - const QString newFileName = - localFilePathIfAny + i18nc("Used when suggesting a new name for a digitally signed file. %1 is the old file name and %2 it's extension", "%1_signed.%2", currentFileInfo.baseName(), currentFileInfo.completeSuffix()); - - return QFileDialog::getSaveFileName(pageView, i18n("Save Signed File As"), newFileName, mimeTypeFilter); } -void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc) +std::pair documentSignatureMessageWidgetText(const Okular::Document *doc) { - Q_ASSERT(form && form->signatureType() == Okular::FormFieldSignature::UnsignedSignature); - QString password, documentPassword; - const std::unique_ptr cert = SignatureGuiUtils::getCertificateAndPasswordForSigning(pageView, doc, &password, &documentPassword); - if (!cert) { - return; + const uint numPages = doc->pages(); + bool isDigitallySigned = false; + for (uint i = 0; i < numPages; i++) { + const QLinkedList formFields = doc->page(i)->formFields(); + for (const Okular::FormField *f : formFields) { + if (f->type() == Okular::FormField::FormSignature) + isDigitallySigned = true; + } } - Okular::NewSignatureData data; - data.setCertNickname(cert->nickName()); - data.setCertSubjectCommonName(cert->subjectInfo(Okular::CertificateInfo::CommonName)); - data.setPassword(password); - data.setDocumentPassword(documentPassword); - password.clear(); - documentPassword.clear(); - - const QString newFilePath = SignatureGuiUtils::getFileNameForNewSignedFile(pageView, doc); + if (isDigitallySigned) { + const QVector signatureFormFields = SignatureGuiUtils::getSignatureFormFields(doc); + bool allSignaturesValid = true; + bool anySignatureUnsigned = false; + for (const Okular::FormFieldSignature *signature : signatureFormFields) { + if (signature->signatureType() == Okular::FormFieldSignature::UnsignedSignature) { + anySignatureUnsigned = true; + } else { + const Okular::SignatureInfo &info = signature->signatureInfo(); + if (info.signatureStatus() != Okular::SignatureInfo::SignatureValid) { + allSignaturesValid = false; + } + } + } - if (!newFilePath.isEmpty()) { - const bool success = form->sign(data, newFilePath); - if (success) { - emit pageView->requestOpenFile(newFilePath, form->page()->number() + 1); + if (anySignatureUnsigned) { + return {KMessageWidget::Information, i18n("This document has unsigned signature fields.")}; + } else if (allSignaturesValid) { + if (signatureFormFields.last()->signatureInfo().signsTotalDocument()) { + return {KMessageWidget::Information, i18n("This document is digitally signed.")}; + } else { + return {KMessageWidget::Warning, i18n("This document is digitally signed. There have been changes since last signed.")}; + } } else { - KMessageBox::error(pageView, i18nc("%1 is a file path", "Could not sign. Invalid certificate password or could not write to '%1'", newFilePath)); + return {KMessageWidget::Warning, i18n("This document is digitally signed. Some of the signatures could not be validated properly.")}; } } + + return {KMessageWidget::Information, QString()}; } } diff --git a/part/signatureguiutils.h b/gui/signatureguiutils.h similarity index 71% rename from part/signatureguiutils.h rename to gui/signatureguiutils.h index 95fa0bba0..538878ab2 100644 --- a/part/signatureguiutils.h +++ b/gui/signatureguiutils.h @@ -11,9 +11,7 @@ #include "core/signatureutils.h" -#include - -class PageView; +#include namespace Okular { @@ -26,17 +24,16 @@ namespace SignatureGuiUtils /** * Returns a vector containing signature form fields sorted by date (last is newer). */ -QVector getSignatureFormFields(Okular::Document *doc); +QVector getSignatureFormFields(const Okular::Document *doc); QString getReadableSignatureStatus(Okular::SignatureInfo::SignatureStatus sigStatus); QString getReadableCertStatus(Okular::SignatureInfo::CertificateStatus certStatus); QString getReadableHashAlgorithm(Okular::SignatureInfo::HashAlgorithm hashAlg); QString getReadablePublicKeyType(Okular::CertificateInfo::PublicKeyType type); QString getReadableKeyUsageCommaSeparated(Okular::CertificateInfo::KeyUsageExtensions kuExtensions); QString getReadableKeyUsageNewLineSeparated(Okular::CertificateInfo::KeyUsageExtensions kuExtensions); +QString getReadableModificationSummary(const Okular::SignatureInfo &signatureInfo); -std::unique_ptr getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword); -QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc); -void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc); +std::pair documentSignatureMessageWidgetText(const Okular::Document *doc); } #endif diff --git a/part/signaturemodel.cpp b/gui/signaturemodel.cpp similarity index 67% rename from part/signaturemodel.cpp rename to gui/signaturemodel.cpp index 98c54643e..7d6049fbd 100644 --- a/part/signaturemodel.cpp +++ b/gui/signaturemodel.cpp @@ -5,10 +5,13 @@ */ #include "signaturemodel.h" + +#include "certificatemodel.h" #include "signatureguiutils.h" #include +#include #include #include #include @@ -73,6 +76,7 @@ public: SignatureModel *q; SignatureItem *root; QPointer document; + mutable QHash certificateForForm; }; SignatureModelPrivate::SignatureModelPrivate(SignatureModel *qq) @@ -83,6 +87,7 @@ SignatureModelPrivate::SignatureModelPrivate(SignatureModel *qq) SignatureModelPrivate::~SignatureModelPrivate() { + qDeleteAll(certificateForForm); delete root; } @@ -120,6 +125,7 @@ void SignatureModelPrivate::notifySetup(const QVector &pages, in if (pages.isEmpty()) { q->endResetModel(); + emit q->countChanged(); return; } @@ -163,6 +169,7 @@ void SignatureModelPrivate::notifySetup(const QVector &pages, in } } q->endResetModel(); + emit q->countChanged(); } QModelIndex SignatureModelPrivate::indexForItem(SignatureItem *item) const @@ -206,13 +213,15 @@ QVariant SignatureModel::data(const QModelIndex &index, int role) const if (item == d->root) return QVariant(); + const Okular::FormFieldSignature *form = item->form ? item->form : item->parent->form; + switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: return item->displayString; case Qt::DecorationRole: if (item->type == SignatureItem::RevisionInfo) { - const Okular::SignatureInfo::SignatureStatus signatureStatus = item->form->signatureInfo().signatureStatus(); + const Okular::SignatureInfo::SignatureStatus signatureStatus = form->signatureInfo().signatureStatus(); switch (signatureStatus) { case Okular::SignatureInfo::SignatureValid: return QIcon::fromTheme(QStringLiteral("dialog-ok")); @@ -226,9 +235,42 @@ QVariant SignatureModel::data(const QModelIndex &index, int role) const } return QIcon(); case FormRole: - return QVariant::fromValue(item->form); + return QVariant::fromValue(form); case PageRole: return item->page; + case ReadableStatusRole: + return SignatureGuiUtils::getReadableSignatureStatus(form->signatureInfo().signatureStatus()); + case ReadableModificationSummary: + return SignatureGuiUtils::getReadableModificationSummary(form->signatureInfo()); + case SignerNameRole: + return form->signatureInfo().signerName(); + case SigningTimeRole: + return form->signatureInfo().signingTime().toString(Qt::DefaultLocaleLongDate); + case SigningLocationRole: + return form->signatureInfo().location(); + case SigningReasonRole: + return form->signatureInfo().reason(); + case CertificateModelRole: { + auto it = d->certificateForForm.constFind(form); + if (it != d->certificateForForm.constEnd()) { + return QVariant::fromValue(it.value()); + } + CertificateModel *cm = new CertificateModel(form->signatureInfo().certificateInfo()); + d->certificateForForm.insert(form, cm); + return QVariant::fromValue(cm); + } + case SignatureRevisionIndexRole: { + const Okular::SignatureInfo &signatureInfo = form->signatureInfo(); + const Okular::SignatureInfo::SignatureStatus signatureStatus = signatureInfo.signatureStatus(); + if (signatureStatus != Okular::SignatureInfo::SignatureStatusUnknown && !signatureInfo.signsTotalDocument()) { + const QVector signatureFormFields = SignatureGuiUtils::getSignatureFormFields(d->document); + return signatureFormFields.indexOf(form); + } + return -1; + } + case IsUnsignedSignatureRole: { + return form->signatureType() == Okular::FormFieldSignature::UnsignedSignature; + } } return QVariant(); @@ -276,4 +318,52 @@ int SignatureModel::rowCount(const QModelIndex &parent) const return item->children.count(); } +QHash SignatureModel::roleNames() const +{ + static QHash res; + if (res.isEmpty()) { + res = QAbstractItemModel::roleNames(); + res.insert(FormRole, "signatureFormField"); + res.insert(PageRole, "page"); + res.insert(ReadableStatusRole, "readableStatus"); + res.insert(ReadableModificationSummary, "readableModificationSummary"); + res.insert(SignerNameRole, "signerName"); + res.insert(SigningTimeRole, "signingTime"); + res.insert(SigningLocationRole, "signingLocation"); + res.insert(SigningReasonRole, "signingReason"); + res.insert(CertificateModelRole, "certificateModel"); + res.insert(SignatureRevisionIndexRole, "signatureRevisionIndex"); + res.insert(IsUnsignedSignatureRole, "isUnsignedSignature"); + } + + return res; +} + +bool SignatureModel::saveSignedVersion(int signatureRevisionIndex, const QUrl &filePath) const +{ + Q_D(const SignatureModel); + const QVector signatureFormFields = SignatureGuiUtils::getSignatureFormFields(d->document); + if (signatureRevisionIndex < 0 || signatureRevisionIndex >= signatureFormFields.count()) { + qWarning() << "Invalid signatureRevisionIndex given to saveSignedVersion"; + return false; + } + const Okular::FormFieldSignature *signature = signatureFormFields[signatureRevisionIndex]; + const QByteArray data = d->document->requestSignedRevisionData(signature->signatureInfo()); + + if (!filePath.isLocalFile()) { + qWarning() << "Unexpected non local path given to saveSignedVersion" << filePath; + return false; + } + QFile f(filePath.toLocalFile()); + if (!f.open(QIODevice::WriteOnly)) { + qWarning() << "Failed to open path for writing in saveSignedVersion" << filePath; + return false; + } + if (f.write(data) != data.size()) { + qWarning() << "Failed to write all data in saveSignedVersion" << filePath; + return false; + } + return true; +} + #include "moc_signaturemodel.cpp" diff --git a/part/signaturemodel.h b/gui/signaturemodel.h similarity index 61% rename from part/signaturemodel.h rename to gui/signaturemodel.h index f4ecb4627..99d36fa1d 100644 --- a/part/signaturemodel.h +++ b/gui/signaturemodel.h @@ -20,8 +20,22 @@ class SignatureModel : public QAbstractItemModel { Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + public: - enum { FormRole = Qt::UserRole + 1000, PageRole }; + enum { + FormRole = Qt::UserRole + 1000, + PageRole, + ReadableStatusRole, + ReadableModificationSummary, + SignerNameRole, + SigningTimeRole, + SigningLocationRole, + SigningReasonRole, + CertificateModelRole, + SignatureRevisionIndexRole, + IsUnsignedSignatureRole + }; explicit SignatureModel(Okular::Document *doc, QObject *parent = nullptr); ~SignatureModel() override; @@ -33,6 +47,18 @@ public: QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int count() const + { + return rowCount(); + } + + QHash roleNames() const override; + + Q_INVOKABLE bool saveSignedVersion(int signatureRevisionIndex, const QUrl &filePath) const; + +Q_SIGNALS: + void countChanged(); + private: Q_DECLARE_PRIVATE(SignatureModel) QScopedPointer d_ptr; diff --git a/part/tocmodel.cpp b/gui/tocmodel.cpp similarity index 98% rename from part/tocmodel.cpp rename to gui/tocmodel.cpp index 9198f909d..365ccc517 100644 --- a/part/tocmodel.cpp +++ b/gui/tocmodel.cpp @@ -189,8 +189,8 @@ TOCModel::~TOCModel() QHash TOCModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); - roles[(int)PageItemDelegate::PageRole] = "page"; - roles[(int)PageItemDelegate::PageLabelRole] = "pageLabel"; + roles[PageRole] = "page"; + roles[PageLabelRole] = "pageLabel"; roles[HighlightRole] = "highlight"; roles[HighlightedParentRole] = "highlightedParent"; return roles; @@ -240,11 +240,11 @@ QVariant TOCModel::data(const QModelIndex &index, int role) const break; case HighlightRole: return item->highlight; - case PageItemDelegate::PageRole: + case PageRole: if (item->viewport.isValid()) return item->viewport.pageNumber + 1; break; - case PageItemDelegate::PageLabelRole: + case PageLabelRole: if (item->viewport.isValid() && item->viewport.pageNumber < int(d->document->pages())) return d->document->page(item->viewport.pageNumber)->label(); break; diff --git a/part/tocmodel.h b/gui/tocmodel.h similarity index 94% rename from part/tocmodel.h rename to gui/tocmodel.h index e76a80404..c74dd9792 100644 --- a/part/tocmodel.h +++ b/gui/tocmodel.h @@ -7,7 +7,6 @@ #ifndef TOCMODEL_H #define TOCMODEL_H -#include "pageitemdelegate.h" #include #include @@ -29,7 +28,7 @@ class TOCModel : public QAbstractItemModel Q_PROPERTY(int count READ count NOTIFY countChanged) public: - enum Roles { HighlightRole = PageItemDelegate::PageLabelRole + 1, HighlightedParentRole }; + enum Roles { PageRole = 0x000f0001, PageLabelRole, HighlightRole, HighlightedParentRole }; explicit TOCModel(Okular::Document *document, QObject *parent = nullptr); ~TOCModel() override; diff --git a/mobile/app/app.qrc b/mobile/app/app.qrc index b75bd1a25..4dd55389b 100644 --- a/mobile/app/app.qrc +++ b/mobile/app/app.qrc @@ -1,5 +1,6 @@ +package/contents/ui/CertificateViewerDialog.qml package/contents/ui/Bookmarks.qml package/contents/ui/main.qml package/contents/ui/MainView.qml @@ -9,5 +10,7 @@ package/contents/ui/ThumbnailsBase.qml package/contents/ui/TreeItem.qml package/contents/ui/TreeViewDecoration.qml +package/contents/ui/Signatures.qml +package/contents/ui/SignaturePropertiesDialog.qml diff --git a/mobile/app/package/contents/ui/CertificateViewerDialog.qml b/mobile/app/package/contents/ui/CertificateViewerDialog.qml new file mode 100644 index 000000000..22648f57e --- /dev/null +++ b/mobile/app/package/contents/ui/CertificateViewerDialog.qml @@ -0,0 +1,173 @@ +/* + * SPDX-FileCopyrightText: 2022 Albert Astals Cid + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Dialogs 1.3 as QQD +import QtQuick.Layouts 1.15 +import org.kde.kirigami 2.17 as Kirigami +import org.kde.okular 2.0 + +Kirigami.OverlaySheet +{ + id: root + + property var certificateModel + + title: i18n("Certificate Viewer") + + ColumnLayout { + // Without this the width is unreasonably narrow, potentially + // https://invent.kde.org/frameworks/kirigami/-/merge_requests/487 fixes it + // check when a kirigami with that is required as minimum version + Layout.preferredWidth: Math.min(Window.window.width, 360) + + QQC2.GroupBox { + Layout.fillWidth: true + + title: i18n("Issued By") + + Kirigami.FormLayout { + width: parent.width + QQC2.Label { + Kirigami.FormData.label: i18n("Common Name:") + text: certificateModel.propertyVisibleValue(CertificateModel.IssuerName) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + Kirigami.FormData.label: i18n("EMail:") + text: certificateModel.propertyVisibleValue(CertificateModel.IssuerEmail) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + Kirigami.FormData.label: i18n("Organization:") + text: certificateModel.propertyVisibleValue(CertificateModel.IssuerOrganization) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + + QQC2.GroupBox { + Layout.fillWidth: true + + title: i18n("Issued To") + + Kirigami.FormLayout { + width: parent.width + QQC2.Label { + Kirigami.FormData.label: i18n("Common Name:") + text: certificateModel.propertyVisibleValue(CertificateModel.SubjectName) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + Kirigami.FormData.label: i18n("EMail:") + text: certificateModel.propertyVisibleValue(CertificateModel.SubjectEmail) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + Kirigami.FormData.label: i18n("Organization:") + text: certificateModel.propertyVisibleValue(CertificateModel.SubjectOrganization) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + + QQC2.GroupBox { + Layout.fillWidth: true + + title: i18n("Validity") + + Kirigami.FormLayout { + width: parent.width + QQC2.Label { + Kirigami.FormData.label: i18n("Issued On:") + text: certificateModel.propertyVisibleValue(CertificateModel.IssuedOn) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + Kirigami.FormData.label: i18n("Expires On:") + text: certificateModel.propertyVisibleValue(CertificateModel.ExpiresOn) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + + QQC2.GroupBox { + Layout.fillWidth: true + + title: i18n("Fingerprints") + + Kirigami.FormLayout { + width: parent.width + QQC2.Label { + Kirigami.FormData.label: i18n("SHA-1 Fingerprint:") + text: certificateModel.propertyVisibleValue(CertificateModel.Sha1) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + Kirigami.FormData.label: i18n("SHA-256 Fingerprint:") + text: certificateModel.propertyVisibleValue(CertificateModel.Sha256) + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + + QQC2.DialogButtonBox { + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.fillWidth: true + + QQC2.Button { + QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.ActionRole + text: i18n("Export...") + onClicked: fileDialog.open() + } + + QQC2.Button { + QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.DestructiveRole + text: i18n("Close") + icon.name: "dialog-close" + onClicked: root.close() + } + } + + QQD.FileDialog { + id: fileDialog + nameFilters: i18n("Certificate File (*.cer)") + folder: "file://" + userPaths.documents + selectExisting: false + onAccepted: { + if (!certificateModel.exportCertificateTo(fileDialog.fileUrl)) { + errorDialog.open(); + } + } + } + + // TODO Use Kirigami.PromptDialog when we depend on KF >= 5.89 + // this way we can probably remove that ridiculous z value + QQC2.Dialog { + id: errorDialog + z: 200 + title: i18n("Error") + contentItem: QQC2.Label { + text: i18n("Could not export the certificate.") + } + standardButtons: QQC2.Dialog.Ok + + onAccepted: close(); + } + } +} diff --git a/mobile/app/package/contents/ui/OkularDrawer.qml b/mobile/app/package/contents/ui/OkularDrawer.qml index eeb7c602c..e5d8d93cb 100644 --- a/mobile/app/package/contents/ui/OkularDrawer.qml +++ b/mobile/app/package/contents/ui/OkularDrawer.qml @@ -12,6 +12,8 @@ import QtQuick.Layouts 1.15 Kirigami.OverlayDrawer { + id: root + bottomPadding: 0 topPadding: 0 leftPadding: 0 @@ -91,6 +93,32 @@ Kirigami.OverlayDrawer { } QQC2.ButtonGroup.group: tabPositionGroup } + QQC2.ToolButton { + id: signatyresButton + enabled: documentItem.signaturesModel.count > 0 + text: tabsToolbar.width > Kirigami.Units.gridUnit * 30 ? i18n("Signatures") : "" + icon.name: "application-pkcs7-signature" + checkable: true + flat: false + onCheckedChanged: { + if (checked) { + pageStack.replace(signaturesComponent) + } + } + QQC2.ButtonGroup.group: tabPositionGroup + } + } + } + } + } + + Component { + id: signaturesComponent + Signatures { + onDialogOpened: { + // We don't want to have two modal things open at the same time + if (root.modal) { + root.close(); } } } diff --git a/mobile/app/package/contents/ui/SignaturePropertiesDialog.qml b/mobile/app/package/contents/ui/SignaturePropertiesDialog.qml new file mode 100644 index 000000000..62da37a7d --- /dev/null +++ b/mobile/app/package/contents/ui/SignaturePropertiesDialog.qml @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2022 Albert Astals Cid + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Dialogs 1.3 as QQD +import QtQuick.Layouts 1.15 +import org.kde.kirigami 2.17 as Kirigami + +Kirigami.OverlaySheet +{ + id: root + + property alias signatureValidityText: signatureValidity.text + property alias documentModificationsText: documentModifications.text + property alias signerNameText: signerName.text + property alias signingTimeText: signingTime.text + property alias signingLocationText: signingLocation.text + property alias signingReasonText: signingReason.text + + property var certificateModel + property int signatureRevisionIndex: -1 + + signal saveSignatureSignedVersion(url path) + + title: i18n("Signature Properties") + + function showErrorDialog() { + errorDialog.open(); + } + + ColumnLayout { + // Without this the width is unreasonably narrow, potentially + // https://invent.kde.org/frameworks/kirigami/-/merge_requests/487 fixes it + // check when a kirigami with that is required as minimum version + Layout.preferredWidth: Math.min(Window.window.width, 360) + + QQC2.GroupBox { + Layout.fillWidth: true + title: i18n("Validity Status") + + Kirigami.FormLayout { + width: parent.width + QQC2.Label { + id: signatureValidity + Kirigami.FormData.label: i18n("Signature Validity:") + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + id: documentModifications + Kirigami.FormData.label: i18n("Document Modifications:") + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + QQC2.GroupBox { + title: i18n("Additional Information") + Layout.fillWidth: true + + Kirigami.FormLayout { + id: additionalInformationLayout + + width: parent.width + QQC2.Label { + id: signerName + Kirigami.FormData.label: i18n("Signed By:") + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + id: signingTime + Kirigami.FormData.label: i18n("Signing Time:") + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + id: signingReason + Kirigami.FormData.label: i18n("Reason:") + visible: text + wrapMode: Text.Wrap + Layout.fillWidth: true + } + QQC2.Label { + id: signingLocation + Kirigami.FormData.label: i18n("Location:") + visible: text + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + + QQC2.GroupBox { + title: i18n("Document Version") + Layout.fillWidth: true + + visible: root.signatureRevisionIndex >= 0 + + RowLayout { + width: parent.width + + QQC2.Label { + Layout.fillWidth: true + text: i18nc("Document Revision of ", "Document Revision %1 of %2", root.signatureRevisionIndex + 1, documentItem.signaturesModel.count) + wrapMode: Text.Wrap + } + QQC2.Button { + text: i18n("Save Signed Version...") + onClicked: { + fileDialog.open(); + } + } + } + } + + QQC2.DialogButtonBox { + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.fillWidth: true + + QQC2.Button { + QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.ActionRole + text: i18n("View Certificate...") + onClicked: { + var dialog = dialogComponent.createObject(Window.window, { + certificateModel: root.certificateModel + }) + dialog.open() + } + + Component { + id: dialogComponent + CertificateViewerDialog { + onSheetOpenChanged: if(!sheetOpen) { + destroy(1000) + } + } + } + } + + QQC2.Button { + QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.DestructiveRole + text: i18n("Close") + icon.name: "dialog-close" + onClicked: root.close() + } + } + + QQD.FileDialog { + id: fileDialog + folder: "file://" + userPaths.documents + selectExisting: false + onAccepted: { + root.saveSignatureSignedVersion(fileDialog.fileUrl); + } + } + + // TODO Use Kirigami.PromptDialog when we depend on KF >= 5.89 + // this way we can probably remove that ridiculous z value + QQC2.Dialog { + id: errorDialog + z: 200 + title: i18n("Error") + contentItem: QQC2.Label { + text: i18n("Could not save the signature.") + } + standardButtons: QQC2.Dialog.Ok + + onAccepted: close(); + } + } +} diff --git a/mobile/app/package/contents/ui/Signatures.qml b/mobile/app/package/contents/ui/Signatures.qml new file mode 100644 index 000000000..a9d1c680d --- /dev/null +++ b/mobile/app/package/contents/ui/Signatures.qml @@ -0,0 +1,64 @@ +/* + SPDX-FileCopyrightText: 2012 Marco Martin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Window 2.15 +import org.kde.kirigami 2.17 as Kirigami +import org.kde.kitemmodels 1.0 + +QQC2.ScrollView { + id: root + + signal dialogOpened + + ListView { + model: KDescendantsProxyModel { + model: documentItem.signaturesModel + expandsByDefault: false + } + + delegate: TreeItem { + + function displayString(str) { + return str ? str : i18n("Not Available"); + } + + text: model.display + onClicked: { + if (!model.isUnsignedSignature) { + var dialog = dialogComponent.createObject(Window.window, { + signatureValidityText: model.readableStatus, + documentModificationsText: model.readableModificationSummary, + signerNameText: displayString(model.signerName), + signingTimeText: displayString(model.signingTime), + signingLocationText: model.signingLocation, + signingReasonText: model.signingReason, + certificateModel: model.certificateModel, + signatureRevisionIndex: model.signatureRevisionIndex + }) + dialog.open() + root.dialogOpened(); + } + } + } + + Component { + id: dialogComponent + SignaturePropertiesDialog { + id: dialog + onSheetOpenChanged: if(!sheetOpen) { + destroy(1000) + } + onSaveSignatureSignedVersion: (path) => { + if (!documentItem.signaturesModel.saveSignedVersion(signatureRevisionIndex, path)) { + dialog.showErrorDialog(); + } + } + } + } + } +} diff --git a/mobile/components/CMakeLists.txt b/mobile/components/CMakeLists.txt index 2b6ee0ea4..e4c302812 100644 --- a/mobile/components/CMakeLists.txt +++ b/mobile/components/CMakeLists.txt @@ -1,9 +1,12 @@ set(okular_SRCS okularplugin.cpp - ${CMAKE_SOURCE_DIR}/part/guiutils.cpp - ${CMAKE_SOURCE_DIR}/part/tocmodel.cpp - ${CMAKE_SOURCE_DIR}/part/pagepainter.cpp - ${CMAKE_SOURCE_DIR}/part/debug_ui.cpp + ${CMAKE_SOURCE_DIR}/gui/guiutils.cpp + ${CMAKE_SOURCE_DIR}/gui/tocmodel.cpp + ${CMAKE_SOURCE_DIR}/gui/signaturemodel.cpp + ${CMAKE_SOURCE_DIR}/gui/signatureguiutils.cpp + ${CMAKE_SOURCE_DIR}/gui/certificatemodel.cpp + ${CMAKE_SOURCE_DIR}/gui/pagepainter.cpp + ${CMAKE_SOURCE_DIR}/gui/debug_ui.cpp pageitem.cpp documentitem.cpp thumbnailitem.cpp diff --git a/mobile/components/documentitem.cpp b/mobile/components/documentitem.cpp index f7ae4be6c..7b9ceac75 100644 --- a/mobile/components/documentitem.cpp +++ b/mobile/components/documentitem.cpp @@ -18,7 +18,9 @@ #include #include -#include "part/tocmodel.h" +#include "gui/signatureguiutils.h" +#include "gui/signaturemodel.h" +#include "gui/tocmodel.h" DocumentItem::DocumentItem(QObject *parent) : QObject(parent) @@ -27,9 +29,11 @@ DocumentItem::DocumentItem(QObject *parent) , m_searchInProgress(false) { qmlRegisterUncreatableType("org.kde.okular.private", 1, 0, "TOCModel", QStringLiteral("Do not create objects of this type.")); + qmlRegisterUncreatableType("org.kde.okular.private", 1, 0, "SignatureModel", QStringLiteral("Do not create objects of this type.")); Okular::Settings::instance(QStringLiteral("okularproviderrc")); m_document = new Okular::Document(nullptr); m_tocModel = new TOCModel(m_document, this); + m_signaturesModel = new SignatureModel(m_document, this); connect(m_document, &Okular::Document::searchFinished, this, &DocumentItem::searchFinished); connect(m_document->bookmarkManager(), &Okular::BookmarkManager::bookmarksChanged, this, &DocumentItem::bookmarkedPagesChanged); @@ -41,6 +45,7 @@ DocumentItem::DocumentItem(QObject *parent) DocumentItem::~DocumentItem() { + delete m_signaturesModel; delete m_document; } @@ -75,6 +80,19 @@ void DocumentItem::setUrl(const QUrl &url) emit supportsSearchingChanged(); emit windowTitleForDocumentChanged(); emit bookmarkedPagesChanged(); + + KMessageWidget::MessageType messageType; + QString message; + std::tie(messageType, message) = SignatureGuiUtils::documentSignatureMessageWidgetText(m_document); + if (!message.isEmpty()) { + if (messageType == KMessageWidget::Information) { + emit notice(message, -1); + } else if (messageType == KMessageWidget::Warning) { + emit warning(message, -1); + } else { + qWarning() << "Unexpected message type" << messageType; + } + } } QString DocumentItem::windowTitleForDocument() const @@ -131,6 +149,11 @@ TOCModel *DocumentItem::tableOfContents() const return m_tocModel; } +SignatureModel *DocumentItem::signaturesModel() const +{ + return m_signaturesModel; +} + QVariantList DocumentItem::bookmarkedPages() const { QList list; diff --git a/mobile/components/documentitem.h b/mobile/components/documentitem.h index 16a708e95..621163979 100644 --- a/mobile/components/documentitem.h +++ b/mobile/components/documentitem.h @@ -20,6 +20,7 @@ class Document; } class Observer; +class SignatureModel; class TOCModel; class DocumentItem : public QObject @@ -71,6 +72,11 @@ class DocumentItem : public QObject */ Q_PROPERTY(TOCModel *tableOfContents READ tableOfContents CONSTANT) + /** + * Signatures model, if available + */ + Q_PROPERTY(SignatureModel *signaturesModel READ signaturesModel CONSTANT) + /** * List of pages that contain a bookmark */ @@ -105,6 +111,8 @@ public: TOCModel *tableOfContents() const; + SignatureModel *signaturesModel() const; + QVariantList bookmarkedPages() const; QStringList bookmarks() const; @@ -169,6 +177,7 @@ private Q_SLOTS: private: Okular::Document *m_document; TOCModel *m_tocModel; + SignatureModel *m_signaturesModel; Observer *m_thumbnailObserver; Observer *m_pageviewObserver; QVariantList m_matchingPages; diff --git a/mobile/components/okularplugin.cpp b/mobile/components/okularplugin.cpp index b8150d8ce..e211050b2 100644 --- a/mobile/components/okularplugin.cpp +++ b/mobile/components/okularplugin.cpp @@ -7,6 +7,7 @@ #include "okularplugin.h" #include "documentitem.h" +#include "gui/certificatemodel.h" #include "okularsingleton.h" #include "pageitem.h" #include "thumbnailitem.h" @@ -28,4 +29,5 @@ void OkularPlugin::registerTypes(const char *uri) qmlRegisterType(uri, 2, 0, "DocumentItem"); qmlRegisterType(uri, 2, 0, "PageItem"); qmlRegisterType(uri, 2, 0, "ThumbnailItem"); + qmlRegisterUncreatableType(uri, 2, 0, "CertificateModel", QStringLiteral("Do not create objects of this type.")); } diff --git a/mobile/components/pageitem.cpp b/mobile/components/pageitem.cpp index 31f3220ee..a2038ab98 100644 --- a/mobile/components/pageitem.cpp +++ b/mobile/components/pageitem.cpp @@ -17,8 +17,8 @@ #include #include -#include "part/pagepainter.h" -#include "part/priorities.h" +#include "gui/pagepainter.h" +#include "gui/priorities.h" #include "settings.h" #define REDRAW_TIMEOUT 250 diff --git a/part/annotationactionhandler.cpp b/part/annotationactionhandler.cpp index 18a4685ed..66afb2d97 100644 --- a/part/annotationactionhandler.cpp +++ b/part/annotationactionhandler.cpp @@ -28,7 +28,7 @@ #include "actionbar.h" #include "annotationwidgets.h" #include "core/annotations.h" -#include "guiutils.h" +#include "gui/guiutils.h" #include "pageview.h" #include "pageviewannotator.h" #include "settings.h" diff --git a/part/annotationmodel.cpp b/part/annotationmodel.cpp index 5b775b49a..91f551d75 100644 --- a/part/annotationmodel.cpp +++ b/part/annotationmodel.cpp @@ -20,7 +20,7 @@ #include "core/document.h" #include "core/observer.h" #include "core/page.h" -#include "guiutils.h" +#include "gui/guiutils.h" struct AnnItem { AnnItem(); diff --git a/part/annotationpopup.cpp b/part/annotationpopup.cpp index 59e7c2b4b..ac7be2f9a 100644 --- a/part/annotationpopup.cpp +++ b/part/annotationpopup.cpp @@ -14,7 +14,7 @@ #include "core/annotations.h" #include "core/document.h" -#include "guiutils.h" +#include "gui/guiutils.h" #include "okmenutitle.h" Q_DECLARE_METATYPE(AnnotationPopup::AnnotPagePair) diff --git a/part/annotationproxymodels.cpp b/part/annotationproxymodels.cpp index 7c29abe90..fdf926cfb 100644 --- a/part/annotationproxymodels.cpp +++ b/part/annotationproxymodels.cpp @@ -12,7 +12,7 @@ #include #include "annotationmodel.h" -#include "debug_ui.h" +#include "gui/debug_ui.h" static quint32 mixIndex(int row, int column) { diff --git a/part/annotationwidgets.cpp b/part/annotationwidgets.cpp index baba22ed4..0ee86f6e1 100644 --- a/part/annotationwidgets.cpp +++ b/part/annotationwidgets.cpp @@ -35,8 +35,8 @@ #include "core/document.h" #include "core/document_p.h" #include "core/page_p.h" -#include "guiutils.h" -#include "pagepainter.h" +#include "gui/guiutils.h" +#include "gui/pagepainter.h" #define FILEATTACH_ICONSIZE 48 diff --git a/part/bookmarklist.cpp b/part/bookmarklist.cpp index 37345a60c..b85be8617 100644 --- a/part/bookmarklist.cpp +++ b/part/bookmarklist.cpp @@ -27,6 +27,7 @@ #include "core/action.h" #include "core/bookmarkmanager.h" #include "core/document.h" +#include "gui/tocmodel.h" #include "pageitemdelegate.h" static const int BookmarkItemType = QTreeWidgetItem::UserType + 1; @@ -46,7 +47,7 @@ public: m_url.setFragment(QString()); setText(0, m_bookmark.fullText()); if (m_viewport.isValid()) - setData(0, PageItemDelegate::PageRole, QString::number(m_viewport.pageNumber + 1)); + setData(0, TOCModel::PageRole, QString::number(m_viewport.pageNumber + 1)); } BookmarkItem(const BookmarkItem &) = delete; diff --git a/part/certificateviewer.cpp b/part/certificateviewer.cpp index d53653776..75a5e1b00 100644 --- a/part/certificateviewer.cpp +++ b/part/certificateviewer.cpp @@ -6,6 +6,8 @@ #include "certificateviewer.h" +#include "gui/certificatemodel.h" + #include #include #include @@ -22,7 +24,7 @@ #include #include -#include "signatureguiutils.h" +#include "gui/signatureguiutils.h" // DN (DistinguishedName) attributes can be // C Country @@ -94,113 +96,6 @@ static QString splitDNAttributes(const QString &text) return splitDNAttributes(QStringList {text}); } -CertificateModel::CertificateModel(const Okular::CertificateInfo &certInfo, QObject *parent) - : QAbstractTableModel(parent) - , m_certificateInfo(certInfo) -{ - m_certificateProperties = {Version, SerialNumber, Issuer, IssuedOn, ExpiresOn, Subject, PublicKey, KeyUsage}; -} - -int CertificateModel::columnCount(const QModelIndex &) const -{ - return 2; -} - -int CertificateModel::rowCount(const QModelIndex &) const -{ - return m_certificateProperties.size(); -} - -static QString propertyVisibleName(CertificateModel::Property p) -{ - switch (p) { - case CertificateModel::Version: - return i18n("Version"); - case CertificateModel::SerialNumber: - return i18n("Serial Number"); - case CertificateModel::Issuer: - return i18n("Issuer"); - case CertificateModel::IssuedOn: - return i18n("Issued On"); - case CertificateModel::ExpiresOn: - return i18n("Expires On"); - case CertificateModel::Subject: - return i18nc("The person/company that made the signature", "Subject"); - case CertificateModel::PublicKey: - return i18n("Public Key"); - case CertificateModel::KeyUsage: - return i18n("Key Usage"); - } - return QString(); -} - -static QString propertyVisibleValue(CertificateModel::Property p, const Okular::CertificateInfo &certInfo) -{ - switch (p) { - case CertificateModel::Version: - return i18n("V%1", QString::number(certInfo.version())); - case CertificateModel::SerialNumber: - return certInfo.serialNumber().toHex(' '); - case CertificateModel::Issuer: - return certInfo.issuerInfo(Okular::CertificateInfo::DistinguishedName); - case CertificateModel::IssuedOn: - return certInfo.validityStart().toString(Qt::DefaultLocaleLongDate); - case CertificateModel::ExpiresOn: - return certInfo.validityEnd().toString(Qt::DefaultLocaleLongDate); - case CertificateModel::Subject: - return certInfo.subjectInfo(Okular::CertificateInfo::DistinguishedName); - case CertificateModel::PublicKey: - return i18n("%1 (%2 bits)", SignatureGuiUtils::getReadablePublicKeyType(certInfo.publicKeyType()), certInfo.publicKeyStrength()); - case CertificateModel::KeyUsage: - return SignatureGuiUtils::getReadableKeyUsageCommaSeparated(certInfo.keyUsageExtensions()); - } - return QString(); -} - -QVariant CertificateModel::data(const QModelIndex &index, int role) const -{ - const int row = index.row(); - if (!index.isValid() || row < 0 || row >= m_certificateProperties.count()) - return QVariant(); - - switch (role) { - case Qt::DisplayRole: - case Qt::ToolTipRole: - switch (index.column()) { - case 0: - return propertyVisibleName(m_certificateProperties[row]); - case 1: - return propertyVisibleValue(m_certificateProperties[row], m_certificateInfo); - default: - return QString(); - } - case PropertyKeyRole: - return m_certificateProperties[row]; - case PropertyVisibleValueRole: - return propertyVisibleValue(m_certificateProperties[row], m_certificateInfo); - } - - return QVariant(); -} - -QVariant CertificateModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role == Qt::TextAlignmentRole) - return QVariant(Qt::AlignLeft); - - if (orientation != Qt::Horizontal || role != Qt::DisplayRole) - return QVariant(); - - switch (section) { - case 0: - return i18n("Property"); - case 1: - return i18n("Value"); - default: - return QVariant(); - } -} - CertificateViewer::CertificateViewer(const Okular::CertificateInfo &certInfo, QWidget *parent) : KPageDialog(parent) , m_certificateInfo(certInfo) @@ -303,6 +198,16 @@ void CertificateViewer::updateText(const QModelIndex &index) case CertificateModel::KeyUsage: text = SignatureGuiUtils::getReadableKeyUsageNewLineSeparated(m_certificateInfo.keyUsageExtensions()); break; + case CertificateModel::IssuerName: + case CertificateModel::IssuerEmail: + case CertificateModel::IssuerOrganization: + case CertificateModel::SubjectName: + case CertificateModel::SubjectEmail: + case CertificateModel::SubjectOrganization: + case CertificateModel::Sha1: + case CertificateModel::Sha256: + Q_ASSERT(false); + qWarning() << "Unused"; } m_propertyText->setText(text); } @@ -312,11 +217,8 @@ void CertificateViewer::exportCertificate() const QString caption = i18n("Where do you want to save this certificate?"); const QString path = QFileDialog::getSaveFileName(this, caption, QStringLiteral("Certificate.cer"), i18n("Certificate File (*.cer)")); if (!path.isEmpty()) { - QFile targetFile(path); - targetFile.open(QIODevice::WriteOnly); - if (targetFile.write(m_certificateInfo.certificateData()) == -1) { + if (!m_certificateModel->exportCertificateTo(path)) { KMessageBox::error(this, i18n("Could not export the certificate")); } - targetFile.close(); } } diff --git a/part/certificateviewer.h b/part/certificateviewer.h index 99107d969..eab1e3de7 100644 --- a/part/certificateviewer.h +++ b/part/certificateviewer.h @@ -8,11 +8,11 @@ #define OKULAR_CERTIFICATEVIEWER_H #include -#include -#include #include "core/signatureutils.h" +class CertificateModel; + class QTextEdit; namespace Okular @@ -20,28 +20,6 @@ namespace Okular class CertificateInfo; } -class CertificateModel : public QAbstractTableModel -{ - Q_OBJECT - -public: - explicit CertificateModel(const Okular::CertificateInfo &certInfo, QObject *parent = nullptr); - - enum { PropertyKeyRole = Qt::UserRole, PropertyVisibleValueRole }; - - enum Property { Version, SerialNumber, Issuer, IssuedOn, ExpiresOn, Subject, PublicKey, KeyUsage }; - Q_ENUM(Property) - - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - -private: - QVector m_certificateProperties; - const Okular::CertificateInfo &m_certificateInfo; -}; - class CertificateViewer : public KPageDialog { Q_OBJECT diff --git a/part/colormodemenu.cpp b/part/colormodemenu.cpp index 4e1e41d00..c7ed60c6b 100644 --- a/part/colormodemenu.cpp +++ b/part/colormodemenu.cpp @@ -10,7 +10,7 @@ #include #include // TODO KF6: Remove, this was needed for KActionMenu::setPopupMode(). -#include "guiutils.h" +#include "gui/guiutils.h" #include "settings.h" ColorModeMenu::ColorModeMenu(KActionCollection *ac, QObject *parent) diff --git a/part/drawingtoolactions.cpp b/part/drawingtoolactions.cpp index 3fa4f4148..d4dbb7163 100644 --- a/part/drawingtoolactions.cpp +++ b/part/drawingtoolactions.cpp @@ -7,7 +7,7 @@ #include "drawingtoolactions.h" -#include "debug_ui.h" +#include "gui/debug_ui.h" #include "settings.h" #include diff --git a/part/embeddedfilesdialog.cpp b/part/embeddedfilesdialog.cpp index 7a0600456..1331afd18 100644 --- a/part/embeddedfilesdialog.cpp +++ b/part/embeddedfilesdialog.cpp @@ -28,7 +28,7 @@ #include #include "core/document.h" -#include "guiutils.h" +#include "gui/guiutils.h" Q_DECLARE_METATYPE(Okular::EmbeddedFile *) diff --git a/part/fileprinterpreview.cpp b/part/fileprinterpreview.cpp index 3c73427d8..341df427a 100644 --- a/part/fileprinterpreview.cpp +++ b/part/fileprinterpreview.cpp @@ -25,7 +25,7 @@ #include #include -#include "debug_ui.h" +#include "gui/debug_ui.h" using namespace Okular; diff --git a/part/formwidgets.cpp b/part/formwidgets.cpp index 8ff1a8bd9..daa2838fe 100644 --- a/part/formwidgets.cpp +++ b/part/formwidgets.cpp @@ -12,7 +12,7 @@ #include "pageview.h" #include "pageviewutils.h" #include "revisionviewer.h" -#include "signatureguiutils.h" +#include "signaturepartutils.h" #include "signaturepropertiesdialog.h" #include @@ -29,7 +29,7 @@ // local includes #include "core/action.h" #include "core/document.h" -#include "debug_ui.h" +#include "gui/debug_ui.h" FormWidgetsController::FormWidgetsController(Okular::Document *doc) : QObject(doc) @@ -1165,7 +1165,7 @@ void SignatureEdit::signUnsignedSignature() Okular::FormFieldSignature *formSignature = static_cast(formField()); PageView *pageView = static_cast(parent()->parent()); - SignatureGuiUtils::signUnsignedSignature(formSignature, pageView, pageView->document()); + SignaturePartUtils::signUnsignedSignature(formSignature, pageView, pageView->document()); } // Code for additional action handling. diff --git a/part/latexrenderer.cpp b/part/latexrenderer.cpp index 07c9af417..268f5726d 100644 --- a/part/latexrenderer.cpp +++ b/part/latexrenderer.cpp @@ -22,7 +22,7 @@ #include #include -#include "debug_ui.h" +#include "gui/debug_ui.h" namespace GuiUtils { diff --git a/part/magnifierview.cpp b/part/magnifierview.cpp index 52d8bd43e..c774755d9 100644 --- a/part/magnifierview.cpp +++ b/part/magnifierview.cpp @@ -9,8 +9,8 @@ #include "core/document.h" #include "core/generator.h" -#include "pagepainter.h" -#include "priorities.h" +#include "gui/pagepainter.h" +#include "gui/priorities.h" static const int SCALE = 10; diff --git a/part/pageitemdelegate.cpp b/part/pageitemdelegate.cpp index 4da4017cc..5602e458d 100644 --- a/part/pageitemdelegate.cpp +++ b/part/pageitemdelegate.cpp @@ -13,6 +13,7 @@ #include // local includes +#include "gui/tocmodel.h" #include "settings.h" #define PAGEITEMDELEGATE_INTERNALMARGIN 3 @@ -46,8 +47,8 @@ void PageItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti void PageItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const { - QVariant pageVariant = d->index.data(PageRole); - QVariant labelVariant = d->index.data(PageLabelRole); + QVariant pageVariant = d->index.data(TOCModel::PageRole); + QVariant labelVariant = d->index.data(TOCModel::PageLabelRole); if ((labelVariant.type() != QVariant::String && !pageVariant.canConvert(QVariant::String)) || !Okular::Settings::tocPageColumn()) { QItemDelegate::drawDisplay(painter, option, rect, text); return; diff --git a/part/pageitemdelegate.h b/part/pageitemdelegate.h index 8ddc4e663..073f092bb 100644 --- a/part/pageitemdelegate.h +++ b/part/pageitemdelegate.h @@ -17,9 +17,6 @@ public: explicit PageItemDelegate(QObject *parent = nullptr); ~PageItemDelegate() override; - static const int PageRole = 0x000f0001; - static const int PageLabelRole = 0x000f0002; - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; protected: diff --git a/part/pageview.cpp b/part/pageview.cpp index 197178114..7aca68dbd 100644 --- a/part/pageview.cpp +++ b/part/pageview.cpp @@ -71,15 +71,15 @@ #include "colormodemenu.h" #include "core/annotations.h" #include "cursorwraphelper.h" -#include "debug_ui.h" #include "formwidgets.h" -#include "guiutils.h" +#include "gui/debug_ui.h" +#include "gui/guiutils.h" +#include "gui/pagepainter.h" +#include "gui/priorities.h" #include "okmenutitle.h" -#include "pagepainter.h" #include "pageviewannotator.h" #include "pageviewmouseannotation.h" #include "pageviewutils.h" -#include "priorities.h" #include "toggleactionmenu.h" #ifdef HAVE_SPEECH #include "tts.h" diff --git a/part/pageviewannotator.cpp b/part/pageviewannotator.cpp index e15d63d54..d0b7edb50 100644 --- a/part/pageviewannotator.cpp +++ b/part/pageviewannotator.cpp @@ -39,12 +39,12 @@ #include "core/document.h" #include "core/page.h" #include "core/signatureutils.h" -#include "debug_ui.h" #include "editannottooldialog.h" -#include "guiutils.h" +#include "gui/debug_ui.h" +#include "gui/guiutils.h" #include "pageview.h" #include "settings.h" -#include "signatureguiutils.h" +#include "signaturepartutils.h" /** @short PickPointEngine */ class PickPointEngine : public AnnotatorEngine @@ -354,7 +354,7 @@ public: } } - const std::unique_ptr cert = SignatureGuiUtils::getCertificateAndPasswordForSigning(m_pageView, m_document, &passToUse, &documentPassword); + const std::unique_ptr cert = SignaturePartUtils::getCertificateAndPasswordForSigning(m_pageView, m_document, &passToUse, &documentPassword); if (!cert) { m_aborted = true; passToUse.clear(); @@ -985,7 +985,7 @@ QRect PageViewAnnotator::performRouteMouseOrTabletEvent(const AnnotatorEngine::E if (signatureMode()) { auto signEngine = static_cast(m_engine); if (signEngine->isAccepted()) { - const QString newFilePath = SignatureGuiUtils::getFileNameForNewSignedFile(m_pageView, m_document); + const QString newFilePath = SignaturePartUtils::getFileNameForNewSignedFile(m_pageView, m_document); if (!newFilePath.isEmpty()) { const bool success = static_cast(m_engine)->sign(newFilePath); diff --git a/part/pageviewmouseannotation.cpp b/part/pageviewmouseannotation.cpp index 93f445012..c02b8fb26 100644 --- a/part/pageviewmouseannotation.cpp +++ b/part/pageviewmouseannotation.cpp @@ -25,7 +25,7 @@ #include "core/document.h" #include "core/page.h" -#include "guiutils.h" +#include "gui/guiutils.h" #include "pageview.h" #include "videowidget.h" diff --git a/part/part.cpp b/part/part.cpp index ced5b03a2..ae6ea5eb2 100644 --- a/part/part.cpp +++ b/part/part.cpp @@ -93,13 +93,14 @@ #include "core/generator.h" #include "core/page.h" #include "core/printoptionswidget.h" -#include "debug_ui.h" #include "drawingtoolactions.h" #include "embeddedfilesdialog.h" #include "extensions.h" #include "fileprinterpreview.h" #include "findbar.h" -#include "guiutils.h" +#include "gui/debug_ui.h" +#include "gui/guiutils.h" +#include "gui/signatureguiutils.h" #include "layers.h" #include "minibar.h" #include "okmenutitle.h" @@ -112,7 +113,6 @@ #include "settings.h" #include "side_reviews.h" #include "sidebar.h" -#include "signatureguiutils.h" #include "signaturepanel.h" #include "thumbnaillist.h" #include "toc.h" @@ -1573,51 +1573,24 @@ bool Part::openFile() } if (ok) { - const uint numPages = m_document->pages(); - bool isDigitallySigned = false; - for (uint i = 0; i < numPages; i++) { - const QLinkedList formFields = m_document->page(i)->formFields(); - for (const Okular::FormField *f : formFields) { - if (f->type() == Okular::FormField::FormSignature) - isDigitallySigned = true; - } - } + KMessageWidget::MessageType messageType; + QString message; + + std::tie(messageType, message) = SignatureGuiUtils::documentSignatureMessageWidgetText(m_document); - if (isDigitallySigned && Okular::Settings::showEmbeddedContentMessages()) { + if (!message.isEmpty()) { if (m_embedMode == PrintPreviewMode) { - m_signatureMessage->setText(i18n("All editing and interactive features for this document are disabled. Please save a copy and reopen to edit this document.")); - } else { - const QVector signatureFormFields = SignatureGuiUtils::getSignatureFormFields(m_document); - bool allSignaturesValid = true; - bool anySignatureUnsigned = false; - for (const Okular::FormFieldSignature *signature : signatureFormFields) { - if (signature->signatureType() == Okular::FormFieldSignature::UnsignedSignature) { - anySignatureUnsigned = true; - } else { - const Okular::SignatureInfo &info = signature->signatureInfo(); - if (info.signatureStatus() != SignatureInfo::SignatureValid) { - allSignaturesValid = false; - } - } + if (Okular::Settings::showEmbeddedContentMessages()) { + m_signatureMessage->setText(i18n("All editing and interactive features for this document are disabled. Please save a copy and reopen to edit this document.")); + m_signatureMessage->setVisible(true); } - - if (anySignatureUnsigned) { - m_signatureMessage->setMessageType(KMessageWidget::Information); - m_signatureMessage->setText(i18n("This document has unsigned signature fields.")); - } else if (allSignaturesValid) { - if (signatureFormFields.last()->signatureInfo().signsTotalDocument()) { - m_signatureMessage->setMessageType(KMessageWidget::Information); - m_signatureMessage->setText(i18n("This document is digitally signed.")); - } else { - m_signatureMessage->setMessageType(KMessageWidget::Warning); - m_signatureMessage->setText(i18n("This document is digitally signed. There have been changes since last signed.")); - } - } else { - m_signatureMessage->setMessageType(KMessageWidget::Warning); - m_signatureMessage->setText(i18n("This document is digitally signed. Some of the signatures could not be validated properly.")); + } else { + if (Okular::Settings::showEmbeddedContentMessages() || messageType > KMessageWidget::Information) { + m_signatureMessage->setMessageType(messageType); + m_signatureMessage->setText(message); + m_signatureMessage->setVisible(true); } } - m_signatureMessage->setVisible(true); } } diff --git a/part/presentationwidget.cpp b/part/presentationwidget.cpp index a6fb3d1c7..da7a102e4 100644 --- a/part/presentationwidget.cpp +++ b/part/presentationwidget.cpp @@ -56,12 +56,12 @@ #include "core/generator.h" #include "core/movie.h" #include "core/page.h" -#include "debug_ui.h" #include "drawingtoolactions.h" -#include "guiutils.h" -#include "pagepainter.h" +#include "gui/debug_ui.h" +#include "gui/guiutils.h" +#include "gui/pagepainter.h" +#include "gui/priorities.h" #include "presentationsearchbar.h" -#include "priorities.h" #include "settings.h" #include "settings_core.h" #include "videowidget.h" diff --git a/part/signaturepanel.cpp b/part/signaturepanel.cpp index 89955be18..f19cbcfe0 100644 --- a/part/signaturepanel.cpp +++ b/part/signaturepanel.cpp @@ -6,10 +6,10 @@ #include "signaturepanel.h" +#include "gui/signaturemodel.h" #include "pageview.h" #include "revisionviewer.h" -#include "signatureguiutils.h" -#include "signaturemodel.h" +#include "signaturepartutils.h" #include "signaturepropertiesdialog.h" #include @@ -114,7 +114,7 @@ void SignaturePanel::slotViewProperties() void SignaturePanel::signUnsignedSignature() { Q_D(SignaturePanel); - SignatureGuiUtils::signUnsignedSignature(d->m_currentForm, d->m_pageView, d->m_document); + SignaturePartUtils::signUnsignedSignature(d->m_currentForm, d->m_pageView, d->m_document); } void SignaturePanel::notifySetup(const QVector & /*pages*/, int setupFlags) diff --git a/part/signaturepartutils.cpp b/part/signaturepartutils.cpp new file mode 100644 index 000000000..aa0f2f909 --- /dev/null +++ b/part/signaturepartutils.cpp @@ -0,0 +1,127 @@ +/* + SPDX-FileCopyrightText: 2018 Chinmoy Ranjan Pradhan + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "signaturepartutils.h" + +#include "core/document.h" +#include "core/form.h" +#include "core/page.h" +#include "pageview.h" + +#include +#include +#include +#include + +#include +#include + +namespace SignaturePartUtils +{ +std::unique_ptr getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword) +{ + const Okular::CertificateStore *certStore = doc->certificateStore(); + bool userCancelled, nonDateValidCerts; + QList certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts); + if (userCancelled) { + return nullptr; + } + + if (certs.isEmpty()) { + pageView->showNoSigningCertificatesDialog(nonDateValidCerts); + return nullptr; + } + + QStringList items; + QHash nickToCert; + for (auto cert : qAsConst(certs)) { + items.append(cert->nickName()); + nickToCert[cert->nickName()] = cert; + } + + bool resok = false; + const QString certNicknameToUse = QInputDialog::getItem(pageView, i18n("Select certificate to sign with"), i18n("Certificates:"), items, 0, false, &resok); + + if (!resok) { + qDeleteAll(certs); + return nullptr; + } + + // I could not find any case in which i need to enter a password to use the certificate, seems that once you unlcok the firefox/NSS database + // you don't need a password anymore, but still there's code to do that in NSS so we have code to ask for it if needed. What we do is + // ask if the empty password is fine, if it is we don't ask the user anything, if it's not, we ask for a password + Okular::CertificateInfo *cert = nickToCert.value(certNicknameToUse); + bool passok = cert->checkPassword(*password); + while (!passok) { + const QString title = i18n("Enter password (if any) to unlock certificate: %1", certNicknameToUse); + bool ok; + *password = QInputDialog::getText(pageView, i18n("Enter certificate password"), title, QLineEdit::Password, QString(), &ok); + if (ok) { + passok = cert->checkPassword(*password); + } else { + passok = false; + break; + } + } + + if (doc->metaData(QStringLiteral("DocumentHasPassword")).toString() == QLatin1String("yes")) { + *documentPassword = QInputDialog::getText(pageView, i18n("Enter document password"), i18n("Enter document password"), QLineEdit::Password, QString(), &passok); + } + + if (passok) { + certs.removeOne(cert); + } + qDeleteAll(certs); + + return passok ? std::unique_ptr(cert) : std::unique_ptr(); +} + +QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc) +{ + QMimeDatabase db; + const QString typeName = doc->documentInfo().get(Okular::DocumentInfo::MimeType); + const QMimeType mimeType = db.mimeTypeForName(typeName); + const QString mimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' '))); + + const QUrl currentFileUrl = doc->currentDocument(); + const QFileInfo currentFileInfo(currentFileUrl.fileName()); + const QString localFilePathIfAny = currentFileUrl.isLocalFile() ? QFileInfo(currentFileUrl.path()).canonicalPath() + QLatin1Char('/') : QString(); + const QString newFileName = + localFilePathIfAny + i18nc("Used when suggesting a new name for a digitally signed file. %1 is the old file name and %2 it's extension", "%1_signed.%2", currentFileInfo.baseName(), currentFileInfo.completeSuffix()); + + return QFileDialog::getSaveFileName(pageView, i18n("Save Signed File As"), newFileName, mimeTypeFilter); +} + +void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc) +{ + Q_ASSERT(form && form->signatureType() == Okular::FormFieldSignature::UnsignedSignature); + QString password, documentPassword; + const std::unique_ptr cert = getCertificateAndPasswordForSigning(pageView, doc, &password, &documentPassword); + if (!cert) { + return; + } + + Okular::NewSignatureData data; + data.setCertNickname(cert->nickName()); + data.setCertSubjectCommonName(cert->subjectInfo(Okular::CertificateInfo::CommonName)); + data.setPassword(password); + data.setDocumentPassword(documentPassword); + password.clear(); + documentPassword.clear(); + + const QString newFilePath = getFileNameForNewSignedFile(pageView, doc); + + if (!newFilePath.isEmpty()) { + const bool success = form->sign(data, newFilePath); + if (success) { + emit pageView->requestOpenFile(newFilePath, form->page()->number() + 1); + } else { + KMessageBox::error(pageView, i18nc("%1 is a file path", "Could not sign. Invalid certificate password or could not write to '%1'", newFilePath)); + } + } +} + +} diff --git a/part/signaturepartutils.h b/part/signaturepartutils.h new file mode 100644 index 000000000..a1875c7fe --- /dev/null +++ b/part/signaturepartutils.h @@ -0,0 +1,24 @@ +/* + SPDX-FileCopyrightText: 2018 Chinmoy Ranjan Pradhan + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef OKULAR_SIGNATUREPARTUTILS_H +#define OKULAR_SIGNATUREPARTUTILS_H + +#include + +#include "gui/signatureguiutils.h" + +class PageView; + +namespace SignaturePartUtils +{ +std::unique_ptr getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, QString *password, QString *documentPassword); +QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc); +void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc); + +} + +#endif diff --git a/part/signaturepropertiesdialog.cpp b/part/signaturepropertiesdialog.cpp index 8c379416c..8b0dc5811 100644 --- a/part/signaturepropertiesdialog.cpp +++ b/part/signaturepropertiesdialog.cpp @@ -21,8 +21,8 @@ #include "core/form.h" #include "certificateviewer.h" +#include "gui/signatureguiutils.h" #include "revisionviewer.h" -#include "signatureguiutils.h" static QString getValidDisplayString(const QString &str) { @@ -40,27 +40,12 @@ SignaturePropertiesDialog::SignaturePropertiesDialog(Okular::Document *doc, cons const Okular::SignatureInfo &signatureInfo = form->signatureInfo(); const Okular::SignatureInfo::SignatureStatus signatureStatus = signatureInfo.signatureStatus(); const QString readableSignatureStatus = SignatureGuiUtils::getReadableSignatureStatus(signatureStatus); + const QString modificationSummary = SignatureGuiUtils::getReadableModificationSummary(signatureInfo); const QString signerName = getValidDisplayString(signatureInfo.signerName()); const QString signingTime = getValidDisplayString(signatureInfo.signingTime().toString(Qt::DefaultLocaleLongDate)); const QString signingLocation = getValidDisplayString(signatureInfo.location()); const QString signingReason = signatureInfo.reason(); - // signature validation status - QString modificationSummary; - if (signatureStatus == Okular::SignatureInfo::SignatureValid) { - if (signatureInfo.signsTotalDocument()) { - modificationSummary = i18n("The document has not been modified since it was signed."); - } else { - modificationSummary = i18n( - "The revision of the document that was covered by this signature has not been modified;\n" - "however there have been subsequent changes to the document."); - } - } else if (signatureStatus == Okular::SignatureInfo::SignatureDigestMismatch) { - modificationSummary = i18n("The document has been modified in a way not permitted by a previous signer."); - } else { - modificationSummary = i18n("The document integrity verification could not be completed."); - } - auto signatureStatusBox = new QGroupBox(i18n("Validity Status")); auto signatureStatusFormLayout = new QFormLayout(signatureStatusBox); signatureStatusFormLayout->setLabelAlignment(Qt::AlignLeft); diff --git a/part/thumbnaillist.cpp b/part/thumbnaillist.cpp index 850e23c79..0adc60d72 100644 --- a/part/thumbnaillist.cpp +++ b/part/thumbnaillist.cpp @@ -31,8 +31,8 @@ #include "core/generator.h" #include "core/page.h" #include "cursorwraphelper.h" -#include "pagepainter.h" -#include "priorities.h" +#include "gui/pagepainter.h" +#include "gui/priorities.h" #include "settings.h" class ThumbnailWidget; diff --git a/part/toc.cpp b/part/toc.cpp index b17c5f5b5..3ee082ea2 100644 --- a/part/toc.cpp +++ b/part/toc.cpp @@ -21,10 +21,10 @@ // local includes #include "core/action.h" +#include "gui/tocmodel.h" #include "ktreeviewsearchline.h" #include "pageitemdelegate.h" #include "settings.h" -#include "tocmodel.h" TOC::TOC(QWidget *parent, Okular::Document *document) : QWidget(parent)