Do not sync encrypted folders if the E2EE mnemonic is not entered. Automatically...
authoralex-z <blackslayer4@gmail.com>
Wed, 7 Dec 2022 18:43:27 +0000 (19:43 +0100)
committeralex-z <blackslayer4@gmail.com>
Thu, 15 Dec 2022 14:09:25 +0000 (15:09 +0100)
Signed-off-by: alex-z <blackslayer4@gmail.com>
src/common/syncjournaldb.h
src/csync/csync_exclude.h
src/gui/accountsettings.cpp
src/gui/accountsettings.h
src/gui/folderstatusmodel.cpp
src/gui/folderstatusmodel.h
src/libsync/discovery.cpp
src/libsync/discovery.h

index 998a93392117f57fadb26ba5c242eeb7300df942..18e1ebd38d98a0d067c3dc5999992cfec75c550a 100644 (file)
@@ -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);
index d701401afe90bc1a5267a90f288683ea0e6a973f..0fd9acbf3b1f831b631c9eae0bc959eebc02b9b0 100644 (file)
@@ -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;
index 2b417699ad6652a5d9d5d375c3c04a46dfef88ed..3fa71150b7998d9f9b58a5e4192f4902ec6d7468 100644 (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()
 {
     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);
index cf32bc73716387d5f8ffa9e46830307a59a46bc9..f8972d8952604a79fe2e6ac594d0c87fd41b17c0 100644 (file)
@@ -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
index fe981726e1b3a3d0a032f09126cc08bc5bb81c8f..a64189dd880ffc60e40a0fa3718a6bfa9648cecf 100644 (file)
@@ -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) {
+            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);
         }
         }
     }
@@ -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) {
index f5fe5018f4ca76fae1b65ecc6ef5947198166930..e2397295c7d4a6077cf77570c6ee6ba1f47e65e7 100644 (file)
@@ -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 &);
index aee314020fc4f4c340e3939a3345ce8b93f3f18a..79758a777aa2fb762b2cfb8b4414256f001d01b3 100644 (file)
@@ -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)
index 1be9f53729d7870a8a79a3330cb55d36f90a63f3..c77e310821f53f4174a559c8002bfedd5bad2a4d 100644 (file)
@@ -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.