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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.operations.e2e.E2EClientData;
import com.owncloud.android.operations.e2e.E2EData;
import com.owncloud.android.operations.e2e.E2EECounterFetchOperation;
import com.owncloud.android.operations.e2e.E2EFiles;
import com.owncloud.android.operations.upload.UploadFileException;
import com.owncloud.android.operations.upload.UploadFileOperationExtensionsKt;
Expand Down Expand Up @@ -516,6 +517,7 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
FileChannel channel = null;
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(getContext());
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
E2EECounterFetchOperation e2eeCounterFetchOperation = new E2EECounterFetchOperation();

try {
result = checkConditions(e2eFiles.getOriginalFile());
Expand All @@ -524,7 +526,8 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
return result;
}

long counter = getE2ECounter(parentFile);
long lastCounter = e2eeCounterFetchOperation.fetch(mContext, parentFile, getStorageManager(), user, client);
long counter = lastCounter + 1;

try {
token = getFolderUnlockTokenOrLockFolder(client, parentFile, counter);
Expand Down Expand Up @@ -621,16 +624,6 @@ private boolean isEndToEndVersionAtLeastV2() {
return E2EVersionHelper.INSTANCE.isV2Plus(capability);
}

private long getE2ECounter(OCFile parentFile) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please check parent folder.

long counter = -1;

if (isEndToEndVersionAtLeastV2()) {
counter = parentFile.getE2eCounter() + 1;
}

return counter;
}

private String getFolderUnlockTokenOrLockFolder(OwnCloudClient client, OCFile parentFile, long counter) throws UploadException {
if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
Log_OC.d(TAG, "Reusing existing folder unlock token from previous upload attempt");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.operations.e2e

import android.content.Context
import com.nextcloud.client.account.User
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.utils.e2ee.E2EVersionHelper.isV2Plus
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.RefreshFolderOperation
import com.owncloud.android.utils.theme.CapabilityUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class E2EECounterFetchOperation {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

companion object {
private const val TAG = "E2EECounterFetchOperation"
}

fun fetchAsync(
context: Context,
file: OCFile,
storageManager: FileDataStorageManager,
user: User,
clientFactory: ClientFactory,
onComplete: (Long) -> Unit
) {
scope.launch {
val client = clientFactory.create(user)
val counter = fetch(context, file, storageManager, user, client)
withContext(Dispatchers.Main) {
onComplete(counter)
}
}
}

fun fetch(
context: Context,
file: OCFile,
storageManager: FileDataStorageManager,
user: User,
client: OwnCloudClient
): Long = try {
val metadata = RefreshFolderOperation
.getDecryptedFolderMetadata(true, file, client, user, context)
if (metadata is DecryptedFolderMetadataFile) {
val result = metadata.metadata.counter
file.setE2eCounter(result)
storageManager.saveFile(file)
Log_OC.i(TAG, "latest counter fetched, counter is: $result")
result
} else {
val result = getFallbackCounter(context, file)
Log_OC.w(TAG, "local counter is used, counter is: $result")
result
}
} catch (e: Exception) {
Log_OC.e(TAG, "error refreshing E2E counter: ${e.message}")
getFallbackCounter(context, file)
}

fun stop() {
scope.cancel()
}

// region private methods
private fun supportsE2EEv2(context: Context): Boolean {
val capability = CapabilityUtils.getCapability(context)
return isV2Plus(capability)
}

private fun getFallbackCounter(context: Context, file: OCFile): Long {
if (!supportsE2EEv2(context)) {
return -1
}
return file.e2eCounter + 1
}
// endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,14 @@
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SharesType;
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.shares.OCShare;
import com.owncloud.android.lib.resources.shares.ShareType;
import com.owncloud.android.lib.resources.status.NextcloudVersion;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.e2e.E2EECounterFetchOperation;
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
import com.owncloud.android.ui.activity.FileActivity;
import com.owncloud.android.ui.activity.FileDisplayActivity;
Expand Down Expand Up @@ -116,6 +114,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda

private ShareeListAdapter externalShareeListAdapter;

private E2EECounterFetchOperation e2eeCounterFetchOperation;

@Inject UserAccountManager accountManager;
@Inject ClientFactory clientFactory;
@Inject ViewThemeUtils viewThemeUtils;
Expand Down Expand Up @@ -155,6 +155,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
}

fileActivity = (FileActivity) getActivity();
e2eeCounterFetchOperation = new E2EECounterFetchOperation();

if (fileActivity == null) {
throw new IllegalArgumentException("FileActivity may not be null");
Expand Down Expand Up @@ -272,6 +273,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
@Override
public void onDestroyView() {
super.onDestroyView();
e2eeCounterFetchOperation.stop();
binding = null;
}

Expand Down Expand Up @@ -349,27 +351,29 @@ private void setupView() {
binding.internalShareHeadline.setText(getResources().getString(R.string.internal_share_headline_end_to_end_encrypted));
binding.internalShareDescription.setVisibility(View.VISIBLE);
binding.externalSharesHeadline.setText(getResources().getString(R.string.create_end_to_end_encrypted_share_title));

fetchE2EECounter(() -> {
if (binding == null) {
return;
}

if (file.getE2eCounter() == -1) {
// V1 cannot share
binding.searchContainer.setVisibility(View.GONE);
binding.createLink.setVisibility(View.GONE);
} else {
binding.createLink.setText(R.string.add_new_secure_file_drop);
binding.searchView.setQueryHint(getResources().getString(R.string.secure_share_search));

if (file.isSharedViaLink()) {
binding.searchView.setQueryHint(getResources().getString(R.string.share_not_allowed_when_file_drop));
binding.searchView.setInputType(InputType.TYPE_NULL);
toggleSearchViewEnable(binding.searchView, false);
}
}
});
final Context context = requireContext();

e2eeCounterFetchOperation.fetchAsync(context, file, fileDataStorageManager, user, clientFactory, counter -> {
if (binding == null) {
return Unit.INSTANCE;
}

if (counter == -1) {
// V1 cannot share
binding.searchContainer.setVisibility(View.GONE);
binding.createLink.setVisibility(View.GONE);
} else {
binding.createLink.setText(R.string.add_new_secure_file_drop);
binding.searchView.setQueryHint(getResources().getString(R.string.secure_share_search));

if (file.isSharedViaLink()) {
binding.searchView.setQueryHint(getResources().getString(R.string.share_not_allowed_when_file_drop));
binding.searchView.setInputType(InputType.TYPE_NULL);
toggleSearchViewEnable(binding.searchView, false);
}
}
return Unit.INSTANCE;
});
} else {
binding.createLink.setText(R.string.create_link);
binding.searchView.setQueryHint(getResources().getString(R.string.share_search_internal));
Expand Down Expand Up @@ -398,28 +402,6 @@ private void setupView() {
);
}

private void fetchE2EECounter(Runnable onComplete) {
final Context context = requireContext();

new Thread(() -> {
try {
OwnCloudClient client = clientFactory.create(user);
Object metadata = RefreshFolderOperation.getDecryptedFolderMetadata(true, file, client, user, context);
if (metadata instanceof DecryptedFolderMetadataFile decryptedMetadata) {
file.setE2eCounter(decryptedMetadata.getMetadata().getCounter());
fileDataStorageManager.saveFile(file);
}
} catch (Exception e) {
Log_OC.e(TAG, "Error refreshing E2E counter: " + e.getMessage());
}

final var activity = getActivity();
if (activity != null) {
activity.runOnUiThread(onComplete);
}
}).start();
}

private void checkShareViaUser() {
if (!MDMConfig.INSTANCE.shareViaUser(requireContext())) {
binding.searchContainer.setVisibility(View.GONE);
Expand Down
Loading