Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/do not sync enc folders if e2ee is not setup #5258

Merged
Merged
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
4 changes: 3 additions & 1 deletion src/common/syncjournaldb.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
SelectiveSyncWhiteList = 2,
/** List of big sync folders that have not been confirmed by the user yet and that the UI
* should notify about */
SelectiveSyncUndecidedList = 3
SelectiveSyncUndecidedList = 3,
/** List of encrypted folders that will need to be removed from the blacklist when E2EE gets set up*/
SelectiveSyncE2eFoldersToRemoveFromBlacklist = 4,
};
/* return the specified list from the database */
QStringList getSelectiveSyncList(SelectiveSyncListType type, bool *ok);
Expand Down
1 change: 1 addition & 0 deletions src/csync/csync_exclude.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum CSYNC_EXCLUDE_TYPE {
CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
CSYNC_FILE_EXCLUDE_LEADING_SPACE,
CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE,
CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED,
};

class ExcludedFilesTest;
Expand Down
70 changes: 69 additions & 1 deletion src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,70 @@ void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft,
}
}

void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
{
if (_accountState->account()->e2e()->_mnemonic.isEmpty()) {
return;
}

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::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]() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, const auto?

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 msg;
Expand Down Expand Up @@ -1478,6 +1542,8 @@ void AccountSettings::customizeStyle()

void AccountSettings::initializeE2eEncryption()
{
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);

if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
slotE2eEncryptionMnemonicReady();
} else {
Expand All @@ -1493,7 +1559,9 @@ void AccountSettings::initializeE2eEncryption()
if (!_accountState->account()->e2e()->_publicKey.isNull()) {
_ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device."
"<br>"
"It can be enabled on this device by entering your mnemonic."));
"It can be enabled on this device by entering your mnemonic."
"<br>"
"This will enable synchronisation of existing encrypted folders."));
}
});
_accountState->account()->setE2eEncryptionKeysGenerationAllowed(false);
Expand Down
7 changes: 7 additions & 0 deletions src/gui/accountsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ protected slots:

void slotSelectiveSyncChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles);
void slotPossiblyUnblacklistE2EeFoldersAndRestartSync();

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:
void displayMnemonic(const QString &mnemonic);
Expand Down Expand Up @@ -139,6 +144,8 @@ protected slots:
QAction *_addAccountAction;

bool _menuShown;

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

} // namespace OCC
Expand Down
49 changes: 36 additions & 13 deletions src/gui/folderstatusmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ static bool sortByFolderHeader(const FolderStatusModel::SubFolderInfo &lhs, cons

void FolderStatusModel::setAccountState(const AccountState *accountState)
{
connect(accountState->account()->e2e(), &OCC::ClientSideEncryption::initializationFinished, this, &FolderStatusModel::e2eInitializationFinished);

beginResetModel();
_dirty = false;
_folders.clear();
Expand Down Expand Up @@ -153,41 +155,49 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
return QVariant();
}
case SubFolder: {
const auto &x = static_cast<SubFolderInfo *>(index.internalPointer())->_subs.at(index.row());
const auto supportsSelectiveSync = x._folder && x._folder->supportsSelectiveSync();
const auto &subfolderInfo = static_cast<SubFolderInfo *>(index.internalPointer())->_subs.at(index.row());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, so much better :D

const auto supportsSelectiveSync = subfolderInfo._folder && subfolderInfo._folder->supportsSelectiveSync();

switch (role) {
case Qt::DisplayRole:
case Qt::DisplayRole: {
//: Example text: "File.txt (23KB)"
return x._size < 0 ? x._name : tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size));
const auto &xParent = static_cast<SubFolderInfo *>(index.internalPointer());
const auto suffix = (subfolderInfo._isNonDecryptable && subfolderInfo._checked && (!xParent || !xParent->_isEncrypted))
? tr(" - %1").arg("Could not decrypt!")
: QString{};
return subfolderInfo._size < 0 ? QString(subfolderInfo._name + suffix) : QString(tr("%1 (%2)").arg(subfolderInfo._name, Utility::octetsToString(subfolderInfo._size)) + suffix);
}
case Qt::ToolTipRole:
return QString(QLatin1String("<qt>") + Utility::escape(x._size < 0 ? x._name : tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size))) + QLatin1String("</qt>"));
return QString(QLatin1String("<qt>") + Utility::escape(subfolderInfo._size < 0 ? subfolderInfo._name : tr("%1 (%2)").arg(subfolderInfo._name, Utility::octetsToString(subfolderInfo._size))) + QLatin1String("</qt>"));
case Qt::CheckStateRole:
if (supportsSelectiveSync) {
return x._checked;
return subfolderInfo._checked;
} else {
return QVariant();
}
case Qt::DecorationRole: {
if (x._isEncrypted) {
if (subfolderInfo._isNonDecryptable && subfolderInfo._checked) {
return QIcon(QLatin1String(":/client/theme/lock-broken.svg"));
}
if (subfolderInfo._isEncrypted) {
return QIcon(QLatin1String(":/client/theme/lock-https.svg"));
} else if (x._size > 0 && isAnyAncestorEncrypted(index)) {
} else if (subfolderInfo._size > 0 && isAnyAncestorEncrypted(index)) {
return QIcon(QLatin1String(":/client/theme/lock-broken.svg"));
}
return QFileIconProvider().icon(x._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder);
return QFileIconProvider().icon(subfolderInfo._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder);
}
case Qt::ForegroundRole:
if (x._isUndecided) {
if (subfolderInfo._isUndecided || (subfolderInfo._isNonDecryptable && subfolderInfo._checked)) {
return QColor(Qt::red);
}
break;
case FileIdRole:
return x._fileId;
return subfolderInfo._fileId;
case FolderStatusDelegate::FolderPathRole: {
auto f = x._folder;
auto f = subfolderInfo._folder;
if (!f)
return QVariant();
return QVariant(f->path() + x._path);
return QVariant(f->path() + subfolderInfo._path);
}
}
}
Expand Down Expand Up @@ -742,6 +752,10 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
newInfo._isEncrypted = encryptionMap.value(removeTrailingSlash(path)).toString() == QStringLiteral("1");
newInfo._path = relativePath;

newInfo._isNonDecryptable = newInfo._isEncrypted
&& _accountState->account()->e2e() && !_accountState->account()->e2e()->_publicKey.isNull()
&& _accountState->account()->e2e()->_privateKey.isNull();

SyncJournalFileRecord rec;
if (!parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec)) {
qCWarning(lcFolderStatus) << "Could not get file record by E2E Mangled Name from local DB" << removeTrailingSlash(relativePath);
Expand Down Expand Up @@ -1133,6 +1147,15 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
emit dataChanged(index(folderIndex), index(folderIndex), roles);
}

void FolderStatusModel::e2eInitializationFinished(bool isNewMnemonicGenerated)
{
Q_UNUSED(isNewMnemonicGenerated);

for (int i = 0; i < _folders.count(); ++i) {
resetAndFetch(index(i));
}
}

void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
{
if (!f) {
Expand Down
3 changes: 3 additions & 0 deletions src/gui/folderstatusmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class FolderStatusModel : public QAbstractItemModel

Qt::CheckState _checked = Qt::Checked;

bool _isNonDecryptable = false;

// Whether this has a FetchLabel subrow
[[nodiscard]] bool hasLabel() const;

Expand Down Expand Up @@ -125,6 +127,7 @@ public slots:
void slotSyncAllPendingBigFolders();
void slotSyncNoPendingBigFolders();
void slotSetProgress(const OCC::ProgressInfo &progress);
void e2eInitializationFinished(bool isNewMnemonicGenerated);

private slots:
void slotUpdateDirectories(const QStringList &);
Expand Down
35 changes: 34 additions & 1 deletion src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* for more details.
*/

#include "account.h"
#include "discovery.h"
#include "common/filesystembase.h"
#include "common/syncjournaldb.h"
Expand Down Expand Up @@ -250,6 +251,13 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
excluded = CSYNC_FILE_EXCLUDE_TRAILING_SPACE;
} else if (startsWithSpace) {
excluded = CSYNC_FILE_EXCLUDE_LEADING_SPACE;
} else if (entries.serverEntry.isValid() && entries.serverEntry.isE2eEncrypted) {
const auto wasE2eEnabledButNotSetup = _discoveryData->_account->e2e()
&& !_discoveryData->_account->e2e()->_publicKey.isNull()
&& _discoveryData->_account->e2e()->_privateKey.isNull();
if (wasE2eEnabledButNotSetup) {
excluded = CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED;
}
}
}

Expand Down Expand Up @@ -296,7 +304,10 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent

if (excluded == CSYNC_NOT_EXCLUDED && !entries.localEntry.isSymLink) {
return false;
} else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
} else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE || excluded == CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED) {
if (excluded == CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED && isDirectory && path != QStringLiteral("/")) {
checkAndUpdateSelectiveSyncListsForE2eeFolders(path);
}
emit _discoveryData->silentlyExcluded(path);
return true;
}
Expand All @@ -312,6 +323,7 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
} else {
switch (excluded) {
case CSYNC_NOT_EXCLUDED:
case CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED:
case CSYNC_FILE_SILENTLY_EXCLUDED:
case CSYNC_FILE_EXCLUDE_AND_REMOVE:
qFatal("These were handled earlier");
Expand Down Expand Up @@ -379,6 +391,27 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
return true;
}

void ProcessDirectoryJob::checkAndUpdateSelectiveSyncListsForE2eeFolders(const QString &path)
{
bool ok = false;
auto blackList = _discoveryData->_statedb->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
auto selectiveSyncE2eFoldersToRemoveFromBlacklist =
_discoveryData->_statedb->getSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, &ok);
const auto pathWithTrailingSpace = path.endsWith(QLatin1Char('/')) ? path : path + QLatin1Char('/');
if (!blackList.contains(pathWithTrailingSpace)) {
blackList.push_back(pathWithTrailingSpace);
blackList.sort();
_discoveryData->_statedb->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
}
// record it into a separate list to automatically remove from blacklist once the e2EE gets set up
if (!selectiveSyncE2eFoldersToRemoveFromBlacklist.contains(pathWithTrailingSpace)) {
selectiveSyncE2eFoldersToRemoveFromBlacklist.push_back(pathWithTrailingSpace);
selectiveSyncE2eFoldersToRemoveFromBlacklist.sort();
_discoveryData->_statedb->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist,
selectiveSyncE2eFoldersToRemoveFromBlacklist);
}
}

void ProcessDirectoryJob::processFile(PathTuple path,
const LocalInfo &localEntry, const RemoteInfo &serverEntry,
const SyncJournalFileRecord &dbEntry)
Expand Down
3 changes: 3 additions & 0 deletions src/libsync/discovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ class ProcessDirectoryJob : public QObject
// path is the full relative path of the file. localName is the base name of the local entry.
bool handleExcluded(const QString &path, const Entries &entries, bool isHidden);

// check if the path is an e2e encrypted and the e2ee is not set up, and insert it into a corresponding list in the sync journal
void checkAndUpdateSelectiveSyncListsForE2eeFolders(const QString &path);

/** Reconcile local/remote/db information for a single item.
*
* Can be a file or a directory.
Expand Down