diff --git a/.drone.yml b/.drone.yml
index 8e8b3c55a5e0a..181a3167ebe43 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -208,4 +208,3 @@ trigger:
kind: signature
hmac: 1fbd0241ba0d4ea2702804324f4932b3f29d3d937ef75906a529cd00c4252a57
-...
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 351b3eaa00c41..f0926e9582411 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -231,20 +231,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 REQUIRED 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()
option(BUILD_WITH_WEBENGINE "BUILD_WITH_WEBENGINE" ON)
diff --git a/NEXTCLOUD.cmake b/NEXTCLOUD.cmake
index e2632dbcfb6d6..982b8cb515758 100644
--- a/NEXTCLOUD.cmake
+++ b/NEXTCLOUD.cmake
@@ -61,7 +61,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/config.h.in b/config.h.in
index be3005502032f..3156eec4ffd66 100644
--- a/config.h.in
+++ b/config.h.in
@@ -65,4 +65,8 @@
#cmakedefine WITH_WEBENGINE
+#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 9d1da12609314..62b27a6ba2483 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
@@ -46,6 +47,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 fffd846b547a1..be99a1f802df1 100644
--- a/src/common/syncjournaldb.cpp
+++ b/src/common/syncjournaldb.cpp
@@ -48,8 +48,9 @@ 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, " \
- " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe, isLivePhoto, livePhotoFile" \
+ " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, " \
+ " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, " \
+ " sharedByMe, isLivePhoto, livePhotoFile" \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"
@@ -67,19 +68,21 @@ 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._lockstate._lockToken = query.stringValue(19);
- rec._isShared = query.intValue(20) > 0;
- rec._lastShareStateFetchedTimestamp = query.int64Value(21);
- rec._sharedByMe = query.intValue(22) > 0;
- rec._isLivePhoto = query.intValue(23) > 0;
- rec._livePhotoFile = query.stringValue(24);
+ 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._lockstate._lockToken = query.stringValue(20);
+ rec._isShared = query.intValue(21) > 0;
+ rec._lastShareStateFetchedTimestamp = query.int64Value(22);
+ rec._sharedByMe = query.intValue(23) > 0;
+ rec._isLivePhoto = query.intValue(24) > 0;
+ rec._livePhotoFile = query.stringValue(25);
}
static QByteArray defaultJournalMode(const QString &dbPath)
@@ -783,6 +786,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"));
@@ -995,9 +999,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, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe, isLivePhoto, livePhotoFile) "
- "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, ?30, ?31);"),
+ "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, ?30, ?31, ?32);"),
_db);
if (!query) {
qCDebug(lcDb) << "database error:" << query->error();
@@ -1022,19 +1026,20 @@ 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._lockstate._lockToken);
- query->bindValue(27, record._isShared);
- query->bindValue(28, record._lastShareStateFetchedTimestamp);
- query->bindValue(29, record._sharedByMe);
- query->bindValue(30, record._isLivePhoto);
- query->bindValue(31, record._livePhotoFile);
+ 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._lockstate._lockToken);
+ query->bindValue(28, record._isShared);
+ query->bindValue(29, record._lastShareStateFetchedTimestamp);
+ query->bindValue(30, record._sharedByMe);
+ query->bindValue(31, record._isLivePhoto);
+ query->bindValue(32, record._livePhotoFile);
if (!query->exec()) {
qCDebug(lcDb) << "database error:" << query->error();
diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h
index 4d299e3a9ff8d..f36b53095b300 100644
--- a/src/common/syncjournalfilerecord.h
+++ b/src/common/syncjournalfilerecord.h
@@ -84,6 +84,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 9da7497f75c7f..8329020f58463 100644
--- a/src/csync/csync.h
+++ b/src/csync/csync.h
@@ -140,24 +140,25 @@ 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_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
+ 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_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
+ CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA = 1 << 14, /* encryption metadata needs update after certificate was migrated */
};
Q_ENUM_NS(SyncInstructions)
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 9660bfb7ee7f2..0370858e2b056 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -146,6 +146,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 0000000000000..8872d978335a5
--- /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 f27d2742703a7..3e98720a4dc3b 100644
--- a/src/gui/accountmanager.cpp
+++ b/src/gui/accountmanager.cpp
@@ -59,6 +59,7 @@ constexpr auto networkUploadLimitSettingC = "networkUploadLimitSetting";
constexpr auto networkDownloadLimitSettingC = "networkDownloadLimitSetting";
constexpr auto networkUploadLimitC = "networkUploadLimit";
constexpr auto networkDownloadLimitC = "networkDownloadLimit";
+constexpr auto encryptionCertificateSha256FingerprintC = "encryptionCertificateSha256Fingerprint";
constexpr auto generalC = "General";
constexpr auto dummyAuthTypeC = "dummy";
@@ -334,12 +335,12 @@ void AccountManager::saveAccountHelper(Account *account, QSettings &settings, bo
settings.setValue(QLatin1String(versionC), maxAccountVersion);
settings.setValue(QLatin1String(urlC), account->_url.toString());
settings.setValue(QLatin1String(davUserC), account->_davUser);
- settings.setValue(QLatin1String(displayNameC), account->davDisplayName());
+ settings.setValue(QLatin1String(displayNameC), account->_displayName);
settings.setValue(QLatin1String(serverVersionC), account->_serverVersion);
settings.setValue(QLatin1String(serverColorC), account->_serverColor);
settings.setValue(QLatin1String(serverTextColorC), account->_serverTextColor);
settings.setValue(QLatin1String(serverHasValidSubscriptionC), account->serverHasValidSubscription());
-
+ settings.setValue(QLatin1String(encryptionCertificateSha256FingerprintC), account->encryptionCertificateFingerprint());
if (!account->_skipE2eeMetadataChecksumValidation) {
settings.remove(QLatin1String(skipE2eeMetadataChecksumValidationC));
} else {
@@ -545,6 +546,8 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
});
job->start();
+ 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 6a7051a092a27..29084e8f296a8 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -74,6 +74,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 {
@@ -290,6 +291,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()
@@ -299,10 +305,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"));
@@ -315,18 +327,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);
}
@@ -396,7 +409,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"
@@ -1122,6 +1135,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;"
@@ -1469,7 +1492,7 @@ void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft,
void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
{
- if (_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+ if (!_accountState->account()->e2e()->isInitialized()) {
return;
}
@@ -1502,6 +1525,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);
@@ -1647,13 +1678,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."
@@ -1662,7 +1693,7 @@ void AccountSettings::initializeE2eEncryption()
}
});
_accountState->account()->setE2eEncryptionKeysGenerationAllowed(false);
- _accountState->account()->e2e()->initialize(_accountState->account());
+ _accountState->account()->e2e()->initialize(this, _accountState->account());
}
}
@@ -1677,7 +1708,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 8981f1dffe6f8..592e7175e1d5c 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/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp
index 75cb21762fcb0..ebe1c1af525f6 100644
--- a/src/gui/connectionvalidator.cpp
+++ b/src/gui/connectionvalidator.cpp
@@ -321,7 +321,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/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp
index a199231998ad7..4dfa8346d5a4d 100644
--- a/src/gui/filedetails/sharemodel.cpp
+++ b/src/gui/filedetails/sharemodel.cpp
@@ -369,8 +369,12 @@ void ShareModel::initShareManager()
connect(_manager.data(), &ShareManager::linkShareCreated, this, &ShareModel::slotAddShare);
connect(_manager.data(), &ShareManager::linkShareRequiresPassword, this, &ShareModel::requestPasswordForLinkShare);
connect(_manager.data(), &ShareManager::serverError, this, [this](const int code, const QString &message) {
- _hasInitialShareFetchCompleted = true;
- Q_EMIT hasInitialShareFetchCompletedChanged();
+ if (!_hasInitialShareFetchCompleted) {
+ _hasInitialShareFetchCompleted = true;
+ Q_EMIT hasInitialShareFetchCompletedChanged();
+ }
+
+ qCWarning(lcShareModel) << "Error from server from ShareManager class and initShareManager" << code << message;
emit serverError(code, message);
});
@@ -633,7 +637,10 @@ void ShareModel::slotAddShare(const SharePtr &share)
const QPersistentModelIndex sharePersistentIndex(shareModelIndex);
_shareIdIndexHash.insert(shareId, sharePersistentIndex);
- connect(share.data(), &Share::serverError, this, &ShareModel::slotServerError);
+ connect(share.data(), &Share::serverError, this, [this] (int code, const QString &message) {
+ qCWarning(lcShareModel) << "Error from server from Share class" << code << message;
+ Q_EMIT serverError(code, message);
+ });
connect(share.data(), &Share::passwordSetError, this, [this, shareId](const int code, const QString &message) {
_shareIdRecentlySetPasswords.remove(shareId);
slotSharePasswordSet(shareId);
@@ -656,10 +663,6 @@ void ShareModel::slotAddShare(const SharePtr &share)
connect(userGroupShare.data(), &UserGroupShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); });
}
- if (_manager) {
- connect(_manager.data(), &ShareManager::serverError, this, &ShareModel::slotServerError);
- }
-
handleLinkShare();
Q_EMIT sharesChanged();
}
@@ -708,12 +711,6 @@ void ShareModel::slotRemoveShareWithId(const QString &shareId)
Q_EMIT sharesChanged();
}
-void ShareModel::slotServerError(const int code, const QString &message)
-{
- qCWarning(lcShareModel) << "Error from server" << code << message;
- Q_EMIT serverError(code, message);
-}
-
void ShareModel::slotAddSharee(const ShareePtr &sharee)
{
if(!sharee) {
diff --git a/src/gui/filedetails/sharemodel.h b/src/gui/filedetails/sharemodel.h
index 5357a9366d6f3..e34dece4b1f9d 100644
--- a/src/gui/filedetails/sharemodel.h
+++ b/src/gui/filedetails/sharemodel.h
@@ -216,7 +216,6 @@ private slots:
void setHideDownloadEnabledChangeInProgress(const QString &shareId, const bool isInProgress);
void slotPropfindReceived(const QVariantMap &result);
- void slotServerError(const int code, const QString &message);
void slotAddShare(const OCC::SharePtr &share);
void slotRemoveShareWithId(const QString &shareId);
void slotSharesFetched(const QList &shares);
diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp
index a2547dafafa69..b055392d72c9e 100644
--- a/src/gui/folder.cpp
+++ b/src/gui/folder.cpp
@@ -138,6 +138,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 2f08c40a2f29c..c8f803b9c300a 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 f712cf92cffdc..4639f2f413bec 100644
--- a/src/gui/folderstatusmodel.cpp
+++ b/src/gui/folderstatusmodel.cpp
@@ -758,8 +758,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 dccdcc3a8c7f9..6ce1c0a3fb4de 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -149,6 +149,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");
qRegisterMetaType("ActivityListModel*");
qRegisterMetaType("UnifiedSearchResultsListModel*");
@@ -605,10 +606,15 @@ void ownCloudGui::slotShowSettings()
if (_settingsDialog.isNull()) {
_settingsDialog = new SettingsDialog(this);
_settingsDialog->setAttribute(Qt::WA_DeleteOnClose, true);
+
#ifdef Q_OS_MAC
auto *fgbg = new ForegroundBackground();
_settingsDialog->installEventFilter(fgbg);
#endif
+
+ 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 7b4d778946f22..ae76ec4aeafb9 100644
--- a/src/gui/settingsdialog.cpp
+++ b/src/gui/settingsdialog.cpp
@@ -222,6 +222,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 ae32d0c6be3aa..75dd094fb68f8 100644
--- a/src/gui/socketapi/socketapi.cpp
+++ b/src/gui/socketapi/socketapi.cpp
@@ -545,7 +545,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 85fee594c9f7b..a2fd1eab773e6 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
@@ -317,6 +320,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 7b17bab423aae..f1b0557e06590 100644
--- a/src/gui/systray.h
+++ b/src/gui/systray.h
@@ -19,6 +19,7 @@
#include "tray/usermodel.h"
#include
+#include
#include
#include
@@ -31,6 +32,8 @@ class QGuiApplication;
namespace OCC {
+class ClientSideEncryptionTokenSelector;
+
class AccessManagerFactory : public QQmlNetworkAccessManagerFactory
{
public:
@@ -115,6 +118,8 @@ class Systray : public QSystemTrayIcon
void syncIsPausedChanged();
void isOpenChanged();
+ void hideSettingsDialog();
+
public slots:
void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
@@ -127,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 +194,9 @@ private slots:
QSet _callsAlreadyNotified;
QPointer _editFileLocallyLoadingDialog;
+ QPointer _encryptionTokenDiscoveryDialog;
QVector _fileDetailDialogs;
+ QQuickWindow* _tokenInitDialog = nullptr;
QStringListModel _fakeActivityModel;
};
diff --git a/src/gui/tray/EncryptionTokenDiscoveryDialog.qml b/src/gui/tray/EncryptionTokenDiscoveryDialog.qml
new file mode 100644
index 0000000000000..123cc750f7aa3
--- /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 e613ad5bf5dd3..3bcd4281eeb50 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 47cc441f6c14f..c91b541b2a611 100644
--- a/src/gui/tray/activitylistmodel.cpp
+++ b/src/gui/tray/activitylistmodel.cpp
@@ -313,14 +313,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()) {
@@ -364,7 +364,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);
}
@@ -389,7 +390,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue(a);
}
- return QVariant();
+ return {};
}
int ActivityListModel::rowCount(const QModelIndex &parent) const
@@ -664,9 +665,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)
@@ -735,6 +737,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 6251a12e9a1ff..c0dcb58ef3ebf 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 30b3d699f81ee..31a79a93ceb5d 100644
--- a/src/gui/tray/usermodel.cpp
+++ b/src/gui/tray/usermodel.cpp
@@ -93,8 +93,27 @@ 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(_activityModel, &ActivityListModel::showSettingsDialog,
+ Systray::instance(), &Systray::openSettings);
+
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 58d820a3412b1..888fd3164f6c9 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}
Qt::Core
Qt::Network
diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp
index 0b20a3551bdc9..5b2e0de250541 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
@@ -80,6 +82,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()
@@ -1079,6 +1084,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 28165f05320fd..eb63073c15f43 100644
--- a/src/libsync/account.h
+++ b/src/libsync/account.h
@@ -102,6 +102,9 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
Q_PROPERTY(AccountNetworkTransferLimitSetting downloadLimitSetting READ downloadLimitSetting WRITE setDownloadLimitSetting NOTIFY downloadLimitSettingChanged)
Q_PROPERTY(unsigned int uploadLimit READ uploadLimit WRITE setUploadLimit NOTIFY uploadLimitChanged)
Q_PROPERTY(unsigned int downloadLimit READ downloadLimit WRITE setDownloadLimit NOTIFY downloadLimitChanged)
+ 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:
// We need to decide whether to use the client's global proxy settings or whether to use
@@ -412,6 +415,13 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
[[nodiscard]] bool serverHasValidSubscription() const;
void setServerHasValidSubscription(bool valid);
+ [[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();
@@ -434,12 +444,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);
@@ -469,6 +483,9 @@ public slots:
void downloadLimitChanged();
void termsOfServiceNeedToBeChecked();
+ void encryptionCertificateFingerprintChanged();
+ void userCertificateNeedsMigrationChanged();
+
protected Q_SLOTS:
void slotCredentialsFetched();
void slotCredentialsAsked();
@@ -556,8 +573,8 @@ private slots:
AccountNetworkTransferLimitSetting _downloadLimitSetting = AccountNetworkTransferLimitSetting::GlobalLimit;
unsigned int _uploadLimit = 0;
unsigned int _downloadLimit = 0;
-
bool _serverHasValidSubscription = false;
+ 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 a5c0364c2b7ab..eec73e645fe7c 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"
@@ -31,6 +38,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -38,11 +47,22 @@
#include
#include
#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include