diff --git a/autotests/kiotesthelper.h b/autotests/kiotesthelper.h --- a/autotests/kiotesthelper.h +++ b/autotests/kiotesthelper.h @@ -192,6 +192,15 @@ return m_messageBoxResult; } + bool askPrivilegeOperationConfirmation(const QString &warningCaption, + const QString &warningMessage, + const QString &warningDetails) override { + Q_UNUSED(warningCaption); + Q_UNUSED(warningMessage); + Q_UNUSED(warningDetails); + return false; + } + // yeah, public, for get and reset. int m_askFileRenameCalled; int m_askSkipCalled; diff --git a/src/core/jobuidelegateextension.h b/src/core/jobuidelegateextension.h --- a/src/core/jobuidelegateextension.h +++ b/src/core/jobuidelegateextension.h @@ -267,6 +267,18 @@ */ virtual void updateUrlInClipboard(const QUrl &src, const QUrl &dest); + /** + * Asks for confirmation to start a privileged file operation. + * + * @param warningCaption the caption of the warning dialog. + * @param warningMessage the message shown in the warning dialog. + * @param warningDetails the details of the current privileged operation. + * + * @since 5.60 + */ + virtual bool askPrivilegeOperationConfirmation(const QString &warningCaption, + const QString &warningMessage, + const QString &warningDetails) = 0; private: class Private; Private *const d; diff --git a/src/core/slavebase.h b/src/core/slavebase.h --- a/src/core/slavebase.h +++ b/src/core/slavebase.h @@ -309,7 +309,7 @@ const QString &buttonYes = QString(), const QString &buttonNo = QString(), const QString &dontAskAgainName = QString()); - + /** * Sets meta-data to be send to the application before the first * data() or finished() signal. @@ -961,6 +961,8 @@ MetaData mOutgoingMetaData; MetaData mIncomingMetaData; + QString privilegeOperationDetails; + enum VirtualFunctionId { AppConnectionMade = 0, GetFileSystemFreeSpace = 1 // KF6 TODO: Turn into a virtual method diff --git a/src/core/slavebase.cpp b/src/core/slavebase.cpp --- a/src/core/slavebase.cpp +++ b/src/core/slavebase.cpp @@ -132,19 +132,6 @@ QString m_warningMessage; int m_privilegeOperationStatus; - PrivilegeOperationStatus askConfirmation() - { - int status = q->messageBox(SlaveBase::WarningContinueCancel, m_warningMessage, m_warningCaption, QStringLiteral("Continue"), QStringLiteral("Cancel")); - switch (status) { - case SlaveBase::Continue: - return OperationAllowed; - case SlaveBase::Cancel: - return OperationCanceled; - default: - return OperationNotAllowed; - } - } - void updateTempAuthStatus() { #ifdef Q_OS_UNIX @@ -1507,7 +1494,8 @@ PrivilegeOperationStatus SlaveBase::requestPrivilegeOperation() { - if (d->m_privilegeOperationStatus == OperationNotAllowed) { + + if (d->m_privilegeOperationStatus == OperationNotAllowed) { QByteArray buffer; send(MSG_PRIVILEGE_EXEC); waitForAnswer(MSG_PRIVILEGE_EXEC, 0, buffer); @@ -1518,7 +1506,14 @@ if (metaData(QStringLiteral("UnitTesting")) != QLatin1String("true") && d->m_privilegeOperationStatus == OperationAllowed && !d->m_confirmationAsked) { - d->m_privilegeOperationStatus = d->askConfirmation(); + bool answer = false; + KIO_DATA << d->m_warningCaption << d->m_warningMessage << privilegeOperationDetails; + send(INF_PRIVILEGE_CONF, data); + if (waitForAnswer(INF_PRIVILEGE_CONF, 0, data) != -1) { + QDataStream stream(data); + stream >> answer; + } + d->m_privilegeOperationStatus = answer ? OperationAllowed : OperationCanceled;; d->m_confirmationAsked = true; } diff --git a/src/core/slaveinterface.h b/src/core/slaveinterface.h --- a/src/core/slaveinterface.h +++ b/src/core/slaveinterface.h @@ -57,7 +57,8 @@ INF_META_DATA, INF_NETWORK_STATUS, INF_MESSAGEBOX, - INF_POSITION + INF_POSITION, + INF_PRIVILEGE_CONF // add new ones here once a release is done, to avoid breaking binary compatibility }; @@ -123,6 +124,8 @@ */ void sendMessageBoxAnswer(int result); + void sendPrivilegeConfirmationAnswer(bool conf); + void setOffset(KIO::filesize_t offset); KIO::filesize_t offset() const; @@ -179,6 +182,8 @@ const QString &buttonYes, const QString &buttonNo, const QString &dontAskAgainName); + void requestPrivilegeConfirmation(const QString &caption, const QString &text, const QString &details); + // I need to identify the slaves void requestNetwork(const QString &, const QString &); void dropNetwork(const QString &, const QString &); diff --git a/src/core/slaveinterface.cpp b/src/core/slaveinterface.cpp --- a/src/core/slaveinterface.cpp +++ b/src/core/slaveinterface.cpp @@ -323,6 +323,13 @@ case MSG_PRIVILEGE_EXEC: emit privilegeOperationRequested(); break; + case INF_PRIVILEGE_CONF: { + QString caption, message, details; + stream >> caption >> message >> details; + requestPrivilegeConfirmation(caption, message, details); + break; + } + default: qCWarning(KIO_CORE) << "Slave sends unknown command (" << _cmd << "), dropping slave"; return false; @@ -388,6 +395,22 @@ // qDebug() << "message box answer" << result; } +void SlaveInterface::sendPrivilegeConfirmationAnswer(bool conf) +{ + Q_D(SlaveInterface); + if (!d->connection) { + return; + } + + if (d->connection->suspended()) { + d->connection->resume(); + } + QByteArray packedArgs; + QDataStream stream(&packedArgs, QIODevice::WriteOnly); + stream << conf; + d->connection->sendnow(INF_PRIVILEGE_CONF, packedArgs); +} + void SlaveInterface::messageBox(int type, const QString &text, const QString &_caption, const QString &buttonYes, const QString &buttonNo) { @@ -403,6 +426,7 @@ } QHash data; + data.insert(UserNotificationHandler::MSG_TYPE, type); data.insert(UserNotificationHandler::MSG_TEXT, text); data.insert(UserNotificationHandler::MSG_CAPTION, caption); data.insert(UserNotificationHandler::MSG_YES_BUTTON_TEXT, buttonYes); @@ -427,7 +451,23 @@ data.insert(UserNotificationHandler::MSG_META_DATA, d->sslMetaData.toVariant()); } - globalUserNotificationHandler()->requestMessageBox(this, type, data); + globalUserNotificationHandler()->createRequest(this, UserNotificationHandler::REQUEST_MSGBOX, data); +} + +void SlaveInterface::requestPrivilegeConfirmation(const QString &caption, const QString &text, const QString &details) +{ + Q_D(SlaveInterface); + if (d->connection) { + d->connection->suspend(); + } + + QHash data; + data.insert(UserNotificationHandler::MSG_TYPE, -1); + data.insert(UserNotificationHandler::MSG_TEXT, text); + data.insert(UserNotificationHandler::MSG_CAPTION, caption); + data.insert(UserNotificationHandler::MSG_DETAILS, details); + globalUserNotificationHandler()->createRequest(this, UserNotificationHandler::REQUEST_PRIVILEGE_CONFIRMATION, data); + } void SlaveInterfacePrivate::slotHostInfo(const QHostInfo &info) diff --git a/src/core/usernotificationhandler.cpp b/src/core/usernotificationhandler.cpp --- a/src/core/usernotificationhandler.cpp +++ b/src/core/usernotificationhandler.cpp @@ -36,7 +36,9 @@ key += slave->host(); key += slave->port(); key += QLatin1Char('-'); - key += type; + key += (int)type; + key += QLatin1Char('-'); + key += msgType; } return key; } @@ -51,12 +53,13 @@ qDeleteAll(m_pendingRequests); } -void UserNotificationHandler::requestMessageBox(SlaveInterface *iface, int type, const QHash &data) +void UserNotificationHandler::createRequest(SlaveInterface *iface, RequestType type, const QHash &data) { Request *r = new Request; r->type = type; r->slave = qobject_cast(iface); r->data = data; + r->msgType = r->data.value(MSG_TYPE).toInt(); m_pendingRequests.append(r); if (m_pendingRequests.count() == 1) { @@ -70,7 +73,7 @@ return; } - int result = -1; + QVariant result; Request *r = m_pendingRequests.first(); if (r->slave) { @@ -85,24 +88,35 @@ if (!delegateExtension) delegateExtension = KIO::defaultJobUiDelegateExtension(); if (delegateExtension) { - const JobUiDelegateExtension::MessageBoxType type = static_cast(r->type); - result = delegateExtension->requestMessageBox(type, - r->data.value(MSG_TEXT).toString(), - r->data.value(MSG_CAPTION).toString(), - r->data.value(MSG_YES_BUTTON_TEXT).toString(), - r->data.value(MSG_NO_BUTTON_TEXT).toString(), - r->data.value(MSG_YES_BUTTON_ICON).toString(), - r->data.value(MSG_NO_BUTTON_ICON).toString(), - r->data.value(MSG_DONT_ASK_AGAIN).toString(), - r->data.value(MSG_META_DATA).toMap()); + if (r->type == REQUEST_MSGBOX) { + result = delegateExtension->requestMessageBox(static_cast(r->msgType), + r->data.value(MSG_TEXT).toString(), + r->data.value(MSG_CAPTION).toString(), + r->data.value(MSG_YES_BUTTON_TEXT).toString(), + r->data.value(MSG_NO_BUTTON_TEXT).toString(), + r->data.value(MSG_YES_BUTTON_ICON).toString(), + r->data.value(MSG_NO_BUTTON_ICON).toString(), + r->data.value(MSG_DONT_ASK_AGAIN).toString(), + r->data.value(MSG_META_DATA).toMap()); + } else if (r->type == REQUEST_PRIVILEGE_CONFIRMATION) { + result = delegateExtension->askPrivilegeOperationConfirmation(r->data.value(MSG_CAPTION).toString(), + r->data.value(MSG_TEXT).toString(), + r->data.value(MSG_DETAILS).toString()); + } } - m_cachedResults.insert(key, new int(result)); + m_cachedResults.insert(key, new QVariant(result)); } } else { qCWarning(KIO_CORE) << "Cannot prompt user because the requesting ioslave died!" << r->slave; } - r->slave->sendMessageBoxAnswer(result); + if (r->type == REQUEST_MSGBOX) { + int res = result.isValid() ? result.toInt() : -1; + r->slave->sendMessageBoxAnswer(res); + } else if (r->type == REQUEST_PRIVILEGE_CONFIRMATION) { + bool conf = result.isValid() ? result.toBool() : false; + r->slave->sendPrivilegeConfirmationAnswer(conf); + } m_pendingRequests.removeFirst(); delete r; diff --git a/src/core/usernotificationhandler_p.h b/src/core/usernotificationhandler_p.h --- a/src/core/usernotificationhandler_p.h +++ b/src/core/usernotificationhandler_p.h @@ -35,37 +35,45 @@ Q_OBJECT public: enum MessageBoxDataType { + MSG_TYPE, MSG_TEXT, MSG_CAPTION, + MSG_DETAILS, MSG_YES_BUTTON_TEXT, MSG_NO_BUTTON_TEXT, MSG_YES_BUTTON_ICON, MSG_NO_BUTTON_ICON, MSG_DONT_ASK_AGAIN, MSG_META_DATA }; + enum RequestType { + REQUEST_MSGBOX, + REQUEST_PRIVILEGE_CONFIRMATION + }; + class Request { public: QString key() const; - int type; + int msgType; + RequestType type; QPointer slave; QHash data; }; UserNotificationHandler(QObject *parent = nullptr); virtual ~UserNotificationHandler(); - void requestMessageBox(SlaveInterface *iface, int type, const QHash &data); + void createRequest(SlaveInterface *iface, RequestType type, const QHash &data); private Q_SLOTS: void processRequest(); private: - QCache m_cachedResults; + QCache m_cachedResults; QList m_pendingRequests; }; } 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 @@ -78,6 +78,68 @@ return QStringLiteral("%1/filehelper%2%3").arg(runtimeDir).arg(KRandom::randomString(6)).arg(getpid()); } +static QString actionDetails(ActionType actionType, QVariantList args) +{ + QString action, detail; + switch(actionType) { + case CHMOD: { + action = i18n("Change File Permissions"); + detail = i18n("New Permissions : %1", args[1].toInt()); + break; + } + case CHOWN: { + action = i18n("Change File Owner"); + detail = i18n("New Owner : UID=%1, GID=%2", args[1].toInt(), args[2].toInt()); + break; + } + case DEL: { + action = i18n("Remove File"); + break; + } + case RMDIR: { + action = i18n("Remove Directory"); + break; + } + case MKDIR: { + action = i18n("Create Directory"); + detail = i18n("Directory Permissions: %1", args[1].toInt()); + break; + } + case OPEN: { + action = i18n("Open File"); + break; + } + case OPENDIR: { + action = i18n("Open Directory"); + break; + } + + case RENAME: { + action = i18n("Rename"); + detail = i18n("New Filename: %1", args[1].toString()); + break; + } + case SYMLINK: { + action = i18n("Create Symlink"); + detail = i18n("Target: %1", args[1].toString()); + break; + } + case UTIME: { + action = i18n("Change Timestamp"); + break; + } + default: { + action = i18n("Unknown Action"); + break; + } + } + + const QString metadata = i18n("Action: %1\n" + "Source: %2\n" + "%3", action, args[0].toString(), detail); + return metadata; +} + bool FileProtocol::privilegeOperationUnitTestMode() { return (metaData(QStringLiteral("UnitTesting")) == QLatin1String("true")) @@ -842,6 +904,10 @@ return PrivilegeOperationReturnValue::failure(errcode); } + if (privilegeOperationDetails.isEmpty()) { + privilegeOperationDetails = actionDetails(action, args); + } + KIO::PrivilegeOperationStatus opStatus = requestPrivilegeOperation(); if (opStatus != KIO::OperationAllowed) { if (opStatus == KIO::OperationCanceled) { diff --git a/src/widgets/jobuidelegate.h b/src/widgets/jobuidelegate.h --- a/src/widgets/jobuidelegate.h +++ b/src/widgets/jobuidelegate.h @@ -163,6 +163,22 @@ */ void updateUrlInClipboard(const QUrl &src, const QUrl &dest) override; + /** + * Asks for confirmation to start a privileged file operation. + * + * @param warningCaption the caption of the warning dialog. + * @param warningMessage the message shown in the warning dialog. + * @param warningDetails the details of the current privileged operation. + * + * @since 5.60 + * + * @internal + */ + + bool askPrivilegeOperationConfirmation(const QString &warningCaption, + const QString &warningMessage, + const QString &warningDetails) override; + private: class Private; Private *const d; diff --git a/src/widgets/jobuidelegate.cpp b/src/widgets/jobuidelegate.cpp --- a/src/widgets/jobuidelegate.cpp +++ b/src/widgets/jobuidelegate.cpp @@ -379,6 +379,17 @@ return result; } +bool KIO::JobUiDelegate::askPrivilegeOperationConfirmation(const QString &warningCaption, + const QString &warningMessage, + const QString &warningDetails) +{ + int result = KMessageBox::warningContinueCancelDetailed( + window(), warningMessage, warningDetails, warningCaption, + KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), + KMessageBox::Options(KMessageBox::Dangerous | KMessageBox::WindowModal)); + return (result == KMessageBox::Continue); +} + KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) { if (qobject_cast(qApp)) {