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

KDE's Doxygen guidelines are available online.