diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/BaseComposeScreenPasswordProtectedDisallowedTermsTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/BaseComposeScreenPasswordProtectedDisallowedTermsTest.kt new file mode 100644 index 0000000000..129f48cc93 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/BaseComposeScreenPasswordProtectedDisallowedTermsTest.kt @@ -0,0 +1,107 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui.gmailapi.passwordprotected + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.pressImeActionButton +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.flowcrypt.email.R +import com.flowcrypt.email.TestConstants +import com.flowcrypt.email.database.entity.AccountEntity +import com.flowcrypt.email.matchers.CustomMatchers.Companion.withTextViewDrawable +import com.flowcrypt.email.matchers.TextViewDrawableMatcher +import com.flowcrypt.email.rules.FlowCryptMockWebServerRule +import com.flowcrypt.email.ui.base.BaseComposeGmailFlow +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Before + +/** + * @author Denys Bondarenko + */ +open class BaseComposeScreenPasswordProtectedDisallowedTermsTest( + accountEntity: AccountEntity = BASE_ACCOUNT_ENTITY +) : BaseComposeGmailFlow(accountEntity) { + override val mockWebServerRule = + FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + return handleCommonAPICalls(request) + } + }) + + @Before + fun setUp() { + onView(withId(R.id.editTextEmailAddress)) + .perform( + typeText(TestConstants.RECIPIENT_WITHOUT_PUBLIC_KEY_ON_ATTESTER), + pressImeActionButton(), + closeSoftKeyboard() + ) + //need to leave focus from 'To' field. move the focus to the next view + onView(withId(R.id.editTextEmailSubject)) + .perform(scrollTo(), click(), replaceText(MATCHING_SUBJECTS.first())) + + onView(withId(R.id.btnSetWebPortalPassword)) + .check(matches(isDisplayed())) + .check(matches(withText(getResString(R.string.tap_to_protect_with_web_portal_password)))) + .check( + matches( + withTextViewDrawable( + resourceId = R.drawable.ic_password_not_protected_white_24, + drawablePosition = TextViewDrawableMatcher.DrawablePosition.LEFT + ) + ) + ).perform(click()) + + onView(withId(R.id.eTPassphrase)) + .perform( + replaceText(PASSWORD), + closeSoftKeyboard() + ) + + onView(withId(R.id.btSetPassword)) + .perform(click()) + } + + companion object { + private const val PASSWORD = "Qwerty1234@" + const val URL = "https://flowcrypt.com" + + val MATCHING_SUBJECTS = listOf( + "[Classification: Data Control: Internal Data Control] Quarter results", + "Conference information [Classification: Data Control: Internal Data Control]", + "[Classification: Data Control: Internal Data Control]", + "aaaa[Classification: Data Control: Internal Data Control]bbb", + "[droid]", + "check -droid- case", + ) + + val NON_MATCHING_SUBJECTS = listOf( + "[1Classification: Data Control: Internal Data Control] Quarter results", + "Conference information [1Classification: Data Control: Internal Data Control]", + "[1Classification: Data Control: Internal Data Control]", + "aaaa[1Classification: Data Control: Internal Data Control]bbb", + "Microdroid androids", + ) + + const val ERROR_TEXT = + "Password-protected messages are disabled, please check $URL" + + val TERMS = listOf( + "droid", + "[Classification: Data Control: Internal Data Control]", + ) + } +} \ No newline at end of file diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsErrorTextMissingFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsErrorTextMissingFlowTest.kt new file mode 100644 index 0000000000..85f8459b15 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsErrorTextMissingFlowTest.kt @@ -0,0 +1,80 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui.gmailapi.passwordprotected + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings +import com.flowcrypt.email.junit.annotations.OutgoingMessageConfiguration +import com.flowcrypt.email.rules.AddRecipientsToDatabaseRule +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@FlowCryptTestSettings(useCommonIdling = false) +@OutgoingMessageConfiguration( + to = [], + cc = [], + bcc = [], + message = "", + subject = "", + isNew = true +) +class ComposeScreenPasswordProtectedDisallowedTermsErrorTextMissingFlowTest : + BaseComposeScreenPasswordProtectedDisallowedTermsTest( + ACCOUNT_ENTITY_WITH_MISSING_ERROR_TEXT + ) { + + @get:Rule + var ruleChain: TestRule = + RuleChain.outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(mockWebServerRule) + .around(addAccountToDatabaseRule) + .around(addPrivateKeyToDatabaseRule) + .around(AddRecipientsToDatabaseRule(prepareRecipientsForTest())) + .around(addLabelsToDatabaseRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + @Test + fun testMissingOptionalPropertiesInClientConfiguration() { + onView(withId(R.id.menuActionSend)) + .check(matches(isDisplayed())) + .perform(click()) + onView(withText(ERROR_TEXT)).check(doesNotExist()) + } + + companion object { + val ACCOUNT_ENTITY_WITH_MISSING_ERROR_TEXT = + BASE_ACCOUNT_ENTITY.copy( + clientConfiguration = BASE_ACCOUNT_ENTITY.clientConfiguration?.copy( + disallowPasswordMessagesErrorText = null, + disallowPasswordMessagesForTerms = TERMS + ) + ) + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsFlowTest.kt new file mode 100644 index 0000000000..323337c07d --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsFlowTest.kt @@ -0,0 +1,133 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui.gmailapi.passwordprotected + +import android.app.Instrumentation +import android.content.Intent +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.openLinkWithText +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.intent.matcher.IntentMatchers.hasData +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings +import com.flowcrypt.email.junit.annotations.OutgoingMessageConfiguration +import com.flowcrypt.email.rules.AddRecipientsToDatabaseRule +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import org.hamcrest.Matchers.allOf +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@FlowCryptTestSettings(useCommonIdling = false, useIntents = true) +@OutgoingMessageConfiguration( + to = [], + cc = [], + bcc = [], + message = "", + subject = "", + isNew = true +) +class ComposeScreenPasswordProtectedDisallowedTermsFlowTest : + BaseComposeScreenPasswordProtectedDisallowedTermsTest( + ACCOUNT_ENTITY_WITH_EXISTING_OPTIONAL_PARAMETERS + ) { + + @get:Rule + var ruleChain: TestRule = + RuleChain.outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(mockWebServerRule) + .around(addAccountToDatabaseRule) + .around(addPrivateKeyToDatabaseRule) + .around(AddRecipientsToDatabaseRule(prepareRecipientsForTest())) + .around(addLabelsToDatabaseRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + @Test + fun testDialogWithErrorText() { + intentsRelease() + + MATCHING_SUBJECTS.forEach { subject -> + onView(withId(R.id.editTextEmailSubject)) + .perform(scrollTo(), click(), replaceText(subject)) + + onView(withId(R.id.menuActionSend)) + .check(matches(isDisplayed())) + .perform(click()) + + isDialogWithTextDisplayed( + decorView, + ERROR_TEXT + ) + + Intents.init() + //test that URL is openable + val expectingIntent = allOf(hasAction(Intent.ACTION_VIEW), hasData(URL)) + //mocking intent to prevent actual navigation during test + Intents.intending(expectingIntent).respondWith(Instrumentation.ActivityResult(0, null)) + //performing action + onView(withText(ERROR_TEXT)) + .perform(openLinkWithText(URL)) + //asserting our expected intent was recorded + Intents.intended(expectingIntent) + Thread.sleep(1000) + Intents.release() + + onView(withId(android.R.id.button1)) + .check(matches(isDisplayed())) + .perform(click()) + } + } + + @Test + fun testDialogWithoutErrorText() { + NON_MATCHING_SUBJECTS.forEach { subject -> + onView(withId(R.id.editTextEmailSubject)) + .perform(scrollTo(), click(), replaceText(subject)) + Thread.sleep(1000) + + onView(withId(R.id.menuActionSend)) + .check(matches(isDisplayed())) + .perform(click()) + + onView( + withText(getResString(R.string.sending_message_must_not_be_empty)) + ).check(matches(isDisplayed())) + } + } + + companion object { + val ACCOUNT_ENTITY_WITH_EXISTING_OPTIONAL_PARAMETERS = + BASE_ACCOUNT_ENTITY.copy( + clientConfiguration = BASE_ACCOUNT_ENTITY.clientConfiguration?.copy( + disallowPasswordMessagesErrorText = ERROR_TEXT, + disallowPasswordMessagesForTerms = TERMS + ) + ) + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsMissingTermsFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsMissingTermsFlowTest.kt new file mode 100644 index 0000000000..ea2e7bd237 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsMissingTermsFlowTest.kt @@ -0,0 +1,80 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui.gmailapi.passwordprotected + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings +import com.flowcrypt.email.junit.annotations.OutgoingMessageConfiguration +import com.flowcrypt.email.rules.AddRecipientsToDatabaseRule +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@FlowCryptTestSettings(useCommonIdling = false) +@OutgoingMessageConfiguration( + to = [], + cc = [], + bcc = [], + message = "", + subject = "", + isNew = true +) +class ComposeScreenPasswordProtectedDisallowedTermsMissingTermsFlowTest : + BaseComposeScreenPasswordProtectedDisallowedTermsTest( + ACCOUNT_ENTITY_WITH_MISSING_TERMS + ) { + + @get:Rule + var ruleChain: TestRule = + RuleChain.outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(mockWebServerRule) + .around(addAccountToDatabaseRule) + .around(addPrivateKeyToDatabaseRule) + .around(AddRecipientsToDatabaseRule(prepareRecipientsForTest())) + .around(addLabelsToDatabaseRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + @Test + fun testMissingOptionalPropertiesInClientConfiguration() { + onView(withId(R.id.menuActionSend)) + .check(matches(isDisplayed())) + .perform(click()) + onView(withText(ERROR_TEXT)).check(doesNotExist()) + } + + companion object { + val ACCOUNT_ENTITY_WITH_MISSING_TERMS = + BASE_ACCOUNT_ENTITY.copy( + clientConfiguration = BASE_ACCOUNT_ENTITY.clientConfiguration?.copy( + disallowPasswordMessagesErrorText = ERROR_TEXT, + disallowPasswordMessagesForTerms = null + ) + ) + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsNullFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsNullFlowTest.kt new file mode 100644 index 0000000000..a190aae148 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsNullFlowTest.kt @@ -0,0 +1,75 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui.gmailapi.passwordprotected + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings +import com.flowcrypt.email.junit.annotations.OutgoingMessageConfiguration +import com.flowcrypt.email.rules.AddRecipientsToDatabaseRule +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@FlowCryptTestSettings(useCommonIdling = false) +@OutgoingMessageConfiguration( + to = [], + cc = [], + bcc = [], + message = "", + subject = "", + isNew = true +) +class ComposeScreenPasswordProtectedDisallowedTermsNullFlowTest : + BaseComposeScreenPasswordProtectedDisallowedTermsTest( + ACCOUNT_ENTITY_WITH_MISSING_OPTIONAL_PROPERTIES + ) { + + @get:Rule + var ruleChain: TestRule = + RuleChain.outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(mockWebServerRule) + .around(addAccountToDatabaseRule) + .around(addPrivateKeyToDatabaseRule) + .around(AddRecipientsToDatabaseRule(prepareRecipientsForTest())) + .around(addLabelsToDatabaseRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + @Test + fun testMissingOptionalPropertiesInClientConfiguration() { + onView(withId(R.id.menuActionSend)) + .check(matches(isDisplayed())) + .perform(click()) + + onView(withText(ERROR_TEXT)).check(doesNotExist()) + } + + companion object { + val ACCOUNT_ENTITY_WITH_MISSING_OPTIONAL_PROPERTIES = BASE_ACCOUNT_ENTITY.copy() + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfiguration.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfiguration.kt index 5bb976756f..e46e688d19 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfiguration.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfiguration.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.api.retrofit.response.model @@ -15,7 +15,7 @@ import kotlinx.parcelize.Parcelize * @author Denys Bondarenko */ @Parcelize -data class ClientConfiguration constructor( +data class ClientConfiguration( @Expose val flags: List? = null, @SerializedName("custom_keyserver_url") @Expose val customKeyserverUrl: String? = null, @@ -30,7 +30,11 @@ data class ClientConfiguration constructor( @SerializedName("enforce_keygen_expire_months") @Expose val enforceKeygenExpireMonths: Int? = null, @SerializedName("in_memory_pass_phrase_session_length") - @Expose val inMemoryPassPhraseSessionLength: Int? = null + @Expose val inMemoryPassPhraseSessionLength: Int? = null, + @SerializedName("disallow_password_messages_for_terms") + @Expose val disallowPasswordMessagesForTerms: List? = null, + @SerializedName("disallow_password_messages_error_text") + @Expose val disallowPasswordMessagesErrorText: String? = null, ) : Parcelable { val inMemoryPassPhraseSessionLengthNormalized: Int? @@ -203,10 +207,41 @@ data class ClientConfiguration constructor( return hasProperty(ConfigurationProperty.HIDE_ARMOR_META) } + /** + * With this option, the app must check the subjects of composed password-protected email + * messages for specified strings(defined in [disallowPasswordMessagesForTerms]). + * If any configured terms are found, the application should display an error message + * (defined in [disallowPasswordMessagesErrorText]) + * indicating that the password encryption method is incompatible with the composed message. + * + * ref https://github.com/FlowCrypt/flowcrypt-android/issues/2905 + */ + fun hasRestrictionForPasswordProtectedMessages(): Boolean { + return !disallowPasswordMessagesForTerms.isNullOrEmpty() + && !disallowPasswordMessagesErrorText.isNullOrEmpty() + } + fun hasProperty(configurationProperty: ConfigurationProperty): Boolean { return flags?.firstOrNull { it == configurationProperty } != null } + fun getDisallowPasswordMessagesForTermsRegex(): Regex? { + val startAndEntWithAnyNonWordCharacterRegex = "^\\W.*\\W\$".toRegex(RegexOption.IGNORE_CASE) + + return disallowPasswordMessagesForTerms?.joinToString( + separator = "|", + prefix = "(", + postfix = ")" + ) { term -> + val escapedTerm = Regex.escape(term) + if (startAndEntWithAnyNonWordCharacterRegex.matches(term)) { + "($escapedTerm)" + } else { + "(\\W$escapedTerm\\W)" + } + }?.toRegex(setOf(RegexOption.IGNORE_CASE)) + } + @Parcelize enum class ConfigurationProperty : Parcelable { NO_PRV_CREATE, diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt index 1f35e5ce74..5c14718003 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.ui.activity.fragment @@ -1538,6 +1538,7 @@ class CreateMessageFragment : BaseFragment(), return false } + var isWebPortalPasswordEnabled = false if (composeMsgViewModel.msgEncryptionType === MessageEncryptionType.ENCRYPTED) { if (composeMsgViewModel.allRecipients.any { it.value.isUpdating }) { toast(R.string.please_wait_while_information_about_recipients_will_be_updated) @@ -1550,6 +1551,7 @@ class CreateMessageFragment : BaseFragment(), val password = composeMsgViewModel.webPortalPasswordStateFlow.value if (password.isNotEmpty()) { + isWebPortalPasswordEnabled = true val keysStorage = KeysStorageImpl.getInstance(requireContext()) if (keysStorage.hasPassphrase(Passphrase(password.toString().toCharArray()))) { showInfoDialog( @@ -1581,6 +1583,21 @@ class CreateMessageFragment : BaseFragment(), binding?.editTextEmailSubject?.requestFocus() return false } + + if (isWebPortalPasswordEnabled && + account?.clientConfiguration?.hasRestrictionForPasswordProtectedMessages() == true + ) { + val termsRegex = account?.clientConfiguration?.getDisallowPasswordMessagesForTermsRegex() + if (termsRegex?.find(binding?.editTextEmailSubject?.text ?: "") != null) { + showInfoDialog( + dialogTitle = "", + dialogMsg = account?.clientConfiguration?.disallowPasswordMessagesErrorText, + useLinkify = true + ) + return false + } + } + if (composeMsgViewModel.attachmentsStateFlow.value.isEmpty() && binding?.editTextEmailMessage?.text?.isEmpty() == true) { showInfoSnackbar( binding?.editTextEmailMessage, diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfigurationTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfigurationTest.kt new file mode 100644 index 0000000000..73872bedbc --- /dev/null +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/api/retrofit/response/model/ClientConfigurationTest.kt @@ -0,0 +1,51 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.api.retrofit.response.model + +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test + +/** + * @author Denys Bondarenko + */ +class ClientConfigurationTest { + @Test + fun testGetDisallowPasswordMessagesForTermsRegex() { + val clientConfiguration = ClientConfiguration( + disallowPasswordMessagesForTerms = listOf( + "droid", + "[Classification: Data Control: Internal Data Control]", + ) + ) + + val regex = requireNotNull(clientConfiguration.getDisallowPasswordMessagesForTermsRegex()) + + val matchingSubjects = listOf( + "[Classification: Data Control: Internal Data Control] Quarter results", + "Conference information [Classification: Data Control: Internal Data Control]", + "[Classification: Data Control: Internal Data Control]", + "aaaa[Classification: Data Control: Internal Data Control]bbb", + "[droid]", + "check -droid- case", + ) + + val nonMatchingSubjects = listOf( + "[1Classification: Data Control: Internal Data Control] Quarter results", + "Conference information [1Classification: Data Control: Internal Data Control]", + "[1Classification: Data Control: Internal Data Control]", + "aaaa[1Classification: Data Control: Internal Data Control]bbb", + "Microdroid androids", + ) + matchingSubjects.forEach { subject -> + assertNotNull("Exception in :$subject", regex.find(subject)) + } + + nonMatchingSubjects.forEach { subject -> + assertNull("Exception in :$subject", regex.find(subject)) + } + } +} \ No newline at end of file