diff --git a/autotests/deletejobtest.h b/autotests/deletejobtest.h --- a/autotests/deletejobtest.h +++ b/autotests/deletejobtest.h @@ -30,6 +30,7 @@ void deleteFileTestCase(); void deleteDirectoryTestCase_data() const; void deleteDirectoryTestCase(); + void deleteWithElevatedPrivilegeTestCase(); private: void createEmptyTestFiles(const QStringList &fileNames, const QString &path) const; diff --git a/autotests/deletejobtest.cpp b/autotests/deletejobtest.cpp --- a/autotests/deletejobtest.cpp +++ b/autotests/deletejobtest.cpp @@ -97,6 +97,41 @@ } } +void DeleteJobTest::deleteWithElevatedPrivilegeTestCase() +{ + // deleting files from read-only folder + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + createEmptyTestFiles(QStringList() << "file1.txt", tempDir.path()); + const QString filepath = tempDir.path() + QDir::separator() + "file1.txt"; + + QFile::Permissions permissions = QFileDevice::ReadOwner | QFileDevice::ExeOwner; + bool ok = QFile::setPermissions(tempDir.path(), permissions); + QVERIFY(ok); + + KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(filepath), KIO::HideProgressInfo); + job->addMetaData("ShowInternalWarning", "true"); + job->addMetaData("UnitTesting", "true"); + job->setUiDelegate(nullptr); + QSignalSpy spy(job, SIGNAL(result(KJob*))); + QVERIFY(spy.isValid()); + QVERIFY(spy.wait(100000)); + QCOMPARE(job->error(), KJOB_NO_ERROR); + + QStringList testData = job->queryMetaData("TestData").split(','); + QCOMPARE(testData.first(), QStringLiteral("org.kde.kio.file.del")); + QCOMPARE(testData.at(1), QString::number(true)); + QCOMPARE(testData.last(), QStringLiteral("warningshown")); + + permissions |= QFileDevice::WriteOwner; + ok = QFile::setPermissions(tempDir.path(), permissions); + QVERIFY(ok); + + QVERIFY(QFile::remove(filepath)); + QVERIFY(!QFile::exists(filepath)); +} + void DeleteJobTest::createEmptyTestFiles(const QStringList &fileNames, const QString &path) const { QStringListIterator iterator(fileNames); 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 @@ -1330,8 +1330,16 @@ } else { //qDebug() << "QFile::remove" << itemPath; if (!QFile::remove(itemPath)) { - error(KIO::ERR_CANNOT_DELETE, itemPath); - return false; + if (errno == EACCES || errno == EPERM) { + if (!execWithElevatedPrivilege(QStringLiteral("del"), QStringLiteral("delete_file"), + itemPath, WARN_PRIVILEGE_DEL)) { + error(KIO::ERR_ACCESS_DENIED, itemPath); + return false; + } + } else { + error(KIO::ERR_CANNOT_DELETE, itemPath); + return false; + } } } } @@ -1339,10 +1347,19 @@ Q_FOREACH (const QString &itemPath, dirsToDelete) { //qDebug() << "QDir::rmdir" << itemPath; if (!dir.rmdir(itemPath)) { - error(KIO::ERR_CANNOT_DELETE, itemPath); - return false; + if (errno == EACCES || errno == EPERM) { + if (!execWithElevatedPrivilege(QStringLiteral("del"), QStringLiteral("delete_dir"), + itemPath, WARN_PRIVILEGE_RMDIR)) { + error(KIO::ERR_ACCESS_DENIED, itemPath); + return false; + } + } else { + error(KIO::ERR_CANNOT_DELETE, itemPath); + return false; + } } } + return true; } 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 @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -527,13 +528,18 @@ if (unlink(_path.data()) == -1) { if ((errno == EACCES) || (errno == EPERM)) { - error(KIO::ERR_ACCESS_DENIED, path); + if (!execWithElevatedPrivilege(QStringLiteral("del"), QStringLiteral("delete_file"), + _path, WARN_PRIVILEGE_DEL)) { + error(KIO::ERR_ACCESS_DENIED, path); + return; + } } else if (errno == EISDIR) { error(KIO::ERR_IS_DIRECTORY, path); + return; } else { error(KIO::ERR_CANNOT_DELETE, path); + return; } - return; } } else { @@ -549,8 +555,11 @@ } if (QT_RMDIR(_path.data()) == -1) { if ((errno == EACCES) || (errno == EPERM)) { - error(KIO::ERR_ACCESS_DENIED, path); - return; + if (!execWithElevatedPrivilege(QStringLiteral("del"), QStringLiteral("delete_dir"), + path, WARN_PRIVILEGE_RMDIR)) { + error(KIO::ERR_ACCESS_DENIED, path); + return; + } } else { // qDebug() << "could not rmdir " << perror; error(KIO::ERR_CANNOT_RMDIR, path); @@ -559,6 +568,7 @@ } } + endPrivilegeOperation(); finished(); } diff --git a/src/ioslaves/file/kauth/file.actions b/src/ioslaves/file/kauth/file.actions --- a/src/ioslaves/file/kauth/file.actions +++ b/src/ioslaves/file/kauth/file.actions @@ -0,0 +1,5 @@ +[org.kde.kio.file.del] +Name=Delete items as a privileged user. +Description=Root privileges are required to complete the delete operation. Be careful, this operation cannot be reverted. +Policy=auth_admin +Persistence=session diff --git a/src/ioslaves/file/kauth/helper.h b/src/ioslaves/file/kauth/helper.h --- a/src/ioslaves/file/kauth/helper.h +++ b/src/ioslaves/file/kauth/helper.h @@ -29,6 +29,9 @@ class Helper : public QObject { Q_OBJECT + +public Q_SLOTS: + ActionReply del(const QVariantMap& args); }; #endif diff --git a/src/ioslaves/file/kauth/helper.cpp b/src/ioslaves/file/kauth/helper.cpp --- a/src/ioslaves/file/kauth/helper.cpp +++ b/src/ioslaves/file/kauth/helper.cpp @@ -19,5 +19,35 @@ ***/ #include "helper.h" +#include +#include + +#include +#include +#include +#include +#include + + +ActionReply Helper::del(const QVariantMap &args) +{ + ActionReply reply; + const QByteArray path = args["arguments"].toByteArray(); + const QString subAction = args["subaction"].toString(); + + if (subAction == "delete_file") { + unlink(path.constData()); + } else if(subAction == "delete_dir") { + QDir dir; + dir.rmdir(path.constData()); + } + if (errno) { + if (errno == EACCES || errno == EPERM) { + reply.setError(KIO::ERR_ACCESS_DENIED); + } + } + + return reply; +} KAUTH_HELPER_MAIN("org.kde.kio.file", Helper)