diff --git a/.idea/dictionaries/luke.xml b/.idea/dictionaries/luke.xml index f3a77a8cad..0a8ab03b7b 100644 --- a/.idea/dictionaries/luke.xml +++ b/.idea/dictionaries/luke.xml @@ -1,6 +1,8 @@ + attester + bondarenko flowcrypt diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index a2bcad7bee..1d837a2394 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -58,8 +58,6 @@ blocks: jobs: - name: 'Build Project' commands: - # Android Gradle plugin requires Java 11 - # - sem-version java 11 # print debug info - cat /proc/cpuinfo # print Java version @@ -101,8 +99,6 @@ blocks: execution_time_limit: minutes: 15 commands: - # switched to use java 11 to be able to run parametrized Robolectric tests - # - sem-version java 11 # run JUnit tests - ./script/ci-junit-tests.sh diff --git a/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_advanced_pub@localhost_pub.asc b/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_advanced_pub@localhost_pub.asc new file mode 100644 index 0000000000..099147a8b9 Binary files /dev/null and b/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_advanced_pub@localhost_pub.asc differ diff --git a/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_direct_pub@localhost_pub.asc b/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_direct_pub@localhost_pub.asc new file mode 100644 index 0000000000..6e98aa6da9 Binary files /dev/null and b/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_direct_pub@localhost_pub.asc differ diff --git a/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_prv@localhost_sec.asc b/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_prv@localhost_sec.asc new file mode 100644 index 0000000000..ca9c623a4b Binary files /dev/null and b/FlowCrypt/src/androidTest/assets/pgp/keys/wkd_prv@localhost_sec.asc differ diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt index 97fe761e8a..b4b25ef0e5 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt @@ -31,7 +31,6 @@ import com.flowcrypt.email.api.email.model.AttachmentInfo import com.flowcrypt.email.api.email.model.IncomingMessageInfo import com.flowcrypt.email.database.FlowCryptRoomDatabase import com.flowcrypt.email.database.entity.AttachmentEntity -import com.flowcrypt.email.matchers.CustomMatchers.Companion.isToast import com.flowcrypt.email.ui.activity.base.BaseActivity import com.flowcrypt.email.util.TestGeneralUtil import com.google.android.material.snackbar.Snackbar @@ -122,12 +121,14 @@ abstract class BaseTest : BaseActivityTestImplementation { * @param delay If we have to check a few toasts one by one * we need to have some timeout between checking. */ + //todo-denbond7 https://github.com/android/android-test/issues/803 + @Deprecated("Toast message assertions not working with android 11 and target sdk 30") protected fun isToastDisplayed(message: String, delay: Long? = null) { - onView(withText(message)) + /*onView(withText(message)) .inRoot(isToast()) .check(matches(isDisplayed())) - delay?.let { Thread.sleep(it) } + delay?.let { Thread.sleep(it) }*/ } /** diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/matchers/ToastMatcher.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/matchers/ToastMatcher.kt index e649ca8aa6..eb3ded4029 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/matchers/ToastMatcher.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/matchers/ToastMatcher.kt @@ -20,6 +20,7 @@ import org.hamcrest.TypeSafeMatcher * See details here * https://stackoverflow.com/questions/28390574/checking-toast-message-in-android-espresso */ +//todo-denbond7 https://github.com/android/android-test/issues/803 class ToastMatcher @RemoteMsgConstructor constructor() : TypeSafeMatcher() { override fun describeTo(description: Description) { description.appendText("is toast") diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AttesterSettingsFragmentTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AttesterSettingsFragmentTest.kt index f205c6a7dc..8f0cd42a95 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AttesterSettingsFragmentTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AttesterSettingsFragmentTest.kt @@ -35,6 +35,7 @@ import org.junit.Test import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.RunWith +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -78,12 +79,12 @@ class AttesterSettingsFragmentTest : BaseTest() { if (request.path?.startsWith("/pub", ignoreCase = true) == true) { val lastSegment = request.requestUrl?.pathSegments?.lastOrNull() if (AccountDaoManager.getDefaultAccountDao().email.equals(lastSegment, true)) { - return MockResponse().setResponseCode(200) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(TestGeneralUtil.readResourceAsString("1.txt")) } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt index 0615df756d..b424404d98 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt @@ -30,6 +30,7 @@ import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.base.BaseBackupKeysFragmentTest import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.TestGeneralUtil +import org.hamcrest.CoreMatchers.not import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -71,10 +72,14 @@ class BackupKeysFragmentSingleKeyPassphraseInRamTest : BaseBackupKeysFragmentTes .check(matches(isDisplayed())) .perform(click()) + onView(withId(R.id.btBackup)) + .check(matches(not(isDisplayed()))) + isToastDisplayed(getResString(R.string.backed_up_successfully)) } @Test + @NotReadyForCI fun testNeedPassphraseDownloadOptionSingleFingerprint() { onView(withId(R.id.rBDownloadOption)) .check(matches(isDisplayed())) @@ -95,6 +100,9 @@ class BackupKeysFragmentSingleKeyPassphraseInRamTest : BaseBackupKeysFragmentTes TestGeneralUtil.deleteFiles(listOf(file)) + onView(withId(R.id.btBackup)) + .check(matches(not(isDisplayed()))) + isToastDisplayed(getResString(R.string.backed_up_successfully)) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTest.kt index 8c3c24d98f..852c0bffdc 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTest.kt @@ -21,6 +21,7 @@ import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.base.BaseBackupKeysFragmentTest import com.flowcrypt.email.util.TestGeneralUtil +import org.hamcrest.CoreMatchers.not import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -70,6 +71,9 @@ class BackupKeysFragmentTest : BaseBackupKeysFragmentTest() { onView(withId(R.id.btBackup)) .check(matches(isDisplayed())) .perform(click()) + + onView(withId(R.id.btBackup)) + .check(matches(not(isDisplayed()))) isToastDisplayed(getResString(R.string.backed_up_successfully)) } @@ -87,6 +91,9 @@ class BackupKeysFragmentTest : BaseBackupKeysFragmentTest() { .perform(click()) TestGeneralUtil.deleteFiles(listOf(file)) + + onView(withId(R.id.btBackup)) + .check(matches(not(isDisplayed()))) isToastDisplayed(getResString(R.string.backed_up_successfully)) } } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTwoKeysSamePassphraseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTwoKeysSamePassphraseTest.kt index 733f149c72..2938417e96 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTwoKeysSamePassphraseTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentTwoKeysSamePassphraseTest.kt @@ -22,6 +22,7 @@ import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.base.BaseBackupKeysFragmentTest import com.flowcrypt.email.util.TestGeneralUtil +import org.hamcrest.CoreMatchers.not import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -62,6 +63,10 @@ class BackupKeysFragmentTwoKeysSamePassphraseTest : BaseBackupKeysFragmentTest() onView(withId(R.id.btBackup)) .check(matches(isDisplayed())) .perform(click()) + + onView(withId(R.id.btBackup)) + .check(matches(not(isDisplayed()))) + isToastDisplayed(getResString(R.string.backed_up_successfully)) } @@ -79,6 +84,10 @@ class BackupKeysFragmentTwoKeysSamePassphraseTest : BaseBackupKeysFragmentTest() .perform(click()) TestGeneralUtil.deleteFiles(listOf(file)) + + onView(withId(R.id.btBackup)) + .check(matches(not(isDisplayed()))) + isToastDisplayed(getResString(R.string.backed_up_successfully)) } } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTest.kt similarity index 93% rename from FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestTest.kt rename to FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTest.kt index 0cdffc7443..2343e334d9 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTest.kt @@ -81,6 +81,7 @@ import org.junit.rules.TemporaryFolder import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.File +import java.net.HttpURLConnection import java.time.Instant /** @@ -94,7 +95,7 @@ import java.time.Instant */ @MediumTest @RunWith(AndroidJUnit4::class) -class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { +class CreateMessageActivityTest : BaseCreateMessageActivityTest() { private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule() private val temporaryFolderRule = TemporaryFolder() @@ -120,7 +121,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testEmptyRecipient() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.editTextRecipientTo)) .check(matches(withText(isEmptyString()))) @@ -137,7 +138,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testEmptyEmailSubject() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.layoutTo)) @@ -163,7 +164,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testEmptyEmailMsg() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.layoutTo)) @@ -185,7 +186,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testUsingStandardMsgEncryptionType() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() if (defaultMsgEncryptionType != MessageEncryptionType.STANDARD) { @@ -200,7 +201,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testUsingSecureMsgEncryptionType() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() if (defaultMsgEncryptionType != MessageEncryptionType.ENCRYPTED) { @@ -214,7 +215,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testSwitchBetweenEncryptionTypes() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() val messageEncryptionType = defaultMsgEncryptionType @@ -238,12 +239,12 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testShowHelpScreen() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) } @Test fun testIsScreenOfComposeNewMsg() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withText(R.string.compose)) @@ -262,7 +263,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testWrongFormatOfRecipientEmailAddress() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() val invalidEmailAddresses = arrayOf("test", "test@", "test@@flowcrypt.test", "@flowcrypt.test") @@ -285,7 +286,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testAddingAtts() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.layoutTo)) @@ -300,7 +301,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testMaxTotalAttachmentSize() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.layoutTo)) @@ -329,7 +330,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testDeletingAtts() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.layoutTo)) @@ -354,7 +355,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testSelectImportPublicKeyFromPopUp() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() intending(hasComponent(ComponentName(getTargetContext(), ImportPublicKeyActivity::class.java))) .respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null)) @@ -413,7 +414,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testSelectedStandardEncryptionTypeFromPopUp() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() fillInAllFields(TestConstants.RECIPIENT_WITHOUT_PUBLIC_KEY_ON_ATTESTER) @@ -428,7 +429,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testSelectedRemoveRecipientFromPopUp() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() onView(withId(R.id.layoutTo)) @@ -476,7 +477,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { @Test fun testSelectedCopyFromOtherContactFromPopUp() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() fillInAllFields(TestConstants.RECIPIENT_WITHOUT_PUBLIC_KEY_ON_ATTESTER) @@ -490,12 +491,24 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { onView(withText(R.string.copy_from_other_contact)) .check(matches(isDisplayed())) .perform(click()) + onView(withId(R.id.editTextRecipientTo)) + .check( + matches( + withChipsBackgroundColor( + chipText = TestConstants.RECIPIENT_WITHOUT_PUBLIC_KEY_ON_ATTESTER, + backgroundColor = UIUtil.getColor( + context = getTargetContext(), + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_EXISTS + ) + ) + ) + ) isToastDisplayed(getResString(R.string.key_successfully_copied)) } @Test fun testSharePubKeySingle() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() openActionBarOverflowOrOptionsMenu(getTargetContext()) @@ -519,7 +532,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { ) val att = EmailUtil.genAttInfoFromPubKey(secondKeyDetails) - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() openActionBarOverflowOrOptionsMenu(getTargetContext()) @@ -553,7 +566,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { TestConstants.DEFAULT_PASSWORD, KeyImportDetails.SourceType.EMAIL ) - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() openActionBarOverflowOrOptionsMenu(getTargetContext()) @@ -575,7 +588,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { FlowCryptRoomDatabase.getDatabase(getTargetContext()) .contactsDao().insert(contact.toContactEntity()) - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() fillInAllFields(contact.email) @@ -616,7 +629,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { Assert.assertTrue(existedKeyExpiration.isBefore(Instant.now())) - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() fillInAllFields(contact.email) @@ -743,7 +756,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { lastSegment, true ) -> { return MockResponse() - .setResponseCode(404) + .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) .setBody(TestGeneralUtil.readResourceAsString("2.txt")) } @@ -751,13 +764,13 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { lastSegment, true ) -> { return MockResponse() - .setResponseCode(200) + .setResponseCode(HttpURLConnection.HTTP_OK) .setBody(TestGeneralUtil.readResourceAsString("3.txt")) } "95FC072E853C9C333C68EDD34B9CA2FBCA5B5FE7".equals(lastSegment, true) -> { return MockResponse() - .setResponseCode(200) + .setResponseCode(HttpURLConnection.HTTP_OK) .setBody( TestGeneralUtil.readFileFromAssetsAsString( "pgp/expired_fixed@flowcrypt.test_not_expired_pub.asc" @@ -767,7 +780,7 @@ class CreateMessageActivityTestTest : BaseCreateMessageActivityTest() { } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestPassInRamTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestPassInRamTest.kt index de3feb252c..62460db1e7 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestPassInRamTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityTestPassInRamTest.kt @@ -34,6 +34,7 @@ import org.junit.Test import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.RunWith +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -58,7 +59,7 @@ class CreateMessageActivityTestPassInRamTest : BaseCreateMessageActivityTest() { @Test fun testShowingNeedPassphraseDialog() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() fillInAllFields(TestConstants.RECIPIENT_WITH_PUBLIC_KEY_ON_ATTESTER) @@ -102,13 +103,13 @@ class CreateMessageActivityTestPassInRamTest : BaseCreateMessageActivityTest() { lastSegment, true ) -> { return MockResponse() - .setResponseCode(200) + .setResponseCode(HttpURLConnection.HTTP_OK) .setBody(TestGeneralUtil.readResourceAsString("3.txt")) } } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityWkdTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityWkdTest.kt new file mode 100644 index 0000000000..4644cad25c --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CreateMessageActivityWkdTest.kt @@ -0,0 +1,249 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui.activity + +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +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.matchers.CustomMatchers.Companion.withChipsBackgroundColor +import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.FlowCryptMockWebServerRule +import com.flowcrypt.email.rules.LazyActivityScenarioRule +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import com.flowcrypt.email.ui.activity.base.BaseCreateMessageActivityTest +import com.flowcrypt.email.ui.widget.CustomChipSpanChipCreator +import com.flowcrypt.email.util.TestGeneralUtil +import com.flowcrypt.email.util.UIUtil +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import okio.Buffer +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 java.net.HttpURLConnection + +/** + * To be able to test WKD need to execute the following: + * adb root + * adb shell "echo 1 > /proc/sys/net/ipv4/ip_forward" + * adb shell "iptables -t nat -A PREROUTING -s 127.0.0.1 -p tcp --dport 443 -j REDIRECT --to 1212" + * adb shell "iptables -t nat -A OUTPUT -s 127.0.0.1 -p tcp --dport 443 -j REDIRECT --to 1212" + * + * @author Denis Bondarenko + * Date: 8/12/21 + * Time: 10:58 AM + * E-mail: DenBond7@gmail.com + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +class CreateMessageActivityWkdTest : BaseCreateMessageActivityTest() { + override val activeActivityRule: LazyActivityScenarioRule? = null + override val activityScenarioRule = activityScenarioRule(intent = intent) + override val activityScenario: ActivityScenario<*>? + get() = activityScenarioRule.scenario + + private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule() + + @get:Rule + val testNameRule = TestName() + + val mockWebServerRule = + FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + if ("/.well-known/openpgpkey/.*/policy".toRegex().matches(request.path ?: "")) { + return handleAdvancedPolicyRequest() + } + + if ("/.well-known/openpgpkey/.*/hu/.*\\?l=.*".toRegex().matches(request.path ?: "")) { + return handleAdvancedWkdRequest() + } + + if (request.path == "/.well-known/openpgpkey/policy") { + return handleDirectPolicyRequest() + } + + if ("/.well-known/openpgpkey/hu/.*\\?l=.*".toRegex().matches(request.path ?: "")) { + return handleDirectWkdRequest() + } + + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + }) + + @get:Rule + var ruleChain: TestRule = RuleChain + .outerRule(ClearAppSettingsRule()) + .around(mockWebServerRule) + .around(addAccountToDatabaseRule) + .around(addPrivateKeyToDatabaseRule) + .around(RetryRule.DEFAULT) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + @Test + fun testWkdAdvancedNoResult() { + check( + recipient = "wkd_advanced_no_result@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_NOT_EXISTS + ) + } + + @Test + fun testWkdAdvancedPub() { + check( + recipient = "wkd_advanced_pub@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_EXISTS + ) + } + + @Test + fun testWkdAdvancedSkippedWkdDirectNoPolicyPub() { + check( + recipient = "wkd_direct_no_policy@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_NOT_EXISTS + ) + } + + @Test + fun testWkdAdvancedSkippedWkdDirectNoResult() { + check( + recipient = "wkd_direct_no_result@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_NOT_EXISTS + ) + } + + @Test + fun testWkdAdvancedSkippedWkdDirectPub() { + check( + recipient = "wkd_direct_pub@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_EXISTS + ) + } + + @Test + fun testWkdAdvancedTimeOutWkdDirectAvailable() { + check( + recipient = "wkd_direct_pub@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_EXISTS + ) + } + + @Test + fun testWkdAdvancedTimeOutWkdDirectTimeOut() { + check( + recipient = "wkd_advanced_direct_timeout@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_NOT_EXISTS + ) + } + + @Test + fun testWkdPrv() { + check( + recipient = "wkd_prv@localhost", + colorResourcesId = CustomChipSpanChipCreator.CHIP_COLOR_RES_ID_PGP_NOT_EXISTS + ) + } + + private fun handleAdvancedPolicyRequest(): MockResponse { + return when (testNameRule.methodName) { + "testWkdAdvancedNoResult", + "testWkdAdvancedPub", + "testWkdPrv" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + } + + "testWkdAdvancedTimeOutWkdDirectAvailable", + "testWkdAdvancedTimeOutWkdDirectTimeOut" -> { + Thread.sleep(5000) + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + + private fun handleAdvancedWkdRequest(): MockResponse { + return when (testNameRule.methodName) { + "testWkdAdvancedNoResult" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + "testWkdAdvancedPub" -> { + genSuccessMockResponseWithKey("pgp/keys/wkd_advanced_pub@localhost_pub.asc") + } + + "testWkdPrv" -> { + genSuccessMockResponseWithKey("pgp/keys/wkd_prv@localhost_sec.asc") + } + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + + private fun handleDirectPolicyRequest(): MockResponse { + return when (testNameRule.methodName) { + "testWkdAdvancedSkippedWkdDirectNoResult", + "testWkdAdvancedSkippedWkdDirectPub", + "testWkdAdvancedTimeOutWkdDirectAvailable" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + } + + "testWkdAdvancedTimeOutWkdDirectTimeOut" -> { + Thread.sleep(5000) + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + + private fun handleDirectWkdRequest(): MockResponse { + return when (testNameRule.methodName) { + "testWkdAdvancedSkippedWkdDirectNoResult" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + "testWkdAdvancedSkippedWkdDirectPub", + "testWkdAdvancedTimeOutWkdDirectAvailable" -> { + genSuccessMockResponseWithKey("pgp/keys/wkd_direct_pub@localhost_pub.asc") + } + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + + private fun genSuccessMockResponseWithKey(keyPath: String) = MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(Buffer().write(TestGeneralUtil.readFileFromAssetsAsByteArray(keyPath))) + + private fun check(recipient: String, colorResourcesId: Int) { + fillInAllFields(recipient) + onView(withId(R.id.editTextRecipientTo)) + .check( + matches( + withChipsBackgroundColor( + chipText = recipient, + backgroundColor = UIUtil.getColor( + context = getTargetContext(), + colorResourcesId = colorResourcesId + ) + ) + ) + ) + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchForDomainTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchForDomainTest.kt index a576ae250d..6b763cdbd3 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchForDomainTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchForDomainTest.kt @@ -25,6 +25,7 @@ import com.flowcrypt.email.rules.ClearAppSettingsRule import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.util.AccountDaoManager +import org.hamcrest.CoreMatchers.not import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -84,6 +85,10 @@ class ImportPgpContactActivityDisallowAttesterSearchForDomainTest : BaseTest() { onView(withId(R.id.iBSearchKey)) .check(matches(isDisplayed())) .perform(click()) + + onView(withId(R.id.layoutProgress)) + .check(matches(not((isDisplayed())))) + isToastDisplayed(getResString(R.string.supported_public_key_not_found)) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchTest.kt index 1635596ed8..506b34e10f 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityDisallowAttesterSearchTest.kt @@ -25,6 +25,7 @@ import com.flowcrypt.email.rules.ClearAppSettingsRule import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.util.AccountDaoManager +import org.hamcrest.CoreMatchers import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -84,6 +85,10 @@ class ImportPgpContactActivityDisallowAttesterSearchTest : BaseTest() { onView(withId(R.id.iBSearchKey)) .check(matches(isDisplayed())) .perform(click()) + + onView(withId(R.id.layoutProgress)) + .check(matches(CoreMatchers.not((isDisplayed())))) + isToastDisplayed(getResString(R.string.supported_public_key_not_found)) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityTest.kt index e9a08d7310..8ea93cbe8c 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivityTest.kt @@ -44,6 +44,7 @@ import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.hasItem +import org.hamcrest.CoreMatchers.not import org.junit.AfterClass import org.junit.BeforeClass import org.junit.ClassRule @@ -53,6 +54,7 @@ import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.File +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -124,6 +126,9 @@ class ImportPgpContactActivityTest : BaseTest() { onView(withId(R.id.iBSearchKey)) .check(matches(isDisplayed())) .perform(click()) + + onView(withId(R.id.layoutProgress)) + .check(matches(not((isDisplayed())))) //due to realization of MockWebServer I can't produce the same response. isToastDisplayed("API error: code = 404, message = ") } @@ -186,13 +191,13 @@ class ImportPgpContactActivityTest : BaseTest() { lastSegment, true ) -> { return MockResponse() - .setResponseCode(200) + .setResponseCode(HttpURLConnection.HTTP_OK) .setBody(TestGeneralUtil.readResourceAsString("3.txt")) } } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPrivateKeyActivityNoPubOrgRulesTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPrivateKeyActivityNoPubOrgRulesTest.kt index 9007800bdc..0bcee69b89 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPrivateKeyActivityNoPubOrgRulesTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ImportPrivateKeyActivityNoPubOrgRulesTest.kt @@ -42,6 +42,7 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.ByteArrayInputStream import java.io.InputStreamReader +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -131,12 +132,13 @@ class ImportPrivateKeyActivityNoPubOrgRulesTest : BaseTest() { ), InitialLegacySubmitResponse::class.java ) - return MockResponse().setResponseCode(200).setBody(gson.toJson(model)) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(model)) } } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ShareIntentsTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ShareIntentsTest.kt index d349c208dd..db39a9a33b 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ShareIntentsTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ShareIntentsTest.kt @@ -46,6 +46,7 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.File import java.io.UnsupportedEncodingException +import java.net.HttpURLConnection import java.net.URLDecoder import java.nio.charset.StandardCharsets import java.util.ArrayList @@ -451,18 +452,18 @@ class ShareIntentsTest : BaseTest() { when { TestConstants.RECIPIENT_WITHOUT_PUBLIC_KEY_ON_ATTESTER.equals(lastSegment, true) -> { - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) .setBody(TestGeneralUtil.readResourceAsString("2.txt")) } TestConstants.RECIPIENT_WITH_PUBLIC_KEY_ON_ATTESTER.equals(lastSegment, true) -> { - return MockResponse().setResponseCode(200) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(TestGeneralUtil.readResourceAsString("3.txt")) } } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/base/BaseCreateMessageActivityTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/base/BaseCreateMessageActivityTest.kt index 6a230a686e..da22f11d00 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/base/BaseCreateMessageActivityTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/base/BaseCreateMessageActivityTest.kt @@ -17,6 +17,7 @@ import com.flowcrypt.email.R import com.flowcrypt.email.base.BaseTest import com.flowcrypt.email.model.MessageEncryptionType import com.flowcrypt.email.rules.AddAccountToDatabaseRule +import com.flowcrypt.email.rules.LazyActivityScenarioRule import com.flowcrypt.email.rules.lazyActivityScenarioRule import com.flowcrypt.email.ui.activity.CreateMessageActivity @@ -28,10 +29,10 @@ import com.flowcrypt.email.ui.activity.CreateMessageActivity */ abstract class BaseCreateMessageActivityTest : BaseTest() { override val useIntents: Boolean = true - override val activeActivityRule = - lazyActivityScenarioRule(launchActivity = false) + override val activeActivityRule: LazyActivityScenarioRule? = + lazyActivityScenarioRule(launchActivity = false) override val activityScenario: ActivityScenario<*>? - get() = activeActivityRule.scenario + get() = activeActivityRule?.scenario protected open val addAccountToDatabaseRule = AddAccountToDatabaseRule() diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/AddNewAccountActivityEnterpriseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/AddNewAccountActivityEnterpriseTest.kt index 31cfcff3fe..7cc539a9e5 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/AddNewAccountActivityEnterpriseTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/AddNewAccountActivityEnterpriseTest.kt @@ -42,6 +42,7 @@ import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.InputStreamReader +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -88,15 +89,18 @@ class AddNewAccountActivityEnterpriseTest : BaseSignActivityTest() { if (request.path.equals("/account/login")) { when (model.account) { - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) + EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode( + HttpURLConnection.HTTP_OK + ) .setBody(gson.toJson(LoginResponse(null, isVerified = true))) } } if (request.path.equals("/account/get")) { when (model.account) { - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson( + EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody( + gson.toJson( DomainOrgRulesResponse( apiError = null, orgRules = OrgRules( @@ -114,7 +118,7 @@ class AddNewAccountActivityEnterpriseTest : BaseSignActivityTest() { } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/CreatePrivateKeyActivityEnterpriseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/CreatePrivateKeyActivityEnterpriseTest.kt index 89f2594741..5df0cb61b7 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/CreatePrivateKeyActivityEnterpriseTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/CreatePrivateKeyActivityEnterpriseTest.kt @@ -42,6 +42,7 @@ import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.InputStreamReader +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -114,12 +115,14 @@ class CreatePrivateKeyActivityEnterpriseTest : BasePassphraseActivityTest() { if (request.path.equals("/initial/legacy_submit")) { when (model.email) { - EMAIL_ENFORCE_ATTESTER_SUBMIT -> return MockResponse().setResponseCode(200) + EMAIL_ENFORCE_ATTESTER_SUBMIT -> return MockResponse().setResponseCode( + HttpURLConnection.HTTP_OK + ) .setBody(gson.toJson(SUBMIT_API_ERROR_RESPONSE)) } } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt index 8e274e867e..df1334e2b3 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt @@ -52,6 +52,7 @@ import org.junit.rules.TestName import org.junit.rules.TestRule import org.junit.runner.RunWith import java.io.InputStreamReader +import java.net.HttpURLConnection /** * @author Denis Bondarenko @@ -103,7 +104,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { return handleGetDomainRulesAPI(model, gson) } - return MockResponse().setResponseCode(404) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } }) @@ -322,11 +323,11 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { while (System.currentTimeMillis() - initialTimeMillis <= delayInMilliseconds) { Thread.sleep(100) } - MockResponse().setResponseCode(404) + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } else { when { testNameRule.methodName == "testFesServerUpHasConnectionHttpCode404" -> { - MockResponse().setResponseCode(404) + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } testNameRule.methodName == "testFesServerUpHasConnectionHttpCodeNotSuccess" -> { @@ -334,12 +335,12 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { } testNameRule.methodName == "testFesServerUpNotEnterpriseServer" -> { - MockResponse().setResponseCode(200) + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "hello"))) } else -> { - MockResponse().setResponseCode(200) + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(FES_SUCCESS_RESPONSE)) } } @@ -348,7 +349,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { private fun handleClientConfigurationAPI(gson: Gson): MockResponse { return MockResponse().setResponseCode( - if (testNameRule.methodName == "testFesServerUpGetClientConfigurationFailed") 403 else 200 + if (testNameRule.methodName == "testFesServerUpGetClientConfigurationFailed") 403 else HttpURLConnection.HTTP_OK ).setBody( gson.toJson( ClientConfigurationResponse( @@ -363,21 +364,22 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { private fun handleEkmAPI(request: RecordedRequest, gson: Gson): MockResponse? { if (request.path.equals("/ekm/error/v1/keys/private")) { - return MockResponse().setResponseCode(200) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(EKM_ERROR_RESPONSE)) } if (request.path.equals("/ekm/empty/v1/keys/private")) { - return MockResponse().setResponseCode(200) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(EkmPrivateKeysResponse(privateKeys = emptyList()))) } if (request.path.equals("/ekm/v1/keys/private")) { - return MockResponse().setResponseCode(200).setBody(gson.toJson(EKM_FES_RESPONSE)) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(EKM_FES_RESPONSE)) } if (request.path.equals("/ekm/not_fully_decrypted_key/v1/keys/private")) { - return MockResponse().setResponseCode(200) + return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody( gson.toJson( EkmPrivateKeysResponse( @@ -398,7 +400,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { private fun handleGetDomainRulesAPI(model: LoginModel, gson: Gson): MockResponse { when (model.account) { - EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) + EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(DOMAIN_ORG_RULES_ERROR_RESPONSE)) EMAIL_WITH_NO_PRV_CREATE_RULE -> return successMockResponseForOrgRules( @@ -490,31 +492,31 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { ) ) - else -> return MockResponse().setResponseCode(404) + else -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } } private fun handleLoginAPI(model: LoginModel, gson: Gson): MockResponse { when (model.account) { - EMAIL_LOGIN_ERROR -> return MockResponse().setResponseCode(200) + EMAIL_LOGIN_ERROR -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(LOGIN_API_ERROR_RESPONSE)) - EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(200) + EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(LoginResponse(null, isVerified = false))) - EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) + EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(LoginResponse(null, isVerified = true))) - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) + EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(LoginResponse(null, isVerified = true))) - else -> return MockResponse().setResponseCode(200) + else -> return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(LoginResponse(null, isVerified = true))) } } private fun successMockResponseForOrgRules(gson: Gson, orgRules: OrgRules) = - MockResponse().setResponseCode(200) + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody( gson.toJson( DomainOrgRulesResponse( diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchForDomainTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchForDomainTest.kt index fa66ba87b9..633fb61ac7 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchForDomainTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchForDomainTest.kt @@ -75,7 +75,7 @@ class CreateMessageFragmentDisallowAttesterSearchForDomainTest : BaseCreateMessa @Test fun testCanLookupThisRecipientOnAttester() { - activeActivityRule.launch(intent) + activeActivityRule?.launch(intent) registerAllIdlingResources() val recipient = "user@$DISALLOWED_DOMAIN" diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchTest.kt index 565a8cdc88..855d213d26 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragmentDisallowAttesterSearchTest.kt @@ -5,9 +5,11 @@ package com.flowcrypt.email.ui.activity.fragment +import androidx.test.core.app.ActivityScenario import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches 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 @@ -19,8 +21,10 @@ 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.LazyActivityScenarioRule import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule +import com.flowcrypt.email.ui.activity.CreateMessageActivity import com.flowcrypt.email.ui.activity.base.BaseCreateMessageActivityTest import com.flowcrypt.email.ui.widget.CustomChipSpanChipCreator import com.flowcrypt.email.util.AccountDaoManager @@ -40,6 +44,10 @@ import org.junit.runner.RunWith @MediumTest @RunWith(AndroidJUnit4::class) class CreateMessageFragmentDisallowAttesterSearchTest : BaseCreateMessageActivityTest() { + override val activeActivityRule: LazyActivityScenarioRule? = null + override val activityScenarioRule = activityScenarioRule(intent = intent) + override val activityScenario: ActivityScenario<*>? + get() = activityScenarioRule.scenario private val userWithOrgRules = AccountDaoManager.getUserWithOrgRules( OrgRules( @@ -70,15 +78,12 @@ class CreateMessageFragmentDisallowAttesterSearchTest : BaseCreateMessageActivit .around(addAccountToDatabaseRule) .around(addPrivateKeyToDatabaseRule) .around(RetryRule.DEFAULT) - .around(activeActivityRule) + .around(activityScenarioRule) .around(ScreenshotTestRule()) @Test fun testDisallowLookupOnAttester() { - activeActivityRule.launch(intent) - registerAllIdlingResources() - - val recipient = getResString(R.string.support_email) + val recipient = "recipient@example.test" fillInAllFields(recipient) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/AccountDaoManager.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/AccountDaoManager.kt index 48791c1d8a..28d3d78323 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/AccountDaoManager.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/AccountDaoManager.kt @@ -46,7 +46,7 @@ class AccountDaoManager { .copy(clientConfiguration = orgRules) } - private fun getUserFromBaseSettings(user: String): AccountEntity { + fun getUserFromBaseSettings(user: String): AccountEntity { return getDefaultAccountDao().copy( email = user, smtpUsername = user, diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/EmailUtil.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/EmailUtil.kt index 48e079d542..47d4c23f88 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/EmailUtil.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/EmailUtil.kt @@ -145,10 +145,10 @@ class EmailUtil { * * @return The domain of some email. */ - fun getDomain(email: String): String { + fun getDomain(email: String?): String { return when { TextUtils.isEmpty(email) -> "" - email.contains("@") -> email.substring(email.indexOf('@') + 1) + email?.contains("@") == true -> email.substring(email.indexOf('@') + 1) else -> "" } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt index d5a18473c1..0d2a906d74 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt @@ -86,7 +86,7 @@ interface ApiRepository : BaseApiRepository { * @param identData A key id or the user email or a fingerprint. * @param orgRules Contains client configurations. */ - suspend fun getPub( + suspend fun pubLookup( requestCode: Long = 0L, context: Context, identData: String, diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt index 50ab9e1ef7..0535328d1f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt @@ -31,6 +31,8 @@ import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query +import retrofit2.http.Streaming import retrofit2.http.Url /** @@ -93,7 +95,47 @@ interface ApiService { * @return [<] */ @GET("pub/{keyIdOrEmailOrFingerprint}") - suspend fun getPub(@Path("keyIdOrEmailOrFingerprint") keyIdOrEmailOrFingerprint: String): Response + suspend fun getPubFromAttester(@Path("keyIdOrEmailOrFingerprint") keyIdOrEmailOrFingerprint: String): Response + + /** + * Get RAW pub key(s) using an advanced WKD url + */ + @Streaming + @GET("https://{advancedHost}/.well-known/openpgpkey/{directDomain}/hu/{hu}") + suspend fun getPubFromWkdAdvanced( + @Path("advancedHost") advancedHost: String, + @Path("directDomain") directDomain: String, + @Path("hu") hu: String, + @Query("l") user: String + ): Response + + /** + * Check that 'policy' file is available for the advanced method + */ + @Streaming + @GET("https://{advancedHost}/.well-known/openpgpkey/{directDomain}/policy") + suspend fun checkPolicyForWkdAdvanced( + @Path("advancedHost") advancedHost: String, + @Path("directDomain") directDomain: String + ): Response + + /** + * Get RAW pub key(s) using a direct WKD url + */ + @Streaming + @GET("https://{directHost}/.well-known/openpgpkey/hu/{hu}") + suspend fun getPubFromWkdDirect( + @Path("directHost") directHost: String, + @Path("hu") hu: String, + @Query("l") user: String + ): Response + + /** + * Check that 'policy' file is available for the direct method + */ + @Streaming + @GET("https://{directHost}/.well-known/openpgpkey/policy") + suspend fun checkPolicyForWkdDirect(@Path("directHost") directHost: String): Response /** * This method calls the API "https://flowcrypt.com/api/account/login" diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt index 965664547a..abe49ea8dc 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt @@ -20,12 +20,21 @@ import com.flowcrypt.email.api.retrofit.response.base.ApiResponse import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.api.retrofit.response.oauth2.MicrosoftOAuth2TokenResponse +import com.flowcrypt.email.api.wkd.WkdClient +import com.flowcrypt.email.extensions.kotlin.isValidEmail +import com.flowcrypt.email.extensions.kotlin.isValidLocalhostEmail +import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.toPgpKeyDetails import com.google.gson.JsonObject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.OkHttpClient +import okhttp3.ResponseBody.Companion.toResponseBody +import org.pgpainless.algorithm.EncryptionPurpose +import org.pgpainless.key.info.KeyRingInfo +import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.net.HttpURLConnection import java.util.concurrent.TimeUnit /** @@ -93,17 +102,56 @@ class FlowcryptApiRepository : ApiRepository { getResult { apiService.postTestWelcomeSuspend(model) } } - //todo-denbond7 need to ask Tom to improve https://flowcrypt.com/attester/pub to use the common - // API response ([ApiResponse]) - override suspend fun getPub( + override suspend fun pubLookup( requestCode: Long, context: Context, identData: String, orgRules: OrgRules? ): Result = withContext(Dispatchers.IO) { - val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) - if (identData.contains('@')) { + val resultWrapperFun = fun(result: Result): Result { + return when (result.status) { + Result.Status.SUCCESS -> Result.success( + requestCode = requestCode, + data = PubResponse(null, result.data) + ) + + Result.Status.ERROR -> Result.error( + requestCode = requestCode, + data = PubResponse(null, null) + ) + + Result.Status.EXCEPTION -> Result.exception( + requestCode = requestCode, + throwable = result.exception ?: Exception(context.getString(R.string.unknown_error)) + ) + + Result.Status.LOADING -> Result.loading(requestCode = requestCode) + + Result.Status.NONE -> Result.none() + } + } + + if (identData.isValidEmail() || identData.isValidLocalhostEmail()) { + val wkdResult = getResult(requestCode = requestCode) { + val pgpPublicKeyRingCollection = WkdClient.lookupEmail(context, identData) + + //For now, we just peak at the first matching key. It should be improved inthe future. + // See more details here https://github.com/FlowCrypt/flowcrypt-android/issues/480 + val firstMatchingKey = pgpPublicKeyRingCollection?.firstOrNull { + KeyRingInfo(it) + .getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS) + .isNotEmpty() + } + firstMatchingKey?.toPgpKeyDetails()?.publicKey?.let { armoredPubKey -> + Response.success(armoredPubKey) + } ?: Response.error(HttpURLConnection.HTTP_NOT_FOUND, "Not found".toResponseBody()) + } + + if (wkdResult.status == Result.Status.SUCCESS && wkdResult.data?.isNotEmpty() == true) { + return@withContext resultWrapperFun(wkdResult) + } + if (orgRules?.canLookupThisRecipientOnAttester(identData) == false) { return@withContext Result.success( requestCode = requestCode, @@ -117,27 +165,9 @@ class FlowcryptApiRepository : ApiRepository { ) } - val result = getResult(requestCode = requestCode) { apiService.getPub(identData) } - return@withContext when (result.status) { - Result.Status.SUCCESS -> Result.success( - requestCode = requestCode, - data = PubResponse(null, result.data) - ) - - Result.Status.ERROR -> Result.error( - requestCode = requestCode, - data = PubResponse(null, null) - ) - - Result.Status.EXCEPTION -> Result.exception( - requestCode = requestCode, throwable = result.exception - ?: Exception() - ) - - Result.Status.LOADING -> Result.loading(requestCode = requestCode) - - Result.Status.NONE -> Result.none() - } + val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) + val result = getResult(requestCode = requestCode) { apiService.getPubFromAttester(identData) } + return@withContext resultWrapperFun(result) } override suspend fun getMicrosoftOAuth2Token( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/wkd/WkdClient.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/wkd/WkdClient.kt index e3753453dc..031a017494 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/wkd/WkdClient.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/wkd/WkdClient.kt @@ -6,76 +6,90 @@ package com.flowcrypt.email.api.wkd +import android.content.Context +import com.flowcrypt.email.api.retrofit.ApiHelper +import com.flowcrypt.email.api.retrofit.ApiService import com.flowcrypt.email.extensions.kotlin.isValidEmail import com.flowcrypt.email.extensions.kotlin.isValidLocalhostEmail import com.flowcrypt.email.util.BetterInternetAddress +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import okhttp3.Request +import okhttp3.ResponseBody import org.apache.commons.codec.binary.ZBase32 import org.apache.commons.codec.digest.DigestUtils import org.bouncycastle.openpgp.PGPPublicKeyRingCollection -import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRingCollection +import org.pgpainless.PGPainless +import retrofit2.Response +import retrofit2.Retrofit import java.io.InterruptedIOException -import java.net.URLEncoder import java.net.UnknownHostException import java.util.Locale import java.util.concurrent.TimeUnit object WkdClient { - const val DEFAULT_REQUEST_TIMEOUT = 4L + private const val DEFAULT_REQUEST_TIMEOUT = 4000L - fun lookupEmail( - email: String, - wkdPort: Int? = null, - useHttps: Boolean = true, - timeout: Long = DEFAULT_REQUEST_TIMEOUT - ): PGPPublicKeyRingCollection? { - val keys = rawLookupEmail(email, wkdPort, useHttps, timeout) ?: return null - val lowerCaseEmail = email.toLowerCase(Locale.ROOT) - val matchingKeys = keys.keyRings.asSequence().filter { - for (userId in it.publicKey.userIDs) { - try { - val parsed = BetterInternetAddress(userId) - if (parsed.emailAddress.toLowerCase(Locale.ROOT) == lowerCaseEmail) return@filter true - } catch (ex: Exception) { - // ignore + suspend fun lookupEmail(context: Context, email: String): PGPPublicKeyRingCollection? = + withContext(Dispatchers.IO) { + val pgpPublicKeyRingCollection = rawLookupEmail(context, email) + val lowerCaseEmail = email.toLowerCase(Locale.ROOT) + val matchingKeys = pgpPublicKeyRingCollection?.keyRings?.asSequence()?.filter { + for (userId in it.publicKey.userIDs) { + try { + val parsed = BetterInternetAddress(userId) + if (parsed.emailAddress.toLowerCase(Locale.ROOT) == lowerCaseEmail) return@filter true + } catch (ex: Exception) { + ex.printStackTrace() + } } - } - false - }.toList() - return if (matchingKeys.isNotEmpty()) PGPPublicKeyRingCollection(matchingKeys) else null - } + return@filter false + }?.toList() + return@withContext if (matchingKeys?.isNotEmpty() == true) { + PGPPublicKeyRingCollection(matchingKeys) + } else null + } - @Suppress("private") - fun rawLookupEmail( + private suspend fun rawLookupEmail( + context: Context, email: String, - wkdPort: Int? = null, - useHttps: Boolean = true, - timeout: Long = DEFAULT_REQUEST_TIMEOUT - ): PGPPublicKeyRingCollection? { + wkdPort: Int? = null + ): PGPPublicKeyRingCollection? = withContext(Dispatchers.IO) { if (!email.isValidEmail() && !email.isValidLocalhostEmail()) { throw IllegalArgumentException("Invalid email address") } + val parts = email.split('@') val user = parts[0].toLowerCase(Locale.ROOT) - val hu = ZBase32().encodeAsString(DigestUtils.sha1(user.toByteArray())) val directDomain = parts[1].toLowerCase(Locale.ROOT) + val advancedDomainPrefix = if (directDomain == "localhost") "" else "openpgpkey." + val hu = ZBase32().encodeAsString(DigestUtils.sha1(user.toByteArray())) val directHost = if (wkdPort == null) directDomain else "${directDomain}:${wkdPort}" val advancedHost = "$advancedDomainPrefix$directHost" - val protocol = if (useHttps) "https" else "http" - val advancedUrl = "$protocol://${advancedHost}/.well-known/openpgpkey/${directDomain}" - val directUrl = "$protocol://${directHost}/.well-known/openpgpkey" - val userPart = "hu/$hu?l=${URLEncoder.encode(user, "UTF-8")}" + try { - val result = urlLookup(advancedUrl, userPart, timeout) + val result = urlLookup( + context = context, + advancedHost = advancedHost, + directDomain = directDomain, + hu = hu, + user = user + ) // Do not retry "direct" if "advanced" had a policy file - if (result.hasPolicy) return result.keys + if (result.hasPolicy) return@withContext result.keys } catch (ex: Exception) { - // ignore + ex.printStackTrace() } - return try { - urlLookup(directUrl, userPart, timeout).keys + + return@withContext try { + val result = urlLookup( + context = context, + directDomain = directDomain, + hu = hu, + user = user + ) + result.keys } catch (ex: UnknownHostException) { null } catch (ex: InterruptedIOException) { @@ -83,22 +97,53 @@ object WkdClient { } } + private suspend fun urlLookup( + context: Context, + advancedHost: String? = null, + directDomain: String, + hu: String, + user: String + ): UrlLookupResult = withContext(Dispatchers.IO) { + val apiService = prepareApiService(context, directDomain) + val wkdResponse: Response = if (advancedHost != null) { + val checkPolicyResponse = apiService.checkPolicyForWkdAdvanced(advancedHost, directDomain) + if (!checkPolicyResponse.isSuccessful) return@withContext UrlLookupResult() + apiService.getPubFromWkdAdvanced(advancedHost, directDomain, hu, user) + } else { + val checkPolicyResponse = apiService.checkPolicyForWkdDirect(directDomain) + if (!checkPolicyResponse.isSuccessful) return@withContext UrlLookupResult() + apiService.getPubFromWkdDirect(directDomain, hu, user) + } + + val incomingBytes = wkdResponse.body()?.byteStream() + + if (!wkdResponse.isSuccessful || incomingBytes == null) { + return@withContext UrlLookupResult(true) + } else { + val keys = PGPainless.readKeyRing().publicKeyRingCollection(incomingBytes) + return@withContext UrlLookupResult(true, keys) + } + } + + private fun prepareApiService(context: Context, directDomain: String): ApiService { + val okHttpClient = OkHttpClient.Builder() + .connectTimeout(DEFAULT_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS) + .writeTimeout(DEFAULT_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(DEFAULT_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS) + .apply { + ApiHelper.configureOkHttpClientForDebuggingIfAllowed(context, this) + }.build() + + val retrofit = Retrofit.Builder() + .baseUrl("https://$directDomain") + .client(okHttpClient) + .build() + + return retrofit.create(ApiService::class.java) + } + private data class UrlLookupResult( val hasPolicy: Boolean = false, val keys: PGPPublicKeyRingCollection? = null ) - - private fun urlLookup(methodUrlBase: String, userPart: String, timeout: Long): UrlLookupResult { - val httpClient = OkHttpClient.Builder().callTimeout(timeout, TimeUnit.SECONDS).build() - val policyRequest = Request.Builder().url("$methodUrlBase/policy").build() - httpClient.newCall(policyRequest).execute().use { policyResponse -> - if (policyResponse.code != 200) return UrlLookupResult() - } - val userRequest = Request.Builder().url("$methodUrlBase/$userPart").build() - httpClient.newCall(userRequest).execute().use { userResponse -> - if (userResponse.code != 200 || userResponse.body == null) return UrlLookupResult(true) - val keys = JcaPGPPublicKeyRingCollection(userResponse.body!!.byteStream()) - return UrlLookupResult(true, keys) - } - } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/AccountKeysInfoViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/AccountPublicKeyServersViewModel.kt similarity index 95% rename from FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/AccountKeysInfoViewModel.kt rename to FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/AccountPublicKeyServersViewModel.kt index 04ce9961e2..f9078f201e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/AccountKeysInfoViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/AccountPublicKeyServersViewModel.kt @@ -30,15 +30,14 @@ import java.util.* /** * This [ViewModel] does job of receiving information about an array of public - * keys from "https://flowcrypt.com/attester/lookup/email". + * keys from FlowCrypt Attester or WKD. * * @author Denis Bondarenko * Date: 13.11.2017 * Time: 15:13 * E-mail: DenBond7@gmail.com */ - -class AccountKeysInfoViewModel(application: Application) : AccountViewModel(application) { +class AccountPublicKeyServersViewModel(application: Application) : AccountViewModel(application) { private val apiRepository: ApiRepository = FlowcryptApiRepository() val accountKeysInfoLiveData = MediatorLiveData>>() private val initLiveData = Transformations @@ -104,7 +103,7 @@ class AccountKeysInfoViewModel(application: Application) : AccountViewModel(appl } for (email in emails) { - val pubResponseResult = apiRepository.getPub( + val pubResponseResult = apiRepository.pubLookup( context = getApplication(), identData = email, orgRules = accountEntity.clientConfiguration diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ContactsViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ContactsViewModel.kt index ae91343045..9128d2370e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ContactsViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ContactsViewModel.kt @@ -30,6 +30,10 @@ import java.io.IOException import java.util.* /** + * This is used in the message compose/reply view when recipient public keys need to be retrieved, + * either from local storage or from remote servers eg Attester or WKD, based on client + * configuration. + * * @author Denis Bondarenko * Date: 4/7/20 * Time: 11:19 AM @@ -63,7 +67,7 @@ class ContactsViewModel(application: Application) : AccountViewModel(application val contactsCcLiveData: MutableLiveData>> = MutableLiveData() val contactsBccLiveData: MutableLiveData>> = MutableLiveData() - val pubKeysFromAttesterLiveData: MutableLiveData> = MutableLiveData() + val pubKeysFromServerLiveData: MutableLiveData> = MutableLiveData() fun updateContactPgpInfo(pgpContact: PgpContact, pgpContactFromKey: PgpContact) { viewModelScope.launch { @@ -275,9 +279,9 @@ class ContactsViewModel(application: Application) : AccountViewModel(application fun fetchPubKeys(keyIdOrEmail: String, requestCode: Long) { viewModelScope.launch { - pubKeysFromAttesterLiveData.value = Result.loading(requestCode = requestCode) + pubKeysFromServerLiveData.value = Result.loading(requestCode = requestCode) val activeAccount = getActiveAccountSuspend() - pubKeysFromAttesterLiveData.value = apiRepository.getPub( + pubKeysFromServerLiveData.value = apiRepository.pubLookup( requestCode = requestCode, context = getApplication(), identData = keyIdOrEmail, @@ -318,7 +322,7 @@ class ContactsViewModel(application: Application) : AccountViewModel(application ): PgpContact? = withContext(Dispatchers.IO) { try { val activeAccount = getActiveAccountSuspend() - val response = apiRepository.getPub( + val response = apiRepository.pubLookup( context = getApplication(), identData = email ?: fingerprint ?: "", orgRules = activeAccount?.clientConfiguration diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MessagesViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MessagesViewModel.kt index ee7f7e7a7e..838f3437e5 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MessagesViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MessagesViewModel.kt @@ -50,6 +50,7 @@ import kotlinx.coroutines.withContext import java.io.File import java.io.IOException import java.math.BigInteger +import java.net.HttpURLConnection import java.util.* import javax.mail.FetchProfile import javax.mail.Folder @@ -806,7 +807,7 @@ class MessagesViewModel(application: Application) : AccountViewModel(application when (e) { is GoogleJsonResponseException -> { if (localFolder.getFolderType() == FoldersManager.FolderType.INBOX - && e.statusCode == 404 + && e.statusCode == HttpURLConnection.HTTP_NOT_FOUND && e.details.errors.any { it.reason.equals("notFound", true) } ) { //client must perform a full sync diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivity.kt index 36d4f9644c..5c548bd23d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ImportPgpContactActivity.kt @@ -166,7 +166,7 @@ class ImportPgpContactActivity : BaseImportKeyActivity() { } private fun setupContactsViewModel() { - contactsViewModel.pubKeysFromAttesterLiveData.observe(this, Observer { + contactsViewModel.pubKeysFromServerLiveData.observe(this, Observer { if (it.requestCode != fetchPubKeysRequestCode) return@Observer when (it.status) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AttesterSettingsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AttesterSettingsFragment.kt index c19e5e9465..a06824fc43 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AttesterSettingsFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AttesterSettingsFragment.kt @@ -17,7 +17,7 @@ import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.incrementSafely -import com.flowcrypt.email.jetpack.viewmodel.AccountKeysInfoViewModel +import com.flowcrypt.email.jetpack.viewmodel.AccountPublicKeyServersViewModel import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment import com.flowcrypt.email.ui.activity.fragment.base.ListProgressBehaviour import com.flowcrypt.email.ui.adapter.AttesterKeyAdapter @@ -45,7 +45,7 @@ class AttesterSettingsFragment : BaseFragment(), ListProgressBehaviour { override val contentResourceId: Int = R.layout.fragment_attester_settings private var sRL: SwipeRefreshLayout? = null - private val accountKeysInfoViewModel: AccountKeysInfoViewModel by viewModels() + private val accountPublicKeyServersViewModel: AccountPublicKeyServersViewModel by viewModels() private val attesterKeyAdapter: AttesterKeyAdapter = AttesterKeyAdapter() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -60,7 +60,7 @@ class AttesterSettingsFragment : BaseFragment(), ListProgressBehaviour { sRL?.setColorSchemeResources(R.color.colorPrimary, R.color.colorPrimary, R.color.colorPrimary) sRL?.setOnRefreshListener { dismissCurrentSnackBar() - accountKeysInfoViewModel.refreshData() + accountPublicKeyServersViewModel.refreshData() } val rVAttester: RecyclerView? = view.findViewById(R.id.rVAttester) @@ -76,7 +76,7 @@ class AttesterSettingsFragment : BaseFragment(), ListProgressBehaviour { } private fun setupAccountKeysInfoViewModel() { - accountKeysInfoViewModel.accountKeysInfoLiveData.observe(viewLifecycleOwner, { + accountPublicKeyServersViewModel.accountKeysInfoLiveData.observe(viewLifecycleOwner, { it?.let { when (it.status) { Result.Status.LOADING -> { @@ -118,7 +118,7 @@ class AttesterSettingsFragment : BaseFragment(), ListProgressBehaviour { btnName = getString(R.string.retry), duration = Snackbar.LENGTH_LONG ) { - accountKeysInfoViewModel.refreshData() + accountPublicKeyServersViewModel.refreshData() } baseActivity.countingIdlingResource.decrementSafely() diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/BetterEmailAddress.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/BetterEmailAddress.kt index 83264ee9ee..e0284d5c5e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/util/BetterEmailAddress.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/BetterEmailAddress.kt @@ -10,46 +10,12 @@ import com.flowcrypt.email.extensions.kotlin.isValidEmail // https://en.wikipedia.org/wiki/Email_address#Internationalization_examples class BetterInternetAddress(str: String, verifySpecialCharacters: Boolean = true) { - - companion object { - const val alphanum = "\\p{L}\\u0900-\\u097F0-9" - const val validEmail = "(?:[${alphanum}!#\$%&'*+/=?^_`{|}~-]+(?:\\.[${alphanum}!#\$%&'*+/=?^" + - "_`{|}~-]+)*|\"(?:[\\x01-\\x08" + - "\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f" + - "])*\")@(?:(?:[${alphanum}](?:[${alphanum}-]*[${alphanum}])?\\.)+[${alphanum}](?:[" + - "${alphanum}-]*[${alphanum}])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:" + - "25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[${alphanum}-]*[${alphanum}]:(?:[\\x01-\\x08\\x0b" + - "\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])" - private const val validPersonalNameWithEmail = - "([$alphanum\\p{Punct}\\p{Space}]*)<($validEmail)>" - - private val validEmailRegex = validEmail.toRegex() - private val validPersonalNameWithEmailRegex = validPersonalNameWithEmail.toRegex() - // if these appear in the display-name they must be double quoted - private val containsSpecialCharacterRegex = ".*[()<>\\[\\]:;@\\\\,.\"].*".toRegex() - // double quotes at ends only - private val doubleQuotedTextRegex = "\"[^\"]*\"".toRegex() - private val validLocalhostEmailRegex = Regex("([a-zA-z])([a-zA-z0-9])+@localhost") - - fun isValidEmail(email: String): Boolean { - return validEmailRegex.matchEntire(email) != null - } - - fun isValidLocalhostEmail(email: String): Boolean { - return validLocalhostEmailRegex.matchEntire(email) != null - } - - fun areValidEmails(emails: Iterable): Boolean { - return emails.all { it.isValidEmail() } - } - } - val personalName: String? val emailAddress: String init { val personalNameWithEmailMatch = validPersonalNameWithEmailRegex.find(str) - val emailMatch = str.matches(validEmailRegex) + val emailMatch = str.matches(validEmailRegex) || str.matches(validLocalhostEmailRegex) when { personalNameWithEmailMatch != null -> { val group = personalNameWithEmailMatch.groupValues @@ -65,11 +31,49 @@ class BetterInternetAddress(str: String, verifySpecialCharacters: Boolean = true ) } } + emailMatch -> { personalName = null emailAddress = str } + else -> throw IllegalArgumentException("Invalid email $str") } } + + companion object { + private const val ALPHANUM = "\\p{L}\\u0900-\\u097F0-9" + private const val VALID_EMAIL = + "(?:[${ALPHANUM}!#\$%&'*+/=?^_`{|}~-]+(?:\\.[${ALPHANUM}!#\$%&'*+/=?^" + + "_`{|}~-]+)*|\"(?:[\\x01-\\x08" + + "\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f" + + "])*\")@(?:(?:[${ALPHANUM}](?:[${ALPHANUM}-]*[${ALPHANUM}])?\\.)+[${ALPHANUM}](?:[" + + "${ALPHANUM}-]*[${ALPHANUM}])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:" + + "25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[${ALPHANUM}-]*[${ALPHANUM}]:(?:[\\x01-\\x08\\x0b" + + "\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])" + private const val VALID_PERSONAL_NAME_WITH_EMAIL = + "([$ALPHANUM\\p{Punct}\\p{Space}]*)<($VALID_EMAIL)>" + + private val validEmailRegex = VALID_EMAIL.toRegex() + private val validPersonalNameWithEmailRegex = VALID_PERSONAL_NAME_WITH_EMAIL.toRegex() + + // if these appear in the display-name they must be double quoted + private val containsSpecialCharacterRegex = ".*[()<>\\[\\]:;@\\\\,.\"].*".toRegex() + + // double quotes at ends only + private val doubleQuotedTextRegex = "\"[^\"]*\"".toRegex() + private val validLocalhostEmailRegex = Regex("([a-zA-z])([a-zA-z0-9])+@localhost") + + fun isValidEmail(email: String): Boolean { + return validEmailRegex.matchEntire(email) != null + } + + fun isValidLocalhostEmail(email: String): Boolean { + return validLocalhostEmailRegex.matchEntire(email) != null + } + + fun areValidEmails(emails: Iterable): Boolean { + return emails.all { it.isValidEmail() } + } + } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt index a0e41c085a..7a0c942be5 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt @@ -30,6 +30,7 @@ import androidx.preference.PreferenceManager import com.flowcrypt.email.BuildConfig import com.flowcrypt.email.Constants import com.flowcrypt.email.R +import com.flowcrypt.email.api.email.EmailUtil import com.flowcrypt.email.api.retrofit.ApiService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -274,8 +275,8 @@ class GeneralUtil { * @return true if the email has valid format, otherwise false. */ fun isEmailValid(email: CharSequence?): Boolean { - return email?.isNotEmpty() == true && android.util.Patterns.EMAIL_ADDRESS.matcher(email) - .matches() + return email?.isNotEmpty() == true && (android.util.Patterns.EMAIL_ADDRESS.matcher(email) + .matches() || EmailUtil.getDomain(email.toString()).toLowerCase(Locale.ROOT) == "localhost") } /** diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/api/email/WkdClientTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/api/email/WkdClientTest.kt index 6dc7056410..1cc2178384 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/api/email/WkdClientTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/api/email/WkdClientTest.kt @@ -1,61 +1,52 @@ /* - * © 2021-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com * Contributors: - * Ivan Pizhenko + * Ivan Pizhenko + * DenBond7 */ package com.flowcrypt.email.api.email import com.flowcrypt.email.api.wkd.WkdClient -import okhttp3.mockwebserver.Dispatcher -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import okhttp3.mockwebserver.RecordedRequest +import kotlinx.coroutines.runBlocking import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +@RunWith(RobolectricTestRunner::class) class WkdClientTest { @Test - fun existingEmailTest() { - val keys = WkdClient.lookupEmail("human@flowcrypt.com") + fun existingEmailTest() = runBlocking { + val keys = + WkdClient.lookupEmail(RuntimeEnvironment.getApplication(), "human@flowcrypt.com") assertTrue("Key not found", keys != null) assertTrue("There are no keys in the key collection", keys!!.keyRings.hasNext()) } @Test - fun nonExistingEmailTest1() { - val keys = WkdClient.lookupEmail("no.such.email.for.sure@flowcrypt.com") + fun nonExistingEmailTest1() = runBlocking { + val keys = WkdClient.lookupEmail( + RuntimeEnvironment.getApplication(), + "no.such.email.for.sure@flowcrypt.com" + ) assertTrue("Key found for non-existing email", keys == null) } @Test - fun nonExistingEmailTest2() { - val keys = WkdClient.lookupEmail("doesnotexist@google.com") + fun nonExistingEmailTest2() = runBlocking { + val keys = WkdClient.lookupEmail(RuntimeEnvironment.getApplication(), "doesnotexist@google.com") assertTrue("Key found for non-existing email", keys == null) } @Test - fun nonExistingDomainTest() { - val keys = WkdClient.lookupEmail("doesnotexist@thisdomaindoesnotexist.test") + fun nonExistingDomainTest() = runBlocking { + val keys = WkdClient.lookupEmail( + RuntimeEnvironment.getApplication(), + "doesnotexist@thisdomaindoesnotexist.test" + ) assertTrue("Key found for non-existing email", keys == null) } - - @Test - fun requestTimeoutTest() { - val mockWebServer = MockWebServer() - mockWebServer.dispatcher = object: Dispatcher() { - private val sleepTimeout = (WkdClient.DEFAULT_REQUEST_TIMEOUT + 2) * 1000 - override fun dispatch(request: RecordedRequest): MockResponse { - Thread.sleep(sleepTimeout) - return MockResponse().setResponseCode(200) - } - } - mockWebServer.start() - val port = mockWebServer.port - mockWebServer.use { - val keys = WkdClient.lookupEmail(email = "user@localhost", wkdPort = port, useHttps = false) - assertTrue("Key found for non-existing email", keys == null) - } - } } diff --git a/script/ci-install-android-sdk.sh b/script/ci-install-android-sdk.sh index 7f0e96581b..26dc924fa2 100755 --- a/script/ci-install-android-sdk.sh +++ b/script/ci-install-android-sdk.sh @@ -30,13 +30,13 @@ else # Install Android SDK (echo "yes" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null | grep -v = || true) - ( sleep 5; echo "y" ) | (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;30.0.3" "platforms;android-29" > /dev/null | grep -v = || true) + ( sleep 5; echo "y" ) | (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;30.0.3" "platforms;android-30" > /dev/null | grep -v = || true) (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "extras;google;m2repository" | grep -v = || true) (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "platform-tools" | grep -v = || true) (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "emulator" | grep -v = || true) (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;22.0.7026061" | grep -v = || true) (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.10.2.4988404" | grep -v = || true) - (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "system-images;android-29;google_apis;x86_64" | grep -v = || true) + (${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "system-images;android-30;google_apis;x86_64" | grep -v = || true) fi #Uncomment this for debug diff --git a/script/ci-setup-and-run-emulator.sh b/script/ci-setup-and-run-emulator.sh index fe1a9c1b74..0fd1a1326b 100755 --- a/script/ci-setup-and-run-emulator.sh +++ b/script/ci-setup-and-run-emulator.sh @@ -1,9 +1,9 @@ #!/bin/bash "$ANDROID_SDK_ROOT/emulator/emulator" -accel-check -echo -ne '\n' | avdmanager -v create avd --name ci-emulator --package "system-images;android-29;google_apis;x86_64" --device 'pixel_xl' --abi 'google_apis/x86_64' +echo -ne '\n' | avdmanager -v create avd --name ci-emulator --package "system-images;android-30;google_apis;x86_64" --device 'pixel_xl' --abi 'google_apis/x86_64' cat ~/.android/avd/ci-emulator.avd/config.ini echo "hw.ramSize=3064" >> ~/.android/avd/ci-emulator.avd/config.ini cat ~/.android/avd/ci-emulator.avd/config.ini "$ANDROID_SDK_ROOT/emulator/emulator" -list-avds #debug -"$ANDROID_SDK_ROOT/emulator/emulator" -avd ci-emulator -no-window -no-boot-anim -no-audio & \ No newline at end of file +"$ANDROID_SDK_ROOT/emulator/emulator" -avd ci-emulator -no-window -no-boot-anim -no-audio & diff --git a/script/ci-wait-for-emulator.sh b/script/ci-wait-for-emulator.sh index 5e905070ce..60e7d121ed 100755 --- a/script/ci-wait-for-emulator.sh +++ b/script/ci-wait-for-emulator.sh @@ -7,4 +7,12 @@ adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 +################################################################################################### +# to test WKD we need to route all traffic for localhost to localhost:1212 +adb root +adb shell "echo 1 > /proc/sys/net/ipv4/ip_forward" +adb shell "iptables -t nat -A PREROUTING -s 127.0.0.1 -p tcp --dport 443 -j REDIRECT --to 1212" +adb shell "iptables -t nat -A OUTPUT -s 127.0.0.1 -p tcp --dport 443 -j REDIRECT --to 1212" +################################################################################################### + echo "Emulator is ready"