]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Stop acting as admin when the authorization expires
authorFelix Ernst <felixernst@kde.org>
Sat, 13 Jul 2024 15:37:31 +0000 (15:37 +0000)
committerFelix Ernst <felixernst@kde.org>
Sat, 13 Jul 2024 15:37:31 +0000 (15:37 +0000)
Previously, the authorization just silently expired without any
visible change. The surprise only came when the user tried to do
any action, which immediately would show a password prompt. We
don't want users to randomly type passwords into password
prompts unexpectedly showing up. This commit avoids that.

With this change, the view container visibly de-escalates
privileges and shows a message explaining what happened.

A method was implemented in kio-admin to make this possible. See:
https://commits.kde.org/kio-admin/a2da29289d12ef845e2c1da17ed04c59f1c47762

This commit also improves some logic around the responsibility
of hiding bars and activating view containers.

This is also part of my project funded by the European Commission.

src/admin/bar.cpp
src/admin/bar.h
src/admin/workerintegration.cpp
src/admin/workerintegration.h
src/dolphinviewcontainer.cpp
src/dolphinviewcontainer.h

index f2e7250ada999d2ddf3f74418ab297e132df0269..52bbc37724206ab159f4f45c65efdc3162bdd680 100644 (file)
@@ -7,16 +7,20 @@
 
 #include "bar.h"
 
 
 #include "bar.h"
 
+#include "dolphinviewcontainer.h"
 #include "workerintegration.h"
 
 #include <KColorScheme>
 #include <KContextualHelpButton>
 #include "workerintegration.h"
 
 #include <KColorScheme>
 #include <KContextualHelpButton>
+#include <KIO/JobUiDelegateFactory>
+#include <KIO/SimpleJob>
 #include <KLocalizedString>
 
 #include <QEvent>
 #include <QGuiApplication>
 #include <QHBoxLayout>
 #include <QLabel>
 #include <KLocalizedString>
 
 #include <QEvent>
 #include <QGuiApplication>
 #include <QHBoxLayout>
 #include <QLabel>
+#include <QPointer>
 #include <QPushButton>
 #include <QStyle>
 #include <QToolButton>
 #include <QPushButton>
 #include <QStyle>
 #include <QToolButton>
 
 using namespace Admin;
 
 
 using namespace Admin;
 
-Bar::Bar(QWidget *parent)
-    : AnimatedHeightWidget{parent}
+namespace
+{
+QPointer<KIO::SimpleJob> waitingForExpirationOfAuthorization;
+}
+
+Bar::Bar(DolphinViewContainer *parentViewContainer)
+    : AnimatedHeightWidget{parentViewContainer}
+    , m_parentViewContainer{parentViewContainer}
 {
     setAutoFillBackground(true);
     updateColors();
 {
     setAutoFillBackground(true);
     updateColors();
@@ -46,8 +56,19 @@ Bar::Bar(QWidget *parent)
                                     contenntsContainer);
     m_closeButton->setToolTip(i18nc("@info:tooltip", "Finish acting as an administrator"));
     m_closeButton->setFlat(true);
                                     contenntsContainer);
     m_closeButton->setToolTip(i18nc("@info:tooltip", "Finish acting as an administrator"));
     m_closeButton->setFlat(true);
-    connect(m_closeButton, &QAbstractButton::clicked, this, &Bar::activated); // Make sure the view connected to this bar is active before exiting admin mode.
-    connect(m_closeButton, &QAbstractButton::clicked, this, &WorkerIntegration::exitAdminMode);
+    connect(m_closeButton, &QAbstractButton::clicked, m_parentViewContainer, [this]() {
+        m_parentViewContainer->setActive(true); // Make sure the view connected to this bar is active before exiting admin mode.
+        QAction *actAsAdminAction = WorkerIntegration::actAsAdminAction();
+        if (actAsAdminAction->isChecked()) {
+            actAsAdminAction->trigger();
+        }
+    });
+    connect(m_parentViewContainer->view(), &DolphinView::urlChanged, this, [this](const QUrl &url) {
+        // The bar is closely related to administrative rights, so we want to hide it instantly when we are no longer using the admin protocol.
+        if (url.scheme() != QStringLiteral("admin")) {
+            setVisible(false, WithAnimation);
+        }
+    });
 
     QHBoxLayout *layout = new QHBoxLayout(contenntsContainer);
     auto contentsMargins = layout->contentsMargins();
 
     QHBoxLayout *layout = new QHBoxLayout(contenntsContainer);
     auto contentsMargins = layout->contentsMargins();
@@ -65,8 +86,15 @@ Bar::Bar(QWidget *parent)
 
 bool Bar::event(QEvent *event)
 {
 
 bool Bar::event(QEvent *event)
 {
-    if (event->type() == QEvent::PaletteChange) {
+    switch (event->type()) {
+    case QEvent::PaletteChange:
         updateColors();
         updateColors();
+        break;
+    case QEvent::Show:
+        hideTheNextTimeAuthorizationExpires();
+        break;
+    default:
+        break;
     }
     return AnimatedHeightWidget::event(event);
 }
     }
     return AnimatedHeightWidget::event(event);
 }
@@ -77,6 +105,52 @@ void Bar::resizeEvent(QResizeEvent *resizeEvent)
     return QWidget::resizeEvent(resizeEvent);
 }
 
     return QWidget::resizeEvent(resizeEvent);
 }
 
+void Bar::hideTheNextTimeAuthorizationExpires()
+{
+    if (waitingForExpirationOfAuthorization.isNull()) {
+        QByteArray packedArgs;
+        QDataStream stream(&packedArgs, QIODevice::WriteOnly);
+        stream << (int)1;
+        waitingForExpirationOfAuthorization = KIO::special(QUrl(QStringLiteral("admin:/")), packedArgs, KIO::HideProgressInfo);
+        waitingForExpirationOfAuthorization->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoWarningHandlingEnabled, m_parentViewContainer));
+
+        connect(waitingForExpirationOfAuthorization, &KJob::finished, this, [](KJob *job) {
+            if (job->error()) {
+                job->uiDelegate()->showErrorMessage();
+            }
+        });
+    }
+
+    connect(waitingForExpirationOfAuthorization, &KJob::finished, this, [this](KJob *job) {
+        if (job->error()) {
+            return;
+        }
+        // We exit admin mode now to avoid random password prompts popping up.
+        QUrl viewContainerUrl = m_parentViewContainer->url();
+        if (viewContainerUrl.scheme() != QStringLiteral("admin")) {
+            return;
+        }
+        viewContainerUrl.setScheme("file");
+        m_parentViewContainer->setUrl(viewContainerUrl);
+
+        // Explain to users that their admin authorization expired.
+        if (!m_reenableActAsAdminAction) {
+            auto actAsAdminAction = WorkerIntegration::actAsAdminAction();
+            m_reenableActAsAdminAction =
+                new QAction{actAsAdminAction->icon(), i18nc("@action:button shown after acting as admin ended", "Act as Administrator Again"), this};
+            m_reenableActAsAdminAction->setToolTip(actAsAdminAction->toolTip());
+            m_reenableActAsAdminAction->setWhatsThis(actAsAdminAction->whatsThis());
+            connect(m_reenableActAsAdminAction, &QAction::triggered, this, [this, actAsAdminAction]() {
+                m_parentViewContainer->setActive(true);
+                actAsAdminAction->trigger();
+            });
+        }
+        m_parentViewContainer->showMessage(i18nc("@info", "Administrator authorization has expired."),
+                                           KMessageWidget::Information,
+                                           {m_reenableActAsAdminAction});
+    });
+}
+
 void Bar::updateColors()
 {
     QPalette palette = parentWidget()->palette();
 void Bar::updateColors()
 {
     QPalette palette = parentWidget()->palette();
index 5399fa66973b15dd93673d2dcda61c016c21027a..d1754f3bb4accdc107dfd0a3f832c5a16addf983 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "animatedheightwidget.h"
 
 
 #include "animatedheightwidget.h"
 
+class DolphinViewContainer;
 class QLabel;
 class QPushButton;
 class QResizeEvent;
 class QLabel;
 class QPushButton;
 class QResizeEvent;
@@ -28,19 +29,24 @@ class Bar : public AnimatedHeightWidget
     Q_OBJECT
 
 public:
     Q_OBJECT
 
 public:
-    explicit Bar(QWidget *parent);
+    explicit Bar(DolphinViewContainer *parentViewContainer);
 
     /** Used to recolor this bar when this application's color scheme changes. */
     bool event(QEvent *event) override;
 
 
     /** Used to recolor this bar when this application's color scheme changes. */
     bool event(QEvent *event) override;
 
-Q_SIGNALS:
-    void activated();
-
 protected:
     /** Calls updateLabelString() */
     void resizeEvent(QResizeEvent *resizeEvent) override;
 
 private:
 protected:
     /** Calls updateLabelString() */
     void resizeEvent(QResizeEvent *resizeEvent) override;
 
 private:
+    /**
+     * Makes sure this admin bar hides itself when the elevated privileges expire so the user doesn't mistakenly assume that they are still acting with
+     * administrative rights. The view container is also changed to a non-admin url, so no password prompts will pop up unexpectedly.
+     * Then this method shows a message to the user to explain this.
+     * The mechanism of this method only fires once and will need to be called again the next time the user regains administrative rights for this view.
+     */
+    void hideTheNextTimeAuthorizationExpires();
+
     /** Recolors this bar based on the current color scheme. */
     void updateColors();
 
     /** Recolors this bar based on the current color scheme. */
     void updateColors();
 
@@ -68,6 +74,19 @@ private:
 
     /** @see preferredHeight() */
     int m_preferredHeight;
 
     /** @see preferredHeight() */
     int m_preferredHeight;
+
+    /**
+     * A proxy action for the real actAsAdminAction.
+     * This proxy action can be used to reenable admin mode for the view belonging to this bar object specifically.
+     */
+    QAction *m_reenableActAsAdminAction = nullptr;
+
+    /**
+     * The parent of this bar. The bar acts on the DolphinViewContainer to exit the admin mode. This can happen in two ways:
+     * 1. The user closes the bar which implies exiting of the admin mode.
+     * 2. The admin authorization expires so all views can factually no longer be in admin mode.
+     */
+    DolphinViewContainer *m_parentViewContainer;
 };
 
 }
 };
 
 }
index 038c5209435f45d9c2fb9cd980399b2b9b70a2db..0b95ec782119a6c6fdca042b001bffc4c12f40cf 100644 (file)
@@ -134,43 +134,41 @@ void WorkerIntegration::createActAsAdminAction(KActionCollection *actionCollecti
     }
 }
 
     }
 }
 
-void WorkerIntegration::exitAdminMode()
-{
-    if (instance->m_actAsAdminAction->isChecked()) {
-        instance->m_actAsAdminAction->trigger();
-    }
-}
-
 void WorkerIntegration::toggleActAsAdmin()
 {
     auto dolphinMainWindow = static_cast<DolphinMainWindow *>(parent());
     QUrl url = dolphinMainWindow->activeViewContainer()->urlNavigator()->locationUrl();
 void WorkerIntegration::toggleActAsAdmin()
 {
     auto dolphinMainWindow = static_cast<DolphinMainWindow *>(parent());
     QUrl url = dolphinMainWindow->activeViewContainer()->urlNavigator()->locationUrl();
-    if (url.scheme() == QStringLiteral("file")) {
-        bool risksAccepted = !KMessageBox::shouldBeShownContinue(warningDontShowAgainName);
 
 
-        if (!risksAccepted) {
-            KMessageDialog warningDialog{KMessageDialog::QuestionTwoActions, warningMessage(), dolphinMainWindow};
-            warningDialog.setCaption(i18nc("@title:window", "Risks of Acting as an Administrator"));
-            warningDialog.setIcon(QIcon::fromTheme(QStringLiteral("security-low")));
-            warningDialog.setButtons(KGuiItem{i18nc("@action:button", "I Understand and Accept These Risks"), QStringLiteral("data-warning")},
-                                     KStandardGuiItem::cancel());
-            warningDialog.setDontAskAgainText(i18nc("@option:check", "Do not warn me about these risks again"));
-
-            risksAccepted = warningDialog.exec() != 4 /* Cancel */;
-            if (warningDialog.isDontAskAgainChecked()) {
-                KMessageBox::saveDontShowAgainContinue(warningDontShowAgainName);
-            }
+    if (url.scheme() == QStringLiteral("admin")) {
+        url.setScheme(QStringLiteral("file"));
+        dolphinMainWindow->changeUrl(url);
+        return;
+    } else if (url.scheme() != QStringLiteral("file")) {
+        return;
+    }
 
 
-            if (!risksAccepted) {
-                updateActAsAdminAction(); // Uncheck the action
-                return;
-            }
+    bool risksAccepted = !KMessageBox::shouldBeShownContinue(warningDontShowAgainName);
+
+    if (!risksAccepted) {
+        KMessageDialog warningDialog{KMessageDialog::QuestionTwoActions, warningMessage(), dolphinMainWindow};
+        warningDialog.setCaption(i18nc("@title:window", "Risks of Acting as an Administrator"));
+        warningDialog.setIcon(QIcon::fromTheme(QStringLiteral("security-low")));
+        warningDialog.setButtons(KGuiItem{i18nc("@action:button", "I Understand and Accept These Risks"), QStringLiteral("data-warning")},
+                                 KStandardGuiItem::cancel());
+        warningDialog.setDontAskAgainText(i18nc("@option:check", "Do not warn me about these risks again"));
+
+        risksAccepted = warningDialog.exec() != 4 /* Cancel */;
+        if (warningDialog.isDontAskAgainChecked()) {
+            KMessageBox::saveDontShowAgainContinue(warningDontShowAgainName);
         }
 
         }
 
-        url.setScheme(QStringLiteral("admin"));
-    } else if (url.scheme() == QStringLiteral("admin")) {
-        url.setScheme(QStringLiteral("file"));
+        if (!risksAccepted) {
+            updateActAsAdminAction(); // Uncheck the action
+            return;
+        }
     }
     }
+
+    url.setScheme(QStringLiteral("admin"));
     dolphinMainWindow->changeUrl(url);
 }
 
     dolphinMainWindow->changeUrl(url);
 }
 
@@ -189,3 +187,8 @@ void WorkerIntegration::updateActAsAdminAction()
         }
     }
 }
         }
     }
 }
+
+QAction *WorkerIntegration::actAsAdminAction()
+{
+    return instance->m_actAsAdminAction;
+}
index 0c87c2ecf28228780b86c91c45bad623608bf4fd..19cc5c17272a4a2d10cb7388504e3f7d3e04e205 100644 (file)
@@ -57,11 +57,6 @@ public:
      */
     static void createActAsAdminAction(KActionCollection *actionCollection, DolphinMainWindow *dolphinMainWindow);
 
      */
     static void createActAsAdminAction(KActionCollection *actionCollection, DolphinMainWindow *dolphinMainWindow);
 
-    /**
-     * Triggers the m_actAsAdminAction only if it is currently checked.
-     */
-    static void exitAdminMode();
-
 private:
     WorkerIntegration(DolphinMainWindow *parent, QAction *actAsAdminAction);
 
 private:
     WorkerIntegration(DolphinMainWindow *parent, QAction *actAsAdminAction);
 
@@ -74,9 +69,14 @@ private:
     /** Updates the toggled/checked state of the action depending on the state of the currently active view. */
     static void updateActAsAdminAction();
 
     /** Updates the toggled/checked state of the action depending on the state of the currently active view. */
     static void updateActAsAdminAction();
 
+    /** Used by the friend class Bar to show the m_actAsAdminAction to users. */
+    static QAction *actAsAdminAction();
+
 private:
     /** @see createActAsAdminAction() */
     QAction *const m_actAsAdminAction = nullptr;
 private:
     /** @see createActAsAdminAction() */
     QAction *const m_actAsAdminAction = nullptr;
+
+    friend class Bar; // Allows the bar to access the actAsAdminAction, so users can use the bar to change who they are acting as.
 };
 
 }
 };
 
 }
index 99ba4efa8a36afd57a6342a0cd5c997289f7ae2d..27f845fa798fa3245248241167ca22775b8bca24 100644 (file)
@@ -145,7 +145,6 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent)
     connect(m_view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewContainer::slotHiddenFilesShownChanged);
     connect(m_view, &DolphinView::sortHiddenLastChanged, this, &DolphinViewContainer::slotSortHiddenLastChanged);
     connect(m_view, &DolphinView::currentDirectoryRemoved, this, &DolphinViewContainer::slotCurrentDirectoryRemoved);
     connect(m_view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewContainer::slotHiddenFilesShownChanged);
     connect(m_view, &DolphinView::sortHiddenLastChanged, this, &DolphinViewContainer::slotSortHiddenLastChanged);
     connect(m_view, &DolphinView::currentDirectoryRemoved, this, &DolphinViewContainer::slotCurrentDirectoryRemoved);
-    connect(m_view, &DolphinView::urlChanged, this, &DolphinViewContainer::updateAdminBarVisibility);
 
     // Initialize status bar
     m_statusBar = new DolphinStatusBar(this);
 
     // Initialize status bar
     m_statusBar = new DolphinStatusBar(this);
@@ -165,7 +164,9 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent)
     });
     connect(m_statusBar, &DolphinStatusBar::stopPressed, this, &DolphinViewContainer::stopDirectoryLoading);
     connect(m_statusBar, &DolphinStatusBar::zoomLevelChanged, this, &DolphinViewContainer::slotStatusBarZoomLevelChanged);
     });
     connect(m_statusBar, &DolphinStatusBar::stopPressed, this, &DolphinViewContainer::stopDirectoryLoading);
     connect(m_statusBar, &DolphinStatusBar::zoomLevelChanged, this, &DolphinViewContainer::slotStatusBarZoomLevelChanged);
-    connect(m_statusBar, &DolphinStatusBar::showMessage, this, &DolphinViewContainer::showMessage);
+    connect(m_statusBar, &DolphinStatusBar::showMessage, this, [this](const QString &message, KMessageWidget::MessageType messageType) {
+        showMessage(message, messageType);
+    });
 
     m_statusBarTimer = new QTimer(this);
     m_statusBarTimer->setSingleShot(true);
 
     m_statusBarTimer = new QTimer(this);
     m_statusBarTimer->setSingleShot(true);
@@ -182,7 +183,6 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent)
     m_topLayout->addWidget(m_statusBar, positionFor.statusBar, 0);
 
     setSearchModeEnabled(isSearchUrl(url));
     m_topLayout->addWidget(m_statusBar, positionFor.statusBar, 0);
 
     setSearchModeEnabled(isSearchUrl(url));
-    updateAdminBarVisibility(url);
 
     // Update view as the ContentDisplaySettings change
     // this happens here and not in DolphinView as DolphinviewContainer and DolphinView are not in the same build target ATM
 
     // Update view as the ContentDisplaySettings change
     // this happens here and not in DolphinView as DolphinviewContainer and DolphinView are not in the same build target ATM
@@ -404,7 +404,7 @@ void DolphinViewContainer::slotSplitTabDisabled()
     }
 }
 
     }
 }
 
-void DolphinViewContainer::showMessage(const QString &message, KMessageWidget::MessageType messageType)
+void DolphinViewContainer::showMessage(const QString &message, KMessageWidget::MessageType messageType, std::initializer_list<QAction *> buttonActions)
 {
     if (message.isEmpty()) {
         return;
 {
     if (message.isEmpty()) {
         return;
@@ -417,6 +417,14 @@ void DolphinViewContainer::showMessage(const QString &message, KMessageWidget::M
     m_messageWidget->setWordWrap(true);
     m_messageWidget->setMessageType(messageType);
 
     m_messageWidget->setWordWrap(true);
     m_messageWidget->setMessageType(messageType);
 
+    const QList<QAction *> previousMessageWidgetActions = m_messageWidget->actions();
+    for (auto action : previousMessageWidgetActions) {
+        m_messageWidget->removeAction(action);
+    }
+    for (QAction *action : buttonActions) {
+        m_messageWidget->addAction(action);
+    }
+
     m_messageWidget->setWordWrap(false);
     const int unwrappedWidth = m_messageWidget->sizeHint().width();
     m_messageWidget->setWordWrap(unwrappedWidth > size().width());
     m_messageWidget->setWordWrap(false);
     const int unwrappedWidth = m_messageWidget->sizeHint().width();
     m_messageWidget->setWordWrap(unwrappedWidth > size().width());
@@ -638,6 +646,17 @@ void DolphinViewContainer::slotDirectoryLoadingCompleted()
     if (m_urlNavigatorConnected) {
         m_urlNavigatorConnected->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
     }
     if (m_urlNavigatorConnected) {
         m_urlNavigatorConnected->setReadOnlyBadgeVisible(rootItem().isLocalFile() && !rootItem().isWritable());
     }
+
+    // Update admin bar visibility
+    if (m_view->url().scheme() == QStringLiteral("admin")) {
+        if (!m_adminBar) {
+            m_adminBar = new Admin::Bar(this);
+            m_topLayout->addWidget(m_adminBar, positionFor.adminBar, 0);
+        }
+        m_adminBar->setVisible(true, WithAnimation);
+    } else if (m_adminBar) {
+        m_adminBar->setVisible(false, WithAnimation);
+    }
 }
 
 void DolphinViewContainer::slotDirectoryLoadingCanceled()
 }
 
 void DolphinViewContainer::slotDirectoryLoadingCanceled()
@@ -740,20 +759,6 @@ void DolphinViewContainer::showItemInfo(const KFileItem &item)
     }
 }
 
     }
 }
 
-void DolphinViewContainer::updateAdminBarVisibility(const QUrl &url)
-{
-    if (url.scheme() == QStringLiteral("admin")) {
-        if (!m_adminBar) {
-            m_adminBar = new Admin::Bar(this);
-            m_topLayout->addWidget(m_adminBar, positionFor.adminBar, 0);
-            connect(m_adminBar, &Admin::Bar::activated, this, &DolphinViewContainer::activate);
-        }
-        m_adminBar->setVisible(true, WithAnimation);
-    } else if (m_adminBar) {
-        m_adminBar->setVisible(false, WithAnimation);
-    }
-}
-
 void DolphinViewContainer::closeFilterBar()
 {
     m_filterBar->closeFilterBar();
 void DolphinViewContainer::closeFilterBar()
 {
     m_filterBar->closeFilterBar();
index 55d00a3b80f49cc995d4719d592c4b46e6843ccf..c5da6b48b12e5974a9d4d6f4c6947ce6c7f724c5 100644 (file)
@@ -21,6 +21,8 @@
 #include <QPushButton>
 #include <QWidget>
 
 #include <QPushButton>
 #include <QWidget>
 
+#include <initializer_list>
+
 namespace Admin
 {
 class Bar;
 namespace Admin
 {
 class Bar;
@@ -150,8 +152,9 @@ public:
 
     /**
      * Shows the message \message with the given type \messageType non-modal above the view-content.
 
     /**
      * Shows the message \message with the given type \messageType non-modal above the view-content.
+     * \buttonActions defines actions which the user can trigger as a response to this message. They are presented as buttons below the \message.
      */
      */
-    void showMessage(const QString &message, KMessageWidget::MessageType messageType);
+    void showMessage(const QString &message, KMessageWidget::MessageType messageType, std::initializer_list<QAction *> buttonActions = {});
 
     /**
      * Refreshes the view container to get synchronized with the (updated) Dolphin settings.
 
     /**
      * Refreshes the view container to get synchronized with the (updated) Dolphin settings.
@@ -328,11 +331,6 @@ private Q_SLOTS:
      */
     void showItemInfo(const KFileItem &item);
 
      */
     void showItemInfo(const KFileItem &item);
 
-    /**
-     * Sets the Admin::Bar visible or invisible based on whether \a url is an admin url.
-     */
-    void updateAdminBarVisibility(const QUrl &url);
-
     void closeFilterBar();
 
     /**
     void closeFilterBar();
 
     /**