Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser
import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFiledrop
import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFiledropUser
import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile
import com.owncloud.android.lib.resources.status.E2EVersion
import com.owncloud.android.operations.RefreshFolderOperation
import com.owncloud.android.util.EncryptionTestIT
import junit.framework.TestCase.assertEquals
Expand Down Expand Up @@ -469,6 +470,7 @@ class EncryptionUtilsV2IT : EncryptionIT() {
val v2 = encryptionUtilsV2.migrateV1ToV2(
v1,
enc1UserId,
storageManager.user,
enc1Cert,
folder,
storageManager
Expand Down Expand Up @@ -601,7 +603,7 @@ class EncryptionUtilsV2IT : EncryptionIT() {

metadata.keyChecksums.add(encryptionUtilsV2.hashMetadataKey(metadata.metadataKey))

return DecryptedFolderMetadataFile(metadata, users, mutableMapOf())
return DecryptedFolderMetadataFile(metadata, users, mutableMapOf(), E2EVersion.V2_1.value)
}

@Test
Expand Down
34 changes: 4 additions & 30 deletions app/src/main/java/com/nextcloud/utils/e2ee/E2EVersionHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,55 +37,29 @@ object E2EVersionHelper {
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 {
val v1 = EncryptionUtils.deserializeJSON<EncryptedFolderMetadataFileV1>(
val v1 = EncryptionUtils.deserializeJSON(
metadata,
object : TypeToken<EncryptedFolderMetadataFileV1>() {}
)

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

fromVersionString(v2.version)
E2EVersion.fromValue(v2.version)
}.getOrDefault(E2EVersion.UNKNOWN)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2452,6 +2452,16 @@ private Cursor getCapabilityCursorForAccount(String accountName) {
return cursor;
}

public String getE2EEVersion(@NonNull User user) {
final var capabilities = getCapability(user);
return capabilities.getEndToEndEncryptionApiVersion().getValue();
}

public E2EVersion getE2EEVersionObject(@NonNull User user) {
final var capabilities = getCapability(user);
return capabilities.getEndToEndEncryptionApiVersion();
}

@NonNull
public OCCapability getCapability(User user) {
return getCapability(user.getAccountName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@
*/
package com.owncloud.android.datamodel.e2e.v2.decrypted

import com.nextcloud.utils.e2ee.E2EVersionHelper

/**
* Decrypted class representation of metadata json of folder metadata.
*/
data class DecryptedFolderMetadataFile(
val metadata: DecryptedMetadata,
var users: MutableList<DecryptedUser> = mutableListOf(),
@Transient
val filedrop: MutableMap<String, DecryptedFile> = HashMap(),
val version: String = E2EVersionHelper.latestVersion(true).value
val version: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ data class EncryptedFolderMetadataFile(
val metadata: EncryptedMetadata,
val users: List<EncryptedUser>,
@Transient val filedrop: MutableMap<String, EncryptedFiledrop>?,
val version: String = E2EVersionHelper.latestVersion(true).value
val version: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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 @@ -145,8 +146,8 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl
privateKey,
publicKey,
arbitraryDataProvider,
user
);
user,
E2EVersion.V1_2.getValue());

metadataExists = metadataPair.first;
metadata = metadataPair.second;
Expand Down Expand Up @@ -183,7 +184,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl
token,
client,
metadataExists,
E2EVersionHelper.INSTANCE.latestVersion(false),
E2EVersion.V1_2,
"",
arbitraryDataProvider,
user);
Expand Down Expand Up @@ -304,7 +305,8 @@ private RemoteOperationResult encryptedCreateV2(OCFile parent, OwnCloudClient cl
String remoteId = result.getResultData();

if (result.isSuccess()) {
DecryptedFolderMetadataFile subFolderMetadata = encryptionUtilsV2.createDecryptedFolderMetadataFile();
String e2eeVersion = getStorageManager().getE2EEVersion(user);
DecryptedFolderMetadataFile subFolderMetadata = encryptionUtilsV2.createDecryptedFolderMetadataFile(e2eeVersion);

// upload metadata
encryptionUtilsV2.serializeAndUploadMetadata(remoteId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ protected RemoteOperationResult run(OwnCloudClient client) {
boolean metadataExists;
if (metadata == null) {
String cert = EncryptionUtils.retrievePublicKeyForUser(user, context);
metadata = new EncryptionUtilsV2().createDecryptedFolderMetadataFile();
String e2eeVersion = getStorageManager().getE2EEVersion(user);
metadata = new EncryptionUtilsV2().createDecryptedFolderMetadataFile(e2eeVersion);
metadata.getUsers().add(new DecryptedUser(client.getUserId(), cert, null));

metadataExists = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,12 +549,10 @@ private void synchronizeData(List<Object> folderAndFiles) {

// get current data about local contents of the folder to synchronize
Map<String, OCFile> localFilesMap;
E2EVersion e2EVersion;
E2EVersion e2EVersion = fileDataStorageManager.getE2EEVersionObject(user);
if (object instanceof DecryptedFolderMetadataFileV1 metadataFileV1) {
e2EVersion = E2EVersionHelper.INSTANCE.latestVersion(false);
localFilesMap = prefillLocalFilesMap(metadataFileV1, fileDataStorageManager.getFolderContent(mLocalFolder, false));
} else {
e2EVersion = E2EVersionHelper.INSTANCE.latestVersion(true);
localFilesMap = prefillLocalFilesMap(object, fileDataStorageManager.getFolderContent(mLocalFolder, false));

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

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

// save updated contents in local database
// update file name for encrypted files
if (e2EVersion == E2EVersionHelper.INSTANCE.latestVersion(false)) {
if (e2EVersion == E2EVersion.V1_2) {
updateFileNameForEncryptedFileV1(fileDataStorageManager,
(DecryptedFolderMetadataFileV1) object,
mLocalFolder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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 @@ -124,7 +125,8 @@ class RemoveRemoteEncryptedFileOperation internal constructor(
privateKey,
publicKey,
arbitraryDataProvider,
user
user,
E2EVersion.V1_2.value
)

val (result, delete) = deleteRemoteFile(client, token)
Expand All @@ -149,7 +151,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor(
token,
client,
metadataExists,
E2EVersionHelper.latestVersion(false),
E2EVersion.V1_2,
"",
arbitraryDataProvider,
user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
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 @@ -883,7 +884,7 @@ private void updateMetadataForV1(DecryptedFolderMetadataFileV1 metadata, E2EData
clientData.getToken(),
clientData.getClient(),
metadataExists,
E2EVersionHelper.INSTANCE.latestVersion(false),
E2EVersion.V1_2,
"",
arbitraryDataProvider,
user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import com.nextcloud.client.account.User
import com.nextcloud.client.appinfo.AppInfo
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.core.Clock
import com.nextcloud.operations.GetMethod
import org.json.JSONObject
import com.nextcloud.client.database.entity.SyncedFolderEntity
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.editimage.EditImageActivity
Expand Down Expand Up @@ -299,6 +301,82 @@ class FileDisplayActivity :
startMetadataSyncForRoot()
handleBackPress()
setupDrawer(menuItemId)
logOcsCredentials()
// Debug helper: fetch raw capabilities JSON and dump `ocs`, `meta` and `data` nodes to logs
// This is temporary and used for debugging backend responses on activity create.
debugDumpCapabilities()
}

private fun debugDumpCapabilities() {
lifecycleScope.launch(Dispatchers.IO) {
try {
val user = accountManager.user
val client = try {
clientFactory.createNextcloudClient(user)
} catch (e: Exception) {
Log_OC.e(this@FileDisplayActivity, "debugDumpCapabilities: failed creating client", e)
return@launch
}

val requestUri = client.baseUri
val uriBuilder = requestUri.buildUpon()
uriBuilder.appendEncodedPath("ocs/v2.php/cloud/capabilities")
uriBuilder.appendQueryParameter("format", "json")

val get = GetMethod(uriBuilder.build().toString(), true)

val status = try {
client.execute(get)
} catch (e: Exception) {
Log_OC.e(this@FileDisplayActivity, "debugDumpCapabilities: execute failed", e)
return@launch
}

if (status == 200) {
val response = get.getResponseBodyAsString()
try {
val respJSON = JSONObject(response)
val respOCS = respJSON.getJSONObject("ocs")
val respMeta = respOCS.getJSONObject("meta")
val respData = respOCS.getJSONObject("data")

Log_OC.d(this@FileDisplayActivity, "[dbg] respOCS: $respOCS")
Log_OC.d(this@FileDisplayActivity, "[dbg] respMeta: $respMeta")
Log_OC.d(this@FileDisplayActivity, "[dbg] respData: $respData")
} catch (e: Exception) {
Log_OC.e(this@FileDisplayActivity, "debugDumpCapabilities: parsing failed", e)
Log_OC.d(this@FileDisplayActivity, "debugDumpCapabilities: raw response: $response")
}
} else {
Log_OC.e(this@FileDisplayActivity, "debugDumpCapabilities: unexpected status=$status")
}
} catch (e: Exception) {
Log_OC.e(this@FileDisplayActivity, "debugDumpCapabilities: unexpected error", e)
}
}
}

@Suppress("DEPRECATION")
private fun logOcsCredentials() {
lifecycleScope.launch(Dispatchers.IO) {
val user = accountManager.user
val serverUrl = user.server.uri.toString()
val accountName = user.accountName

try {
val client = clientFactory.create(user)
val username = client.userIdPlain
val authToken = client.credentials.authToken
Log_OC.d(TAG, "OCS credentials — serverUrl=$serverUrl")
Log_OC.d(TAG, "OCS credentials — accountName=$accountName username=$username authToken=$authToken")
} catch (e: CreationException) {
Log_OC.e(
TAG,
"OCS credentials — serverUrl=$serverUrl accountName=$accountName (client creation failed)",
e
)
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1987,10 +1987,8 @@ private void encryptFolder(OCFile folder,

DecryptedFolderMetadataFileV1 metadata = new DecryptedFolderMetadataFileV1();
metadata.setMetadata(new DecryptedMetadata());

final var latestV1E2EEVersion = E2EVersionHelper.INSTANCE.latestVersion(false);

metadata.getMetadata().setVersion(Double.parseDouble(latestV1E2EEVersion.getValue()));
final var latestV1E2EEVersion = storageManager.getE2EEVersion(user);
metadata.getMetadata().setVersion(Double.parseDouble(latestV1E2EEVersion));
metadata.getMetadata().setMetadataKeys(new HashMap<>());
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
Expand All @@ -2010,7 +2008,7 @@ private void encryptFolder(OCFile folder,
token,
client,
false,
E2EVersionHelper.INSTANCE.latestVersion(false),
storageManager.getE2EEVersionObject(user),
"",
arbitraryDataProvider,
user);
Expand Down
Loading
Loading