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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,7 @@ Qt5::Core PRIVATE ${BLKID_LIBRARIES} + ${POLKITQT-1_LIBRARIES} Qt5::DBus Qt5::Gui qca-qt5 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 @@ -43,6 +45,7 @@ target_link_libraries(kpmcore_externalcommand qca-qt5 + ${POLKITQT-1_LIBRARIES} Qt5::Core Qt5::DBus KF5::AuthCore diff --git a/src/util/externalcommand.h b/src/util/externalcommand.h --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -120,6 +120,12 @@ static void setParentWidget(QWidget *p) { parent = p; } + + + // Application side functions, used by startHelper() + void executeAction(const QString &action, const QString &helperID, const QVariantMap &arguments, int timeout = -1); + + void stopAction(const QString &action, const QString &helperID); Q_SIGNALS: void progress(int); diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -361,7 +361,7 @@ d->m_thread = new DBusThread; d->m_thread->start(); - KAuth::Action action = KAuth::Action(QStringLiteral("org.kde.kpmcore.externalcommand.init")); + /*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); @@ -377,7 +377,13 @@ QObject::connect(m_job, &KJob::finished, [=] () { if(m_job->error()) exitLoop(); } ); loop.exec(); QObject::disconnect(conn); - + */ + + /* + * Use exeuteAction() and stopAction() + */ + + helperStarted = true; return true; } @@ -390,6 +396,17 @@ } +void ExternalCommand::executeAction(const QString &action, const QString &helperID, const QVariantMap &arguments, int timeout/*= -1*/) +{ + // Use PolkitQt1 backend to execute the action + QTimer::singleShot(timeout, this, &ExternalCommand::quit); +} + +void ExternalCommand::stopAction(const QString &action, const QString &helperID) +{ + // Use PolkitQt1 backend to revoke the action +} + void DBusThread::run() { if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface")) || 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,159 @@ +/************************************************************************* + * 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 EXTERNALCOMMAND_POLKITBACKEND_H +#define EXTERNALCOMMAND_POLKITBACKEND_H + +#include +#include +#include +#include +#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 functions 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) + Q_PLUGIN_METADATA(IID "org.kde.Polkit1Backend") + +public: + /** + * \brief Constructor of PolkitQt1Backend class + */ + PolkitQt1Backend(); + + /** + * \brief Destructor of PolkitQt1Backend class + */ + ~PolkitQt1Backend(); + + /** + * \brief Initializes the KDE Polkit Authentication Agent. + * + * \param action Action in question + * \param parent Parent widget + * + */ + void initPolkitAgent(const QString &action, QWidget *parent = nullptr); + + /** + * \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, QByteArray &callerID); + + /** + * \brief Function to get the current Application process ID + * + * \return Application process ID of the action + */ + QByteArray callerID() const; + + /** + * \brief Tries to grant authorization 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, \c false Action is not authorized. + * + */ + bool isCallerAuthorized(const QString &action, QByteArray &callerID); + + /** + * \brief Chexcks if the action already exists ie. the action has already been granted authorization + * + * \param action Action in question. + * + * \return \c true if action is already authorized, \c false if action is not previously authorized. + * + */ + bool actionAlreadyExists(const QString &action); + + /** + * \return the code of last error which has occured. + */ + Authority::ErrorCode pollError() const; + + + // Dummy function for calling QTimer + void quit() const; + +public Q_SLOTS: + void sendProgressStep(const int percent); + void sendProgressStep(const QVariantMap &step); + + void checkForResultChanged(); + void updateCachedActions(const PolkitQt1::ActionDescription::List &actions); + +Q_SIGNALS: + void actionStarted(const QString &action); + void actionAuthorized(bool isAuthorized = false); + void actionCompleted(const QString &action); + +private: + QHash m_cachedResults; + QStringList m_existingActions; + bool m_flyingActions; // Already running actions +}; + +class PolkitEventLoop : public QEventLoop +{ + Q_OBJECT + +public: + PolkitEventLoop(QObject *parent = nullptr); + ~PolkitEventLoop(); + + Authority::Result result() const; + +public Q_SLOTS: + // Quits from the current Polkit Event loop + void requestQuit(const Authority::Result &result); + +private: + Authority::Result m_result; +}; + +} // namespace Auth + +#endif // 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,258 @@ + /************************************************************************* + * 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 +#include +#include + +#include +#include +#include + +using namespace PolkitQt1; + +namespace Auth +{ + +PolkitEventLoop::PolkitEventLoop(QObject *parent) + : QEventLoop(parent), + m_result(result()) +{ + +} + +PolkitEventLoop::~PolkitEventLoop() +{ + +} + +void PolkitEventLoop::requestQuit(const Authority::Result &result) +{ + m_result = result; + quit(); +} + +Authority::Result PolkitEventLoop::result() const +{ + return m_result; +} + +PolkitQt1Backend::PolkitQt1Backend() + : m_existingActions(QStringList()), + m_flyingActions(false) +{ + // Connect various useful Polkit signals + connect(Authority::instance(), &Authority::configChanged, this, &Auth::PolkitQt1Backend::checkForResultChanged); + connect(Authority::instance(), &Authority::consoleKitDBChanged, this, &Auth::PolkitQt1Backend::checkForResultChanged); + + //connect(Authority::instance(), /*SIGNAL(consoleKitDBChanged())*/&Gui::dataChanged, this, /*SLOT(checkForResultChanged())*/ &Auth::PolkitQt1Backend::checkForResultChanged); + //connect(Authority::instance(), /*SIGNAL(enumerateActionsFinished(PolkitQt1::ActionDescription::List))*/,this, /*SLOT(updateCachedActions(PolkitQt1::ActionDescription::List))*/); + + // Take the current action into account. + m_flyingActions = true; + + +} + +PolkitQt1Backend::~PolkitQt1Backend() +{ + +} + +void PolkitQt1Backend::initPolkitAgent(const QString &action, QWidget *parent /*= nullptr*/) +{ + if (!parent) { + qWarning() << "Parent widget does not exists, can not proceed further"; + return; + } + + // Check if KDE authentication agent exists + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String("org.kde.polkit-kde-authentication-agent-1"))) { + // Check if we are running terminal session or GUI session + if (!qApp || !qobject_cast(qApp)) { + qWarning() << "We are running a TTY session"; + } + + // Retrieve the dialog root window Id + qulonglong wId = parent->effectiveWinId(); + + // Send it over the bus to our agent + QDBusMessage methodCall = + QDBusMessage::createMethodCall(QLatin1String("org.kde.polkit-kde-authentication-agent-1"), QLatin1String("/org/kde/Polkit1AuthAgent"), QLatin1String("org.kde.Polkit1AuthAgent"), + QLatin1String("setWIdForAction")); + + methodCall << action; + methodCall << wId; + + QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(methodCall); + call.waitForFinished(); + + if (call.isError()) { + qWarning() << "ERROR while streaming the parent!!" << call.error(); + } + } else { + qInfo() << "KDE polkit agent appears too old or not registered on the bus"; + } +} + +Authority::Result PolkitQt1Backend::actionStatus(const QString &action, QByteArray &callerID) +{ + // Get the process ID of the caller + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + + auto authority = PolkitQt1::Authority::instance(); + + auto result = authority->checkAuthorizationSync(action, subject, Authority::None); + + if (authority->hasError()) { + qDebug() << "Encountered error while checking action status, error code:" << authority->lastError() << "\nError 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(); + } + + return result; +} + +QByteArray PolkitQt1Backend::callerID() const +{ + // Get the current Application process ID + return QDBusConnection::systemBus().baseService().toUtf8(); +} + +bool PolkitQt1Backend::isCallerAuthorized(const QString &action, QByteArray &callerID) +{ + SystemBusNameSubject subject(QString::fromUtf8(callerID)); + Authority *authority = Authority::instance(); + + PolkitEventLoop event; + + connect(authority, &Authority::checkAuthorizationFinished, &event, &PolkitEventLoop::requestQuit); + + authority->checkAuthorizationSync(action, subject, Authority::None); + + event.exec(); + + if (authority->hasError()) { + qWarning() << "Encountered error while checking authorization, Error code:" << authority->lastError() << "\n" <<"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() == Authority::Yes) { + // Emit signal signalling that current action is authorized by the authority instance + emit actionAuthorized(true); + return true; + } else { + emit actionAuthorized(false); + return false; + } +} + +bool PolkitQt1Backend::actionAlreadyExists(const QString &action) +{ + auto authority = Authority::instance(); + + if (m_flyingActions && authority->hasError() /*&& !authority->enumerateActionsSync()*/) { + // This can happen if enumerateActions call failed + qWarning() << "Encountered error while checking authorization, Error code:" << authority->lastError() << "\n" <<"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(); + + m_flyingActions = false; + } + + // Any flying actions? + if (m_flyingActions) { + int tries = 0; + while (m_flyingActions && tries < 10) { + // Wait max 2 seconds + QEventLoop e; + QTimer::singleShot(2000, &e, /*&quit*/SLOT(quit())); + e.exec(); + ++tries; + } + } + + return m_existingActions.contains(action); +} + +Authority::ErrorCode PolkitQt1Backend::pollError() const +{ + // Get current instance of the Authority + auto authority = Authority::instance(); + + if (authority->hasError()) { + return authority->lastError(); + } + + return Authority::E_None; +} + +// Dummy function for calling QTimer +void PolkitQt1Backend::quit() const +{ + +} + +void PolkitQt1Backend::sendProgressStep(const int percent) +{ + +} + +void PolkitQt1Backend::sendProgressStep(const QVariantMap &step) +{ + +} + +void PolkitQt1Backend::checkForResultChanged() +{ + 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; +} + +void PolkitQt1Backend::updateCachedActions(const ActionDescription::List &actions) +{ + +} + +} // namespace Auth + + + +