E2EE Fix incorrect name of a nested encrypted item in the Settings dialog when the root folder is non-encrypted and it is renamed.

Signed-off-by: allexzander <blackslayer4@gmail.com>
This commit is contained in:
allexzander 2021-01-21 17:07:30 +02:00
parent 483a874cb6
commit eb80f54dcf
10 changed files with 82 additions and 52 deletions

Binary file not shown.

View file

@ -937,9 +937,9 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
// If it's not a move it's just a local-NEW // If it's not a move it's just a local-NEW
if (!moveCheck()) { if (!moveCheck()) {
const bool isRootEncryptedFolderRename = base._isE2eEncrypted; if (base._isE2eEncrypted) {
if (isRootEncryptedFolderRename) { // renaming the encrypted folder is done via remove + re-upload hence we need to mark the newly created folder as encrypted
// if we're renaming the root encrypted folder, we'd need to mark it's NEW item with _isEncrypted true so the further processing will work correctly // base is a record in the SyncJournal database that contains the data about the being-renamed folder with it's old name and encryption information
item->_isEncrypted = true; item->_isEncrypted = true;
} }
postProcessLocalNew(); postProcessLocalNew();

View file

@ -186,6 +186,10 @@ public:
, _item(item) , _item(item)
, _parallelism(FullParallelism) , _parallelism(FullParallelism)
{ {
// we should always execute jobs that process the E2EE API calls as sequential jobs
// TODO: In fact, we must make sure Lock/Unlock are not colliding and always wait for each other to complete. So, we could refactor this "_parallelism" later
// so every "PropagateItemJob" that will potentially execute Lock job on E2EE folder will get executed sequentially.
// As an alternative, we could optimize Lock/Unlock calls, so we do a batch-write on one folder and only lock and unlock a folder once per batch.
_parallelism = (_item->_isEncrypted || hasEncryptedAncestor()) ? WaitForFinished : FullParallelism; _parallelism = (_item->_isEncrypted || hasEncryptedAncestor()) ? WaitForFinished : FullParallelism;
} }
~PropagateItemJob(); ~PropagateItemJob();

View file

@ -93,7 +93,7 @@ void PropagateRemoteMkdir::slotStartEncryptedMkcolJob(const QString &path, const
auto job = new MkColJob(propagator()->account(), auto job = new MkColJob(propagator()->account(),
propagator()->fullRemotePath(filename), propagator()->fullRemotePath(filename),
{{"e2e-token", _uploadEncryptedHelper->_folderToken }}, {{"e2e-token", _uploadEncryptedHelper->folderToken() }},
this); this);
connect(job, qOverload<QNetworkReply::NetworkError>(&MkColJob::finished), connect(job, qOverload<QNetworkReply::NetworkError>(&MkColJob::finished),
this, &PropagateRemoteMkdir::slotMkcolJobFinished); this, &PropagateRemoteMkdir::slotMkcolJobFinished);
@ -120,6 +120,7 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
{ {
if (_item->_httpErrorCode == 405) { if (_item->_httpErrorCode == 405) {
// This happens when the directory already exists. Nothing to do. // This happens when the directory already exists. Nothing to do.
qDebug(lcPropagateRemoteMkdir) << "Folder" << jobPath << "already exists.";
} else if (err != QNetworkReply::NoError) { } else if (err != QNetworkReply::NoError) {
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode, SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&propagator()->_anotherSyncNeeded); &propagator()->_anotherSyncNeeded);
@ -155,7 +156,8 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
if (!_uploadEncryptedHelper && !_item->_isEncrypted) { if (!_uploadEncryptedHelper && !_item->_isEncrypted) {
success(); success();
} else { } else {
// We still need to mark that folder encrypted // We still need to mark that folder encrypted in case we were uploading it as encrypted one
// Another scenario, is we are creating a new folder because of move operation on an encrypted folder that works via remove + re-upload
propagator()->_activeJobList.append(this); propagator()->_activeJobList.append(this);
// We're expecting directory path in /Foo/Bar convention... // We're expecting directory path in /Foo/Bar convention...
@ -214,7 +216,8 @@ void PropagateRemoteMkdir::slotMkcolJobFinished()
const auto jobPath = _job->path(); const auto jobPath = _job->path();
if (_uploadEncryptedHelper && !_uploadEncryptedHelper->_folderId.isEmpty()) { if (_uploadEncryptedHelper && _uploadEncryptedHelper->isFolderLocked() && !_uploadEncryptedHelper->isUnlockRunning()) {
// since we are done, we need to unlock a folder in case it was locked
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, [this, err, jobHttpReasonPhraseString, jobPath]() { connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, [this, err, jobHttpReasonPhraseString, jobPath]() {
finalizeMkColJob(err, jobHttpReasonPhraseString, jobPath); finalizeMkColJob(err, jobHttpReasonPhraseString, jobPath);
}); });

View file

@ -60,8 +60,5 @@ private slots:
private: private:
void finalizeMkColJob(QNetworkReply::NetworkError err, const QString &jobHttpReasonPhraseString, const QString &jobPath); void finalizeMkColJob(QNetworkReply::NetworkError err, const QString &jobHttpReasonPhraseString, const QString &jobPath);
private:
QPair<SyncFileItem::Status, QString> _status = { SyncFileItem::NoStatus, QString() };
}; };
} }

View file

@ -85,6 +85,34 @@ void PropagateRemoteMove::start()
if (origin == _item->_renameTarget) { if (origin == _item->_renameTarget) {
// The parent has been renamed already so there is nothing more to do. // The parent has been renamed already so there is nothing more to do.
if (!_item->_encryptedFileName.isEmpty()) {
// when renaming non-encrypted folder that contains encrypted folder, nested files of its encrypted folder are incorrectly displayed in the Settings dialog
// encrypted name is displayed instead of a local folder name, unless the sync folder is removed, then added again and re-synced
// we are fixing it by modifying the "_encryptedFileName" in such a way so it will have a renamed root path at the beginning of it as expected
// corrected "_encryptedFileName" is later used in propagator()->updateMetadata() call that will update the record in the Sync journal DB
const auto path = _item->_file;
const auto slashPosition = path.lastIndexOf('/');
const auto parentPath = slashPosition >= 0 ? path.left(slashPosition) : QString();
SyncJournalFileRecord parentRec;
bool ok = propagator()->_journal->getFileRecord(parentPath, &parentRec);
if (!ok) {
done(SyncFileItem::NormalError);
return;
}
const auto remoteParentPath = parentRec._e2eMangledName.isEmpty() ? parentPath : parentRec._e2eMangledName;
const auto lastSlashPosition = _item->_encryptedFileName.lastIndexOf('/');
const auto encryptedName = lastSlashPosition >= 0 ? _item->_encryptedFileName.mid(lastSlashPosition + 1) : QString();
if (!encryptedName.isEmpty()) {
_item->_encryptedFileName = remoteParentPath + "/" + encryptedName;
}
}
finalize(); finalize();
return; return;
} }

View file

@ -237,7 +237,7 @@ void PropagateUploadFileCommon::start()
_uploadEncryptedHelper = new PropagateUploadEncrypted(propagator(), remoteParentPath, _item, this); _uploadEncryptedHelper = new PropagateUploadEncrypted(propagator(), remoteParentPath, _item, this);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::finalized, connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::finalized,
this, &PropagateUploadFileCommon::setupEncryptedFile); this, &PropagateUploadFileCommon::setupEncryptedFile);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::error, [this]{ connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::error, [this] {
qCDebug(lcPropagateUpload) << "Error setting up encryption."; qCDebug(lcPropagateUpload) << "Error setting up encryption.";
done(SyncFileItem::FatalError, tr("Failed to upload encrypted file.")); done(SyncFileItem::FatalError, tr("Failed to upload encrypted file."));
}); });
@ -416,17 +416,17 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionCh
void PropagateUploadFileCommon::slotFolderUnlocked(const QByteArray &folderId, int httpReturnCode) void PropagateUploadFileCommon::slotFolderUnlocked(const QByteArray &folderId, int httpReturnCode)
{ {
qDebug() << "Failed to unlock encrypted folder" << folderId; qDebug() << "Failed to unlock encrypted folder" << folderId;
if (_status.first == SyncFileItem::NoStatus && httpReturnCode != 200) { if (_uploadStatus.status == SyncFileItem::NoStatus && httpReturnCode != 200) {
done(SyncFileItem::FatalError, tr("Failed to unlock encrypted folder.")); done(SyncFileItem::FatalError, tr("Failed to unlock encrypted folder."));
} else { } else {
done(_status.first, _status.second); done(_uploadStatus.status, _uploadStatus.message);
} }
} }
void PropagateUploadFileCommon::slotOnErrorStartFolderUnlock(SyncFileItem::Status status, const QString &errorString) void PropagateUploadFileCommon::slotOnErrorStartFolderUnlock(SyncFileItem::Status status, const QString &errorString)
{ {
if (_uploadingEncrypted) { if (_uploadingEncrypted) {
_status = { status, errorString }; _uploadStatus = { status, errorString };
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadFileCommon::slotFolderUnlocked); connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadFileCommon::slotFolderUnlocked);
_uploadEncryptedHelper->unlockFolder(); _uploadEncryptedHelper->unlockFolder();
} else { } else {
@ -750,8 +750,8 @@ QMap<QByteArray, QByteArray> PropagateUploadFileCommon::headers()
headers[QByteArrayLiteral("OC-ConflictBaseEtag")] = conflictRecord.baseEtag; headers[QByteArrayLiteral("OC-ConflictBaseEtag")] = conflictRecord.baseEtag;
} }
if (_uploadEncryptedHelper && !_uploadEncryptedHelper->_folderToken.isEmpty()) { if (_uploadEncryptedHelper && !_uploadEncryptedHelper->folderToken().isEmpty()) {
headers.insert("e2e-token", _uploadEncryptedHelper->_folderToken); headers.insert("e2e-token", _uploadEncryptedHelper->folderToken());
} }
return headers; return headers;
@ -786,7 +786,7 @@ void PropagateUploadFileCommon::finalize()
propagator()->_journal->commit("upload file start"); propagator()->_journal->commit("upload file start");
if (_uploadingEncrypted) { if (_uploadingEncrypted) {
_status = { SyncFileItem::Success, QString() }; _uploadStatus = { SyncFileItem::Success, QString() };
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadFileCommon::slotFolderUnlocked); connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadFileCommon::slotFolderUnlocked);
_uploadEncryptedHelper->unlockFolder(); _uploadEncryptedHelper->unlockFolder();
} else { } else {

View file

@ -205,6 +205,11 @@ class PropagateUploadFileCommon : public PropagateItemJob
{ {
Q_OBJECT Q_OBJECT
struct UploadStatus {
SyncFileItem::Status status = SyncFileItem::NoStatus;
QString message;
};
protected: protected:
QVector<AbstractNetworkJob *> _jobs; /// network jobs that are currently in transit QVector<AbstractNetworkJob *> _jobs; /// network jobs that are currently in transit
bool _finished BITFIELD(1); /// Tells that all the jobs have been finished bool _finished BITFIELD(1); /// Tells that all the jobs have been finished
@ -314,7 +319,7 @@ protected:
private: private:
PropagateUploadEncrypted *_uploadEncryptedHelper; PropagateUploadEncrypted *_uploadEncryptedHelper;
bool _uploadingEncrypted; bool _uploadingEncrypted;
QPair<SyncFileItem::Status, QString> _status = { SyncFileItem::NoStatus, QString() }; UploadStatus _uploadStatus;
}; };
/** /**

View file

@ -95,6 +95,7 @@ void PropagateUploadEncrypted::slotFolderLockedSuccessfully(const QByteArray& fi
_currentLockingInProgress = true; _currentLockingInProgress = true;
_folderToken = token; _folderToken = token;
_folderId = fileId; _folderId = fileId;
_isFolderLocked = true;
auto job = new GetMetadataApiJob(_propagator->account(), _folderId); auto job = new GetMetadataApiJob(_propagator->account(), _folderId);
connect(job, &GetMetadataApiJob::jsonReceived, connect(job, &GetMetadataApiJob::jsonReceived,
@ -178,7 +179,8 @@ void PropagateUploadEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDo
if (!encryptionResult) { if (!encryptionResult) {
qCDebug(lcPropagateUploadEncrypted()) << "There was an error encrypting the file, aborting upload."; qCDebug(lcPropagateUploadEncrypted()) << "There was an error encrypting the file, aborting upload.";
unlockFolder(true); connect(this, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadEncrypted::error);
unlockFolder();
return; return;
} }
@ -229,7 +231,8 @@ void PropagateUploadEncrypted::slotUpdateMetadataError(const QByteArray& fileId,
{ {
qCDebug(lcPropagateUploadEncrypted) << "Update metadata error for folder" << fileId << "with error" << httpErrorResponse; qCDebug(lcPropagateUploadEncrypted) << "Update metadata error for folder" << fileId << "with error" << httpErrorResponse;
qCDebug(lcPropagateUploadEncrypted()) << "Unlocking the folder."; qCDebug(lcPropagateUploadEncrypted()) << "Unlocking the folder.";
unlockFolder(true); connect(this, &PropagateUploadEncrypted::folderUnlocked, this, &PropagateUploadEncrypted::error);
unlockFolder();
} }
void PropagateUploadEncrypted::slotFolderLockedError(const QByteArray& fileId, int httpErrorCode) void PropagateUploadEncrypted::slotFolderLockedError(const QByteArray& fileId, int httpErrorCode)
@ -262,49 +265,34 @@ void PropagateUploadEncrypted::slotFolderEncryptedIdError(QNetworkReply *r)
qCDebug(lcPropagateUploadEncrypted) << "Error retrieving the Id of the encrypted folder."; qCDebug(lcPropagateUploadEncrypted) << "Error retrieving the Id of the encrypted folder.";
} }
void PropagateUploadEncrypted::unlockFolder(bool uploadFailed) void PropagateUploadEncrypted::unlockFolder()
{ {
{ ASSERT(!_isUnlockRunning);
QMutexLocker locker(&_isUnlockRunningMutex);
ASSERT(!_isUnlockRunning); if (_isUnlockRunning) {
qWarning() << "Double-call to unlockFolder.";
if (_isUnlockRunning) { return;
qWarning() << "Double-call to unlockFolder.";
return;
}
_isUnlockRunning = true;
} }
_isUnlockRunning = true;
qDebug() << "Calling Unlock"; qDebug() << "Calling Unlock";
auto *unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), auto *unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(),
_folderId, _folderToken, this); _folderId, _folderToken, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this, uploadFailed](const QByteArray &folderId) { connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this](const QByteArray &folderId) {
qDebug() << "Successfully Unlocked"; qDebug() << "Successfully Unlocked";
_folderToken = ""; _folderToken = "";
_folderId = ""; _folderId = "";
_isFolderLocked = false;
emit folderUnlocked(folderId, 200); emit folderUnlocked(folderId, 200);
if (uploadFailed) {
emit error();
}
QMutexLocker locker(&_isUnlockRunningMutex);
_isUnlockRunning = false; _isUnlockRunning = false;
}); });
connect(unlockJob, &UnlockEncryptFolderApiJob::error, [this, uploadFailed](const QByteArray &folderId, int httpStatus) { connect(unlockJob, &UnlockEncryptFolderApiJob::error, [this](const QByteArray &folderId, int httpStatus) {
qDebug() << "Unlock Error"; qDebug() << "Unlock Error";
emit folderUnlocked(folderId, httpStatus); emit folderUnlocked(folderId, httpStatus);
if (uploadFailed) {
emit error();
}
QMutexLocker locker(&_isUnlockRunningMutex);
_isUnlockRunning = false; _isUnlockRunning = false;
}); });
unlockJob->start(); unlockJob->start();

View file

@ -33,13 +33,15 @@ class PropagateUploadEncrypted : public QObject
Q_OBJECT Q_OBJECT
public: public:
PropagateUploadEncrypted(OwncloudPropagator *propagator, const QString &remoteParentPath, SyncFileItemPtr item, QObject *parent = nullptr); PropagateUploadEncrypted(OwncloudPropagator *propagator, const QString &remoteParentPath, SyncFileItemPtr item, QObject *parent = nullptr);
~PropagateUploadEncrypted() = default;
void start(); void start();
/* unlocks the current folder that holds this file */ void unlockFolder();
void unlockFolder(bool uploadFailed = false);
// Used by propagateupload bool isUnlockRunning() const { return _isUnlockRunning; }
QByteArray _folderToken; bool isFolderLocked() const { return _isFolderLocked; }
QByteArray _folderId; const QByteArray folderToken() const { return _folderToken; }
private slots: private slots:
void slotFolderEncryptedIdReceived(const QStringList &list); void slotFolderEncryptedIdReceived(const QStringList &list);
@ -63,17 +65,20 @@ private:
QString _remoteParentPath; QString _remoteParentPath;
SyncFileItemPtr _item; SyncFileItemPtr _item;
QByteArray _folderToken;
QByteArray _folderId;
QElapsedTimer _folderLockFirstTry; QElapsedTimer _folderLockFirstTry;
bool _currentLockingInProgress = false; bool _currentLockingInProgress = false;
bool _isUnlockRunning = false;
bool _isFolderLocked = false;
QByteArray _generatedKey; QByteArray _generatedKey;
QByteArray _generatedIv; QByteArray _generatedIv;
FolderMetadata *_metadata; FolderMetadata *_metadata;
EncryptedFile _encryptedFile; EncryptedFile _encryptedFile;
QString _completeFileName; QString _completeFileName;
bool _isUnlockRunning = false; // protection against multiple calls to unlock the same folder
QMutex _isUnlockRunningMutex; // corresponding mutex for _isUnlockRunning
}; };