Merge pull request #5258 from nextcloud/feature/do-not-sync-enc-folders-if-e2ee-is-not-setup

Feature/do not sync enc folders if e2ee is not setup
This commit is contained in:
allexzander 2022-12-15 15:44:29 +01:00 committed by GitHub
commit f6cfb65939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 157 additions and 17 deletions

View file

@ -168,7 +168,9 @@ public:
SelectiveSyncWhiteList = 2, SelectiveSyncWhiteList = 2,
/** List of big sync folders that have not been confirmed by the user yet and that the UI /** List of big sync folders that have not been confirmed by the user yet and that the UI
* should notify about */ * 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 */ /* return the specified list from the database */
QStringList getSelectiveSyncList(SelectiveSyncListType type, bool *ok); QStringList getSelectiveSyncList(SelectiveSyncListType type, bool *ok);

View file

@ -47,6 +47,7 @@ enum CSYNC_EXCLUDE_TYPE {
CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED, CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
CSYNC_FILE_EXCLUDE_LEADING_SPACE, CSYNC_FILE_EXCLUDE_LEADING_SPACE,
CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE, CSYNC_FILE_EXCLUDE_LEADING_AND_TRAILING_SPACE,
CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED,
}; };
class ExcludedFilesTest; class ExcludedFilesTest;

View file

@ -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]() {
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() void AccountSettings::refreshSelectiveSyncStatus()
{ {
QString msg; QString msg;
@ -1478,6 +1542,8 @@ void AccountSettings::customizeStyle()
void AccountSettings::initializeE2eEncryption() void AccountSettings::initializeE2eEncryption()
{ {
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);
if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) { if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
slotE2eEncryptionMnemonicReady(); slotE2eEncryptionMnemonicReady();
} else { } else {
@ -1493,7 +1559,9 @@ void AccountSettings::initializeE2eEncryption()
if (!_accountState->account()->e2e()->_publicKey.isNull()) { if (!_accountState->account()->e2e()->_publicKey.isNull()) {
_ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device." _ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device."
"<br>" "<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); _accountState->account()->setE2eEncryptionKeysGenerationAllowed(false);

View file

@ -111,6 +111,11 @@ protected slots:
void slotSelectiveSyncChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, void slotSelectiveSyncChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles); 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: private:
void displayMnemonic(const QString &mnemonic); void displayMnemonic(const QString &mnemonic);
@ -139,6 +144,8 @@ private:
QAction *_addAccountAction; QAction *_addAccountAction;
bool _menuShown; bool _menuShown;
QHash<QString, QMetaObject::Connection> _folderConnections;
}; };
} // namespace OCC } // namespace OCC

View file

@ -60,6 +60,8 @@ static bool sortByFolderHeader(const FolderStatusModel::SubFolderInfo &lhs, cons
void FolderStatusModel::setAccountState(const AccountState *accountState) void FolderStatusModel::setAccountState(const AccountState *accountState)
{ {
connect(accountState->account()->e2e(), &OCC::ClientSideEncryption::initializationFinished, this, &FolderStatusModel::e2eInitializationFinished);
beginResetModel(); beginResetModel();
_dirty = false; _dirty = false;
_folders.clear(); _folders.clear();
@ -153,41 +155,49 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
case SubFolder: { case SubFolder: {
const auto &x = static_cast<SubFolderInfo *>(index.internalPointer())->_subs.at(index.row()); const auto &subfolderInfo = static_cast<SubFolderInfo *>(index.internalPointer())->_subs.at(index.row());
const auto supportsSelectiveSync = x._folder && x._folder->supportsSelectiveSync(); const auto supportsSelectiveSync = subfolderInfo._folder && subfolderInfo._folder->supportsSelectiveSync();
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole: {
//: Example text: "File.txt (23KB)" //: 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: 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: case Qt::CheckStateRole:
if (supportsSelectiveSync) { if (supportsSelectiveSync) {
return x._checked; return subfolderInfo._checked;
} else { } else {
return QVariant(); return QVariant();
} }
case Qt::DecorationRole: { case Qt::DecorationRole: {
if (x._isEncrypted) { if (subfolderInfo._isNonDecryptable && subfolderInfo._checked) {
return QIcon(QLatin1String(":/client/theme/lock-https.svg"));
} else if (x._size > 0 && isAnyAncestorEncrypted(index)) {
return QIcon(QLatin1String(":/client/theme/lock-broken.svg")); return QIcon(QLatin1String(":/client/theme/lock-broken.svg"));
} }
return QFileIconProvider().icon(x._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder); if (subfolderInfo._isEncrypted) {
return QIcon(QLatin1String(":/client/theme/lock-https.svg"));
} else if (subfolderInfo._size > 0 && isAnyAncestorEncrypted(index)) {
return QIcon(QLatin1String(":/client/theme/lock-broken.svg"));
}
return QFileIconProvider().icon(subfolderInfo._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder);
} }
case Qt::ForegroundRole: case Qt::ForegroundRole:
if (x._isUndecided) { if (subfolderInfo._isUndecided || (subfolderInfo._isNonDecryptable && subfolderInfo._checked)) {
return QColor(Qt::red); return QColor(Qt::red);
} }
break; break;
case FileIdRole: case FileIdRole:
return x._fileId; return subfolderInfo._fileId;
case FolderStatusDelegate::FolderPathRole: { case FolderStatusDelegate::FolderPathRole: {
auto f = x._folder; auto f = subfolderInfo._folder;
if (!f) if (!f)
return QVariant(); return QVariant();
return QVariant(f->path() + x._path); return QVariant(f->path() + subfolderInfo._path);
} }
} }
} }
@ -742,6 +752,10 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
newInfo._isEncrypted = encryptionMap.value(removeTrailingSlash(path)).toString() == QStringLiteral("1"); newInfo._isEncrypted = encryptionMap.value(removeTrailingSlash(path)).toString() == QStringLiteral("1");
newInfo._path = relativePath; newInfo._path = relativePath;
newInfo._isNonDecryptable = newInfo._isEncrypted
&& _accountState->account()->e2e() && !_accountState->account()->e2e()->_publicKey.isNull()
&& _accountState->account()->e2e()->_privateKey.isNull();
SyncJournalFileRecord rec; SyncJournalFileRecord rec;
if (!parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &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); qCWarning(lcFolderStatus) << "Could not get file record by E2E Mangled Name from local DB" << removeTrailingSlash(relativePath);
@ -1133,6 +1147,15 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
emit dataChanged(index(folderIndex), index(folderIndex), roles); 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) void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
{ {
if (!f) { if (!f) {

View file

@ -80,6 +80,8 @@ public:
Qt::CheckState _checked = Qt::Checked; Qt::CheckState _checked = Qt::Checked;
bool _isNonDecryptable = false;
// Whether this has a FetchLabel subrow // Whether this has a FetchLabel subrow
[[nodiscard]] bool hasLabel() const; [[nodiscard]] bool hasLabel() const;
@ -125,6 +127,7 @@ public slots:
void slotSyncAllPendingBigFolders(); void slotSyncAllPendingBigFolders();
void slotSyncNoPendingBigFolders(); void slotSyncNoPendingBigFolders();
void slotSetProgress(const OCC::ProgressInfo &progress); void slotSetProgress(const OCC::ProgressInfo &progress);
void e2eInitializationFinished(bool isNewMnemonicGenerated);
private slots: private slots:
void slotUpdateDirectories(const QStringList &); void slotUpdateDirectories(const QStringList &);

View file

@ -12,6 +12,7 @@
* for more details. * for more details.
*/ */
#include "account.h"
#include "discovery.h" #include "discovery.h"
#include "common/filesystembase.h" #include "common/filesystembase.h"
#include "common/syncjournaldb.h" #include "common/syncjournaldb.h"
@ -250,6 +251,13 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
excluded = CSYNC_FILE_EXCLUDE_TRAILING_SPACE; excluded = CSYNC_FILE_EXCLUDE_TRAILING_SPACE;
} else if (startsWithSpace) { } else if (startsWithSpace) {
excluded = CSYNC_FILE_EXCLUDE_LEADING_SPACE; 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;
}
} }
} }
@ -296,7 +304,10 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
if (excluded == CSYNC_NOT_EXCLUDED && !entries.localEntry.isSymLink) { if (excluded == CSYNC_NOT_EXCLUDED && !entries.localEntry.isSymLink) {
return false; 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); emit _discoveryData->silentlyExcluded(path);
return true; return true;
} }
@ -312,6 +323,7 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
} else { } else {
switch (excluded) { switch (excluded) {
case CSYNC_NOT_EXCLUDED: case CSYNC_NOT_EXCLUDED:
case CSYNC_FILE_E2E_COULD_NOT_DECRYPT_EXCLUDED:
case CSYNC_FILE_SILENTLY_EXCLUDED: case CSYNC_FILE_SILENTLY_EXCLUDED:
case CSYNC_FILE_EXCLUDE_AND_REMOVE: case CSYNC_FILE_EXCLUDE_AND_REMOVE:
qFatal("These were handled earlier"); qFatal("These were handled earlier");
@ -379,6 +391,27 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent
return true; 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, void ProcessDirectoryJob::processFile(PathTuple path,
const LocalInfo &localEntry, const RemoteInfo &serverEntry, const LocalInfo &localEntry, const RemoteInfo &serverEntry,
const SyncJournalFileRecord &dbEntry) const SyncJournalFileRecord &dbEntry)

View file

@ -148,6 +148,9 @@ private:
// path is the full relative path of the file. localName is the base name of the local entry. // 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); 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. /** Reconcile local/remote/db information for a single item.
* *
* Can be a file or a directory. * Can be a file or a directory.