KAuth

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

KDE's Doxygen guidelines are available online.