diff --git a/autotests/kiotesthelper.h b/autotests/kiotesthelper.h --- a/autotests/kiotesthelper.h +++ b/autotests/kiotesthelper.h @@ -171,6 +171,14 @@ return m_deleteResult; } + bool askPrivilegeOpConfirmation(const QList &urls, + PrivilegeConfType privilegeConfType) Q_DECL_OVERRIDE { + Q_UNUSED(urls); + Q_UNUSED(privilegeConfType); + ++m_askPrivilegeOpCalled; + return m_privilegeOpResult; + } + int requestMessageBox(MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, @@ -196,10 +204,12 @@ int m_askFileRenameCalled; int m_askSkipCalled; int m_askDeleteCalled; + int m_askPrivilegeOpCalled; int m_messageBoxCalled; KIO::RenameDialog_Result m_renameResult; KIO::SkipDialog_Result m_skipResult; bool m_deleteResult; + bool m_privilegeOpResult; int m_messageBoxResult; }; diff --git a/src/core/jobuidelegateextension.h b/src/core/jobuidelegateextension.h --- a/src/core/jobuidelegateextension.h +++ b/src/core/jobuidelegateextension.h @@ -213,7 +213,21 @@ */ virtual bool askDeleteConfirmation(const QList &urls, DeletionType deletionType, ConfirmationType confirmationType) = 0; - + /** + * The type of warning to be shown prior to a privileged file I/O operation. + */ + enum PrivilegeConfType { PrivilegeDelete, PrivilegeRename, PrivilegeMkDir, PrivilegeSymlink }; + /** + * Ask for confirmation before starting a privileged file operation. + * + * If the client supports privileged file management then this method must be called + * before starting the job which would perform the privileged operation. In this case the + * confirmation type is always ForceConfirmation. + * + * @param urls the urls upon which the job will operate + * @param privilegeOpType the operation for which the confirmation is being shown + */ + virtual bool askPrivilegeOpConfirmation(const QList &urls, PrivilegeConfType privilegeConfType) = 0; /** * Message box types. * diff --git a/src/ioslaves/file/CMakeLists.txt b/src/ioslaves/file/CMakeLists.txt --- a/src/ioslaves/file/CMakeLists.txt +++ b/src/ioslaves/file/CMakeLists.txt @@ -21,8 +21,13 @@ configure_file(config-kioslave-file.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kioslave-file.h ) add_library(kio_file MODULE ${kio_file_PART_SRCS}) + target_link_libraries(kio_file KF5::KIOCore KF5::I18n) +if(UNIX) + target_link_libraries(kio_file KF5::Auth) +endif() + if (HAVE_VOLMGT AND CMAKE_SYSTEM_NAME MATCHES SunOS) target_link_libraries(kio_file -lvolmgt) endif () @@ -33,3 +38,7 @@ set_target_properties(kio_file PROPERTIES OUTPUT_NAME "file") install(TARGETS kio_file DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio) + +if (UNIX) + add_subdirectory(kauth) +endif() diff --git a/src/ioslaves/file/file.h b/src/ioslaves/file/file.h --- a/src/ioslaves/file/file.h +++ b/src/ioslaves/file/file.h @@ -100,10 +100,20 @@ void fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Turn into virtual method in SlaveBase + enum PrivilegeWarning { + WARN_PRIVILEGE_DEL = 1, + WARN_PRIVILEGE_RMDIR + }; + bool execWithElevatedPrivilege(const QString &action, const QString &subAction, const QVariant &args, PrivilegeWarning warning); + void endPrivilegeOperation(); + QString warningMessage(PrivilegeWarning warnId) const; + bool showWarning(PrivilegeWarning warnId); + private: mutable QHash mUsercache; mutable QHash mGroupcache; QFile *mFile; + bool mPriviledgeOpStarted; }; #endif diff --git a/src/ioslaves/file/file.cpp b/src/ioslaves/file/file.cpp --- a/src/ioslaves/file/file.cpp +++ b/src/ioslaves/file/file.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #if HAVE_VOLMGT #include @@ -154,7 +155,7 @@ } FileProtocol::FileProtocol(const QByteArray &pool, const QByteArray &app) - : SlaveBase(QByteArrayLiteral("file"), pool, app), mFile(nullptr) + : SlaveBase(QByteArrayLiteral("file"), pool, app), mFile(nullptr), mPriviledgeOpStarted(false) { } @@ -1376,5 +1377,102 @@ } } +bool FileProtocol::execWithElevatedPrivilege(const QString &action, const QString &subAction, const QVariant &args, PrivilegeWarning warning) +{ +#ifdef Q_OS_UNIX + QVariantMap argv; + argv.insert(QStringLiteral("arguments"), args); + argv.insert(QStringLiteral("subaction"), subAction); + + KAuth::Action execAction(QStringLiteral("org.kde.kio.file.%1").arg(action)); + execAction.setHelperId(QStringLiteral("org.kde.kio.file")); + execAction.setArguments(argv); + + const bool showWarningForAction = (metaData(QStringLiteral("ShowInternalWarning")) == QStringLiteral("true")); + + int kauthStatus = execAction.status(); + if (kauthStatus == KAuth::Action::AuthRequiredStatus) { + mPriviledgeOpStarted = true; + } + + // if we are unit testing let's pretend to execute the action. + if (metaData(QStringLiteral("UnitTesting")) == QStringLiteral("true")) { + QStringList testData; + testData += execAction.name(); + testData += QString::number(execAction.isValid()); + testData += execAction.helperId(); + testData += QString::number(kauthStatus); + + // fake authorization + // if "ShowInternalWarning" is also "true" then this fake authorization will make the + // situation appear as if 'execWithRoot' method has been called for the second time + // within the the threshold time which means a warning must be shown instead of an + // authentication dialog. + kauthStatus = KAuth::Action::AuthorizedStatus; + + // fake operation ending + mPriviledgeOpStarted = false; + + // fake warning + if (showWarningForAction) + testData += QStringLiteral("warningshown"); + + setMetaData(QStringLiteral("TestData"), testData.join(QStringLiteral(","))); + return true; + } + + bool proceed = true; + if (kauthStatus == KAuth::Action::AuthorizedStatus + && !mPriviledgeOpStarted + && showWarningForAction) { + mPriviledgeOpStarted = true; + proceed = showWarning(warning); + } + + if (proceed) { + auto reply = execAction.execute(); + if (!reply->exec()) { + endPrivilegeOperation(); + return false; + } + return true; + } + endPrivilegeOperation(); +#endif + return false; +} + +void FileProtocol::endPrivilegeOperation() +{ + mPriviledgeOpStarted = false; +} + +QString FileProtocol::warningMessage(PrivilegeWarning warnId) const +{ + QString message; + switch(warnId) { + + case WARN_PRIVILEGE_DEL: { + message = i18n("You are attempting to delete a file from a write protected location. " + "Doing so may cause irreversible damage to your system. Do you want to proceed?"); + break; + } + case WARN_PRIVILEGE_RMDIR: { + message = i18n("You are attempting to delete a directory from a write protected location. " + "Doing so may cause irreversible damage to your system. Do you want to proceed?"); + break; + } + + } + + return message; +} + +bool FileProtocol::showWarning(PrivilegeWarning warnId) +{ + int status = messageBox(WarningContinueCancel, warningMessage(warnId), QStringLiteral("Warning!"), QStringLiteral("Continue"), QStringLiteral("Cancel")); + return status != 2; +} + // needed for JSON file embedding #include "file.moc" diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cpp --- a/src/ioslaves/file/file_unix.cpp +++ b/src/ioslaves/file/file_unix.cpp @@ -549,6 +549,7 @@ if (QT_RMDIR(_path.data()) == -1) { if ((errno == EACCES) || (errno == EPERM)) { error(KIO::ERR_ACCESS_DENIED, path); + return; } else { // qDebug() << "could not rmdir " << perror; error(KIO::ERR_CANNOT_RMDIR, path); diff --git a/src/ioslaves/file/kauth/CMakeLists.txt b/src/ioslaves/file/kauth/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/ioslaves/file/kauth/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(file_helper helper.cpp) +target_link_libraries(file_helper KF5::Auth KF5::I18n KF5::KIOCore) + +install(TARGETS file_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) +kauth_install_helper_files(file_helper org.kde.kio.file root) +kauth_install_actions(org.kde.kio.file file.actions) diff --git a/src/ioslaves/file/kauth/file.actions b/src/ioslaves/file/kauth/file.actions new file mode 100644 diff --git a/src/ioslaves/file/kauth/helper.h b/src/ioslaves/file/kauth/helper.h new file mode 100644 --- /dev/null +++ b/src/ioslaves/file/kauth/helper.h @@ -0,0 +1,34 @@ +/*** + Copyright (C) 2017 by Chinmoy Ranjan Pradhan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +***/ + + +#ifndef HELPER_H +#define HELPER_H + +#include + +using namespace KAuth; + +class Helper : public QObject +{ + Q_OBJECT +}; + +#endif diff --git a/src/ioslaves/file/kauth/helper.cpp b/src/ioslaves/file/kauth/helper.cpp new file mode 100644 --- /dev/null +++ b/src/ioslaves/file/kauth/helper.cpp @@ -0,0 +1,23 @@ +/*** + Copyright (C) 2017 by Chinmoy Ranjan Pradhan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +***/ + +#include "helper.h" + +KAUTH_HELPER_MAIN("org.kde.kio.file", Helper) diff --git a/src/widgets/jobuidelegate.h b/src/widgets/jobuidelegate.h --- a/src/widgets/jobuidelegate.h +++ b/src/widgets/jobuidelegate.h @@ -127,6 +127,17 @@ */ bool askDeleteConfirmation(const QList &urls, DeletionType deletionType, ConfirmationType confirmationType) Q_DECL_OVERRIDE; + /** + * Ask for confirmation before starting a privileged file operation. + * + * If the client supports privileged file management then this method must be called + * before starting the job which would perform the privileged operation. In this case the + * confirmation type is always ForceConfirmation. + * + * @param urls the urls upon which the job will operate + * @param privilegeOpType the operation for which the confirmation is being shown + */ + bool askPrivilegeOpConfirmation(const QList &urls, PrivilegeConfType privilegeConfType) Q_DECL_OVERRIDE; /** * This function allows for the delegation user prompts from the ioslaves. diff --git a/src/widgets/jobuidelegate.cpp b/src/widgets/jobuidelegate.cpp --- a/src/widgets/jobuidelegate.cpp +++ b/src/widgets/jobuidelegate.cpp @@ -271,6 +271,66 @@ return true; } +bool KIO::JobUiDelegate::askPrivilegeOpConfirmation(const QList &urls, + PrivilegeConfType privilegeConfType) +{ + QStringList prettyList; + Q_FOREACH (const QUrl &url, urls) { + prettyList.append(url.toDisplayString(QUrl::PreferLocalFile)); + } + + int result; + QWidget *widget = window(); + const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); + switch (privilegeConfType) + { + case PrivilegeDelete: + result = KMessageBox::warningContinueCancelList( + widget, + i18np("This item is write-protected. Deleting it might break the system. Do you really want to delete this item?", + "These items are write-protected. Deleting them might break the system. Do you really want to delete these %1 items?", prettyList.count()), + prettyList, + i18n("Delete Write-Protected Files"), + KStandardGuiItem::del(), + KStandardGuiItem::cancel(), + QString(), options); + break; + case PrivilegeRename: + result = KMessageBox::warningContinueCancelList( + widget, + i18np("The item is in a write-protected location. Do you really want to rename this item?", + "The items are in a write-protected location. Do you really want to rename these %1 items?", prettyList.count()), + prettyList, + i18n("Rename"), + KGuiItem(i18nc("Verb", "&Rename"), QStringLiteral("edit-rename")), + KStandardGuiItem::cancel(), + QString(), options); + break; + case PrivilegeMkDir: + result = KMessageBox::warningContinueCancel( + widget, + i18n("This is a read only directory. Do you really want to create a folder here?"), + i18n("Create Dir"), + KGuiItem(i18n("Create Folder"), QStringLiteral("folder-new")), + KStandardGuiItem::cancel(), + QString(), options); + break; + case PrivilegeSymlink: + result = KMessageBox::warningContinueCancel( + widget, + i18n("This is a read only directory. Do you really want to create a symlink here?"), + i18n("Create symlink"), + KGuiItem(i18n("Create Symlink"), QStringLiteral("emblem-symbolic-link")), + KStandardGuiItem::cancel(), + QString(), options); + break; + default: + result = -1; + break; + } + return (result == KMessageBox::Continue); +} + int KIO::JobUiDelegate::requestMessageBox(KIO::JobUiDelegate::MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo,