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
32constexpr QLatin1String c_kdeAgentService{"org.kde.polkit-kde-authentication-agent-1"};
33constexpr QLatin1String c_kdeAgentPath{"/org/kde/Polkit1AuthAgent"};
34constexpr QLatin1String c_kdeAgentInterface{"org.kde.Polkit1AuthAgent"};
35
36namespace KAuth
37{
38
39Polkit1Backend::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
49Polkit1Backend::~Polkit1Backend()
50{
51}
52
53void 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
106void 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
125void 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
156Action::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
163void Polkit1Backend::setupAction(const QString &action)
164{
165 m_cachedResults[action] = actionStatus(action);
166}
167
168Action::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
191QByteArray Polkit1Backend::callerID() const
192{
193 return QDBusConnection::systemBus().baseService().toUtf8();
194}
195
196bool 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
232void 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
243QVariantMap 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

source code of kauth/src/backends/polkit-1/Polkit1Backend.cpp