Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 15 additions & 59 deletions src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1497,37 +1497,9 @@ void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft,

void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
{
if (!_accountState->account()->e2e()->isInitialized()) {
return;
}

// FolderMan handles E2E folder restoration globally via slotE2eInitializationStateChanged
// Just disconnect this signal to avoid duplicate handling
disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);

for (const auto folder : FolderMan::instance()->map()) {
if (folder->accountState() != _accountState) {
continue;
}
bool ok = false;
const auto foldersToRemoveFromBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, &ok);
if (foldersToRemoveFromBlacklist.isEmpty()) {
continue;
}
auto blackList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
const auto blackListSize = blackList.size();
if (blackListSize == 0) {
continue;
}
for (const auto &pathToRemoveFromBlackList : foldersToRemoveFromBlacklist) {
blackList.removeAll(pathToRemoveFromBlackList);
}
if (blackList.size() != blackListSize) {
if (folder->isSyncRunning()) {
folderTerminateSyncAndUpdateBlackList(blackList, folder, foldersToRemoveFromBlacklist);
return;
}
updateBlackListAndScheduleFolderSync(blackList, folder, foldersToRemoveFromBlacklist);
}
}
}

void AccountSettings::slotE2eEncryptionCertificateNeedMigration()
Expand All @@ -1538,35 +1510,6 @@ void AccountSettings::slotE2eEncryptionCertificateNeedMigration()
});
}

void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const
{
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, {});
for (const auto &pathToRemoteDiscover : foldersToRemoveFromBlacklist) {
folder->journalDb()->schedulePathForRemoteDiscovery(pathToRemoteDiscover);
}
FolderMan::instance()->scheduleFolder(folder);
}

void AccountSettings::folderTerminateSyncAndUpdateBlackList(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist)
{
if (_folderConnections.contains(folder->alias())) {
qCWarning(lcAccountSettings) << "Folder " << folder->alias() << "is already terminating the sync.";
return;
}
// in case sync is already running - terminate it and start a new one
const QMetaObject::Connection syncTerminatedConnection = connect(folder, &Folder::syncFinished, this, [this, blackList, folder, foldersToRemoveFromBlacklist]() {
const auto foundConnectionIt = _folderConnections.find(folder->alias());
if (foundConnectionIt != _folderConnections.end()) {
disconnect(*foundConnectionIt);
_folderConnections.erase(foundConnectionIt);
}
updateBlackListAndScheduleFolderSync(blackList, folder, foldersToRemoveFromBlacklist);
});
_folderConnections.insert(folder->alias(), syncTerminatedConnection);
folder->slotTerminateSync();
}

void AccountSettings::refreshSelectiveSyncStatus()
{
QString unsyncedFoldersString;
Expand Down Expand Up @@ -1685,6 +1628,8 @@ void AccountSettings::setupE2eEncryption()

if (_accountState->account()->e2e()->isInitialized()) {
slotE2eEncryptionMnemonicReady();
// Ensure we restore any E2E folders that may have been blacklisted during startup
QTimer::singleShot(100, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);
} else {
setupE2eEncryptionMessage();

Expand Down Expand Up @@ -1714,6 +1659,17 @@ void AccountSettings::forgetE2eEncryption()
const auto account = _accountState->account();
if (!account->e2e()->isInitialized()) {
FolderMan::instance()->removeE2eFiles(account);

// Clear E2E restoration tracking list for all folders
for (const auto folder : FolderMan::instance()->map()) {
if (folder->accountState()->account() == account) {
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, {});
}
}

// Reset E2E initialization state to allow re-setup
account->setE2eEncryptionKeysGenerationAllowed(false);
account->setAskUserForMnemonic(false);
}
}

Expand Down
6 changes: 0 additions & 6 deletions src/gui/accountsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,6 @@ protected slots:

void slotE2eEncryptionCertificateNeedMigration();

private slots:
void updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const;
void folderTerminateSyncAndUpdateBlackList(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist);

private slots:
void displayMnemonic(const QString &mnemonic);
void forgetEncryptionOnDeviceForAccount(const OCC::AccountPtr &account) const;
Expand Down Expand Up @@ -147,8 +143,6 @@ private slots:
QAction *_addAccountAction = nullptr;

bool _menuShown = false;

QHash<QString, QMetaObject::Connection> _folderConnections;
};

} // namespace OCC
Expand Down
84 changes: 84 additions & 0 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <pushnotifications.h>
#include <syncengine.h>
#include "updatee2eefolderusersmetadatajob.h"
#include "libsync/clientsideencryption.h"

#ifdef Q_OS_MACOS
#include <CoreServices/CoreServices.h>
Expand Down Expand Up @@ -861,6 +862,23 @@ void FolderMan::slotAccountStateChanged()

if (accountState->isConnected()) {
qCInfo(lcFolderMan) << "Account" << accountName << "connected, scheduling its folders";

// Set up E2E initialization tracking for this account
if (accountState->account()->capabilities().clientSideEncryptionAvailable()) {
if (accountState->account()->e2e()) {
// Connect to E2E initialization signals to restore blacklisted folders when ready
connect(accountState->account()->e2e(), &ClientSideEncryption::initializationStateChanged,
this, &FolderMan::slotE2eInitializationStateChanged, Qt::UniqueConnection);

// If E2E is already initialized, trigger immediate folder restoration
if (accountState->account()->e2e()->isInitialized()) {
qCInfo(lcFolderMan) << "E2E already initialized for account" << accountName << "- checking for folders to restore";
QTimer::singleShot(500, [this, accountState]() {
restoreE2eFoldersForAccount(accountState);
});
}
}
}

const auto folderMapValues = _folderMap.values();
for (const auto f : folderMapValues) {
Expand Down Expand Up @@ -1364,6 +1382,72 @@ void FolderMan::addFolderToSelectiveSyncList(const QString &path, const SyncJour
}
}

void FolderMan::restoreE2eFoldersForAccount(AccountState *accountState)
{
if (!accountState || !accountState->account()->e2e() || !accountState->account()->e2e()->isInitialized()) {
qCWarning(lcFolderMan) << "Cannot restore E2E folders - E2E not properly initialized";
return;
}

qCInfo(lcFolderMan) << "Restoring E2E encrypted folders for account" << accountState->account()->displayName();

for (const auto &folder : _folderMap) {
if (folder->accountState() != accountState) {
continue;
}

bool ok = false;
const auto foldersToRemoveFromBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, &ok);
if (!ok || foldersToRemoveFromBlacklist.isEmpty()) {
continue;
}

auto blackList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
if (!ok) {
qCWarning(lcFolderMan) << "Failed to read blacklist for folder" << folder->alias();
continue;
}

bool folderModified = false;
for (const auto &pathToRemove : foldersToRemoveFromBlacklist) {
if (blackList.removeAll(pathToRemove) > 0) {
folderModified = true;
qCInfo(lcFolderMan) << "Restored E2E folder from blacklist:" << pathToRemove << "in folder" << folder->alias();
}
}

if (folderModified) {
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, {});

// Schedule the folder for sync to pick up the restored folders
if (folder->canSync()) {
scheduleFolder(folder);
}
} else {
// Clear the restoration list even if no changes were made
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, {});
}
}
}

void FolderMan::slotE2eInitializationStateChanged(ClientSideEncryption::InitializationState state)
{
qCInfo(lcFolderMan) << "E2E initialization state changed to:" << static_cast<int>(state);

if (state == ClientSideEncryption::InitializationState::Initialized) {
// E2E is now initialized - restore blacklisted folders and schedule resync
qCInfo(lcFolderMan) << "E2E initialized - restoring encrypted folders";

const auto accountStates = AccountManager::instance()->accounts();
for (const auto &accountState : accountStates) {
if (accountState && accountState->account() && accountState->account()->e2e() && accountState->account()->e2e()->isInitialized()) {
restoreE2eFoldersForAccount(accountState.data());
}
}
}
}

void FolderMan::whitelistFolderPath(const QString &path)
{
addFolderToSelectiveSyncList(path, SyncJournalDb::SelectiveSyncListType::SelectiveSyncWhiteList);
Expand Down
4 changes: 4 additions & 0 deletions src/gui/folderman.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "navigationpanehelper.h"
#endif
#include "syncfileitem.h"
#include "libsync/clientsideencryption.h"

class TestFolderMan;
class TestCfApiShellExtensionsIPC;
Expand Down Expand Up @@ -294,6 +295,7 @@ private slots:
void slotFolderCanSyncChanged();
void slotFolderSyncStarted();
void slotFolderSyncFinished(const OCC::SyncResult &);
void slotE2eInitializationStateChanged(OCC::ClientSideEncryption::InitializationState state);

void slotRunOneEtagJob();
void slotEtagJobDestroyed(QObject *);
Expand Down Expand Up @@ -368,6 +370,8 @@ private slots:
[[nodiscard]] bool isSwitchToVfsNeeded(const FolderDefinition &folderDefinition) const;

void addFolderToSelectiveSyncList(const QString &path, const SyncJournalDb::SelectiveSyncListType list);

void restoreE2eFoldersForAccount(AccountState *accountState);

QSet<Folder *> _disabledFolders;
Folder::Map _folderMap;
Expand Down
30 changes: 30 additions & 0 deletions src/libsync/clientsideencryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,16 @@ bool ClientSideEncryption::isInitialized() const
return useTokenBasedEncryption() || !getMnemonic().isEmpty();
}

bool ClientSideEncryption::isInitializing() const
{
return _initializationState == InitializationState::Initializing;
}

ClientSideEncryption::InitializationState ClientSideEncryption::initializationState() const
{
return _initializationState;
}

QSslKey ClientSideEncryption::getPublicKey() const
{
return _encryptionCertificate.getSslPublicKey();
Expand Down Expand Up @@ -1051,8 +1061,19 @@ void ClientSideEncryption::initialize(QWidget *settingsDialog)
Q_ASSERT(_account);

qCInfo(lcCse()) << "Initializing";

if (_initializationState == InitializationState::Initializing) {
qCWarning(lcCse()) << "E2E encryption already initializing, ignoring duplicate request";
return;
}

_initializationState = InitializationState::Initializing;
emit initializationStateChanged(_initializationState);

if (!_account->capabilities().clientSideEncryptionAvailable()) {
qCInfo(lcCse()) << "No Client side encryption available on server.";
_initializationState = InitializationState::Failed;
emit initializationStateChanged(_initializationState);
emit initializationFinished();
return;
}
Expand Down Expand Up @@ -1795,6 +1816,11 @@ void ClientSideEncryption::forgetSensitiveData()
_encryptionCertificate.clear();
_otherCertificates.clear();
_context.clear();

// Reset initialization state so E2E can be re-initialized
_initializationState = InitializationState::NotStarted;
emit initializationStateChanged(_initializationState);

Q_EMIT canDecryptChanged();
Q_EMIT canEncryptChanged();
Q_EMIT userCertificateNeedsMigrationChanged();
Expand Down Expand Up @@ -1901,6 +1927,8 @@ bool ClientSideEncryption::sensitiveDataRemaining() const
void ClientSideEncryption::failedToInitialize()
{
forgetSensitiveData();
_initializationState = InitializationState::Failed;
emit initializationStateChanged(_initializationState);
Q_EMIT initializationFinished();
}

Expand Down Expand Up @@ -2110,6 +2138,8 @@ void ClientSideEncryption::sendPublicKey()
case 200:
case 409:
saveCertificateIdentification();
_initializationState = InitializationState::Initialized;
emit initializationStateChanged(_initializationState);
emit initializationFinished();

break;
Expand Down
16 changes: 16 additions & 0 deletions src/libsync/clientsideencryption.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,25 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {
FatalError,
};

enum class InitializationState
{
NotStarted,
Initializing,
Initialized,
Failed
};

Q_ENUM(EncryptionErrorType)
Q_ENUM(InitializationState)

explicit ClientSideEncryption();

[[nodiscard]] bool isInitialized() const;

[[nodiscard]] bool isInitializing() const;

[[nodiscard]] InitializationState initializationState() const;

[[nodiscard]] bool tokenIsSetup() const;

[[nodiscard]] QSslKey getPublicKey() const;
Expand Down Expand Up @@ -297,6 +310,7 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {

signals:
void initializationFinished(bool isNewMnemonicGenerated = false);
void initializationStateChanged(InitializationState state);
void sensitiveDataForgotten();
void privateKeyDeleted();
void certificateDeleted();
Expand Down Expand Up @@ -400,6 +414,8 @@ private slots:

AccountPtr _account;

InitializationState _initializationState = InitializationState::NotStarted;

QString _mnemonic;
bool _newMnemonicGenerated = false;

Expand Down
Loading