diff --git a/src/core/job.cpp b/src/core/job.cpp --- a/src/core/job.cpp +++ b/src/core/job.cpp @@ -33,6 +33,7 @@ #include #include "slave.h" #include "scheduler.h" +#include "slavebase.h" using namespace KIO; @@ -262,6 +263,99 @@ return d_func()->m_outgoingMetaData; } +PrivilegeOperationStatus JobPrivate::tryAskPrivilegeOpConfirmation() +{ + if (m_confirmationAsked) { + return OperationAllowed; + } + + if (m_parentJob) { + if (!m_parentJob->d_func()->m_privilegeExecutionEnabled) { + return OperationNotAllowed; + } + + if (!m_parentJob->d_func()->m_confirmationAsked) { + PrivilegeOperationStatus opStatus = m_parentJob->d_func()->tryAskPrivilegeOpConfirmation(); + if (opStatus == OperationAllowed) { + // Copy meta-data from parent job + m_incomingMetaData.insert(QStringLiteral("TestData"), m_parentJob->queryMetaData(QStringLiteral("TestData"))); + m_confirmationAsked = true; + } + return opStatus; + } else { + return OperationAllowed; + } + } else { + // In case of SimpleJob like chmod, chown, etc. which don't accept JobFlags + if (!m_privilegeExecutionEnabled) { + return OperationNotAllowed; + } + } + + switch (m_operationType) { + case ChangeAttr: + m_caption = i18n("Change Attribute"); + m_message = i18n("Root privileges are required to change file attributes. " + "Do you want to continue?"); + break; + case Copy: + m_caption = i18n("Copy Files"); + m_message = i18n("Root privileges are required to complete the copy operation. " + "Do you want to continue?"); + break; + case Delete: + m_caption = i18n("Delete Files"); + m_message = i18n("Root privileges are required to complete the delete operation." + "However, doing so may damage your system. Do you want to continue?"); + break; + case MkDir: + m_caption = i18n("Create Folder"); + m_message = i18n("Root privileges are required to create this folder. " + "Do you want to continue?"); + break; + case Move: + m_caption = i18n("Move Items"); + m_message = i18n("Root privileges are required to complete the move operation. " + "Do you want to continue?"); + break; + case Rename: + m_caption = i18n("Rename"); + m_message = i18n("Root privileges are required to complete renaming. " + "Do you want to continue?"); + break; + case Symlink: + m_caption = i18n("Create Symlink"); + m_message = i18n("Root privileges are required to create a symlink. " + "Do you want to continue?"); + break; + case Transfer: + m_caption = i18n("Transfer data"); + m_message = i18n("Root privileges are required to complete transferring data. " + "Do you want to continue?"); + default: + break; + } + + if (m_outgoingMetaData.value(QStringLiteral("UnitTesting")) == QLatin1String("true")) { + // Set meta-data for the top-level job + m_incomingMetaData.insert(QStringLiteral("TestData"), QStringLiteral("PrivilegeOperationAllowed")); + return OperationAllowed; + } + + if (!m_uiDelegateExtension) { + return OperationNotAllowed; + } + + int status = m_uiDelegateExtension->requestMessageBox(JobUiDelegateExtension::WarningContinueCancel, + m_message, m_caption, i18n("Continue"), i18n("Cancel")); + m_confirmationAsked = true; + + if (status == SlaveBase::Cancel) { + return OperationCanceled; + } + return OperationAllowed; +} + ////////////////////////// class KIO::DirectCopyJobPrivate: public KIO::SimpleJobPrivate diff --git a/src/core/job_base.h b/src/core/job_base.h --- a/src/core/job_base.h +++ b/src/core/job_base.h @@ -301,7 +301,26 @@ * This is used by KIO::rename(), KIO::put(), KIO::file_copy(), KIO::file_move(), KIO::symlink(). * Otherwise the operation will fail with ERR_FILE_ALREADY_EXIST or ERR_DIR_ALREADY_EXIST. */ - Overwrite = 4 + Overwrite = 4, + + //TODO: Only one of these flags is required. Remove the one less stable. + /** + * When set, notifies the slave that application/job does not want privilege execution. + * So in case of failure due to insufficient privileges show an error without attempting + * to run the operation as root first. + * + * @since 5.43 + */ + NoPrivilegeExecution = 8, + + /** + * When set, notifies the slave that the application(job) wants the file operation to be + * performed as root user if there occurs a failure due to insufficient privilege. + * + * @since 5.43 + */ + PrivilegeExecution = 16 + }; Q_DECLARE_FLAGS(JobFlags, JobFlag) Q_DECLARE_OPERATORS_FOR_FLAGS(JobFlags) diff --git a/src/core/job_p.h b/src/core/job_p.h --- a/src/core/job_p.h +++ b/src/core/job_p.h @@ -34,6 +34,7 @@ #include #include #include "kiocoredebug.h" +#include "global.h" #define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream @@ -47,7 +48,8 @@ public: JobPrivate() : m_parentJob(nullptr), m_extraFlags(0), - m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension()) + m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension()), + m_privilegeExecutionEnabled(false), m_confirmationAsked(false) { } @@ -64,6 +66,18 @@ EF_KillCalled = (1 << 4) }; + enum FileOperationType { + ChangeAttr, // chmod(), chown(), setModificationTime() + Copy, + Delete, + MkDir, + Move, + Rename, + Symlink, + Transfer, // put() and get() + Other // if other file operation set message, caption inside the job. + }; + // Maybe we could use the QObject parent/child mechanism instead // (requires a new ctor, and moving the ctor code to some init()). Job *m_parentJob; @@ -73,7 +87,13 @@ MetaData m_outgoingMetaData; JobUiDelegateExtension *m_uiDelegateExtension; Job *q_ptr; + // For privilege operation + bool m_privilegeExecutionEnabled; + bool m_confirmationAsked; + QString m_caption, m_message; + FileOperationType m_operationType; + PrivilegeOperationStatus tryAskPrivilegeOpConfirmation(); void slotSpeed(KJob *job, unsigned long speed); static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest); @@ -172,6 +192,11 @@ */ void _k_slotSlaveInfoMessage(const QString &s); + /** + * Called when privilegeOperationRequested() is emitted by slave. + */ + void slotPrivilegeOperationRequested(); + /** * @internal * Called by the scheduler when a slave gets to diff --git a/src/core/simplejob.h b/src/core/simplejob.h --- a/src/core/simplejob.h +++ b/src/core/simplejob.h @@ -146,6 +146,7 @@ Q_PRIVATE_SLOT(d_func(), void slotSpeed(unsigned long speed)) Q_PRIVATE_SLOT(d_func(), void slotTotalSize(KIO::filesize_t data_size)) Q_PRIVATE_SLOT(d_func(), void _k_slotSlaveInfoMessage(const QString &)) + Q_PRIVATE_SLOT(d_func(), void slotPrivilegeOperationRequested()) Q_DECLARE_PRIVATE(SimpleJob) }; diff --git a/src/core/simplejob.cpp b/src/core/simplejob.cpp --- a/src/core/simplejob.cpp +++ b/src/core/simplejob.cpp @@ -148,6 +148,9 @@ q->connect(slave, SIGNAL(finished()), SLOT(slotFinished())); + q->connect(slave, SIGNAL(privilegeOperationRequested()), + SLOT(slotPrivilegeOperationRequested())); + if ((m_extraFlags & EF_TransferJobDataSent) == 0) { // this is a "get" job q->connect(slave, SIGNAL(totalSize(KIO::filesize_t)), SLOT(slotTotalSize(KIO::filesize_t))); @@ -337,6 +340,11 @@ Q_UNUSED(redirectionURL); } +void SimpleJobPrivate::slotPrivilegeOperationRequested() +{ + m_slave->send(MSG_PRIVILEGE_EXEC, QByteArray::number(tryAskPrivilegeOpConfirmation())); +} + ////////// SimpleJob *KIO::rmdir(const QUrl &url) { 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 @@ -77,8 +77,6 @@ #include #include -#include "fdreceiver.h" - Q_LOGGING_CATEGORY(KIO_FILE, "kf5.kio.kio_file") // Pseudo plugin class to embed meta data @@ -300,8 +298,8 @@ QT_STATBUF buff; if (QT_LSTAT(QFile::encodeName(path).constData(), &buff) == -1) { - bool dirCreated; - if (!(dirCreated = QDir().mkdir(path))) { + bool dirCreated = QDir().mkdir(path); + if (!dirCreated) { if (auto err = execWithElevatedPrivilege(MKDIR, path)) { if (!err.wasCanceled()) { //TODO: add access denied & disk full (or another reasons) handling (into Qt, possibly) @@ -673,10 +671,12 @@ } return; } else { +#ifndef Q_OS_WIN if ((_flags & KIO::Resume)) { execWithElevatedPrivilege(CHOWN, dest, getuid(), getgid()); QFile::setPermissions(dest, modeToQFilePermissions(filemode)); } +#endif } } }