mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-24 14:05:58 +03:00
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:
commit
f6cfb65939
8 changed files with 157 additions and 17 deletions
|
@ -168,7 +168,9 @@ public:
|
|||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
QString msg;
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
@ -139,6 +144,8 @@ private:
|
|||
QAction *_addAccountAction;
|
||||
|
||||
bool _menuShown;
|
||||
|
||||
QHash<QString, QMetaObject::Connection> _folderConnections;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -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();
|
||||
|
@ -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());
|
||||
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) {
|
||||
return QIcon(QLatin1String(":/client/theme/lock-https.svg"));
|
||||
} else if (x._size > 0 && isAnyAncestorEncrypted(index)) {
|
||||
if (subfolderInfo._isNonDecryptable && subfolderInfo._checked) {
|
||||
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:
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
|
|
@ -80,6 +80,8 @@ public:
|
|||
|
||||
Qt::CheckState _checked = Qt::Checked;
|
||||
|
||||
bool _isNonDecryptable = false;
|
||||
|
||||
// Whether this has a FetchLabel subrow
|
||||
[[nodiscard]] bool hasLabel() const;
|
||||
|
||||
|
@ -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 &);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
#include "account.h"
|
||||
#include "discovery.h"
|
||||
#include "common/filesystembase.h"
|
||||
#include "common/syncjournaldb.h"
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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)
|
||||
|
|
|
@ -148,6 +148,9 @@ private:
|
|||
// 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.
|
||||
|
|
Loading…
Reference in a new issue