From 547077e0d646aecd82803ebe8b29abe48f943d52 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 23 Jan 2026 13:10:41 +0100 Subject: [PATCH 1/5] add new e2ee version Signed-off-by: alperozturk96 --- .../utils/extensions/E2EVersionExtensions.kt | 48 +++++++++++++++++++ .../operations/CreateFolderOperation.java | 16 ++++--- .../operations/RefreshFolderOperation.java | 8 +++- .../RemoveRemoteEncryptedFileOperation.kt | 3 +- .../operations/UploadFileOperation.java | 3 +- .../ui/fragment/OCFileListFragment.java | 13 +++-- .../android/utils/EncryptionUtils.java | 19 +++++--- 7 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt diff --git a/app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt new file mode 100644 index 000000000000..b65a21f134ad --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt @@ -0,0 +1,48 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import com.google.gson.reflect.TypeToken +import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1 +import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile +import com.owncloud.android.lib.resources.status.E2EVersion +import com.owncloud.android.utils.EncryptionUtils + +fun E2EVersion.isV2orAbove(): Boolean { + return this == E2EVersion.V2_0 || this == E2EVersion.V2_1 +} + +fun E2EVersion.isV1(): Boolean { + return this == E2EVersion.V1_0 || this == E2EVersion.V1_1 || this == E2EVersion.V1_2 +} + +fun determineE2EVersion(metadata: String): E2EVersion { + return runCatching { + val v1 = EncryptionUtils.deserializeJSON( + metadata, + object : TypeToken() {} + ) + when (val version = v1?.metadata?.version?.toString()) { + "1.0" -> E2EVersion.V1_0 + "1.1" -> E2EVersion.V1_1 + "1.2" -> E2EVersion.V1_2 + else -> throw IllegalStateException("Unknown V1 version: $version") + } + }.recoverCatching { + val v2 = EncryptionUtils.deserializeJSON( + metadata, + object : TypeToken() {} + ) + + when (v2?.version) { + "2.0", "2" -> E2EVersion.V2_0 + "2.1" -> E2EVersion.V2_1 + else -> E2EVersion.UNKNOWN + } + }.getOrDefault(E2EVersion.UNKNOWN) +} diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index c2d6e3930ff0..3d54f920371a 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -15,6 +15,7 @@ import android.util.Pair; import com.nextcloud.client.account.User; +import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -96,15 +97,16 @@ protected RemoteOperationResult run(OwnCloudClient client) { boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager()); if (encryptedAncestor) { - E2EVersion e2EVersion = getStorageManager().getCapability(user).getEndToEndEncryptionApiVersion(); - if (e2EVersion == E2EVersion.V1_0 || - e2EVersion == E2EVersion.V1_1 || - e2EVersion == E2EVersion.V1_2) { - return encryptedCreateV1(parent, client); - } else if (e2EVersion == E2EVersion.V2_0) { + final var capability = getStorageManager().getCapability(user); + final var version = capability.getEndToEndEncryptionApiVersion(); + + if (E2EVersionExtensionsKt.isV2orAbove(version)) { return encryptedCreateV2(parent, client); + } else if (E2EVersionExtensionsKt.isV1(version)) { + return encryptedCreateV1(parent, client); } - return new RemoteOperationResult(new IllegalStateException("E2E not supported")); + + return new RemoteOperationResult<>(new IllegalStateException("E2E not supported")); } else { return normalCreate(client); } diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index f760af5e0538..0d1de5267d1b 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -16,6 +16,7 @@ import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainRemoteOperation; import com.nextcloud.client.account.User; import com.nextcloud.common.NextcloudClient; +import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; import com.nextcloud.utils.extensions.StringExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -538,7 +539,10 @@ private void synchronizeData(List folderAndFiles) { mContext); } - if (CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) { + final var capability = CapabilityUtils.getCapability(mContext); + final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); + + if (E2EVersionExtensionsKt.isV2orAbove(e2eeVersion)) { if (encryptedAncestor && object == null) { throw new IllegalStateException("metadata is null!"); } @@ -551,7 +555,7 @@ private void synchronizeData(List folderAndFiles) { e2EVersion = E2EVersion.V1_2; localFilesMap = prefillLocalFilesMap(metadataFileV1, fileDataStorageManager.getFolderContent(mLocalFolder, false)); } else { - e2EVersion = E2EVersion.V2_0; + e2EVersion = E2EVersion.V2_1; localFilesMap = prefillLocalFilesMap(object, fileDataStorageManager.getFolderContent(mLocalFolder, false)); // update counter diff --git a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt index 5c85d0a5b20a..d8a697eb73ca 100644 --- a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt +++ b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt @@ -11,6 +11,7 @@ import android.content.Context import androidx.core.util.component1 import androidx.core.util.component2 import com.nextcloud.client.account.User +import com.nextcloud.utils.extensions.isV2orAbove import com.owncloud.android.datamodel.ArbitraryDataProvider import com.owncloud.android.datamodel.ArbitraryDataProviderImpl import com.owncloud.android.datamodel.FileDataStorageManager @@ -56,7 +57,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor( var delete: DeleteMethod? = null var token: String? = null val e2eVersion = CapabilityUtils.getCapability(context).endToEndEncryptionApiVersion - val isE2EVersionAtLeast2 = e2eVersion >= E2EVersion.V2_0 + val isE2EVersionAtLeast2 = (e2eVersion.isV2orAbove()) try { token = EncryptionUtils.lockFolder(parentFolder, client) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index f29faf6d308a..5acd1a8abdb7 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -23,6 +23,7 @@ import com.nextcloud.client.network.Connectivity; import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.utils.autoRename.AutoRename; +import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -585,7 +586,7 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare } private boolean isEndToEndVersionAtLeastV2() { - return getE2EVersion().compareTo(E2EVersion.V2_0) >= 0; + return E2EVersionExtensionsKt.isV2orAbove(getE2EVersion()); } private E2EVersion getE2EVersion() { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index cfe5f17f3f57..9eeb338e3ec3 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -55,6 +55,7 @@ import com.nextcloud.utils.EditorUtils; import com.nextcloud.utils.ShortcutUtil; import com.nextcloud.utils.extensions.BundleExtensionsKt; +import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.FragmentExtensionsKt; import com.nextcloud.utils.extensions.IntentExtensionsKt; @@ -1953,8 +1954,8 @@ private void encryptFolder(OCFile folder, String token = EncryptionUtils.lockFolder(folder, client); OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName()); - - if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V2_0) { + final var e2eeVersion = ocCapability.getEndToEndEncryptionApiVersion(); + if (E2EVersionExtensionsKt.isV2orAbove(e2eeVersion)) { // Update metadata Pair metadataPair = EncryptionUtils.retrieveMetadata(folder, client, @@ -1980,13 +1981,11 @@ private void encryptFolder(OCFile folder, // unlock folder EncryptionUtils.unlockFolder(folder, client, token); - } else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_0 || - ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_1 || - ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_2 - ) { + + } else if (E2EVersionExtensionsKt.isV1(e2eeVersion)) { // unlock folder EncryptionUtils.unlockFolderV1(folder, client, token); - } else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.UNKNOWN) { + } else if (e2eeVersion == E2EVersion.UNKNOWN) { throw new IllegalArgumentException("Unknown E2E version"); } diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index de6f21b5e1a6..cf87b4724e88 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -17,6 +17,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.nextcloud.client.account.User; +import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -417,7 +418,7 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2(); String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata(); - E2EVersion version = determinateVersion(serializedEncryptedMetadata); + E2EVersion version = E2EVersionExtensionsKt.determineE2EVersion(serializedEncryptedMetadata); switch (version) { case UNKNOWN: @@ -439,7 +440,8 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde user, folder.getLocalId()); - if (capability.getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) { + final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); + if (E2EVersionExtensionsKt.isV2orAbove(e2eeVersion)) { new EncryptionUtilsV2().migrateV1ToV2andUpload( v1, client.getUserId(), @@ -448,8 +450,7 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde new FileDataStorageManager(user, context.getContentResolver()), client, user, - context - ); + context); } else { return v1; } @@ -497,6 +498,10 @@ public static E2EVersion determinateVersion(String metadata) { if ("2.0".equals(v2.getVersion()) || "2".equals(v2.getVersion())) { return E2EVersion.V2_0; } + + if ("2.1".equals(v2.getVersion())) { + return E2EVersion.V2_1; + } } else { return E2EVersion.UNKNOWN; } @@ -1322,7 +1327,7 @@ public static Pair retrieveMetadata(OCFile metadata = new DecryptedFolderMetadataFile(new com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata(), new ArrayList<>(), new HashMap<>(), - E2EVersion.V2_0.getValue()); + E2EVersion.V2_1.getValue()); metadata.getUsers().add(new DecryptedUser(client.getUserId(), publicKey, null)); byte[] metadataKey = EncryptionUtils.generateKey(); @@ -1352,7 +1357,7 @@ public static void uploadMetadata(ServerFileInterface parentFile, RemoteOperationResult uploadMetadataOperationResult; if (metadataExists) { // update metadata - if (version == E2EVersion.V2_0) { + if (E2EVersionExtensionsKt.isV2orAbove(version)) { uploadMetadataOperationResult = new UpdateMetadataV2RemoteOperation( parentFile.getRemoteId(), serializedFolderMetadata, @@ -1368,7 +1373,7 @@ public static void uploadMetadata(ServerFileInterface parentFile, } } else { // store metadata - if (version == E2EVersion.V2_0) { + if (E2EVersionExtensionsKt.isV2orAbove(version)) { uploadMetadataOperationResult = new StoreMetadataV2RemoteOperation( parentFile.getRemoteId(), serializedFolderMetadata, From 5ea666bcb769912ce32ed0388a4b26c56ce9c86f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 23 Jan 2026 13:38:14 +0100 Subject: [PATCH 2/5] use object for consolidate logics Signed-off-by: alperozturk96 --- .../nextcloud/utils/e2ee/E2EVersionHelper.kt | 59 +++++++ .../utils/extensions/E2EVersionExtensions.kt | 48 ------ .../operations/CreateFolderOperation.java | 8 +- .../operations/RefreshFolderOperation.java | 12 +- .../RemoveRemoteEncryptedFileOperation.kt | 7 +- .../operations/UploadFileOperation.java | 6 +- .../ui/fragment/OCFileListFragment.java | 6 +- .../android/utils/EncryptionUtils.java | 153 +++++++---------- .../android/utils/E2EVersionHelperTest.kt | 158 ++++++++++++++++++ 9 files changed, 293 insertions(+), 164 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt delete mode 100644 app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt create mode 100644 app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt diff --git a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt new file mode 100644 index 000000000000..1f7c5178a025 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt @@ -0,0 +1,59 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.e2ee + +import com.google.gson.reflect.TypeToken +import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1 +import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile +import com.owncloud.android.lib.resources.status.E2EVersion +import com.owncloud.android.utils.EncryptionUtils + +object E2EVersionHelper { + + fun isV2orAbove(version: E2EVersion): Boolean { + return version == E2EVersion.V2_0 || version == E2EVersion.V2_1 + } + + fun isV1(version: E2EVersion): Boolean { + return version == E2EVersion.V1_0 || version == E2EVersion.V1_1 || version == E2EVersion.V1_2 + } + + fun getLatestE2EVersion(isV2: Boolean): E2EVersion { + return if (isV2) { + E2EVersion.V2_1 + } else { + E2EVersion.V1_2 + } + } + + fun determineE2EVersion(metadata: String): E2EVersion { + return runCatching { + val v1 = EncryptionUtils.deserializeJSON( + metadata, + object : TypeToken() {} + ) + when (val version = v1?.metadata?.version?.toString()) { + "1.0" -> E2EVersion.V1_0 + "1.1" -> E2EVersion.V1_1 + "1.2" -> E2EVersion.V1_2 + else -> throw IllegalStateException("Unknown V1 version: $version") + } + }.recoverCatching { + val v2 = EncryptionUtils.deserializeJSON( + metadata, + object : TypeToken() {} + ) + + when (v2?.version) { + "2.0", "2" -> E2EVersion.V2_0 + "2.1" -> E2EVersion.V2_1 + else -> E2EVersion.UNKNOWN + } + }.getOrDefault(E2EVersion.UNKNOWN) + } +} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt deleted file mode 100644 index b65a21f134ad..000000000000 --- a/app/src/main/java/com/nextcloud/utils/extensions/E2EVersionExtensions.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.nextcloud.utils.extensions - -import com.google.gson.reflect.TypeToken -import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1 -import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile -import com.owncloud.android.lib.resources.status.E2EVersion -import com.owncloud.android.utils.EncryptionUtils - -fun E2EVersion.isV2orAbove(): Boolean { - return this == E2EVersion.V2_0 || this == E2EVersion.V2_1 -} - -fun E2EVersion.isV1(): Boolean { - return this == E2EVersion.V1_0 || this == E2EVersion.V1_1 || this == E2EVersion.V1_2 -} - -fun determineE2EVersion(metadata: String): E2EVersion { - return runCatching { - val v1 = EncryptionUtils.deserializeJSON( - metadata, - object : TypeToken() {} - ) - when (val version = v1?.metadata?.version?.toString()) { - "1.0" -> E2EVersion.V1_0 - "1.1" -> E2EVersion.V1_1 - "1.2" -> E2EVersion.V1_2 - else -> throw IllegalStateException("Unknown V1 version: $version") - } - }.recoverCatching { - val v2 = EncryptionUtils.deserializeJSON( - metadata, - object : TypeToken() {} - ) - - when (v2?.version) { - "2.0", "2" -> E2EVersion.V2_0 - "2.1" -> E2EVersion.V2_1 - else -> E2EVersion.UNKNOWN - } - }.getOrDefault(E2EVersion.UNKNOWN) -} diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index 3d54f920371a..a5d0fb27a760 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -15,7 +15,7 @@ import android.util.Pair; import com.nextcloud.client.account.User; -import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; +import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -100,9 +100,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { final var capability = getStorageManager().getCapability(user); final var version = capability.getEndToEndEncryptionApiVersion(); - if (E2EVersionExtensionsKt.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { return encryptedCreateV2(parent, client); - } else if (E2EVersionExtensionsKt.isV1(version)) { + } else if (E2EVersionHelper.INSTANCE.isV1(version)) { return encryptedCreateV1(parent, client); } @@ -176,7 +176,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl token, client, metadataExists, - E2EVersion.V1_2, + E2EVersionHelper.INSTANCE.getLatestE2EVersion(false), "", arbitraryDataProvider, user); diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 0d1de5267d1b..8cf0e9ec8071 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -16,7 +16,7 @@ import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainRemoteOperation; import com.nextcloud.client.account.User; import com.nextcloud.common.NextcloudClient; -import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; +import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.nextcloud.utils.extensions.StringExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -542,7 +542,7 @@ private void synchronizeData(List folderAndFiles) { final var capability = CapabilityUtils.getCapability(mContext); final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); - if (E2EVersionExtensionsKt.isV2orAbove(e2eeVersion)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(e2eeVersion)) { if (encryptedAncestor && object == null) { throw new IllegalStateException("metadata is null!"); } @@ -552,10 +552,10 @@ private void synchronizeData(List folderAndFiles) { Map localFilesMap; E2EVersion e2EVersion; if (object instanceof DecryptedFolderMetadataFileV1 metadataFileV1) { - e2EVersion = E2EVersion.V1_2; + e2EVersion = E2EVersionHelper.INSTANCE.getLatestE2EVersion(false); localFilesMap = prefillLocalFilesMap(metadataFileV1, fileDataStorageManager.getFolderContent(mLocalFolder, false)); } else { - e2EVersion = E2EVersion.V2_1; + e2EVersion = E2EVersionHelper.INSTANCE.getLatestE2EVersion(true); localFilesMap = prefillLocalFilesMap(object, fileDataStorageManager.getFolderContent(mLocalFolder, false)); // update counter @@ -602,7 +602,7 @@ private void synchronizeData(List folderAndFiles) { FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName()); // update file name for encrypted files - if (e2EVersion == E2EVersion.V1_2) { + if (e2EVersion == E2EVersionHelper.INSTANCE.getLatestE2EVersion(false)) { updateFileNameForEncryptedFileV1(fileDataStorageManager, (DecryptedFolderMetadataFileV1) object, updatedFile); @@ -625,7 +625,7 @@ private void synchronizeData(List folderAndFiles) { // save updated contents in local database // update file name for encrypted files - if (e2EVersion == E2EVersion.V1_2) { + if (e2EVersion == E2EVersionHelper.INSTANCE.getLatestE2EVersion(false)) { updateFileNameForEncryptedFileV1(fileDataStorageManager, (DecryptedFolderMetadataFileV1) object, mLocalFolder); diff --git a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt index d8a697eb73ca..7522dd043005 100644 --- a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt +++ b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt @@ -11,7 +11,7 @@ import android.content.Context import androidx.core.util.component1 import androidx.core.util.component2 import com.nextcloud.client.account.User -import com.nextcloud.utils.extensions.isV2orAbove +import com.nextcloud.utils.e2ee.E2EVersionHelper import com.owncloud.android.datamodel.ArbitraryDataProvider import com.owncloud.android.datamodel.ArbitraryDataProviderImpl import com.owncloud.android.datamodel.FileDataStorageManager @@ -20,7 +20,6 @@ import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.lib.resources.status.E2EVersion import com.owncloud.android.utils.EncryptionUtils import com.owncloud.android.utils.EncryptionUtilsV2 import com.owncloud.android.utils.theme.CapabilityUtils @@ -57,7 +56,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor( var delete: DeleteMethod? = null var token: String? = null val e2eVersion = CapabilityUtils.getCapability(context).endToEndEncryptionApiVersion - val isE2EVersionAtLeast2 = (e2eVersion.isV2orAbove()) + val isE2EVersionAtLeast2 = (E2EVersionHelper.isV2orAbove(e2eVersion)) try { token = EncryptionUtils.lockFolder(parentFolder, client) @@ -150,7 +149,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor( token, client, metadataExists, - E2EVersion.V1_2, + E2EVersionHelper.getLatestE2EVersion(false), "", arbitraryDataProvider, user diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 5acd1a8abdb7..d2d10f91b5b3 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -23,7 +23,7 @@ import com.nextcloud.client.network.Connectivity; import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.utils.autoRename.AutoRename; -import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; +import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -586,7 +586,7 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare } private boolean isEndToEndVersionAtLeastV2() { - return E2EVersionExtensionsKt.isV2orAbove(getE2EVersion()); + return E2EVersionHelper.INSTANCE.isV2orAbove(getE2EVersion()); } private E2EVersion getE2EVersion() { @@ -855,7 +855,7 @@ private void updateMetadataForV1(DecryptedFolderMetadataFileV1 metadata, E2EData clientData.getToken(), clientData.getClient(), metadataExists, - E2EVersion.V1_2, + E2EVersionHelper.INSTANCE.getLatestE2EVersion(false), "", arbitraryDataProvider, user); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 9eeb338e3ec3..ca512b91c262 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -54,8 +54,8 @@ import com.nextcloud.ui.fileactions.FileActionsBottomSheet; import com.nextcloud.utils.EditorUtils; import com.nextcloud.utils.ShortcutUtil; +import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.nextcloud.utils.extensions.BundleExtensionsKt; -import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.FragmentExtensionsKt; import com.nextcloud.utils.extensions.IntentExtensionsKt; @@ -1955,7 +1955,7 @@ private void encryptFolder(OCFile folder, OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName()); final var e2eeVersion = ocCapability.getEndToEndEncryptionApiVersion(); - if (E2EVersionExtensionsKt.isV2orAbove(e2eeVersion)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(e2eeVersion)) { // Update metadata Pair metadataPair = EncryptionUtils.retrieveMetadata(folder, client, @@ -1982,7 +1982,7 @@ private void encryptFolder(OCFile folder, EncryptionUtils.unlockFolder(folder, client, token); - } else if (E2EVersionExtensionsKt.isV1(e2eeVersion)) { + } else if (E2EVersionHelper.INSTANCE.isV1(e2eeVersion)) { // unlock folder EncryptionUtils.unlockFolderV1(folder, client, token); } else if (e2eeVersion == E2EVersion.UNKNOWN) { diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index cf87b4724e88..946d6edfc398 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -17,7 +17,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.nextcloud.client.account.User; -import com.nextcloud.utils.extensions.E2EVersionExtensionsKt; +import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -405,109 +405,65 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde Context context, User user ) { - RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId()) - .execute(client); + RemoteOperationResult getMetadataOperationResult = + new GetMetadataRemoteOperation(folder.getLocalId()) + .execute(client); if (!getMetadataOperationResult.isSuccess()) { return null; } - OCCapability capability = CapabilityUtils.getCapability(context); - - // decrypt metadata - EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2(); String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata(); - - E2EVersion version = E2EVersionExtensionsKt.determineE2EVersion(serializedEncryptedMetadata); - - switch (version) { - case UNKNOWN: - Log_OC.e(TAG, "Unknown e2e state"); - return null; - - case V1_0, V1_1, V1_2: - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context); - String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY); - String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY); - EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON( - serializedEncryptedMetadata, new TypeToken<>() { - }); - - try { - DecryptedFolderMetadataFileV1 v1 = decryptFolderMetaData(encryptedFolderMetadata, - privateKey, - arbitraryDataProvider, - user, - folder.getLocalId()); - - final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); - if (E2EVersionExtensionsKt.isV2orAbove(e2eeVersion)) { - new EncryptionUtilsV2().migrateV1ToV2andUpload( - v1, - client.getUserId(), - publicKey, - folder, - new FileDataStorageManager(user, context.getContentResolver()), - client, - user, - context); - } else { - return v1; - } - } catch (Exception e) { - // TODO do not crash, but show meaningful error - Log_OC.e(TAG, "Could not decrypt metadata for " + folder.getDecryptedFileName(), e); - return null; - } - - case V2_0: - return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(), - user, - client, - context, - folder); - } - return null; - } - - public static E2EVersion determinateVersion(String metadata) { - try { - EncryptedFolderMetadataFileV1 v1 = EncryptionUtils.deserializeJSON( - metadata, - new TypeToken<>() { - }); - - double version = v1.getMetadata().getVersion(); - - if (version == 1.0) { - return E2EVersion.V1_0; - } else if (version == 1.1) { - return E2EVersion.V1_1; - } else if (version == 1.2) { - return E2EVersion.V1_2; - } else { - throw new IllegalStateException("Unknown version"); - } - } catch (Exception e) { - EncryptedFolderMetadataFile v2 = EncryptionUtils.deserializeJSON( - metadata, - new TypeToken<>() { + E2EVersion version = E2EVersionHelper.INSTANCE.determineE2EVersion(serializedEncryptedMetadata); + + if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { + EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2(); + return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(), + user, + client, + context, + folder); + } else if (E2EVersionHelper.INSTANCE.isV1(version)) { + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context); + String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY); + String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY); + EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON( + serializedEncryptedMetadata, new TypeToken<>() { }); - if (v2 != null) { - if ("2.0".equals(v2.getVersion()) || "2".equals(v2.getVersion())) { - return E2EVersion.V2_0; - } - - if ("2.1".equals(v2.getVersion())) { - return E2EVersion.V2_1; + try { + DecryptedFolderMetadataFileV1 v1 = decryptFolderMetaData(encryptedFolderMetadata, + privateKey, + arbitraryDataProvider, + user, + folder.getLocalId()); + + OCCapability capability = CapabilityUtils.getCapability(context); + final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); + if (E2EVersionHelper.INSTANCE.isV2orAbove(e2eeVersion)) { + new EncryptionUtilsV2().migrateV1ToV2andUpload( + v1, + client.getUserId(), + publicKey, + folder, + new FileDataStorageManager(user, context.getContentResolver()), + client, + user, + context); + } else { + return v1; } - } else { - return E2EVersion.UNKNOWN; + } catch (Exception e) { + // TODO do not crash, but show meaningful error + Log_OC.e(TAG, "Could not decrypt metadata for " + folder.getDecryptedFileName(), e); + return null; } + } else if (version == E2EVersion.UNKNOWN) { + Log_OC.e(TAG, "Unknown e2e state"); + return null; } - return E2EVersion.UNKNOWN; + return null; } /* @@ -1264,7 +1220,10 @@ public static Pair retrieveMetadataV1(OC // new metadata metadata = new DecryptedFolderMetadataFileV1(); metadata.setMetadata(new DecryptedMetadata()); - metadata.getMetadata().setVersion(Double.parseDouble(E2EVersion.V1_2.getValue())); + + final var latestV1E2EEVersion = E2EVersionHelper.INSTANCE.getLatestE2EVersion(false); + + metadata.getMetadata().setVersion(Double.parseDouble(latestV1E2EEVersion.getValue())); metadata.getMetadata().setMetadataKeys(new HashMap<>()); String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey()); String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey); @@ -1323,11 +1282,13 @@ public static Pair retrieveMetadata(OCFile } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND || getMetadataOperationResult.getHttpCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) { + final var latestE2EEV2Version = E2EVersionHelper.INSTANCE.getLatestE2EVersion(true); + // new metadata metadata = new DecryptedFolderMetadataFile(new com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata(), new ArrayList<>(), new HashMap<>(), - E2EVersion.V2_1.getValue()); + latestE2EEV2Version.getValue()); metadata.getUsers().add(new DecryptedUser(client.getUserId(), publicKey, null)); byte[] metadataKey = EncryptionUtils.generateKey(); @@ -1357,7 +1318,7 @@ public static void uploadMetadata(ServerFileInterface parentFile, RemoteOperationResult uploadMetadataOperationResult; if (metadataExists) { // update metadata - if (E2EVersionExtensionsKt.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { uploadMetadataOperationResult = new UpdateMetadataV2RemoteOperation( parentFile.getRemoteId(), serializedFolderMetadata, @@ -1373,7 +1334,7 @@ public static void uploadMetadata(ServerFileInterface parentFile, } } else { // store metadata - if (E2EVersionExtensionsKt.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { uploadMetadataOperationResult = new StoreMetadataV2RemoteOperation( parentFile.getRemoteId(), serializedFolderMetadata, diff --git a/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt new file mode 100644 index 000000000000..ae6d3094ee47 --- /dev/null +++ b/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt @@ -0,0 +1,158 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.utils + +import com.google.gson.reflect.TypeToken +import com.nextcloud.utils.e2ee.E2EVersionHelper +import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1 +import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile +import com.owncloud.android.lib.resources.status.E2EVersion +import io.mockk.every +import io.mockk.mockk +import junit.framework.TestCase +import junit.framework.TestCase.assertEquals +import org.junit.After +import org.junit.Before +import org.junit.Test + +class E2EVersionHelperTest { + + @Before + fun setup() { + io.mockk.mockkStatic(EncryptionUtils::class) + } + + @After + fun teardown() { + io.mockk.unmockkAll() + } + + @Test + fun `isV2orAbove returns true for V2 versions`() { + TestCase.assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_0)) + TestCase.assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_1)) + } + + @Test + fun `isV2orAbove returns false for non V2 versions`() { + TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_0)) + TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_1)) + TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_2)) + TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.UNKNOWN)) + } + + @Test + fun `isV1 returns true for all V1 versions`() { + TestCase.assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_0)) + TestCase.assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_1)) + TestCase.assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_2)) + } + + @Test + fun `isV1 returns false for non V1 versions`() { + TestCase.assertFalse(E2EVersionHelper.isV1(E2EVersion.V2_0)) + TestCase.assertFalse(E2EVersionHelper.isV1(E2EVersion.V2_1)) + TestCase.assertFalse(E2EVersionHelper.isV1(E2EVersion.UNKNOWN)) + } + + @Test + fun `getLatestE2EVersion returns latest V2 when isV2 is true`() { + assertEquals(E2EVersion.V2_1, E2EVersionHelper.getLatestE2EVersion(true)) + } + + @Test + fun `getLatestE2EVersion returns latest V1 when isV2 is false`() { + assertEquals(E2EVersion.V1_2, E2EVersionHelper.getLatestE2EVersion(false)) + } + + @Test + fun `determineE2EVersion returns V1_0`() { + mockV1("1.0") + assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion returns V1_1`() { + mockV1("1.1") + assertEquals(E2EVersion.V1_1, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion returns V1_2`() { + mockV1("1.2") + assertEquals(E2EVersion.V1_2, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion returns V2_0 for 2_0 or 2`() { + mockV1Throw() + mockV2("2.0") + assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EVersion("meta")) + + mockV2("2") + assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion returns V2_1`() { + mockV1Throw() + mockV2("2.1") + assertEquals(E2EVersion.V2_1, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion returns UNKNOWN for unknown V2 version`() { + mockV1Throw() + mockV2("3.0") + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion returns UNKNOWN when both deserializations fail`() { + every { + EncryptionUtils.deserializeJSON(any(), any>()) + } throws RuntimeException() + + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EVersion("meta")) + } + + private fun mockV1(version: String) { + val v1 = mockk { + every { metadata.version } returns version.toDouble() + } + + every { + EncryptionUtils.deserializeJSON( + any(), + ofType>() + ) + } returns v1 + } + + private fun mockV1Throw() { + every { + EncryptionUtils.deserializeJSON( + any(), + ofType>() + ) + } throws RuntimeException() + } + + private fun mockV2(version: String) { + val v2 = mockk { + every { this@mockk.version } returns version + } + + every { + EncryptionUtils.deserializeJSON( + any(), + ofType>() + ) + } returns v2 + } +} From b56f46742c02fcef911c9589e96dd51b698afc68 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 23 Jan 2026 14:28:20 +0100 Subject: [PATCH 3/5] add tests Signed-off-by: alperozturk96 --- .../nextcloud/utils/e2ee/E2EVersionHelper.kt | 68 +++++++------ .../decrypted/DecryptedFolderMetadataFile.kt | 4 +- .../encrypted/EncryptedFolderMetadataFile.kt | 4 +- .../android/utils/EncryptionUtilsV2.kt | 5 +- .../android/utils/E2EVersionHelperTest.kt | 96 ++++++++++++++++--- 5 files changed, 124 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt index 1f7c5178a025..bc8fb5ea6a34 100644 --- a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt +++ b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt @@ -15,45 +15,43 @@ import com.owncloud.android.utils.EncryptionUtils object E2EVersionHelper { - fun isV2orAbove(version: E2EVersion): Boolean { - return version == E2EVersion.V2_0 || version == E2EVersion.V2_1 - } + fun isV2orAbove(version: E2EVersion): Boolean = version == E2EVersion.V2_0 || version == E2EVersion.V2_1 + + fun isV1(version: E2EVersion): Boolean = + version == E2EVersion.V1_0 || version == E2EVersion.V1_1 || version == E2EVersion.V1_2 - fun isV1(version: E2EVersion): Boolean { - return version == E2EVersion.V1_0 || version == E2EVersion.V1_1 || version == E2EVersion.V1_2 + fun getLatestE2EVersion(isV2: Boolean): E2EVersion = if (isV2) { + E2EVersion.V2_1 + } else { + E2EVersion.V1_2 } - fun getLatestE2EVersion(isV2: Boolean): E2EVersion { - return if (isV2) { - E2EVersion.V2_1 - } else { - E2EVersion.V1_2 - } + fun determineE2EFromVersionString(version: String?): E2EVersion = when (version?.trim()) { + "1.0" -> E2EVersion.V1_0 + "1.1" -> E2EVersion.V1_1 + "1.2" -> E2EVersion.V1_2 + "2", "2.0" -> E2EVersion.V2_0 + "2.1" -> E2EVersion.V2_1 + else -> E2EVersion.UNKNOWN } - fun determineE2EVersion(metadata: String): E2EVersion { - return runCatching { - val v1 = EncryptionUtils.deserializeJSON( - metadata, - object : TypeToken() {} - ) - when (val version = v1?.metadata?.version?.toString()) { - "1.0" -> E2EVersion.V1_0 - "1.1" -> E2EVersion.V1_1 - "1.2" -> E2EVersion.V1_2 - else -> throw IllegalStateException("Unknown V1 version: $version") - } - }.recoverCatching { - val v2 = EncryptionUtils.deserializeJSON( - metadata, - object : TypeToken() {} - ) - - when (v2?.version) { - "2.0", "2" -> E2EVersion.V2_0 - "2.1" -> E2EVersion.V2_1 - else -> E2EVersion.UNKNOWN + fun determineE2EVersion(metadata: String): E2EVersion = runCatching { + val v1 = EncryptionUtils.deserializeJSON( + metadata, + object : TypeToken() {} + ) + + determineE2EFromVersionString(v1?.metadata?.version.toString()).also { + if (it == E2EVersion.UNKNOWN) { + throw IllegalStateException("Unknown V1 version") } - }.getOrDefault(E2EVersion.UNKNOWN) - } + } + }.recoverCatching { + val v2 = EncryptionUtils.deserializeJSON( + metadata, + object : TypeToken() {} + ) + + determineE2EFromVersionString(v2.version) + }.getOrDefault(E2EVersion.UNKNOWN) } diff --git a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt index c092b8dc657b..9c41fc4d7a63 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt @@ -7,6 +7,8 @@ */ package com.owncloud.android.datamodel.e2e.v2.decrypted +import com.nextcloud.utils.e2ee.E2EVersionHelper + /** * Decrypted class representation of metadata json of folder metadata. */ @@ -15,5 +17,5 @@ data class DecryptedFolderMetadataFile( var users: MutableList = mutableListOf(), @Transient val filedrop: MutableMap = HashMap(), - val version: String = "2.0" + val version: String = E2EVersionHelper.getLatestE2EVersion(true).value ) diff --git a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt index 5c7b279cb4d7..ddbd9f172dbf 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt @@ -7,6 +7,8 @@ */ package com.owncloud.android.datamodel.e2e.v2.encrypted +import com.nextcloud.utils.e2ee.E2EVersionHelper + /** * Decrypted class representation of metadata json of folder metadata. */ @@ -14,5 +16,5 @@ data class EncryptedFolderMetadataFile( val metadata: EncryptedMetadata, val users: List, @Transient val filedrop: MutableMap?, - val version: String = "2.0" + val version: String = E2EVersionHelper.getLatestE2EVersion(true).value ) diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index 778547f172af..1b9d515af817 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -13,6 +13,7 @@ import androidx.annotation.VisibleForTesting import com.google.gson.reflect.TypeToken import com.nextcloud.client.account.User import com.nextcloud.utils.autoRename.AutoRename +import com.nextcloud.utils.e2ee.E2EVersionHelper import com.nextcloud.utils.extensions.showToast import com.owncloud.android.MainApp import com.owncloud.android.R @@ -606,7 +607,9 @@ class EncryptionUtilsV2 { object : TypeToken() {} ) - val decryptedFolderMetadata = if (v2.version == "2.0" || v2.version == "2") { + val e2eeVersion = E2EVersionHelper.determineE2EFromVersionString(v2.version) + + val decryptedFolderMetadata = if (E2EVersionHelper.isV2orAbove(e2eeVersion)) { val userId = AccountManager.get(context).getUserData( user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID diff --git a/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt index ae6d3094ee47..fbe5e3134b38 100644 --- a/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt +++ b/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt @@ -14,12 +14,14 @@ import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFi import com.owncloud.android.lib.resources.status.E2EVersion import io.mockk.every import io.mockk.mockk -import junit.framework.TestCase -import junit.framework.TestCase.assertEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse import org.junit.After import org.junit.Before import org.junit.Test +@Suppress("TooManyFunctions") class E2EVersionHelperTest { @Before @@ -34,30 +36,30 @@ class E2EVersionHelperTest { @Test fun `isV2orAbove returns true for V2 versions`() { - TestCase.assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_0)) - TestCase.assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_1)) + assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_0)) + assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_1)) } @Test fun `isV2orAbove returns false for non V2 versions`() { - TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_0)) - TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_1)) - TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_2)) - TestCase.assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.UNKNOWN)) + assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_0)) + assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_1)) + assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_2)) + assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.UNKNOWN)) } @Test fun `isV1 returns true for all V1 versions`() { - TestCase.assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_0)) - TestCase.assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_1)) - TestCase.assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_2)) + assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_0)) + assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_1)) + assertTrue(E2EVersionHelper.isV1(E2EVersion.V1_2)) } @Test fun `isV1 returns false for non V1 versions`() { - TestCase.assertFalse(E2EVersionHelper.isV1(E2EVersion.V2_0)) - TestCase.assertFalse(E2EVersionHelper.isV1(E2EVersion.V2_1)) - TestCase.assertFalse(E2EVersionHelper.isV1(E2EVersion.UNKNOWN)) + assertFalse(E2EVersionHelper.isV1(E2EVersion.V2_0)) + assertFalse(E2EVersionHelper.isV1(E2EVersion.V2_1)) + assertFalse(E2EVersionHelper.isV1(E2EVersion.UNKNOWN)) } @Test @@ -76,6 +78,30 @@ class E2EVersionHelperTest { assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) } + @Test + fun `determineE2EVersion via double returns V1_0`() { + mockV1Double(1.0) + assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion via second double returns V1_0`() { + mockV1Double(1.00) + assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion via third double returns V1_1`() { + mockV1Double(1.10) + assertEquals(E2EVersion.V1_1, E2EVersionHelper.determineE2EVersion("meta")) + } + + @Test + fun `determineE2EVersion via fourth double returns V1_2`() { + mockV1Double(1.2) + assertEquals(E2EVersion.V1_2, E2EVersionHelper.determineE2EVersion("meta")) + } + @Test fun `determineE2EVersion returns V1_1`() { mockV1("1.1") @@ -115,12 +141,39 @@ class E2EVersionHelperTest { @Test fun `determineE2EVersion returns UNKNOWN when both deserializations fail`() { every { - EncryptionUtils.deserializeJSON(any(), any>()) + EncryptionUtils.deserializeJSON( + any(), + ofType>() + ) + } throws RuntimeException() + + every { + EncryptionUtils.deserializeJSON( + any(), + ofType>() + ) } throws RuntimeException() assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EVersion("meta")) } + @Test + fun `determineE2EFromVersionString maps versions correctly`() { + assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EFromVersionString("1.0")) + assertEquals(E2EVersion.V1_1, E2EVersionHelper.determineE2EFromVersionString("1.1")) + assertEquals(E2EVersion.V1_2, E2EVersionHelper.determineE2EFromVersionString("1.2")) + assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EFromVersionString("2")) + assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EFromVersionString("2.0")) + assertEquals(E2EVersion.V2_1, E2EVersionHelper.determineE2EFromVersionString("2.1")) + } + + @Test + fun `determineE2EFromVersionString returns UNKNOWN for invalid input`() { + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EFromVersionString(null)) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EFromVersionString("")) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EFromVersionString("3.0")) + } + private fun mockV1(version: String) { val v1 = mockk { every { metadata.version } returns version.toDouble() @@ -134,6 +187,19 @@ class E2EVersionHelperTest { } returns v1 } + private fun mockV1Double(version: Double) { + val v1 = mockk { + every { metadata.version } returns version + } + + every { + EncryptionUtils.deserializeJSON( + any(), + ofType>() + ) + } returns v1 + } + private fun mockV1Throw() { every { EncryptionUtils.deserializeJSON( From 8343ab9a98fb4ca35448896d356ba5873e315063 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 23 Jan 2026 14:35:13 +0100 Subject: [PATCH 4/5] add tests Signed-off-by: alperozturk96 --- .../java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt | 5 +++++ .../android/operations/CreateFolderOperation.java | 5 ++--- .../android/operations/RefreshFolderOperation.java | 3 +-- .../operations/RemoveRemoteEncryptedFileOperation.kt | 4 ++-- .../owncloud/android/operations/UploadFileOperation.java | 8 ++------ .../owncloud/android/ui/fragment/OCFileListFragment.java | 7 +++---- .../java/com/owncloud/android/utils/EncryptionUtils.java | 3 +-- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt index bc8fb5ea6a34..16a0b9bfc8d6 100644 --- a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt +++ b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt @@ -11,12 +11,17 @@ import com.google.gson.reflect.TypeToken import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1 import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile import com.owncloud.android.lib.resources.status.E2EVersion +import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.utils.EncryptionUtils object E2EVersionHelper { + fun isV2orAbove(capability: OCCapability): Boolean = isV2orAbove(capability.endToEndEncryptionApiVersion) + fun isV2orAbove(version: E2EVersion): Boolean = version == E2EVersion.V2_0 || version == E2EVersion.V2_1 + fun isV1(capability: OCCapability): Boolean = isV1(capability.endToEndEncryptionApiVersion) + fun isV1(version: E2EVersion): Boolean = version == E2EVersion.V1_0 || version == E2EVersion.V1_1 || version == E2EVersion.V1_2 diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index a5d0fb27a760..6b76b82ab2a5 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -98,11 +98,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (encryptedAncestor) { final var capability = getStorageManager().getCapability(user); - final var version = capability.getEndToEndEncryptionApiVersion(); - if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(capability)) { return encryptedCreateV2(parent, client); - } else if (E2EVersionHelper.INSTANCE.isV1(version)) { + } else if (E2EVersionHelper.INSTANCE.isV1(capability)) { return encryptedCreateV1(parent, client); } diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 8cf0e9ec8071..d3be4268d248 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -540,9 +540,8 @@ private void synchronizeData(List folderAndFiles) { } final var capability = CapabilityUtils.getCapability(mContext); - final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); - if (E2EVersionHelper.INSTANCE.isV2orAbove(e2eeVersion)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(capability)) { if (encryptedAncestor && object == null) { throw new IllegalStateException("metadata is null!"); } diff --git a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt index 7522dd043005..4e1ca6e3cc63 100644 --- a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt +++ b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt @@ -55,8 +55,8 @@ class RemoveRemoteEncryptedFileOperation internal constructor( var result: RemoteOperationResult var delete: DeleteMethod? = null var token: String? = null - val e2eVersion = CapabilityUtils.getCapability(context).endToEndEncryptionApiVersion - val isE2EVersionAtLeast2 = (E2EVersionHelper.isV2orAbove(e2eVersion)) + val capability = CapabilityUtils.getCapability(context) + val isE2EVersionAtLeast2 = (E2EVersionHelper.isV2orAbove(capability)) try { token = EncryptionUtils.lockFolder(parentFolder, client) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index d2d10f91b5b3..ecc79b2834f2 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -52,7 +52,6 @@ import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; -import com.owncloud.android.lib.resources.status.E2EVersion; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.operations.common.SyncOperation; import com.owncloud.android.operations.e2e.E2EClientData; @@ -586,11 +585,8 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare } private boolean isEndToEndVersionAtLeastV2() { - return E2EVersionHelper.INSTANCE.isV2orAbove(getE2EVersion()); - } - - private E2EVersion getE2EVersion() { - return CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion(); + final var capability = CapabilityUtils.getCapability(mContext); + return E2EVersionHelper.INSTANCE.isV2orAbove(capability); } private long getE2ECounter(OCFile parentFile) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index ca512b91c262..470bd3195834 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1954,8 +1954,7 @@ private void encryptFolder(OCFile folder, String token = EncryptionUtils.lockFolder(folder, client); OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName()); - final var e2eeVersion = ocCapability.getEndToEndEncryptionApiVersion(); - if (E2EVersionHelper.INSTANCE.isV2orAbove(e2eeVersion)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(ocCapability)) { // Update metadata Pair metadataPair = EncryptionUtils.retrieveMetadata(folder, client, @@ -1982,10 +1981,10 @@ private void encryptFolder(OCFile folder, EncryptionUtils.unlockFolder(folder, client, token); - } else if (E2EVersionHelper.INSTANCE.isV1(e2eeVersion)) { + } else if (E2EVersionHelper.INSTANCE.isV1(ocCapability)) { // unlock folder EncryptionUtils.unlockFolderV1(folder, client, token); - } else if (e2eeVersion == E2EVersion.UNKNOWN) { + } else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.UNKNOWN) { throw new IllegalArgumentException("Unknown E2E version"); } diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index 946d6edfc398..384db1a35359 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -439,8 +439,7 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde folder.getLocalId()); OCCapability capability = CapabilityUtils.getCapability(context); - final var e2eeVersion = capability.getEndToEndEncryptionApiVersion(); - if (E2EVersionHelper.INSTANCE.isV2orAbove(e2eeVersion)) { + if (E2EVersionHelper.INSTANCE.isV2orAbove(capability)) { new EncryptionUtilsV2().migrateV1ToV2andUpload( v1, client.getUserId(), From a490d85a398926d42be2484691c0b6ca38edb28f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 23 Jan 2026 14:57:57 +0100 Subject: [PATCH 5/5] add documentation Signed-off-by: alperozturk96 --- .../nextcloud/utils/e2ee/E2EVersionHelper.kt | 43 +++++++++++--- .../decrypted/DecryptedFolderMetadataFile.kt | 2 +- .../encrypted/EncryptedFolderMetadataFile.kt | 2 +- .../operations/CreateFolderOperation.java | 5 +- .../operations/RefreshFolderOperation.java | 10 ++-- .../RemoveRemoteEncryptedFileOperation.kt | 4 +- .../operations/UploadFileOperation.java | 4 +- .../ui/fragment/OCFileListFragment.java | 2 +- .../android/utils/EncryptionUtils.java | 14 ++--- .../android/utils/EncryptionUtilsV2.kt | 4 +- .../android/utils/E2EVersionHelperTest.kt | 58 +++++++++---------- 11 files changed, 88 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt index 16a0b9bfc8d6..90cd2a791c07 100644 --- a/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt +++ b/app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt @@ -16,22 +16,45 @@ import com.owncloud.android.utils.EncryptionUtils object E2EVersionHelper { - fun isV2orAbove(capability: OCCapability): Boolean = isV2orAbove(capability.endToEndEncryptionApiVersion) + /** + * Returns true if the given E2EE version is v2 or newer. + */ + fun isV2Plus(capability: OCCapability): Boolean = isV2Plus(capability.endToEndEncryptionApiVersion) - fun isV2orAbove(version: E2EVersion): Boolean = version == E2EVersion.V2_0 || version == E2EVersion.V2_1 + /** + * Returns true if the given E2EE version is v2 or newer. + */ + fun isV2Plus(version: E2EVersion): Boolean = version == E2EVersion.V2_0 || version == E2EVersion.V2_1 + /** + * Returns true if the given E2EE version is v1.x. + */ fun isV1(capability: OCCapability): Boolean = isV1(capability.endToEndEncryptionApiVersion) + /** + * Returns true if the given E2EE version is v1.x. + */ fun isV1(version: E2EVersion): Boolean = version == E2EVersion.V1_0 || version == E2EVersion.V1_1 || version == E2EVersion.V1_2 - fun getLatestE2EVersion(isV2: Boolean): E2EVersion = if (isV2) { + /** + * Returns the latest supported E2EE version. + * + * @param isV2 indicates whether the E2EE v2 series should be used + */ + fun latestVersion(isV2: Boolean): E2EVersion = if (isV2) { E2EVersion.V2_1 } else { E2EVersion.V1_2 } - fun determineE2EFromVersionString(version: String?): E2EVersion = when (version?.trim()) { + /** + * Maps a raw version string to an [E2EVersion]. + * + * @param version version string + * @return resolved [E2EVersion] or [E2EVersion.UNKNOWN] if unsupported + */ + fun fromVersionString(version: String?): E2EVersion = when (version?.trim()) { "1.0" -> E2EVersion.V1_0 "1.1" -> E2EVersion.V1_1 "1.2" -> E2EVersion.V1_2 @@ -40,13 +63,19 @@ object E2EVersionHelper { else -> E2EVersion.UNKNOWN } - fun determineE2EVersion(metadata: String): E2EVersion = runCatching { + /** + * Determines the E2EE version by inspecting encrypted folder metadata. + * + * Supports both V1 and V2 metadata formats and falls back safely + * to [E2EVersion.UNKNOWN] if parsing fails. + */ + fun fromMetadata(metadata: String): E2EVersion = runCatching { val v1 = EncryptionUtils.deserializeJSON( metadata, object : TypeToken() {} ) - determineE2EFromVersionString(v1?.metadata?.version.toString()).also { + fromVersionString(v1?.metadata?.version.toString()).also { if (it == E2EVersion.UNKNOWN) { throw IllegalStateException("Unknown V1 version") } @@ -57,6 +86,6 @@ object E2EVersionHelper { object : TypeToken() {} ) - determineE2EFromVersionString(v2.version) + fromVersionString(v2.version) }.getOrDefault(E2EVersion.UNKNOWN) } diff --git a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt index 9c41fc4d7a63..da5caae88827 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/decrypted/DecryptedFolderMetadataFile.kt @@ -17,5 +17,5 @@ data class DecryptedFolderMetadataFile( var users: MutableList = mutableListOf(), @Transient val filedrop: MutableMap = HashMap(), - val version: String = E2EVersionHelper.getLatestE2EVersion(true).value + val version: String = E2EVersionHelper.latestVersion(true).value ) diff --git a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt index ddbd9f172dbf..b7d13dfc13c9 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt @@ -16,5 +16,5 @@ data class EncryptedFolderMetadataFile( val metadata: EncryptedMetadata, val users: List, @Transient val filedrop: MutableMap?, - val version: String = E2EVersionHelper.getLatestE2EVersion(true).value + val version: String = E2EVersionHelper.latestVersion(true).value ) diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index 6b76b82ab2a5..a426ee2359e0 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -34,7 +34,6 @@ import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; -import com.owncloud.android.lib.resources.status.E2EVersion; import com.owncloud.android.operations.common.SyncOperation; import com.owncloud.android.utils.EncryptionUtils; import com.owncloud.android.utils.EncryptionUtilsV2; @@ -99,7 +98,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (encryptedAncestor) { final var capability = getStorageManager().getCapability(user); - if (E2EVersionHelper.INSTANCE.isV2orAbove(capability)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(capability)) { return encryptedCreateV2(parent, client); } else if (E2EVersionHelper.INSTANCE.isV1(capability)) { return encryptedCreateV1(parent, client); @@ -175,7 +174,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl token, client, metadataExists, - E2EVersionHelper.INSTANCE.getLatestE2EVersion(false), + E2EVersionHelper.INSTANCE.latestVersion(false), "", arbitraryDataProvider, user); diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index d3be4268d248..9dbfddc41289 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -541,7 +541,7 @@ private void synchronizeData(List folderAndFiles) { final var capability = CapabilityUtils.getCapability(mContext); - if (E2EVersionHelper.INSTANCE.isV2orAbove(capability)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(capability)) { if (encryptedAncestor && object == null) { throw new IllegalStateException("metadata is null!"); } @@ -551,10 +551,10 @@ private void synchronizeData(List folderAndFiles) { Map localFilesMap; E2EVersion e2EVersion; if (object instanceof DecryptedFolderMetadataFileV1 metadataFileV1) { - e2EVersion = E2EVersionHelper.INSTANCE.getLatestE2EVersion(false); + e2EVersion = E2EVersionHelper.INSTANCE.latestVersion(false); localFilesMap = prefillLocalFilesMap(metadataFileV1, fileDataStorageManager.getFolderContent(mLocalFolder, false)); } else { - e2EVersion = E2EVersionHelper.INSTANCE.getLatestE2EVersion(true); + e2EVersion = E2EVersionHelper.INSTANCE.latestVersion(true); localFilesMap = prefillLocalFilesMap(object, fileDataStorageManager.getFolderContent(mLocalFolder, false)); // update counter @@ -601,7 +601,7 @@ private void synchronizeData(List folderAndFiles) { FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName()); // update file name for encrypted files - if (e2EVersion == E2EVersionHelper.INSTANCE.getLatestE2EVersion(false)) { + if (e2EVersion == E2EVersionHelper.INSTANCE.latestVersion(false)) { updateFileNameForEncryptedFileV1(fileDataStorageManager, (DecryptedFolderMetadataFileV1) object, updatedFile); @@ -624,7 +624,7 @@ private void synchronizeData(List folderAndFiles) { // save updated contents in local database // update file name for encrypted files - if (e2EVersion == E2EVersionHelper.INSTANCE.getLatestE2EVersion(false)) { + if (e2EVersion == E2EVersionHelper.INSTANCE.latestVersion(false)) { updateFileNameForEncryptedFileV1(fileDataStorageManager, (DecryptedFolderMetadataFileV1) object, mLocalFolder); diff --git a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt index 4e1ca6e3cc63..e1b47c58e381 100644 --- a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt +++ b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.kt @@ -56,7 +56,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor( var delete: DeleteMethod? = null var token: String? = null val capability = CapabilityUtils.getCapability(context) - val isE2EVersionAtLeast2 = (E2EVersionHelper.isV2orAbove(capability)) + val isE2EVersionAtLeast2 = (E2EVersionHelper.isV2Plus(capability)) try { token = EncryptionUtils.lockFolder(parentFolder, client) @@ -149,7 +149,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor( token, client, metadataExists, - E2EVersionHelper.getLatestE2EVersion(false), + E2EVersionHelper.latestVersion(false), "", arbitraryDataProvider, user diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index ecc79b2834f2..fa8b2a13c72a 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -586,7 +586,7 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare private boolean isEndToEndVersionAtLeastV2() { final var capability = CapabilityUtils.getCapability(mContext); - return E2EVersionHelper.INSTANCE.isV2orAbove(capability); + return E2EVersionHelper.INSTANCE.isV2Plus(capability); } private long getE2ECounter(OCFile parentFile) { @@ -851,7 +851,7 @@ private void updateMetadataForV1(DecryptedFolderMetadataFileV1 metadata, E2EData clientData.getToken(), clientData.getClient(), metadataExists, - E2EVersionHelper.INSTANCE.getLatestE2EVersion(false), + E2EVersionHelper.INSTANCE.latestVersion(false), "", arbitraryDataProvider, user); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 470bd3195834..f0b294a3007d 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1954,7 +1954,7 @@ private void encryptFolder(OCFile folder, String token = EncryptionUtils.lockFolder(folder, client); OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName()); - if (E2EVersionHelper.INSTANCE.isV2orAbove(ocCapability)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(ocCapability)) { // Update metadata Pair metadataPair = EncryptionUtils.retrieveMetadata(folder, client, diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index 384db1a35359..078bc598b357 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -414,9 +414,9 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde } String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata(); - E2EVersion version = E2EVersionHelper.INSTANCE.determineE2EVersion(serializedEncryptedMetadata); + E2EVersion version = E2EVersionHelper.INSTANCE.fromMetadata(serializedEncryptedMetadata); - if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(version)) { EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2(); return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(), user, @@ -439,7 +439,7 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde folder.getLocalId()); OCCapability capability = CapabilityUtils.getCapability(context); - if (E2EVersionHelper.INSTANCE.isV2orAbove(capability)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(capability)) { new EncryptionUtilsV2().migrateV1ToV2andUpload( v1, client.getUserId(), @@ -1220,7 +1220,7 @@ public static Pair retrieveMetadataV1(OC metadata = new DecryptedFolderMetadataFileV1(); metadata.setMetadata(new DecryptedMetadata()); - final var latestV1E2EEVersion = E2EVersionHelper.INSTANCE.getLatestE2EVersion(false); + final var latestV1E2EEVersion = E2EVersionHelper.INSTANCE.latestVersion(false); metadata.getMetadata().setVersion(Double.parseDouble(latestV1E2EEVersion.getValue())); metadata.getMetadata().setMetadataKeys(new HashMap<>()); @@ -1281,7 +1281,7 @@ public static Pair retrieveMetadata(OCFile } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND || getMetadataOperationResult.getHttpCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) { - final var latestE2EEV2Version = E2EVersionHelper.INSTANCE.getLatestE2EVersion(true); + final var latestE2EEV2Version = E2EVersionHelper.INSTANCE.latestVersion(true); // new metadata metadata = new DecryptedFolderMetadataFile(new com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata(), @@ -1317,7 +1317,7 @@ public static void uploadMetadata(ServerFileInterface parentFile, RemoteOperationResult uploadMetadataOperationResult; if (metadataExists) { // update metadata - if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(version)) { uploadMetadataOperationResult = new UpdateMetadataV2RemoteOperation( parentFile.getRemoteId(), serializedFolderMetadata, @@ -1333,7 +1333,7 @@ public static void uploadMetadata(ServerFileInterface parentFile, } } else { // store metadata - if (E2EVersionHelper.INSTANCE.isV2orAbove(version)) { + if (E2EVersionHelper.INSTANCE.isV2Plus(version)) { uploadMetadataOperationResult = new StoreMetadataV2RemoteOperation( parentFile.getRemoteId(), serializedFolderMetadata, diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index 1b9d515af817..6f8fdc011f09 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -607,9 +607,9 @@ class EncryptionUtilsV2 { object : TypeToken() {} ) - val e2eeVersion = E2EVersionHelper.determineE2EFromVersionString(v2.version) + val e2eeVersion = E2EVersionHelper.fromVersionString(v2.version) - val decryptedFolderMetadata = if (E2EVersionHelper.isV2orAbove(e2eeVersion)) { + val decryptedFolderMetadata = if (E2EVersionHelper.isV2Plus(e2eeVersion)) { val userId = AccountManager.get(context).getUserData( user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID diff --git a/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt index fbe5e3134b38..3671e3f53b76 100644 --- a/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt +++ b/app/src/test/java/com/owncloud/android/utils/E2EVersionHelperTest.kt @@ -36,16 +36,16 @@ class E2EVersionHelperTest { @Test fun `isV2orAbove returns true for V2 versions`() { - assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_0)) - assertTrue(E2EVersionHelper.isV2orAbove(E2EVersion.V2_1)) + assertTrue(E2EVersionHelper.isV2Plus(E2EVersion.V2_0)) + assertTrue(E2EVersionHelper.isV2Plus(E2EVersion.V2_1)) } @Test fun `isV2orAbove returns false for non V2 versions`() { - assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_0)) - assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_1)) - assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.V1_2)) - assertFalse(E2EVersionHelper.isV2orAbove(E2EVersion.UNKNOWN)) + assertFalse(E2EVersionHelper.isV2Plus(E2EVersion.V1_0)) + assertFalse(E2EVersionHelper.isV2Plus(E2EVersion.V1_1)) + assertFalse(E2EVersionHelper.isV2Plus(E2EVersion.V1_2)) + assertFalse(E2EVersionHelper.isV2Plus(E2EVersion.UNKNOWN)) } @Test @@ -64,78 +64,78 @@ class E2EVersionHelperTest { @Test fun `getLatestE2EVersion returns latest V2 when isV2 is true`() { - assertEquals(E2EVersion.V2_1, E2EVersionHelper.getLatestE2EVersion(true)) + assertEquals(E2EVersion.V2_1, E2EVersionHelper.latestVersion(true)) } @Test fun `getLatestE2EVersion returns latest V1 when isV2 is false`() { - assertEquals(E2EVersion.V1_2, E2EVersionHelper.getLatestE2EVersion(false)) + assertEquals(E2EVersion.V1_2, E2EVersionHelper.latestVersion(false)) } @Test fun `determineE2EVersion returns V1_0`() { mockV1("1.0") - assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_0, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion via double returns V1_0`() { mockV1Double(1.0) - assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_0, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion via second double returns V1_0`() { mockV1Double(1.00) - assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_0, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion via third double returns V1_1`() { mockV1Double(1.10) - assertEquals(E2EVersion.V1_1, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_1, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion via fourth double returns V1_2`() { mockV1Double(1.2) - assertEquals(E2EVersion.V1_2, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_2, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion returns V1_1`() { mockV1("1.1") - assertEquals(E2EVersion.V1_1, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_1, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion returns V1_2`() { mockV1("1.2") - assertEquals(E2EVersion.V1_2, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V1_2, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion returns V2_0 for 2_0 or 2`() { mockV1Throw() mockV2("2.0") - assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V2_0, E2EVersionHelper.fromMetadata("meta")) mockV2("2") - assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V2_0, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion returns V2_1`() { mockV1Throw() mockV2("2.1") - assertEquals(E2EVersion.V2_1, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.V2_1, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EVersion returns UNKNOWN for unknown V2 version`() { mockV1Throw() mockV2("3.0") - assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.fromMetadata("meta")) } @Test @@ -154,24 +154,24 @@ class E2EVersionHelperTest { ) } throws RuntimeException() - assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EVersion("meta")) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.fromMetadata("meta")) } @Test fun `determineE2EFromVersionString maps versions correctly`() { - assertEquals(E2EVersion.V1_0, E2EVersionHelper.determineE2EFromVersionString("1.0")) - assertEquals(E2EVersion.V1_1, E2EVersionHelper.determineE2EFromVersionString("1.1")) - assertEquals(E2EVersion.V1_2, E2EVersionHelper.determineE2EFromVersionString("1.2")) - assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EFromVersionString("2")) - assertEquals(E2EVersion.V2_0, E2EVersionHelper.determineE2EFromVersionString("2.0")) - assertEquals(E2EVersion.V2_1, E2EVersionHelper.determineE2EFromVersionString("2.1")) + assertEquals(E2EVersion.V1_0, E2EVersionHelper.fromVersionString("1.0")) + assertEquals(E2EVersion.V1_1, E2EVersionHelper.fromVersionString("1.1")) + assertEquals(E2EVersion.V1_2, E2EVersionHelper.fromVersionString("1.2")) + assertEquals(E2EVersion.V2_0, E2EVersionHelper.fromVersionString("2")) + assertEquals(E2EVersion.V2_0, E2EVersionHelper.fromVersionString("2.0")) + assertEquals(E2EVersion.V2_1, E2EVersionHelper.fromVersionString("2.1")) } @Test fun `determineE2EFromVersionString returns UNKNOWN for invalid input`() { - assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EFromVersionString(null)) - assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EFromVersionString("")) - assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.determineE2EFromVersionString("3.0")) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.fromVersionString(null)) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.fromVersionString("")) + assertEquals(E2EVersion.UNKNOWN, E2EVersionHelper.fromVersionString("3.0")) } private fun mockV1(version: String) {