diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index eee232d55..221f6e502 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -26,6 +26,7 @@ #include "deletejob.h" #include "folderman.h" #include "folder.h" +#include "encryptfolderjob.h" #include "theme.h" #include "common/syncjournalfilerecord.h" #include "syncengine.h" @@ -82,6 +83,11 @@ // The second number should be changed when there are new features. #define MIRALL_SOCKET_API_VERSION "1.1" +namespace { +constexpr auto encryptJobPropertyFolder = "folder"; +constexpr auto encryptJobPropertyPath = "path"; +} + namespace { const QLatin1Char RecordSeparator() @@ -501,6 +507,61 @@ void SocketApi::processFileActivityRequest(const QString &localFile) emit fileActivityCommandReceived(fileData.localPath); } +void SocketApi::processEncryptRequest(const QString &localFile) +{ + Q_ASSERT(QFileInfo(localFile).isDir()); + + const auto fileData = FileData::get(localFile); + + const auto folder = fileData.folder; + Q_ASSERT(folder); + + const auto account = folder->accountState()->account(); + Q_ASSERT(account); + + const auto rec = fileData.journalRecord(); + Q_ASSERT(rec.isValid()); + + if (!account->e2e() || account->e2e()->_mnemonic.isEmpty()) { + const int ret = QMessageBox::critical(nullptr, + tr("Failed to encrypt folder at \"%1\"").arg(fileData.folderRelativePath), + tr("The account %1 does not have end-to-end encryption configured. " + "Please configure this in your account settings to enable folder encryption.").arg(account->prettyName())); + Q_UNUSED(ret) + return; + } + + auto path = rec._path; + // Folder records have directory paths in Foo/Bar/ convention... + // But EncryptFolderJob expects directory path Foo/Bar convention + auto choppedPath = path; + if (choppedPath.endsWith('/') && choppedPath != QStringLiteral("/")) { + choppedPath.chop(1); + } + if (choppedPath.startsWith('/') && choppedPath != QStringLiteral("/")) { + choppedPath = choppedPath.mid(1); + } + + auto job = new OCC::EncryptFolderJob(account, folder->journalDb(), choppedPath, rec.numericFileId(), this); + connect(job, &OCC::EncryptFolderJob::finished, this, [fileData, job](const int status) { + if (status == OCC::EncryptFolderJob::Error) { + const int ret = QMessageBox::critical(nullptr, + tr("Failed to encrypt folder"), + tr("Could not encrypt the following folder: \"%1\". \n\n" + "Server replied with error: %2").arg(fileData.folderRelativePath, job->errorString())); + Q_UNUSED(ret) + } else { + const int ret = QMessageBox::information(nullptr, + tr("Folder encrypted successfully").arg(fileData.folderRelativePath), + tr("The following folder was encrypted successfully: \"%1\"").arg(fileData.folderRelativePath)); + Q_UNUSED(ret) + } + }); + job->setProperty(encryptJobPropertyFolder, QVariant::fromValue(folder)); + job->setProperty(encryptJobPropertyPath, QVariant::fromValue(path)); + job->start(); +} + void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener) { auto theme = Theme::instance(); @@ -603,6 +664,13 @@ void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *liste processFileActivityRequest(localFile); } +void SocketApi::command_ENCRYPT(const QString &localFile, SocketListener *listener) +{ + Q_UNUSED(listener); + + processEncryptRequest(localFile); +} + void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener) { processShareRequest(localFile, listener); @@ -1093,6 +1161,39 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi //listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email …")); } +void SocketApi::sendEncryptFolderCommandMenuEntries(const QFileInfo &fileInfo, + const FileData &fileData, + const bool isE2eEncryptedPath, + const OCC::SocketListener* const listener) const +{ + if (!listener || + !fileData.folder || + !fileData.folder->accountState() || + !fileData.folder->accountState()->account() || + !fileData.folder->accountState()->account()->capabilities().clientSideEncryptionAvailable() || + !fileInfo.isDir() || + isE2eEncryptedPath) { + return; + } + + bool anyAncestorEncrypted = false; + auto ancestor = fileData.parentFolder(); + while (ancestor.journalRecord().isValid()) { + if (ancestor.journalRecord()._isE2eEncrypted) { + anyAncestorEncrypted = true; + break; + } + + ancestor = ancestor.parentFolder(); + } + + if (!anyAncestorEncrypted) { + const auto isOnTheServer = fileData.journalRecord().isValid(); + const auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); + listener->sendMessage(QStringLiteral("MENU_ITEM:ENCRYPT") + flagString + tr("Encrypt")); + } +} + void SocketApi::sendLockFileCommandMenuEntries(const QFileInfo &fileInfo, Folder* const syncFolder, const FileData &fileData, @@ -1215,6 +1316,7 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe const QFileInfo fileInfo(fileData.localPath); sendLockFileInfoMenuEntries(fileInfo, syncFolder, fileData, listener, record); + if (!fileInfo.isDir()) { listener->sendMessage(QLatin1String("MENU_ITEM:ACTIVITY") + flagString + tr("Activity")); } @@ -1227,6 +1329,7 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser")); } + sendEncryptFolderCommandMenuEntries(fileInfo, fileData, isE2eEncryptedPath, listener); sendLockFileCommandMenuEntries(fileInfo, syncFolder, fileData, listener); sendSharingContextMenuOptions(fileData, listener, !isE2eEncryptedPath); diff --git a/src/gui/socketapi/socketapi.h b/src/gui/socketapi/socketapi.h index 5cedd53d7..0806158a2 100644 --- a/src/gui/socketapi/socketapi.h +++ b/src/gui/socketapi/socketapi.h @@ -104,6 +104,7 @@ private: void processShareRequest(const QString &localFile, SocketListener *listener); void processLeaveShareRequest(const QString &localFile, SocketListener *listener); void processFileActivityRequest(const QString &localFile); + void processEncryptRequest(const QString &localFile); Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, OCC::SocketListener *listener); Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, OCC::SocketListener *listener); @@ -114,6 +115,7 @@ private: // The context menu actions Q_INVOKABLE void command_ACTIVITY(const QString &localFile, OCC::SocketListener *listener); + Q_INVOKABLE void command_ENCRYPT(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_SHARE(const QString &localFile, OCC::SocketListener *listener); Q_INVOKABLE void command_LEAVESHARE(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, OCC::SocketListener *listener); @@ -151,8 +153,15 @@ private: // Sends the context menu options relating to sharing to listener void sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener, bool enabled); - void - sendLockFileCommandMenuEntries(const QFileInfo &fileInfo, Folder *const syncFolder, const FileData &fileData, const SocketListener *const listener) const; + void sendEncryptFolderCommandMenuEntries(const QFileInfo &fileInfo, + const FileData &fileData, + const bool isE2eEncryptedPath, + const OCC::SocketListener* const listener) const; + + void sendLockFileCommandMenuEntries(const QFileInfo &fileInfo, + Folder *const syncFolder, + const FileData &fileData, + const SocketListener *const listener) const; void sendLockFileInfoMenuEntries(const QFileInfo &fileInfo, Folder* const syncFolder, diff --git a/src/libsync/clientsideencryptionjobs.cpp b/src/libsync/clientsideencryptionjobs.cpp index 71bb8a510..e99af0948 100644 --- a/src/libsync/clientsideencryptionjobs.cpp +++ b/src/libsync/clientsideencryptionjobs.cpp @@ -177,7 +177,7 @@ bool UnlockEncryptFolderApiJob::finished() if (retCode != 200) { qCInfo(lcCseJob()) << "error unlocking file" << path() << errorString() << retCode; qCInfo(lcCseJob()) << "Full Error Log" << reply()->readAll(); - emit error(_fileId, retCode); + emit error(_fileId, retCode, errorString()); return true; } emit success(_fileId); @@ -241,7 +241,7 @@ bool LockEncryptFolderApiJob::finished() int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (retCode != 200) { qCInfo(lcCseJob()) << "error locking file" << path() << errorString() << retCode; - emit error(_fileId, retCode); + emit error(_fileId, retCode, errorString()); return true; } @@ -282,7 +282,7 @@ bool SetEncryptionFlagApiJob::finished() emit success(_fileId); } else { qCInfo(lcCseJob()) << "Setting the encrypted flag failed with" << path() << errorString() << retCode; - emit error(_fileId, retCode); + emit error(_fileId, retCode, errorString()); } return true; } diff --git a/src/libsync/clientsideencryptionjobs.h b/src/libsync/clientsideencryptionjobs.h index 1ab38fa5c..c32038292 100644 --- a/src/libsync/clientsideencryptionjobs.h +++ b/src/libsync/clientsideencryptionjobs.h @@ -129,7 +129,9 @@ protected: signals: void success(const QByteArray &fileId); - void error(const QByteArray &fileId, int httpReturnCode); + void error(const QByteArray &fileId, + const int httpReturnCode, + const QString &errorMessage); private: QByteArray _fileId; @@ -150,7 +152,9 @@ protected: signals: void success(const QByteArray& fileId, const QByteArray& token); - void error(const QByteArray& fileId, int httpdErrorCode); + void error(const QByteArray& fileId, + const int httpErrorCode, + const QString &errorMessage); private: QByteArray _fileId; @@ -175,7 +179,9 @@ protected: signals: void success(const QByteArray& fileId); - void error(const QByteArray& fileId, int httpReturnCode); + void error(const QByteArray& fileId, + const int httpReturnCode, + const QString &errorMessage); private: QByteArray _fileId; diff --git a/src/libsync/encryptfolderjob.cpp b/src/libsync/encryptfolderjob.cpp index e9a8f4caa..eadcb9357 100644 --- a/src/libsync/encryptfolderjob.cpp +++ b/src/libsync/encryptfolderjob.cpp @@ -70,9 +70,12 @@ void EncryptFolderJob::slotEncryptionFlagSuccess(const QByteArray &fileId) lockJob->start(); } -void EncryptFolderJob::slotEncryptionFlagError(const QByteArray &fileId, int httpErrorCode) +void EncryptFolderJob::slotEncryptionFlagError(const QByteArray &fileId, + const int httpErrorCode, + const QString &errorMessage) { qDebug() << "Error on the encryption flag of" << fileId << "HTTP code:" << httpErrorCode; + _errorString = errorMessage; emit finished(Error); } @@ -108,7 +111,7 @@ void EncryptFolderJob::slotUploadMetadataSuccess(const QByteArray &folderId) unlockJob->start(); } -void EncryptFolderJob::slotUpdateMetadataError(const QByteArray &folderId, int httpReturnCode) +void EncryptFolderJob::slotUpdateMetadataError(const QByteArray &folderId, const int httpReturnCode) { Q_UNUSED(httpReturnCode); @@ -120,15 +123,21 @@ void EncryptFolderJob::slotUpdateMetadataError(const QByteArray &folderId, int h unlockJob->start(); } -void EncryptFolderJob::slotLockForEncryptionError(const QByteArray &fileId, int httpErrorCode) +void EncryptFolderJob::slotLockForEncryptionError(const QByteArray &fileId, + const int httpErrorCode, + const QString &errorMessage) { qCInfo(lcEncryptFolderJob()) << "Locking error for" << fileId << "HTTP code:" << httpErrorCode; + _errorString = errorMessage; emit finished(Error); } -void EncryptFolderJob::slotUnlockFolderError(const QByteArray &fileId, int httpErrorCode) +void EncryptFolderJob::slotUnlockFolderError(const QByteArray &fileId, + const int httpErrorCode, + const QString &errorMessage) { qCInfo(lcEncryptFolderJob()) << "Unlocking error for" << fileId << "HTTP code:" << httpErrorCode; + _errorString = errorMessage; emit finished(Error); } void EncryptFolderJob::slotUnlockFolderSuccess(const QByteArray &fileId) diff --git a/src/libsync/encryptfolderjob.h b/src/libsync/encryptfolderjob.h index 1a8646f64..bad5a8976 100644 --- a/src/libsync/encryptfolderjob.h +++ b/src/libsync/encryptfolderjob.h @@ -40,13 +40,13 @@ signals: private slots: void slotEncryptionFlagSuccess(const QByteArray &folderId); - void slotEncryptionFlagError(const QByteArray &folderId, int httpReturnCode); + void slotEncryptionFlagError(const QByteArray &folderId, const int httpReturnCode, const QString &errorMessage); void slotLockForEncryptionSuccess(const QByteArray &folderId, const QByteArray &token); - void slotLockForEncryptionError(const QByteArray &folderId, int httpReturnCode); + void slotLockForEncryptionError(const QByteArray &folderId, const int httpReturnCode, const QString &errorMessage); void slotUnlockFolderSuccess(const QByteArray &folderId); - void slotUnlockFolderError(const QByteArray &folderId, int httpReturnCode); + void slotUnlockFolderError(const QByteArray &folderId, const int httpReturnCode, const QString &errorMessage); void slotUploadMetadataSuccess(const QByteArray &folderId); - void slotUpdateMetadataError(const QByteArray &folderId, int httpReturnCode); + void slotUpdateMetadataError(const QByteArray &folderId, const int httpReturnCode); private: AccountPtr _account;