Skip to content

Commit

Permalink
fixed "no private key available" issue. Part 1. import key (or load f…
Browse files Browse the repository at this point in the history
…rom backups) (#2660)

* wip

* Added ActionsDialogFragmen.| #597

* wip

* wip

* wip

* wip

* Added ComposeScreenNoKeyAvailableFlowTest.| #597
  • Loading branch information
DenBond7 authored Mar 28, 2024
1 parent 636be46 commit b30cdf1
Show file tree
Hide file tree
Showing 26 changed files with 616 additions and 169 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: DenBond7
*/

package com.flowcrypt.email.ui

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasTextColor
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.flowcrypt.email.R
import com.flowcrypt.email.TestConstants
import com.flowcrypt.email.extensions.kotlin.asInternetAddress
import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings
import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule
import com.flowcrypt.email.rules.ClearAppSettingsRule
import com.flowcrypt.email.rules.FlowCryptMockWebServerRule
import com.flowcrypt.email.rules.GrantPermissionRuleChooser
import com.flowcrypt.email.rules.RetryRule
import com.flowcrypt.email.rules.ScreenshotTestRule
import com.flowcrypt.email.ui.base.BaseComposeScreenTest
import com.flowcrypt.email.util.PrivateKeysManager
import com.flowcrypt.email.util.TestGeneralUtil
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
import org.hamcrest.CoreMatchers.not
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import java.net.HttpURLConnection

/**
* @author Denys Bondarenko
*/
@MediumTest
@RunWith(AndroidJUnit4::class)
@FlowCryptTestSettings(useCommonIdling = false)
class ComposeScreenNoKeyAvailableFlowTest : BaseComposeScreenTest() {
private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule(
keyPath = "pgp/[email protected]_prv_strong_primary.asc"
)

private val pgpKeyDetails =
PrivateKeysManager.getPgpKeyDetailsFromAssets("pgp/[email protected]_fisrtKey_prv_strong.asc")

@get:Rule
var ruleChain: TestRule = RuleChain
.outerRule(RetryRule.DEFAULT)
.around(ClearAppSettingsRule())
.around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS))
.around(addAccountToDatabaseRule)
.around(addPrivateKeyToDatabaseRule)
.around(activeActivityRule)
.around(ScreenshotTestRule())

@Test
fun testImportKey() {
activeActivityRule?.launch(intent)
registerAllIdlingResources()
fillInAllFields(
to = setOf(
requireNotNull(TestConstants.RECIPIENT_WITH_PUBLIC_KEY_ON_ATTESTER.asInternetAddress())
)
)

//check that editTextFrom has gray text color. It means a sender doesn't have a private key
onView(withId(R.id.editTextFrom))
.check(matches(isDisplayed()))
.check(matches(hasTextColor(R.color.gray)))

onView(withId(R.id.menuActionSend))
.check(matches(isDisplayed()))
.perform(click())

isDialogWithTextDisplayed(
decorView,
getResString(R.string.no_key_available, addAccountToDatabaseRule.account.email)
)

addTextToClipboard("private key", requireNotNull(pgpKeyDetails.privateKey))
onView(withText(R.string.import_private_key))
.check(matches(isDisplayed()))
.perform(click())

Thread.sleep(1000)
onView(withText(R.string.load_from_clipboard))
.check(matches(isDisplayed()))
.perform(click())

onView(withId(R.id.editTextKeyPassword))
.perform(
replaceText(TestConstants.DEFAULT_STRONG_PASSWORD),
closeSoftKeyboard()
)
onView(withId(R.id.buttonPositiveAction))
.perform(scrollTo(), click())

waitForObjectWithText(addAccountToDatabaseRule.account.email, 5000)

//check that editTextFrom doesn't have gray text color. It means a sender has a private key.
onView(withId(R.id.editTextFrom))
.check(matches(isDisplayed()))
.check(matches(not(hasTextColor(R.color.gray))))
}

companion object {
@get:ClassRule
@JvmStatic
val mockWebServerRule = FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT,
object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
if (request.path?.startsWith("/attester/pub", ignoreCase = true) == true) {
val lastSegment = request.requestUrl?.pathSegments?.lastOrNull()

when {
TestConstants.RECIPIENT_WITH_PUBLIC_KEY_ON_ATTESTER.equals(
lastSegment, true
) -> {
return MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(TestGeneralUtil.readResourceAsString("3.txt"))
}
}
}

return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.flowcrypt.email.util.GeneralUtil
import com.flowcrypt.email.util.PrivateKeysManager
import com.flowcrypt.email.util.TestGeneralUtil
import com.flowcrypt.email.util.UIUtil
import com.flowcrypt.email.util.exception.NoKeyAvailableException
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
Expand Down Expand Up @@ -78,12 +79,15 @@ class ComposeScreenNoSuitablePrivateKeysFlowTest : BaseComposeScreenTest() {
.check(matches(isDisplayed()))
.perform(click())

val exception = Assert.assertThrows(IllegalStateException::class.java) {
val exception = Assert.assertThrows(NoKeyAvailableException::class.java) {
SecurityUtils.getSenderPublicKeys(getTargetContext(), addAccountToDatabaseRule.account.email)
}

assertEquals(
"There are no usable for encryption keys for " + addPrivateKeyToDatabaseRule.pgpKeyRingDetails.getPrimaryInternetAddress(),
getResString(
R.string.no_key_available_for_your_email_account,
getResString(R.string.support_email)
),
exception.message
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import androidx.lifecycle.LifecycleOwner
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import com.flowcrypt.email.R
import com.flowcrypt.email.model.DialogItem
import com.flowcrypt.email.model.Screenshot
import com.flowcrypt.email.ui.activity.fragment.FeedbackFragmentArgs
import com.flowcrypt.email.ui.activity.fragment.dialog.ActionsDialogFragmentArgs
import com.flowcrypt.email.ui.activity.fragment.dialog.FixNeedPassphraseIssueDialogFragment
import com.flowcrypt.email.ui.activity.fragment.dialog.FixNeedPassphraseIssueDialogFragmentArgs
import com.flowcrypt.email.ui.activity.fragment.dialog.InfoDialogFragmentArgs
Expand Down Expand Up @@ -160,3 +162,23 @@ fun LifecycleOwner.showFeedbackFragment(
navController?.navigate(navDirections)
}
}

fun LifecycleOwner.showActionDialogFragment(
navController: NavController?,
requestKey: String,
dialogTitle: String? = null,
isCancelable: Boolean = true,
items: List<DialogItem>
) {
showDialogFragment(navController) {
return@showDialogFragment object : NavDirections {
override val actionId = R.id.actions_dialog_graph
override val arguments = ActionsDialogFragmentArgs(
requestKey = requestKey,
dialogTitle = dialogTitle,
isCancelable = isCancelable,
items = items.toTypedArray()
).toBundle()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import kotlinx.parcelize.Parcelize
* @author Denys Bondarenko
*/
@Parcelize
data class DialogItem constructor(
data class DialogItem(
/**
* It should be an image 32*32
*/
val iconResourceId: Int = 0,
val title: String = "",
val id: Int = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class SecurityUtils {
val matchingKeyRingInfoList = keysStorage.getPGPSecretKeyRingsByUserId(senderEmail)
.map { KeyRingInfo(it) }.filter { it.usableForEncryption }
if (matchingKeyRingInfoList.isEmpty()) {
throw IllegalStateException("There are no usable for encryption keys for $senderEmail")
throw NoKeyAvailableException(context = context, email = senderEmail)
}
return matchingKeyRingInfoList.map { PGPPublicKeyRing(it.publicKeys).armor() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.setFragmentResultListener
import androidx.navigation.NavDirections
import androidx.preference.PreferenceManager
import com.flowcrypt.email.Constants
import com.flowcrypt.email.R
Expand Down Expand Up @@ -357,15 +358,17 @@ class AddOtherAccountFragment : BaseSingInFragment<FragmentAddOtherAccountBindin
}
} else {
navController?.navigate(
AddOtherAccountFragmentDirections
.actionAddOtherAccountFragmentToCheckKeysFragment(
object : NavDirections {
override val actionId = R.id.check_keys_graph
override val arguments = CheckKeysFragmentArgs(
requestKey = REQUEST_KEY_CHECK_PRIVATE_KEYS,
privateKeys = (keyDetailsList ?: ArrayList()).toTypedArray(),
sourceType = KeyImportDetails.SourceType.EMAIL,
positiveBtnTitle = getString(R.string.continue_),
negativeBtnTitle = getString(R.string.use_another_account),
initSubTitlePlurals = R.plurals.found_backup_of_your_account_key
)
).toBundle()
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.addCallback
import androidx.fragment.app.viewModels
import androidx.navigation.NavDirections
import com.flowcrypt.email.Constants
import com.flowcrypt.email.R
import com.flowcrypt.email.api.retrofit.response.base.Result
Expand Down Expand Up @@ -295,12 +296,14 @@ class BackupKeysFragment : BaseFragment<FragmentBackupKeysBinding>(), ProgressBe
duration = Snackbar.LENGTH_LONG
) {
navController?.navigate(
BackupKeysFragmentDirections
.actionBackupKeysFragmentToCheckPassphraseStrengthFragment(
object : NavDirections {
override val actionId = R.id.pass_phrase_strength_graph
override val arguments = CheckPassphraseStrengthFragmentArgs(
popBackStackIdIfSuccess = R.id.backupKeysFragment,
title = getString(R.string.change_pass_phrase),
lostPassphraseTitle = getString(R.string.loss_of_this_pass_phrase_cannot_be_recovered)
)
).toBundle()
}
)
}
}
Expand Down
Loading

0 comments on commit b30cdf1

Please sign in to comment.