From: Kevin Ottens Date: Mon, 30 Nov 2020 17:14:05 +0000 (+0100) Subject: Start hooking up E2EE to the new discovery algorithm X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~21^2~468^2~49 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=1d07af07a562da76c17304e7addda7f2d767bc1c;p=nextcloud-desktop.git Start hooking up E2EE to the new discovery algorithm Now we pull the encrypted metadata during the discovery which is a better approach than before. This shall remove the need for some of the deep propfinds we have been using so far. It already simplifies the code a bit for the download scenario. Signed-off-by: Kevin Ottens --- diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 89bdf70ce..75eaa4143 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -161,6 +161,11 @@ void ProcessDirectoryJob::process() } } + // On the server the path is mangled in case of E2EE + if (!e.serverEntry.e2eMangledName.isEmpty()) { + path._server = e.serverEntry.e2eMangledName; + } + // If the filename starts with a . we consider it a hidden file // For windows, the hidden state is also discovered within the vio // local stat function. @@ -306,7 +311,9 @@ void ProcessDirectoryJob::processFile(PathTuple path, << " | perm: " << dbEntry._remotePerm << "//" << serverEntry.remotePerm << " | fileid: " << dbEntry._fileId << "//" << serverEntry.fileId << " | inode: " << dbEntry._inode << "/" << localEntry.inode << "/" - << " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile); + << " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile) + << " | e2ee: " << dbEntry._isE2eEncrypted << "/" << serverEntry.isE2eEncrypted + << " | e2eeMangledName: " << dbEntry._e2eMangledName << "/" << serverEntry.e2eMangledName; if (_discoveryData->isRenamed(path._original)) { qCDebug(lcDisco) << "Ignoring renamed"; @@ -391,6 +398,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( item->_etag = serverEntry.etag; item->_directDownloadUrl = serverEntry.directDownloadUrl; item->_directDownloadCookies = serverEntry.directDownloadCookies; + item->_encryptedFileName = serverEntry.e2eMangledName; // Check for missing server data { diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 24b4fb155..d79497df9 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -16,6 +16,8 @@ #include "discovery.h" #include "account.h" +#include "clientsideencryptionjobs.h" + #include "common/asserts.h" #include "common/checksums.h" @@ -332,6 +334,7 @@ DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &accou , _ignoredFirst(false) , _isRootPath(false) , _isExternalStorage(false) + , _isE2eEncrypted(false) { } @@ -356,6 +359,9 @@ void DiscoverySingleDirectoryJob::start() // Server older than 10.0 have performances issue if we ask for the share-types on every PROPFIND props << "http://owncloud.org/ns:share-types"; } + if (_account->capabilities().clientSideEncryptionAvailable()) { + props << "http://nextcloud.org/ns:is-encrypted"; + } lsColJob->setProperties(props); @@ -416,6 +422,8 @@ static void propertyMapToRemoteInfo(const QMap &map, RemoteInf // Piggy back on the persmission field result.remotePerm.setPermission(RemotePermissions::IsShared); } + } else if (property == "is-encrypted" && value == QStringLiteral("1")) { + result.isE2eEncrypted = true; } } } @@ -437,6 +445,13 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(const QString &fi _dataFingerprint = "[empty]"; } } + if (map.contains("id")) { + _fileId = map.value("id").toUtf8(); + } + if (map.contains("is-encrypted") && map.value("is-encrypted") == QStringLiteral("1")) { + _isE2eEncrypted = true; + Q_ASSERT(!_fileId.isEmpty()); + } } else { RemoteInfo result; @@ -483,6 +498,9 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot() emit finished(HttpError{ 0, _error }); deleteLater(); return; + } else if (_isE2eEncrypted) { + fetchE2eMetadata(); + return; } emit etag(_firstEtag); emit finished(_results); @@ -502,4 +520,57 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot(QNetworkReply *r) emit finished(HttpError{ httpCode, msg }); deleteLater(); } + +void DiscoverySingleDirectoryJob::fetchE2eMetadata() +{ + auto job = new GetMetadataApiJob(_account, _fileId); + connect(job, &GetMetadataApiJob::jsonReceived, + this, &DiscoverySingleDirectoryJob::metadataReceived); + connect(job, &GetMetadataApiJob::error, + this, &DiscoverySingleDirectoryJob::metadataError); + job->start(); +} + +void DiscoverySingleDirectoryJob::metadataReceived(const QJsonDocument &json, int statusCode) +{ + qCDebug(lcDiscovery) << "Metadata received, applying it to the result list"; + Q_ASSERT(_subPath.startsWith('/')); + + const auto metadata = FolderMetadata(_account, json.toJson(QJsonDocument::Compact), statusCode); + const auto encryptedFiles = metadata.files(); + + const auto findEncryptedFile = [=](const QString &name) { + const auto it = std::find_if(std::cbegin(encryptedFiles), std::cend(encryptedFiles), [=](const EncryptedFile &file) { + return file.encryptedFilename == name; + }); + if (it == std::cend(encryptedFiles)) { + return Optional(); + } else { + return Optional(*it); + } + }; + + std::transform(std::cbegin(_results), std::cend(_results), std::begin(_results), [=](const RemoteInfo &info) { + auto result = info; + const auto encryptedFileInfo = findEncryptedFile(result.name); + if (encryptedFileInfo) { + result.isE2eEncrypted = true; + result.e2eMangledName = _subPath.mid(1) + QLatin1Char('/') + result.name; + result.name = encryptedFileInfo->originalFilename; + } + return result; + }); + + emit etag(_firstEtag); + emit finished(_results); + deleteLater(); +} + +void DiscoverySingleDirectoryJob::metadataError(const QByteArray &fileId, int httpReturnCode) +{ + qCWarning(lcDiscovery) << "E2EE Metadata job error. Trying to proceed without it." << fileId << httpReturnCode; + emit etag(_firstEtag); + emit finished(_results); + deleteLater(); +} } diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 6a8d1fa48..748d57537 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -56,6 +56,9 @@ struct RemoteInfo time_t modtime = 0; int64_t size = 0; bool isDirectory = false; + bool isE2eEncrypted = false; + QString e2eMangledName; + bool isValid() const { return !name.isNull(); } QString directDownloadUrl; @@ -130,11 +133,15 @@ private slots: void directoryListingIteratedSlot(const QString &, const QMap &); void lsJobFinishedWithoutErrorSlot(); void lsJobFinishedWithErrorSlot(QNetworkReply *); + void fetchE2eMetadata(); + void metadataReceived(const QJsonDocument &json, int statusCode); + void metadataError(const QByteArray& fileId, int httpReturnCode); private: QVector _results; QString _subPath; QString _firstEtag; + QByteArray _fileId; AccountPtr _account; // The first result is for the directory itself and need to be ignored. // This flag is true if it was already ignored. @@ -143,6 +150,8 @@ private: bool _isRootPath; // If this directory is an external storage (The first item has 'M' in its permission) bool _isExternalStorage; + // If this directory is e2ee + bool _isE2eEncrypted; // If set, the discovery will finish with an error QString _error; QPointer _lsColJob; diff --git a/src/libsync/propagatedownloadencrypted.cpp b/src/libsync/propagatedownloadencrypted.cpp index e8c0add00..6cbdc8402 100644 --- a/src/libsync/propagatedownloadencrypted.cpp +++ b/src/libsync/propagatedownloadencrypted.cpp @@ -95,20 +95,10 @@ void PropagateDownloadEncrypted::checkFolderEncryptedMetadata(const QJsonDocumen auto meta = new FolderMetadata(_propagator->account(), json.toJson(QJsonDocument::Compact)); const QVector files = meta->files(); - const QString encryptedFilename = _item->_instruction == CSYNC_INSTRUCTION_NEW ? - _item->_file.section(QLatin1Char('/'), -1) : - _item->_encryptedFileName.section(QLatin1Char('/'), -1); + const QString encryptedFilename = _item->_encryptedFileName.section(QLatin1Char('/'), -1); for (const EncryptedFile &file : files) { if (encryptedFilename == file.encryptedFilename) { _encryptedInfo = file; - if (_item->_encryptedFileName.isEmpty()) { - _item->_encryptedFileName = _item->_file; - } - if (!_localParentPath.isEmpty()) { - _item->_file = _localParentPath + QLatin1Char('/') + _encryptedInfo.originalFilename; - } else { - _item->_file = _encryptedInfo.originalFilename; - } qCDebug(lcPropagateDownloadEncrypted) << "Found matching encrypted metadata for file, starting download"; emit folderStatusEncrypted();