Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,368 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "DBusHelperProxy.h"
#include "BackendsManager.h"
#include "kauthdebug.h"
#include "kf6authadaptor.h"
#include <QDBusConnectionInterface>
#include <QDBusMessage>
#include <QDBusMetaType>
#include <QDBusUnixFileDescriptor>
#include <QMap>
#include <QMetaMethod>
#include <QObject>
#include <QTimer>
#include <qplugin.h>
extern Q_CORE_EXPORT const QtPrivate::QMetaTypeInterface *qMetaTypeGuiHelper;
namespace KAuth
{
static void debugMessageReceived(int t, const QString &message);
DBusHelperProxy::DBusHelperProxy()
: responder(nullptr)
, m_stopRequest(false)
, m_busConnection(QDBusConnection::systemBus())
{
qDBusRegisterMetaType<QMap<QString, QDBusUnixFileDescriptor>>();
}
DBusHelperProxy::DBusHelperProxy(const QDBusConnection &busConnection)
: responder(nullptr)
, m_stopRequest(false)
, m_busConnection(busConnection)
{
qDBusRegisterMetaType<QMap<QString, QDBusUnixFileDescriptor>>();
}
DBusHelperProxy::~DBusHelperProxy()
{
}
void DBusHelperProxy::stopAction(const QString &action, const QString &helperID)
{
QDBusMessage message;
message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf6auth"), QLatin1String("stopAction"));
QList<QVariant> args;
args << action;
message.setArguments(args);
m_busConnection.asyncCall(message);
}
void DBusHelperProxy::executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout)
{
QMap<QString, QDBusUnixFileDescriptor> fds;
QVariantMap nonFds;
for (auto [key, value] : arguments.asKeyValueRange()) {
if (value.metaType() == QMetaType::fromType<QDBusUnixFileDescriptor>()) {
fds.insert(key, value.value<QDBusUnixFileDescriptor>());
} else {
nonFds.insert(key, value);
}
}
QByteArray blob;
{
QDataStream stream(&blob, QIODevice::WriteOnly);
stream << nonFds;
}
// on unit tests we won't have a service, but the service will already be running
const auto reply = m_busConnection.interface()->startService(helperID);
if (!reply.isValid() && !m_busConnection.interface()->isServiceRegistered(helperID)) {
ActionReply errorReply = ActionReply::DBusErrorReply();
errorReply.setErrorDescription(tr("DBus Backend error: service start %1 failed: %2").arg(helperID, reply.error().message()));
Q_EMIT actionPerformed(action, errorReply);
return;
}
const bool connected = m_busConnection.connect(helperID,
QLatin1String("/"),
QLatin1String("org.kde.kf6auth"),
QLatin1String("remoteSignal"),
this,
SLOT(remoteSignalReceived(int, QString, QByteArray)));
// if already connected reply will be false but we won't have an error or a reason to fail
if (!connected && m_busConnection.lastError().isValid()) {
ActionReply errorReply = ActionReply::DBusErrorReply();
errorReply.setErrorDescription(tr("DBus Backend error: connection to helper failed. %1\n(application: %2 helper: %3)")
.arg(m_busConnection.lastError().message(), qApp->applicationName(), helperID));
Q_EMIT actionPerformed(action, errorReply);
return;
}
QDBusMessage message;
message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf6auth"), QLatin1String("performAction"));
QList<QVariant> args;
args << action << BackendsManager::authBackend()->callerID() << BackendsManager::authBackend()->backendDetails(details) << blob << QVariant::fromValue(fds);
message.setArguments(args);
m_actionsInProgress.push_back(action);
QDBusPendingCall pendingCall = m_busConnection.asyncCall(message, timeout);
auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, action, args, message, watcher, timeout]() mutable {
watcher->deleteLater();
QDBusMessage reply = watcher->reply();
if (reply.type() == QDBusMessage::ErrorMessage) {
if (watcher->error().type() == QDBusError::InvalidArgs) {
// For backwards compatibility if helper binary was built with older KAuth version.
args.removeAt(args.count() - 2); // remove backend details
message.setArguments(args);
reply = m_busConnection.call(message, QDBus::Block, timeout);
if (reply.type() != QDBusMessage::ErrorMessage) {
return;
}
}
ActionReply r = ActionReply::DBusErrorReply();
r.setErrorDescription(tr("DBus Backend error: could not contact the helper. "
"Connection error: %1. Message error: %2")
.arg(reply.errorMessage(), m_busConnection.lastError().message()));
qCWarning(KAUTH) << reply.errorMessage();
Q_EMIT actionPerformed(action, r);
}
});
}
bool DBusHelperProxy::initHelper(const QString &name)
{
new Kf6authAdaptor(this);
if (!m_busConnection.registerService(name)) {
qCWarning(KAUTH) << "Error registering helper DBus service" << name << m_busConnection.lastError().message();
return false;
}
if (!m_busConnection.registerObject(QLatin1String("/"), this)) {
qCWarning(KAUTH) << "Error registering helper DBus object:" << m_busConnection.lastError().message();
return false;
}
m_name = name;
return true;
}
void DBusHelperProxy::setHelperResponder(QObject *o)
{
responder = o;
}
void DBusHelperProxy::remoteSignalReceived(int t, const QString &action, QByteArray blob)
{
SignalType type = static_cast<SignalType>(t);
QDataStream stream(&blob, QIODevice::ReadOnly);
if (type == ActionStarted) {
Q_EMIT actionStarted(action);
} else if (type == ActionPerformed) {
ActionReply reply = ActionReply::deserialize(blob);
m_actionsInProgress.removeOne(action);
Q_EMIT actionPerformed(action, reply);
} else if (type == DebugMessage) {
int level;
QString message;
stream >> level >> message;
debugMessageReceived(level, message);
} else if (type == ProgressStepIndicator) {
int step;
stream >> step;
Q_EMIT progressStep(action, step);
} else if (type == ProgressStepData) {
QVariantMap data;
stream >> data;
Q_EMIT progressStepData(action, data);
}
}
void DBusHelperProxy::stopAction(const QString &action)
{
Q_UNUSED(action)
//#warning FIXME: The stop request should be action-specific rather than global
m_stopRequest = true;
}
bool DBusHelperProxy::hasToStopAction()
{
QEventLoop loop;
loop.processEvents(QEventLoop::AllEvents);
return m_stopRequest;
}
bool DBusHelperProxy::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
{
Q_UNUSED(callerID); // this only exists for the benefit of the mac backend. We obtain our callerID from dbus!
return BackendsManager::authBackend()->isCallerAuthorized(action, message().service().toUtf8(), details);
}
QByteArray DBusHelperProxy::performAction(const QString &action,
const QByteArray &callerID,
const QVariantMap &details,
QByteArray arguments,
const QMap<QString, QDBusUnixFileDescriptor> &fdArguments)
{
if (!responder) {
return ActionReply::NoResponderReply().serialized();
}
if (!m_currentAction.isEmpty()) {
return ActionReply::HelperBusyReply().serialized();
}
// Make sure we don't try restoring gui variants, in particular QImage/QPixmap/QIcon are super dangerous
// since they end up calling the image loaders and thus are a vector for crashing → executing code
auto origMetaTypeGuiHelper = qMetaTypeGuiHelper;
qMetaTypeGuiHelper = nullptr;
QVariantMap args;
QDataStream s(&arguments, QIODevice::ReadOnly);
s >> args;
for (auto [key, value] : fdArguments.asKeyValueRange()) {
args.insert(key, QVariant::fromValue(value));
}
qMetaTypeGuiHelper = origMetaTypeGuiHelper;
m_currentAction = action;
Q_EMIT remoteSignal(ActionStarted, action, QByteArray());
QEventLoop e;
e.processEvents(QEventLoop::AllEvents);
ActionReply retVal;
QTimer *timer = responder->property("__KAuth_Helper_Shutdown_Timer").value<QTimer *>();
timer->stop();
if (isCallerAuthorized(action, callerID, details)) {
QString slotname = action;
if (slotname.startsWith(m_name + QLatin1Char('.'))) {
slotname = slotname.right(slotname.length() - m_name.length() - 1);
}
slotname.replace(QLatin1Char('.'), QLatin1Char('_'));
// For legacy reasons we could be dealing with ActionReply types (i.e.
// `using namespace KAuth`). Since Qt type names are verbatim this would
// mismatch a return type that is called 'KAuth::ActionReply' and
// vice versa. This effectively required client code to always 'use' the
// namespace as otherwise we'd not be able to call into it.
// To support both scenarios we now dynamically determine what kind of return type
// we deal with and call Q_RETURN_ARG either with or without namespace.
const auto metaObj = responder->metaObject();
const QString slotSignature(slotname + QStringLiteral("(QVariantMap)"));
const QMetaMethod method = metaObj->method(metaObj->indexOfMethod(qPrintable(slotSignature)));
if (method.isValid()) {
const auto needle = "KAuth::";
bool success = false;
if (strncmp(needle, method.typeName(), strlen(needle)) == 0) {
success = method.invoke(responder, Qt::DirectConnection, Q_RETURN_ARG(KAuth::ActionReply, retVal), Q_ARG(QVariantMap, args));
} else {
success = method.invoke(responder, Qt::DirectConnection, Q_RETURN_ARG(ActionReply, retVal), Q_ARG(QVariantMap, args));
}
if (!success) {
retVal = ActionReply::NoSuchActionReply();
}
} else {
retVal = ActionReply::NoSuchActionReply();
}
} else {
retVal = ActionReply::AuthorizationDeniedReply();
}
timer->start();
Q_EMIT remoteSignal(ActionPerformed, action, retVal.serialized());
e.processEvents(QEventLoop::AllEvents);
m_currentAction.clear();
m_stopRequest = false;
return retVal.serialized();
}
void DBusHelperProxy::sendDebugMessage(int level, const char *msg)
{
QByteArray blob;
QDataStream stream(&blob, QIODevice::WriteOnly);
stream << level << QString::fromLocal8Bit(msg);
Q_EMIT remoteSignal(DebugMessage, m_currentAction, blob);
}
void DBusHelperProxy::sendProgressStep(int step)
{
QByteArray blob;
QDataStream stream(&blob, QIODevice::WriteOnly);
stream << step;
Q_EMIT remoteSignal(ProgressStepIndicator, m_currentAction, blob);
}
void DBusHelperProxy::sendProgressStepData(const QVariantMap &data)
{
QByteArray blob;
QDataStream stream(&blob, QIODevice::WriteOnly);
stream << data;
Q_EMIT remoteSignal(ProgressStepData, m_currentAction, blob);
}
void debugMessageReceived(int t, const QString &message)
{
QtMsgType type = static_cast<QtMsgType>(t);
switch (type) {
case QtDebugMsg:
qDebug("Debug message from helper: %s", message.toLatin1().data());
break;
case QtInfoMsg:
qInfo("Info message from helper: %s", message.toLatin1().data());
break;
case QtWarningMsg:
qWarning("Warning from helper: %s", message.toLatin1().data());
break;
case QtCriticalMsg:
qCritical("Critical warning from helper: %s", message.toLatin1().data());
break;
case QtFatalMsg:
qFatal("Fatal error from helper: %s", message.toLatin1().data());
break;
}
}
int DBusHelperProxy::callerUid() const
{
QDBusConnectionInterface *iface = connection().interface();
if (!iface) {
return -1;
}
return iface->serviceUid(message().service());
}
} // namespace KAuth
#include "moc_DBusHelperProxy.cpp"
@@ -0,0 +1,82 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef DBUS_HELPER_PROXY_H
#define DBUS_HELPER_PROXY_H
#include "HelperProxy.h"
#include "actionreply.h"
#include <QDBusConnection>
#include <QDBusContext>
#include <QDBusUnixFileDescriptor>
#include <QVariant>
namespace KAuth
{
class DBusHelperProxy : public HelperProxy, protected QDBusContext
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.DBusHelperProxy")
Q_INTERFACES(KAuth::HelperProxy)
QObject *responder;
QString m_name;
QString m_currentAction;
bool m_stopRequest;
QList<QString> m_actionsInProgress;
QDBusConnection m_busConnection;
enum SignalType {
ActionStarted, // The blob argument is empty
ActionPerformed, // The blob argument contains the ActionReply
DebugMessage, // The blob argument contains the debug level and the message (in this order)
ProgressStepIndicator, // The blob argument contains the step indicator
ProgressStepData, // The blob argument contains the QVariantMap
};
public:
DBusHelperProxy();
DBusHelperProxy(const QDBusConnection &busConnection);
~DBusHelperProxy() override;
virtual void
executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout = -1) override;
void stopAction(const QString &action, const QString &helperID) override;
bool initHelper(const QString &name) override;
void setHelperResponder(QObject *o) override;
bool hasToStopAction() override;
void sendDebugMessage(int level, const char *msg) override;
void sendProgressStep(int step) override;
void sendProgressStepData(const QVariantMap &data) override;
int callerUid() const override;
public Q_SLOTS:
void stopAction(const QString &action);
QByteArray performAction(const QString &action,
const QByteArray &callerID,
const QVariantMap &details,
QByteArray arguments,
const QMap<QString, QDBusUnixFileDescriptor> &fdArguments);
Q_SIGNALS:
void remoteSignal(int type, const QString &action, const QByteArray &blob); // This signal is sent from the helper to the app
private Q_SLOTS:
void remoteSignalReceived(int type, const QString &action, QByteArray blob);
private:
bool isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details);
};
} // namespace Auth
#endif
@@ -0,0 +1,15 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Only user root can own the foo helper -->
<policy user="@HELPER_USER@">
<allow own="@HELPER_ID@"/>
</policy>
<policy context="default">
<allow send_destination="@HELPER_ID@"/>
</policy>
</busconfig>
@@ -0,0 +1,4 @@
[D-BUS Service]
Name=@HELPER_ID@
Exec=@KAUTH_HELPER_INSTALL_ABSOLUTE_DIR@/@HELPER_TARGET@
User=@HELPER_USER@
@@ -0,0 +1,13 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Allow anyone to call into the service - we'll reject callers using Polkit -->
<policy context="default">
<allow send_interface="org.kde.kf6auth"/>
<allow receive_sender="org.kde.kf6auth"/>
<allow receive_interface="org.kde.kf6auth"/>
</policy>
</busconfig>
@@ -0,0 +1,24 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.kf6auth">
<method name="performAction" >
<arg name="action" type="s" direction="in" />
<arg name="callerID" type="ay" direction="in" />
<arg name="details" type="a{sv}" direction="in" />
<arg name="arguments" type="ay" direction="in" />
<arg name="fdArguments" type="a{sh}" direction="in" />
<arg name="r" type="ay" direction="out" />
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In4" value="QMap&lt;QString,QDBusUnixFileDescriptor&gt;"/>
</method>
<method name="stopAction" >
<arg name="action" type="s" direction="in" />
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
<signal name="remoteSignal" >
<arg name="type" type="i" />
<arg name="action" type="s" />
<arg name="blob" type="ay" />
</signal>
</interface>
</node>
@@ -0,0 +1,49 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "FakeBackend.h"
namespace KAuth
{
FakeBackend::FakeBackend()
: AuthBackend()
{
setCapabilities(NoCapability);
}
Action::AuthStatus FakeBackend::authorizeAction(const QString &action)
{
Q_UNUSED(action)
return Action::DeniedStatus;
}
void FakeBackend::setupAction(const QString &action)
{
Q_UNUSED(action)
}
Action::AuthStatus FakeBackend::actionStatus(const QString &action)
{
Q_UNUSED(action)
return Action::DeniedStatus;
}
QByteArray FakeBackend::callerID() const
{
return QByteArray();
}
bool FakeBackend::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
{
Q_UNUSED(action)
Q_UNUSED(callerID)
Q_UNUSED(details)
return false;
}
} // namespace Auth
#include "moc_FakeBackend.cpp"
@@ -0,0 +1,33 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef FAKE_BACKEND_H
#define FAKE_BACKEND_H
#include "AuthBackend.h"
#include <QHash>
class QByteArray;
namespace KAuth
{
class FakeBackend : public AuthBackend
{
Q_OBJECT
Q_INTERFACES(KAuth::AuthBackend)
public:
FakeBackend();
void setupAction(const QString &) override;
Action::AuthStatus authorizeAction(const QString &) override;
Action::AuthStatus actionStatus(const QString &) override;
QByteArray callerID() const override;
bool isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details) override;
};
} // namespace Auth
#endif
@@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <auth/policy-gen/policy-gen.h>
#include <QDebug>
#include <QTextStream>
#include <cstdio>
const char header[] =
""
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<!DOCTYPE policyconfig PUBLIC\n"
"\"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN\"\n"
"\"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd\">\n"
"<policyconfig>\n";
const char policy_tag[] =
""
" <defaults>\n"
" <allow_inactive>no</allow_inactive>\n"
" <allow_active>%1</allow_active>\n"
" </defaults>\n";
const char dent[] = " ";
void output(const QList<Action> &actions, const QMap<QString, QString> &domain)
{
Q_UNUSED(domain)
QTextStream out(stdout);
out << header;
for (const Action &action : std::as_const(actions)) {
out << dent << "<action id=\"" << action.name << "\" >\n";
const auto lstKeys = action.descriptions.keys();
for (const QString &lang : lstKeys) {
out << dent << dent << "<description";
if (lang != "en") {
out << " xml:lang=\"" << lang << '"';
}
out << '>' << action.messages.value(lang) << "</description>\n";
}
const auto lstMessagesKeys = action.messages.keys();
for (const QString &lang : lstMessagesKeys) {
out << dent << dent << "<message";
if (lang != "en") {
out << " xml:lang=\"" << lang << '"';
}
out << '>' << action.descriptions.value(lang) << "</message>\n";
}
QString policy = action.policy;
if (!action.persistence.isEmpty()) {
policy += "_keep_" + action.persistence;
}
out << QString(policy_tag).arg(policy);
out << dent << "</action>\n";
}
out << "</policyconfig>\n";
}
@@ -0,0 +1,75 @@
/*
SPDX-FileCopyrightText: 2010 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "FakeHelperProxy.h"
namespace KAuth
{
FakeHelperProxy::FakeHelperProxy()
: HelperProxy()
{
}
FakeHelperProxy::~FakeHelperProxy()
{
}
void FakeHelperProxy::sendProgressStepData(const QVariantMap &step)
{
Q_UNUSED(step)
}
void FakeHelperProxy::sendProgressStep(int step)
{
Q_UNUSED(step)
}
void FakeHelperProxy::sendDebugMessage(int level, const char *msg)
{
Q_UNUSED(level)
Q_UNUSED(msg)
}
bool FakeHelperProxy::hasToStopAction()
{
return false;
}
void FakeHelperProxy::setHelperResponder(QObject *o)
{
Q_UNUSED(o)
}
bool FakeHelperProxy::initHelper(const QString &name)
{
Q_UNUSED(name)
return false;
}
void FakeHelperProxy::stopAction(const QString &action, const QString &helperID)
{
Q_UNUSED(action)
Q_UNUSED(helperID)
}
void FakeHelperProxy::executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout)
{
Q_UNUSED(helperID)
Q_UNUSED(details)
Q_UNUSED(arguments)
Q_UNUSED(timeout)
Q_EMIT actionPerformed(action, KAuth::ActionReply::NoSuchActionReply());
}
int FakeHelperProxy::callerUid() const
{
return -1;
}
}
#include "moc_FakeHelperProxy.cpp"
@@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2010 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef FAKEHELPERPROXY_H
#define FAKEHELPERPROXY_H
#include "HelperProxy.h"
namespace KAuth
{
class FakeHelperProxy : public HelperProxy
{
Q_OBJECT
Q_INTERFACES(KAuth::HelperProxy)
public:
FakeHelperProxy();
~FakeHelperProxy() override;
void sendProgressStepData(const QVariantMap &step) override;
void sendProgressStep(int step) override;
void sendDebugMessage(int level, const char *msg) override;
bool hasToStopAction() override;
void setHelperResponder(QObject *o) override;
bool initHelper(const QString &name) override;
void stopAction(const QString &action, const QString &helperID) override;
void executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout = -1) override;
int callerUid() const override;
};
}
#endif // FAKEHELPERPROXY_H
@@ -0,0 +1,175 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2014, 2016 René Bertin <rjvbertin@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "AuthServicesBackend.h"
#include <qplugin.h>
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KAUTH_OSX)
// logging category for this backend, default: log stuff >= warning
Q_LOGGING_CATEGORY(KAUTH_OSX, "kf.auth.apple", QtWarningMsg)
namespace KAuth
{
static AuthorizationRef s_authRef = NULL;
AuthorizationRef authRef()
{
if (!s_authRef) {
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &s_authRef);
}
return s_authRef;
}
// GetActionRights return codes:
// errAuthorizationSuccess = 0,
// errAuthorizationInvalidSet = -60001, /* The authorization rights are invalid. */
// errAuthorizationInvalidRef = -60002, /* The authorization reference is invalid. */
// errAuthorizationInvalidTag = -60003, /* The authorization tag is invalid. */
// errAuthorizationInvalidPointer = -60004, /* The returned authorization is invalid. */
// errAuthorizationDenied = -60005, /* The authorization was denied. */
// errAuthorizationCanceled = -60006, /* The authorization was cancelled by the user. */
// errAuthorizationInteractionNotAllowed = -60007, /* The authorization was denied since no user interaction was possible. */
// errAuthorizationInternal = -60008, /* Unable to obtain authorization for this operation. */
// errAuthorizationExternalizeNotAllowed = -60009, /* The authorization is not allowed to be converted to an external format. */
// errAuthorizationInternalizeNotAllowed = -60010, /* The authorization is not allowed to be created from an external format. */
// errAuthorizationInvalidFlags = -60011, /* The provided option flag(s) are invalid for this authorization operation. */
// errAuthorizationToolExecuteFailure = -60031, /* The specified program could not be executed. */
// errAuthorizationToolEnvironmentError = -60032, /* An invalid status was returned during execution of a privileged tool. */
// errAuthorizationBadAddress = -60033, /* The requested socket address is invalid (must be 0-1023 inclusive). */
static OSStatus GetActionRights(const QString &action, AuthorizationFlags flags, AuthorizationRef auth)
{
AuthorizationItem item;
item.name = action.toUtf8().constData();
item.valueLength = 0;
item.value = NULL;
item.flags = 0;
AuthorizationRights rights;
rights.count = 1;
rights.items = &item;
OSStatus result = AuthorizationCopyRights(auth, &rights, kAuthorizationEmptyEnvironment, flags, NULL);
return result;
}
// On OS X we avoid using a helper but grab privilege from here, the client.
AuthServicesBackend::AuthServicesBackend()
: AuthBackend()
{
setCapabilities(AuthorizeFromClientCapability);
}
AuthServicesBackend::~AuthServicesBackend()
{
if (s_authRef) {
OSStatus err = AuthorizationFree(s_authRef, kAuthorizationFlagDefaults);
qCDebug(KAUTH_OSX) << "AuthorizationFree(" << s_authRef << ") returned" << err;
s_authRef = NULL;
}
}
void AuthServicesBackend::setupAction(const QString &)
{
// Nothing to do here...
}
Action::AuthStatus AuthServicesBackend::authorizeAction(const QString &action)
{
Action::AuthStatus retval;
OSStatus result = GetActionRights(action, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, authRef());
qCDebug(KAUTH_OSX) << "AuthServicesBackend::authorizeAction(" << action << ") AuthorizationCopyRights returned" << result;
switch (result) {
case errAuthorizationSuccess:
retval = Action::AuthorizedStatus;
break;
case errAuthorizationCanceled:
retval = Action::UserCancelledStatus;
break;
case errAuthorizationInteractionNotAllowed:
case errAuthorizationDenied:
retval = Action::DeniedStatus;
break;
case errAuthorizationInternal:
// does this make sense?
retval = Action::AuthRequiredStatus;
break;
case errAuthorizationExternalizeNotAllowed:
case errAuthorizationInternalizeNotAllowed:
case errAuthorizationToolExecuteFailure:
case errAuthorizationToolEnvironmentError:
case errAuthorizationBadAddress:
retval = Action::ErrorStatus;
break;
default:
retval = Action::InvalidStatus;
break;
}
return retval;
}
Action::AuthStatus AuthServicesBackend::actionStatus(const QString &action)
{
Action::AuthStatus retval;
OSStatus result = GetActionRights(action, kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize, authRef());
qCDebug(KAUTH_OSX) << "AuthServicesBackend::actionStatus(" << action << ") AuthorizationCopyRights returned" << result;
// this function has a simpler return code parser:
switch (result) {
case errAuthorizationSuccess:
retval = Action::AuthorizedStatus;
break;
case errAuthorizationCanceled:
retval = Action::UserCancelledStatus;
break;
case errAuthorizationInteractionNotAllowed:
retval = Action::AuthRequiredStatus;
break;
default:
retval = Action::DeniedStatus;
break;
}
return retval;
}
QByteArray AuthServicesBackend::callerID() const
{
AuthorizationExternalForm ext;
AuthorizationMakeExternalForm(authRef(), &ext);
QByteArray id((const char *)&ext, sizeof(ext));
return id;
}
bool AuthServicesBackend::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
{
Q_UNUSED(details);
AuthorizationExternalForm ext;
memcpy(&ext, callerID.data(), sizeof(ext));
AuthorizationRef auth;
if (AuthorizationCreateFromExternalForm(&ext, &auth) != noErr) {
qCWarning(KAUTH_OSX()) << "AuthorizationCreateFromExternalForm(" << action << "," << callerID.constData() << ") failed";
return false;
}
OSStatus result = GetActionRights(action, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, auth);
AuthorizationFree(auth, kAuthorizationFlagDefaults);
qCDebug(KAUTH_OSX) << "AuthServicesBackend::isCallerAuthorized(" << action << "," << callerID.constData() << ") AuthorizationCopyRights returned" << result;
return result == errAuthorizationSuccess;
}
}; // namespace KAuth
#include "moc_AuthServicesBackend.cpp"
@@ -0,0 +1,33 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef AUTHSERVICES_BACKEND_H
#define AUTHSERVICES_BACKEND_H
#include "AuthBackend.h"
#include <Security/Security.h>
namespace KAuth
{
class AuthServicesBackend : public AuthBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.AuthServicesBackend")
Q_INTERFACES(KAuth::AuthBackend)
public:
AuthServicesBackend();
virtual ~AuthServicesBackend();
virtual void setupAction(const QString &);
virtual Action::AuthStatus authorizeAction(const QString &);
virtual Action::AuthStatus actionStatus(const QString &);
virtual QByteArray callerID() const;
virtual bool isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details);
};
} // namespace KAuth
#endif
@@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "../../policy-gen/policy-gen.h"
#include <Security/Security.h>
#include <iostream>
#include <QDebug>
using namespace std;
void output(const QList<Action> &actions, const QMap<QString, QString> &domain)
{
AuthorizationRef auth;
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &auth);
OSStatus err;
for (const Action &action : std::as_const(actions)) {
err = AuthorizationRightGet(action.name.toLatin1().constData(), NULL);
if (err != errAuthorizationSuccess) {
QString rule;
if (action.policy == QLatin1String("yes")) {
rule = QString::fromLatin1(kAuthorizationRuleClassAllow);
} else if (action.policy == QLatin1String("no")) {
rule = QString::fromLatin1(kAuthorizationRuleClassDeny);
} else if (action.policy == QLatin1String("auth_self")) {
rule = QString::fromLatin1(kAuthorizationRuleAuthenticateAsSessionUser);
} else if (action.policy == QLatin1String("auth_admin")) {
rule = QString::fromLatin1(kAuthorizationRuleAuthenticateAsAdmin);
}
CFStringRef cfRule = CFStringCreateWithCString(NULL, rule.toLatin1().constData(), kCFStringEncodingASCII);
CFStringRef cfPrompt =
CFStringCreateWithCString(NULL, action.descriptions.value(QLatin1String("en")).toLatin1().constData(), kCFStringEncodingASCII);
err = AuthorizationRightSet(auth, action.name.toLatin1().constData(), cfRule, cfPrompt, NULL, NULL);
if (err != noErr) {
cerr << "You don't have the right to edit the security database (try to run cmake with sudo): " << err << endl;
exit(1);
} else {
qInfo() << "Created or updated rule" << rule << "for right entry" << action.name << "policy" << action.policy << "; domain=" << domain;
}
}
}
}
@@ -0,0 +1,262 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Radek Novacek <rnovacek@redhat.com>
SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "Polkit1Backend.h"
#include "kauthdebug.h"
#include <KWaylandExtras>
#include <KWindowSystem>
#include <QCoreApplication>
#include <QTimer>
#include <qplugin.h>
#include <QGuiApplication>
#include <QWindow>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <PolkitQt1/Subject>
#include <polkitqt1-version.h>
constexpr QLatin1String c_kdeAgentService{"org.kde.polkit-kde-authentication-agent-1"};
constexpr QLatin1String c_kdeAgentPath{"/org/kde/Polkit1AuthAgent"};
constexpr QLatin1String c_kdeAgentInterface{"org.kde.Polkit1AuthAgent"};
namespace KAuth
{
Polkit1Backend::Polkit1Backend()
: AuthBackend()
{
setCapabilities(AuthorizeFromHelperCapability | PreAuthActionCapability);
// Setup useful signals
connect(PolkitQt1::Authority::instance(), &PolkitQt1::Authority::configChanged, this, &KAuth::Polkit1Backend::checkForResultChanged);
connect(PolkitQt1::Authority::instance(), &PolkitQt1::Authority::consoleKitDBChanged, this, &KAuth::Polkit1Backend::checkForResultChanged);
}
Polkit1Backend::~Polkit1Backend()
{
}
void Polkit1Backend::preAuthAction(const QString &action, QWindow *parentWindow)
{
// If a parent was not specified, skip this
if (!parentWindow) {
qCDebug(KAUTH) << "Parent widget does not exist, skipping";
return;
}
// Check if we actually are entitled to use GUI capabilities
if (!qGuiApp) {
qCDebug(KAUTH) << "Not streaming parent as we are on a TTY application";
return;
}
// Are we running our KDE auth agent?
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String("org.kde.polkit-kde-authentication-agent-1"))) {
if (KWindowSystem::isPlatformWayland()) {
KWaylandExtras::exportWindow(parentWindow);
connect(
KWaylandExtras::self(),
&KWaylandExtras::windowExported,
this,
[this, action, parentWindow](QWindow *window, const QString &handle) {
if (window == parentWindow) {
sendWindowHandle(action, handle);
}
},
Qt::SingleShotConnection);
// Generate and send an XDG Activation token.
sendActivationToken(action, parentWindow);
} else {
// Retrieve the dialog root window Id
const qulonglong wId = parentWindow->winId();
sendWindowHandle(action, QString::number(wId));
// Call the old method for compatibility.
QDBusMessage methodCall = QDBusMessage::createMethodCall(c_kdeAgentService, c_kdeAgentPath, c_kdeAgentInterface, QLatin1String("setWIdForAction"));
methodCall << action;
methodCall << wId;
// Legacy call has to be blocking, old agent doesn't handle it coming in delayed.
const auto reply = QDBusConnection::sessionBus().call(methodCall);
if (reply.type() != QDBusMessage::ReplyMessage) {
qWarning() << "Failed to set window id" << wId << "for" << action << reply.errorMessage();
}
}
} else {
qCDebug(KAUTH) << "KDE polkit agent appears too old or not registered on the bus";
}
}
void Polkit1Backend::sendWindowHandle(const QString &action, const QString &handle)
{
// Send it over the bus to our agent
QDBusMessage methodCall = QDBusMessage::createMethodCall(c_kdeAgentService, c_kdeAgentPath, c_kdeAgentInterface, QLatin1String("setWindowHandleForAction"));
methodCall << action;
methodCall << handle;
const auto reply = QDBusConnection::sessionBus().asyncCall(methodCall);
auto *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, handle, action] {
watcher->deleteLater();
QDBusPendingReply<> reply = *watcher;
if (reply.isError()) {
qCWarning(KAUTH) << "Failed to set window handle" << handle << "for" << action << reply.error().message();
}
});
}
void Polkit1Backend::sendActivationToken(const QString &action, QWindow *window)
{
const auto requestedSerial = KWaylandExtras::lastInputSerial(window);
connect(
KWaylandExtras::self(),
&KWaylandExtras::xdgActivationTokenArrived,
this,
[this, requestedSerial, action](quint32 serial, const QString &token) {
if (serial != requestedSerial || token.isEmpty()) {
return;
}
QDBusMessage methodCall =
QDBusMessage::createMethodCall(c_kdeAgentService, c_kdeAgentPath, c_kdeAgentInterface, QLatin1String("setActivationTokenForAction"));
methodCall << action;
methodCall << token;
const auto reply = QDBusConnection::sessionBus().asyncCall(methodCall);
auto *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, token, action] {
watcher->deleteLater();
QDBusPendingReply<> reply = *watcher;
if (reply.isError()) {
qCWarning(KAUTH) << "Failed to set activation token" << token << "for" << action << reply.error().message();
}
});
},
Qt::SingleShotConnection);
KWaylandExtras::requestXdgActivationToken(window, requestedSerial, {});
}
Action::AuthStatus Polkit1Backend::authorizeAction(const QString &action)
{
Q_UNUSED(action)
// Always return Yes here, we'll authorize inside isCallerAuthorized
return Action::AuthorizedStatus;
}
void Polkit1Backend::setupAction(const QString &action)
{
m_cachedResults[action] = actionStatus(action);
}
Action::AuthStatus Polkit1Backend::actionStatus(const QString &action)
{
PolkitQt1::SystemBusNameSubject subject(QString::fromUtf8(callerID()));
auto authority = PolkitQt1::Authority::instance();
PolkitQt1::Authority::Result r = authority->checkAuthorizationSync(action, subject, PolkitQt1::Authority::None);
if (authority->hasError()) {
qCDebug(KAUTH) << "Encountered error while checking action status, error code:" << authority->lastError() << authority->errorDetails();
authority->clearError();
return Action::InvalidStatus;
}
switch (r) {
case PolkitQt1::Authority::Yes:
return Action::AuthorizedStatus;
case PolkitQt1::Authority::No:
case PolkitQt1::Authority::Unknown:
return Action::DeniedStatus;
default:
return Action::AuthRequiredStatus;
}
}
QByteArray Polkit1Backend::callerID() const
{
return QDBusConnection::systemBus().baseService().toUtf8();
}
bool Polkit1Backend::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
{
PolkitQt1::SystemBusNameSubject subject(QString::fromUtf8(callerID));
PolkitQt1::Authority *authority = PolkitQt1::Authority::instance();
QMap<QString, QString> polkit1Details;
for (auto it = details.cbegin(); it != details.cend(); ++it) {
polkit1Details.insert(it.key(), it.value().toString());
}
PolkitQt1::Authority::Result result;
QEventLoop e;
connect(authority, &PolkitQt1::Authority::checkAuthorizationFinished, &e, [&result, &e](PolkitQt1::Authority::Result _result) {
result = _result;
e.quit();
});
#if POLKITQT1_IS_VERSION(0, 113, 0)
authority->checkAuthorizationWithDetails(action, subject, PolkitQt1::Authority::AllowUserInteraction, polkit1Details);
#else
authority->checkAuthorization(action, subject, PolkitQt1::Authority::AllowUserInteraction);
#endif
e.exec();
if (authority->hasError()) {
qCDebug(KAUTH) << "Encountered error while checking authorization, error code:" << authority->lastError() << authority->errorDetails();
authority->clearError();
}
switch (result) {
case PolkitQt1::Authority::Yes:
return true;
default:
return false;
}
}
void Polkit1Backend::checkForResultChanged()
{
for (auto it = m_cachedResults.begin(); it != m_cachedResults.end(); ++it) {
const QString action = it.key();
if (it.value() != actionStatus(action)) {
*it = actionStatus(action);
Q_EMIT actionStatusChanged(action, *it);
}
}
}
QVariantMap Polkit1Backend::backendDetails(const DetailsMap &details)
{
QVariantMap backendDetails;
for (auto it = details.cbegin(); it != details.cend(); ++it) {
switch (it.key()) {
case Action::AuthDetail::DetailMessage:
backendDetails.insert(QStringLiteral("polkit.message"), it.value());
break;
case Action::AuthDetail::DetailOther:
default:
backendDetails.insert(QStringLiteral("other_details"), it.value());
break;
}
}
return backendDetails;
}
} // namespace Auth
#include "Polkit1Backend.moc"
@@ -0,0 +1,56 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Radek Novacek <rnovacek@redhat.com>
SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef POLKIT1BACKEND_H
#define POLKIT1BACKEND_H
#include "AuthBackend.h"
#include <QEventLoop>
#include <QHash>
#include <QStringList>
#include <PolkitQt1/Authority>
#include <memory>
class QByteArray;
namespace KAuth
{
class Polkit1Backend : public AuthBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.Polkit1Backend")
Q_INTERFACES(KAuth::AuthBackend)
public:
Polkit1Backend();
~Polkit1Backend() override;
void setupAction(const QString &) override;
void preAuthAction(const QString &action, QWindow *parent) override;
Action::AuthStatus authorizeAction(const QString &) override;
Action::AuthStatus actionStatus(const QString &) override;
QByteArray callerID() const override;
bool isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details) override;
QVariantMap backendDetails(const DetailsMap &details) override;
private Q_SLOTS:
void checkForResultChanged();
private:
void sendWindowHandle(const QString &action, const QString &handle);
void sendActivationToken(const QString &action, QWindow *window);
QHash<QString, Action::AuthStatus> m_cachedResults;
};
} // namespace Auth
#endif
@@ -0,0 +1,84 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <policy-gen/policy-gen.h>
#include <QDebug>
#include <QTextStream>
#include <cstdio>
const char header[] =
""
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<!DOCTYPE policyconfig PUBLIC\n"
"\"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN\"\n"
"\"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd\">\n"
"<policyconfig>\n";
const char policy_tag[] =
""
" <defaults>\n"
" <allow_inactive>%1</allow_inactive>\n"
" <allow_active>%2</allow_active>\n"
" </defaults>\n";
const char dent[] = " ";
void output(const QList<Action> &actions, const QMap<QString, QString> &domain)
{
QTextStream out(stdout);
out << header;
if (domain.contains(QLatin1String("vendor"))) {
out << "<vendor>" << domain[QStringLiteral("vendor")].toHtmlEscaped() << "</vendor>\n";
}
if (domain.contains(QLatin1String("vendorurl"))) {
out << "<vendor_url>" << domain[QStringLiteral("vendorurl")] << "</vendor_url>\n";
}
if (domain.contains(QLatin1String("icon"))) {
out << "<icon_name>" << domain[QStringLiteral("icon")] << "</icon_name>\n";
}
for (const Action &action : actions) {
out << dent << "<action id=\"" << action.name << "\" >\n";
// Not a typo, messages and descriptions are actually inverted
for (auto i = action.messages.cbegin(); i != action.messages.cend(); ++i) {
out << dent << dent << "<description";
if (i.key() != QLatin1String("en")) {
out << " xml:lang=\"" << i.key() << '"';
}
out << '>' << i.value().toHtmlEscaped() << "</description>\n";
}
for (auto i = action.descriptions.cbegin(); i != action.descriptions.cend(); ++i) {
out << dent << dent << "<message";
if (i.key() != QLatin1String("en")) {
out << " xml:lang=\"" << i.key() << '"';
}
out << '>' << i.value().toHtmlEscaped() << "</message>\n";
}
QString policy = action.policy;
QString policyInactive = action.policyInactive.isEmpty() ? QLatin1String("no") : action.policyInactive;
if (!action.persistence.isEmpty() && policy != QLatin1String("yes") && policy != QLatin1String("no")) {
policy += QLatin1String("_keep");
}
if (!action.persistence.isEmpty() && policyInactive != QLatin1String("yes") && policyInactive != QLatin1String("no")) {
policyInactive += QLatin1String("_keep");
}
out << QString(QLatin1String(policy_tag)).arg(policyInactive, policy);
out << dent << "</action>\n";
}
out << "</policyconfig>\n";
}