Skip to content

Commit

Permalink
Implement passing the counter header to lock job on V2.
Browse files Browse the repository at this point in the history
Signed-off-by: alex-z <[email protected]>
  • Loading branch information
allexzander committed Jun 21, 2023
1 parent 872de5a commit 25f6e14
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/*
* Copyright (C) 2023 by Oleksandr Zolotov <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

#include "fetchanduploade2eefoldermetadatajob.h"

#include "account.h"
#include "common/syncjournaldb.h"
#include "clientsideencryptionjobs.h"
#include "clientsideencryption.h"
#include "foldermetadata.h"

#include <QLoggingCategory>
#include <QNetworkReply>

namespace OCC {

Q_LOGGING_CATEGORY(lcFetchAndUploadE2eeFolderMetadataJob, "nextcloud.sync.propagator.fetchanduploade2eefoldermetadatajob", QtInfoMsg)

}

namespace OCC {

FetchAndUploadE2eeFolderMetadataJob::FetchAndUploadE2eeFolderMetadataJob(const AccountPtr &account,
const QString &folderPath,
SyncJournalDb *const journalDb,
const QString &pathForTopLevelFolder,
QObject *parent)
: QObject(parent),
_account(account),
_folderPath(folderPath),
_journalDb(journalDb),
_pathForTopLevelFolder(pathForTopLevelFolder)
{
}

void FetchAndUploadE2eeFolderMetadataJob::setFolderMetadata(const QSharedPointer<FolderMetadata> &folderMetadata)
{
_folderMetadata = folderMetadata;
}

QSharedPointer<FolderMetadata> FetchAndUploadE2eeFolderMetadataJob::folderMetadata() const
{
return _folderMetadata;
}

const QByteArray FetchAndUploadE2eeFolderMetadataJob::folderId() const
{
return _folderId;
}

void FetchAndUploadE2eeFolderMetadataJob::setFolderToken(const QByteArray &token)
{
_folderToken = token;
}

const QByteArray FetchAndUploadE2eeFolderMetadataJob::folderToken() const
{
return _folderToken;
}

const bool FetchAndUploadE2eeFolderMetadataJob::isUnlockRunning() const
{
return _isUnlockRunning;
}

const bool FetchAndUploadE2eeFolderMetadataJob::isFolderLocked() const
{
return _isFolderLocked;
}

void FetchAndUploadE2eeFolderMetadataJob::fetchMetadata(bool allowEmptyMetadata)
{
_allowEmptyMetadata = allowEmptyMetadata;
slotFetchFolderEncryptedId();
}

void FetchAndUploadE2eeFolderMetadataJob::uploadMetadata(bool keepLock)
{
_keepLockedAfterUpdate = keepLock;
if (!_folderToken.isEmpty()) {
slotUploadMetadata();
return;
}
slotLockFolder();
}

void FetchAndUploadE2eeFolderMetadataJob::unlockFolder(bool success)
{
if (_folderToken.isEmpty()) {
emit folderUnlocked(_folderId, 200);
return;
}
if (success) {
connect(this, &FetchAndUploadE2eeFolderMetadataJob::folderUnlocked, this, &FetchAndUploadE2eeFolderMetadataJob::slotEmitUploadSuccess);
} else {
connect(this, &FetchAndUploadE2eeFolderMetadataJob::folderUnlocked, this, &FetchAndUploadE2eeFolderMetadataJob::slotEmitUploadError);
}
slotUnlockFolder();
}

void FetchAndUploadE2eeFolderMetadataJob::slotFetchFolderMetadata()
{
const auto job = new GetMetadataApiJob(_account, _folderId);
connect(job, &GetMetadataApiJob::jsonReceived, this, &FetchAndUploadE2eeFolderMetadataJob::slotMetadataReceived);
connect(job, &GetMetadataApiJob::error, this, &FetchAndUploadE2eeFolderMetadataJob::slotMetadataReceivedError);
job->start();
}

void FetchAndUploadE2eeFolderMetadataJob::slotFetchFolderEncryptedId()
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Folder is encrypted, let's get the Id from it.";
const auto job = new LsColJob(_account, _folderPath, this);
job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"});
connect(job, &LsColJob::directoryListingSubfolders, this, &FetchAndUploadE2eeFolderMetadataJob::slotFolderEncryptedIdReceived);
connect(job, &LsColJob::finishedWithError, this, &FetchAndUploadE2eeFolderMetadataJob::slotFolderEncryptedIdError);
job->start();
}

void FetchAndUploadE2eeFolderMetadataJob::slotFolderEncryptedIdReceived(const QStringList &list)
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Received id of folder. Fetching metadata...";
const auto job = qobject_cast<LsColJob *>(sender());
const auto &folderInfo = job->_folderInfos.value(list.first());
_folderId = folderInfo.fileId;
slotFetchFolderMetadata();
}

void FetchAndUploadE2eeFolderMetadataJob::slotFolderEncryptedIdError(QNetworkReply *reply)
{
Q_ASSERT(reply);
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Error retrieving the Id of the encrypted folder.";
if (reply) {
const auto errorCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
const auto errorMessage = reply->errorString();
emit fetchFinished(errorCode, errorMessage);
return;
}
emit fetchFinished(-1, tr("Error fetching encrypted folder id."));
}

void FetchAndUploadE2eeFolderMetadataJob::slotMetadataReceived(const QJsonDocument &json, int statusCode)
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Metadata Received, parsing it and decrypting" << json.toVariant();

QSharedPointer<FolderMetadata> metadata(new FolderMetadata(_account,
statusCode == 404 ? QByteArray{} : json.toJson(QJsonDocument::Compact),
FolderMetadata::RootEncryptedFolderInfo(FolderMetadata::RootEncryptedFolderInfo::createRootPath(_pathForTopLevelFolder, _folderPath))));
connect(metadata.data(), &FolderMetadata::setupComplete, this, [this, statusCode, metadata] {
if (!metadata->isValid()) {
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob()) << "Error parsing or decrypting metadata.";
emit fetchFinished(-1, tr("Error parsing or decrypting metadata."));
return;
}
_folderMetadata = metadata;
emit fetchFinished(200);
});
}

void FetchAndUploadE2eeFolderMetadataJob::slotMetadataReceivedError(const QByteArray &folderId, int httpReturnCode)
{
Q_UNUSED(folderId);
if (_allowEmptyMetadata) {
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob()) << "Error Getting the encrypted metadata. Pretend we got empty metadata.";
_isNewMetadataCreated = true;
slotMetadataReceived({}, httpReturnCode);
return;
}
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob()) << "Error Getting the encrypted metadata.";
emit fetchFinished(httpReturnCode, tr("Error fetching metadata."));
}

void FetchAndUploadE2eeFolderMetadataJob::slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token)
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Folder" << folderId << "Locked Successfully for Upload, Fetching Metadata";
_folderToken = token;
_isFolderLocked = true;
slotUploadMetadata();
}

void FetchAndUploadE2eeFolderMetadataJob::slotFolderLockedError(const QByteArray &folderId, int httpErrorCode)
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob()) << "Error locking folder" << folderId;
emit fetchFinished(httpErrorCode, tr("Error locking folder."));
}

void FetchAndUploadE2eeFolderMetadataJob::slotLockFolder()
{
const auto lockJob = new LockEncryptFolderApiJob(_account, _folderId, _journalDb, _account->e2e()->_publicKey, this);
connect(lockJob, &LockEncryptFolderApiJob::success, this, &FetchAndUploadE2eeFolderMetadataJob::slotFolderLockedSuccessfully);
connect(lockJob, &LockEncryptFolderApiJob::error, this, &FetchAndUploadE2eeFolderMetadataJob::slotFolderLockedError);
auto dValue = _account->capabilities().clientSideEncryptionVersion();
if (_account->capabilities().clientSideEncryptionVersion() >= 2.0) {
lockJob->setCounter(_folderMetadata->newCounter());
}
lockJob->start();
}

void FetchAndUploadE2eeFolderMetadataJob::slotUnlockFolder()
{
Q_ASSERT(!_isUnlockRunning);

if (_isUnlockRunning) {
qWarning(lcFetchAndUploadE2eeFolderMetadataJob()) << "Double-call to unlockFolder.";
return;
}

_isUnlockRunning = true;

qDebug() << "Calling Unlock";
const auto unlockJob = new UnlockEncryptFolderApiJob(_account, _folderId, _folderToken, _journalDb, this);

connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this](const QByteArray &folderId) {
qDebug() << "Successfully Unlocked";
_isFolderLocked = false;
emit folderUnlocked(folderId, 200);
_isUnlockRunning = false;
});
connect(unlockJob, &UnlockEncryptFolderApiJob::error, [this](const QByteArray &folderId, int httpStatus) {
qDebug() << "Unlock Error";
emit folderUnlocked(folderId, httpStatus);
_isUnlockRunning = false;
});
unlockJob->start();
}

void FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadata()
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Metadata created, sending to the server.";

_uploadSignalEmitted = false;

if (!_folderMetadata->isValid()) {
slotUploadMetadataError(_folderId, -1);
return;
}

const auto encryptedMetadata = _folderMetadata->encryptedMetadata();
if (_isNewMetadataCreated) {
const auto job = new StoreMetaDataApiJob(_account, _folderId, _folderToken, encryptedMetadata);
connect(job, &StoreMetaDataApiJob::success, this, &FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadataSuccess);
connect(job, &StoreMetaDataApiJob::error, this, &FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadataError);
job->start();
} else {
const auto job = new UpdateMetadataApiJob(_account, _folderId, encryptedMetadata, _folderToken);
connect(job, &UpdateMetadataApiJob::success, this, &FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadataSuccess);
connect(job, &UpdateMetadataApiJob::error, this, &FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadataError);
job->start();
}
}

void FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadataSuccess(const QByteArray &folderId)
{
Q_UNUSED(folderId);
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Uploading of the metadata success.";
if (_keepLockedAfterUpdate) {
slotEmitUploadSuccess();
return;
}
connect(this, &FetchAndUploadE2eeFolderMetadataJob::folderUnlocked, this, &FetchAndUploadE2eeFolderMetadataJob::slotEmitUploadSuccess);
if (_isFolderLocked) {
slotUnlockFolder();
} else {
slotEmitUploadSuccess();
}
}

void FetchAndUploadE2eeFolderMetadataJob::slotUploadMetadataError(const QByteArray &folderId, int httpReturnCode)
{
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Update metadata error for folder" << folderId << "with error" << httpReturnCode;
qCDebug(lcFetchAndUploadE2eeFolderMetadataJob()) << "Unlocking the folder.";
connect(this, &FetchAndUploadE2eeFolderMetadataJob::folderUnlocked, this, &FetchAndUploadE2eeFolderMetadataJob::slotEmitUploadError);
slotUnlockFolder();
}

void FetchAndUploadE2eeFolderMetadataJob::slotEmitUploadSuccess()
{
if (!_uploadSignalEmitted) {
_uploadSignalEmitted = true;
emit uploadFinished(200);
}
}

void FetchAndUploadE2eeFolderMetadataJob::slotEmitUploadError()
{
emit uploadFinished(1, tr("Failed to upload metadata"));
}

}
8 changes: 8 additions & 0 deletions src/libsync/clientsideencryptionjobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ void LockEncryptFolderApiJob::start()

QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
if (_counter > 0) {
req.setRawHeader("X-NC-E2EE-COUNTER", QByteArray::number(_counter));
}
QUrlQuery query;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path());
Expand Down Expand Up @@ -331,6 +334,11 @@ bool LockEncryptFolderApiJob::finished()
return true;
}

void LockEncryptFolderApiJob::setCounter(quint64 counter)
{
_counter = counter;
}

SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, FlagAction flagAction, QObject* parent)
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction)
{
Expand Down
3 changes: 3 additions & 0 deletions src/libsync/clientsideencryptionjobs.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ class OWNCLOUDSYNC_EXPORT LockEncryptFolderApiJob : public AbstractNetworkJob
public:
explicit LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray &fileId, SyncJournalDb *journalDb, const QSslKey publicKey, QObject *parent = nullptr);

void setCounter(const quint64 counter);

public slots:
void start() override;

Expand All @@ -163,6 +165,7 @@ public slots:
QByteArray _fileId;
QPointer<SyncJournalDb> _journalDb;
QSslKey _publicKey;
quint64 _counter = 0;
};


Expand Down
3 changes: 3 additions & 0 deletions src/libsync/fetchanduploade2eefoldermetadatajob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ void FetchAndUploadE2eeFolderMetadataJob::slotLockFolder()
const auto lockJob = new LockEncryptFolderApiJob(_account, _folderId, _journalDb, _account->e2e()->_publicKey, this);
connect(lockJob, &LockEncryptFolderApiJob::success, this, &FetchAndUploadE2eeFolderMetadataJob::slotFolderLockedSuccessfully);
connect(lockJob, &LockEncryptFolderApiJob::error, this, &FetchAndUploadE2eeFolderMetadataJob::slotFolderLockedError);
if (_account->capabilities().clientSideEncryptionVersion() >= 2.0) {
lockJob->setCounter(_folderMetadata->newCounter());
}
lockJob->start();
}

Expand Down
4 changes: 2 additions & 2 deletions src/libsync/foldermetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject

[[nodiscard]] bool isVersion2AndUp() const;

[[nodiscard]] quint64 newCounter() const;

private:
QByteArray encryptedMetadataLegacy();

Expand All @@ -130,8 +132,6 @@ class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject

[[nodiscard]] const MetadataVersion latestSupportedMetadataVersion() const;

[[nodiscard]] quint64 newCounter() const;

static EncryptionStatusEnums::ItemEncryptionStatus fromMedataVersionToItemEncryptionStatus(const MetadataVersion &metadataVersion);
static MetadataVersion fromItemEncryptionStatusToMedataVersion(const EncryptionStatusEnums::ItemEncryptionStatus &encryptionStatus);

Expand Down

0 comments on commit 25f6e14

Please sign in to comment.