Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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.lib.resources.status.OCCapability
import com.owncloud.android.utils.EncryptionUtils

object E2EVersionHelper {

/**
* Returns true if the given E2EE version is v2 or newer.
*/
fun isV2Plus(capability: OCCapability): Boolean = isV2Plus(capability.endToEndEncryptionApiVersion)

/**
* 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

/**
* 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
}

/**
* 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
"2", "2.0" -> E2EVersion.V2_0
"2.1" -> E2EVersion.V2_1
else -> E2EVersion.UNKNOWN
}

/**
* 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 {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacement of public static E2EVersion determinateVersion(String metadata) {

Tries v1 if fails v2 if fails E2EVersion.UNKNOWN

val v1 = EncryptionUtils.deserializeJSON<EncryptedFolderMetadataFileV1>(
metadata,
object : TypeToken<EncryptedFolderMetadataFileV1>() {}
)

fromVersionString(v1?.metadata?.version.toString()).also {
if (it == E2EVersion.UNKNOWN) {
throw IllegalStateException("Unknown V1 version")
}
}
}.recoverCatching {
val v2 = EncryptionUtils.deserializeJSON<EncryptedFolderMetadataFile>(
metadata,
object : TypeToken<EncryptedFolderMetadataFile>() {}
)

fromVersionString(v2.version)
}.getOrDefault(E2EVersion.UNKNOWN)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -15,5 +17,5 @@ data class DecryptedFolderMetadataFile(
var users: MutableList<DecryptedUser> = mutableListOf(),
@Transient
val filedrop: MutableMap<String, DecryptedFile> = HashMap(),
val version: String = "2.0"
val version: String = E2EVersionHelper.latestVersion(true).value
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaces hardcoded hidden strings with actual latest version so that in future we dont come across issues like this. @tobiasKaminsky

)
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
*/
package com.owncloud.android.datamodel.e2e.v2.encrypted

import com.nextcloud.utils.e2ee.E2EVersionHelper

/**
* Decrypted class representation of metadata json of folder metadata.
*/
data class EncryptedFolderMetadataFile(
val metadata: EncryptedMetadata,
val users: List<EncryptedUser>,
@Transient val filedrop: MutableMap<String, EncryptedFiledrop>?,
val version: String = "2.0"
val version: String = E2EVersionHelper.latestVersion(true).value
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import android.util.Pair;

import com.nextcloud.client.account.User;
import com.nextcloud.utils.e2ee.E2EVersionHelper;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
Expand All @@ -33,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;
Expand Down Expand Up @@ -96,15 +96,15 @@ 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);

if (E2EVersionHelper.INSTANCE.isV2Plus(capability)) {
return encryptedCreateV2(parent, client);
} else if (E2EVersionHelper.INSTANCE.isV1(capability)) {
return encryptedCreateV1(parent, client);
}
return new RemoteOperationResult(new IllegalStateException("E2E not supported"));

return new RemoteOperationResult<>(new IllegalStateException("E2E not supported"));
} else {
return normalCreate(client);
}
Expand Down Expand Up @@ -174,7 +174,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl
token,
client,
metadataExists,
E2EVersion.V1_2,
E2EVersionHelper.INSTANCE.latestVersion(false),
"",
arbitraryDataProvider,
user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.e2ee.E2EVersionHelper;
import com.nextcloud.utils.extensions.StringExtensionsKt;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
Expand Down Expand Up @@ -538,7 +539,9 @@ private void synchronizeData(List<Object> folderAndFiles) {
mContext);
}

if (CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) {
final var capability = CapabilityUtils.getCapability(mContext);

if (E2EVersionHelper.INSTANCE.isV2Plus(capability)) {
if (encryptedAncestor && object == null) {
throw new IllegalStateException("metadata is null!");
}
Expand All @@ -548,10 +551,10 @@ private void synchronizeData(List<Object> folderAndFiles) {
Map<String, OCFile> localFilesMap;
E2EVersion e2EVersion;
if (object instanceof DecryptedFolderMetadataFileV1 metadataFileV1) {
e2EVersion = E2EVersion.V1_2;
e2EVersion = E2EVersionHelper.INSTANCE.latestVersion(false);
localFilesMap = prefillLocalFilesMap(metadataFileV1, fileDataStorageManager.getFolderContent(mLocalFolder, false));
} else {
e2EVersion = E2EVersion.V2_0;
e2EVersion = E2EVersionHelper.INSTANCE.latestVersion(true);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, for each version iteration, these hidden hardcoded values need to be upgraded. With latestVersion function we can just replace from there. In this case, E2EVersion.V2_1 should be the new value.

localFilesMap = prefillLocalFilesMap(object, fileDataStorageManager.getFolderContent(mLocalFolder, false));

// update counter
Expand Down Expand Up @@ -598,7 +601,7 @@ private void synchronizeData(List<Object> folderAndFiles) {
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());

// update file name for encrypted files
if (e2EVersion == E2EVersion.V1_2) {
if (e2EVersion == E2EVersionHelper.INSTANCE.latestVersion(false)) {
updateFileNameForEncryptedFileV1(fileDataStorageManager,
(DecryptedFolderMetadataFileV1) object,
updatedFile);
Expand All @@ -621,7 +624,7 @@ private void synchronizeData(List<Object> folderAndFiles) {

// save updated contents in local database
// update file name for encrypted files
if (e2EVersion == E2EVersion.V1_2) {
if (e2EVersion == E2EVersionHelper.INSTANCE.latestVersion(false)) {
updateFileNameForEncryptedFileV1(fileDataStorageManager,
(DecryptedFolderMetadataFileV1) object,
mLocalFolder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.e2ee.E2EVersionHelper
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.datamodel.FileDataStorageManager
Expand All @@ -19,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
Expand Down Expand Up @@ -55,8 +55,8 @@ class RemoveRemoteEncryptedFileOperation internal constructor(
var result: RemoteOperationResult<Void>
var delete: DeleteMethod? = null
var token: String? = null
val e2eVersion = CapabilityUtils.getCapability(context).endToEndEncryptionApiVersion
val isE2EVersionAtLeast2 = e2eVersion >= E2EVersion.V2_0
val capability = CapabilityUtils.getCapability(context)
val isE2EVersionAtLeast2 = (E2EVersionHelper.isV2Plus(capability))

try {
token = EncryptionUtils.lockFolder(parentFolder, client)
Expand Down Expand Up @@ -149,7 +149,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor(
token,
client,
metadataExists,
E2EVersion.V1_2,
E2EVersionHelper.latestVersion(false),
"",
arbitraryDataProvider,
user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.e2ee.E2EVersionHelper;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
Expand Down Expand Up @@ -51,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;
Expand Down Expand Up @@ -585,11 +585,8 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
}

private boolean isEndToEndVersionAtLeastV2() {
return getE2EVersion().compareTo(E2EVersion.V2_0) >= 0;
}

private E2EVersion getE2EVersion() {
return CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion();
final var capability = CapabilityUtils.getCapability(mContext);
return E2EVersionHelper.INSTANCE.isV2Plus(capability);
}

private long getE2ECounter(OCFile parentFile) {
Expand Down Expand Up @@ -854,7 +851,7 @@ private void updateMetadataForV1(DecryptedFolderMetadataFileV1 metadata, E2EData
clientData.getToken(),
clientData.getClient(),
metadataExists,
E2EVersion.V1_2,
E2EVersionHelper.INSTANCE.latestVersion(false),
"",
arbitraryDataProvider,
user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
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.FileExtensionsKt;
import com.nextcloud.utils.extensions.FragmentExtensionsKt;
Expand Down Expand Up @@ -1953,8 +1954,7 @@ private void encryptFolder(OCFile folder,
String token = EncryptionUtils.lockFolder(folder, client);

OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName());

if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V2_0) {
if (E2EVersionHelper.INSTANCE.isV2Plus(ocCapability)) {
// Update metadata
Pair<Boolean, DecryptedFolderMetadataFile> metadataPair = EncryptionUtils.retrieveMetadata(folder,
client,
Expand All @@ -1980,10 +1980,8 @@ 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 (E2EVersionHelper.INSTANCE.isV1(ocCapability)) {
// unlock folder
EncryptionUtils.unlockFolderV1(folder, client, token);
} else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.UNKNOWN) {
Expand Down
Loading
Loading