Skip to content
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

[stable-3.28] E2EE #149

Closed
wants to merge 1 commit into from
Closed
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.files.downloader.FileDownloadWorker
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.services.FileUploader
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: FileUploader.FileUploaderBinder

@MockK
private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener

@MockK
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder

@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.fileUploaderBinder } 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.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…")))
}
}
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 @@ -638,14 +638,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
20 changes: 12 additions & 8 deletions app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,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 All @@ -202,19 +202,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 @@ -300,8 +302,8 @@ private boolean isRichDocumentEditingSupported(OCCapability capability, String m
}

private void filterSync(List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing || containsEncryptedFile()
|| containsEncryptedFolder()) {
//NMC Customization --> show sync option for e2ee folder
if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
toHide.add(R.id.action_sync_file);
}
}
Expand Down Expand Up @@ -343,8 +345,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
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ open class FolderPickerActivity :
private var mSyncBroadcastReceiver: SyncBroadcastReceiver? = null
private var mSyncInProgress = false
private var mSearchOnlyFolders = false
private var mShowOnlyFolder = false
private var mHideEncryptedFolder = false
var isDoNotEnterEncryptedFolder = false
private set

Expand Down Expand Up @@ -125,6 +127,9 @@ open class FolderPickerActivity :
}

private fun setupAction() {
mShowOnlyFolder = intent.getBooleanExtra(EXTRA_SHOW_ONLY_FOLDER, false)
mHideEncryptedFolder = intent.getBooleanExtra(EXTRA_HIDE_ENCRYPTED_FOLDER, false)

action = intent.getStringExtra(EXTRA_ACTION)

if (action != null && action == CHOOSE_LOCATION) {
Expand Down Expand Up @@ -370,7 +375,7 @@ open class FolderPickerActivity :
}

private fun refreshListOfFilesFragment(fromSearch: Boolean) {
listOfFilesFragment?.listDirectory(false, fromSearch)
listOfFilesFragment?.listDirectoryFolder(false, fromSearch, mShowOnlyFolder, mHideEncryptedFolder)
}

fun browseToRoot() {
Expand Down Expand Up @@ -672,6 +677,12 @@ open class FolderPickerActivity :
@JvmField
val EXTRA_ACTION = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_ACTION")

@JvmField
val EXTRA_SHOW_ONLY_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_SHOW_ONLY_FOLDER")

@JvmField
val EXTRA_HIDE_ENCRYPTED_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_HIDE_ENCRYPTED_FOLDER")

const val MOVE_OR_COPY = "MOVE_OR_COPY"
const val CHOOSE_LOCATION = "CHOOSE_LOCATION"
private val TAG = FolderPickerActivity::class.java.simpleName
Expand Down
Loading