From 73b5ffc5f15583ffac1de7c056d52f4e38d93742 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 28 Apr 2026 11:30:15 +0200 Subject: [PATCH 1/7] feat(e2ee): add debug deletion Signed-off-by: alperozturk96 --- .../DeleteEncryptedFilesRemoteOperation.kt | 52 +++++++++++++ .../e2e/DeletePrivateKeyRemoteOperation.kt | 52 +++++++++++++ .../e2e/DeletePublicKeyRemoteOperation.kt | 52 +++++++++++++ .../operations/e2e/E2EDeletionService.kt | 73 +++++++++++++++++++ .../android/ui/activity/SettingsActivity.java | 32 +++++++- app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences.xml | 4 + 7 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt b/app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt new file mode 100644 index 000000000000..a89359de1184 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt @@ -0,0 +1,52 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations.e2e + +import android.util.Log +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.DeleteMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import org.apache.commons.httpclient.HttpStatus + +class DeleteEncryptedFilesRemoteOperation : RemoteOperation() { + + @Deprecated("Deprecated in Java") + @Suppress("Detekt.TooGenericExceptionCaught", "DEPRECATION") + override fun run(client: NextcloudClient): RemoteOperationResult { + val method = + DeleteMethod(uri = client.baseUri.toString() + ENCRYPTED_FILES_URL, useOcsApiRequestHeader = true) + + return try { + val status = client.execute(method) + + if (status == HttpStatus.SC_OK) { + RemoteOperationResult(true, method) + } else { + RemoteOperationResult(false, method).also { + Log.e( + TAG, + "Deleting encrypted files failed: ${method.getResponseBodyAsString()}", + it.exception + ) + } + } + } catch (e: Exception) { + RemoteOperationResult(e).also { + Log.e(TAG, "Deleting encrypted files failed: ${it.logMessage}", it.exception) + } + } finally { + method.releaseConnection() + } + } + + companion object { + private val TAG = DeleteEncryptedFilesRemoteOperation::class.java.simpleName + private const val ENCRYPTED_FILES_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/encrypted-files" + } +} diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt b/app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt new file mode 100644 index 000000000000..8acc2f7e0a6d --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt @@ -0,0 +1,52 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations.e2e + +import android.util.Log +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.DeleteMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import org.apache.commons.httpclient.HttpStatus + +class DeletePrivateKeyRemoteOperation : RemoteOperation() { + + @Deprecated("Deprecated in Java") + @Suppress("Detekt.TooGenericExceptionCaught", "DEPRECATION") + override fun run(client: NextcloudClient): RemoteOperationResult { + val method = + DeleteMethod(uri = client.baseUri.toString() + PRIVATE_KEY_URL, useOcsApiRequestHeader = true) + + return try { + val status = client.execute(method) + + if (status == HttpStatus.SC_OK) { + RemoteOperationResult(true, method) + } else { + RemoteOperationResult(false, method).also { + Log.e( + TAG, + "Deleting private key failed: ${method.getResponseBodyAsString()}", + it.exception + ) + } + } + } catch (e: Exception) { + RemoteOperationResult(e).also { + Log.e(TAG, "Deleting private key failed: ${it.logMessage}", it.exception) + } + } finally { + method.releaseConnection() + } + } + + companion object { + private val TAG = DeletePrivateKeyRemoteOperation::class.java.simpleName + private const val PRIVATE_KEY_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/private-key" + } +} diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt b/app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt new file mode 100644 index 000000000000..a30d7eabec4f --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt @@ -0,0 +1,52 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations.e2e + +import android.util.Log +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.DeleteMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import org.apache.commons.httpclient.HttpStatus + +class DeletePublicKeyRemoteOperation : RemoteOperation() { + + @Deprecated("Deprecated in Java") + @Suppress("Detekt.TooGenericExceptionCaught", "DEPRECATION") + override fun run(client: NextcloudClient): RemoteOperationResult { + val method = + DeleteMethod(uri = client.baseUri.toString() + PUBLIC_KEY_URL, useOcsApiRequestHeader = true) + + return try { + val status = client.execute(method) + + if (status == HttpStatus.SC_OK) { + RemoteOperationResult(true, method) + } else { + RemoteOperationResult(false, method).also { + Log.e( + TAG, + "Deleting public key failed: ${method.getResponseBodyAsString()}", + it.exception + ) + } + } + } catch (e: Exception) { + RemoteOperationResult(e).also { + Log.e(TAG, "Deleting public key failed: ${it.logMessage}", it.exception) + } + } finally { + method.releaseConnection() + } + } + + companion object { + private val TAG = DeletePublicKeyRemoteOperation::class.java.simpleName + private const val PUBLIC_KEY_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/public-key" + } +} diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt new file mode 100644 index 000000000000..638b0b198f4d --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt @@ -0,0 +1,73 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations.e2e + +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.util.Log +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.nextcloud.client.account.User +import com.nextcloud.client.network.ClientFactory +import com.owncloud.android.R + +class E2EDeletionService( + private val clientFactory: ClientFactory +) { + private val mainHandler = Handler(Looper.getMainLooper()) + + fun showRemoveE2EKeysAndFilesAlertDialog( + context: Context, + user: User, + onResult: (Boolean) -> Unit + ) { + MaterialAlertDialogBuilder(context, R.style.FallbackTheming_Dialog) + .setTitle(R.string.prefs_remove_e2e_keys_and_files) + .setMessage(R.string.remove_e2e_keys_and_files_dialog_warning) + .setCancelable(true) + .setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.confirm_removal) { dialog, _ -> + deleteKeysAndFiles(user) { + dialog.dismiss() + onResult(it) + } + } + .show() + } + + private fun deleteKeysAndFiles(user: User, onResult: (Boolean) -> Unit) { + Thread { + val result = runCatching { + val client = clientFactory.createNextcloudClient(user) + + if (!DeletePrivateKeyRemoteOperation().execute(client).isSuccess) { + return@runCatching false + } + + if (!DeletePublicKeyRemoteOperation().execute(client).isSuccess) { + return@runCatching false + } + + if (!DeleteEncryptedFilesRemoteOperation().execute(client).isSuccess) { + return@runCatching false + } + + true + }.getOrElse { e -> + Log.e(TAG, "Cannot delete E2E keys and files", e) + false + } + + mainHandler.post { onResult(result) } + }.start() + } + + companion object { + private val TAG = E2EDeletionService::class.java.simpleName + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index 5018c9f45e89..ca106c3bb531 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -54,6 +54,7 @@ import com.nextcloud.client.preferences.DarkMode; import com.nextcloud.utils.extensions.ContextExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; +import com.owncloud.android.BuildConfig; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AuthenticatorActivity; @@ -63,6 +64,7 @@ import com.owncloud.android.lib.common.ExternalLink; import com.owncloud.android.lib.common.ExternalLinkType; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.e2e.E2EDeletionService; import com.owncloud.android.providers.DocumentsStorageProvider; import com.owncloud.android.ui.ThemeableSwitchPreference; import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask; @@ -78,7 +80,6 @@ import com.owncloud.android.utils.theme.CapabilityUtils; import com.owncloud.android.utils.theme.ViewThemeUtils; -import java.util.List; import java.util.Objects; import javax.inject.Inject; @@ -91,7 +92,6 @@ import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import kotlin.Unit; -import kotlin.jvm.functions.Function1; import static com.owncloud.android.ui.activity.DrawerActivity.REQ_ALL_FILES_ACCESS; @@ -138,6 +138,8 @@ public class SettingsActivity extends PreferenceActivity private String storagePath; private String pendingLock; + private E2EDeletionService e2EDeletionService; + private User user; @Inject ArbitraryDataProvider arbitraryDataProvider; @Inject AppPreferences preferences; @@ -164,6 +166,7 @@ public void onCreate(Bundle savedInstanceState) { PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen"); user = accountManager.getUser(); + e2EDeletionService = new E2EDeletionService(clientFactory); // retrieve user's base uri setupBaseUri(); @@ -535,6 +538,31 @@ private void removeE2E(PreferenceCategory preferenceCategoryMore) { }); } } + + if (BuildConfig.DEBUG) { + Preference removeKeysAndFilesPreference = findPreference("remove_e2eremove_e2e_files_and_keys"); + if (removeKeysAndFilesPreference != null) { + removeKeysAndFilesPreference.setOnPreferenceClickListener(p -> { + showRemoveE2EKeysAndFilesAlertDialog(preferenceCategoryMore, removeKeysAndFilesPreference); + return true; + }); + } + } + } + + private void showRemoveE2EKeysAndFilesAlertDialog(PreferenceCategory preferenceCategoryMore, Preference preference) { + e2EDeletionService.showRemoveE2EKeysAndFilesAlertDialog(this, user, success -> { + if (success) { + EncryptionUtils.removeE2E(arbitraryDataProvider, user); + preferenceCategoryMore.removePreference(preference); + + Preference pMnemonic = findPreference("mnemonic"); + if (pMnemonic != null) { + preferenceCategoryMore.removePreference(pMnemonic); + } + } + return Unit.INSTANCE; + }); } private void showRemoveE2EAlertDialog(PreferenceCategory preferenceCategoryMore, Preference preference) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f763f4f7973d..7045f9e11c3b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1383,7 +1383,10 @@ An internet connection is required to set up the encrypted folder Set up end-to-end encryption End-to-end encryption is set up! + Remove encrypted files and keys Remove encryption locally + This operation will remove all encrypted files, private and public keys. Are you sure? + Remove encrypted files, private and public keys You can remove end-to-end encryption locally on this client Remove local encryption You can remove end-to-end encryption locally on this client. The encrypted files will remain on server, but will not be synced to this computer any longer. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 74655f678fa1..c1614837d468 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -113,6 +113,10 @@ android:title="@string/prefs_remove_e2e" android:key="remove_e2e" android:summary="@string/remove_e2e" /> + Date: Tue, 28 Apr 2026 12:11:03 +0200 Subject: [PATCH 2/7] fix codacy Signed-off-by: alperozturk96 --- .../operations/e2e/E2EDeletionService.kt | 19 ++++++++-------- .../android/ui/activity/SettingsActivity.java | 22 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 - app/src/main/res/xml/preferences.xml | 3 +-- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt index 638b0b198f4d..3783aad58a47 100644 --- a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt +++ b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt @@ -15,23 +15,18 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.client.account.User import com.nextcloud.client.network.ClientFactory import com.owncloud.android.R +import com.owncloud.android.lib.common.utils.Log_OC -class E2EDeletionService( - private val clientFactory: ClientFactory -) { +class E2EDeletionService(private val clientFactory: ClientFactory) { private val mainHandler = Handler(Looper.getMainLooper()) - fun showRemoveE2EKeysAndFilesAlertDialog( - context: Context, - user: User, - onResult: (Boolean) -> Unit - ) { + fun showRemoveE2EKeysAndFilesAlertDialog(context: Context, user: User, onResult: (Boolean) -> Unit) { MaterialAlertDialogBuilder(context, R.style.FallbackTheming_Dialog) .setTitle(R.string.prefs_remove_e2e_keys_and_files) .setMessage(R.string.remove_e2e_keys_and_files_dialog_warning) .setCancelable(true) .setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.dismiss() } - .setPositiveButton(R.string.confirm_removal) { dialog, _ -> + .setPositiveButton(R.string.common_ok) { dialog, _ -> deleteKeysAndFiles(user) { dialog.dismiss() onResult(it) @@ -49,14 +44,20 @@ class E2EDeletionService( return@runCatching false } + Log_OC.i(TAG, "🔑" + "private key is deleted") + if (!DeletePublicKeyRemoteOperation().execute(client).isSuccess) { return@runCatching false } + Log_OC.i(TAG, "🗝" + "public key is deleted") + if (!DeleteEncryptedFilesRemoteOperation().execute(client).isSuccess) { return@runCatching false } + Log_OC.i(TAG, "🗂️" + "encrypted files are deleted") + true }.getOrElse { e -> Log.e(TAG, "Cannot delete E2E keys and files", e) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index ca106c3bb531..e80656e32876 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -371,6 +371,8 @@ private void setupMoreCategory() { removeE2E(preferenceCategoryMore); + removeE2EFilesAndKeys(preferenceCategoryMore); + setupHelpPreference(preferenceCategoryMore); setupRecommendPreference(preferenceCategoryMore); @@ -538,19 +540,29 @@ private void removeE2E(PreferenceCategory preferenceCategoryMore) { }); } } + } + private void removeE2EFilesAndKeys(PreferenceCategory preferenceCategoryMore) { if (BuildConfig.DEBUG) { - Preference removeKeysAndFilesPreference = findPreference("remove_e2eremove_e2e_files_and_keys"); + Preference removeKeysAndFilesPreference = findPreference("remove_e2e_files_and_keys"); if (removeKeysAndFilesPreference != null) { - removeKeysAndFilesPreference.setOnPreferenceClickListener(p -> { - showRemoveE2EKeysAndFilesAlertDialog(preferenceCategoryMore, removeKeysAndFilesPreference); - return true; - }); + if (!FileOperationsHelper.isEndToEndEncryptionSetup(this, user)) { + preferenceCategoryMore.removePreference(removeKeysAndFilesPreference); + } else { + removeKeysAndFilesPreference.setOnPreferenceClickListener(p -> { + showRemoveE2EKeysAndFilesAlertDialog(preferenceCategoryMore, removeKeysAndFilesPreference); + return true; + }); + } } } } private void showRemoveE2EKeysAndFilesAlertDialog(PreferenceCategory preferenceCategoryMore, Preference preference) { + if (e2EDeletionService == null) { + return; + } + e2EDeletionService.showRemoveE2EKeysAndFilesAlertDialog(this, user, success -> { if (success) { EncryptionUtils.removeE2E(arbitraryDataProvider, user); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7045f9e11c3b..ffc70f139ff0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1386,7 +1386,6 @@ Remove encrypted files and keys Remove encryption locally This operation will remove all encrypted files, private and public keys. Are you sure? - Remove encrypted files, private and public keys You can remove end-to-end encryption locally on this client Remove local encryption You can remove end-to-end encryption locally on this client. The encrypted files will remain on server, but will not be synced to this computer any longer. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c1614837d468..fb7f89111f0f 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -115,8 +115,7 @@ android:summary="@string/remove_e2e" /> + android:key="remove_e2e_files_and_keys" /> Date: Wed, 29 Apr 2026 12:12:03 +0200 Subject: [PATCH 3/7] use lib Signed-off-by: alperozturk96 # Conflicts: # gradle/libs.versions.toml --- .../DeleteEncryptedFilesRemoteOperation.kt | 52 ------------------- .../e2e/DeletePrivateKeyRemoteOperation.kt | 52 ------------------- .../e2e/DeletePublicKeyRemoteOperation.kt | 52 ------------------- .../operations/e2e/E2EDeletionService.kt | 12 +++-- .../android/ui/activity/SettingsActivity.java | 5 ++ gradle/verification-metadata.xml | 8 +++ 6 files changed, 21 insertions(+), 160 deletions(-) delete mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt delete mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt delete mode 100644 app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt b/app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt deleted file mode 100644 index a89359de1184..000000000000 --- a/app/src/main/java/com/owncloud/android/operations/e2e/DeleteEncryptedFilesRemoteOperation.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.operations.e2e - -import android.util.Log -import com.nextcloud.common.NextcloudClient -import com.nextcloud.operations.DeleteMethod -import com.owncloud.android.lib.common.operations.RemoteOperation -import com.owncloud.android.lib.common.operations.RemoteOperationResult -import org.apache.commons.httpclient.HttpStatus - -class DeleteEncryptedFilesRemoteOperation : RemoteOperation() { - - @Deprecated("Deprecated in Java") - @Suppress("Detekt.TooGenericExceptionCaught", "DEPRECATION") - override fun run(client: NextcloudClient): RemoteOperationResult { - val method = - DeleteMethod(uri = client.baseUri.toString() + ENCRYPTED_FILES_URL, useOcsApiRequestHeader = true) - - return try { - val status = client.execute(method) - - if (status == HttpStatus.SC_OK) { - RemoteOperationResult(true, method) - } else { - RemoteOperationResult(false, method).also { - Log.e( - TAG, - "Deleting encrypted files failed: ${method.getResponseBodyAsString()}", - it.exception - ) - } - } - } catch (e: Exception) { - RemoteOperationResult(e).also { - Log.e(TAG, "Deleting encrypted files failed: ${it.logMessage}", it.exception) - } - } finally { - method.releaseConnection() - } - } - - companion object { - private val TAG = DeleteEncryptedFilesRemoteOperation::class.java.simpleName - private const val ENCRYPTED_FILES_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/encrypted-files" - } -} diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt b/app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt deleted file mode 100644 index 8acc2f7e0a6d..000000000000 --- a/app/src/main/java/com/owncloud/android/operations/e2e/DeletePrivateKeyRemoteOperation.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.operations.e2e - -import android.util.Log -import com.nextcloud.common.NextcloudClient -import com.nextcloud.operations.DeleteMethod -import com.owncloud.android.lib.common.operations.RemoteOperation -import com.owncloud.android.lib.common.operations.RemoteOperationResult -import org.apache.commons.httpclient.HttpStatus - -class DeletePrivateKeyRemoteOperation : RemoteOperation() { - - @Deprecated("Deprecated in Java") - @Suppress("Detekt.TooGenericExceptionCaught", "DEPRECATION") - override fun run(client: NextcloudClient): RemoteOperationResult { - val method = - DeleteMethod(uri = client.baseUri.toString() + PRIVATE_KEY_URL, useOcsApiRequestHeader = true) - - return try { - val status = client.execute(method) - - if (status == HttpStatus.SC_OK) { - RemoteOperationResult(true, method) - } else { - RemoteOperationResult(false, method).also { - Log.e( - TAG, - "Deleting private key failed: ${method.getResponseBodyAsString()}", - it.exception - ) - } - } - } catch (e: Exception) { - RemoteOperationResult(e).also { - Log.e(TAG, "Deleting private key failed: ${it.logMessage}", it.exception) - } - } finally { - method.releaseConnection() - } - } - - companion object { - private val TAG = DeletePrivateKeyRemoteOperation::class.java.simpleName - private const val PRIVATE_KEY_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/private-key" - } -} diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt b/app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt deleted file mode 100644 index a30d7eabec4f..000000000000 --- a/app/src/main/java/com/owncloud/android/operations/e2e/DeletePublicKeyRemoteOperation.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.operations.e2e - -import android.util.Log -import com.nextcloud.common.NextcloudClient -import com.nextcloud.operations.DeleteMethod -import com.owncloud.android.lib.common.operations.RemoteOperation -import com.owncloud.android.lib.common.operations.RemoteOperationResult -import org.apache.commons.httpclient.HttpStatus - -class DeletePublicKeyRemoteOperation : RemoteOperation() { - - @Deprecated("Deprecated in Java") - @Suppress("Detekt.TooGenericExceptionCaught", "DEPRECATION") - override fun run(client: NextcloudClient): RemoteOperationResult { - val method = - DeleteMethod(uri = client.baseUri.toString() + PUBLIC_KEY_URL, useOcsApiRequestHeader = true) - - return try { - val status = client.execute(method) - - if (status == HttpStatus.SC_OK) { - RemoteOperationResult(true, method) - } else { - RemoteOperationResult(false, method).also { - Log.e( - TAG, - "Deleting public key failed: ${method.getResponseBodyAsString()}", - it.exception - ) - } - } - } catch (e: Exception) { - RemoteOperationResult(e).also { - Log.e(TAG, "Deleting public key failed: ${it.logMessage}", it.exception) - } - } finally { - method.releaseConnection() - } - } - - companion object { - private val TAG = DeletePublicKeyRemoteOperation::class.java.simpleName - private const val PUBLIC_KEY_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/public-key" - } -} diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt index 3783aad58a47..d7d0646d27c6 100644 --- a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt +++ b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt @@ -16,6 +16,9 @@ import com.nextcloud.client.account.User import com.nextcloud.client.network.ClientFactory import com.owncloud.android.R import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.e2ee.DeleteEncryptedFilesRemoteOperation +import com.owncloud.android.lib.resources.users.DeletePrivateKeyRemoteOperation +import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation class E2EDeletionService(private val clientFactory: ClientFactory) { private val mainHandler = Handler(Looper.getMainLooper()) @@ -39,26 +42,27 @@ class E2EDeletionService(private val clientFactory: ClientFactory) { Thread { val result = runCatching { val client = clientFactory.createNextcloudClient(user) + var successfulOperationResultCount = 3 if (!DeletePrivateKeyRemoteOperation().execute(client).isSuccess) { - return@runCatching false + successfulOperationResultCount -= 1 } Log_OC.i(TAG, "🔑" + "private key is deleted") if (!DeletePublicKeyRemoteOperation().execute(client).isSuccess) { - return@runCatching false + successfulOperationResultCount -= 1 } Log_OC.i(TAG, "🗝" + "public key is deleted") if (!DeleteEncryptedFilesRemoteOperation().execute(client).isSuccess) { - return@runCatching false + successfulOperationResultCount -= 1 } Log_OC.i(TAG, "🗂️" + "encrypted files are deleted") - true + successfulOperationResultCount == 3 }.getOrElse { e -> Log.e(TAG, "Cannot delete E2E keys and files", e) false diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index e80656e32876..d1093bcb5d93 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -572,6 +572,11 @@ private void showRemoveE2EKeysAndFilesAlertDialog(PreferenceCategory preferenceC if (pMnemonic != null) { preferenceCategoryMore.removePreference(pMnemonic); } + + Preference pRemoveE2E = findPreference("remove_e2e"); + if (pRemoveE2E != null) { + preferenceCategoryMore.removePreference(pRemoveE2E); + } } return Unit.INSTANCE; }); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index c7804dce66d7..3056f8f53553 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -21550,6 +21550,14 @@ + + + + + + + + From 7b46e0c9ee09d462100d52213901359d3587e785 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 29 Apr 2026 12:15:39 +0200 Subject: [PATCH 4/7] fix codacy Signed-off-by: alperozturk96 --- .../com/owncloud/android/operations/e2e/E2EDeletionService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt index d7d0646d27c6..d3b9038bf3e2 100644 --- a/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt +++ b/app/src/main/java/com/owncloud/android/operations/e2e/E2EDeletionService.kt @@ -20,6 +20,7 @@ import com.owncloud.android.lib.resources.e2ee.DeleteEncryptedFilesRemoteOperati import com.owncloud.android.lib.resources.users.DeletePrivateKeyRemoteOperation import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation +@Suppress("MagicNumber") class E2EDeletionService(private val clientFactory: ClientFactory) { private val mainHandler = Handler(Looper.getMainLooper()) From 76447333aca7176bb5279ba8f0c63c7a969b246c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 4 May 2026 16:13:07 +0200 Subject: [PATCH 5/7] add tests Signed-off-by: alperozturk96 --- .../operations/DeleteE2ERemoteOperationIT.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt diff --git a/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt b/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt new file mode 100644 index 000000000000..9ae062e25700 --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt @@ -0,0 +1,51 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations + +import com.owncloud.android.AbstractOnServerIT +import com.owncloud.android.lib.resources.e2ee.DeleteEncryptedFilesRemoteOperation +import com.owncloud.android.lib.resources.users.DeletePrivateKeyRemoteOperation +import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation +import com.owncloud.android.lib.resources.users.GetPrivateKeyRemoteOperation +import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation +import com.owncloud.android.lib.resources.users.StorePrivateKeyRemoteOperation +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class DeleteE2ERemoteOperationIT : AbstractOnServerIT() { + + @Test + fun testDeleteEncryptedFiles() { + val sut = DeleteEncryptedFilesRemoteOperation() + val result = sut.execute(nextcloudClient) + assertTrue(result.isSuccess) + } + + @Test + fun testDeletePrivateKey() { + StorePrivateKeyRemoteOperation("private_key").execute(nextcloudClient) + + val sut = DeletePrivateKeyRemoteOperation() + val result = sut.execute(nextcloudClient) + assertTrue(result.isSuccess) + + val getResult = GetPrivateKeyRemoteOperation().execute(nextcloudClient) + assertFalse(getResult.isSuccess) + } + + @Test + fun testDeletePublicKey() { + val sut = DeletePublicKeyRemoteOperation() + val result = sut.execute(nextcloudClient) + assertTrue(result.isSuccess) + + val getResult = GetPublicKeyRemoteOperation().execute(nextcloudClient) + assertFalse(getResult.isSuccess) + } +} From 327d47af6cf53d8fc937fc7e672f064332dc41cc Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 4 May 2026 16:18:22 +0200 Subject: [PATCH 6/7] add tests Signed-off-by: alperozturk96 --- .../operations/DeleteE2ERemoteOperationIT.kt | 14 +++++++++++++- gradle/verification-metadata.xml | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt b/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt index 9ae062e25700..88d072a60efa 100644 --- a/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/operations/DeleteE2ERemoteOperationIT.kt @@ -14,6 +14,8 @@ import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation import com.owncloud.android.lib.resources.users.GetPrivateKeyRemoteOperation import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation import com.owncloud.android.lib.resources.users.StorePrivateKeyRemoteOperation +import com.owncloud.android.utils.EncryptionUtils +import com.owncloud.android.utils.crypto.CryptoHelper import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -29,7 +31,17 @@ class DeleteE2ERemoteOperationIT : AbstractOnServerIT() { @Test fun testDeletePrivateKey() { - StorePrivateKeyRemoteOperation("private_key").execute(nextcloudClient) + val keyPair = EncryptionUtils.generateKeyPair() + val privateKey = keyPair.private + val keyPhrase = "moreovertelevisionfactorytendencyindependenceinternationalintellectualimpress" + + "interestvolunteer" + val privatePemKeyString = EncryptionUtils.privateKeyToPEM(privateKey) + val encryptedPrivateKey = CryptoHelper.encryptPrivateKey( + privatePemKeyString, + keyPhrase + ) + + StorePrivateKeyRemoteOperation(encryptedPrivateKey).execute(nextcloudClient) val sut = DeletePrivateKeyRemoteOperation() val result = sut.execute(nextcloudClient) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 3056f8f53553..5f730e920213 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -21094,6 +21094,14 @@ + + + + + + + + From ca712c827b6e7d6c96672a677e82411a78efcb00 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 5 May 2026 10:56:47 +0200 Subject: [PATCH 7/7] wip Signed-off-by: alperozturk96 --- .../java/com/owncloud/android/utils/EncryptionUtils.java | 7 ++++--- gradle/libs.versions.toml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) 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 078bc598b357..9e7b1bd2de90 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.common.SessionTimeOutKt; import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; @@ -42,7 +43,6 @@ import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation; import com.owncloud.android.lib.resources.e2ee.StoreMetadataV2RemoteOperation; import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation; -import com.owncloud.android.lib.resources.e2ee.UnlockFileV1RemoteOperation; import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation; import com.owncloud.android.lib.resources.e2ee.UpdateMetadataV2RemoteOperation; import com.owncloud.android.lib.resources.files.model.ServerFileInterface; @@ -1166,7 +1166,8 @@ public static String lockFolder(ServerFileInterface parentFile, OwnCloudClient c public static String lockFolder(ServerFileInterface parentFile, OwnCloudClient client, long counter) throws UploadException { // Lock folder LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId(), - counter); + counter, + SessionTimeOutKt.getDefaultSessionTimeOut()); RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client); if (lockFileOperationResult.isSuccess() && @@ -1366,7 +1367,7 @@ public static RemoteOperationResult unlockFolder(ServerFileInterface paren public static RemoteOperationResult unlockFolderV1(ServerFileInterface parentFolder, OwnCloudClient client, String token) { if (token != null) { - return new UnlockFileV1RemoteOperation(parentFolder.getLocalId(), token).execute(client); + return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token, SessionTimeOutKt.getDefaultSessionTimeOut(), false).execute(client); } else { return new RemoteOperationResult<>(new Exception("No token available")); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e2da03d5fef9..91cd34537eb3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.33.2" androidGifDrawableVersion = "1.2.31" androidImageCropperVersion = "4.7.0" -androidLibraryVersion ="20e8cd17191f337d34b2ed97e0ac61e84a0bfc39" +androidLibraryVersion ="3c4d9d6" androidPluginVersion = "9.2.0" androidsvgVersion = "1.4" androidxMediaVersion = "1.5.1"