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,60 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2010 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "AuthBackend.h"
namespace KAuth
{
class AuthBackend::Private
{
public:
Private()
{
}
virtual ~Private()
{
}
Capabilities capabilities;
};
AuthBackend::AuthBackend()
: QObject(nullptr)
, d(new Private)
{
}
AuthBackend::~AuthBackend()
{
delete d;
}
AuthBackend::Capabilities AuthBackend::capabilities() const
{
return d->capabilities;
}
void AuthBackend::setCapabilities(AuthBackend::Capabilities capabilities)
{
d->capabilities = capabilities;
}
void AuthBackend::preAuthAction(const QString &action, QWindow *parent)
{
Q_UNUSED(action)
Q_UNUSED(parent)
}
QVariantMap AuthBackend::backendDetails(const DetailsMap &details)
{
Q_UNUSED(details);
return QVariantMap();
}
} // namespace KAuth
#include "moc_AuthBackend.cpp"
@@ -0,0 +1,62 @@
/*
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
*/
#ifndef KAUTH_AUTH_BACKEND_H
#define KAUTH_AUTH_BACKEND_H
#include <QObject>
#include "action.h"
namespace KAuth
{
typedef Action::DetailsMap DetailsMap;
class AuthBackend : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(AuthBackend)
public:
enum Capability {
NoCapability = 0,
AuthorizeFromClientCapability = 1,
AuthorizeFromHelperCapability = 2,
PreAuthActionCapability = 4,
};
Q_DECLARE_FLAGS(Capabilities, Capability)
AuthBackend();
~AuthBackend() override;
virtual void setupAction(const QString &action) = 0;
virtual void preAuthAction(const QString &action, QWindow *parent);
virtual Action::AuthStatus authorizeAction(const QString &action) = 0;
virtual Action::AuthStatus actionStatus(const QString &action) = 0;
virtual QByteArray callerID() const = 0;
virtual bool isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details) = 0;
virtual QVariantMap backendDetails(const DetailsMap &details);
Capabilities capabilities() const;
protected:
void setCapabilities(Capabilities capabilities);
Q_SIGNALS:
void actionStatusChanged(const QString &action, KAuth::Action::AuthStatus status);
private:
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(AuthBackend::Capabilities)
} // namespace Auth
Q_DECLARE_INTERFACE(KAuth::AuthBackend, "org.kde.kf6auth.AuthBackend/0.1")
#endif
@@ -0,0 +1,3 @@
#cmakedefine01 KAUTH_COMPILING_FAKE_BACKEND
#define KAUTH_BACKEND_PLUGIN_DIR "${KDE_INSTALL_FULL_PLUGINDIR}/${KAUTH_BACKEND_PLUGIN_DIR}"
#define KAUTH_HELPER_PLUGIN_DIR "${KDE_INSTALL_FULL_PLUGINDIR}/${KAUTH_HELPER_PLUGIN_DIR}"
@@ -0,0 +1,118 @@
/*
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "BackendsManager.h"
#include "BackendsConfig.h"
// Include fake backends
#include "backends/fake/FakeBackend.h"
#include "backends/fakehelper/FakeHelperProxy.h"
#include "kauthdebug.h"
#include <QCoreApplication>
#include <QDir>
#include <QPluginLoader>
namespace KAuth
{
AuthBackend *BackendsManager::auth = nullptr;
HelperProxy *BackendsManager::helper = nullptr;
BackendsManager::BackendsManager()
{
}
QList<QObject *> BackendsManager::retrieveInstancesIn(const QString &path)
{
QList<QObject *> retlist;
QDir pluginPath(path);
if (!pluginPath.exists() || path.isEmpty()) {
return retlist;
}
const QFileInfoList entryList = pluginPath.entryInfoList(QDir::NoDotAndDotDot | QDir::Files);
for (const QFileInfo &fi : entryList) {
const QString filePath = fi.filePath(); // file name with path
// QString fileName = fi.fileName(); // just file name
if (!QLibrary::isLibrary(filePath)) {
continue;
}
QPluginLoader loader(filePath);
QObject *instance = loader.instance();
if (instance) {
retlist.append(instance);
} else {
qCWarning(KAUTH) << "Couldn't load" << filePath << "error:" << loader.errorString();
}
}
return retlist;
}
void BackendsManager::init()
{
// Backend plugin
const QList<QObject *> backends = retrieveInstancesIn(QFile::decodeName(KAUTH_BACKEND_PLUGIN_DIR));
for (QObject *instance : backends) {
auth = qobject_cast<KAuth::AuthBackend *>(instance);
if (auth) {
break;
}
}
// Helper plugin
const QList<QObject *> helpers = retrieveInstancesIn(QFile::decodeName(KAUTH_HELPER_PLUGIN_DIR));
for (QObject *instance : helpers) {
helper = qobject_cast<KAuth::HelperProxy *>(instance);
if (helper) {
break;
}
}
if (!auth) {
// Load the fake auth backend then
auth = new FakeBackend;
#if !KAUTH_COMPILING_FAKE_BACKEND
// Spit a fat warning
qCWarning(KAUTH) << "WARNING: KAuth was compiled with a working backend, but was unable to load it! Check your installation!";
#endif
}
if (!helper) {
// Load the fake helper backend then
helper = new FakeHelperProxy;
#if !KAUTH_COMPILING_FAKE_BACKEND
// Spit a fat warning
qCWarning(KAUTH) << "WARNING: KAuth was compiled with a working helper backend, but was unable to load it! "
"Check your installation!";
#endif
}
}
AuthBackend *BackendsManager::authBackend()
{
if (!auth) {
init();
}
return auth;
}
HelperProxy *BackendsManager::helperProxy()
{
if (!helper) {
init();
}
return helper;
}
} // namespace Auth
@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KAUTH_BACKENDS_MANAGER_H
#define KAUTH_BACKENDS_MANAGER_H
#include "AuthBackend.h"
#include "HelperProxy.h"
#include "kauthcore_export.h"
namespace KAuth
{
class KAUTHCORE_EXPORT BackendsManager
{
private:
static AuthBackend *auth;
static HelperProxy *helper;
KAUTHCORE_NO_EXPORT BackendsManager();
public:
static AuthBackend *authBackend();
static HelperProxy *helperProxy();
private:
KAUTHCORE_NO_EXPORT static void init();
KAUTHCORE_NO_EXPORT static QList<QObject *> retrieveInstancesIn(const QString &path);
};
} // namespace Auth
#endif
@@ -0,0 +1,169 @@
# This file handles all the logic for compiling KAuth's backends
include(ConfigureChecks.cmake)
# Configure a small file to tell BackendsManager what to use
configure_file(BackendsConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/BackendsConfig.h)
set(KAUTH_INSTALL_INCLUDEDIR "${KDE_INSTALL_INCLUDEDIR_KF}/KAuth")
set(KAUTHCORE_INSTALL_INCLUDEDIR "${KDE_INSTALL_INCLUDEDIR_KF}/KAuthCore")
ecm_create_qm_loader(KAuth_QM_LOADER kauth6_qt)
set(kauthdebug_SRCS)
ecm_qt_declare_logging_category(kauthdebug_SRCS
HEADER kauthdebug.h
IDENTIFIER KAUTH
CATEGORY_NAME kf.auth
OLD_CATEGORY_NAMES kf5.kauth
DESCRIPTION "KAuth"
EXPORT KAUTH
)
if(NOT KAUTH_BUILD_CODEGENERATOR_ONLY)
add_library(KF6AuthCore)
add_library(KF6::AuthCore ALIAS KF6AuthCore)
set_target_properties(KF6AuthCore PROPERTIES
VERSION ${KAUTH_VERSION}
SOVERSION ${KAUTH_SOVERSION}
EXPORT_NAME AuthCore
)
target_sources(KF6AuthCore PRIVATE
action.cpp
actionreply.cpp
executejob.cpp
AuthBackend.cpp
BackendsManager.cpp
HelperProxy.cpp
helpersupport.cpp
${kauthdebug_SRCS}
backends/fake/FakeBackend.cpp
backends/fakehelper/FakeHelperProxy.cpp
${KAuth_QM_LOADER}
)
ecm_generate_export_header(KF6AuthCore
BASE_NAME KAuthCore
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KAuth
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_link_libraries(KF6AuthCore PUBLIC Qt6::Core Qt6::Gui KF6::CoreAddons) # for KJob
target_include_directories(KF6AuthCore INTERFACE
"$<INSTALL_INTERFACE:${KAUTHCORE_INSTALL_INCLUDEDIR};${KAUTH_INSTALL_INCLUDEDIR}>"
)
install(TARGETS KF6AuthCore EXPORT KF6AuthTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
ecm_generate_headers(KAuthCore_CamelCase_HEADERS
HEADER_NAMES
Action
ActionReply
ExecuteJob
HelperSupport
PREFIX KAuth
REQUIRED_HEADERS KAuthCore_HEADERS
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kauthcore_export.h
${KAuthCore_HEADERS}
DESTINATION ${KAUTHCORE_INSTALL_INCLUDEDIR}/kauth # C++ namespace
COMPONENT Devel
)
install(FILES
${KAuthCore_CamelCase_HEADERS}
DESTINATION ${KAUTHCORE_INSTALL_INCLUDEDIR}/KAuth # C++ namespace
COMPONENT Devel
)
endif()
# KAuth policy generator executable
# Compile only if fake backend has not been selected
if (NOT "${KAUTH_BACKEND_NAME}" STREQUAL "FAKE")
# KAUTH_POLICY_GEN_SRCS has been generated from ConfigureChecks.cmake
add_executable(kauth-policy-gen ${KAUTH_POLICY_GEN_SRCS})
add_executable(KF6::kauth-policy-gen ALIAS kauth-policy-gen)
if (APPLE)
include(ECMMarkNonGuiExecutable)
ecm_mark_nongui_executable(kauth-policy-gen)
endif ()
# KAUTH_POLICY_GEN_LIBRARIES has been generated from ConfigureChecks.cmake
target_link_libraries( kauth-policy-gen PRIVATE ${KAUTH_POLICY_GEN_LIBRARIES} )
install( TARGETS kauth-policy-gen EXPORT KF6AuthToolsTargets DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
endif ()
# KAuth backend plugin
if (NOT "${KAUTH_BACKEND_NAME}" STREQUAL "FAKE" AND NOT KAUTH_BUILD_CODEGENERATOR_ONLY)
# KAUTH_BACKEND_SRCS has been set from ConfigureChecks.cmake
# KAuth::AuthBackend is not exported
add_library(kauth_backend_plugin MODULE ${KAUTH_BACKEND_SRCS} AuthBackend.cpp ${kauthdebug_SRCS})
target_link_libraries(kauth_backend_plugin PRIVATE ${KAUTH_BACKEND_LIBS})
set_target_properties(kauth_backend_plugin PROPERTIES PREFIX "")
install(TARGETS kauth_backend_plugin
LIBRARY DESTINATION "${KDE_INSTALL_PLUGINDIR}/${KAUTH_BACKEND_PLUGIN_DIR}"
ARCHIVE DESTINATION "${KDE_INSTALL_PLUGINDIR}/${KAUTH_BACKEND_PLUGIN_DIR}"
RUNTIME DESTINATION "${KDE_INSTALL_PLUGINDIR}/${KAUTH_BACKEND_PLUGIN_DIR}"
)
endif ()
# KAuth helper plugin
if (NOT "${KAUTH_HELPER_BACKEND_NAME}" STREQUAL "FAKE" AND NOT KAUTH_BUILD_CODEGENERATOR_ONLY)
# KAuth::HelperProxy is not exported
add_library(kauth_helper_plugin MODULE ${KAUTH_HELPER_BACKEND_SRCS} HelperProxy.cpp ${kauthdebug_SRCS})
target_link_libraries(kauth_helper_plugin PRIVATE ${KAUTH_HELPER_BACKEND_LIBS})
set_target_properties(kauth_helper_plugin PROPERTIES PREFIX "")
install(TARGETS kauth_helper_plugin
LIBRARY DESTINATION "${KDE_INSTALL_PLUGINDIR}/${KAUTH_HELPER_PLUGIN_DIR}"
ARCHIVE DESTINATION "${KDE_INSTALL_PLUGINDIR}/${KAUTH_HELPER_PLUGIN_DIR}"
RUNTIME DESTINATION "${KDE_INSTALL_PLUGINDIR}/${KAUTH_HELPER_PLUGIN_DIR}"
)
endif ()
ecm_qt_install_logging_categories(
EXPORT KAUTH
FILE kauth.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
if(BUILD_QCH)
ecm_add_qch(
KF6Auth_QCH
NAME KAuth
BASE_NAME KF6Auth
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KAuthCore_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
KF6CoreAddons_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
EXAMPLE_DIRS
${CMAKE_SOURCE_DIR}/examples
BLANK_MACROS
KAUTHCORE_EXPORT
KAUTH_EXPORT
KAUTH_DEPRECATED
KAUTH_DEPRECATED_EXPORT
"KAUTHCORE_DEPRECATED_VERSION(x, y, t)"
"KAUTHCORE_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,227 @@
####### checks for kdecore/kauth ###############
set(KAUTH_BACKEND_NAME "" CACHE STRING "Specifies the KAuth backend to build. Current available options are
PolkitQt6-1, Fake, OSX. Not setting this variable will build the most
appropriate backend for your system")
# Case-insensitive
string(TOUPPER "${KAUTH_BACKEND_NAME}" KAUTH_BACKEND_NAME)
set(KAUTH_BACKEND ${KAUTH_BACKEND_NAME})
set(POLKITQT_MIN_VERSION 0.112.0 PARENT_SCOPE)
## Check if the user did not specify a backend to be built. If that is the case,
## we check what is the best backend to build on this system.
if(NOT KAUTH_BACKEND)
# Look for the most appropriate backend
message(STATUS "No backend for KAuth was explicitly specified: probing system to find the best one available")
if(APPLE)
set(KAUTH_BACKEND "OSX")
elseif(UNIX)
find_package(PolkitQt6-1 ${POLKITQT_MIN_VERSION})
if(PolkitQt6-1_FOUND)
set(KAUTH_BACKEND "POLKITQT6-1")
set_package_properties(PolkitQt6-1 PROPERTIES
URL "http://techbase.kde.org/Polkit-Qt-1"
DESCRIPTION "PolicyKit API for Qt"
TYPE RECOMMENDED
PURPOSE "Support for executing privileged actions in a controlled way (KAuth)"
)
find_package(KF6WindowSystem ${KF_DEP_VERSION} REQUIRED)
else()
set(KAUTH_BACKEND "FAKE")
endif()
else()
set(KAUTH_BACKEND "FAKE")
endif()
elseif(KAUTH_BACKEND AND NOT KAUTH_BUILD_CODEGENERATOR_ONLY)
# Check if the specified backend is valid. If it is not, we fall back to the FAKE one
if(NOT KAUTH_BACKEND STREQUAL "OSX"
AND NOT KAUTH_BACKEND STREQUAL "POLKITQT6-1"
AND NOT KAUTH_BACKEND STREQUAL "FAKE")
message("WARNING: The KAuth Backend ${KAUTH_BACKEND} you specified does not exist. Falling back to Fake backend")
set(KAUTH_BACKEND "FAKE")
endif()
# Check requirements for each backend. If not, fall back to the fake one
if(KAUTH_BACKEND STREQUAL "OSX" AND NOT APPLE)
message("WARNING: You chose the OSX KAuth backend but your system does not support it. Falling back to Fake backend")
set(KAUTH_BACKEND "FAKE")
endif()
if(KAUTH_BACKEND STREQUAL "POLKITQT6-1")
find_package(PolkitQt6-1 ${POLKITQT_MIN_VERSION})
set_package_properties(PolkitQt6-1 PROPERTIES
URL "http://techbase.kde.org/Polkit-Qt-1"
DESCRIPTION "PolicyKit API for Qt"
TYPE RECOMMENDED
PURPOSE "Support for executing privileged actions in a controlled way (KAuth). This is required to make KAuth work, and hence enable certain workspace functionalities"
)
if(NOT PolkitQt6-1_FOUND)
message("WARNING: You chose the PolkitQt6-1 KAuth backend but you don't have PolkitQt6-1 installed.
Falling back to Fake backend")
set(KAUTH_BACKEND "FAKE")
else()
find_package(KF6WindowSystem ${KF_DEP_VERSION} REQUIRED)
find_package(Qt6DBus ${REQUIRED_QT_VERSION} CONFIG REQUIRED)
endif()
endif()
endif()
set(KAUTH_BACKEND_NAME ${KAUTH_BACKEND} CACHE STRING "Specifies the KAuth backend to build. Current available options are
PolkitQt6-1, Fake, OSX. Not setting this variable will build the most
appropriate backend for your system" FORCE)
# Add the correct libraries depending on the backend, and eventually set the policy files install location
if(KAUTH_BACKEND_NAME STREQUAL "OSX")
find_library(CORE_FOUNDATION_LIBRARY CoreFoundation)
find_library(SECURITY_LIBRARY Security)
message(STATUS "Building OSX KAuth backend")
set(KAUTH_BACKEND_SRCS
backends/mac/AuthServicesBackend.cpp
)
set(KAUTH_BACKEND_LIBS ${SECURITY_LIBRARY} Qt6::Core KF6::AuthCore)
elseif(KAUTH_BACKEND_NAME STREQUAL "POLKITQT6-1")
message(STATUS "Building PolkitQt6-1 KAuth backend")
include_directories(SYSTEM ${POLKITQT-1_INCLUDE_DIR})
set(KAUTH_BACKEND_SRCS
backends/polkit-1/Polkit1Backend.cpp
)
set(KAUTH_BACKEND_LIBS ${POLKITQT-1_CORE_LIBRARY} Qt6::DBus Qt6::Gui KF6::AuthCore KF6::WindowSystem)
# POLKITQT-1_POLICY_FILES_INSTALL_DIR has an absolute pathname, fix that.
if(PolkitQt6-1_FOUND)
string(REPLACE ${POLKITQT-1_INSTALL_DIR}
${CMAKE_INSTALL_PREFIX} _KAUTH_POLICY_FILES_INSTALL_DIR
${POLKITQT-1_POLICY_FILES_INSTALL_DIR})
endif()
set(KAUTH_POLICY_FILES_INSTALL_DIR ${_KAUTH_POLICY_FILES_INSTALL_DIR} CACHE STRING
"Where policy files generated by KAuth will be installed" FORCE)
elseif(KAUTH_BACKEND_NAME STREQUAL "FAKE")
set(KAUTH_COMPILING_FAKE_BACKEND TRUE)
message(STATUS "Building Fake KAuth backend")
message("WARNING: No valid KAuth backends will be built. The library will not work properly unless compiled with
a working backend")
endif()
# KAuth policy generator executable source probing
set(KAUTH_POLICY_GEN_SRCS
policy-gen/policy-gen.cpp)
set(KAUTH_POLICY_GEN_LIBRARIES)
if(KAUTH_BACKEND_NAME STREQUAL "OSX")
set(KAUTH_POLICY_GEN_SRCS ${KAUTH_POLICY_GEN_SRCS}
backends/mac/kauth-policy-gen-mac.cpp)
set(KAUTH_POLICY_GEN_LIBRARIES ${KAUTH_POLICY_GEN_LIBRARIES} ${CORE_FOUNDATION_LIBRARY} ${SECURITY_LIBRARY} Qt6::Core)
elseif(KAUTH_BACKEND_NAME STREQUAL "POLKITQT6-1")
set(KAUTH_POLICY_GEN_SRCS ${KAUTH_POLICY_GEN_SRCS}
backends/polkit-1/kauth-policy-gen-polkit1.cpp)
set(KAUTH_POLICY_GEN_LIBRARIES ${KAUTH_POLICY_GEN_LIBRARIES}
Qt6::Core)
endif()
########################
# Helper backend probing
set(KAUTH_HELPER_BACKEND_NAME "" CACHE STRING "Specifies the KAuth helper backend to build. Current available options are
DBus, Fake. Not setting this variable will build the most appropriate backend for your system")
set(KAUTH_HELPER_BACKEND ${KAUTH_HELPER_BACKEND_NAME})
if(NOT KAUTH_HELPER_BACKEND)
# No checks needed, just set the dbus backend
set(KAUTH_HELPER_BACKEND "DBus")
string(TOUPPER ${KAUTH_HELPER_BACKEND} KAUTH_HELPER_BACKEND_UPPER)
set(KAUTH_HELPER_BACKEND ${KAUTH_HELPER_BACKEND_UPPER})
else()
# No checks needed here either
string(TOUPPER ${KAUTH_HELPER_BACKEND} KAUTH_HELPER_BACKEND_UPPER)
set(KAUTH_HELPER_BACKEND ${KAUTH_HELPER_BACKEND_UPPER})
endif()
set(KAUTH_HELPER_BACKEND_NAME ${KAUTH_HELPER_BACKEND} CACHE STRING "Specifies the KAuth helper backend to build. Current
available options are DBus, Fake. Not setting this variable will
build the most appropriate backend for your system" FORCE)
# Add the correct libraries/files depending on the backend
if(KAUTH_HELPER_BACKEND_NAME STREQUAL "DBUS")
qt_add_dbus_adaptor(kauth_dbus_adaptor_SRCS
backends/dbus/org.kde.kf6auth.xml
backends/dbus/DBusHelperProxy.h
KAuth::DBusHelperProxy)
set(KAUTH_HELPER_BACKEND_SRCS
backends/dbus/DBusHelperProxy.cpp
${kauth_dbus_adaptor_SRCS}
)
set(KAUTH_HELPER_BACKEND_LIBS Qt6::DBus KF6::AuthCore)
# Install some files as well
install(FILES backends/dbus/org.kde.kf6auth.conf
DESTINATION ${KDE_INSTALL_DBUSDIR}/system.d)
install(FILES backends/dbus/dbus_policy.stub
backends/dbus/dbus_service.stub
DESTINATION ${KDE_INSTALL_DATADIR_KF}/kauth COMPONENT Devel)
elseif(KAUTH_HELPER_BACKEND_NAME STREQUAL "FAKE")
message("WARNING: No valid KAuth helper backends will be built. The library will not work properly unless compiled with
a working backend")
endif()
# Set directories for plugins
if(NOT WIN32)
# ###
# WARNING Copied from KDE4Internal. Decide whether this should be fixed in
# CMake or in ECM:
# ###
# This macro implements some very special logic how to deal with the cache.
# By default the various install locations inherit their value from their "parent" variable
# so if you set CMAKE_INSTALL_PREFIX, then EXEC_INSTALL_PREFIX, PLUGIN_INSTALL_DIR will
# calculate their value by appending subdirs to CMAKE_INSTALL_PREFIX .
# This would work completely without using the cache.
# But if somebody wants e.g. a different EXEC_INSTALL_PREFIX this value has to go into
# the cache, otherwise it will be forgotten on the next cmake run.
# Once a variable is in the cache, it doesn't depend on its "parent" variables
# anymore and you can only change it by editing it directly.
# this macro helps in this regard, because as long as you don't set one of the
# variables explicitly to some location, it will always calculate its value from its
# parents. So modifying CMAKE_INSTALL_PREFIX later on will have the desired effect.
# But once you decide to set e.g. EXEC_INSTALL_PREFIX to some special location
# this will go into the cache and it will no longer depend on CMAKE_INSTALL_PREFIX.
#
# additionally if installing to the same location as kdelibs, the other install
# directories are reused from the installed kdelibs
macro(_SET_FANCY _var _value _comment)
set(predefinedvalue "${_value}")
if(NOT DEFINED ${_var})
set(${_var} ${predefinedvalue})
else()
set(${_var} "${${_var}}" CACHE PATH "${_comment}")
endif()
endmacro()
_set_fancy(KAUTH_HELPER_PLUGIN_DIR "kf6/kauth/helper" "Where KAuth's helper plugin will be installed")
_set_fancy(KAUTH_BACKEND_PLUGIN_DIR "kf6/kauth/backend" "Where KAuth's backend plugin will be installed")
#set(KAUTH_OTHER_PLUGIN_DIR "${QT_PLUGINS_DIR}/kauth/plugins")
else()
set(KAUTH_HELPER_PLUGIN_DIR "kf6/kauth/helper")
set(KAUTH_BACKEND_PLUGIN_DIR "kf6/kauth/backend")
endif()
## End
@@ -0,0 +1,18 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "HelperProxy.h"
namespace KAuth
{
HelperProxy::~HelperProxy()
{
}
} // namespace KAuth
#include "moc_HelperProxy.cpp"
@@ -0,0 +1,55 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KAUTH_HELPER_PROXY_H
#define KAUTH_HELPER_PROXY_H
#include <QMap>
#include <QObject>
#include <QString>
#include <QVariant>
#include "action.h"
#include "actionreply.h"
namespace KAuth
{
typedef Action::DetailsMap DetailsMap;
class HelperProxy : public QObject
{
Q_OBJECT
public:
~HelperProxy() override;
// Application-side methods
virtual void executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout) = 0;
virtual void stopAction(const QString &action, const QString &helperID) = 0;
// Helper-side methods
virtual bool initHelper(const QString &name) = 0;
virtual void setHelperResponder(QObject *o) = 0;
virtual bool hasToStopAction() = 0;
virtual void sendDebugMessage(int level, const char *msg) = 0;
virtual void sendProgressStep(int step) = 0;
virtual void sendProgressStepData(const QVariantMap &step) = 0;
// Attempts to resolve the UID of the unprivileged remote process.
virtual int callerUid() const = 0;
Q_SIGNALS:
void actionStarted(const QString &action);
void actionPerformed(const QString &action, const KAuth::ActionReply &reply);
void progressStep(const QString &action, int progress);
void progressStepData(const QString &action, const QVariantMap &data);
};
} // namespace KAuth
Q_DECLARE_INTERFACE(KAuth::HelperProxy, "org.kde.kf6auth.HelperProxy/0.1")
#endif
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Extract strings from all source files.
# EXTRACT_TR_STRINGS extracts strings with lupdate and convert them to .pot with
# lconvert.
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/kauth6_qt.pot
@@ -0,0 +1,7 @@
Todo list for libkauth:
- Add cmake macros
- Add vendor name and icon to actions.ini
- Add an option (either on build-time or at invocation time) to enable/disable the helper quit timeout.
This is useful for debugging.
- Check with others on k-c-d if it's needed to add strings with error descriptions.
- Stop requests must be action-specific instead of global
@@ -0,0 +1,196 @@
/*
SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "action.h"
#include <QPointer>
#include <QRegularExpression>
#include <QWindow>
#include <QtGlobal>
#include "executejob.h"
#include "BackendsManager.h"
namespace KAuth
{
class ActionData : public QSharedData
{
public:
ActionData()
: parent(nullptr)
, timeout(-1)
{
}
ActionData(const ActionData &other)
: QSharedData(other)
, name(other.name)
, helperId(other.helperId)
, details(other.details)
, args(other.args)
, parent(other.parent)
, timeout(other.timeout)
{
}
~ActionData()
{
}
QString name;
QString helperId;
Action::DetailsMap details;
QVariantMap args;
QPointer<QWindow> parent;
int timeout;
};
// Constructors
Action::Action()
: d(new ActionData())
{
}
Action::Action(const Action &action)
: d(action.d)
{
}
Action::Action(const QString &name)
: d(new ActionData())
{
setName(name);
BackendsManager::authBackend()->setupAction(d->name);
}
Action::Action(const QString &name, const DetailsMap &details)
: d(new ActionData())
{
setName(name);
setDetailsV2(details);
BackendsManager::authBackend()->setupAction(d->name);
}
Action::~Action()
{
}
// Operators
Action &Action::operator=(const Action &action)
{
if (this == &action) {
// Protect against self-assignment
return *this;
}
d = action.d;
return *this;
}
bool Action::operator==(const Action &action) const
{
return d->name == action.d->name;
}
bool Action::operator!=(const Action &action) const
{
return d->name != action.d->name;
}
// Accessors
QString Action::name() const
{
return d->name;
}
void Action::setName(const QString &name)
{
d->name = name;
}
// Accessors
int Action::timeout() const
{
return d->timeout;
}
void Action::setTimeout(int timeout)
{
d->timeout = timeout;
}
Action::DetailsMap Action::detailsV2() const
{
return d->details;
}
void Action::setDetailsV2(const DetailsMap &details)
{
d->details = details;
}
bool Action::isValid() const
{
return !d->name.isEmpty();
}
void Action::setArguments(const QVariantMap &arguments)
{
d->args = arguments;
}
void Action::addArgument(const QString &key, const QVariant &value)
{
d->args.insert(key, value);
}
QVariantMap Action::arguments() const
{
return d->args;
}
QString Action::helperId() const
{
return d->helperId;
}
// TODO: Check for helper id's syntax
void Action::setHelperId(const QString &id)
{
d->helperId = id;
}
void Action::setParentWindow(QWindow *parent)
{
d->parent = parent;
}
QWindow *Action::parentWindow() const
{
return d->parent.data();
}
Action::AuthStatus Action::status() const
{
if (!isValid()) {
return Action::InvalidStatus;
}
return BackendsManager::authBackend()->actionStatus(d->name);
}
ExecuteJob *Action::execute(ExecutionMode mode)
{
return new ExecuteJob(*this, mode, nullptr);
}
bool Action::hasHelper() const
{
return !d->helperId.isEmpty();
}
} // namespace Auth
#include "moc_action.cpp"
@@ -0,0 +1,416 @@
/*
SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KAUTH_ACTION_H
#define KAUTH_ACTION_H
#include "kauthcore_export.h"
#include <QHash>
#include <QSharedDataPointer>
#include <QString>
#include <QVariant>
#if __has_include(<chrono>)
#include <chrono>
#endif
class QWindow;
namespace KAuth
{
class ExecuteJob;
class ActionData;
/**
* @class Action action.h <KAuth/Action>
*
* @brief Class to access, authorize and execute actions.
*
* This is the main class of the KAuth API. It provides the interface to
* manipulate actions. Every action is identified by its name. Every instance
* of the Action class with the same name refers to the same action.
*
* Once you have an action object you can tell the helper to execute it
* (asking the user to authenticate if needed) with the execute() method.
* The simplest thing to do is to execute a single action synchronously
* blocking for the reply by calling KJob::exec() on the job object returned by
* execute().
*
* For asynchronous calls, use KAuth::ExecuteJob::start() instead.
* It sends the request
* to the helper and returns immediately. Before doing so you should however
* connect to at least the KJob::result(KJob *) signal to receive a slot call
* once the action is done executing.
*
* To use the execute() method you have to set the default helper's ID using
* the setHelperId() static method. Alternatively, you can specify the helperID using
* the overloaded version of the methods that takes it as a parameter.
*
* Each action object contains a QVariantMap object that is passed directly to the
* helper when the action is executed. You can access this map using the arguments()
* method. You can insert into it any kind of custom data you need to pass to the helper.
*
* @code
* void MyApp::runAction()
* {
* action = KAuth::Action("org.kde.myapp.action");
* KAuth::ExecuteJob *job = action.execute();
* connect(job, &KAuth::ExecuteJob::result, this, &MyApp::actionResult);
* job->start();
* }
*
* void MyApp::actionResult(KJob *kjob)
* {
* auto job = qobject_cast<KAuth::ExecuteJob *>(kjob);
* qDebug() << job.error() << job.data();
* }
* @endcode
*
* @since 4.4
*/
class KAUTHCORE_EXPORT Action
{
Q_GADGET
public:
/**
* The three values set by authorization methods
*/
enum AuthStatus {
DeniedStatus, ///< The authorization has been denied by the authorization backend
ErrorStatus, ///< An error occurred
InvalidStatus, ///< An invalid action cannot be authorized
AuthorizedStatus, ///< The authorization has been granted by the authorization backend
AuthRequiredStatus, ///< The user could obtain the authorization after authentication
UserCancelledStatus, ///< The user pressed Cancel the authentication dialog. Currently used only on the mac
};
Q_ENUM(AuthStatus)
enum ExecutionMode {
ExecuteMode,
AuthorizeOnlyMode,
};
Q_ENUM(ExecutionMode)
/**
* The backend specific details.
*/
enum class AuthDetail {
DetailOther = 0,
DetailMessage, ///< The message to show in authentication dialog.
};
Q_ENUM(AuthDetail)
/**
* Map of details.
*/
typedef QMap<AuthDetail, QVariant> DetailsMap;
/**
* @brief Default constructor
*
* This constructor sets the name to the empty string.
* Such an action is invalid and cannot be authorized nor executed, so
* you need to call setName() before you can use the object.
*/
Action();
/** Copy constructor */
Action(const Action &action);
/**
* This creates a new action object with this name
* @param name The name of the new action
*/
Action(const QString &name);
/**
* This creates a new action object with this name and details
* @param name The name of the new action
* @param details The details of the action
*
* @see setDetails
* @since 5.68
*/
Action(const QString &name, const DetailsMap &details);
/// Virtual destructor
~Action();
/// Assignment operator
Action &operator=(const Action &action);
/**
* @brief Comparison operator
*
* This comparison operator compares the <b>names</b> of two
* actions and returns whether they are the same. It does not
* care about the arguments stored in the actions. However,
* if two actions are invalid they'll match as equal, even
* if the invalid names are different.
*
* @returns true if the two actions are the same or both invalid
*/
bool operator==(const Action &action) const;
/**
* @brief Negated comparison operator
*
* Returns the negation of operator==
*
* @returns true if the two actions are different and not both invalid
*/
bool operator!=(const Action &action) const;
/**
* @brief Gets the action's name.
*
* This is the unique attribute that identifies
* an action object. Two action objects with the same
* name always refer to the same action.
*
* @return The action name
*/
QString name() const;
/**
* @brief Sets the action's name.
*
* It's not common to change the action name
* after its creation. Usually you set the name
* with the constructor (and you have to, because
* there's no default constructor)
*/
void setName(const QString &name);
/**
* @brief Gets the action's timeout.
*
* The timeout of the action in milliseconds
* -1 means the default D-Bus timeout (usually 25 seconds)
*
* @since 5.29
*
* @return The action timeouts
*/
int timeout() const;
/**
* @brief Sets the action's timeout.
*
* The timeout of the action in milliseconds
* -1 means the default D-Bus timeout (usually 25 seconds)
*
* @since 5.29
*
*/
void setTimeout(int timeout);
#if __has_include(<chrono>)
/**
* Convenience overload suporting C++ chrono types. May also be used with chrono literals.
* @since 5.93
*/
void setTimeout(std::chrono::milliseconds msec)
{
setTimeout(int(msec.count()));
}
#endif
/**
* @brief Sets the action's details
*
* You can use this function to provide the user more details
* (if the backend supports it) on the action being authorized in
* the authorization dialog
*
* @param details the details describing the action. For e.g, "DetailMessage" key can
* be used to give a customized authentication message.
*
* @since 5.68
*/
void setDetailsV2(const DetailsMap &details);
/**
* @brief Gets the action's details
*
* The details that will be shown in the authorization dialog, if the
* backend supports it.
*
* @return The action's details
* @since 5.68
*/
DetailsMap detailsV2() const;
/**
* @brief Returns if the object represents a valid action
*
* Action names have to respect a simple syntax.
* They have to be all in lowercase characters, separated
* by dots. Dots can't appear at the beginning and at the end of
* the name.
*
* In other words, the action name has to match this perl-like
* regular expression:
* @code
* /^[a-z]+(\.[a-z]+)*$/
* @endcode
*
* This method returns @c false if the action name doesn't match the
* valid syntax.
*
* If the backend supports it, this method also checks if the action is
* valid and recognized by the backend itself.
* @note This may spawn a nested event loop.
*
* Invalid actions cannot be authorized nor executed.
* The empty string is not a valid action name, so the default
* constructor returns an invalid action.
*/
bool isValid() const;
/**
* @brief Gets the default helper ID used for actions execution
*
* The helper ID is the string that uniquely identifies the helper in
* the system. It is the string passed to the KAUTH_HELPER_MAIN() macro
* in the helper source. Because one could have different helpers,
* you need to specify an helper ID for each execution, or set a default
* ID by calling setHelperId(). This method returns the current default
* value.
*
* @return The default helper ID.
*/
QString helperId() const;
/**
* @brief Sets the default helper ID used for actions execution
*
* This method sets the helper ID which contains the body of this action.
* If the string is non-empty, the corresponding helper will be fired and
* the action executed inside the helper. Otherwise, the action will be just
* authorized.
*
* @note To unset a previously set helper, just pass an empty string
*
* @param id The default helper ID.
*
* @see hasHelper
* @see helperId
*/
void setHelperId(const QString &id);
/**
* @brief Checks if the action has an helper
*
* This function can be used to check if an helper will be called upon the
* execution of an action. Such an helper can be set through setHelperId(). If
* this function returns false, upon execution the action will be just authorized.
*
* @since 4.5
*
* @return Whether the action has an helper or not
*
* @see setHelperId
*/
bool hasHelper() const;
/**
* @brief Sets the map object used to pass arguments to the helper.
*
* This method sets the variant map that the application
* can use to pass arbitrary data to the helper when executing the action.
*
* Only non-gui variants are supported.
*
* @param arguments The new arguments map
*/
void setArguments(const QVariantMap &arguments);
/**
* @brief Returns map object used to pass arguments to the helper.
*
* This method returns the variant map that the application
* can use to pass arbitrary data to the helper when executing the action.
*
* @return The arguments map that will be passed to the helper.
*/
QVariantMap arguments() const;
/**
* @brief Convenience method to add an argument.
*
* This method adds the pair @c key/value to the QVariantMap used to
* send custom data to the helper.
*
* Use this method if you don't want to create a new QVariantMap only to
* add a new entry.
*
* @param key The new entry's key
* @param value The value of the new entry
*/
void addArgument(const QString &key, const QVariant &value);
/**
* @brief Gets information about the authorization status of an action
*
* This methods query the authorization backend to know if the user can try
* to acquire the authorization for this action. If the result is Action::AuthRequired,
* the user can try to acquire the authorization by authenticating.
*
* It should not be needed to call this method directly, because the execution methods
* already take care of all the authorization stuff.
*
* @return @c Action::Denied if the user doesn't have the authorization to execute the action,
* @c Action::Authorized if the action can be executed,
* @c Action::AuthRequired if the user could acquire the authorization after authentication,
* @c Action::UserCancelled if the user cancels the authentication dialog. Not currently supported by the Polkit backend
*/
AuthStatus status() const;
/**
* @brief Get the job object used to execute the action
*
* @return The KAuth::ExecuteJob object to be used to run the action.
*/
ExecuteJob *execute(ExecutionMode mode = ExecuteMode);
/**
* @brief Sets a parent window for the authentication dialog
*
* This function is used for explicitly setting a parent window for an eventual authentication dialog required when
* authorization is triggered. Some backends, in fact, (like polkit-1) need to have a parent explicitly set for displaying
* the dialog correctly.
*
* @note If you are using KAuth through one of KDE's GUI components (KPushButton, KCModule...) you do not need and should not
* call this function, as it is already done by the component itself.
*
* @since 6.0
*
* @param parent A QWidget which will be used as the dialog's parent
*/
void setParentWindow(QWindow *parent);
/**
* @brief Returns the parent widget for the authentication dialog for this action
*
* @since 6.0
*
* @returns A QWindow which will is being used as the dialog's parent
*/
QWindow *parentWindow() const;
private:
QSharedDataPointer<ActionData> d;
};
} // namespace Auth
Q_DECLARE_TYPEINFO(KAuth::Action, Q_RELOCATABLE_TYPE);
#endif
@@ -0,0 +1,233 @@
/*
SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "actionreply.h"
#include <QDebug>
#include <QIODevice>
namespace KAuth
{
class ActionReplyData : public QSharedData
{
public:
ActionReplyData()
{
}
ActionReplyData(const ActionReplyData &other)
: QSharedData(other)
, data(other.data)
, errorCode(other.errorCode)
, errorDescription(other.errorDescription)
, type(other.type)
{
}
~ActionReplyData()
{
}
QVariantMap data; // User-defined data for success and helper error replies, empty for kauth errors
uint errorCode;
QString errorDescription;
ActionReply::Type type;
};
// Predefined replies
const ActionReply ActionReply::SuccessReply()
{
return ActionReply();
}
const ActionReply ActionReply::HelperErrorReply()
{
ActionReply reply(ActionReply::HelperErrorType);
reply.setError(-1);
return reply;
}
const ActionReply ActionReply::HelperErrorReply(int error)
{
ActionReply reply(ActionReply::HelperErrorType);
reply.setError(error);
return reply;
}
const ActionReply ActionReply::NoResponderReply()
{
return ActionReply(ActionReply::NoResponderError);
}
const ActionReply ActionReply::NoSuchActionReply()
{
return ActionReply(ActionReply::NoSuchActionError);
}
const ActionReply ActionReply::InvalidActionReply()
{
return ActionReply(ActionReply::InvalidActionError);
}
const ActionReply ActionReply::AuthorizationDeniedReply()
{
return ActionReply(ActionReply::AuthorizationDeniedError);
}
const ActionReply ActionReply::UserCancelledReply()
{
return ActionReply(ActionReply::UserCancelledError);
}
const ActionReply ActionReply::HelperBusyReply()
{
return ActionReply(ActionReply::HelperBusyError);
}
const ActionReply ActionReply::AlreadyStartedReply()
{
return ActionReply(ActionReply::AlreadyStartedError);
}
const ActionReply ActionReply::DBusErrorReply()
{
return ActionReply(ActionReply::DBusError);
}
// Constructors
ActionReply::ActionReply(const ActionReply &reply)
: d(reply.d)
{
}
ActionReply::ActionReply()
: d(new ActionReplyData())
{
d->errorCode = 0;
d->type = SuccessType;
}
ActionReply::ActionReply(ActionReply::Type type)
: d(new ActionReplyData())
{
d->errorCode = 0;
d->type = type;
}
ActionReply::ActionReply(int error)
: d(new ActionReplyData())
{
d->errorCode = error;
d->type = KAuthErrorType;
}
ActionReply::~ActionReply()
{
}
void ActionReply::setData(const QVariantMap &data)
{
d->data = data;
}
void ActionReply::addData(const QString &key, const QVariant &value)
{
d->data.insert(key, value);
}
QVariantMap ActionReply::data() const
{
return d->data;
}
ActionReply::Type ActionReply::type() const
{
return d->type;
}
void ActionReply::setType(ActionReply::Type type)
{
d->type = type;
}
bool ActionReply::succeeded() const
{
return d->type == SuccessType;
}
bool ActionReply::failed() const
{
return !succeeded();
}
ActionReply::Error ActionReply::errorCode() const
{
return (ActionReply::Error)d->errorCode;
}
void ActionReply::setErrorCode(Error errorCode)
{
d->errorCode = errorCode;
if (d->type != HelperErrorType) {
d->type = KAuthErrorType;
}
}
int ActionReply::error() const
{
return d->errorCode;
}
void ActionReply::setError(int error)
{
d->errorCode = error;
}
QString ActionReply::errorDescription() const
{
return d->errorDescription;
}
void ActionReply::setErrorDescription(const QString &error)
{
d->errorDescription = error;
}
QByteArray ActionReply::serialized() const
{
QByteArray data;
QDataStream s(&data, QIODevice::WriteOnly);
s << d->data << d->errorCode << static_cast<quint32>(d->type) << d->errorDescription;
return data;
}
ActionReply ActionReply::deserialize(const QByteArray &data)
{
ActionReply reply;
QByteArray a(data);
QDataStream s(&a, QIODevice::ReadOnly);
quint32 i;
s >> reply.d->data >> reply.d->errorCode >> i >> reply.d->errorDescription;
reply.d->type = static_cast<ActionReply::Type>(i);
return reply;
}
// Operators
ActionReply &ActionReply::operator=(const ActionReply &reply)
{
if (this == &reply) {
// Protect against self-assignment
return *this;
}
d = reply.d;
return *this;
}
bool ActionReply::operator==(const ActionReply &reply) const
{
return (d->type == reply.d->type && d->errorCode == reply.d->errorCode);
}
bool ActionReply::operator!=(const ActionReply &reply) const
{
return (d->type != reply.d->type || d->errorCode != reply.d->errorCode);
}
} // namespace Auth
@@ -0,0 +1,594 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KAUTH_ACTION_REPLY_H
#define KAUTH_ACTION_REPLY_H
#include "kauthcore_export.h"
#include <QDataStream>
#include <QMap>
#include <QSharedDataPointer>
#include <QString>
#include <QVariant>
/**
@namespace KAuth
@section kauth_intro Introduction
The KDE Authorization API allows developers to write desktop applications that
run high-privileged tasks in an easy, secure and cross-platform way.
Previously, if an application had to do administrative tasks, it had to be run
as root, using mechanisms such as sudo or graphical equivalents, or by setting
the executable's setuid bit. This approach has some drawbacks. For example, the
whole application code, including GUI handling and network communication, had
to be done as root. More code that runs as root means more possible security
holes.
The solution is the caller/helper pattern. With this pattern, the privileged
code is isolated in a small helper tool that runs as root. This tool includes
only the few lines of code that actually need to be run with privileges, not
the whole application logic. All the other parts of the application are run as
a normal user, and the helper tool is called when needed, using a secure
mechanism that ensures that the user is authorized to do so. This pattern is
not very easy to implement, because the developer has to deal with a lot of
details about how to authorize the user, how to call the helper with the right
privileges, how to exchange data with the helper, etc.. This is where the new
KDE Authorization API becomes useful. Thanks to this new library, every
developer can implement the caller/helper pattern to write application that
require high privileges, with a few lines of code in an easy, secure and
cross-platform way.
Not only: the library can also be used to lock down some actions in your
application without using a helper but just checking for authorization and
verifying if the user is allowed to perform it.
The KDE Authorization library uses different backends depending on the system
where it's built. As far as the user authorization is concerned, it currently
uses polkit-1 on linux and Authorization Services on Mac OSX, and a Windows
backend will eventually be written, too. At the communication layer, the
library uses D-Bus on every supported platform.
@section kauth_concepts Concepts
There are a few concepts to understand when using the library. Much of those
are carried from underlying APIs such as polkit-1, so if you know something
about them there shouldn't be problems.
An <i>action</i> is a single task that needs to be done by the application. You
refer to an action using an action identifier, which is a string in reverse
domain name syntax (to avoid duplicates). For example, if the date/time control
center module needs to change the date, it would need an action like
"org.kde.datatime.change". If your application has to perform more than one
privileged task, you should configure more than one action. This allows system
administrators to fine tune the policies that allow users to perform your
actions.
The <i>authorization</i> is the process that is executed to decide if a user
can perform an action or not. In order to execute the helper as root, the user
has to be authorized. For example, on linux, che policykit backend will look at
the policykit policy database to see what requirements the user has to meet in
order to execute the action you requested. The policy set for that action could
allow or deny that user, or could say the user has to authenticate in order to
gain the authorization.
The <i>authentication</i> is the process that allows the system to know that
the person is in front of the console is who he says to be. If an action can be
allowed or not depending on the user's identity, it has to be proved by
entering a password or any other identification data the system requires.
A typical session with the authorization API is like this:
- The user want to perform some privileged task
- The application asks the system if the user is authorized.
- The system asks the user to authenticate, if needed, and reply the application.
- The application uses some system-provided mechanism to execute the helper's
code as the root user. Previously, you had to set the setuid bit to do this,
but we have something cool called
"D-Bus activation" that doesn't require the setuid bit and is much more flexible.
- The helper code, immediately after starting, checks if the caller is
authorized to do what it asks. If not the helper immediately exits!
- If the caller is authorized, the helper executes the task and exits.
- The application receives data back from the helper.
All these steps are managed by the library. Following sections will focus on
how to write the helper to implement your actions and how to call the helper
from the application.
@section kauth_helper Writing the helper tool
The first thing you need to do before writing anything is to decide what
actions you need to implement. Every action needs to be identified by a string
in the reverse domain name syntax. This helps to avoid duplicates. An example
of action id is "org.kde.datetime.change" or "org.kde.ksysguard.killprocess".
Action names can only contain lowercase letters and dots (not as the first or
last char). You also need an identifier for your helper. An application using
the KDE auth api can implement and use more than one helper, implementing
different actions. An helper is uniquely identified in the system context with
a string. It, again, is in reverse domain name syntax to avoid duplicates. A
common approach is to call the helper like the common prefix of your action
names. For example, the Date/Time kcm module could use a helper called
"org.kde.datetime", to perform actions like "org.kde.datetime.changedate" and
"org.kde.datetime.changetime". This naming convention simplifies the
implementation of the helper.
From the code point of view, the helper is implemented as a QObject subclass.
Every action is implemented by a public slot. In the example/ directory in the
source code tree you find a complete example. Let's look at that. The
helper.h file declares the class that implements the helper. It looks like:
@snippet helper.cpp helper_declaration
The slot names are the last part of the action name, without the helper's ID if
it's a prefix, with all the dots replaced by underscores. In this case, the
helper ID is "org.kde.kf6auth.example", so those three slots implement the
actions "org.kde.kf6auth.example.read", "org.kde.kf6auth.example.write" and
"org.kde.kf6auth.example.longaction". The helper ID doesn't have to appear at
the beginning of the action name, but it's good practice. If you want to extend
MyHelper to implement also a different action like
"org.kde.datetime.changetime", since the helper ID doesn't match you'll have to
implement a slot called org_kde_datetime_changetime().
The slot's signature is fixed: the return type is ActionReply, a class that
allows you to return results, error codes and custom data to the application
when your action has finished to run.
Let's look at the read action implementation. Its purpose is to read files:
@snippet helper.cpp helper_read_action
First, the code creates a default reply object. The default constructor creates
a reply that reports success. Then it gets the filename parameter from the
argument QVariantMap, that has previously been set by the application, before
calling the helper. If it fails to open the file, it creates an ActionReply
object that notifies that some error has happened in the helper, then set the
error code to that returned by QFile and returns. If there is no error, it
reads the file. The contents are added to the reply.
Because this class will be compiled into a standalone executable, we need a
main() function and some code to initialize everything: you don't have to write
it. Instead, you use the KAUTH_HELPER_MAIN() macro that will take care of
everything. It's used like this:
@snippet helper.cpp helper_main
The first parameter is the string containing the helper identifier. Please note
that you need to use this same string in the application's code to tell the
library which helper to call, so please stay away from typos, because we don't
have any way to detect them. The second parameter is the name of the helper's
class. Your helper, if complex, can be composed of a lot of source files, but
the important thing is to include this macro in at least one of them.
To build the helper, KDE macros provide a function named
kauth_install_helper_files(). Use it in your cmake file like this:
@code
add_executable(<helper_target> your sources...)
target_link_libraries(<helper_target> your libraries...)
install(TARGETS <helper_target> DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
kauth_install_helper_files(<helper_target> <helper_id> <user>)
@endcode
As locale is not inherited, the auth helper will have the text codec explicitly set
to use UTF-8.
The first argument is the cmake target name for the helper executable, which
you have to build and install separately. Make sure to INSTALL THE HELPER IN
@c ${KAUTH_HELPER_INSTALL_DIR}, otherwise @c kauth_install_helper_files will not work. The
second argument is the helper id. Please be sure to don't misspell it, and to
not quote it. The user parameter is the user that the helper has to be run as.
It usually is root, but some actions could require less strict permissions, so
you should use the right user where possible (for example the user apache if
you have to mess with apache settings). Note that the target created by this
macro already links to libkauth and QtCore.
@section kauth_actions Action registration
To be able to authorize the actions, they have to be added to the policy
database. To do this in a cross-platform way, we provide a cmake macro. It
looks like:
@code
kauth_install_actions(<helper_id> <actions definition file>)
@endcode
The action definition file describes which actions are implemented by your code
and which default security options they should have. It is a common text file
in ini format, with one section for each action and some parameters. The
definition for the read action is:
@verbatim
[org.kde.kf6auth.example.read]
Name=Read action
Description=Read action description
Policy=auth_admin
Persistence=session
@endverbatim
The name parameter is a text describing the action for <i>who reads the
file</i>. The description parameter is the message shown to the user in the
authentication dialog. It should be a finite phrase. The policy attribute
specify the default rule that the user must satisfy to be authorized. Possible
values are:
- yes: the action should be always allowed
- no: the action should be always denied
- auth_self: the user should authenticate as itself
- auth_admin: the user should authenticate as an administrator user
The persistence attribute is optional. It says how long an authorization should
be retained for that action. The values could be:
- session: the authorization persists until the user logs-out
- always: the authorization will persist indefinitely
If this attribute is missing, the authorization will be queried every time.
@note Only the PolicyKit and polkit-1 backends use this attribute.
@warning With the polkit-1 backend, 'session' and 'always' have the same meaning.
They just make the authorization persists for a few minutes.
@section kauth_app Calling the helper from the application
Once the helper is ready, we need to call it from the main application.
In examples/client.cpp you can see how this is done. To create a reference to
an action, an object of type Action has to be created. Every Action object
refers to an action by its action id. Two objects with the same action id will
act on the same action. With an Action object, you can authorize and execute
the action. To execute an action you need to retrieve an ExecuteJob, which is
a standard KJob that you can run synchronously or asynchronously.
See the KJob documentation (from KCoreAddons) for more details.
The piece of code that calls the action of the previous example is:
@snippet client.cpp client_how_to_call_helper
First of all, it creates the action object specifying the action id. Then it
loads the filename (we want to read a forbidden file) into the arguments()
QVariantMap, which will be directly passed to the helper in the read() slot's
parameter. This example code uses a synchronous call to execute the action and
retrieve the reply. If the reply succeeded, the reply data is retrieved from
the returned QVariantMap object. Please note that you have
to explicitly set the helper ID to the action: this is done for added safety,
to prevent the caller from accidentally invoking a helper, and also because
KAuth actions may be used without a helper attached (the default).
Please note that if your application is calling the helper multiple times it
must do so from the same thread.
@section kauth_async Asynchronous calls, data reporting, and action termination
For a more advanced example, we look at the action
"org.kde.kf6auth.example.longaction" in the example helper. This is an action
that takes a long time to execute, so we need some features:
- The helper needs to regularly send data to the application, to inform about
the execution status.
- The application needs to be able to stop the action execution if the user
stops it or close the application.
The example code follows:
@snippet helper.cpp helper_longaction
In this example, the action is only waiting a "long" time using a loop, but we
can see some interesting line. The progress status is sent to the application
using the HelperSupport::progressStep(int) and
HelperSupport::progressStep(const QVariantMap &) methods.
When those methods are called, the HelperProxy associated with this action
will emit the HelperProxy::progressStep(const QString &, int) and
HelperProxy::progressStepData(const QString &, const QVariantMap &) signals,
respectively, reporting back the data to the application.
The method that takes an integer argument is the one used here.
Its meaning is application dependent, so you can use it as a sort of
percentage. If you want to report custom data back to the application, you
can use the other method that takes a QVariantMap object which is directly
passed to the app.
In this example code, the loop exits when the HelperSupport::isStopped()
returns true. This happens when the application calls the HelperProxy::stopAction()
method on the corresponding action object.
The stopAction() method, this way, asks the helper to
stop the action execution. It's up to the helper to obbey to this request, and
if it does so, it should return from the slot, _not_ exit.
@section kauth_other Other features
It doesn't happen very frequently that you code something that doesn't require
some debugging, and you'll need some tool, even a basic one, to debug your
helper code as well. For this reason, the KDE Authorization library provides a
message handler for the Qt debugging system. This means that every call to
qDebug() & co. will be reported to the application, and printed using the same
qt debugging system, with the same debug level. If, in the helper code, you
write something like:
@code
qDebug() << "I'm in the helper";
@endcode
You'll see something like this in the <i>application</i>'s output:
@verbatim
Debug message from the helper: I'm in the helper
@endverbatim
Remember that the debug level is preserved, so if you use qFatal() you won't
only abort the helper (which isn't suggested anyway), but also the application.
*/
namespace KAuth
{
class ActionReplyData;
/**
* @class ActionReply actionreply.h <KAuth/ActionReply>
*
* @brief Class that encapsulates a reply coming from the helper after executing
* an action
*
* Helper applications will return this to describe the result of the action.
*
* Callers should access the reply though the KAuth::ExecuteJob job.
*
* @since 4.4
*/
class KAUTHCORE_EXPORT ActionReply
{
public:
/**
* Enumeration of the different kinds of replies.
*/
enum Type {
KAuthErrorType, ///< An error reply generated by the library itself.
HelperErrorType, ///< An error reply generated by the helper.
SuccessType, ///< The action has been completed successfully
};
static const ActionReply SuccessReply(); ///< An empty successful reply. Same as using the default constructor
static const ActionReply HelperErrorReply(); ///< An empty reply with type() == HelperError and errorCode() == -1
static const ActionReply HelperErrorReply(int error); ///< An empty reply with type() == HelperError and error is set to the passed value
static const ActionReply NoResponderReply(); ///< errorCode() == NoResponder
static const ActionReply NoSuchActionReply(); ///< errorCode() == NoSuchAction
static const ActionReply InvalidActionReply(); ///< errorCode() == InvalidAction
static const ActionReply AuthorizationDeniedReply(); ///< errorCode() == AuthorizationDenied
static const ActionReply UserCancelledReply(); ///< errorCode() == UserCancelled
static const ActionReply HelperBusyReply(); ///< errorCode() == HelperBusy
static const ActionReply AlreadyStartedReply(); ///< errorCode() == AlreadyStartedError
static const ActionReply DBusErrorReply(); ///< errorCode() == DBusError
/**
* The enumeration of the possible values of errorCode() when type() is ActionReply::KAuthError
*/
enum Error {
NoError = 0, ///< No error.
NoResponderError, ///< The helper responder object hasn't been set. This shouldn't happen if you use the KAUTH_HELPER macro in the helper source
NoSuchActionError, ///< The action you tried to execute doesn't exist.
InvalidActionError, ///< You tried to execute an invalid action object
AuthorizationDeniedError, ///< You don't have the authorization to execute the action
UserCancelledError, ///< Action execution has been cancelled by the user
HelperBusyError, ///< The helper is busy executing another action (or group of actions). Try later
AlreadyStartedError, ///< The action was already started and is currently running
DBusError, ///< An error from D-Bus occurred
BackendError, ///< The underlying backend reported an error
};
/// Default constructor. Sets type() to Success and errorCode() to zero.
ActionReply();
/**
* @brief Constructor to directly set the type.
*
* This constructor directly sets the reply type. You shouldn't need to
* directly call this constructor, because you can use the more convenient
* predefined replies constants. You also shouldn't create a reply with
* the KAuthError type because it's reserved for errors coming from the
* library.
*
* @param type The type of the new reply
*/
ActionReply(Type type);
/**
* @brief Constructor that creates a KAuthError reply with a specified error code.
* Do not use outside the library.
*
* This constructor is for internal use only, since it creates a reply
* with KAuthError type, which is reserved for errors coming from the library.
*
* @param errorCode The error code of the new reply
*/
ActionReply(int errorCode);
/// Copy constructor
ActionReply(const ActionReply &reply);
/// Virtual destructor
virtual ~ActionReply();
/**
* @brief Sets the custom data to send back to the application
*
* In the helper's code you can use this function to set an QVariantMap
* with custom data that will be sent back to the application.
*
* @param data The new QVariantMap object.
*/
void setData(const QVariantMap &data);
/**
* @brief Returns the custom data coming from the helper.
*
* This method is used to get the object that contains the custom
* data coming from the helper. In the helper's code, you can set it
* using setData() or the convenience method addData().
*
* @return The data coming from (or that will be sent by) the helper
*/
QVariantMap data() const;
/**
* @brief Convenience method to add some data to the reply.
*
* This method adds the pair @c key/value to the QVariantMap used to
* report back custom data to the application.
*
* Use this method if you don't want to create a new QVariantMap only to
* add a new entry.
*
* @param key The new entry's key
* @param value The value of the new entry
*/
void addData(const QString &key, const QVariant &value);
/// Returns the reply's type
Type type() const;
/**
* @brief Sets the reply type
*
* Every time you create an action reply, you implicitly set a type.
* Default constructed replies or ActionReply::SuccessReply have
* type() == Success.
* ActionReply::HelperErrorReply has type() == HelperError.
* Predefined error replies have type() == KAuthError.
*
* This means you rarely need to change the type after the creation,
* but if you need to, don't set it to KAuthError, because it's reserved
* for errors coming from the library.
*
* @param type The new reply type
*/
void setType(Type type);
/// Returns true if type() == Success
bool succeeded() const;
/// Returns true if type() != Success
bool failed() const;
/**
* @brief Returns the error code of an error reply
*
* The error code returned is one of the values in the ActionReply::Error
* enumeration if type() == KAuthError, or is totally application-dependent if
* type() == HelperError. It also should be zero for successful replies.
*
* @return The reply error code
*/
int error() const;
/**
* @brief Returns the error code of an error reply
*
* The error code returned is one of the values in the ActionReply::Error
* enumeration if type() == KAuthError.
* Result is only valid if the type() == HelperError
*
* @return The reply error code
*/
Error errorCode() const;
/**
* @brief Sets the error code of an error reply
*
* If you're setting the error code in the helper because
* you need to return an error to the application, please make sure
* you already have set the type to HelperError, either by calling
* setType() or by creating the reply in the right way.
*
* If the type is Success when you call this method, it will become KAuthError
*
* @param error The new reply error code
*/
void setError(int error);
/**
* @brief Sets the error code of an error reply
*
* @see
* If you're setting the error code in the helper, use setError(int)
*
* If the type is Success when you call this method, it will become KAuthError
*
* @param errorCode The new reply error code
*/
void setErrorCode(Error errorCode);
/**
* @brief Gets a human-readble description of the error, if available
*
* Currently, replies of type KAuthError rarely report an error description.
* This situation could change in the future.
*
* By now, you can use this method for custom errors of type HelperError.
*
* @return The error human-readable description
*/
QString errorDescription() const;
/**
* @brief Sets a human-readble description of the error
*
* Call this method from the helper if you want to send back a description for
* a custom error. Note that this method doesn't affect the errorCode in any way
*
* @param error The new error description
*/
void setErrorDescription(const QString &error);
/**
* @brief Serialize the reply into a QByteArray.
*
* This is a convenience method used internally to sent the reply to a remote peer.
* To recreate the reply, use deserialize()
*
* @return A QByteArray representation of this reply
*/
QByteArray serialized() const;
/**
* @brief Deserialize a reply from a QByteArray
*
* This method returns a reply from a QByteArray obtained from
* the serialized() method.
*
* @param data A QByteArray obtained with serialized()
*/
static ActionReply deserialize(const QByteArray &data);
/// Assignment operator
ActionReply &operator=(const ActionReply &reply);
/**
* @brief Comparison operator
*
* This operator checks if the type and the error code of two replies are the same.
* It <b>doesn't</b> compare the data or the error descriptions, so be careful.
*
* The suggested use is to compare a reply against one of the predefined error replies:
* @code
* if(reply == ActionReply::HelperBusyReply) {
* // Do something...
* }
* @endcode
*
* Note that you can do it also by compare errorCode() with the relative enumeration value.
*/
bool operator==(const ActionReply &reply) const;
/**
* @brief Negated comparison operator
*
* See the operator==() for an important notice.
*/
bool operator!=(const ActionReply &reply) const;
private:
QSharedDataPointer<ActionReplyData> d;
};
} // namespace Auth
Q_DECLARE_METATYPE(KAuth::ActionReply)
#endif
@@ -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";
}
@@ -0,0 +1,241 @@
/*
SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "executejob.h"
#include "BackendsManager.h"
#include "kauthdebug.h"
#include <QCoreApplication>
#include <QEventLoop>
#include <QHash>
#include <QTimer>
namespace KAuth
{
class ExecuteJobPrivate
{
Q_DECLARE_TR_FUNCTIONS(KAuth::ExecuteJob)
public:
explicit ExecuteJobPrivate(ExecuteJob *parent)
: q(parent)
{
}
ExecuteJob *q;
Action action;
Action::ExecutionMode mode;
QVariantMap data;
void doExecuteAction();
void doAuthorizeAction();
void actionPerformedSlot(const QString &action, const ActionReply &reply);
void progressStepSlot(const QString &action, int i);
void progressStepSlot(const QString &action, const QVariantMap &data);
void statusChangedSlot(const QString &action, KAuth::Action::AuthStatus status);
};
ExecuteJob::ExecuteJob(const Action &action, Action::ExecutionMode mode, QObject *parent)
: KJob(parent)
, d(new ExecuteJobPrivate(this))
{
d->action = action;
d->mode = mode;
HelperProxy *helper = BackendsManager::helperProxy();
connect(helper, &KAuth::HelperProxy::actionPerformed, this, [this](const QString &action, const ActionReply &reply) {
d->actionPerformedSlot(action, reply);
});
connect(helper, &KAuth::HelperProxy::progressStep, this, [this](const QString &action, int i) {
d->progressStepSlot(action, i);
});
connect(helper, &KAuth::HelperProxy::progressStepData, this, [this](const QString &action, const QVariantMap &data) {
d->progressStepSlot(action, data);
});
connect(BackendsManager::authBackend(), &KAuth::AuthBackend::actionStatusChanged, this, [this](const QString &action, Action::AuthStatus status) {
d->statusChangedSlot(action, status);
});
}
ExecuteJob::~ExecuteJob() = default;
Action ExecuteJob::action() const
{
return d->action;
}
QVariantMap ExecuteJob::data() const
{
return d->data;
}
void ExecuteJob::start()
{
if (!d->action.isValid()) {
qCWarning(KAUTH) << "Tried to start an invalid action: " << d->action.name();
ActionReply reply(ActionReply::InvalidActionError);
reply.setErrorDescription(tr("Tried to start an invalid action"));
d->actionPerformedSlot(d->action.name(), reply);
return;
}
switch (d->mode) {
case Action::ExecuteMode:
QTimer::singleShot(0, this, [this]() {
d->doExecuteAction();
});
break;
case Action::AuthorizeOnlyMode:
QTimer::singleShot(0, this, [this]() {
d->doAuthorizeAction();
});
break;
default: {
ActionReply reply(ActionReply::InvalidActionError);
reply.setErrorDescription(tr("Unknown execution mode chosen"));
d->actionPerformedSlot(d->action.name(), reply);
break;
}
}
}
bool ExecuteJob::kill(KillVerbosity verbosity)
{
BackendsManager::helperProxy()->stopAction(d->action.name(), d->action.helperId());
KJob::kill(verbosity);
return true;
}
void ExecuteJobPrivate::doExecuteAction()
{
// If this action authorizes from the client, let's do it now
if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromClientCapability) {
if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::PreAuthActionCapability) {
BackendsManager::authBackend()->preAuthAction(action.name(), action.parentWindow());
}
Action::AuthStatus s = BackendsManager::authBackend()->authorizeAction(action.name());
if (s == Action::AuthorizedStatus) {
if (action.hasHelper()) {
BackendsManager::helperProxy()->executeAction(action.name(), action.helperId(), action.detailsV2(), action.arguments(), action.timeout());
} else {
// Done
actionPerformedSlot(action.name(), ActionReply::SuccessReply());
}
} else {
// Abort if authorization fails
switch (s) {
case Action::DeniedStatus:
actionPerformedSlot(action.name(), ActionReply::AuthorizationDeniedReply());
break;
case Action::InvalidStatus:
actionPerformedSlot(action.name(), ActionReply::InvalidActionReply());
break;
case Action::UserCancelledStatus:
actionPerformedSlot(action.name(), ActionReply::UserCancelledReply());
break;
default: {
ActionReply r(ActionReply::BackendError);
r.setErrorDescription(tr("Unknown status for the authentication procedure"));
actionPerformedSlot(action.name(), r);
break;
}
}
}
} else if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromHelperCapability) {
if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::PreAuthActionCapability) {
BackendsManager::authBackend()->preAuthAction(action.name(), action.parentWindow());
}
if (!action.hasHelper()) {
ActionReply r(ActionReply::InvalidActionReply());
r.setErrorDescription(tr("The current backend only allows helper authorization, but this action does not have a helper."));
actionPerformedSlot(action.name(), r);
return;
}
BackendsManager::helperProxy()->executeAction(action.name(), action.helperId(), action.detailsV2(), action.arguments(), action.timeout());
} else {
// There's something totally wrong here
ActionReply r(ActionReply::BackendError);
r.setErrorDescription(tr("The backend does not specify how to authorize"));
actionPerformedSlot(action.name(), r);
}
}
void ExecuteJobPrivate::doAuthorizeAction()
{
// Check the status first
Action::AuthStatus s = action.status();
if (s == Action::AuthRequiredStatus) {
// Let's check what to do
if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromClientCapability) {
// In this case we can actually try an authorization
if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::PreAuthActionCapability) {
BackendsManager::authBackend()->preAuthAction(action.name(), action.parentWindow());
}
s = BackendsManager::authBackend()->authorizeAction(action.name());
} else if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromHelperCapability) {
// In this case, just throw out success, as the auth will take place later
s = Action::AuthorizedStatus;
} else {
// This should never, never happen
ActionReply r(ActionReply::BackendError);
r.setErrorDescription(tr("The backend does not specify how to authorize"));
actionPerformedSlot(action.name(), r);
}
}
// Return based on the current status
if (s == Action::AuthorizedStatus) {
actionPerformedSlot(action.name(), ActionReply::SuccessReply());
} else {
actionPerformedSlot(action.name(), ActionReply::AuthorizationDeniedReply());
}
}
void ExecuteJobPrivate::actionPerformedSlot(const QString &taction, const ActionReply &reply)
{
if (taction == action.name()) {
if (reply.failed()) {
q->setError(reply.errorCode());
q->setErrorText(reply.errorDescription());
} else {
data = reply.data();
}
q->emitResult();
}
}
void ExecuteJobPrivate::progressStepSlot(const QString &taction, int i)
{
if (taction == action.name()) {
q->setPercent(i);
}
}
void ExecuteJobPrivate::progressStepSlot(const QString &taction, const QVariantMap &data)
{
if (taction == action.name()) {
Q_EMIT q->newData(data);
}
}
void ExecuteJobPrivate::statusChangedSlot(const QString &taction, Action::AuthStatus status)
{
if (taction == action.name()) {
Q_EMIT q->statusChanged(status);
}
}
} // namespace Auth
#include "moc_executejob.cpp"
@@ -0,0 +1,130 @@
/*
SPDX-FileCopyrightText: 2012 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KAUTH_EXECUTE_JOB_H
#define KAUTH_EXECUTE_JOB_H
#include <kjob.h>
#include "action.h"
#include "actionreply.h"
#include <memory>
namespace KAuth
{
class ExecuteJobPrivate;
/**
* @class ExecuteJob executejob.h <KAuth/ExecuteJob>
*
* @brief Job for executing an Action
*
* To run the action synchonously use KJob::exec() and check the return code for
* success.
*
* For longer tasks connect KJob::result(KJob*) and any other signals such as
* percent(KJob*, unsigned long) and newData(const QVariantMap &) then run start().
*
* To check for authentiation success or problems connect to
* statusChanged(KAuth::Action::AuthStatus status) signal.
*
* Use data() to get the return result of the action.
*
* @since 5.0
*/
class KAUTHCORE_EXPORT ExecuteJob : public KJob
{
Q_OBJECT
private:
KAUTHCORE_NO_EXPORT ExecuteJob(const KAuth::Action &action, KAuth::Action::ExecutionMode mode, QObject *parent);
friend class Action;
friend class ExecuteJobPrivate;
std::unique_ptr<ExecuteJobPrivate> const d;
Q_PRIVATE_SLOT(d, void doExecuteAction())
Q_PRIVATE_SLOT(d, void doAuthorizeAction())
Q_PRIVATE_SLOT(d, void actionPerformedSlot(const QString &action, const KAuth::ActionReply &reply))
Q_PRIVATE_SLOT(d, void progressStepSlot(const QString &action, int i))
Q_PRIVATE_SLOT(d, void statusChangedSlot(const QString &action, KAuth::Action::AuthStatus status))
public:
/// Virtual destructor
~ExecuteJob() override;
/**
* Starts the job asynchronously.
* @see KJob::result
* @see newData
* @see statusChanged
*/
void start() override;
/**
* @returns the action associated with this job
*/
Action action() const;
/**
* Use this to get the data set in the action by
* HelperSupport::progressStep(QVariant) or returned at the end of the
* action.
*
* This function is particularly useful once the job has completed. During
* execution, simply read the data in the newData() signal.
*
* @returns the data set by the helper
*
* @see ExecuteJob::newData
*/
QVariantMap data() const;
public Q_SLOTS:
/**
* Attempts to halt the execution of the action associated with this job.
* You should listen to the finished and result signals to work out whether
* halting was successful (as long running operations can also take time
* to shut down cleanly).
* @return Always returns @c true
*
* @see HelperSupport::isStopped()
* @see KJob::result
* @see KJob::finished
*/
bool kill(KillVerbosity verbosity = Quietly);
Q_SIGNALS:
/**
* @brief Signal emitted by the helper to notify the action's progress
*
* This signal is emitted every time the helper's code calls the
* HelperSupport::progressStep(QVariantMap) method. This is useful to let the
* helper notify the execution status of a long action, also providing
* some data, for example if you want to achieve some sort of progressive loading.
* The meaning of the data passed here is totally application-dependent.
* If you only need to pass some percentage, you can use the other signal that
* pass an int.
*
* @param data The progress data from the helper
*/
void newData(const QVariantMap &data);
/**
* @brief Signal emitted when the authentication status changes
* @param status the new authentication status
*/
void statusChanged(KAuth::Action::AuthStatus status);
private:
Q_DISABLE_COPY(ExecuteJob)
};
} // namespace Auth
#endif
@@ -0,0 +1,158 @@
/*
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
*/
#include "helpersupport.h"
#include <cstdlib>
#ifndef Q_OS_WIN
#include <pwd.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
#else
// Quick hack to replace syslog (just write to stderr)
// TODO: should probably use ReportEvent
#define LOG_ERR 3
#define LOG_WARNING 4
#define LOG_DEBUG 7
#define LOG_INFO 6
#define LOG_USER (1 << 3)
static inline void openlog(const char *, int, int)
{
}
static inline void closelog()
{
}
#define syslog(level, ...) fprintf(stderr, __VA_ARGS__)
#endif
#include <QCoreApplication>
#include <QTimer>
#include "BackendsManager.h"
namespace KAuth
{
namespace HelperSupport
{
void helperDebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgStr);
}
static bool remote_dbg = false;
#ifdef Q_OS_UNIX
static void fixEnvironment()
{
// try correct HOME
const char *home = "HOME";
if (getenv(home) == nullptr) {
struct passwd *pw = getpwuid(getuid());
if (pw != nullptr) {
int overwrite = 0;
setenv(home, pw->pw_dir, overwrite);
}
}
}
#endif
int HelperSupport::helperMain(int argc, char **argv, const char *id, QObject *responder)
{
#ifdef Q_OS_UNIX
fixEnvironment();
#endif
#ifdef Q_OS_OSX
openlog(id, LOG_CONS | LOG_PID, LOG_USER);
int logLevel = LOG_WARNING;
#else
openlog(id, 0, LOG_USER);
int logLevel = LOG_DEBUG;
#endif
qInstallMessageHandler(&HelperSupport::helperDebugHandler);
// NOTE: The helper proxy might use dbus, and we should have the qapp
// before using dbus.
QCoreApplication app(argc, argv);
if (!BackendsManager::helperProxy()->initHelper(QString::fromLatin1(id))) {
syslog(logLevel, "Helper initialization failed");
return -1;
}
// closelog();
remote_dbg = true;
BackendsManager::helperProxy()->setHelperResponder(responder);
// Attach the timer
QTimer *timer = new QTimer(nullptr);
responder->setProperty("__KAuth_Helper_Shutdown_Timer", QVariant::fromValue(timer));
timer->setInterval(10000);
timer->start();
QObject::connect(timer, &QTimer::timeout, &app, &QCoreApplication::quit);
app.exec(); // krazy:exclude=crashy
return 0;
}
void HelperSupport::helperDebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgStr)
{
Q_UNUSED(context); // can be used to find out about file, line, function name
QByteArray msg = msgStr.toLocal8Bit();
if (!remote_dbg) {
int level = LOG_DEBUG;
switch (type) {
case QtDebugMsg:
level = LOG_DEBUG;
break;
case QtWarningMsg:
level = LOG_WARNING;
break;
case QtCriticalMsg:
case QtFatalMsg:
level = LOG_ERR;
break;
case QtInfoMsg:
level = LOG_INFO;
break;
}
syslog(level, "%s", msg.constData());
} else if (!QCoreApplication::closingDown()) {
BackendsManager::helperProxy()->sendDebugMessage(type, msg.constData());
}
// Anyway I should follow the rule:
if (type == QtFatalMsg) {
exit(-1);
}
}
void HelperSupport::progressStep(int step)
{
BackendsManager::helperProxy()->sendProgressStep(step);
}
void HelperSupport::progressStep(const QVariantMap &data)
{
BackendsManager::helperProxy()->sendProgressStepData(data);
}
bool HelperSupport::isStopped()
{
return BackendsManager::helperProxy()->hasToStopAction();
}
int HelperSupport::callerUid()
{
return BackendsManager::helperProxy()->callerUid();
}
} // namespace Auth
@@ -0,0 +1,121 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KAUTH_HELPER_SUPPORT_H
#define KAUTH_HELPER_SUPPORT_H
#include <QObject>
#include <QVariant>
#include "kauthcore_export.h"
/**
* @relates KAuth
*
* The main macro for writing a helper tool.
* @param ID The helper ID as passed to the macro
* @param HelperClass The class name of the responder object for the helper
*
* @see @ref kauth_helper
*/
#define KAUTH_HELPER_MAIN(ID, HelperClass) \
int main(int argc, char **argv) \
{ \
return KAuth::HelperSupport::helperMain(argc, argv, ID, new HelperClass()); \
}
namespace KAuth
{
/**
* @brief Support class with some KAUTHCORE_EXPORT methods useful to the helper's code
*
* This class provides the API to write the helper tool that executes your actions.
* You don't create instances of HelperSupport. Instead, you use its KAUTHCORE_EXPORT methods.
*
* This them you can notify the application of progress in your action's execution
* and you can check if the application asked you to terminate it.
*
* @since 4.4
*/
namespace HelperSupport
{
/**
* @brief Send a progressStep signal to the caller application
*
* You can use this method to notify progress information about the
* action execution. When you call this method, the KAuth::ExecuteJob
* object associated with the current action will emit the KJob::percent(KJob*, unsigned long)
* signal. The meaning of the integer passed here is totally application dependent,
* but you'll want to use it as a sort of percentage.
* If you need to be more expressive, use the other overload which takes a QVariantMap
*
* @param step The progress indicator
*/
KAUTHCORE_EXPORT void progressStep(int step);
/**
* @brief Send a progressStep signal to the caller application
*
* You can use this method to notify progress information about the
* action execution. When you call this method, the KAuth::ExecuteJob
* object associated with the current action will emit the progressStep(QVariantMap)
* signal. The meaning of the data passed here is totally application dependent.
*
* If you only need a simple percentage value, use the other overload which takes an int.
*
* @param data The progress data
*/
KAUTHCORE_EXPORT void progressStep(const QVariantMap &data);
/**
* @brief Check if the caller asked the helper to stop the execution
*
* This method will return @c true if the helper has been asked to stop the
* execution of the current action. If this happens, your helper should
* return (NOT exit). The meaning of the data you return in this case is
* application-dependent.
*
* It's good practice to check it regularly if you have a long-running action
*
* @return @c true if the helper has been asked to stop, @c false otherwise
*
* @see ExecuteJob::kill
*/
KAUTHCORE_EXPORT bool isStopped();
/**
* @brief Method that implements the main function of the helper tool. Do not call directly
*
* This method is called in the proper way by the code generated by the KAUTH_HELPER_MAIN() macro,
* which creates a main() function for the helper tool.
* You shouldn't call this method directly.
*
* @param argc The argc parameter from the main() function.
* @param argv The argv parameter from the main() function.
* @param id The helper ID as passed to the macro
* @param responder The responder object for the helper. The macro passes a default-constructed,
* heap-allocated object of the class specified as the last macro parameter
*/
KAUTHCORE_EXPORT int helperMain(int argc, char **argv, const char *id, QObject *responder);
/**
* @brief Obtains the caller user id if available.
*
* This method offers a way to obtain the unprivileged client's UID if possible.
* For example when a D-Bus-based backend is used this will resolve the caller
* of the D-Bus method and obtain its process' UID.
*
* @since 5.74
*
* @return caller UID or -1 when not available
*/
KAUTHCORE_EXPORT int callerUid();
} // namespace HelperSupport
} // namespace Auth
#endif
@@ -0,0 +1,163 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "policy-gen.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QSettings>
#include <QStringList>
#include <cerrno>
#include <cstdio>
using namespace std;
QList<Action> parse(QSettings &ini);
QMap<QString, QString> parseDomain(const QSettings &ini);
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
if (argc < 2) {
qCritical("Too few arguments");
return 1;
}
QSettings ini(QFile::decodeName(argv[1]), QSettings::IniFormat);
if (ini.status()) {
qCritical("Error loading file: %s", argv[1]);
return 1;
}
if (argc == 3) {
// Support an optional 2nd argument pointing to the output file
//
// This is safer to use in build systems than
// "kauth-policy-gen foo.actions > foo.policy" because when using a
// redirection "foo.policy" is created even if kauth-policy-gen fails.
// This means the first call to make fails, but a second call succeeds
// because an empty "foo.policy" exists.
if (!freopen(argv[2], "w", stdout)) {
qCritical("Failed to open %s for writing: %s", argv[2], strerror(errno));
return 1;
}
}
output(parse(ini), parseDomain(ini));
}
QList<Action> parse(QSettings &ini)
{
// clazy:excludeall=use-static-qregularexpression
// example: [org.kde.kcontrol.kcmfoo.save]
const QRegularExpression actionExp(QRegularExpression::anchoredPattern(QStringLiteral("[0-9a-z]+(\\.[0-9a-z]+)*")));
// example: Description[ca]=Mòdul de control del Foo.
const QRegularExpression descriptionExp(QRegularExpression::anchoredPattern(QStringLiteral("description(?:\\[(\\w+)\\])?")),
QRegularExpression::CaseInsensitiveOption);
// example: Name[ca]=Mòdul de control del Foo
const QRegularExpression nameExp(QRegularExpression::anchoredPattern(QStringLiteral("name(?:\\[(\\w+)\\])?")), QRegularExpression::CaseInsensitiveOption);
// example: Policy=auth_admin
const QRegularExpression policyExp(QRegularExpression::anchoredPattern(QStringLiteral("(?:yes|no|auth_self|auth_admin)")));
QList<Action> actions;
const auto listChilds = ini.childGroups();
for (const QString &name : listChilds) {
Action action;
if (name == QLatin1String("Domain")) {
continue;
}
if (!actionExp.match(name).hasMatch()) {
qCritical("Wrong action syntax: %s\n", name.toLatin1().data());
exit(1);
}
action.name = name;
ini.beginGroup(name);
const QStringList listChildKeys = ini.childKeys();
for (const QString &key : listChildKeys) {
QRegularExpressionMatch match;
if ((match = descriptionExp.match(key)).hasMatch()) {
QString lang = match.captured(1);
if (lang.isEmpty()) {
lang = QString::fromLatin1("en");
}
action.descriptions.insert(lang, ini.value(key).toString());
} else if ((match = nameExp.match(key)).hasMatch()) {
QString lang = match.captured(1);
if (lang.isEmpty()) {
lang = QString::fromLatin1("en");
}
action.messages.insert(lang, ini.value(key).toString());
} else if (key.toLower() == QLatin1String("policy")) {
QString policy = ini.value(key).toString();
if (!policyExp.match(policy).hasMatch()) {
qCritical("Wrong policy: %s", policy.toLatin1().data());
exit(1);
}
action.policy = policy;
} else if (key.toLower() == QLatin1String("policyinactive")) {
QString policyInactive = ini.value(key).toString();
if (!policyExp.match(policyInactive).hasMatch()) {
qCritical("Wrong policy: %s", policyInactive.toLatin1().data());
exit(1);
}
action.policyInactive = policyInactive;
} else if (key.toLower() == QLatin1String("persistence")) {
QString persistence = ini.value(key).toString();
if (persistence != QLatin1String("session") && persistence != QLatin1String("always")) {
qCritical("Wrong persistence: %s", persistence.toLatin1().data());
exit(1);
}
action.persistence = persistence;
}
}
if (action.policy.isEmpty() || action.messages.isEmpty() || action.descriptions.isEmpty()) {
qCritical("Missing option in action: %s", name.toLatin1().data());
exit(1);
}
ini.endGroup();
actions.append(action);
}
return actions;
}
QMap<QString, QString> parseDomain(const QSettings &ini)
{
QMap<QString, QString> rethash;
if (ini.childGroups().contains(QString::fromLatin1("Domain"))) {
if (ini.contains(QString::fromLatin1("Domain/Name"))) {
rethash[QString::fromLatin1("vendor")] = ini.value(QString::fromLatin1("Domain/Name")).toString();
}
if (ini.contains(QString::fromLatin1("Domain/URL"))) {
rethash[QString::fromLatin1("vendorurl")] = ini.value(QString::fromLatin1("Domain/URL")).toString();
}
if (ini.contains(QString::fromLatin1("Domain/Icon"))) {
rethash[QString::fromLatin1("icon")] = ini.value(QString::fromLatin1("Domain/Icon")).toString();
}
}
return rethash;
}
@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef _POLICY_GEN_H_
#define _POLICY_GEN_H_
#include <QHash>
#include <QMap>
#include <QString>
struct Action {
QString name;
QMap<QString, QString> descriptions;
QMap<QString, QString> messages;
QString policy;
QString policyInactive;
QString persistence;
};
extern void output(const QList<Action> &actions, const QMap<QString, QString> &domain);
#endif