| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com> |
| 3 | SPDX-FileCopyrightText: 2009 Radek Novacek <rnovacek@redhat.com> |
| 4 | SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org> |
| 5 | SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org> |
| 6 | SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de> |
| 7 | |
| 8 | SPDX-License-Identifier: LGPL-2.1-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "Polkit1Backend.h" |
| 12 | #include "kauthdebug.h" |
| 13 | |
| 14 | #include <KWaylandExtras> |
| 15 | #include <KWindowSystem> |
| 16 | |
| 17 | #include <QCoreApplication> |
| 18 | #include <QTimer> |
| 19 | #include <qplugin.h> |
| 20 | |
| 21 | #include <QGuiApplication> |
| 22 | #include <QWindow> |
| 23 | |
| 24 | #include <QDBusConnection> |
| 25 | #include <QDBusConnectionInterface> |
| 26 | #include <QDBusPendingCallWatcher> |
| 27 | #include <QDBusPendingReply> |
| 28 | |
| 29 | #include <PolkitQt1/Subject> |
| 30 | #include <polkitqt1-version.h> |
| 31 | |
| 32 | constexpr QLatin1String c_kdeAgentService{"org.kde.polkit-kde-authentication-agent-1" }; |
| 33 | constexpr QLatin1String c_kdeAgentPath{"/org/kde/Polkit1AuthAgent" }; |
| 34 | constexpr QLatin1String c_kdeAgentInterface{"org.kde.Polkit1AuthAgent" }; |
| 35 | |
| 36 | namespace KAuth |
| 37 | { |
| 38 | |
| 39 | Polkit1Backend::Polkit1Backend() |
| 40 | : AuthBackend() |
| 41 | { |
| 42 | setCapabilities(AuthorizeFromHelperCapability | PreAuthActionCapability); |
| 43 | |
| 44 | // Setup useful signals |
| 45 | connect(sender: PolkitQt1::Authority::instance(), signal: &PolkitQt1::Authority::configChanged, context: this, slot: &KAuth::Polkit1Backend::checkForResultChanged); |
| 46 | connect(sender: PolkitQt1::Authority::instance(), signal: &PolkitQt1::Authority::consoleKitDBChanged, context: this, slot: &KAuth::Polkit1Backend::checkForResultChanged); |
| 47 | } |
| 48 | |
| 49 | Polkit1Backend::~Polkit1Backend() |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | void Polkit1Backend::preAuthAction(const QString &action, QWindow *parentWindow) |
| 54 | { |
| 55 | // If a parent was not specified, skip this |
| 56 | if (!parentWindow) { |
| 57 | qCDebug(KAUTH) << "Parent widget does not exist, skipping" ; |
| 58 | return; |
| 59 | } |
| 60 | |
| 61 | // Check if we actually are entitled to use GUI capabilities |
| 62 | if (!qGuiApp) { |
| 63 | qCDebug(KAUTH) << "Not streaming parent as we are on a TTY application" ; |
| 64 | return; |
| 65 | } |
| 66 | |
| 67 | // Are we running our KDE auth agent? |
| 68 | if (QDBusConnection::sessionBus().interface()->isServiceRegistered(serviceName: QLatin1String("org.kde.polkit-kde-authentication-agent-1" ))) { |
| 69 | if (KWindowSystem::isPlatformWayland()) { |
| 70 | KWaylandExtras::exportWindow(window: parentWindow); |
| 71 | connect( |
| 72 | sender: KWaylandExtras::self(), |
| 73 | signal: &KWaylandExtras::windowExported, |
| 74 | context: this, |
| 75 | slot: [this, action, parentWindow](QWindow *window, const QString &handle) { |
| 76 | if (window == parentWindow) { |
| 77 | sendWindowHandle(action, handle); |
| 78 | } |
| 79 | }, |
| 80 | type: Qt::SingleShotConnection); |
| 81 | |
| 82 | // Generate and send an XDG Activation token. |
| 83 | sendActivationToken(action, window: parentWindow); |
| 84 | } else { |
| 85 | // Retrieve the dialog root window Id |
| 86 | const qulonglong wId = parentWindow->winId(); |
| 87 | |
| 88 | sendWindowHandle(action, handle: QString::number(wId)); |
| 89 | |
| 90 | // Call the old method for compatibility. |
| 91 | QDBusMessage methodCall = QDBusMessage::createMethodCall(destination: c_kdeAgentService, path: c_kdeAgentPath, interface: c_kdeAgentInterface, method: QLatin1String("setWIdForAction" )); |
| 92 | methodCall << action; |
| 93 | methodCall << wId; |
| 94 | |
| 95 | // Legacy call has to be blocking, old agent doesn't handle it coming in delayed. |
| 96 | const auto reply = QDBusConnection::sessionBus().call(message: methodCall); |
| 97 | if (reply.type() != QDBusMessage::ReplyMessage) { |
| 98 | qWarning() << "Failed to set window id" << wId << "for" << action << reply.errorMessage(); |
| 99 | } |
| 100 | } |
| 101 | } else { |
| 102 | qCDebug(KAUTH) << "KDE polkit agent appears too old or not registered on the bus" ; |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | void Polkit1Backend::sendWindowHandle(const QString &action, const QString &handle) |
| 107 | { |
| 108 | // Send it over the bus to our agent |
| 109 | QDBusMessage methodCall = QDBusMessage::createMethodCall(destination: c_kdeAgentService, path: c_kdeAgentPath, interface: c_kdeAgentInterface, method: QLatin1String("setWindowHandleForAction" )); |
| 110 | methodCall << action; |
| 111 | methodCall << handle; |
| 112 | |
| 113 | const auto reply = QDBusConnection::sessionBus().asyncCall(message: methodCall); |
| 114 | auto *watcher = new QDBusPendingCallWatcher(reply, this); |
| 115 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: [this, watcher, handle, action] { |
| 116 | watcher->deleteLater(); |
| 117 | |
| 118 | QDBusPendingReply<> reply = *watcher; |
| 119 | if (reply.isError()) { |
| 120 | qCWarning(KAUTH) << "Failed to set window handle" << handle << "for" << action << reply.error().message(); |
| 121 | } |
| 122 | }); |
| 123 | } |
| 124 | |
| 125 | void Polkit1Backend::sendActivationToken(const QString &action, QWindow *window) |
| 126 | { |
| 127 | const auto requestedSerial = KWaylandExtras::lastInputSerial(window); |
| 128 | connect( |
| 129 | sender: KWaylandExtras::self(), |
| 130 | signal: &KWaylandExtras::xdgActivationTokenArrived, |
| 131 | context: this, |
| 132 | slot: [this, requestedSerial, action](quint32 serial, const QString &token) { |
| 133 | if (serial != requestedSerial || token.isEmpty()) { |
| 134 | return; |
| 135 | } |
| 136 | QDBusMessage methodCall = |
| 137 | QDBusMessage::createMethodCall(destination: c_kdeAgentService, path: c_kdeAgentPath, interface: c_kdeAgentInterface, method: QLatin1String("setActivationTokenForAction" )); |
| 138 | methodCall << action; |
| 139 | methodCall << token; |
| 140 | |
| 141 | const auto reply = QDBusConnection::sessionBus().asyncCall(message: methodCall); |
| 142 | auto *watcher = new QDBusPendingCallWatcher(reply, this); |
| 143 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: [this, watcher, token, action] { |
| 144 | watcher->deleteLater(); |
| 145 | |
| 146 | QDBusPendingReply<> reply = *watcher; |
| 147 | if (reply.isError()) { |
| 148 | qCWarning(KAUTH) << "Failed to set activation token" << token << "for" << action << reply.error().message(); |
| 149 | } |
| 150 | }); |
| 151 | }, |
| 152 | type: Qt::SingleShotConnection); |
| 153 | KWaylandExtras::requestXdgActivationToken(win: window, serial: requestedSerial, app_id: {}); |
| 154 | } |
| 155 | |
| 156 | Action::AuthStatus Polkit1Backend::authorizeAction(const QString &action) |
| 157 | { |
| 158 | Q_UNUSED(action) |
| 159 | // Always return Yes here, we'll authorize inside isCallerAuthorized |
| 160 | return Action::AuthorizedStatus; |
| 161 | } |
| 162 | |
| 163 | void Polkit1Backend::setupAction(const QString &action) |
| 164 | { |
| 165 | m_cachedResults[action] = actionStatus(action); |
| 166 | } |
| 167 | |
| 168 | Action::AuthStatus Polkit1Backend::actionStatus(const QString &action) |
| 169 | { |
| 170 | PolkitQt1::SystemBusNameSubject subject(QString::fromUtf8(ba: callerID())); |
| 171 | auto authority = PolkitQt1::Authority::instance(); |
| 172 | PolkitQt1::Authority::Result r = authority->checkAuthorizationSync(actionId: action, subject, flags: PolkitQt1::Authority::None); |
| 173 | |
| 174 | if (authority->hasError()) { |
| 175 | qCDebug(KAUTH) << "Encountered error while checking action status, error code:" << authority->lastError() << authority->errorDetails(); |
| 176 | authority->clearError(); |
| 177 | return Action::InvalidStatus; |
| 178 | } |
| 179 | |
| 180 | switch (r) { |
| 181 | case PolkitQt1::Authority::Yes: |
| 182 | return Action::AuthorizedStatus; |
| 183 | case PolkitQt1::Authority::No: |
| 184 | case PolkitQt1::Authority::Unknown: |
| 185 | return Action::DeniedStatus; |
| 186 | default: |
| 187 | return Action::AuthRequiredStatus; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | QByteArray Polkit1Backend::callerID() const |
| 192 | { |
| 193 | return QDBusConnection::systemBus().baseService().toUtf8(); |
| 194 | } |
| 195 | |
| 196 | bool Polkit1Backend::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details) |
| 197 | { |
| 198 | PolkitQt1::SystemBusNameSubject subject(QString::fromUtf8(ba: callerID)); |
| 199 | PolkitQt1::Authority *authority = PolkitQt1::Authority::instance(); |
| 200 | QMap<QString, QString> polkit1Details; |
| 201 | for (auto it = details.cbegin(); it != details.cend(); ++it) { |
| 202 | polkit1Details.insert(key: it.key(), value: it.value().toString()); |
| 203 | } |
| 204 | |
| 205 | PolkitQt1::Authority::Result result; |
| 206 | QEventLoop e; |
| 207 | connect(sender: authority, signal: &PolkitQt1::Authority::checkAuthorizationFinished, context: &e, slot: [&result, &e](PolkitQt1::Authority::Result _result) { |
| 208 | result = _result; |
| 209 | e.quit(); |
| 210 | }); |
| 211 | |
| 212 | #if POLKITQT1_IS_VERSION(0, 113, 0) |
| 213 | authority->checkAuthorizationWithDetails(actionId: action, subject, flags: PolkitQt1::Authority::AllowUserInteraction, details: polkit1Details); |
| 214 | #else |
| 215 | authority->checkAuthorization(action, subject, PolkitQt1::Authority::AllowUserInteraction); |
| 216 | #endif |
| 217 | e.exec(); |
| 218 | |
| 219 | if (authority->hasError()) { |
| 220 | qCDebug(KAUTH) << "Encountered error while checking authorization, error code:" << authority->lastError() << authority->errorDetails(); |
| 221 | authority->clearError(); |
| 222 | } |
| 223 | |
| 224 | switch (result) { |
| 225 | case PolkitQt1::Authority::Yes: |
| 226 | return true; |
| 227 | default: |
| 228 | return false; |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | void Polkit1Backend::checkForResultChanged() |
| 233 | { |
| 234 | for (auto it = m_cachedResults.begin(); it != m_cachedResults.end(); ++it) { |
| 235 | const QString action = it.key(); |
| 236 | if (it.value() != actionStatus(action)) { |
| 237 | *it = actionStatus(action); |
| 238 | Q_EMIT actionStatusChanged(action, status: *it); |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | QVariantMap Polkit1Backend::backendDetails(const DetailsMap &details) |
| 244 | { |
| 245 | QVariantMap backendDetails; |
| 246 | for (auto it = details.cbegin(); it != details.cend(); ++it) { |
| 247 | switch (it.key()) { |
| 248 | case Action::AuthDetail::DetailMessage: |
| 249 | backendDetails.insert(QStringLiteral("polkit.message" ), value: it.value()); |
| 250 | break; |
| 251 | case Action::AuthDetail::DetailOther: |
| 252 | default: |
| 253 | backendDetails.insert(QStringLiteral("other_details" ), value: it.value()); |
| 254 | break; |
| 255 | } |
| 256 | } |
| 257 | return backendDetails; |
| 258 | } |
| 259 | |
| 260 | } // namespace Auth |
| 261 | |
| 262 | #include "Polkit1Backend.moc" |
| 263 | |
| 264 | #include "moc_Polkit1Backend.cpp" |
| 265 | |