diff --git a/.drone.yml b/.drone.yml
index 4a76132e95f5..177cc676ffac 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -198,4 +198,3 @@ trigger:
kind: signature
hmac: a8fd97516ee53ca8c938ad413e030dc7df483f116c4b19b5811e359960b7b44d
-...
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 687733588813..3572266e9889 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -220,20 +220,25 @@ if(BUILD_CLIENT)
find_package(Sphinx)
find_package(PdfLatex)
find_package(OpenSSL 1.1 REQUIRED )
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(OPENSC-LIBP11 libp11 IMPORTED_TARGET)
- find_package(ZLIB REQUIRED)
- find_package(SQLite3 3.9.0 REQUIRED)
+ set(ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "c:/Windows/System32/eTPKCS11.dll" CACHE PATH "Path to the driver for end-to-end encryption token")
+ option(CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN "Enforce use of an hardware token for end-to-end encryption" true)
- if(NOT WIN32 AND NOT APPLE)
- find_package(PkgConfig REQUIRED)
- pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
+ find_package(ZLIB REQUIRED)
+ find_package(SQLite3 3.9.0 REQUIRED)
- if(CLOUDPROVIDERS_FOUND)
- pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
- pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
- pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
- endif()
- endif()
+ if(NOT WIN32 AND NOT APPLE)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
+
+ if(CLOUDPROVIDERS_FOUND)
+ pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
+ pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
+ pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
+ endif()
+ endif()
endif()
if (APPLE)
diff --git a/NEXTCLOUD.cmake b/NEXTCLOUD.cmake
index 4b49a32b300b..64cfb4986fb0 100644
--- a/NEXTCLOUD.cmake
+++ b/NEXTCLOUD.cmake
@@ -54,7 +54,6 @@ set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CA
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header")
option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.(png|svg)' else the default application icon is used" ON )
-
#
## Windows Shell Extensions & MSI - IMPORTANT: Generate new GUIDs for custom builds with "guidgen" or "uuidgen"
#
diff --git a/admin/linux/build-appimage.sh b/admin/linux/build-appimage.sh
index fb1898328734..bbabc9d87a1c 100755
--- a/admin/linux/build-appimage.sh
+++ b/admin/linux/build-appimage.sh
@@ -73,6 +73,7 @@ cp -R /usr/lib/x86_64-linux-gnu/libcrypto.so* ./usr/lib/
cp -P /usr/local/lib*/libssl.so* ./usr/lib/
cp -P /usr/local/lib*/libcrypto.so* ./usr/lib/
cp -P /usr/local/lib*/libsqlite*.so* ./usr/lib/
+cp -P /usr/local/lib*/libp11.so* ./usr/lib/
# NSS fun
cp -P -r /usr/lib/x86_64-linux-gnu/nss ./usr/lib/
diff --git a/config.h.in b/config.h.in
index ca9907c52f29..5baea3153fc5 100644
--- a/config.h.in
+++ b/config.h.in
@@ -61,4 +61,8 @@
#cmakedefine CFAPI_SHELL_EXTENSIONS_LIB_NAME "@CFAPI_SHELL_EXTENSIONS_LIB_NAME@"
+#cmakedefine01 CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN
+
+#cmakedefine ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "@ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH@"
+
#endif
diff --git a/resources.qrc b/resources.qrc
index 7222e8c9ff0e..270c85b9e7d4 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -7,6 +7,7 @@
src/gui/PredefinedStatusButton.qml
src/gui/BasicComboBox.qml
src/gui/ErrorBox.qml
+ src/gui/EncryptionTokenSelectionWindow.qml
src/gui/filedetails/FileActivityView.qml
src/gui/filedetails/FileDetailsPage.qml
src/gui/filedetails/FileDetailsView.qml
@@ -48,6 +49,7 @@
src/gui/tray/TalkReplyTextField.qml
src/gui/tray/CallNotificationDialog.qml
src/gui/tray/EditFileLocallyLoadingDialog.qml
+ src/gui/tray/EncryptionTokenDiscoveryDialog.qml
src/gui/tray/NCBusyIndicator.qml
src/gui/tray/NCIconWithBackgroundImage.qml
src/gui/tray/NCToolTip.qml
diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp
index 895ad115c4d8..5683d2d4907a 100644
--- a/src/common/syncjournaldb.cpp
+++ b/src/common/syncjournaldb.cpp
@@ -48,7 +48,7 @@ Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg)
#define GET_FILE_RECORD_QUERY \
"SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \
- " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \
+ " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, " \
" lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe" \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"
@@ -67,16 +67,18 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
rec._checksumHeader = query.baValue(9);
rec._e2eMangledName = query.baValue(10);
rec._e2eEncryptionStatus = static_cast(query.intValue(11));
- rec._lockstate._locked = query.intValue(12) > 0;
- rec._lockstate._lockOwnerDisplayName = query.stringValue(13);
- rec._lockstate._lockOwnerId = query.stringValue(14);
- rec._lockstate._lockOwnerType = query.int64Value(15);
- rec._lockstate._lockEditorApp = query.stringValue(16);
- rec._lockstate._lockTime = query.int64Value(17);
- rec._lockstate._lockTimeout = query.int64Value(18);
- rec._isShared = query.intValue(19) > 0;
- rec._lastShareStateFetchedTimestamp = query.int64Value(20);
- rec._sharedByMe = query.intValue(21) > 0;
+ rec._e2eCertificateFingerprint = query.baValue(12);
+ Q_ASSERT(rec._e2eEncryptionStatus == SyncJournalFileRecord::EncryptionStatus::NotEncrypted || !rec._e2eCertificateFingerprint.isEmpty());
+ rec._lockstate._locked = query.intValue(13) > 0;
+ rec._lockstate._lockOwnerDisplayName = query.stringValue(14);
+ rec._lockstate._lockOwnerId = query.stringValue(15);
+ rec._lockstate._lockOwnerType = query.int64Value(16);
+ rec._lockstate._lockEditorApp = query.stringValue(17);
+ rec._lockstate._lockTime = query.int64Value(18);
+ rec._lockstate._lockTimeout = query.int64Value(19);
+ rec._isShared = query.intValue(20) > 0;
+ rec._lastShareStateFetchedTimestamp = query.int64Value(21);
+ rec._sharedByMe = query.intValue(22) > 0;
}
static QByteArray defaultJournalMode(const QString &dbPath)
@@ -777,6 +779,7 @@ bool SyncJournalDb::updateMetadataTableStructure()
addColumn(QStringLiteral("contentChecksumTypeId"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("e2eMangledName"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER"));
+ addColumn(QStringLiteral("e2eCertificateFingerprint"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("isShared"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("lastShareStateFetchedTimestmap"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("sharedByMe"), QStringLiteral("INTEGER"));
@@ -983,9 +986,9 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, "
- "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
+ "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
"lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe) "
- "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28);"),
+ "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29);"),
_db);
if (!query) {
return query->error();
@@ -1009,16 +1012,17 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
query->bindValue(16, contentChecksumTypeId);
query->bindValue(17, record._e2eMangledName);
query->bindValue(18, static_cast(record._e2eEncryptionStatus));
- query->bindValue(19, record._lockstate._locked ? 1 : 0);
- query->bindValue(20, record._lockstate._lockOwnerType);
- query->bindValue(21, record._lockstate._lockOwnerDisplayName);
- query->bindValue(22, record._lockstate._lockOwnerId);
- query->bindValue(23, record._lockstate._lockEditorApp);
- query->bindValue(24, record._lockstate._lockTime);
- query->bindValue(25, record._lockstate._lockTimeout);
- query->bindValue(26, record._isShared);
- query->bindValue(27, record._lastShareStateFetchedTimestamp);
- query->bindValue(28, record._sharedByMe);
+ query->bindValue(19, record._e2eCertificateFingerprint);
+ query->bindValue(20, record._lockstate._locked ? 1 : 0);
+ query->bindValue(21, record._lockstate._lockOwnerType);
+ query->bindValue(22, record._lockstate._lockOwnerDisplayName);
+ query->bindValue(23, record._lockstate._lockOwnerId);
+ query->bindValue(24, record._lockstate._lockEditorApp);
+ query->bindValue(25, record._lockstate._lockTime);
+ query->bindValue(26, record._lockstate._lockTimeout);
+ query->bindValue(27, record._isShared);
+ query->bindValue(28, record._lastShareStateFetchedTimestamp);
+ query->bindValue(29, record._sharedByMe);
if (!query->exec()) {
return query->error();
diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h
index 7270fac13796..38abaf5255c4 100644
--- a/src/common/syncjournalfilerecord.h
+++ b/src/common/syncjournalfilerecord.h
@@ -83,6 +83,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord
QByteArray _checksumHeader;
QByteArray _e2eMangledName;
EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted;
+ QByteArray _e2eCertificateFingerprint;
SyncJournalFileLockInfo _lockstate;
bool _isShared = false;
qint64 _lastShareStateFetchedTimestamp = 0;
diff --git a/src/csync/csync.h b/src/csync/csync.h
index 235f0cd729af..4950d65f6886 100644
--- a/src/csync/csync.h
+++ b/src/csync/csync.h
@@ -136,23 +136,24 @@ Q_ENUM_NS(csync_status_codes_e)
* the csync state of a file.
*/
enum SyncInstructions {
- CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */
- CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */
- CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */
- CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */
- CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
- CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */
- CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */
- CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */
- CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */
- CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7,
- CSYNC_INSTRUCTION_ERROR = 1 << 8,
- CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE)
- Used when the type of something changes from directory to file
- or back. */
- CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db,
- but without any propagation (UPDATE|RECONCILE) */
- CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
+ CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */
+ CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */
+ CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */
+ CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */
+ CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
+ CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */
+ CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */
+ CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */
+ CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */
+ CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7,
+ CSYNC_INSTRUCTION_ERROR = 1 << 8,
+ CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE)
+ Used when the type of something changes from directory to file
+ or back. */
+ CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db,
+ but without any propagation (UPDATE|RECONCILE) */
+ CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
+ CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA = 1 << 13,
};
Q_ENUM_NS(SyncInstructions)
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 6e4acf203d19..75f2d8ee530b 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -156,6 +156,7 @@ set(client_SRCS
syncrunfilelog.cpp
systray.h
systray.cpp
+ EncryptionTokenSelectionWindow.qml
thumbnailjob.h
thumbnailjob.cpp
userinfo.h
diff --git a/src/gui/EncryptionTokenSelectionWindow.qml b/src/gui/EncryptionTokenSelectionWindow.qml
new file mode 100644
index 000000000000..8872d978335a
--- /dev/null
+++ b/src/gui/EncryptionTokenSelectionWindow.qml
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 by Matthieu Gallien
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+import QtQml.Models 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+import "./tray"
+
+ApplicationWindow {
+ id: encryptionKeyChooserDialog
+
+ required property var certificatesInfo
+ required property ClientSideEncryptionTokenSelector certificateSelector
+ property string selectedSerialNumber: ''
+
+ flags: Qt.Window | Qt.Dialog
+ visible: true
+ modality: Qt.ApplicationModal
+
+ width: 400
+ height: 600
+ minimumWidth: 400
+ minimumHeight: 600
+
+ title: qsTr('Token Encryption Key Chooser')
+
+ // TODO: Rather than setting all these palette colours manually,
+ // create a custom style and do it for all components globally
+ palette {
+ text: Style.ncTextColor
+ windowText: Style.ncTextColor
+ buttonText: Style.ncTextColor
+ brightText: Style.ncTextBrightColor
+ highlight: Style.lightHover
+ highlightedText: Style.ncTextColor
+ light: Style.lightHover
+ midlight: Style.ncSecondaryTextColor
+ mid: Style.darkerHover
+ dark: Style.menuBorder
+ button: Style.buttonBackgroundColor
+ window: Style.backgroundColor
+ base: Style.backgroundColor
+ toolTipBase: Style.backgroundColor
+ toolTipText: Style.ncTextColor
+ }
+
+ onClosing: function(close) {
+ Systray.destroyDialog(self);
+ close.accepted = true
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 20
+ anchors.rightMargin: 20
+ anchors.bottomMargin: 20
+ anchors.topMargin: 20
+ spacing: 15
+ z: 2
+
+ EnforcedPlainTextLabel {
+ text: qsTr("Available Keys for end-to-end Encryption:")
+ font.bold: true
+ font.pixelSize: Style.bigFontPixelSizeResolveConflictsDialog
+ Layout.fillWidth: true
+ }
+
+ ScrollView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ clip: true
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+ ListView {
+ id: tokensListView
+
+ currentIndex: -1
+
+ model: DelegateModel {
+ model: certificatesInfo
+
+ delegate: ItemDelegate {
+ width: tokensListView.contentItem.width
+
+ text: modelData.subject
+
+ highlighted: tokensListView.currentIndex === index
+
+ onClicked: function()
+ {
+ tokensListView.currentIndex = index
+ selectedSerialNumber = modelData.serialNumber
+ }
+ }
+ }
+ }
+ }
+
+ DialogButtonBox {
+ Layout.fillWidth: true
+
+ Button {
+ text: qsTr("Choose")
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ }
+ Button {
+ text: qsTr("Cancel")
+ DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
+ }
+
+ onAccepted: function() {
+ Systray.destroyDialog(encryptionKeyChooserDialog)
+ certificateSelector.serialNumber = selectedSerialNumber
+ }
+
+ onRejected: function() {
+ Systray.destroyDialog(encryptionKeyChooserDialog)
+ certificateSelector.serialNumber = ''
+ }
+ }
+ }
+
+ Rectangle {
+ color: Style.backgroundColor
+ anchors.fill: parent
+ z: 1
+ }
+}
diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp
index ac1dcf81e557..1f724404ac88 100644
--- a/src/gui/accountmanager.cpp
+++ b/src/gui/accountmanager.cpp
@@ -46,6 +46,7 @@ constexpr auto serverVersionC = "serverVersion";
constexpr auto serverColorC = "serverColor";
constexpr auto serverTextColorC = "serverTextColor";
constexpr auto skipE2eeMetadataChecksumValidationC = "skipE2eeMetadataChecksumValidation";
+constexpr auto encryptionCertificateSha256FingerprintC = "encryptionCertificateSha256Fingerprint";
constexpr auto generalC = "General";
constexpr auto dummyAuthTypeC = "dummy";
@@ -322,6 +323,7 @@ void AccountManager::saveAccountHelper(Account *acc, QSettings &settings, bool s
settings.setValue(QLatin1String(serverVersionC), acc->_serverVersion);
settings.setValue(QLatin1String(serverColorC), acc->_serverColor);
settings.setValue(QLatin1String(serverTextColorC), acc->_serverTextColor);
+ settings.setValue(QLatin1String(encryptionCertificateSha256FingerprintC), acc->encryptionCertificateFingerprint());
if (!acc->_skipE2eeMetadataChecksumValidation) {
settings.remove(QLatin1String(skipE2eeMetadataChecksumValidationC));
} else {
@@ -448,6 +450,8 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
acc->setCredentials(CredentialsFactory::create(authType));
+ acc->setEncryptionCertificateFingerprint(settings.value(QLatin1String(encryptionCertificateSha256FingerprintC)).toByteArray());
+
// now the server cert, it is in the general group
settings.beginGroup(QLatin1String(generalC));
const auto certs = QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray());
diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp
index 719f85704f8d..fcad2106c067 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -73,6 +73,7 @@ constexpr auto e2eUiActionIdKey = "id";
constexpr auto e2EeUiActionEnableEncryptionId = "enable_encryption";
constexpr auto e2EeUiActionDisableEncryptionId = "disable_encryption";
constexpr auto e2EeUiActionDisplayMnemonicId = "display_mnemonic";
+constexpr auto e2EeUiActionMigrateCertificateId = "migrate_certificate";
}
namespace OCC {
@@ -282,6 +283,11 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
this, &AccountSettings::slotUpdateQuota);
customizeStyle();
+
+ connect(_accountState->account()->e2e(), &ClientSideEncryption::startingDiscoveryEncryptionUsbToken,
+ Systray::instance(), &Systray::createEncryptionTokenDiscoveryDialog);
+ connect(_accountState->account()->e2e(), &ClientSideEncryption::finishedDiscoveryEncryptionUsbToken,
+ Systray::instance(), &Systray::destroyEncryptionTokenDiscoveryDialog);
}
void AccountSettings::slotE2eEncryptionMnemonicReady()
@@ -291,10 +297,16 @@ void AccountSettings::slotE2eEncryptionMnemonicReady()
disableEncryptionForAccount(_accountState->account());
});
- const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId);
- connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() {
- displayMnemonic(_accountState->account()->e2e()->_mnemonic);
- });
+ if (_accountState->account()->e2e()->userCertificateNeedsMigration()) {
+ slotE2eEncryptionCertificateNeedMigration();
+ }
+
+ if (!_accountState->account()->e2e()->getMnemonic().isEmpty()) {
+ const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId);
+ connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() {
+ displayMnemonic(_accountState->account()->e2e()->getMnemonic());
+ });
+ }
_ui->encryptionMessage->setMessageType(KMessageWidget::Positive);
_ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled for this account"));
@@ -307,18 +319,19 @@ void AccountSettings::slotE2eEncryptionGenerateKeys()
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
_accountState->account()->setE2eEncryptionKeysGenerationAllowed(true);
_accountState->account()->setAskUserForMnemonic(true);
- _accountState->account()->e2e()->initialize(_accountState->account());
+ _accountState->account()->e2e()->initialize(this, _accountState->account());
}
void AccountSettings::slotE2eEncryptionInitializationFinished(bool isNewMnemonicGenerated)
{
disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
- if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+ if (_accountState->account()->e2e()->isInitialized()) {
removeActionFromEncryptionMessage(e2EeUiActionEnableEncryptionId);
slotE2eEncryptionMnemonicReady();
if (isNewMnemonicGenerated) {
- displayMnemonic(_accountState->account()->e2e()->_mnemonic);
+ displayMnemonic(_accountState->account()->e2e()->getMnemonic());
}
+ Q_EMIT _accountState->account()->wantsFoldersSynced();
}
_accountState->account()->setAskUserForMnemonic(false);
}
@@ -388,7 +401,7 @@ bool AccountSettings::canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo
return false;
}
- if (!_accountState->account()->e2e() || _accountState->account()->e2e()->_mnemonic.isEmpty()) {
+ if (!_accountState->account()->e2e() || !_accountState->account()->e2e()->isInitialized()) {
QMessageBox msgBox;
msgBox.setText(tr("End-to-end encryption is not configured on this device. "
"Once it is configured, you will be able to encrypt this folder.\n"
@@ -1114,6 +1127,16 @@ void AccountSettings::disableEncryptionForAccount(const AccountPtr &account) con
}
}
+void AccountSettings::migrateCertificateForAccount(const AccountPtr &account)
+{
+ for (const auto action : _ui->encryptionMessage->actions()) {
+ _ui->encryptionMessage->removeAction(action);
+ }
+
+ account->e2e()->migrateCertificate();
+ slotE2eEncryptionGenerateKeys();
+}
+
void AccountSettings::showConnectionLabel(const QString &message, QStringList errors)
{
const auto errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;"
@@ -1458,7 +1481,7 @@ void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft,
void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
{
- if (_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+ if (!_accountState->account()->e2e()->isInitialized()) {
return;
}
@@ -1491,6 +1514,14 @@ void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
}
}
+void AccountSettings::slotE2eEncryptionCertificateNeedMigration()
+{
+ const auto actionMigrateCertificate = addActionToEncryptionMessage(tr("Migrate certificate to a new one"), e2EeUiActionMigrateCertificateId);
+ connect(actionMigrateCertificate, &QAction::triggered, this, [this] {
+ migrateCertificateForAccount(_accountState->account());
+ });
+}
+
void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const
{
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
@@ -1636,13 +1667,13 @@ void AccountSettings::initializeE2eEncryption()
{
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);
- if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+ if (_accountState->account()->e2e()->isInitialized()) {
slotE2eEncryptionMnemonicReady();
} else {
initializeE2eEncryptionSettingsMessage();
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, [this] {
- if (!_accountState->account()->e2e()->_publicKey.isNull()) {
+ if (!_accountState->account()->e2e()->getPublicKey().isNull()) {
_ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device."
"
"
"It can be enabled on this device by entering your mnemonic."
@@ -1651,7 +1682,7 @@ void AccountSettings::initializeE2eEncryption()
}
});
_accountState->account()->setE2eEncryptionKeysGenerationAllowed(false);
- _accountState->account()->e2e()->initialize(_accountState->account());
+ _accountState->account()->e2e()->initialize(this, _accountState->account());
}
}
@@ -1666,7 +1697,7 @@ void AccountSettings::resetE2eEncryption()
checkClientSideEncryptionState();
const auto account = _accountState->account();
- if (account->e2e()->_mnemonic.isEmpty()) {
+ if (!account->e2e()->isInitialized()) {
FolderMan::instance()->removeE2eFiles(account);
}
}
diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h
index 117a3aa536a0..2e41856f38b6 100644
--- a/src/gui/accountsettings.h
+++ b/src/gui/accountsettings.h
@@ -62,6 +62,7 @@ class AccountSettings : public QWidget
~AccountSettings() override;
[[nodiscard]] QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
bool canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo* folderInfo);
+ [[nodiscard]] OCC::AccountState *accountsState() const { return _accountState; }
signals:
void folderChanged();
@@ -76,7 +77,6 @@ public slots:
void slotUpdateQuota(qint64 total, qint64 used);
void slotAccountStateChanged();
void slotStyleChanged();
- OCC::AccountState *accountsState() { return _accountState; }
void slotHideSelectiveSyncWidget();
protected slots:
@@ -116,6 +116,8 @@ protected slots:
const QVector &roles);
void slotPossiblyUnblacklistE2EeFoldersAndRestartSync();
+ 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);
@@ -123,6 +125,7 @@ private slots:
private slots:
void displayMnemonic(const QString &mnemonic);
void disableEncryptionForAccount(const OCC::AccountPtr &account) const;
+ void migrateCertificateForAccount(const OCC::AccountPtr &account);
void showConnectionLabel(const QString &message, QStringList errors = QStringList());
void openIgnoredFilesDialog(const QString & absFolderPath);
void customizeStyle();
diff --git a/src/gui/application.cpp b/src/gui/application.cpp
index 48fb06682f10..482e9b859f06 100644
--- a/src/gui/application.cpp
+++ b/src/gui/application.cpp
@@ -662,6 +662,12 @@ void Application::setupLogging()
logger->setLogExpire(_logExpire > 0 ? _logExpire : ConfigFile().logExpire());
logger->setLogFlush(_logFlush || ConfigFile().logFlush());
logger->setLogDebug(_logDebug || ConfigFile().logDebug());
+
+#if defined(QT_DEBUG)
+ logger->setLogDebug(true);
+ logger->setLogFlush(true);
+#endif
+
if (!logger->isLoggingToFile() && ConfigFile().automaticLogDir()) {
logger->setupTemporaryFolderLogDir();
}
diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp
index 9cc24de4a752..e60010340946 100644
--- a/src/gui/connectionvalidator.cpp
+++ b/src/gui/connectionvalidator.cpp
@@ -315,7 +315,7 @@ void ConnectionValidator::slotUserFetched(UserInfo *userInfo)
#ifndef TOKEN_AUTH_ONLY
connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
- _account->e2e()->initialize(_account);
+ _account->e2e()->initialize(nullptr, _account);
#else
reportResult(Connected);
#endif
diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp
index 7b42535f22d8..6be29e133a83 100644
--- a/src/gui/folder.cpp
+++ b/src/gui/folder.cpp
@@ -129,6 +129,11 @@ Folder::Folder(const FolderDefinition &definition,
connect(_accountState->account().data(), &Account::capabilitiesChanged, this, &Folder::slotCapabilitiesChanged);
+ connect(_accountState->account().data(), &Account::wantsFoldersSynced, this, [this] () {
+ _engine->setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::FilesystemOnly);
+ QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
+ });
+
// Potentially upgrade suffix vfs to windows vfs
ENFORCE(_vfs);
if (_definition.virtualFilesMode == Vfs::WithSuffix
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index ffced3a51ccc..d34472b61e91 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -668,7 +668,7 @@ void FolderMan::forceSyncForFolder(Folder *folder)
void FolderMan::removeE2eFiles(const AccountPtr &account) const
{
- Q_ASSERT(account->e2e()->_mnemonic.isEmpty());
+ Q_ASSERT(!account->e2e()->isInitialized());
for (const auto folder : map()) {
if(folder->accountState()->account()->id() == account->id()) {
folder->removeLocalE2eFiles();
diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp
index e21644aecd8f..0ccb7364d3b8 100644
--- a/src/gui/folderstatusmodel.cpp
+++ b/src/gui/folderstatusmodel.cpp
@@ -754,8 +754,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
newInfo._isNonDecryptable = newInfo.isEncrypted()
&& _accountState->account()->e2e()
- && !_accountState->account()->e2e()->_publicKey.isNull()
- && _accountState->account()->e2e()->_privateKey.isNull();
+ && !_accountState->account()->e2e()->isInitialized();
SyncJournalFileRecord rec;
if (!parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec)) {
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index c14b5125a086..2ae6253844a4 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -136,6 +136,7 @@ ownCloudGui::ownCloudGui(Application *parent)
qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel");
qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum");
qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "Sharee", "Access to Type enum");
+ qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "ClientSideEncryptionTokenSelector", "Access to the certificate selector");
qRegisterMetaTypeStreamOperators();
@@ -579,6 +580,10 @@ void ownCloudGui::slotShowSettings()
if (_settingsDialog.isNull()) {
_settingsDialog = new SettingsDialog(this);
_settingsDialog->setAttribute(Qt::WA_DeleteOnClose, true);
+
+ connect(_tray.data(), &Systray::hideSettingsDialog,
+ _settingsDialog.data(), &SettingsDialog::close);
+
_settingsDialog->show();
}
raiseDialog(_settingsDialog.data());
diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp
index 8f8b1e2f8f76..b13103ff20d9 100644
--- a/src/gui/settingsdialog.cpp
+++ b/src/gui/settingsdialog.cpp
@@ -136,10 +136,6 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
_actionGroupWidgets.insert(generalAction, generalSettings);
_actionGroupWidgets.insert(networkAction, networkSettings);
- foreach(auto ai, AccountManager::instance()->accounts()) {
- accountAdded(ai.data());
- }
-
QTimer::singleShot(1, this, &SettingsDialog::showFirstPage);
auto *showLogWindow = new QAction(this);
@@ -214,6 +210,10 @@ void SettingsDialog::slotSwitchPage(QAction *action)
void SettingsDialog::showFirstPage()
{
+ foreach(auto ai, AccountManager::instance()->accounts()) {
+ accountAdded(ai.data());
+ }
+
QList actions = _toolBar->actions();
if (!actions.empty()) {
actions.first()->trigger();
diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp
index 45e517183f85..af568a3c3e55 100644
--- a/src/gui/socketapi/socketapi.cpp
+++ b/src/gui/socketapi/socketapi.cpp
@@ -538,7 +538,7 @@ void SocketApi::processEncryptRequest(const QString &localFile)
const auto rec = fileData.journalRecord();
Q_ASSERT(rec.isValid());
- if (!account->e2e() || account->e2e()->_mnemonic.isEmpty()) {
+ if (!account->e2e() || !account->e2e()->isInitialized()) {
const int ret = QMessageBox::critical(nullptr,
tr("Failed to encrypt folder at \"%1\"").arg(fileData.folderRelativePath),
tr("The account %1 does not have end-to-end encryption configured. "
diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp
index fb8451aa4c7b..46ce10561913 100644
--- a/src/gui/systray.cpp
+++ b/src/gui/systray.cpp
@@ -24,6 +24,7 @@
#include "configfile.h"
#include "accessmanager.h"
#include "callstatechecker.h"
+#include "clientsideencryptiontokenselector.h"
#include
#include
@@ -34,6 +35,8 @@
#include
#include
#include
+#include
+#include
#ifdef USE_FDO_NOTIFICATIONS
#include
@@ -320,6 +323,34 @@ void Systray::createResolveConflictsDialog(const OCC::ActivityList &allConflicts
dialog.take();
}
+void Systray::createEncryptionTokenDiscoveryDialog()
+{
+ if (_encryptionTokenDiscoveryDialog) {
+ return;
+ }
+
+ qCDebug(lcSystray) << "Opening an encryption token discovery dialog...";
+
+ const auto encryptionTokenDiscoveryDialog = new QQmlComponent(_trayEngine.get(), QStringLiteral("qrc:/qml/src/gui/tray/EncryptionTokenDiscoveryDialog.qml"));
+
+ if (encryptionTokenDiscoveryDialog->isError()) {
+ qCWarning(lcSystray) << encryptionTokenDiscoveryDialog->errorString();
+ return;
+ }
+
+ _encryptionTokenDiscoveryDialog = encryptionTokenDiscoveryDialog->createWithInitialProperties(QVariantMap{});
+}
+
+void Systray::destroyEncryptionTokenDiscoveryDialog()
+{
+ if (!_encryptionTokenDiscoveryDialog) {
+ return;
+ }
+ qCDebug(lcSystray) << "Closing an encryption token discovery dialog...";
+ _encryptionTokenDiscoveryDialog->deleteLater();
+ _encryptionTokenDiscoveryDialog = nullptr;
+}
+
bool Systray::raiseDialogs()
{
return raiseFileDetailDialogs();
diff --git a/src/gui/systray.h b/src/gui/systray.h
index 5a450580841a..589536b26b2b 100644
--- a/src/gui/systray.h
+++ b/src/gui/systray.h
@@ -15,11 +15,11 @@
#ifndef SYSTRAY_H
#define SYSTRAY_H
-#include
-
#include "accountmanager.h"
#include "tray/usermodel.h"
+#include
+#include
#include
class QScreen;
@@ -31,6 +31,8 @@ class QGuiApplication;
namespace OCC {
+class ClientSideEncryptionTokenSelector;
+
class AccessManagerFactory : public QQmlNetworkAccessManagerFactory
{
public:
@@ -115,6 +117,8 @@ class Systray : public QSystemTrayIcon
void syncIsPausedChanged();
void isOpenChanged();
+ void hideSettingsDialog();
+
public slots:
void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
@@ -128,6 +132,8 @@ public slots:
void createEditFileLocallyLoadingDialog(const QString &fileName);
void destroyEditFileLocallyLoadingDialog();
void createResolveConflictsDialog(const OCC::ActivityList &allConflicts);
+ void createEncryptionTokenDiscoveryDialog();
+ void destroyEncryptionTokenDiscoveryDialog();
void slotCurrentUserChanged();
@@ -187,7 +193,9 @@ private slots:
QSet _callsAlreadyNotified;
QPointer _editFileLocallyLoadingDialog;
+ QPointer _encryptionTokenDiscoveryDialog;
QVector _fileDetailDialogs;
+ QQuickWindow* _tokenInitDialog = nullptr;
};
} // namespace OCC
diff --git a/src/gui/tray/EncryptionTokenDiscoveryDialog.qml b/src/gui/tray/EncryptionTokenDiscoveryDialog.qml
new file mode 100644
index 000000000000..123cc750f7aa
--- /dev/null
+++ b/src/gui/tray/EncryptionTokenDiscoveryDialog.qml
@@ -0,0 +1,89 @@
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import Style 1.0
+import com.nextcloud.desktopclient 1.0
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+ApplicationWindow {
+ id: root
+ flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
+
+ color: "transparent"
+
+ width: 320
+ height: contentLayout.implicitHeight
+ modality: Qt.ApplicationModal
+
+ readonly property real fontPixelSize: Style.topLinePixelSize * 1.5
+ readonly property real iconWidth: fontPixelSize * 2
+
+ // TODO: Rather than setting all these palette colours manually,
+ // create a custom style and do it for all components globally
+ palette {
+ text: Style.ncTextColor
+ windowText: Style.ncTextColor
+ buttonText: Style.ncTextColor
+ brightText: Style.ncTextBrightColor
+ highlight: Style.lightHover
+ highlightedText: Style.ncTextColor
+ light: Style.lightHover
+ midlight: Style.ncSecondaryTextColor
+ mid: Style.darkerHover
+ dark: Style.menuBorder
+ button: Style.buttonBackgroundColor
+ window: Style.backgroundColor
+ base: Style.backgroundColor
+ toolTipBase: Style.backgroundColor
+ toolTipText: Style.ncTextColor
+ }
+
+ Component.onCompleted: {
+ Systray.forceWindowInit(root);
+ x = Screen.width / 2 - width / 2
+ y = Screen.height / 2 - height / 2
+ root.show();
+ root.raise();
+ root.requestActivate();
+ }
+
+ Rectangle {
+ id: windowBackground
+ color: Style.backgroundColor
+ radius: Style.trayWindowRadius
+ border.color: palette.dark
+ anchors.fill: parent
+ }
+
+ ColumnLayout {
+ id: contentLayout
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: Style.standardSpacing
+ anchors.rightMargin: Style.standardSpacing
+ spacing: Style.standardSpacing
+
+ NCBusyIndicator {
+ id: busyIndicator
+ Layout.topMargin: Style.standardSpacing
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: root.iconWidth
+ Layout.preferredHeight: root.iconWidth
+ imageSourceSizeHeight: root.iconWidth
+ imageSourceSizeWidth: root.iconWidth
+ padding: 0
+ color: palette.windowText
+ running: true
+ }
+ EnforcedPlainTextLabel {
+ id: labelMessage
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.bottomMargin: Style.standardSpacing
+ text: qsTr("Discovering the certificates stored on your USB token")
+ elide: Text.ElideRight
+ font.pixelSize: root.fontPixelSize
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+}
diff --git a/src/gui/tray/activitydata.h b/src/gui/tray/activitydata.h
index e613ad5bf5dd..3bcd4281eeb5 100644
--- a/src/gui/tray/activitydata.h
+++ b/src/gui/tray/activitydata.h
@@ -149,6 +149,7 @@ class Activity
// Note that these are in the order we want to present them in the model!
enum Type {
DummyFetchingActivityType,
+ OpenSettingsNotificationType,
NotificationType,
SyncResultType,
SyncFileItemType,
diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp
index d0866c440c50..79a66691a95b 100644
--- a/src/gui/tray/activitylistmodel.cpp
+++ b/src/gui/tray/activitylistmodel.cpp
@@ -41,6 +41,8 @@ Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
ActivityListModel::ActivityListModel(QObject *parent)
: QAbstractListModel(parent)
{
+ connect(this, &ActivityListModel::showSettingsDialog,
+ Systray::instance(), &Systray::openSettings);
}
ActivityListModel::ActivityListModel(AccountState *accountState,
@@ -48,6 +50,8 @@ ActivityListModel::ActivityListModel(AccountState *accountState,
: QAbstractListModel(parent)
, _accountState(accountState)
{
+ connect(this, &ActivityListModel::showSettingsDialog,
+ Systray::instance(), &Systray::openSettings);
if (_accountState) {
connect(_accountState, &AccountState::stateChanged,
this, &ActivityListModel::accountStateChanged);
@@ -227,7 +231,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
const auto generateIconPath = [&]() {
auto colorIconPath = role == DarkIconRole ? QStringLiteral("qrc:///client/theme/white/") : QStringLiteral("qrc:///client/theme/black/");
- if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) {
+ if ((a._type == Activity::NotificationType || a._type == Activity::OpenSettingsNotificationType) &&
+ !a._talkNotificationData.userAvatar.isEmpty()) {
return QStringLiteral("qrc:///client/theme/colored/talk-bordered.svg");
} else if (a._type == Activity::SyncResultType) {
colorIconPath.append("state-error.svg");
@@ -317,14 +322,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
case Activity::DummyMoreActivitiesAvailableType:
return "Activity";
case Activity::NotificationType:
+ case Activity::OpenSettingsNotificationType:
return "Notification";
case Activity::SyncFileItemType:
return "File";
case Activity::SyncResultType:
return "Sync";
- default:
- return QVariant();
}
+ break;
}
case ActionTextRole:
if(a._subjectDisplay.isEmpty()) {
@@ -368,7 +373,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
case IsCurrentUserFileActivityRole:
return a._isCurrentUserFileActivity;
case ThumbnailRole: {
- if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) {
+ if ((a._type == Activity::NotificationType || a._type == Activity::OpenSettingsNotificationType) &&
+ !a._talkNotificationData.userAvatar.isEmpty()) {
return generateAvatarThumbnailMap(a._talkNotificationData.userAvatar);
}
@@ -393,7 +399,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue(a);
}
- return QVariant();
+ return {};
}
int ActivityListModel::rowCount(const QModelIndex &parent) const
@@ -674,9 +680,10 @@ void ActivityListModel::removeActivityFromActivityList(const Activity &activity)
}
if (activity._type != Activity::ActivityType &&
- activity._type != Activity::DummyFetchingActivityType &&
- activity._type != Activity::DummyMoreActivitiesAvailableType &&
- activity._type != Activity::NotificationType) {
+ activity._type != Activity::DummyFetchingActivityType &&
+ activity._type != Activity::DummyMoreActivitiesAvailableType &&
+ activity._type != Activity::NotificationType &&
+ activity._type != Activity::OpenSettingsNotificationType) {
const auto notificationErrorsListIndex = _notificationErrorsLists.indexOf(activity);
if (notificationErrorsListIndex != -1)
@@ -740,6 +747,8 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
_currentInvalidFilenameDialog->open();
ownCloudGui::raiseDialog(_currentInvalidFilenameDialog);
return;
+ } else if (activity._type == Activity::OpenSettingsNotificationType) {
+ Q_EMIT showSettingsDialog();
}
if (!path.isEmpty()) {
diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h
index 6251a12e9a1f..c0dcb58ef3eb 100644
--- a/src/gui/tray/activitylistmodel.h
+++ b/src/gui/tray/activitylistmodel.h
@@ -153,6 +153,8 @@ public slots:
void interactiveActivityReceived();
+ void showSettingsDialog();
+
protected:
[[nodiscard]] bool currentlyFetching() const;
diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp
index da175d3e28bf..efa0bdaff440 100644
--- a/src/gui/tray/usermodel.cpp
+++ b/src/gui/tray/usermodel.cpp
@@ -92,8 +92,25 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::slotAccountCapabilitiesChangedRefreshGroupFolders);
connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
-
+
connect(this, &User::sendReplyMessage, this, &User::slotSendReplyMessage);
+
+ connect(_account->account().data(), &Account::userCertificateNeedsMigrationChanged, this, [this] () {
+ auto certificateNeedMigration = Activity{};
+ certificateNeedMigration._type = Activity::OpenSettingsNotificationType;
+ certificateNeedMigration._subject = tr("End-to-end certificate needs to be migrated to a new one");
+ certificateNeedMigration._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
+ certificateNeedMigration._message = tr("Trigger the migration");
+ certificateNeedMigration._accName = _account->account()->displayName();
+ certificateNeedMigration._id = qHash("migrate-certificate");
+
+ _activityModel->removeActivityFromActivityList(certificateNeedMigration);
+
+ if (_account->account()->e2e()->userCertificateNeedsMigration()) {
+ _activityModel->addNotificationToActivityList(certificateNeedMigration);
+ showDesktopNotification(certificateNeedMigration);
+ }
+ });
}
void User::checkNotifiedNotifications()
diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt
index 57b4ddef8d82..fb8fcdce9700 100644
--- a/src/libsync/CMakeLists.txt
+++ b/src/libsync/CMakeLists.txt
@@ -12,6 +12,12 @@ if ( APPLE )
)
endif()
+if (WIN32)
+ list(APPEND OS_SPECIFIC_LINK_LIBRARIES
+ Crypt32
+ )
+endif()
+
set(libsync_SRCS
account.h
account.cpp
@@ -123,6 +129,8 @@ set(libsync_SRCS
clientsideencryptionjobs.cpp
clientsideencryptionprimitives.h
clientsideencryptionprimitives.cpp
+ clientsideencryptiontokenselector.h
+ clientsideencryptiontokenselector.cpp
datetimeprovider.h
datetimeprovider.cpp
rootencryptedfolderinfo.h
@@ -205,6 +213,7 @@ target_link_libraries(nextcloudsync
Nextcloud::csync
OpenSSL::Crypto
OpenSSL::SSL
+ PkgConfig::OPENSC-LIBP11
${OS_SPECIFIC_LINK_LIBRARIES}
Qt5::Core
Qt5::Network
diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp
index 9be583b49c8a..e3dc2827266e 100644
--- a/src/libsync/account.cpp
+++ b/src/libsync/account.cpp
@@ -33,6 +33,8 @@
#include "clientsideencryption.h"
#include "ocsuserstatusconnector.h"
+#include "config.h"
+
#include
#include
#include
@@ -78,6 +80,9 @@ Account::Account(QObject *parent)
_pushNotificationsReconnectTimer.setInterval(pushNotificationsReconnectInterval);
connect(&_pushNotificationsReconnectTimer, &QTimer::timeout, this, &Account::trySetupPushNotifications);
+
+ connect(&_e2e, &ClientSideEncryption::userCertificateNeedsMigrationChanged,
+ this, &Account::userCertificateNeedsMigrationChanged);
}
AccountPtr Account::create()
@@ -1056,6 +1061,41 @@ bool Account::askUserForMnemonic() const
return _e2eAskUserForMnemonic;
}
+bool Account::enforceUseHardwareTokenEncryption() const
+{
+#if defined CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN
+ return CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN;
+#else
+ return false;
+#endif
+}
+
+QString Account::encryptionHardwareTokenDriverPath() const
+{
+#if defined ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH
+ return ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH;
+#else
+ return {};
+#endif
+}
+
+QByteArray Account::encryptionCertificateFingerprint() const
+{
+ return _encryptionCertificateFingerprint;
+}
+
+void Account::setEncryptionCertificateFingerprint(const QByteArray &fingerprint)
+{
+ if (_encryptionCertificateFingerprint == fingerprint) {
+ return;
+ }
+
+ _encryptionCertificateFingerprint = fingerprint;
+ _e2e.usbTokenInformation()->setSha256Fingerprint(fingerprint);
+ Q_EMIT encryptionCertificateFingerprintChanged();
+ Q_EMIT wantsAccountSaved(this);
+}
+
void Account::setAskUserForMnemonic(const bool ask)
{
_e2eAskUserForMnemonic = ask;
diff --git a/src/libsync/account.h b/src/libsync/account.h
index b7702e525fdf..7bd352804488 100644
--- a/src/libsync/account.h
+++ b/src/libsync/account.h
@@ -89,6 +89,9 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
Q_PROPERTY(QUrl url MEMBER _url)
Q_PROPERTY(bool e2eEncryptionKeysGenerationAllowed MEMBER _e2eEncryptionKeysGenerationAllowed)
Q_PROPERTY(bool askUserForMnemonic READ askUserForMnemonic WRITE setAskUserForMnemonic NOTIFY askUserForMnemonicChanged)
+ Q_PROPERTY(bool enforceUseHardwareTokenEncryption READ enforceUseHardwareTokenEncryption NOTIFY enforceUseHardwareTokenEncryptionChanged)
+ Q_PROPERTY(QString encryptionHardwareTokenDriverPath READ encryptionHardwareTokenDriverPath NOTIFY encryptionHardwareTokenDriverPathChanged)
+ Q_PROPERTY(QByteArray encryptionCertificateFingerprint READ encryptionCertificateFingerprint WRITE setEncryptionCertificateFingerprint NOTIFY encryptionCertificateFingerprintChanged)
public:
static AccountPtr create();
@@ -334,6 +337,14 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
[[nodiscard]] bool askUserForMnemonic() const;
+ [[nodiscard]] bool enforceUseHardwareTokenEncryption() const;
+
+ [[nodiscard]] QString encryptionHardwareTokenDriverPath() const;
+
+ [[nodiscard]] QByteArray encryptionCertificateFingerprint() const;
+
+ void setEncryptionCertificateFingerprint(const QByteArray &fingerprint);
+
public slots:
/// Used when forgetting credentials
void clearQNAMCache();
@@ -356,12 +367,16 @@ public slots:
// e.g. when the approved SSL certificates changed
void wantsAccountSaved(OCC::Account *acc);
+ void wantsFoldersSynced();
+
void serverVersionChanged(OCC::Account *account, const QString &newVersion, const QString &oldVersion);
void accountChangedAvatar();
void accountChangedDisplayName();
void prettyNameChanged();
void askUserForMnemonicChanged();
+ void enforceUseHardwareTokenEncryptionChanged();
+ void encryptionHardwareTokenDriverPathChanged();
/// Used in RemoteWipe
void appPasswordRetrieved(QString);
@@ -378,6 +393,9 @@ public slots:
void lockFileSuccess();
void lockFileError(const QString&);
+ void encryptionCertificateFingerprintChanged();
+ void userCertificateNeedsMigrationChanged();
+
protected Q_SLOTS:
void slotCredentialsFetched();
void slotCredentialsAsked();
@@ -453,6 +471,8 @@ private slots:
QHash> _lockStatusChangeInprogress;
+ QByteArray _encryptionCertificateFingerprint;
+
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
*
diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp
index ba34300420a1..71c0fd73ce1c 100644
--- a/src/libsync/clientsideencryption.cpp
+++ b/src/libsync/clientsideencryption.cpp
@@ -1,12 +1,19 @@
-#include "clientsideencryption.h"
+/*
+ * Copyright © 2017, Tomaz Canabrava
+ * Copyright © 2020, Andreas Jellinghaus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "clientsideencryption.h"
#include "account.h"
#include "capabilities.h"
@@ -29,6 +36,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -36,13 +45,24 @@
#include
#include
#include
+#include
+#include
+#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
#include