Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c7d050c
Added RefreshPrivateKeysFromEkmViewModel.| #1740
DenBond7 Apr 26, 2022
8a9cdfa
Refactored code.| #1740
DenBond7 Apr 26, 2022
fb16c08
Added retrieving missing passphrase to refresh keys from EKM.| #1740
DenBond7 Apr 27, 2022
08a8dd0
Fixed FlowCryptMockWebServerRule.| #1740
DenBond7 Apr 28, 2022
33d1999
Added RefreshKeysFromEkmFlowTest.(not completed)| #1740
DenBond7 Apr 28, 2022
40da28a
Completed RefreshKeysFromEkmFlowTest.| #1740
DenBond7 Apr 28, 2022
f937f0f
Refactored code.| #1740
DenBond7 Apr 28, 2022
5bb839f
Refactored code. Step 2.| #1740
DenBond7 Apr 28, 2022
e8bd906
Refactored code. Step 3.| #1740
DenBond7 Apr 28, 2022
9eff2f4
Refactored code. Step 4.| #1740
DenBond7 Apr 28, 2022
9f47a47
wip
DenBond7 Apr 29, 2022
83ed2ed
Refactored code. Step 5.| #1740
DenBond7 Apr 29, 2022
b9a4864
Fixed UI for a case when have to ask a pass phrase.| #1740
DenBond7 Apr 29, 2022
98878de
Fixed some UI tests.| #1740
DenBond7 May 2, 2022
3d4704c
debugging
DenBond7 May 3, 2022
137c90e
debugging 1
DenBond7 May 3, 2022
2a180bb
debugging 2
DenBond7 May 3, 2022
76371b3
debugging 3
DenBond7 May 3, 2022
d840773
debugging 4
DenBond7 May 3, 2022
4038d2a
Refactored code
DenBond7 May 3, 2022
3326a65
debugging 6
DenBond7 May 3, 2022
0ca3ba9
debugging 7
DenBond7 May 3, 2022
56b8629
debugging 8
DenBond7 May 3, 2022
158b39c
debugging 9
DenBond7 May 3, 2022
216dfe1
debugging 10
DenBond7 May 3, 2022
58abbd8
Reverted back some changes. Temporary disabled some tests.
DenBond7 May 3, 2022
759b814
Reverted back changes in ci-instrumentation-tests-with-mailserver.sh
DenBond7 May 3, 2022
657604b
Reverted back changes in ci-instrumentation-tests-with-mailserver.sh(…
DenBond7 May 3, 2022
859f89f
Fixed ImportRecipientsFromSourceFragmentTest.testFetchKeyFromAttester…
DenBond7 May 3, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----

lIYESz2PphYJKwYBBAHaRw8BAQdA3iJ88J7rikp//hNes1k5m6ZyX7qkksquqPom
WZlr30H+BwMC8VJLCQT+tND/muzVdfnKIWkglbw5doSEM8R3aVgRsw0rOJ6beNjA
YEzSS00ov2jqbTTd2Ex4SSTEVuUgxWVNKwTcEnqhpQA/bWtdg/8JtrQkRXhwaXJl
ZCBLZXkgPGV4cGlyZWRAZmxvd2NyeXB0LnRlc3Q+iJYEExYIAD4CGwMFCwkIBwIG
FQoJCAsCBBYCAwECHgECF4AWIQRZkTLxWgRIeqY1bH9xe3ifBdh02gUCYmpTHgUJ
KfjGeAAKCRBxe3ifBdh02mmNAP9qHdytsr+RyasbRXdmdt4JdVSJJh+GkhuPGbcW
hrvbGgEAnwcFpvW1OkVNXjnjtAwp5P6Kb8S1Q9XQblZoN4XdAAmciwRLPY+mEgor
BgEEAZdVAQUBAQdABLWPxegWOttKTGzaM5Xp019pY9BJUnzM6TDFOa42XGEDAQgH
/gcDArolD17X2jUC/xMWAWWc2Y9On66ejawKoGeGLhb/F9/bb2qBNR1EwrwdwU3C
yqMMJDtHiMU210EE3H3Z0nv/xNEB4+zlyeo5S8HJxqTla6yIfgQYFggAJgIbDBYh
BFmRMvFaBEh6pjVsf3F7eJ8F2HTaBQJialNGBQkp+MagAAoJEHF7eJ8F2HTajfgB
AKUIwkMF0BclDeAPlZ6Ci1oro3Oj2U8JsNKZdAcA9lJDAP4kgE2PAHb3jFqhIJxc
HtYFmCQbOsaW6U14mxSDUHDPCw==
=vgGg
-----END PGP PRIVATE KEY BLOCK-----
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,18 @@ import javax.net.ssl.SSLSocketFactory
* E-mail: DenBond7@gmail.com
*/
class FlowCryptMockWebServerRule(val port: Int, val responseDispatcher: Dispatcher) : BaseRule() {
var server = MockWebServer()

override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
val server = MockWebServer()
try {
server.dispatcher = responseDispatcher
val sslSocketFactory = getSSLSocketFactory()
server.useHttps(sslSocketFactory, false)
server.start(port)
base.evaluate()
} finally {
server.shutdown()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* 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.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.activityScenarioRule
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.api.retrofit.ApiHelper
import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse
import com.flowcrypt.email.api.retrofit.response.model.Key
import com.flowcrypt.email.api.retrofit.response.model.OrgRules
import com.flowcrypt.email.base.BaseTest
import com.flowcrypt.email.database.entity.KeyEntity
import com.flowcrypt.email.extensions.org.pgpainless.util.asString
import com.flowcrypt.email.model.KeyImportDetails
import com.flowcrypt.email.rules.AddAccountToDatabaseRule
import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule
import com.flowcrypt.email.rules.ClearAppSettingsRule
import com.flowcrypt.email.rules.FlowCryptMockWebServerRule
import com.flowcrypt.email.rules.RetryRule
import com.flowcrypt.email.rules.ScreenshotTestRule
import com.flowcrypt.email.security.KeysStorageImpl
import com.flowcrypt.email.security.model.PgpKeyDetails
import com.flowcrypt.email.security.pgp.PgpKey
import com.flowcrypt.email.ui.activity.MainActivity
import com.flowcrypt.email.util.AccountDaoManager
import com.flowcrypt.email.util.PrivateKeysManager
import com.flowcrypt.email.util.exception.ApiException
import com.google.gson.Gson
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestName
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.pgpainless.util.Passphrase
import java.net.HttpURLConnection

/**
* @author Denis Bondarenko
* Date: 8/6/21
* Time: 2:05 PM
* E-mail: DenBond7@gmail.com
*/
@MediumTest
@RunWith(AndroidJUnit4::class)
class RefreshKeysFromEkmFlowTest : BaseTest() {
private val userWithOrgRules = AccountDaoManager.getUserWithOrgRules(
OrgRules(
flags = listOf(
OrgRules.DomainRule.NO_PRV_CREATE,
OrgRules.DomainRule.NO_PRV_BACKUP,
OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE
),
customKeyserverUrl = null,
keyManagerUrl = EKM_URL,
enforceKeygenAlgo = null,
enforceKeygenExpireMonths = null
)
).copy(email = "ekm@localhost:1212")

private val addAccountToDatabaseRule = AddAccountToDatabaseRule(userWithOrgRules)
private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule(
accountEntity = addAccountToDatabaseRule.account,
keyPath = "pgp/expired@flowcrypt.test_prv_default.asc",
passphrase = TestConstants.DEFAULT_PASSWORD,
sourceType = KeyImportDetails.SourceType.EMAIL,
passphraseType = KeyEntity.PassphraseType.RAM
)
private val mockWebServerRule = FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT,
object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val gson = ApiHelper.getInstance(getTargetContext()).gson

if (request.path?.startsWith("/ekm") == true) {
return handleEkmAPI(gson)
}

return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
}
})

override val activityScenarioRule = activityScenarioRule<MainActivity>()

@get:Rule
val testNameRule = TestName()

@get:Rule
var ruleChain: TestRule = RuleChain
.outerRule(RetryRule.DEFAULT)
.around(ClearAppSettingsRule())
.around(mockWebServerRule)
.around(addAccountToDatabaseRule)
.around(addPrivateKeyToDatabaseRule)
.around(activityScenarioRule)
.around(ScreenshotTestRule())

@Test
fun testUpdatePrvKeyFromEkmSuccessSilent() {
val keysStorage = KeysStorageImpl.getInstance(getTargetContext())
addPassphraseToRamCache(keysStorage)

//check existing key before updating
val existingPgpKeyDetailsBeforeUpdating = checkExistingKeyBeforeUpdating(keysStorage)

//we need to make a delay to wait while [KeysStorageImpl] will update internal data
Thread.sleep(2000)

//check existing key after updating
val existingPgpKeyDetailsAfterUpdating = keysStorage.getPgpKeyDetailsList().first()
assertTrue(!existingPgpKeyDetailsAfterUpdating.isExpired)
assertTrue(existingPgpKeyDetailsAfterUpdating.isNewerThan(existingPgpKeyDetailsBeforeUpdating))

onView(withId(R.id.toolbar))
.check(matches(isDisplayed()))
}

@Test
fun testUpdatePrvKeyFromEkmShowFixMissingPassphrase() {
val keysStorage = KeysStorageImpl.getInstance(getTargetContext())

//check existing key before updating
val existingPgpKeyDetailsBeforeUpdating = keysStorage.getPgpKeyDetailsList().first()
assertTrue(existingPgpKeyDetailsBeforeUpdating.isExpired)

//check that we show dialog where a user provides a pass phrase
isDialogWithTextDisplayed(
decorView,
getResString(R.string.please_provide_passphrase_for_following_keys_to_keep_keys_up_to_date)
)

onView(withId(R.id.eTKeyPassword))
.inRoot(isDialog())
.perform(typeText(TestConstants.DEFAULT_PASSWORD))

onView(withId(R.id.btnUpdatePassphrase))
.inRoot(isDialog())
.perform(click())

//we need to make a delay to wait while [KeysStorageImpl] will update internal data
Thread.sleep(2000)

//check existing key after updating
val existingPgpKeyDetailsAfterUpdating = keysStorage.getPgpKeyDetailsList().first()
assertTrue(!existingPgpKeyDetailsAfterUpdating.isExpired)
assertTrue(existingPgpKeyDetailsAfterUpdating.isNewerThan(existingPgpKeyDetailsBeforeUpdating))

onView(withId(R.id.toolbar))
.check(matches(isDisplayed()))
}

@Test
fun testUpdatePrvKeyFromEkmShowApiError() {
val keysStorage = KeysStorageImpl.getInstance(getTargetContext())
addPassphraseToRamCache(keysStorage)

//check existing key before updating
val existingPgpKeyDetailsBeforeUpdating = checkExistingKeyBeforeUpdating(keysStorage)

//check error dialog content
isDialogWithTextDisplayed(decorView, getResString(R.string.refreshing_keys_from_ekm_failed))

val stringBuilder = StringBuilder()
val exception = ApiException(EKM_ERROR_RESPONSE.apiError)
stringBuilder.append(exception.javaClass.simpleName)
stringBuilder.append(":")
stringBuilder.append(exception.message)

isDialogWithTextDisplayed(decorView, stringBuilder.toString())

//check that after fetching prv keys from EKM we have the same keys as before
val existingPgpKeyDetailsAfterUpdating = keysStorage.getPgpKeyDetailsList().first()
assertEquals(existingPgpKeyDetailsBeforeUpdating, existingPgpKeyDetailsAfterUpdating)
}

private fun checkExistingKeyBeforeUpdating(keysStorage: KeysStorageImpl): PgpKeyDetails {
val existingPgpKeyDetailsBeforeUpdating = keysStorage.getPgpKeyDetailsList().first()
assertTrue(existingPgpKeyDetailsBeforeUpdating.isExpired)
assertEquals(
addPrivateKeyToDatabaseRule.passphrase,
keysStorage.getPassphraseByFingerprint(existingPgpKeyDetailsBeforeUpdating.fingerprint)?.asString
)
return existingPgpKeyDetailsBeforeUpdating
}

private fun addPassphraseToRamCache(keysStorage: KeysStorageImpl) {
keysStorage.putPassphraseToCache(
fingerprint = addPrivateKeyToDatabaseRule.pgpKeyDetails.fingerprint,
passphrase = Passphrase.fromPassword(TestConstants.DEFAULT_PASSWORD),
validUntil = KeysStorageImpl.calculateLifeTimeForPassphrase(),
passphraseType = KeyEntity.PassphraseType.RAM
)
}

private fun handleEkmAPI(gson: Gson): MockResponse {
//simulate network operation to prevent too fast response
Thread.sleep(500)

return when (testNameRule.methodName) {
"testUpdatePrvKeyFromEkmSuccessSilent", "testUpdatePrvKeyFromEkmShowFixMissingPassphrase" ->
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(gson.toJson(EKM_RESPONSE_SUCCESS))

"testUpdatePrvKeyFromEkmShowApiError" ->
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(gson.toJson(EKM_ERROR_RESPONSE))

else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
}
}

companion object {
private const val EKM_URL = "https://localhost:1212/ekm/"
private val EKM_KEY_WITH_EXTENDED_EXPIRATION = PrivateKeysManager.getPgpKeyDetailsFromAssets(
"pgp/expired_extended@flowcrypt.test_prv_default.asc"
)
private const val EKM_ERROR = "some error"
private val EKM_ERROR_RESPONSE = EkmPrivateKeysResponse(
code = 400,
message = EKM_ERROR
)
private val EKM_RESPONSE_SUCCESS = EkmPrivateKeysResponse(
privateKeys = listOf(
Key(
PgpKey.decryptKey(
requireNotNull(EKM_KEY_WITH_EXTENDED_EXPIRATION.privateKey),
Passphrase.fromPassword(TestConstants.DEFAULT_PASSWORD)
)
)
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class ImportRecipientsFromSourceFragmentTest : BaseTest() {
.perform(click())

//due to realization of MockWebServer I can't produce the same response.
isDialogWithTextDisplayed(decorView, "API error: code = 404, message = ")
isDialogWithTextDisplayed(decorView, "API error: code = 404, message = Public key not found")
}

@Test
Expand Down
Loading