diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") +find_package(PolkitQt5-1 REQUIRED) include(KDEInstallDirs) include(KDECMakeSettings) @@ -60,6 +61,7 @@ Core DBus Gui + Test Widgets ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,17 +39,19 @@ ki18n_wrap_ui(kpmcore_SRCS ${gui_UIFILES}) add_library(kpmcore SHARED ${kpmcore_SRCS}) + +# We do not link to KF5::AuthCore because we just have build-time dependency on it target_link_libraries( kpmcore PUBLIC Qt5::Core PRIVATE ${BLKID_LIBRARIES} + ${POLKITQT-1_LIBRARIES} Qt5::DBus Qt5::Gui qca-qt5 KF5::I18n KF5::CoreAddons KF5::WidgetsAddons - KF5::AuthCore ) install(TARGETS kpmcore EXPORT KPMcoreTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -20,6 +20,7 @@ ${HelperInterface_SRCS} util/capacity.cpp util/externalcommand.cpp + util/externalcommand_polkitbackend.cpp util/globallog.cpp util/helpers.cpp util/htmlreport.cpp @@ -30,6 +31,7 @@ util/libpartitionmanagerexport.h util/capacity.h util/externalcommand.h + util/externalcommand_polkitbackend.h util/globallog.h util/helpers.h util/htmlreport.h @@ -42,16 +44,15 @@ ) target_link_libraries(kpmcore_externalcommand - qca-qt5 + ${POLKITQT-1_LIBRARIES} Qt5::Core Qt5::DBus - KF5::AuthCore KF5::I18n ) -install(TARGETS kpmcore_externalcommand DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) +install( TARGETS kpmcore_externalcommand DESTINATION ${KAUTH_HELPER_INSTALL_DIR} ) install( FILES util/org.kde.kpmcore.helperinterface.conf DESTINATION ${KDE_INSTALL_DBUSDIR}/system.d ) install( FILES util/org.kde.kpmcore.applicationinterface.conf DESTINATION ${KDE_INSTALL_DBUSDIR}/system.d ) +install( FILES util/org.kde.kpmcore.externalcommand.service DESTINATION ${DBUS_SYSTEM_SERVICES_INSTALL_DIR} ) -kauth_install_helper_files(kpmcore_externalcommand org.kde.kpmcore.externalcommand root) kauth_install_actions(org.kde.kpmcore.externalcommand util/org.kde.kpmcore.externalcommand.actions) diff --git a/src/util/externalcommand.h b/src/util/externalcommand.h --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -1,6 +1,7 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016-2018 by Andrius Štikonas * + * Copyright (C) 2019 by Shubham * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -25,31 +26,20 @@ #include #include #include -#include -#include #include #include -namespace KAuth { class ExecuteJob; } +namespace Auth { class PolkitQt1Backend; } -class KJob; -class Report; class CopySource; class CopyTarget; +class Report; class QDBusInterface; +class KJob; struct ExternalCommandPrivate; -class DBusThread : public QThread -{ - Q_OBJECT - // We register on DBus so the helper can monitor us and terminate if we - // terminate. - Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.applicationinterface") - void run() override; -}; - /** An external command. Runs an external command as a child process. @@ -60,6 +50,8 @@ class LIBKPMCORE_EXPORT ExternalCommand : public QObject { Q_OBJECT + // We register on DBus so the helper can monitor us and terminate if we terminate. + Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.applicationinterface") Q_DISABLE_COPY(ExternalCommand) public: @@ -106,7 +98,7 @@ /**< Dummy function for QTimer when needed. */ void quit(); - + // KAuth /**< start ExternalCommand Helper */ bool startHelper(); @@ -125,20 +117,25 @@ void progress(int); void reportSignal(const QVariantMap&); + void newData(); + public Q_SLOTS: void emitProgress(KJob*, unsigned long percent) { emit progress(percent); } + Q_SCRIPTABLE void emitNewData(int percent); + Q_SCRIPTABLE void emitNewData(QString message); + private: void setExitCode(int i); - void onReadOutput(); +// void onReadOutput(); private: std::unique_ptr d; - // KAuth - static KAuth::ExecuteJob *m_job; + // Authorize using Polkit backend + static Auth::PolkitQt1Backend *m_authJob; static bool helperStarted; static QWidget *parent; }; -#endif +#endif // KPMCORE_EXTERNALCOMMAND_H diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -1,6 +1,7 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016-2018 by Andrius Štikonas * + * Copyright (C) 2019 by Shubham * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -25,44 +26,40 @@ #include "core/copytargetdevice.h" #include "util/globallog.h" #include "util/externalcommand.h" +#include "util/externalcommand_polkitbackend.h" #include "util/report.h" #include "externalcommandhelper_interface.h" -#include -#include -#include -#include +#include #include -#include #include #include #include -#include #include +#include #include -#include #include #include +#include + struct ExternalCommandPrivate { Report *m_Report; QString m_Command; QStringList m_Args; int m_ExitCode; QByteArray m_Output; QByteArray m_Input; - DBusThread *m_thread; QProcess::ProcessChannelMode processChannelMode; }; -KAuth::ExecuteJob* ExternalCommand::m_job; +Auth::PolkitQt1Backend* ExternalCommand::m_authJob; bool ExternalCommand::helperStarted = false; QWidget* ExternalCommand::parent; - /** Creates a new ExternalCommand instance without Report. @param cmd the command to run @param args the arguments to pass to the command @@ -76,6 +73,8 @@ d->m_ExitCode = -1; d->m_Output = QByteArray(); + m_authJob = new Auth::PolkitQt1Backend; + if (!helperStarted) if(!startHelper()) Log(Log::Level::error) << xi18nc("@info:status", "Could not obtain administrator privileges."); @@ -96,13 +95,12 @@ d->m_Args = args; d->m_ExitCode = -1; d->m_Output = QByteArray(); - d->processChannelMode = processChannelMode; } ExternalCommand::~ExternalCommand() { - + } /* @@ -119,26 +117,29 @@ */ bool ExternalCommand::start(int timeout) { - if (command().isEmpty()) - return false; - if (!QDBusConnection::systemBus().isConnected()) { qWarning() << QDBusConnection::systemBus().lastError().message(); QTimer::singleShot(timeout, this, &ExternalCommand::quit); return false; } + if (command().isEmpty()) { + qInfo() << "There is no command to execute, exiting"; + return false; + } + if (report()) report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); - if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" )) + if (qEnvironmentVariableIsSet("KPMCORE_DEBUG")) qDebug() << xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" "))); QString cmd = QStandardPaths::findExecutable(command()); + if (cmd.isEmpty()) cmd = QStandardPaths::findExecutable(command(), { QStringLiteral("/sbin/"), QStringLiteral("/usr/sbin/"), QStringLiteral("/usr/local/sbin/") }); - auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), + auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days @@ -182,11 +183,7 @@ return false; } - // TODO KF6:Use new signal-slot syntax - connect(m_job, SIGNAL(percent(KJob*, unsigned long)), this, SLOT(emitProgress(KJob*, unsigned long))); - connect(m_job, &KAuth::ExecuteJob::newData, this, &ExternalCommand::emitReport); - - auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), + auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days @@ -230,10 +227,10 @@ return false; } - auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), + auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days - + QDBusPendingCall pcall = interface->writeData(buffer, deviceNode, firstByte); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); @@ -261,7 +258,9 @@ { if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" )) qDebug() << "Command input:" << QString::fromLocal8Bit(input); + d->m_Input = input; + return true; } @@ -274,8 +273,8 @@ return start(timeout) /* && exitStatus() == 0*/; } -void ExternalCommand::onReadOutput() -{ +//void ExternalCommand::onReadOutput() +//{ // const QByteArray s = readAllStandardOutput(); // // if(m_Output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems @@ -288,7 +287,7 @@ // // if (report()) // *report() << QString::fromLocal8Bit(s); -} +//} void ExternalCommand::setCommand(const QString& cmd) { @@ -340,64 +339,68 @@ d->m_ExitCode = i; } -/**< Dummy function for QTimer when needed. */ +/**< Dummy function for QTimer */ void ExternalCommand::quit() { - + } bool ExternalCommand::startHelper() { if (!QDBusConnection::systemBus().isConnected()) { + qWarning() << "Error starting the helper application!!"; qWarning() << QDBusConnection::systemBus().lastError().message(); return false; } - + QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); + if (iface.isValid()) { exit(0); } - d->m_thread = new DBusThread; - d->m_thread->start(); + /** Authorize using Polkit backend **/ - KAuth::Action action = KAuth::Action(QStringLiteral("org.kde.kpmcore.externalcommand.init")); - action.setHelperId(QStringLiteral("org.kde.kpmcore.externalcommand")); - action.setTimeout(10 * 24 * 3600 * 1000); // 10 days - action.setParentWidget(parent); - QVariantMap arguments; - action.setArguments(arguments); - m_job = action.execute(); - m_job->start(); + // initialize KDE Polkit daemon + m_authJob->initPolkitAgent(QStringLiteral("org.kde.kpmcore.externalcommand.init"), parent); - // Wait until ExternalCommand Helper is ready (helper sends newData signal just before it enters event loop) + // Kick start the helper application + m_authJob->startHelper(QStringLiteral("org.kde.kpmcore.externalcommand.init"), QStringLiteral("org.kde.kpmcore.externalcommand")); + + bool isActionAuthorized = m_authJob->authorizeAction(QStringLiteral("org.kde.kpmcore.externalcommand.init"), m_authJob->callerID()); + + // Wait until ExternalCommand Helper is ready and sends signal(Connect to newData signal) QEventLoop loop; auto exitLoop = [&] () { loop.exit(); }; - auto conn = QObject::connect(m_job, &KAuth::ExecuteJob::newData, exitLoop); - QObject::connect(m_job, &KJob::finished, [=] () { if(m_job->error()) exitLoop(); } ); + + connect(this, &ExternalCommand::newData, exitLoop); + loop.exec(); - QObject::disconnect(conn); + + if (!isActionAuthorized) { + qDebug() << "Unable to obtain Administrative privileges, the action can not be executed!!"; + } helperStarted = true; return true; } void ExternalCommand::stopHelper() { - auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), + auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus()); interface->exit(); } -void DBusThread::run() +void ExternalCommand::emitNewData(int percent) { - if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface")) || - !QDBusConnection::systemBus().registerObject(QStringLiteral("/Application"), this, QDBusConnection::ExportAllSlots)) { - qWarning() << QDBusConnection::systemBus().lastError().message(); - return; - } - - QEventLoop loop; - loop.exec(); + Q_UNUSED(percent) + emit newData(); +} + +void ExternalCommand::emitNewData(QString message) +{ + Q_UNUSED(message) + emit newData(); } diff --git a/src/util/externalcommand_polkitbackend.h b/src/util/externalcommand_polkitbackend.h new file mode 100644 --- /dev/null +++ b/src/util/externalcommand_polkitbackend.h @@ -0,0 +1,150 @@ +/************************************************************************* + * Copyright (C) 2019 by Shubham * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#ifndef KPMCORE_EXTERNALCOMMAND_POLKITBACKEND_H +#define KPMCORE_EXTERNALCOMMAND_POLKITBACKEND_H + +#include +#include +#include +#include +#include + +#include + +using namespace PolkitQt1; + +namespace Auth +{ + +/** A Polkit Qt backend class for authorizing actions. + + This class is used to authorize various actions if they + ask for privileged execution. It starts by verifying the + action under consideration if it is the one it is + saying and authorizes it based on the credentials + provided. + + @author Shubham +**/ +class PolkitQt1Backend : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(PolkitQt1Backend) + +public: + /** + * \brief Constructor of PolkitQt1Backend class + */ + PolkitQt1Backend(); + + /** + * \brief Destructor of PolkitQt1Backend class + */ + ~PolkitQt1Backend(); + + /** + * \brief A function to kick start the helper application. + * + * \param action Action name + * \param helperID Helper service name + * + */ + void startHelper(const QString &action, const QString &helperID) const; + + /** + * \brief Initializes the KDE Polkit Authentication Agent. + * + * \param action Action in question + * \param parent Parent widget + * + */ + void initPolkitAgent(const QString &action, QWidget *parent = nullptr) const; + + /** + * \brief A function to check for the action's current status. + * + * \param action Action in question + * \param calledID The Application process ID of the action + * + * \return the result of action status ie. If action is Authorized or not. + */ + Authority::Result actionStatus(const QString &action, const QByteArray &callerID) const; + + /** + * \brief Function to get the current Application process ID + * + * \return Application process ID of the action + */ + QByteArray callerID() const; + + /** + * \brief Tries to authorize to the \p action in question. + * + * \param action Action in question. + * \param callerID The Application process ID of the action + * + * \return \c true if authority authorizes the action successfully, \c false Action is not authorized. + * + */ + bool authorizeAction(const QString &action, const QByteArray &callerID); + + /** + * \brief Stops the running \p action from executing. + * + * \param action Action in question. + * + */ + bool revokeAuthorization(const QString &action, const QByteArray &callerID); + +public Q_SLOTS: + void authStatusChanged(); + +private: + QHash m_cachedResults; + bool m_flyingActions; // Already running actions +}; + +/** A Polkit event loop class. + + This class is used to implement a polkit event + loop and has the capability of returning the + current authorization result of the action in + que. + + @author Shubham +**/ +class PolkitEventLoop : public QEventLoop +{ + Q_OBJECT + +public: + PolkitEventLoop(QObject *parent = nullptr); + ~PolkitEventLoop(); + + Authority::Result result() const; + +public Q_SLOTS: + void requestQuit(const PolkitQt1::Authority::Result &result); + +public: + static Authority::Result m_result; +}; + +} // namespace Auth + +#endif // KPMCORE_EXTERNALCOMMAND_POLKITBACKEND_H diff --git a/src/util/externalcommand_polkitbackend.cpp b/src/util/externalcommand_polkitbackend.cpp new file mode 100644 --- /dev/null +++ b/src/util/externalcommand_polkitbackend.cpp @@ -0,0 +1,204 @@ +/************************************************************************* + * Copyright (C) 2019 by Shubham * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "util/externalcommand_polkitbackend.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace PolkitQt1; + +Authority::Result Auth::PolkitEventLoop::m_result = Authority::No; + +namespace Auth +{ + +PolkitEventLoop::PolkitEventLoop(QObject *parent) + : QEventLoop(qobject_cast(parent)) +{ + +} + +PolkitEventLoop::~PolkitEventLoop() +{ + +} + +void PolkitEventLoop::requestQuit(const Authority::Result &result) +{ + m_result = result; + quit(); +} + +Authority::Result PolkitEventLoop::result() const +{ + return m_result; +} + +PolkitQt1Backend::PolkitQt1Backend() + : m_flyingActions(false) +{ + // Connect various useful Polkit signals + connect(Authority::instance(), &Authority::configChanged, this, &Auth::PolkitQt1Backend::authStatusChanged); + connect(Authority::instance(), &Authority::consoleKitDBChanged, this, &Auth::PolkitQt1Backend::authStatusChanged); + + m_flyingActions = true; + PolkitQt1::Authority::instance()->enumerateActions(); +} + +PolkitQt1Backend::~PolkitQt1Backend() +{ + +} + +void PolkitQt1Backend::startHelper(const QString &action, const QString &helperID) const +{ + Q_UNUSED(action) + + const auto reply = QDBusConnection::systemBus().interface()->startService(helperID); + + if (!reply.isValid() && !QDBusConnection::systemBus().interface()->isServiceRegistered(helperID)) { + qWarning() << QDBusConnection::systemBus().lastError().message(); + qWarning() << "Failure starting the helper"; + return; + } +} + +void PolkitQt1Backend::initPolkitAgent(const QString &action, QWidget *parent /*= nullptr*/) const +{ + if (!parent) { + qWarning() << "Parent widget does not exists, can not proceed further"; + return; + } + + // Check if we are running terminal session or GUI session + if (!qApp) { + qWarning() << "We are running a TTY (Terminal) session"; + qDebug() << "Can not proceed further since we do not support Text based Polkit Authentication Agent"; + return; + } + + // Get the dialog parent window Id + quint64 parentWindowID = parent->effectiveWinId(); + + // Make a call to the KDE polkit Authentication Agent asking for it's services + QDBusMessage callAgent = QDBusMessage::createMethodCall(QStringLiteral("org.kde.polkit-kde-authentication-agent-1"), QStringLiteral("/org/kde/Polkit1AuthAgent"), QStringLiteral("org.kde.Polkit1AuthAgent"), + QStringLiteral("setWIdForAction")); + + callAgent << action; + callAgent << parentWindowID; + + QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(callAgent); + + auto watcher = new QDBusPendingCallWatcher(call); + + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, action, watcher](){ + watcher->deleteLater(); + const QDBusMessage reply = watcher->reply(); + + if (reply.type() == QDBusMessage::ErrorMessage) { + qWarning() << "Could not call the Authentication Agent, Error:" << reply.errorMessage(); + } + }); +} + +Authority::Result PolkitQt1Backend::actionStatus(const QString &action, const QByteArray &callerID) const +{ + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + + auto authority = Authority::instance(); + + PolkitEventLoop::m_result = authority->checkAuthorizationSync(action, subject, Authority::None); + + if (authority->hasError()) { + qDebug() << "Encountered error while checking action status, Error code:" << authority->lastError() << "\n"; + qDebug() << "Error Details:" << authority->errorDetails(); + authority->clearError(); + } + + return PolkitEventLoop::m_result; +} + +QByteArray PolkitQt1Backend::callerID() const +{ + return QDBusConnection::systemBus().baseService().toUtf8(); +} + +bool PolkitQt1Backend::authorizeAction(const QString &action, const QByteArray &callerID) +{ + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + + auto authority = Authority::instance(); + + PolkitEventLoop event; + event.processEvents(); + + connect(authority, &Authority::checkAuthorizationFinished, &event, &PolkitEventLoop::requestQuit); + authority->checkAuthorizationSync(action, subject, Authority::AllowUserInteraction); + + event.exec(); + + if (authority->hasError()) { + qWarning() << "Encountered error while checking authorization, Error code:" << authority->lastError() << "\n"; + qDebug() << "Error details:" << authority->errorDetails(); + + // Clear all the errors from the buffer so that hasError() does not give previous error as a result when called later + authority->clearError(); + } + + if (event.result() /*PolkitEventLoop::m_result*/ == Authority::Yes) { + return true; + } else { + return false; + } +} + +bool PolkitQt1Backend::revokeAuthorization(const QString &action, const QByteArray &callerID) +{ + Q_UNUSED(action) + + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + + auto authority = Authority::instance(); + + return authority->revokeTemporaryAuthorizationsSync(subject); +} + +void PolkitQt1Backend::authStatusChanged() +{ + for (auto it = m_cachedResults.begin(); it != m_cachedResults.end(); ++it) { + const QString action = it.key(); + QByteArray pid = QDBusConnection::systemBus().baseService().toUtf8(); + if (it.value() != actionStatus(action, pid)) { + *it = actionStatus(action, pid); + } + } + + // Force updating known actions + Authority::instance()->enumerateActions(); + m_flyingActions = true; +} + +} // namespace Auth diff --git a/src/util/externalcommandhelper.h b/src/util/externalcommandhelper.h --- a/src/util/externalcommandhelper.h +++ b/src/util/externalcommandhelper.h @@ -1,5 +1,6 @@ /************************************************************************* * Copyright (C) 2017-2018 by Andrius Štikonas * + * Copyright (C) 2019 by Shubham * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -21,40 +22,38 @@ #include #include -#include - #include -#include #include +#include +#include -using namespace KAuth; +#define HELPER_MAIN() \ + int main(int argc, char **argv) { ExternalCommandHelper helper; return helper.helperMain(argc, argv); } class ExternalCommandHelper : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.externalcommand") -Q_SIGNALS: - void progress(int); - void quit(); - public: bool readData(const QString& sourceDevice, QByteArray& buffer, const qint64 offset, const qint64 size); bool writeData(const QString& targetDevice, const QByteArray& buffer, const qint64 offset); + void sendProgress(int percent); + void sendProgress(QString message); + int helperMain(int argc, char **argv); public Q_SLOTS: - ActionReply init(const QVariantMap& args); Q_SCRIPTABLE QVariantMap start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode); Q_SCRIPTABLE QVariantMap copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize); Q_SCRIPTABLE bool writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte); Q_SCRIPTABLE void exit(); private: - void onReadOutput(); - std::unique_ptr m_loop; QProcess m_cmd; + // QByteArray output; +// void onReadOutput(); }; -#endif +#endif // KPMCORE_EXTERNALCOMMANDHELPER_H diff --git a/src/util/externalcommandhelper.cpp b/src/util/externalcommandhelper.cpp --- a/src/util/externalcommandhelper.cpp +++ b/src/util/externalcommandhelper.cpp @@ -1,5 +1,6 @@ /************************************************************************* * Copyright (C) 2017-2018 by Andrius Štikonas * + * Copyright (C) 2019 by Shubham * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -16,21 +17,23 @@ *************************************************************************/ #include "externalcommandhelper.h" + +#include "externalcommand_interface.h" #include "externalcommand_whitelist.h" -#include #include +#include #include +#include #include #include -#include #include #include /** Initialize ExternalCommandHelper Daemon and prepare DBus interface * - * KAuth helper runs in the background until application exits. + * Helper runs in the background until application exits. * To avoid forever running helper in case of application crash * ExternalCommand class opens a DBus service that we monitor for changes. * If helper is not busy then it exits when the client services gets @@ -40,50 +43,54 @@ * * This helper also starts another DBus interface where it listens to * command execution requests from the application that started the helper. - * + * */ -ActionReply ExternalCommandHelper::init(const QVariantMap& args) -{ - Q_UNUSED(args) - - ActionReply reply; - if (!QDBusConnection::systemBus().isConnected() || !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || - !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { +/** Reads the given number of bytes from the sourceDevice into the given buffer. + @param argc argument count + @param argv argument vector + @return zero on success, non-zero on failure +*/ +int ExternalCommandHelper::helperMain(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + if (!QDBusConnection::systemBus().isConnected() || + !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots) || + !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || + !QDBusConnection::systemBus().registerObject(QStringLiteral("/Application"), this, QDBusConnection::ExportAllSlots) || + !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface"))) { + qDebug() << "Failed to initialize the Helper"; qWarning() << QDBusConnection::systemBus().lastError().message(); - reply.addData(QStringLiteral("success"), false); - - // Also end the application loop started by KAuth's main() code. Our loop - // exits when our client disappears. Without client we have no reason to - // live. + + // We have no reason to live when Main GUI app has expired qApp->quit(); - - return reply; + + return app.exec(); } - + m_loop = std::make_unique(); - HelperSupport::progressStep(QVariantMap()); - - // End the loop and return only once the client is done using us. - auto serviceWatcher = - new QDBusServiceWatcher(QStringLiteral("org.kde.kpmcore.applicationinterface"), - QDBusConnection::systemBus(), - QDBusServiceWatcher::WatchForUnregistration, - this); + + // We send zero percent new data on initial start-up + sendProgress(0); + + // QDBus Service watcher which keeps an eye on the client (Main GUI app) + // End the loop and return only once the client has unregistered over the QDBus. + auto serviceWatcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kpmcore.applicationinterface"), + QDBusConnection::systemBus(), + QDBusServiceWatcher::WatchForUnregistration, + this); + connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, [this]() { m_loop->exit(); }); m_loop->exec(); - reply.addData(QStringLiteral("success"), true); - // Also end the application loop started by KAuth's main() code. Our loop - // exits when our client disappears. Without client we have no reason to - // live. qApp->quit(); - return reply; + return app.exec(); } @@ -146,6 +153,62 @@ return true; } +/** Sends progress to the main application in terms of percentage. + @param percent Percent of job completed. +*/ +void ExternalCommandHelper::sendProgress(int percent) +{ + auto interface = new org::kde::kpmcore::applicationinterface(QStringLiteral("org.kde.kpmcore.applicationinterface"), + QStringLiteral("/Application"), QDBusConnection::systemBus(), this); + interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days + + QDBusPendingCall pcall = interface->emitNewData(percent); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + QEventLoop loop; + + auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { + loop.exit(); + if (watcher->isError()) + qWarning() << watcher->error(); + }; + + connect(watcher, &QDBusPendingCallWatcher::finished, [this, exitLoop, watcher](){ + watcher->deleteLater(); + } + ); + + loop.exec(); +} + +/** Sends progress to the main application in terms of string message. + @param message Message to send to the main application. +*/ +void ExternalCommandHelper::sendProgress(QString message) +{ + auto interface = new org::kde::kpmcore::applicationinterface(QStringLiteral("org.kde.kpmcore.applicationinterface"), + QStringLiteral("/Application"), QDBusConnection::systemBus(), this); + interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days + + QDBusPendingCall pcall = interface->emitNewData(message); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + QEventLoop loop; + + auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { + loop.exit(); + if (watcher->isError()) + qWarning() << watcher->error(); + }; + + connect(watcher, &QDBusPendingCallWatcher::finished, [this, exitLoop, watcher](){ + watcher->deleteLater(); + } + ); + + loop.exec(); +} + // If targetDevice is empty then return QByteArray with data that was read from disk. QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize) { @@ -170,17 +233,16 @@ QByteArray buffer; int percent = 0; - QTime t; + + QElapsedTimer t; t.start(); - QVariantMap report; + sendProgress(xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy, - report[QStringLiteral("report")] = xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy, - sourceLength, readOffset, writeOffset, copyDirection == 1 ? i18nc("direction: left", "left") - : i18nc("direction: right", "right")); - HelperSupport::progressStep(report); + sourceLength, readOffset, writeOffset, copyDirection == 1 ? i18nc("direction: left", "left") + : i18nc("direction: right", "right"))); bool rval = true; @@ -199,10 +261,11 @@ if (percent % 5 == 0 && t.elapsed() > 1000) { const qint64 mibsPerSec = (blocksCopied * blockSize / 1024 / 1024) / (t.elapsed() / 1000); const qint64 estSecsLeft = (100 - percent) * t.elapsed() / percent / 1000; - report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString()); - HelperSupport::progressStep(report); + + sendProgress(xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString())); + } - HelperSupport::progressStep(percent); + sendProgress(percent); } } @@ -212,8 +275,9 @@ const qint64 lastBlockReadOffset = copyDirection > 0 ? readOffset + blockSize * blocksCopied : sourceFirstByte; const qint64 lastBlockWriteOffset = copyDirection > 0 ? writeOffset + blockSize * blocksCopied : targetFirstByte; - report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset); - HelperSupport::progressStep(report); + + sendProgress(xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset)); + rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock); if (rval) { @@ -224,13 +288,12 @@ } if (rval) { - HelperSupport::progressStep(100); + sendProgress(100); bytesWritten += buffer.size(); } } - report[QStringLiteral("report")] = xi18ncp("@info:progress argument 2 is a string such as 7 bytes (localized accordingly)", "Copying 1 block (%2) finished.", "Copying %1 blocks (%2) finished.", blocksCopied, i18np("1 byte", "%1 bytes", bytesWritten)); - HelperSupport::progressStep(report); + sendProgress(xi18ncp("@info:progress argument 2 is a string such as 7 bytes (localized accordingly)", "Copying 1 block (%2) finished.", "Copying %1 blocks (%2) finished.", blocksCopied, i18np("1 byte", "%1 bytes", bytesWritten))); reply[QStringLiteral("success")] = rval; return reply; @@ -245,7 +308,6 @@ return writeData(targetDevice, buffer, targetFirstByte); } - QVariantMap ExternalCommandHelper::start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode) { QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); @@ -260,7 +322,7 @@ // Compare with command whitelist QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { - qInfo() << command <<" command is not one of the whitelisted command"; + qInfo() << command <<"Command is not one of the whitelisted command"; m_loop->exit(); reply[QStringLiteral("success")] = false; return reply; @@ -274,7 +336,9 @@ m_cmd.write(input); m_cmd.closeWriteChannel(); m_cmd.waitForFinished(-1); + QByteArray output = m_cmd.readAllStandardOutput(); + reply[QStringLiteral("output")] = output; reply[QStringLiteral("exitCode")] = m_cmd.exitCode(); @@ -289,9 +353,9 @@ QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface")); } -void ExternalCommandHelper::onReadOutput() +/*void ExternalCommandHelper::onReadOutput() { -/* const QByteArray s = cmd.readAllStandardOutput(); + const QByteArray s = cmd.readAllStandardOutput(); if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems if (report()) @@ -302,7 +366,7 @@ output += s; if (report()) - *report() << QString::fromLocal8Bit(s);*/ -} + *report() << QString::fromLocal8Bit(s); +}*/ -KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper) +HELPER_MAIN() diff --git a/src/util/org.kde.kpmcore.helperinterface.conf b/src/util/org.kde.kpmcore.helperinterface.conf --- a/src/util/org.kde.kpmcore.helperinterface.conf +++ b/src/util/org.kde.kpmcore.helperinterface.conf @@ -11,6 +11,5 @@ - diff --git a/src/util/org.kde.kpmcore.externalcommand.service b/src/util/org.kde.kpmcore.externalcommand.service new file mode 100644 --- /dev/null +++ b/src/util/org.kde.kpmcore.externalcommand.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.kde.kpmcore.externalcommand +Exec=/usr/lib/kauth/libexec/kpmcore_externalcommand +User=root + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,7 +4,7 @@ # how to use the library, how to perform common tasks. set(CMAKE_SKIP_BUILD_RPATH FALSE) -SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) include_directories(${CMAKE_SOURCE_DIR}/src) # To get at KPMcore headers @@ -20,9 +20,14 @@ macro (kpm_test name) add_executable(${name} ${ARGN}) - target_link_libraries(${name} testhelpers kpmcore Qt5::Core) + target_link_libraries(${name} testhelpers kpmcore Qt5::Core Qt5::DBus Qt5::Test ${POLKITQT-1_LIBRARIES}) endmacro() +if(NOT Qt5Test_FOUND) + message(STATUS "Qt5Test not found, testpolkitauthbackend will not be built.") + return() +endif() + ### # # Tests of initialization: try explicitly loading some backends @@ -66,3 +71,11 @@ # Test Device kpm_test(testdevice testdevice.cpp) add_test(NAME testdevice COMMAND testdevice ${BACKEND}) + + +# Include Polkit-Qt1 backend files reference +set(POLKITBACKEND ${CMAKE_SOURCE_DIR}/src/util/externalcommand_polkitbackend.cpp) + +# Test PolkitQt-1 Authorization Backend +kpm_test(testpolkitauthbackend testpolkitauthbackend.cpp ${POLKITBACKEND}) +add_test(NAME testpolkitauthbackend COMMAND testpolkitauthbackend ${BACKEND}) diff --git a/test/testpolkitauthbackend.h b/test/testpolkitauthbackend.h new file mode 100644 --- /dev/null +++ b/test/testpolkitauthbackend.h @@ -0,0 +1,50 @@ +/************************************************************************* + * Copyright (C) 2019 by Shubham * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* +**************************************************************************/ + +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef TESTPOLKITAUTHBACKEND_H +#define TESTPOLKITAUTHBACKEND_H + +#include + +#include + +class QByteArray; +class QString; + +using namespace PolkitQt1; + +class TestPolkitAuthBackend : public QObject +{ + Q_OBJECT + +public: + TestPolkitAuthBackend(); + ~TestPolkitAuthBackend(); + +private Q_SLOTS: + void startHelper(const QString &action, const QString &helperID) const; + void initAgent(const QString &action, QWidget *parent = nullptr) const; + QByteArray callerID() const; + Authority::Result actionStatus(const QString &action, const QByteArray &callerID) const; + bool authorizeAction(const QString &action, const QByteArray &caller) const; + bool revokeAuthorization(const QString &action, const QByteArray &callerID) const; +}; + +#endif // TESTPOLKITAUTHBACKEND_H + diff --git a/test/testpolkitauthbackend.cpp b/test/testpolkitauthbackend.cpp new file mode 100644 --- /dev/null +++ b/test/testpolkitauthbackend.cpp @@ -0,0 +1,105 @@ +/************************************************************************* + * Copyright (C) 2019 by Shubham * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* +**************************************************************************/ + +// SPDX-License-Identifier: GPL-3.0+ + +#include "testpolkitauthbackend.h" +#include "util/externalcommand_polkitbackend.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace Auth; +using namespace PolkitQt1; + +TestPolkitAuthBackend::TestPolkitAuthBackend() +{ +} + +TestPolkitAuthBackend::~TestPolkitAuthBackend() +{ +} + +void TestPolkitAuthBackend::startHelper(const QString &action, const QString &helperID) const +{ + PolkitQt1Backend *m_authJob = new PolkitQt1Backend; + m_authJob->startHelper(action, helperID); +} + +void TestPolkitAuthBackend::initAgent(const QString &action, QWidget *parent/* = nullptr*/) const +{ + PolkitQt1Backend *m_authJob = new PolkitQt1Backend; + m_authJob->initPolkitAgent(action, parent); +} + +QByteArray TestPolkitAuthBackend::callerID() const +{ + return QByteArray("Random caller ID"); +} + +Authority::Result TestPolkitAuthBackend::actionStatus(const QString &action, const QByteArray &callerID) const +{ + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + + auto authority = PolkitQt1::Authority::instance(); + auto result = authority->checkAuthorizationSync(action, subject, Authority::None); + + return result; +} + +bool TestPolkitAuthBackend::authorizeAction(const QString &action, const QByteArray &caller) const +{ + if (action == QLatin1String("doomed.to.fail")) { + return false; + } else if (action == QLatin1String("requires.auth")) { + return true; + } else if (action == QLatin1String("generates.error")) { + return false; + } else if (action == QLatin1String("always.authorized")) { + return true; + } else if (action.startsWith(QLatin1String("org.kde.externalcommand.init"))) { + qDebug() << "Caller ID:" << callerID(); + const QByteArray calling = callerID(); + + if (caller == calling) { + return true; + } else { + return false; + } + } + + return false; +} + +bool TestPolkitAuthBackend::revokeAuthorization(const QString &action, const QByteArray &callerID) const +{ + Q_UNUSED(action) + + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + + auto authority = Authority::instance(); + + return authority->revokeTemporaryAuthorizationsSync(subject); +} + +QTEST_MAIN(TestPolkitAuthBackend)