File indexing completed on 2024-04-28 03:52:39

0001 /*
0002     SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
0003     SPDX-FileCopyrightText: 2009 Radek Novacek <rnovacek@redhat.com>
0004     SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
0005     SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
0006     SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
0007 
0008     SPDX-License-Identifier: LGPL-2.1-or-later
0009 */
0010 
0011 #include "Polkit1Backend.h"
0012 #include "kauthdebug.h"
0013 
0014 #include <KWaylandExtras>
0015 #include <KWindowSystem>
0016 
0017 #include <QCoreApplication>
0018 #include <QTimer>
0019 #include <qplugin.h>
0020 
0021 #include <QGuiApplication>
0022 #include <QWindow>
0023 
0024 #include <QDBusConnection>
0025 #include <QDBusConnectionInterface>
0026 #include <QDBusPendingCallWatcher>
0027 #include <QDBusPendingReply>
0028 
0029 #include <PolkitQt1/Subject>
0030 #include <polkitqt1-version.h>
0031 
0032 constexpr QLatin1String c_kdeAgentService{"org.kde.polkit-kde-authentication-agent-1"};
0033 constexpr QLatin1String c_kdeAgentPath{"/org/kde/Polkit1AuthAgent"};
0034 constexpr QLatin1String c_kdeAgentInterface{"org.kde.Polkit1AuthAgent"};
0035 
0036 namespace KAuth
0037 {
0038 
0039 Polkit1Backend::Polkit1Backend()
0040     : AuthBackend()
0041 {
0042     setCapabilities(AuthorizeFromHelperCapability | PreAuthActionCapability);
0043 
0044     // Setup useful signals
0045     connect(PolkitQt1::Authority::instance(), &PolkitQt1::Authority::configChanged, this, &KAuth::Polkit1Backend::checkForResultChanged);
0046     connect(PolkitQt1::Authority::instance(), &PolkitQt1::Authority::consoleKitDBChanged, this, &KAuth::Polkit1Backend::checkForResultChanged);
0047 }
0048 
0049 Polkit1Backend::~Polkit1Backend()
0050 {
0051 }
0052 
0053 void Polkit1Backend::preAuthAction(const QString &action, QWindow *parentWindow)
0054 {
0055     // If a parent was not specified, skip this
0056     if (!parentWindow) {
0057         qCDebug(KAUTH) << "Parent widget does not exist, skipping";
0058         return;
0059     }
0060 
0061     // Check if we actually are entitled to use GUI capabilities
0062     if (!qGuiApp) {
0063         qCDebug(KAUTH) << "Not streaming parent as we are on a TTY application";
0064         return;
0065     }
0066 
0067     // Are we running our KDE auth agent?
0068     if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String("org.kde.polkit-kde-authentication-agent-1"))) {
0069         if (KWindowSystem::isPlatformWayland()) {
0070             KWaylandExtras::exportWindow(parentWindow);
0071             connect(
0072                 KWaylandExtras::self(),
0073                 &KWaylandExtras::windowExported,
0074                 this,
0075                 [this, action, parentWindow](QWindow *window, const QString &handle) {
0076                     if (window == parentWindow) {
0077                         sendWindowHandle(action, handle);
0078                     }
0079                 },
0080                 Qt::SingleShotConnection);
0081 
0082             // Generate and send an XDG Activation token.
0083             sendActivationToken(action, parentWindow);
0084         } else {
0085             // Retrieve the dialog root window Id
0086             const qulonglong wId = parentWindow->winId();
0087 
0088             sendWindowHandle(action, QString::number(wId));
0089 
0090             // Call the old method for compatibility.
0091             QDBusMessage methodCall = QDBusMessage::createMethodCall(c_kdeAgentService, c_kdeAgentPath, c_kdeAgentInterface, QLatin1String("setWIdForAction"));
0092             methodCall << action;
0093             methodCall << wId;
0094 
0095             // Legacy call has to be blocking, old agent doesn't handle it coming in delayed.
0096             const auto reply = QDBusConnection::sessionBus().call(methodCall);
0097             if (reply.type() != QDBusMessage::ReplyMessage) {
0098                 qWarning() << "Failed to set window id" << wId << "for" << action << reply.errorMessage();
0099             }
0100         }
0101     } else {
0102         qCDebug(KAUTH) << "KDE polkit agent appears too old or not registered on the bus";
0103     }
0104 }
0105 
0106 void Polkit1Backend::sendWindowHandle(const QString &action, const QString &handle)
0107 {
0108     // Send it over the bus to our agent
0109     QDBusMessage methodCall = QDBusMessage::createMethodCall(c_kdeAgentService, c_kdeAgentPath, c_kdeAgentInterface, QLatin1String("setWindowHandleForAction"));
0110     methodCall << action;
0111     methodCall << handle;
0112 
0113     const auto reply = QDBusConnection::sessionBus().asyncCall(methodCall);
0114     auto *watcher = new QDBusPendingCallWatcher(reply, this);
0115     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, handle, action] {
0116         watcher->deleteLater();
0117 
0118         QDBusPendingReply<> reply = *watcher;
0119         if (reply.isError()) {
0120             qCWarning(KAUTH) << "Failed to set window handle" << handle << "for" << action << reply.error().message();
0121         }
0122     });
0123 }
0124 
0125 void Polkit1Backend::sendActivationToken(const QString &action, QWindow *window)
0126 {
0127     const auto requestedSerial = KWaylandExtras::lastInputSerial(window);
0128     connect(
0129         KWaylandExtras::self(),
0130         &KWaylandExtras::xdgActivationTokenArrived,
0131         this,
0132         [this, requestedSerial, action](quint32 serial, const QString &token) {
0133             if (serial != requestedSerial || token.isEmpty()) {
0134                 return;
0135             }
0136             QDBusMessage methodCall =
0137                 QDBusMessage::createMethodCall(c_kdeAgentService, c_kdeAgentPath, c_kdeAgentInterface, QLatin1String("setActivationTokenForAction"));
0138             methodCall << action;
0139             methodCall << token;
0140 
0141             const auto reply = QDBusConnection::sessionBus().asyncCall(methodCall);
0142             auto *watcher = new QDBusPendingCallWatcher(reply, this);
0143             connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, token, action] {
0144                 watcher->deleteLater();
0145 
0146                 QDBusPendingReply<> reply = *watcher;
0147                 if (reply.isError()) {
0148                     qCWarning(KAUTH) << "Failed to set activation token" << token << "for" << action << reply.error().message();
0149                 }
0150             });
0151         },
0152         Qt::SingleShotConnection);
0153     KWaylandExtras::requestXdgActivationToken(window, requestedSerial, {});
0154 }
0155 
0156 Action::AuthStatus Polkit1Backend::authorizeAction(const QString &action)
0157 {
0158     Q_UNUSED(action)
0159     // Always return Yes here, we'll authorize inside isCallerAuthorized
0160     return Action::AuthorizedStatus;
0161 }
0162 
0163 void Polkit1Backend::setupAction(const QString &action)
0164 {
0165     m_cachedResults[action] = actionStatus(action);
0166 }
0167 
0168 Action::AuthStatus Polkit1Backend::actionStatus(const QString &action)
0169 {
0170     PolkitQt1::SystemBusNameSubject subject(QString::fromUtf8(callerID()));
0171     auto authority = PolkitQt1::Authority::instance();
0172     PolkitQt1::Authority::Result r = authority->checkAuthorizationSync(action, subject, PolkitQt1::Authority::None);
0173 
0174     if (authority->hasError()) {
0175         qCDebug(KAUTH) << "Encountered error while checking action status, error code:" << authority->lastError() << authority->errorDetails();
0176         authority->clearError();
0177         return Action::InvalidStatus;
0178     }
0179 
0180     switch (r) {
0181     case PolkitQt1::Authority::Yes:
0182         return Action::AuthorizedStatus;
0183     case PolkitQt1::Authority::No:
0184     case PolkitQt1::Authority::Unknown:
0185         return Action::DeniedStatus;
0186     default:
0187         return Action::AuthRequiredStatus;
0188     }
0189 }
0190 
0191 QByteArray Polkit1Backend::callerID() const
0192 {
0193     return QDBusConnection::systemBus().baseService().toUtf8();
0194 }
0195 
0196 bool Polkit1Backend::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
0197 {
0198     PolkitQt1::SystemBusNameSubject subject(QString::fromUtf8(callerID));
0199     PolkitQt1::Authority *authority = PolkitQt1::Authority::instance();
0200     QMap<QString, QString> polkit1Details;
0201     for (auto it = details.cbegin(); it != details.cend(); ++it) {
0202         polkit1Details.insert(it.key(), it.value().toString());
0203     }
0204 
0205     PolkitQt1::Authority::Result result;
0206     QEventLoop e;
0207     connect(authority, &PolkitQt1::Authority::checkAuthorizationFinished, &e, [&result, &e](PolkitQt1::Authority::Result _result) {
0208         result = _result;
0209         e.quit();
0210     });
0211 
0212 #if POLKITQT1_IS_VERSION(0, 113, 0)
0213     authority->checkAuthorizationWithDetails(action, subject, PolkitQt1::Authority::AllowUserInteraction, polkit1Details);
0214 #else
0215     authority->checkAuthorization(action, subject, PolkitQt1::Authority::AllowUserInteraction);
0216 #endif
0217     e.exec();
0218 
0219     if (authority->hasError()) {
0220         qCDebug(KAUTH) << "Encountered error while checking authorization, error code:" << authority->lastError() << authority->errorDetails();
0221         authority->clearError();
0222     }
0223 
0224     switch (result) {
0225     case PolkitQt1::Authority::Yes:
0226         return true;
0227     default:
0228         return false;
0229     }
0230 }
0231 
0232 void Polkit1Backend::checkForResultChanged()
0233 {
0234     for (auto it = m_cachedResults.begin(); it != m_cachedResults.end(); ++it) {
0235         const QString action = it.key();
0236         if (it.value() != actionStatus(action)) {
0237             *it = actionStatus(action);
0238             Q_EMIT actionStatusChanged(action, *it);
0239         }
0240     }
0241 }
0242 
0243 QVariantMap Polkit1Backend::backendDetails(const DetailsMap &details)
0244 {
0245     QVariantMap backendDetails;
0246     for (auto it = details.cbegin(); it != details.cend(); ++it) {
0247         switch (it.key()) {
0248         case Action::AuthDetail::DetailMessage:
0249             backendDetails.insert(QStringLiteral("polkit.message"), it.value());
0250             break;
0251         case Action::AuthDetail::DetailOther:
0252         default:
0253             backendDetails.insert(QStringLiteral("other_details"), it.value());
0254             break;
0255         }
0256     }
0257     return backendDetails;
0258 }
0259 
0260 } // namespace Auth
0261 
0262 #include "Polkit1Backend.moc"