mirror of https://github.com/nextcloud/desktop
372 lines
16 KiB
C++
372 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2023 by Oleksandr Zolotov <alex@nextcloud.com>
|
|
*
|
|
* 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 "account.h"
|
|
#include "updatee2eefolderusersmetadatajob.h"
|
|
#include "foldermetadata.h"
|
|
#include "common/syncjournalfilerecord.h"
|
|
#include "common/syncjournaldb.h"
|
|
|
|
#include <QSslCertificate>
|
|
|
|
namespace OCC
|
|
{
|
|
Q_LOGGING_CATEGORY(lcUpdateE2eeFolderUsersMetadataJob, "nextcloud.gui.updatee2eefolderusersmetadatajob", QtInfoMsg)
|
|
|
|
UpdateE2eeFolderUsersMetadataJob::UpdateE2eeFolderUsersMetadataJob(const AccountPtr &account,
|
|
SyncJournalDb *journalDb,
|
|
const QString &syncFolderRemotePath,
|
|
const Operation operation,
|
|
const QString &fullRemotePath,
|
|
const QString &folderUserId,
|
|
const QSslCertificate &certificate,
|
|
QObject *parent)
|
|
: QObject(parent)
|
|
, _account(account)
|
|
, _journalDb(journalDb)
|
|
, _syncFolderRemotePath(Utility::noLeadingSlashPath(Utility::noTrailingSlashPath(syncFolderRemotePath)))
|
|
, _operation(operation)
|
|
, _fullRemotePath(Utility::noLeadingSlashPath(fullRemotePath))
|
|
, _folderUserId(folderUserId)
|
|
, _folderUserCertificate(certificate)
|
|
{
|
|
Q_ASSERT(_syncFolderRemotePath == QStringLiteral("/") || _fullRemotePath.startsWith(_syncFolderRemotePath));
|
|
SyncJournalFileRecord rec;
|
|
if (!_journalDb->getRootE2eFolderRecord(Utility::fullRemotePathToRemoteSyncRootRelative(_fullRemotePath, _syncFolderRemotePath), &rec) || !rec.isValid()) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Could not get root E2ee folder recort for path" << _fullRemotePath;
|
|
return;
|
|
}
|
|
_encryptedFolderMetadataHandler.reset(new EncryptedFolderMetadataHandler(_account, _fullRemotePath, _syncFolderRemotePath, _journalDb, rec.path()));
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::start(const bool keepLock)
|
|
{
|
|
qCWarning(lcUpdateE2eeFolderUsersMetadataJob) << "[DEBUG_LEAVE_SHARE]: UpdateE2eeFolderUsersMetadataJob::start";
|
|
|
|
if (!_encryptedFolderMetadataHandler) {
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
if (keepLock) {
|
|
connect(_encryptedFolderMetadataHandler.data(), &EncryptedFolderMetadataHandler::folderUnlocked, this, &UpdateE2eeFolderUsersMetadataJob::deleteLater);
|
|
} else {
|
|
connect(this, &UpdateE2eeFolderUsersMetadataJob::slotFolderUnlocked, this, &UpdateE2eeFolderUsersMetadataJob::deleteLater);
|
|
}
|
|
_keepLock = keepLock;
|
|
if (_operation != Operation::Add && _operation != Operation::Remove && _operation != Operation::ReEncrypt) {
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
if (_operation == Operation::Add) {
|
|
connect(this, &UpdateE2eeFolderUsersMetadataJob::certificateReady, this, &UpdateE2eeFolderUsersMetadataJob::slotStartE2eeMetadataJobs);
|
|
if (!_folderUserCertificate.isNull()) {
|
|
emit certificateReady();
|
|
return;
|
|
}
|
|
connect(_account->e2e(), &ClientSideEncryption::certificateFetchedFromKeychain,
|
|
this, &UpdateE2eeFolderUsersMetadataJob::slotCertificateFetchedFromKeychain);
|
|
_account->e2e()->fetchCertificateFromKeyChain(_account, _folderUserId);
|
|
return;
|
|
}
|
|
slotStartE2eeMetadataJobs();
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotStartE2eeMetadataJobs()
|
|
{
|
|
if (_operation == Operation::Add && _folderUserCertificate.isNull()) {
|
|
emit finished(404, tr("Could not fetch public key for user %1").arg(_folderUserId));
|
|
return;
|
|
}
|
|
|
|
const auto folderPathRelative = Utility::fullRemotePathToRemoteSyncRootRelative(_fullRemotePath, _syncFolderRemotePath);
|
|
SyncJournalFileRecord rec;
|
|
if (!_journalDb->getRootE2eFolderRecord(Utility::fullRemotePathToRemoteSyncRootRelative(folderPathRelative, _syncFolderRemotePath), &rec) || !rec.isValid()) {
|
|
emit finished(404, tr("Could not find root encrypted folder for folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
const auto rootEncFolderInfo = RootEncryptedFolderInfo(RootEncryptedFolderInfo::createRootPath(folderPathRelative, rec.path()), _metadataKeyForEncryption, _metadataKeyForDecryption, _keyChecksums);
|
|
connect(_encryptedFolderMetadataHandler.data(), &EncryptedFolderMetadataHandler::fetchFinished,
|
|
this, &UpdateE2eeFolderUsersMetadataJob::slotFetchMetadataJobFinished);
|
|
_encryptedFolderMetadataHandler->fetchMetadata(rootEncFolderInfo, EncryptedFolderMetadataHandler::FetchMode::AllowEmptyMetadata);
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotFetchMetadataJobFinished(int statusCode, const QString &message)
|
|
{
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Metadata Received, Preparing it for the new file." << message;
|
|
|
|
if (statusCode != 200) {
|
|
qCritical(lcUpdateE2eeFolderUsersMetadataJob) << "fetch metadata finished with error" << statusCode << message;
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
if (!_encryptedFolderMetadataHandler->folderMetadata() || !_encryptedFolderMetadataHandler->folderMetadata()->isValid()) {
|
|
emit finished(403, tr("Could not add or remove a folder user %1, for folder %2").arg(_folderUserId).arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
startUpdate();
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::startUpdate()
|
|
{
|
|
if (_operation == Operation::Invalid) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Invalid operation";
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
if (_operation == Operation::Add || _operation == Operation::Remove) {
|
|
if (!_encryptedFolderMetadataHandler->folderMetadata()) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Metadata is null";
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
const auto result = _operation == Operation::Add
|
|
? _encryptedFolderMetadataHandler->folderMetadata()->addUser(_folderUserId, _folderUserCertificate)
|
|
: _encryptedFolderMetadataHandler->folderMetadata()->removeUser(_folderUserId);
|
|
|
|
if (!result) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Could not perform operation" << _operation << "on metadata";
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
return;
|
|
}
|
|
|
|
}
|
|
connect(_encryptedFolderMetadataHandler.data(), &EncryptedFolderMetadataHandler::uploadFinished,
|
|
this, &UpdateE2eeFolderUsersMetadataJob::slotUpdateMetadataFinished);
|
|
_encryptedFolderMetadataHandler->setFolderToken(_folderToken);
|
|
_encryptedFolderMetadataHandler->uploadMetadata(EncryptedFolderMetadataHandler::UploadMode::KeepLock);
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotUpdateMetadataFinished(int code, const QString &message)
|
|
{
|
|
if (code != 200) {
|
|
qCWarning(lcUpdateE2eeFolderUsersMetadataJob) << "Update metadata error for folder" << _encryptedFolderMetadataHandler->folderId() << "with error"
|
|
<< code << message;
|
|
|
|
if (_operation == Operation::Add || _operation == Operation::Remove) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob()) << "Unlocking the folder.";
|
|
unlockFolder(EncryptedFolderMetadataHandler::UnlockFolderWithResult::Failure);
|
|
} else {
|
|
emit finished(code, tr("Error updating metadata for a folder %1").arg(_fullRemotePath) + QStringLiteral(":%1").arg(message));
|
|
}
|
|
return;
|
|
}
|
|
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Uploading of the metadata success.";
|
|
if (_operation == Operation::Add || _operation == Operation::Remove) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Trying to schedule more jobs.";
|
|
scheduleSubJobs();
|
|
if (_subJobs.isEmpty()) {
|
|
if (_keepLock) {
|
|
emit finished(200);
|
|
} else {
|
|
unlockFolder(EncryptedFolderMetadataHandler::UnlockFolderWithResult::Success);
|
|
}
|
|
} else {
|
|
_subJobs.values().last()->start();
|
|
}
|
|
} else {
|
|
emit finished(200);
|
|
}
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::scheduleSubJobs()
|
|
{
|
|
const auto isMetadataValid = _encryptedFolderMetadataHandler->folderMetadata() && _encryptedFolderMetadataHandler->folderMetadata()->isValid();
|
|
if (!isMetadataValid) {
|
|
if (_operation == Operation::Add || _operation == Operation::Remove) {
|
|
qCWarning(lcUpdateE2eeFolderUsersMetadataJob()) << "Metadata is invalid. Unlocking the folder.";
|
|
unlockFolder(EncryptedFolderMetadataHandler::UnlockFolderWithResult::Failure);
|
|
} else {
|
|
qCWarning(lcUpdateE2eeFolderUsersMetadataJob()) << "Metadata is invalid.";
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath));
|
|
}
|
|
return;
|
|
}
|
|
|
|
const auto pathInDb = Utility::fullRemotePathToRemoteSyncRootRelative(_fullRemotePath, _syncFolderRemotePath);
|
|
[[maybe_unused]] const auto result = _journalDb->getFilesBelowPath(pathInDb.toUtf8(), [this](const SyncJournalFileRecord &record) {
|
|
if (record.isDirectory()) {
|
|
const auto folderMetadata = _encryptedFolderMetadataHandler->folderMetadata();
|
|
const auto subJob = new UpdateE2eeFolderUsersMetadataJob(_account, _journalDb, _syncFolderRemotePath, UpdateE2eeFolderUsersMetadataJob::ReEncrypt, Utility::trailingSlashPath(_syncFolderRemotePath) + QString::fromUtf8(record._e2eMangledName));
|
|
subJob->setMetadataKeyForEncryption(folderMetadata->metadataKeyForEncryption());
|
|
subJob->setMetadataKeyForDecryption(folderMetadata->metadataKeyForDecryption());
|
|
subJob->setKeyChecksums(folderMetadata->keyChecksums());
|
|
subJob->setParent(this);
|
|
subJob->setFolderToken(_encryptedFolderMetadataHandler->folderToken());
|
|
_subJobs.insert(subJob);
|
|
connect(subJob, &UpdateE2eeFolderUsersMetadataJob::finished, this, &UpdateE2eeFolderUsersMetadataJob::slotSubJobFinished);
|
|
}
|
|
});
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::unlockFolder(const EncryptedFolderMetadataHandler::UnlockFolderWithResult result)
|
|
{
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Calling Unlock";
|
|
connect(_encryptedFolderMetadataHandler.data(), &EncryptedFolderMetadataHandler::folderUnlocked, this, &UpdateE2eeFolderUsersMetadataJob::slotFolderUnlocked);
|
|
_encryptedFolderMetadataHandler->unlockFolder(result);
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotFolderUnlocked(const QByteArray &folderId, int httpStatus)
|
|
{
|
|
emit folderUnlocked();
|
|
if (_keepLock) {
|
|
return;
|
|
}
|
|
if (httpStatus != 200) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "Failed to unlock a folder" << folderId << httpStatus;
|
|
}
|
|
const auto message = httpStatus != 200 ? tr("Failed to unlock a folder.") : QString{};
|
|
emit finished(httpStatus, message);
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::subJobsFinished(bool success)
|
|
{
|
|
unlockFolder(success
|
|
? EncryptedFolderMetadataHandler::UnlockFolderWithResult::Success
|
|
: EncryptedFolderMetadataHandler::UnlockFolderWithResult::Failure);
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotSubJobFinished(int code, const QString &message)
|
|
{
|
|
if (code != 200) {
|
|
qCDebug(lcUpdateE2eeFolderUsersMetadataJob) << "sub job finished with error" << message;
|
|
subJobsFinished(false);
|
|
return;
|
|
}
|
|
const auto job = qobject_cast<UpdateE2eeFolderUsersMetadataJob *>(sender());
|
|
Q_ASSERT(job);
|
|
if (!job) {
|
|
qCWarning(lcUpdateE2eeFolderUsersMetadataJob) << "slotSubJobFinished must be invoked by signal";
|
|
emit finished(-1, tr("Error updating metadata for a folder %1").arg(_fullRemotePath) + QStringLiteral(":%1").arg(message));
|
|
subJobsFinished(false);
|
|
return;
|
|
}
|
|
|
|
{
|
|
QMutexLocker locker(&_subJobSyncItemsMutex);
|
|
const auto foundInHash = _subJobSyncItems.constFind(job->path());
|
|
if (foundInHash != _subJobSyncItems.constEnd() && foundInHash.value()) {
|
|
foundInHash.value()->_e2eEncryptionStatus = job->encryptionStatus();
|
|
foundInHash.value()->_e2eEncryptionStatusRemote = job->encryptionStatus();
|
|
foundInHash.value()->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_account->capabilities().clientSideEncryptionVersion());
|
|
_subJobSyncItems.erase(foundInHash);
|
|
}
|
|
}
|
|
|
|
_subJobs.remove(job);
|
|
job->deleteLater();
|
|
|
|
if (_subJobs.isEmpty()) {
|
|
subJobsFinished(true);
|
|
} else {
|
|
_subJobs.values().last()->start();
|
|
}
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotCertificateFetchedFromKeychain(const QSslCertificate &certificate)
|
|
{
|
|
disconnect(_account->e2e(),
|
|
&ClientSideEncryption::certificateFetchedFromKeychain,
|
|
this,
|
|
&UpdateE2eeFolderUsersMetadataJob::slotCertificateFetchedFromKeychain);
|
|
if (certificate.isNull()) {
|
|
// get folder user's public key
|
|
_account->e2e()->getUsersPublicKeyFromServer(_account, {_folderUserId});
|
|
connect(_account->e2e(),
|
|
&ClientSideEncryption::certificatesFetchedFromServer,
|
|
this,
|
|
&UpdateE2eeFolderUsersMetadataJob::slotCertificatesFetchedFromServer);
|
|
return;
|
|
}
|
|
_folderUserCertificate = certificate;
|
|
emit certificateReady();
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::slotCertificatesFetchedFromServer(const QHash<QString, QSslCertificate> &results)
|
|
{
|
|
const auto certificate = results.isEmpty() ? QSslCertificate{} : results.value(_folderUserId);
|
|
_folderUserCertificate = certificate;
|
|
if (certificate.isNull()) {
|
|
emit certificateReady();
|
|
return;
|
|
}
|
|
_account->e2e()->writeCertificate(_account, _folderUserId, certificate);
|
|
connect(_account->e2e(), &ClientSideEncryption::certificateWriteComplete, this, &UpdateE2eeFolderUsersMetadataJob::certificateReady);
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::setUserData(const UserData &userData)
|
|
{
|
|
_userData = userData;
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::setFolderToken(const QByteArray &folderToken)
|
|
{
|
|
_folderToken = folderToken;
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::setMetadataKeyForEncryption(const QByteArray &metadataKey)
|
|
{
|
|
_metadataKeyForEncryption = metadataKey;
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::setMetadataKeyForDecryption(const QByteArray &metadataKey)
|
|
{
|
|
_metadataKeyForDecryption = metadataKey;
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::setKeyChecksums(const QSet<QByteArray> &keyChecksums)
|
|
{
|
|
_keyChecksums = keyChecksums;
|
|
}
|
|
|
|
void UpdateE2eeFolderUsersMetadataJob::setSubJobSyncItems(const QHash<QString, SyncFileItemPtr> &subJobSyncItems)
|
|
{
|
|
_subJobSyncItems = subJobSyncItems;
|
|
}
|
|
|
|
const QString &UpdateE2eeFolderUsersMetadataJob::path() const
|
|
{
|
|
return _fullRemotePath;
|
|
}
|
|
|
|
const UpdateE2eeFolderUsersMetadataJob::UserData &UpdateE2eeFolderUsersMetadataJob::userData() const
|
|
{
|
|
return _userData;
|
|
}
|
|
|
|
SyncFileItem::EncryptionStatus UpdateE2eeFolderUsersMetadataJob::encryptionStatus() const
|
|
{
|
|
const auto folderMetadata = _encryptedFolderMetadataHandler->folderMetadata();
|
|
const auto isMetadataValid = folderMetadata && folderMetadata->isValid();
|
|
if (!isMetadataValid) {
|
|
qCWarning(lcUpdateE2eeFolderUsersMetadataJob) << "_encryptedFolderMetadataHandler->folderMetadata() is invalid";
|
|
}
|
|
return !isMetadataValid
|
|
? EncryptionStatusEnums::ItemEncryptionStatus::NotEncrypted
|
|
: folderMetadata->encryptedMetadataEncryptionStatus();
|
|
}
|
|
|
|
const QByteArray UpdateE2eeFolderUsersMetadataJob::folderToken() const
|
|
{
|
|
return _encryptedFolderMetadataHandler->folderToken();
|
|
}
|
|
|
|
}
|