Blob Blame History Raw
From 92675896ed9d1b4a6f41143803630768a46eab7a Mon Sep 17 00:00:00 2001
From: Fabian Vogt <fabian@ritter-vogt.de>
Date: Mon, 26 Aug 2019 13:22:25 +0200
Subject: [PATCH 30/51] Add support for license prompts

Summary:
Currently, if a transaction requires accepting a license, it just fails.
This implements a basic dialog (which makes it easier to read the
license than displaying it inline) and the necessary backend
functionality.

It was necessary to move the list of packages out of a QObject property
into a new member to allow restarting the transaction outside of the
onFinished slot.

Test Plan:
Downgraded two packages with EULAs and searched for updates.
Hit the install updates button and got two license prompts.
Only after accepting both with the "Yes" button are the updates installed.

Reviewers: lukas, jgrulich, bruns, antlarr

Differential Revision: https://phabricator.kde.org/D23462
---
 src/declarative/pkupdates.cpp   | 61 ++++++++++++++++++++++++++++-----
 src/declarative/pkupdates.h     | 29 ++++++++++++++++
 src/plasma/contents/ui/Full.qml | 59 +++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 8 deletions(-)

diff --git a/src/declarative/pkupdates.cpp b/src/declarative/pkupdates.cpp
index db85eb1..9eac70f 100644
--- a/src/declarative/pkupdates.cpp
+++ b/src/declarative/pkupdates.cpp
@@ -296,8 +296,9 @@ void PkUpdates::installUpdates(const QStringList &packageIds, bool simulate, boo
         flags = PackageKit::Transaction::TransactionFlagNone;
     }
 
-    m_installTrans = PackageKit::Daemon::updatePackages(packageIds, flags);
-    m_installTrans->setProperty("packages", packageIds);
+    m_requiredEulas.clear();
+    m_packages = packageIds;
+    m_installTrans = PackageKit::Daemon::updatePackages(m_packages, flags);
     setActivity(InstallingUpdates);
 
     connect(m_installTrans.data(), &PackageKit::Transaction::statusChanged, this, &PkUpdates::onStatusChanged);
@@ -306,6 +307,7 @@ void PkUpdates::installUpdates(const QStringList &packageIds, bool simulate, boo
     connect(m_installTrans.data(), &PackageKit::Transaction::package, this, &PkUpdates::onPackageUpdating);
     connect(m_installTrans.data(), &PackageKit::Transaction::requireRestart, this, &PkUpdates::onRequireRestart);
     connect(m_installTrans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PkUpdates::onRepoSignatureRequired);
+    connect(m_installTrans.data(), &PackageKit::Transaction::eulaRequired, this, &PkUpdates::onEulaRequired);
 }
 
 void PkUpdates::onChanged()
@@ -431,16 +433,19 @@ void PkUpdates::onFinished(PackageKit::Transaction::Exit status, uint runtime)
         qCDebug(PLASMA_PK_UPDATES) << "Total number of updates: " << count();
         emit done();
     } else if (trans->role() == PackageKit::Transaction::RoleUpdatePackages) {
-        const QStringList packages = trans->property("packages").toStringList();
-        qCDebug(PLASMA_PK_UPDATES) << "Finished updating packages:" << packages;
+        qCDebug(PLASMA_PK_UPDATES) << "Finished updating packages:" << m_packages;
         if (status == PackageKit::Transaction::ExitNeedUntrusted) {
             qCDebug(PLASMA_PK_UPDATES) << "Transaction needs untrusted packages";
             // restart transaction with "untrusted" flag
-            installUpdates(packages, false /*simulate*/, true /*untrusted*/);
+            installUpdates(m_packages, false /*simulate*/, true /*untrusted*/);
+            return;
+        } else if (status == PackageKit::Transaction::ExitEulaRequired) {
+            qCDebug(PLASMA_PK_UPDATES) << "Acceptance of EULAs required";
+            promptNextEulaAgreement();
             return;
         } else if (status == PackageKit::Transaction::ExitSuccess && trans->transactionFlags().testFlag(PackageKit::Transaction::TransactionFlagSimulate)) {
             qCDebug(PLASMA_PK_UPDATES) << "Simulation finished with success, restarting the transaction";
-            installUpdates(packages, false /*simulate*/, false /*untrusted*/);
+            installUpdates(m_packages, false /*simulate*/, false /*untrusted*/);
             return;
         } else if (status == PackageKit::Transaction::ExitSuccess) {
             qCDebug(PLASMA_PK_UPDATES) << "Update packages transaction finished successfully";
@@ -449,7 +454,7 @@ void PkUpdates::onFinished(PackageKit::Transaction::Exit status, uint runtime)
             }
             KNotification::event(s_eventIdUpdatesInstalled,
                                  i18n("Updates Installed"),
-                                 i18np("Successfully updated %1 package", "Successfully updated %1 packages", packages.count()),
+                                 i18np("Successfully updated %1 package", "Successfully updated %1 packages", m_packages.count()),
                                  s_pkUpdatesIconName, nullptr,
                                  KNotification::CloseOnTimeout,
                                  s_componentName);
@@ -475,7 +480,7 @@ void PkUpdates::onFinished(PackageKit::Transaction::Exit status, uint runtime)
 void PkUpdates::onErrorCode(PackageKit::Transaction::Error error, const QString &details)
 {
     qWarning() << "PK error:" << details << "type:" << PackageKit::Daemon::enumToString<PackageKit::Transaction>((int)error, "Error");
-    if (error == PackageKit::Transaction::ErrorBadGpgSignature)
+    if (error == PackageKit::Transaction::ErrorBadGpgSignature || error == PackageKit::Transaction::ErrorNoLicenseAgreement)
         return;
 
     KNotification::event(s_eventIdError, i18n("Update Error"),
@@ -553,6 +558,46 @@ void PkUpdates::onRepoSignatureRequired(const QString &packageID, const QString
     qCDebug(PLASMA_PK_UPDATES) << "Repo sig required" << packageID;
 }
 
+void PkUpdates::onEulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement)
+{
+    m_requiredEulas[eulaID] = {packageID, vendor, licenseAgreement};
+}
+
+void PkUpdates::promptNextEulaAgreement()
+{
+    if(m_requiredEulas.empty()) {
+        // Restart the transaction
+        installUpdates(m_packages, false, false);
+        return;
+    }
+
+    QString eulaID = m_requiredEulas.firstKey();
+    const EulaData &eula = m_requiredEulas[eulaID];
+    emit eulaRequired(eulaID, eula.packageID, eula.vendor, eula.licenseAgreement);
+}
+
+void PkUpdates::eulaAgreementResult(const QString &eulaID, bool agreed)
+{
+    if(!agreed) {
+        qCDebug(PLASMA_PK_UPDATES) << "EULA declined";
+        // Do the same as the failure case in onFinished
+        checkUpdates(false /* force */);
+        return;
+    }
+
+    m_eulaTrans = PackageKit::Daemon::acceptEula(eulaID);
+    connect(m_eulaTrans.data(), &PackageKit::Transaction::finished, this,
+            [this, eulaID] (PackageKit::Transaction::Exit exit, uint) {
+                if (exit == PackageKit::Transaction::ExitSuccess) {
+                    m_requiredEulas.remove(eulaID);
+                    promptNextEulaAgreement();
+                } else {
+                    qCWarning(PLASMA_PK_UPDATES) << "EULA acceptance failed";
+                }
+            }
+    );
+}
+
 void PkUpdates::setStatusMessage(const QString &message)
 {
     m_statusMessage = message;
diff --git a/src/declarative/pkupdates.h b/src/declarative/pkupdates.h
index 877bd52..0f48d2d 100644
--- a/src/declarative/pkupdates.h
+++ b/src/declarative/pkupdates.h
@@ -156,6 +156,17 @@ signals:
      */
     void updateDetail(const QString &packageID, const QString &updateText, const QStringList &urls);
 
+    /**
+     * Emitted when an EULA agreement prevents the transaction from running
+     * @param eulaId the EULA identifier
+     * @param packageID ID of the package for which an EULA is required
+     * @param vendorName the vendor name
+     * @param licenseAgreement the EULA text
+     *
+     * @see eulaAgreementResult()
+     */
+    void eulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement);
+
     // private ;)
     void statusMessageChanged();
     void isActiveChanged();
@@ -205,6 +216,11 @@ public slots:
 
     Q_INVOKABLE void doDelayedCheckUpdates();
 
+    /**
+     * If agreed to eulaID, starts an EULA acceptance transaction and continues.
+     */
+    Q_INVOKABLE void eulaAgreementResult(const QString &eulaID, bool agreed);
+
 private slots:
     void getUpdates();
     void onChanged();
@@ -221,15 +237,25 @@ private slots:
                         const QDateTime &issued, const QDateTime &updated);
     void onRepoSignatureRequired(const QString & packageID, const QString & repoName, const QString & keyUrl, const QString & keyUserid,
                                  const QString & keyId, const QString & keyFingerprint, const QString & keyTimestamp, PackageKit::Transaction::SigType type);
+    void onEulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement);
 
 private:
+    struct EulaData {
+        QString packageID;
+        QString vendor;
+        QString licenseAgreement;
+    };
+
     void setStatusMessage(const QString &message);
     void setActivity(Activity act);
     void setPercentage(int value);
+    void promptNextEulaAgreement();
     QPointer<PackageKit::Transaction> m_updatesTrans;
     QPointer<PackageKit::Transaction> m_cacheTrans;
     QPointer<PackageKit::Transaction> m_installTrans;
     QPointer<PackageKit::Transaction> m_detailTrans;
+    QPointer<PackageKit::Transaction> m_eulaTrans;
+    QStringList m_packages;
     QPointer<KNotification> m_lastNotification;
     int m_lastUpdateCount = 0;
     QVariantMap m_updateList;
@@ -241,6 +267,9 @@ private:
     bool m_lastCheckSuccessful = false;
     bool m_checkUpdatesWhenNetworkOnline = false;
     bool m_isOnBattery;
+    // If a transaction failed because of required EULAs,
+    // this contains a map of their IDs to their data
+    QMap<QString, EulaData> m_requiredEulas;
 };
 
 #endif // PLASMA_PK_UPDATES_H
diff --git a/src/plasma/contents/ui/Full.qml b/src/plasma/contents/ui/Full.qml
index 7cf37eb..de2a47e 100644
--- a/src/plasma/contents/ui/Full.qml
+++ b/src/plasma/contents/ui/Full.qml
@@ -22,6 +22,7 @@
 import QtQuick 2.1
 import QtQuick.Layouts 1.1
 import QtQuick.Controls 1.3
+import QtQuick.Dialogs 1.2
 import org.kde.plasma.components 2.0 as PlasmaComponents
 import org.kde.plasma.extras 2.0 as PlasmaExtras
 import org.kde.plasma.core 2.0 as PlasmaCore
@@ -46,10 +47,68 @@ Item {
         onUpdatesChanged: populateModel()
         onUpdateDetail: updateDetails(packageID, updateText, urls)
         onUpdatesInstalled: plasmoid.expanded = false
+        onEulaRequired: eulaDialog.showPrompt(eulaID, packageID, vendor, licenseAgreement)
     }
 
     Component.onCompleted: populateModel()
 
+    Dialog {
+        property string eulaID: ""
+        property string packageName: ""
+        property string vendor: ""
+        property string licenseText: ""
+
+        property bool buttonClicked: false
+
+        id: eulaDialog
+        title: i18n("License Agreement for %1").arg(packageName)
+        standardButtons: StandardButton.Yes | StandardButton.No
+
+        ColumnLayout {
+            anchors.fill: parent
+
+            Label {
+                text: i18n("License agreement required for %1 (from %2):").arg(eulaDialog.packageName).arg(eulaDialog.vendor)
+            }
+
+            TextArea {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+                Layout.minimumWidth: 400
+                Layout.minimumHeight: 200
+                text: eulaDialog.licenseText
+                readOnly: true
+            }
+
+            Label {
+                text: i18n("Do you accept?")
+            }
+        }
+
+        onVisibleChanged: {
+            // onRejected does not fire on dialog closing, so implement that ourselves
+            if(!visible && !buttonClicked)
+                onNo();
+        }
+        onNo: {
+            buttonClicked = true;
+            PkUpdates.eulaAgreementResult(this.eulaID, false);
+        }
+        onYes: {
+            buttonClicked = true;
+            PkUpdates.eulaAgreementResult(this.eulaID, true);
+        }
+
+        function showPrompt(eulaID, packageID, vendor, licenseAgreement) {
+            this.eulaID = eulaID;
+            this.packageName = PkUpdates.packageName(packageID);
+            this.vendor = vendor;
+            this.licenseText = licenseAgreement;
+
+            this.visible = true;
+        }
+    }
+
     ListModel {
         id: updatesModel
     }
-- 
2.28.0