KAuth

DBusHelperProxy.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
3 SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
4 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "DBusHelperProxy.h"
10#include "BackendsManager.h"
11#include "kauthdebug.h"
12#include "kf6authadaptor.h"
13
14#include <QDBusConnectionInterface>
15#include <QDBusMessage>
16#include <QDBusMetaType>
17#include <QDBusUnixFileDescriptor>
18#include <QMap>
19#include <QMetaMethod>
20#include <QObject>
21#include <QTimer>
22#include <qplugin.h>
23
24extern Q_CORE_EXPORT const QtPrivate::QMetaTypeInterface *qMetaTypeGuiHelper;
25
26namespace KAuth
27{
28static void debugMessageReceived(int t, const QString &message);
29
30DBusHelperProxy::DBusHelperProxy()
31 : responder(nullptr)
32 , m_stopRequest(false)
33 , m_busConnection(QDBusConnection::systemBus())
34{
35 qDBusRegisterMetaType<QMap<QString, QDBusUnixFileDescriptor>>();
36}
37
38DBusHelperProxy::DBusHelperProxy(const QDBusConnection &busConnection)
39 : responder(nullptr)
40 , m_stopRequest(false)
41 , m_busConnection(busConnection)
42{
43 qDBusRegisterMetaType<QMap<QString, QDBusUnixFileDescriptor>>();
44}
45
46DBusHelperProxy::~DBusHelperProxy()
47{
48}
49
50void DBusHelperProxy::stopAction(const QString &action, const QString &helperID)
51{
52 QDBusMessage message;
53 message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf6auth"), QLatin1String("stopAction"));
54
55 QList<QVariant> args;
56 args << action;
57 message.setArguments(args);
58
59 m_busConnection.asyncCall(message);
60}
61
62void DBusHelperProxy::executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout)
63{
65 QVariantMap nonFds;
66 for (auto [key, value] : arguments.asKeyValueRange()) {
67 if (value.metaType() == QMetaType::fromType<QDBusUnixFileDescriptor>()) {
68 fds.insert(key, value.value<QDBusUnixFileDescriptor>());
69 } else {
70 nonFds.insert(key, value);
71 }
72 }
73
74 QByteArray blob;
75 {
76 QDataStream stream(&blob, QIODevice::WriteOnly);
77 stream << nonFds;
78 }
79
80 // on unit tests we won't have a service, but the service will already be running
81 const auto reply = m_busConnection.interface()->startService(helperID);
82 if (!reply.isValid() && !m_busConnection.interface()->isServiceRegistered(helperID)) {
83 ActionReply errorReply = ActionReply::DBusErrorReply();
84 errorReply.setErrorDescription(tr("DBus Backend error: service start %1 failed: %2").arg(helperID, reply.error().message()));
85 Q_EMIT actionPerformed(action, errorReply);
86 return;
87 }
88
89 const bool connected = m_busConnection.connect(helperID,
90 QLatin1String("/"),
91 QLatin1String("org.kde.kf6auth"),
92 QLatin1String("remoteSignal"),
93 this,
94 SLOT(remoteSignalReceived(int, QString, QByteArray)));
95
96 // if already connected reply will be false but we won't have an error or a reason to fail
97 if (!connected && m_busConnection.lastError().isValid()) {
98 ActionReply errorReply = ActionReply::DBusErrorReply();
99 errorReply.setErrorDescription(tr("DBus Backend error: connection to helper failed. %1\n(application: %2 helper: %3)")
100 .arg(m_busConnection.lastError().message(), qApp->applicationName(), helperID));
101 Q_EMIT actionPerformed(action, errorReply);
102 return;
103 }
104
105 QDBusMessage message;
106 message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf6auth"), QLatin1String("performAction"));
107
108 QList<QVariant> args;
109 args << action << BackendsManager::authBackend()->callerID() << BackendsManager::authBackend()->backendDetails(details) << blob << QVariant::fromValue(fds);
110 message.setArguments(args);
111
112 m_actionsInProgress.push_back(action);
113
114 QDBusPendingCall pendingCall = m_busConnection.asyncCall(message, timeout);
115
116 auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
117
118 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, action, args, message, watcher, timeout]() mutable {
119 watcher->deleteLater();
120
121 QDBusMessage reply = watcher->reply();
122
123 if (reply.type() == QDBusMessage::ErrorMessage) {
124 if (watcher->error().type() == QDBusError::InvalidArgs) {
125 // For backwards compatibility if helper binary was built with older KAuth version.
126 args.removeAt(args.count() - 2); // remove backend details
127 message.setArguments(args);
128 reply = m_busConnection.call(message, QDBus::Block, timeout);
129 if (reply.type() != QDBusMessage::ErrorMessage) {
130 return;
131 }
132 }
133 ActionReply r = ActionReply::DBusErrorReply();
134 r.setErrorDescription(tr("DBus Backend error: could not contact the helper. "
135 "Connection error: %1. Message error: %2")
136 .arg(reply.errorMessage(), m_busConnection.lastError().message()));
137 qCWarning(KAUTH) << reply.errorMessage();
138
139 Q_EMIT actionPerformed(action, r);
140 }
141 });
142}
143
144bool DBusHelperProxy::initHelper(const QString &name)
145{
146 new Kf6authAdaptor(this);
147
148 if (!m_busConnection.registerService(name)) {
149 qCWarning(KAUTH) << "Error registering helper DBus service" << name << m_busConnection.lastError().message();
150 return false;
151 }
152
153 if (!m_busConnection.registerObject(QLatin1String("/"), this)) {
154 qCWarning(KAUTH) << "Error registering helper DBus object:" << m_busConnection.lastError().message();
155 return false;
156 }
157
158 m_name = name;
159
160 return true;
161}
162
163void DBusHelperProxy::setHelperResponder(QObject *o)
164{
165 responder = o;
166}
167
168void DBusHelperProxy::remoteSignalReceived(int t, const QString &action, QByteArray blob)
169{
170 SignalType type = static_cast<SignalType>(t);
171 QDataStream stream(&blob, QIODevice::ReadOnly);
172
173 if (type == ActionStarted) {
174 Q_EMIT actionStarted(action);
175 } else if (type == ActionPerformed) {
176 ActionReply reply = ActionReply::deserialize(blob);
177
178 m_actionsInProgress.removeOne(action);
179 Q_EMIT actionPerformed(action, reply);
180 } else if (type == DebugMessage) {
181 int level;
182 QString message;
183
184 stream >> level >> message;
185
186 debugMessageReceived(level, message);
187 } else if (type == ProgressStepIndicator) {
188 int step;
189 stream >> step;
190
191 Q_EMIT progressStep(action, step);
192 } else if (type == ProgressStepData) {
193 QVariantMap data;
194 stream >> data;
195 Q_EMIT progressStepData(action, data);
196 }
197}
198
199void DBusHelperProxy::stopAction(const QString &action)
200{
201 Q_UNUSED(action)
202 //#warning FIXME: The stop request should be action-specific rather than global
203 m_stopRequest = true;
204}
205
206bool DBusHelperProxy::hasToStopAction()
207{
208 QEventLoop loop;
210
211 return m_stopRequest;
212}
213
214bool DBusHelperProxy::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
215{
216 Q_UNUSED(callerID); // this only exists for the benefit of the mac backend. We obtain our callerID from dbus!
217 return BackendsManager::authBackend()->isCallerAuthorized(action, message().service().toUtf8(), details);
218}
219
220QByteArray DBusHelperProxy::performAction(const QString &action,
221 const QByteArray &callerID,
222 const QVariantMap &details,
223 QByteArray arguments,
225{
226 if (!responder) {
227 return ActionReply::NoResponderReply().serialized();
228 }
229
230 if (!m_currentAction.isEmpty()) {
231 return ActionReply::HelperBusyReply().serialized();
232 }
233
234 // Make sure we don't try restoring gui variants, in particular QImage/QPixmap/QIcon are super dangerous
235 // since they end up calling the image loaders and thus are a vector for crashing → executing code
236 auto origMetaTypeGuiHelper = qMetaTypeGuiHelper;
237 qMetaTypeGuiHelper = nullptr;
238
239 QVariantMap args;
240 QDataStream s(&arguments, QIODevice::ReadOnly);
241 s >> args;
242
243 for (auto [key, value] : fdArguments.asKeyValueRange()) {
244 args.insert(key, QVariant::fromValue(value));
245 }
246
247 qMetaTypeGuiHelper = origMetaTypeGuiHelper;
248
249 m_currentAction = action;
250 Q_EMIT remoteSignal(ActionStarted, action, QByteArray());
251 QEventLoop e;
253
254 ActionReply retVal;
255
256 QTimer *timer = responder->property("__KAuth_Helper_Shutdown_Timer").value<QTimer *>();
257 timer->stop();
258
259 if (isCallerAuthorized(action, callerID, details)) {
260 QString slotname = action;
261 if (slotname.startsWith(m_name + QLatin1Char('.'))) {
262 slotname = slotname.right(slotname.length() - m_name.length() - 1);
263 }
264
265 slotname.replace(QLatin1Char('.'), QLatin1Char('_'));
266
267 // For legacy reasons we could be dealing with ActionReply types (i.e.
268 // `using namespace KAuth`). Since Qt type names are verbatim this would
269 // mismatch a return type that is called 'KAuth::ActionReply' and
270 // vice versa. This effectively required client code to always 'use' the
271 // namespace as otherwise we'd not be able to call into it.
272 // To support both scenarios we now dynamically determine what kind of return type
273 // we deal with and call Q_RETURN_ARG either with or without namespace.
274 const auto metaObj = responder->metaObject();
275 const QString slotSignature(slotname + QStringLiteral("(QVariantMap)"));
276 const QMetaMethod method = metaObj->method(metaObj->indexOfMethod(qPrintable(slotSignature)));
277 if (method.isValid()) {
278 const auto needle = "KAuth::";
279 bool success = false;
280 if (strncmp(needle, method.typeName(), strlen(needle)) == 0) {
281 success = method.invoke(responder, Qt::DirectConnection, Q_RETURN_ARG(KAuth::ActionReply, retVal), Q_ARG(QVariantMap, args));
282 } else {
283 success = method.invoke(responder, Qt::DirectConnection, Q_RETURN_ARG(ActionReply, retVal), Q_ARG(QVariantMap, args));
284 }
285 if (!success) {
286 retVal = ActionReply::NoSuchActionReply();
287 }
288 } else {
289 retVal = ActionReply::NoSuchActionReply();
290 }
291 } else {
292 retVal = ActionReply::AuthorizationDeniedReply();
293 }
294
295 timer->start();
296
297 Q_EMIT remoteSignal(ActionPerformed, action, retVal.serialized());
299 m_currentAction.clear();
300 m_stopRequest = false;
301
302 return retVal.serialized();
303}
304
305void DBusHelperProxy::sendDebugMessage(int level, const char *msg)
306{
307 QByteArray blob;
308 QDataStream stream(&blob, QIODevice::WriteOnly);
309
310 stream << level << QString::fromLocal8Bit(msg);
311
312 Q_EMIT remoteSignal(DebugMessage, m_currentAction, blob);
313}
314
315void DBusHelperProxy::sendProgressStep(int step)
316{
317 QByteArray blob;
318 QDataStream stream(&blob, QIODevice::WriteOnly);
319
320 stream << step;
321
322 Q_EMIT remoteSignal(ProgressStepIndicator, m_currentAction, blob);
323}
324
325void DBusHelperProxy::sendProgressStepData(const QVariantMap &data)
326{
327 QByteArray blob;
328 QDataStream stream(&blob, QIODevice::WriteOnly);
329
330 stream << data;
331
332 Q_EMIT remoteSignal(ProgressStepData, m_currentAction, blob);
333}
334
335void debugMessageReceived(int t, const QString &message)
336{
337 QtMsgType type = static_cast<QtMsgType>(t);
338 switch (type) {
339 case QtDebugMsg:
340 qDebug("Debug message from helper: %s", message.toLatin1().data());
341 break;
342 case QtInfoMsg:
343 qInfo("Info message from helper: %s", message.toLatin1().data());
344 break;
345 case QtWarningMsg:
346 qWarning("Warning from helper: %s", message.toLatin1().data());
347 break;
348 case QtCriticalMsg:
349 qCritical("Critical warning from helper: %s", message.toLatin1().data());
350 break;
351 case QtFatalMsg:
352 qFatal("Fatal error from helper: %s", message.toLatin1().data());
353 break;
354 }
355}
356
357int DBusHelperProxy::callerUid() const
358{
359 QDBusConnectionInterface *iface = connection().interface();
360 if (!iface) {
361 return -1;
362 }
363 return iface->serviceUid(message().service());
364}
365
366} // namespace KAuth
367
368#include "moc_DBusHelperProxy.cpp"
Class that encapsulates a reply coming from the helper after executing an action.
Type type(const QSqlDatabase &db)
KAUTHCORE_EXPORT void progressStep(int step)
Send a progressStep signal to the caller application.
QStringView level(QStringView ifopt)
QString name(StandardAction id)
char * data()
QString interface() const const
QDBusReply< uint > serviceUid(const QString &serviceName) const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
QString errorMessage() const const
void setArguments(const QList< QVariant > &arguments)
MessageType type() const const
void finished(QDBusPendingCallWatcher *self)
bool processEvents(ProcessEventsFlags flags)
qsizetype count() const const
void removeAt(qsizetype i)
auto asKeyValueRange() &
iterator insert(const Key &key, const T &value)
bool invoke(QObject *obj, Args &&... arguments) const const
bool isValid() const const
const char * typeName() const const
QMetaType fromType()
QVariant property(const char *name) const const
QString fromLocal8Bit(QByteArrayView str)
qsizetype length() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString right(qsizetype n) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
DirectConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void stop()
QVariant fromValue(T &&value)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:07:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.