Skip to content

[stable-3.32] E2EE Customizations #347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: stable-3.32
Choose a base branch
from
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
152 changes: 152 additions & 0 deletions app/src/androidTest/java/com/nmc/android/FileMenuFilterIT.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Nextcloud Android client application
*
* @author Álvaro Brey Vilas
* Copyright (C) 2022 Álvaro Brey Vilas
* Copyright (C) 2022 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nmc.android

import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.account.User
import com.nextcloud.client.jobs.download.FileDownloadWorker
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.test.TestActivity
import com.nextcloud.utils.EditorUtils
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.FileMenuFilter
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.services.OperationsService
import com.owncloud.android.ui.activity.ComponentsGetter
import com.owncloud.android.utils.MimeType
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.security.SecureRandom

@RunWith(AndroidJUnit4::class)
class FileMenuFilterIT : AbstractIT() {

@MockK
private lateinit var mockComponentsGetter: ComponentsGetter

@MockK
private lateinit var mockStorageManager: FileDataStorageManager

@MockK
private lateinit var mockFileUploaderBinder: FileUploadHelper

@MockK
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder

@MockK
private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener

@MockK
private lateinit var mockArbitraryDataProvider: ArbitraryDataProvider

private lateinit var editorUtils: EditorUtils

@Before
fun setup() {
MockKAnnotations.init(this)
every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
every { mockComponentsGetter.fileUploaderHelper } returns mockFileUploaderBinder
every { mockFileDownloadProgressListener.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloadProgressListener } returns mockFileDownloadProgressListener
every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
every { mockStorageManager.getFileById(any()) } returns OCFile("/")
every { mockStorageManager.getFolderContent(any(), any()) } returns ArrayList<OCFile>()
every { mockArbitraryDataProvider.getValue(any<User>(), any()) } returns ""
editorUtils = EditorUtils(mockArbitraryDataProvider)
}

@Test
fun hide_shareAndFavouriteMenu_encryptedFolder() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.TRUE
}

val encryptedFolder = OCFile("/encryptedFolder/").apply {
isEncrypted = true
mimeType = MimeType.DIRECTORY
fileLength = SecureRandom().nextLong()
}

configureCapability(capability)

launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory =
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)

val sut = filterFactory.newInstance(encryptedFolder, mockComponentsGetter, true, user)
val toHide = sut.getToHide(false)

// encrypted folder
assertTrue(toHide.contains(R.id.action_see_details))
assertTrue(toHide.contains(R.id.action_favorite))
assertTrue(toHide.contains(R.id.action_unset_favorite))
}
}
}

@Test
fun show_shareAndFavouriteMenu_normalFolder() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.TRUE
}

val normalFolder = OCFile("/folder/").apply {
mimeType = MimeType.DIRECTORY
fileLength = SecureRandom().nextLong()
}

configureCapability(capability)

launchActivity<TestActivity>().use {
it.onActivity { activity ->
val filterFactory =
FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)

val sut = filterFactory.newInstance(normalFolder, mockComponentsGetter, true, user)
val toHide = sut.getToHide(false)

// normal folder
assertFalse(toHide.contains(R.id.action_see_details))
assertFalse(toHide.contains(R.id.action_favorite))
assertTrue(toHide.contains(R.id.action_unset_favorite))
}
}
}

private fun configureCapability(capability: OCCapability) {
every { mockStorageManager.getCapability(any<User>()) } returns capability
every { mockStorageManager.getCapability(any<String>()) } returns capability
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.nmc.android

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment
import org.junit.Rule
import org.junit.Test
import com.owncloud.android.R

class SetupEncryptionDialogFragmentIT : AbstractIT() {

@get:Rule
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)

@Test
fun validatePassphraseInputHint() {
val activity = testActivityRule.launchActivity(null)

val sut = SetupEncryptionDialogFragment.newInstance(user, 0)

sut.show(activity.supportFragmentManager, "1")

val keyWords = arrayListOf(
"ability",
"able",
"about",
"above",
"absent",
"absorb",
"abstract",
"absurd",
"abuse",
"access",
"accident",
"account",
"accuse"
)

shortSleep()

UiThreadStatement.runOnUiThread {
sut.setMnemonic(keyWords)
sut.showMnemonicInfo()
}

waitForIdleSync()

onView(withId(R.id.encryption_passwordInput)).check(matches(withHint("Passphrase…")))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@ public void createFolder() {

}

@Override
public void createEncryptedFolder() {

}

@Override
public void uploadFromApp() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,18 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {

testFolder.richWorkspace = " "
activity.storageManager.saveFile(testFolder)
sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "")
sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "",
showOnlyFolder = false,
hideEncryptedFolder = false
)
Assert.assertFalse(sut.adapter.shouldShowHeader())

testFolder.richWorkspace = null
activity.storageManager.saveFile(testFolder)
sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "")
sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "",
showOnlyFolder = false,
hideEncryptedFolder = false
)
Assert.assertFalse(sut.adapter.shouldShowHeader())

testFolder.richWorkspace = "1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ fun List<OCFile>.limitToPersonalFiles(userId: String): List<OCFile> = filter { f
ownerId == userId && !file.isSharedWithMe && !file.isGroupFolder
} == true
}

// NMC method to filter only folders with/without e2ee folders
fun List<OCFile>.filterByFolder(hideEncryptedFolder: Boolean = false): List<OCFile> =
filter { it.isFolder && (!hideEncryptedFolder || !it.isEncrypted) }
7 changes: 6 additions & 1 deletion app/src/main/java/com/owncloud/android/datamodel/OCFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -610,14 +610,19 @@ public boolean isHidden() {
}

/**
* The unique fileId for the file within the instance This only works if we have 12 digits for instanceId RemoteId
* is a combination of localId + instanceId If a file has remoteId: 4174305739oc97a8ddfc96, in this 4174305739 is
* localId & oc97a8ddfc96 is instanceId which is of 12 digits
*
* unique fileId for the file within the instance
*/
@SuppressFBWarnings("STT")
public long getLocalId() {
if (localId > 0) {
return localId;
} else if (remoteId != null && remoteId.length() > 8) {
return Long.parseLong(remoteId.substring(0, 8).replaceAll("^0*", ""));
//NMC Customization --> for long remote id's
return Long.parseLong(remoteId.substring(0, remoteId.length() - 12).replaceAll("^0*", ""));
} else {
return -1;
}
Expand Down
29 changes: 22 additions & 7 deletions app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private List<Integer> filter(boolean inSingleFileFragment) {


private void filterShareFile(List<Integer> toHide, OCCapability capability) {
if (!isSingleSelection() || containsEncryptedFile() || hasEncryptedParent() ||
if (!isSingleSelection() || containsEncryptedFile() || hasEncryptedParent() || containsEncryptedFolder() ||
(!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
!isShareApiEnabled(capability) || !files.iterator().next().canReshare()) {
toHide.add(R.id.action_send_share_file);
Expand Down Expand Up @@ -208,19 +208,21 @@ private void filterSendFiles(List<Integer> toHide, boolean inSingleFileFragment)
}

private void filterDetails(Collection<Integer> toHide) {
if (!isSingleSelection()) {
if (!isSingleSelection() || containsEncryptedFolder() || containsEncryptedFile()) {
toHide.add(R.id.action_see_details);
}
}

private void filterFavorite(List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || allFavorites()) {
if (files.isEmpty() || synchronizing || allFavorites() || containsEncryptedFile()
|| containsEncryptedFolder()) {
toHide.add(R.id.action_favorite);
}
}

private void filterUnfavorite(List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || allNotFavorites()) {
if (files.isEmpty() || synchronizing || allNotFavorites() || containsEncryptedFile()
|| containsEncryptedFolder()) {
toHide.add(R.id.action_unset_favorite);
}
}
Expand Down Expand Up @@ -253,7 +255,7 @@ private void filterUnlock(List<Integer> toHide, boolean fileLockingEnabled) {

private void filterEncrypt(List<Integer> toHide, boolean endToEndEncryptionEnabled) {
if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() || isGroupFolder()
|| !endToEndEncryptionEnabled || !isEmptyFolder() || isShared()) {
|| !endToEndEncryptionEnabled || !isEmptyFolder() || isShared() || isInSubFolder()) {
toHide.add(R.id.action_encrypted);
}
}
Expand Down Expand Up @@ -355,8 +357,10 @@ private void filterSelectAll(List<Integer> toHide, boolean inSingleFileFragment)
}

private void filterRemove(List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || containsLockedFile()
|| containsEncryptedFolder() || isFolderAndContainsEncryptedFile()) {
if ((files.isEmpty() || synchronizing || containsLockedFile()
|| containsEncryptedFolder() || isFolderAndContainsEncryptedFile())
//show delete option for encrypted sub-folder
&& !hasEncryptedParent()) {
toHide.add(R.id.action_remove_file);
}
}
Expand Down Expand Up @@ -597,4 +601,15 @@ private boolean isShared() {
}
return false;
}

private boolean isInSubFolder() {
OCFile folder = files.iterator().next();
OCFile parent = storageManager.getFileById(folder.getParentId());

if (parent == null) {
return false;
}

return !OCFile.ROOT_PATH.equals(parent.getRemotePath());
}
}
Loading