Skip to content

Commit b7218b2

Browse files
committed
E2EE related changes with test cases.
1 parent 2eca0aa commit b7218b2

File tree

16 files changed

+2651
-1999
lines changed

16 files changed

+2651
-1999
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Nextcloud Android client application
3+
*
4+
* @author Álvaro Brey Vilas
5+
* Copyright (C) 2022 Álvaro Brey Vilas
6+
* Copyright (C) 2022 Nextcloud GmbH
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
package com.nmc.android
22+
23+
import androidx.test.core.app.launchActivity
24+
import androidx.test.ext.junit.runners.AndroidJUnit4
25+
import com.nextcloud.client.account.User
26+
import com.nextcloud.test.TestActivity
27+
import com.nextcloud.utils.EditorUtils
28+
import com.owncloud.android.AbstractIT
29+
import com.owncloud.android.R
30+
import com.owncloud.android.datamodel.ArbitraryDataProvider
31+
import com.owncloud.android.datamodel.FileDataStorageManager
32+
import com.owncloud.android.datamodel.OCFile
33+
import com.owncloud.android.files.FileMenuFilter
34+
import com.owncloud.android.files.services.FileDownloader
35+
import com.owncloud.android.files.services.FileUploader
36+
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
37+
import com.owncloud.android.lib.resources.status.OCCapability
38+
import com.owncloud.android.services.OperationsService
39+
import com.owncloud.android.ui.activity.ComponentsGetter
40+
import com.owncloud.android.utils.MimeType
41+
import io.mockk.MockKAnnotations
42+
import io.mockk.every
43+
import io.mockk.impl.annotations.MockK
44+
import org.junit.Assert.assertFalse
45+
import org.junit.Assert.assertTrue
46+
import org.junit.Before
47+
import org.junit.Test
48+
import org.junit.runner.RunWith
49+
import java.security.SecureRandom
50+
51+
@RunWith(AndroidJUnit4::class)
52+
class FileMenuFilterIT : AbstractIT() {
53+
54+
@MockK
55+
private lateinit var mockComponentsGetter: ComponentsGetter
56+
57+
@MockK
58+
private lateinit var mockStorageManager: FileDataStorageManager
59+
60+
@MockK
61+
private lateinit var mockFileUploaderBinder: FileUploader.FileUploaderBinder
62+
63+
@MockK
64+
private lateinit var mockFileDownloaderBinder: FileDownloader.FileDownloaderBinder
65+
66+
@MockK
67+
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder
68+
69+
@MockK
70+
private lateinit var mockArbitraryDataProvider: ArbitraryDataProvider
71+
72+
private lateinit var editorUtils: EditorUtils
73+
74+
@Before
75+
fun setup() {
76+
MockKAnnotations.init(this)
77+
every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
78+
every { mockComponentsGetter.fileUploaderBinder } returns mockFileUploaderBinder
79+
every { mockFileDownloaderBinder.isDownloading(any(), any()) } returns false
80+
every { mockComponentsGetter.fileDownloaderBinder } returns mockFileDownloaderBinder
81+
every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
82+
every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
83+
every { mockStorageManager.getFileById(any()) } returns OCFile("/")
84+
every { mockStorageManager.getFolderContent(any(), any()) } returns ArrayList<OCFile>()
85+
every { mockArbitraryDataProvider.getValue(any<User>(), any()) } returns ""
86+
editorUtils = EditorUtils(mockArbitraryDataProvider)
87+
}
88+
89+
@Test
90+
fun hide_shareAndFavouriteMenu_encryptedFolder() {
91+
val capability = OCCapability().apply {
92+
endToEndEncryption = CapabilityBooleanType.TRUE
93+
}
94+
95+
val encryptedFolder = OCFile("/encryptedFolder/").apply {
96+
isEncrypted = true
97+
mimeType = MimeType.DIRECTORY
98+
fileLength = SecureRandom().nextLong()
99+
}
100+
101+
configureCapability(capability)
102+
103+
launchActivity<TestActivity>().use {
104+
it.onActivity { activity ->
105+
val filterFactory =
106+
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
107+
108+
val sut = filterFactory.newInstance(encryptedFolder, mockComponentsGetter, true, user)
109+
val toHide = sut.getToHide(false)
110+
111+
// encrypted folder
112+
assertTrue(toHide.contains(R.id.action_see_details))
113+
assertTrue(toHide.contains(R.id.action_favorite))
114+
assertTrue(toHide.contains(R.id.action_unset_favorite))
115+
}
116+
}
117+
}
118+
119+
@Test
120+
fun show_shareAndFavouriteMenu_normalFolder() {
121+
val capability = OCCapability().apply {
122+
endToEndEncryption = CapabilityBooleanType.TRUE
123+
}
124+
125+
val normalFolder = OCFile("/folder/").apply {
126+
mimeType = MimeType.DIRECTORY
127+
fileLength = SecureRandom().nextLong()
128+
}
129+
130+
configureCapability(capability)
131+
132+
launchActivity<TestActivity>().use {
133+
it.onActivity { activity ->
134+
val filterFactory =
135+
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
136+
137+
val sut = filterFactory.newInstance(normalFolder, mockComponentsGetter, true, user)
138+
val toHide = sut.getToHide(false)
139+
140+
// normal folder
141+
assertFalse(toHide.contains(R.id.action_see_details))
142+
assertFalse(toHide.contains(R.id.action_favorite))
143+
assertTrue(toHide.contains(R.id.action_unset_favorite))
144+
}
145+
}
146+
}
147+
148+
private fun configureCapability(capability: OCCapability) {
149+
every { mockStorageManager.getCapability(any<User>()) } returns capability
150+
every { mockStorageManager.getCapability(any<String>()) } returns capability
151+
}
152+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.nmc.android
2+
3+
import androidx.test.espresso.Espresso.onView
4+
import androidx.test.espresso.assertion.ViewAssertions.matches
5+
import androidx.test.espresso.intent.rule.IntentsTestRule
6+
import androidx.test.espresso.matcher.ViewMatchers.withHint
7+
import androidx.test.espresso.matcher.ViewMatchers.withId
8+
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
9+
import com.nextcloud.test.TestActivity
10+
import com.owncloud.android.AbstractIT
11+
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment
12+
import org.junit.Rule
13+
import org.junit.Test
14+
import com.owncloud.android.R
15+
16+
class SetupEncryptionDialogFragmentIT : AbstractIT() {
17+
18+
@get:Rule
19+
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
20+
21+
@Test
22+
fun validatePassphraseInputHint() {
23+
val activity = testActivityRule.launchActivity(null)
24+
25+
val sut = SetupEncryptionDialogFragment.newInstance(user, 0)
26+
27+
sut.show(activity.supportFragmentManager, "1")
28+
29+
val keyWords = arrayListOf(
30+
"ability",
31+
"able",
32+
"about",
33+
"above",
34+
"absent",
35+
"absorb",
36+
"abstract",
37+
"absurd",
38+
"abuse",
39+
"access",
40+
"accident",
41+
"account",
42+
"accuse"
43+
)
44+
45+
shortSleep()
46+
47+
UiThreadStatement.runOnUiThread {
48+
sut.setMnemonic(keyWords)
49+
sut.showMnemonicInfo()
50+
}
51+
52+
waitForIdleSync()
53+
54+
onView(withId(R.id.encryption_passwordInput)).check(matches(withHint("Passphrase…")))
55+
}
56+
}

app/src/main/java/com/owncloud/android/datamodel/OCFile.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,14 +601,19 @@ public boolean isHidden() {
601601
}
602602

603603
/**
604+
* The unique fileId for the file within the instance This only works if we have 12 digits for instanceId RemoteId
605+
* is a combination of localId + instanceId If a file has remoteId: 4174305739oc97a8ddfc96, in this 4174305739 is
606+
* localId & oc97a8ddfc96 is instanceId which is of 12 digits
607+
*
604608
* unique fileId for the file within the instance
605609
*/
606610
@SuppressFBWarnings("STT")
607611
public long getLocalId() {
608612
if (localId > 0) {
609613
return localId;
610614
} else if (remoteId != null && remoteId.length() > 8) {
611-
return Long.parseLong(remoteId.substring(0, 8).replaceAll("^0*", ""));
615+
//NMC Customization --> for long remote id's
616+
return Long.parseLong(remoteId.substring(0, remoteId.length() - 12).replaceAll("^0*", ""));
612617
} else {
613618
return -1;
614619
}

app/src/main/java/com/owncloud/android/files/FileMenuFilter.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ private List<Integer> filter(boolean inSingleFileFragment) {
187187

188188

189189
private void filterShareFile(List<Integer> toHide, OCCapability capability) {
190-
if (!isSingleSelection() || containsEncryptedFile() ||
190+
if (!isSingleSelection() || containsEncryptedFile() || containsEncryptedFolder() ||
191191
(!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
192192
!isShareApiEnabled(capability) || !files.iterator().next().canReshare()) {
193193
toHide.add(R.id.action_send_share_file);
@@ -203,19 +203,21 @@ private void filterSendFiles(List<Integer> toHide, boolean inSingleFileFragment)
203203
}
204204

205205
private void filterDetails(Collection<Integer> toHide) {
206-
if (!isSingleSelection()) {
206+
if (!isSingleSelection() || containsEncryptedFolder() || containsEncryptedFile()) {
207207
toHide.add(R.id.action_see_details);
208208
}
209209
}
210210

211211
private void filterFavorite(List<Integer> toHide, boolean synchronizing) {
212-
if (files.isEmpty() || synchronizing || allFavorites()) {
212+
if (files.isEmpty() || synchronizing || allFavorites() || containsEncryptedFile()
213+
|| containsEncryptedFolder()) {
213214
toHide.add(R.id.action_favorite);
214215
}
215216
}
216217

217218
private void filterUnfavorite(List<Integer> toHide, boolean synchronizing) {
218-
if (files.isEmpty() || synchronizing || allNotFavorites()) {
219+
if (files.isEmpty() || synchronizing || allNotFavorites() || containsEncryptedFile()
220+
|| containsEncryptedFolder()) {
219221
toHide.add(R.id.action_unset_favorite);
220222
}
221223
}
@@ -297,8 +299,8 @@ private boolean isRichDocumentEditingSupported(OCCapability capability, String m
297299
}
298300

299301
private void filterSync(List<Integer> toHide, boolean synchronizing) {
300-
if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing || containsEncryptedFile()
301-
|| containsEncryptedFolder()) {
302+
//NMC Customization --> show sync option for e2ee folder
303+
if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
302304
toHide.add(R.id.action_sync_file);
303305
}
304306
}
@@ -340,8 +342,11 @@ private void filterSelectAll(List<Integer> toHide, boolean inSingleFileFragment)
340342
}
341343

342344
private void filterRemove(List<Integer> toHide, boolean synchronizing) {
343-
if (files.isEmpty() || synchronizing || containsLockedFile()
344-
|| containsEncryptedFolder() || containsEncryptedFile()) {
345+
if ((files.isEmpty() || synchronizing || containsLockedFile()
346+
|| containsEncryptedFolder() || containsEncryptedFile())
347+
//show delete option for encrypted sub-folder
348+
&& !hasEncryptedParent()
349+
) {
345350
toHide.add(R.id.action_remove_file);
346351
}
347352
}

app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ open class FolderPickerActivity :
7070
private var mSyncBroadcastReceiver: SyncBroadcastReceiver? = null
7171
private var mSyncInProgress = false
7272
private var mSearchOnlyFolders = false
73+
private var mShowOnlyFolder = false
74+
private var mHideEncryptedFolder = false
7375
var isDoNotEnterEncryptedFolder = false
7476
private set
7577
private var mCancelBtn: MaterialButton? = null
@@ -100,6 +102,10 @@ open class FolderPickerActivity :
100102
View.VISIBLE
101103
findViewById<View>(R.id.switch_grid_view_button).visibility =
102104
View.GONE
105+
106+
mShowOnlyFolder = intent.getBooleanExtra(EXTRA_SHOW_ONLY_FOLDER, false)
107+
mHideEncryptedFolder = intent.getBooleanExtra(EXTRA_HIDE_ENCRYPTED_FOLDER, false)
108+
103109
mAction = intent.getStringExtra(EXTRA_ACTION)
104110
if (mAction != null) {
105111
when (mAction) {
@@ -316,7 +322,7 @@ open class FolderPickerActivity :
316322

317323
private fun refreshListOfFilesFragment(fromSearch: Boolean) {
318324
val fileListFragment = listOfFilesFragment
319-
fileListFragment?.listDirectory(false, fromSearch)
325+
fileListFragment?.listDirectoryFolder(false, fromSearch, mShowOnlyFolder, mHideEncryptedFolder)
320326
}
321327

322328
fun browseToRoot() {
@@ -592,6 +598,12 @@ open class FolderPickerActivity :
592598
@JvmField
593599
val EXTRA_ACTION = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_ACTION")
594600

601+
@JvmField
602+
val EXTRA_SHOW_ONLY_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_SHOW_ONLY_FOLDER")
603+
604+
@JvmField
605+
val EXTRA_HIDE_ENCRYPTED_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_HIDE_ENCRYPTED_FOLDER")
606+
595607
const val MOVE = "MOVE"
596608
const val COPY = "COPY"
597609
const val CHOOSE_LOCATION = "CHOOSE_LOCATION"

0 commit comments

Comments
 (0)