Skip to content

Commit aab499a

Browse files
committed
refactor
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent 9a4474c commit aab499a

32 files changed

Lines changed: 2632 additions & 393 deletions

File tree

app/schemas/com.nextcloud.client.database.NextcloudDatabase/103.json

Lines changed: 1323 additions & 0 deletions
Large diffs are not rendered by default.

app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt

Lines changed: 178 additions & 70 deletions
Large diffs are not rendered by default.

app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ import com.owncloud.android.db.ProviderMeta
101101
AutoMigration(from = 98, to = 99),
102102
// manual migration used for 99 to 100
103103
AutoMigration(from = 100, to = 101, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
104-
AutoMigration(from = 101, to = 102, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
104+
AutoMigration(from = 101, to = 102, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
105+
AutoMigration(from = 102, to = 103)
105106
],
106107
exportSchema = true
107108
)

app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,7 @@ interface FileDao {
188188

189189
@Query("DELETE FROM filelist WHERE file_owner = :fileOwner AND path = :remotePath")
190190
fun deleteFileByRemotePath(fileOwner: String, remotePath: String): Int
191+
192+
@Query("UPDATE filelist SET is_read_only = :readOnly WHERE file_owner = :fileOwner AND path = :path")
193+
fun setReadOnly(fileOwner: String, path: String, readOnly: Int): Int
191194
}

app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,7 @@ data class FileEntity(
121121
@ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT)
122122
val internalTwoWaySyncResult: String?,
123123
@ColumnInfo(name = ProviderTableMeta.FILE_UPLOADED)
124-
val uploaded: Long?
124+
val uploaded: Long?,
125+
@ColumnInfo(name = ProviderTableMeta.FILE_IS_READ_ONLY)
126+
val isReadOnly: Int?
125127
)

app/src/main/java/com/nextcloud/client/di/AppModule.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,21 @@
3838
import com.nextcloud.client.migrations.MigrationsManager;
3939
import com.nextcloud.client.migrations.MigrationsManagerImpl;
4040
import com.nextcloud.client.network.ClientFactory;
41+
import com.nextcloud.client.network.ConnectivityService;
4142
import com.nextcloud.client.notifications.AppNotificationManager;
4243
import com.nextcloud.client.notifications.AppNotificationManagerImpl;
4344
import com.nextcloud.client.preferences.AppPreferences;
4445
import com.nextcloud.client.utils.Throttler;
45-
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
46+
import com.nextcloud.utils.e2ee.E2EEActionResolver;
47+
import com.nextcloud.utils.e2ee.E2EEKeyInspector;
48+
import com.nextcloud.utils.thumbnail.FolderThumbnailGenerator;
4649
import com.owncloud.android.authentication.PassCodeManager;
4750
import com.owncloud.android.datamodel.ArbitraryDataProvider;
4851
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
4952
import com.owncloud.android.datamodel.FileDataStorageManager;
5053
import com.owncloud.android.datamodel.SyncedFolderProvider;
5154
import com.owncloud.android.datamodel.UploadsStorageManager;
55+
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
5256
import com.owncloud.android.ui.activities.data.activities.ActivitiesRepository;
5357
import com.owncloud.android.ui.activities.data.activities.ActivitiesServiceApi;
5458
import com.owncloud.android.ui.activities.data.activities.ActivitiesServiceApiImpl;
@@ -57,7 +61,6 @@
5761
import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
5862
import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository;
5963
import com.owncloud.android.ui.dialog.setupEncryption.CertificateValidator;
60-
import com.nextcloud.utils.thumbnail.FolderThumbnailGenerator;
6164
import com.owncloud.android.utils.theme.ViewThemeUtils;
6265

6366
import org.greenrobot.eventbus.EventBus;
@@ -280,4 +283,23 @@ FolderThumbnailGenerator folderThumbnailGenerator(
280283
return new FolderThumbnailGenerator(appPreferences, viewThemeUtils, context, accountManager);
281284
}
282285

286+
@Provides
287+
E2EEKeyInspector e2eeKeyInspector(
288+
Context context,
289+
FileDataStorageManager fileDataStorageManager,
290+
CertificateValidator certificateValidator,
291+
ArbitraryDataProvider arbitraryDataProvider,
292+
UserAccountManager accountManager) {
293+
return new E2EEKeyInspector(context, fileDataStorageManager, certificateValidator, arbitraryDataProvider, accountManager);
294+
}
295+
296+
@Provides
297+
E2EEActionResolver e2eeActionResolver(
298+
FileDataStorageManager fileDataStorageManager,
299+
ArbitraryDataProvider arbitraryDataProvider,
300+
UserAccountManager accountManager,
301+
ConnectivityService connectivityService,
302+
E2EEKeyInspector e2eeKeyInspector) {
303+
return new E2EEActionResolver(fileDataStorageManager, arbitraryDataProvider, accountManager, connectivityService, e2eeKeyInspector);
304+
}
283305
}

app/src/main/java/com/nextcloud/client/jobs/offlineOperations/OfflineOperationsWorker.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.nextcloud.client.network.ConnectivityService
1818
import com.nextcloud.model.OfflineOperationType
1919
import com.nextcloud.model.WorkerState
2020
import com.nextcloud.model.WorkerStateObserver
21+
import com.nextcloud.utils.extensions.isNetworkAndServerAvailableSuspended
2122
import com.owncloud.android.datamodel.FileDataStorageManager
2223
import com.owncloud.android.datamodel.OCFile
2324
import com.owncloud.android.lib.common.OwnCloudClient
@@ -35,8 +36,6 @@ import com.owncloud.android.utils.MimeTypeUtil
3536
import com.owncloud.android.utils.theme.ViewThemeUtils
3637
import kotlinx.coroutines.Dispatchers
3738
import kotlinx.coroutines.withContext
38-
import kotlin.coroutines.resume
39-
import kotlin.coroutines.suspendCoroutine
4039

4140
private typealias OfflineOperationResult = Pair<RemoteOperationResult<*>?, RemoteOperation<*>?>?
4241

@@ -67,7 +66,7 @@ class OfflineOperationsWorker(
6766
Log_OC.d(TAG, "[$jobName] OfflineOperationsWorker started for user: ${user.accountName}")
6867

6968
// check network connection
70-
if (!isNetworkAndServerAvailable()) {
69+
if (!connectivityService.isNetworkAndServerAvailableSuspended()) {
7170
Log_OC.w(TAG, "⚠️ No internet/server connection. Retrying later...")
7271
return@withContext Result.retry()
7372
}
@@ -161,12 +160,6 @@ class OfflineOperationsWorker(
161160
}
162161
// endregion
163162

164-
private suspend fun isNetworkAndServerAvailable(): Boolean = suspendCoroutine { continuation ->
165-
connectivityService.isNetworkAndServerAvailable { result ->
166-
continuation.resume(result)
167-
}
168-
}
169-
170163
// region Operation Execution
171164
@Suppress("ComplexCondition", "LongMethod")
172165
private suspend fun executeOperation(
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.utils.e2ee
9+
10+
import com.nextcloud.client.account.UserAccountManager
11+
import com.nextcloud.client.di.Injectable
12+
import com.nextcloud.client.network.ConnectivityService
13+
import com.nextcloud.utils.e2ee.model.E2EEKeyCheck
14+
import com.nextcloud.utils.extensions.isNetworkAndServerAvailableSuspended
15+
import com.owncloud.android.datamodel.ArbitraryDataProvider
16+
import com.owncloud.android.datamodel.FileDataStorageManager
17+
import com.owncloud.android.datamodel.OCFile
18+
import com.owncloud.android.lib.common.utils.Log_OC
19+
import com.owncloud.android.ui.dialog.setupEncryption.model.DownloadKeyResult
20+
import com.owncloud.android.utils.EncryptionUtils
21+
import kotlinx.coroutines.Dispatchers
22+
import kotlinx.coroutines.withContext
23+
import javax.inject.Inject
24+
25+
@Suppress("LongParameterList")
26+
class E2EEActionResolver @Inject constructor(
27+
private val storageManager: FileDataStorageManager,
28+
private val arbitraryDataProvider: ArbitraryDataProvider,
29+
private val accountManager: UserAccountManager,
30+
private val connectivityService: ConnectivityService,
31+
private val inspector: E2EEKeyInspector
32+
) : Injectable {
33+
34+
companion object {
35+
private const val TAG = "E2EEActionResolver"
36+
}
37+
38+
suspend fun markFolderReadOnly(file: OCFile) = withContext(Dispatchers.IO) {
39+
storageManager.setReadOnly(file, true)
40+
}
41+
42+
suspend fun checkFolderMetadataKey(file: OCFile): Boolean = withContext(Dispatchers.IO) {
43+
val capability = storageManager.getCapability(accountManager.user)
44+
val canDecrypt = inspector.canDecryptFolderMetadata(file, capability)
45+
storageManager.setReadOnly(file, !canDecrypt)
46+
return@withContext canDecrypt
47+
}
48+
49+
suspend fun checkKeys(): E2EEKeyCheck = withContext(Dispatchers.IO) { resolveKeyCheck() }
50+
51+
private suspend fun resolveKeyCheck(): E2EEKeyCheck {
52+
val keysAbsentLocally = inspector.isLocalKeysAbsent()
53+
54+
if (!inspector.fetchCapabilities()) {
55+
return if (connectivityService.isNetworkAndServerAvailableSuspended()) {
56+
E2EEKeyCheck.CHECK_FAILED
57+
} else {
58+
E2EEKeyCheck.NO_NETWORK
59+
}
60+
}
61+
62+
val capability = storageManager.getCapability(accountManager.user)
63+
val keysExistOnServer = capability.endToEndEncryptionKeysExist
64+
65+
val result = when {
66+
keysExistOnServer.isUnknown || capability.endToEndEncryption.isUnknown -> E2EEKeyCheck.E2EE_UNAVAILABLE
67+
keysAbsentLocally && keysExistOnServer.isTrue -> E2EEKeyCheck.ONLY_ON_SERVER
68+
keysAbsentLocally -> E2EEKeyCheck.MISSING_EVERYWHERE
69+
!keysExistOnServer.isTrue -> E2EEKeyCheck.ONLY_ON_DEVICE
70+
else -> compareKeys()
71+
}
72+
73+
Log_OC.d(TAG, "e2ee key check result: $result")
74+
75+
return result
76+
}
77+
78+
private suspend fun compareKeys(): E2EEKeyCheck {
79+
val storedPublicKey = arbitraryDataProvider.getValue(accountManager.user, EncryptionUtils.PUBLIC_KEY)
80+
81+
return when (val result = inspector.compareWithServerKey(storedPublicKey)) {
82+
is DownloadKeyResult.CompareKeys ->
83+
if (result.same) E2EEKeyCheck.SAME_AS_SERVER else E2EEKeyCheck.DIFFERS_FROM_SERVER
84+
85+
is DownloadKeyResult.NoServerKey -> E2EEKeyCheck.ONLY_ON_DEVICE
86+
87+
else -> E2EEKeyCheck.CHECK_FAILED
88+
}
89+
}
90+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.utils.e2ee
9+
10+
import com.nextcloud.utils.e2ee.model.E2EEDialog
11+
import com.owncloud.android.R
12+
import com.owncloud.android.lib.common.utils.Log_OC
13+
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment
14+
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
15+
import com.owncloud.android.ui.fragment.OCFileListFragment
16+
17+
class E2EEDialogPresenter(private val fragment: OCFileListFragment) {
18+
19+
companion object {
20+
private const val TAG = "E2EEDialogPresenter"
21+
private const val ENCRYPTION_KEY_ALERT_DIALOG_TAG = "ENCRYPTION_KEY_HANDLER_DIALOG"
22+
private const val NO_BUTTON = -1
23+
}
24+
25+
fun show(dialog: E2EEDialog) {
26+
if (fragment.parentFragmentManager.findFragmentByTag(ENCRYPTION_KEY_ALERT_DIALOG_TAG) != null) {
27+
return
28+
}
29+
30+
ConfirmationDialogFragment
31+
.newInstance(
32+
titleResId = dialog.titleId,
33+
titleIconId = R.drawable.ic_lock_open_white,
34+
messageResId = dialog.descriptionId,
35+
positiveButtonTextId = R.string.common_ok,
36+
neutralButtonTextId = NO_BUTTON,
37+
negativeButtonTextId = NO_BUTTON,
38+
messageArguments = null
39+
).apply {
40+
setOnConfirmationListener(dismissListener(dialog))
41+
}.show(fragment.parentFragmentManager, ENCRYPTION_KEY_ALERT_DIALOG_TAG)
42+
}
43+
44+
private fun dismissListener(dialog: E2EEDialog): ConfirmationDialogFragmentListener =
45+
object : ConfirmationDialogFragmentListener {
46+
override fun onConfirmation(callerTag: String?) = Log_OC.d(TAG, "$dialog acknowledged")
47+
override fun onNeutral(callerTag: String?) = Unit
48+
override fun onCancel(callerTag: String?) = Unit
49+
}
50+
}

0 commit comments

Comments
 (0)