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 | |