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:
@@ -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
@@ -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<QString,QDBusUnixFileDescriptor>"/>
|
||||
</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
|
||||
Reference in New Issue
Block a user