From 40180a4df8b2bb8068940c3281d8ff36743c82b3 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Mon, 3 May 2021 15:04:01 +0300 Subject: [PATCH 01/41] Refactored code.| #1168 --- .../AddNewAccountActivityEnterpriseTest.kt | 17 +++- .../SignInActivityEnterpriseTest.kt | 17 +++- .../response/api/DomainRulesResponse.kt | 10 +-- .../retrofit/response/model/DomainRules.kt | 38 -------- .../api/retrofit/response/model/OrgRules.kt | 89 +++++++++++++++++++ .../email/database/entity/AccountEntity.kt | 13 +-- .../jetpack/viewmodel/PrivateKeysViewModel.kt | 7 +- .../viewmodel/SubmitPubKeyViewModel.kt | 3 +- .../ui/activity/ChangePassPhraseActivity.kt | 4 +- .../ui/activity/CreateOrImportKeyActivity.kt | 3 +- .../activity/fragment/MainSettingsFragment.kt | 3 +- .../activity/fragment/MainSignInFragment.kt | 7 +- .../SystemNotificationManager.kt | 3 +- 13 files changed, 144 insertions(+), 70 deletions(-) delete mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/DomainRules.kt create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt 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 b2b049e983..8546a911dd 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 @@ -21,7 +21,7 @@ import com.flowcrypt.email.api.retrofit.ApiHelper import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse -import com.flowcrypt.email.api.retrofit.response.model.DomainRules +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.junit.annotations.NotReadyForCI import com.flowcrypt.email.rules.AddAccountToDatabaseRule import com.flowcrypt.email.rules.ClearAppSettingsRule @@ -93,8 +93,19 @@ class AddNewAccountActivityEnterpriseTest : BaseSignActivityTest() { if (request.path.equals("/account/get")) { when (model.account) { EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(DomainRulesResponse(null, DomainRules(listOf - ("NO_PRV_CREATE", "NO_PRV_BACKUP"))))) + .setBody(gson.toJson( + DomainRulesResponse( + apiError = null, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.NO_PRV_CREATE, + OrgRules.DomainRule.NO_PRV_BACKUP), + customKeyserverUrl = null, + keyManagerUrl = null, + disallowAttesterSearchForDomains = null, + enforceKeygenAlgo = null, + enforceKeygenExpireMonths = null) + ))) } } 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 4b381035f3..11fda7be54 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 @@ -22,7 +22,7 @@ import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.base.ApiError -import com.flowcrypt.email.api.retrofit.response.model.DomainRules +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.junit.annotations.NotReadyForCI import com.flowcrypt.email.rules.ClearAppSettingsRule import com.flowcrypt.email.rules.FlowCryptMockWebServerRule @@ -137,8 +137,19 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { .setBody(gson.toJson(DOMAIN_RULES_ERROR_RESPONSE)) EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(DomainRulesResponse(null, DomainRules(listOf - ("NO_PRV_CREATE", "NO_PRV_BACKUP"))))) + .setBody(gson.toJson( + DomainRulesResponse( + apiError = null, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.NO_PRV_CREATE, + OrgRules.DomainRule.NO_PRV_BACKUP), + customKeyserverUrl = null, + keyManagerUrl = null, + disallowAttesterSearchForDomains = null, + enforceKeygenAlgo = null, + enforceKeygenExpireMonths = null) + ))) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt index 296880d1d7..d5a05a670f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt @@ -9,7 +9,7 @@ import android.os.Parcel import android.os.Parcelable import com.flowcrypt.email.api.retrofit.response.base.ApiError import com.flowcrypt.email.api.retrofit.response.base.ApiResponse -import com.flowcrypt.email.api.retrofit.response.model.DomainRules +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName @@ -22,17 +22,17 @@ import com.google.gson.annotations.SerializedName * E-mail: DenBond7@gmail.com */ data class DomainRulesResponse constructor(@SerializedName("error") - @Expose override val apiError: ApiError?, + @Expose override val apiError: ApiError?, @SerializedName("domain_org_rules") - @Expose val domainRules: DomainRules?) : ApiResponse { + @Expose val orgRules: OrgRules?) : ApiResponse { constructor(parcel: Parcel) : this( parcel.readParcelable(ApiError::class.java.classLoader), - parcel.readParcelable(DomainRules::class.java.classLoader)) + parcel.readParcelable(OrgRules::class.java.classLoader)) override fun writeToParcel(parcel: Parcel, flags: Int) { with(parcel) { writeParcelable(apiError, flags) - writeParcelable(domainRules, flags) + writeParcelable(orgRules, flags) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/DomainRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/DomainRules.kt deleted file mode 100644 index 40792b8a0b..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/DomainRules.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.api.retrofit.response.model - -import android.os.Parcel -import android.os.Parcelable -import com.google.gson.annotations.Expose - -/** - * @author Denis Bondarenko - * Date: 10/29/19 - * Time: 11:30 AM - * E-mail: DenBond7@gmail.com - */ -data class DomainRules constructor(@Expose val flags: List?) : Parcelable { - constructor(parcel: Parcel) : this(parcel.createStringArrayList()) - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeStringList(this.flags) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): DomainRules { - return DomainRules(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} \ No newline at end of file diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt new file mode 100644 index 0000000000..a68f989c8c --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -0,0 +1,89 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.api.retrofit.response.model + +import android.os.Parcel +import android.os.Parcelable +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +/** + * @author Denis Bondarenko + * Date: 10/29/19 + * Time: 11:30 AM + * E-mail: DenBond7@gmail.com + */ +data class OrgRules constructor( + @Expose val flags: List?, + @SerializedName("custom_keyserver_url") + @Expose val customKeyserverUrl: String?, + @SerializedName("key_manager_url") + @Expose val keyManagerUrl: String?, + @SerializedName("disallow_attester_search_for_domains") + @Expose val disallowAttesterSearchForDomains: List?, + @SerializedName("enforce_keygen_algo") + @Expose val enforceKeygenAlgo: String?, + @SerializedName("enforce_keygen_expire_months") + @Expose val enforceKeygenExpireMonths: Int? +) : Parcelable { + + constructor(parcel: Parcel) : this( + parcel.createTypedArrayList(DomainRule.CREATOR), + parcel.readString(), + parcel.readString(), + parcel.createStringArrayList(), + parcel.readString(), + parcel.readValue(Int::class.java.classLoader) as? Int) + + override fun writeToParcel(parcel: Parcel, flagsList: Int) { + parcel.writeTypedList(flags) + parcel.writeString(customKeyserverUrl) + parcel.writeString(keyManagerUrl) + parcel.writeStringList(disallowAttesterSearchForDomains) + parcel.writeString(enforceKeygenAlgo) + parcel.writeValue(enforceKeygenExpireMonths) + } + + override fun describeContents(): Int { + return 0 + } + + enum class DomainRule : Parcelable { + NO_PRV_CREATE, + NO_PRV_BACKUP, + PRV_AUTOIMPORT_OR_AUTOGEN, + PASS_PHRASE_QUIET_AUTOGEN, + ENFORCE_ATTESTER_SUBMIT, + NO_ATTESTER_SUBMIT, + NO_KEY_MANAGER_PUB_LOOKUP, + USE_LEGACY_ATTESTER_SUBMIT, + DEFAULT_REMEMBER_PASS_PHRASE, + HIDE_ARMOR_META; + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): DomainRule = values()[parcel.readInt()] + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeInt(ordinal) + } + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): OrgRules { + return OrgRules(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/AccountEntity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/AccountEntity.kt index d6f8ebd53b..ef654ded09 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/AccountEntity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/AccountEntity.kt @@ -18,6 +18,7 @@ import com.flowcrypt.email.api.email.JavaEmailConstants import com.flowcrypt.email.api.email.gmail.GmailConstants import com.flowcrypt.email.api.email.model.AuthCredentials import com.flowcrypt.email.api.email.model.SecurityType +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.util.FlavorSettings import com.google.android.gms.auth.api.signin.GoogleSignInAccount import java.util.* @@ -74,7 +75,7 @@ data class AccountEntity constructor( get() = JavaEmailConstants.AUTH_MECHANISMS_XOAUTH2 == imapAuthMechanisms constructor(googleSignInAccount: GoogleSignInAccount, uuid: String? = null, - domainRules: List? = null) : + domainRules: List? = null) : this( email = googleSignInAccount.email!!.toLowerCase(Locale.US), accountType = googleSignInAccount.account?.type?.toLowerCase(Locale.US), @@ -107,7 +108,7 @@ data class AccountEntity constructor( useAPI = FlavorSettings.isGMailAPIEnabled() ) - constructor(authCredentials: AuthCredentials, uuid: String? = null, domainRules: List? = null) : + constructor(authCredentials: AuthCredentials, uuid: String? = null, domainRules: List? = null) : this( email = authCredentials.email.toLowerCase(Locale.US), accountType = authCredentials.email.substring(authCredentials.email.indexOf('@') + 1).toLowerCase(Locale.getDefault()), @@ -242,17 +243,11 @@ data class AccountEntity constructor( } } - fun isRuleExist(domainRule: DomainRule): Boolean { + fun isRuleExist(domainRule: OrgRules.DomainRule): Boolean { val rules = domainRulesList() return domainRule.name in rules } - enum class DomainRule { - NO_PRV_CREATE, - NO_PRV_BACKUP, - ENFORCE_ATTESTER_SUBMIT - } - override fun describeContents() = 0 override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt index 6b89e8e57e..cc9b934711 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt @@ -27,6 +27,7 @@ import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel 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.model.node.NodeKeyDetails import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.ActionQueueEntity @@ -331,7 +332,7 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl } private suspend fun doAdditionalOperationsAfterKeyCreation(accountEntity: AccountEntity, nodeKeyDetails: NodeKeyDetails) { - if (accountEntity.isRuleExist(AccountEntity.DomainRule.ENFORCE_ATTESTER_SUBMIT)) { + if (accountEntity.isRuleExist(OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT)) { val model = InitialLegacySubmitModel(accountEntity.email, nodeKeyDetails.publicKey!!) val initialLegacySubmitResult = apiRepository.postInitialLegacySubmit(getApplication(), model) @@ -349,7 +350,7 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl } } - if (!accountEntity.isRuleExist(AccountEntity.DomainRule.NO_PRV_BACKUP)) { + if (!accountEntity.isRuleExist(OrgRules.DomainRule.NO_PRV_BACKUP)) { if (!saveCreatedPrivateKeyAsBackupToInbox(accountEntity, nodeKeyDetails)) { val backupAction = ActionQueueEntity.fromAction(BackupPrivateKeyToInboxAction(0, accountEntity.email, 0, nodeKeyDetails.longId!!)) @@ -357,7 +358,7 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl } } } else { - if (!accountEntity.isRuleExist(AccountEntity.DomainRule.NO_PRV_BACKUP)) { + if (!accountEntity.isRuleExist(OrgRules.DomainRule.NO_PRV_BACKUP)) { if (!saveCreatedPrivateKeyAsBackupToInbox(accountEntity, nodeKeyDetails)) { val backupAction = ActionQueueEntity.fromAction(BackupPrivateKeyToInboxAction(0, accountEntity.email, 0, nodeKeyDetails.longId!!)) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt index 07840588a6..4aacabc1e9 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt @@ -15,6 +15,7 @@ import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse 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.model.node.NodeKeyDetails import com.flowcrypt.email.database.FlowCryptRoomDatabase import com.flowcrypt.email.database.entity.AccountEntity @@ -45,7 +46,7 @@ class SubmitPubKeyViewModel(application: Application) : BaseAndroidViewModel(app when (result.status) { Result.Status.ERROR, Result.Status.EXCEPTION -> { - if (account.isRuleExist(AccountEntity.DomainRule.ENFORCE_ATTESTER_SUBMIT)) { + if (account.isRuleExist(OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT)) { submitPubKeyLiveData.value = result } else { val registerAction = ActionQueueEntity.fromAction(RegisterUserPublicKeyAction(0, account.email, 0, it)) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ChangePassPhraseActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ChangePassPhraseActivity.kt index ddb8c8ea45..c778d662d1 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ChangePassPhraseActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/ChangePassPhraseActivity.kt @@ -14,7 +14,7 @@ import android.widget.Toast import androidx.activity.viewModels import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result -import com.flowcrypt.email.database.entity.AccountEntity +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.jetpack.viewmodel.LoadPrivateKeysViewModel @@ -136,7 +136,7 @@ class ChangePassPhraseActivity : BasePassPhraseManagerActivity() { Result.Status.SUCCESS -> { if (it.data == true) { - if (activeAccount?.isRuleExist(AccountEntity.DomainRule.NO_PRV_BACKUP) == true) { + if (activeAccount?.isRuleExist(OrgRules.DomainRule.NO_PRV_BACKUP) == true) { isBackEnabled = true Toast.makeText(this, R.string.pass_phrase_changed, Toast.LENGTH_SHORT).show() setResult(Activity.RESULT_OK) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateOrImportKeyActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateOrImportKeyActivity.kt index 057a88eda0..7ab9491801 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateOrImportKeyActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateOrImportKeyActivity.kt @@ -11,6 +11,7 @@ import android.content.Intent import android.os.Bundle import android.view.View import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.model.KeyImportModel import com.flowcrypt.email.ui.activity.base.BaseCheckClipboardBackStackActivity @@ -115,7 +116,7 @@ class CreateOrImportKeyActivity : BaseCheckClipboardBackStackActivity(), View.On private fun initViews() { findViewById(R.id.buttonImportMyKey)?.setOnClickListener(this) - if (tempAccount.isRuleExist(AccountEntity.DomainRule.NO_PRV_CREATE)) { + if (tempAccount.isRuleExist(OrgRules.DomainRule.NO_PRV_CREATE)) { findViewById(R.id.buttonCreateNewKey)?.visibility = View.GONE } else { findViewById(R.id.buttonCreateNewKey)?.setOnClickListener(this) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSettingsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSettingsFragment.kt index 9fd5ff8937..d0df6469c3 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSettingsFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSettingsFragment.kt @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.preference.Preference import com.flowcrypt.email.Constants import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.ui.activity.fragment.base.BasePreferenceFragment @@ -41,7 +42,7 @@ class MainSettingsFragment : BasePreferenceFragment() { override fun onAccountInfoRefreshed(accountEntity: AccountEntity?) { super.onAccountInfoRefreshed(accountEntity) findPreference(Constants.PREF_KEY_BACKUPS)?.isVisible = - !(accountEntity?.isRuleExist(AccountEntity.DomainRule.NO_PRV_BACKUP) ?: false) + !(accountEntity?.isRuleExist(OrgRules.DomainRule.NO_PRV_BACKUP) ?: false) if (accountEntity?.useAPI == false) { findPreference(Constants.PREF_KEY_SERVER_SETTINGS)?.isVisible = true diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index bca13c2bce..4e2c9f07e2 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -19,6 +19,7 @@ import com.flowcrypt.email.api.email.EmailUtil import com.flowcrypt.email.api.email.JavaEmailConstants import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse 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.model.node.NodeKeyDetails import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.showInfoDialog @@ -59,7 +60,7 @@ class MainSignInFragment : BaseSingInFragment() { private lateinit var client: GoogleSignInClient private var googleSignInAccount: GoogleSignInAccount? = null private var uuid: String? = null - private var domainRules: List? = null + private var domainRules: List? = null private val enterpriseDomainRulesViewModel: EnterpriseDomainRulesViewModel by viewModels() @@ -221,7 +222,7 @@ class MainSignInFragment : BaseSingInFragment() { if (existedAccount == null) { getTempAccount()?.let { - if (domainRules?.contains(AccountEntity.DomainRule.NO_PRV_BACKUP.name) == true) { + if (domainRules?.first { rule -> rule == OrgRules.DomainRule.NO_PRV_BACKUP } != null) { requireContext().startService(Intent(requireContext(), CheckClipboardToFindKeyService::class.java)) val intent = CreateOrImportKeyActivity.newIntent(requireContext(), it, true) startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY) @@ -341,7 +342,7 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.SUCCESS -> { val result = it.data as? DomainRulesResponse - domainRules = result?.domainRules?.flags ?: emptyList() + domainRules = result?.orgRules?.flags ?: emptyList() onSignSuccess(googleSignInAccount) enterpriseDomainRulesViewModel.domainRulesLiveData.value = null } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/SystemNotificationManager.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/SystemNotificationManager.kt index 6ac0b5c9b1..36a52b922a 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/SystemNotificationManager.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/SystemNotificationManager.kt @@ -13,6 +13,7 @@ import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import com.flowcrypt.email.BuildConfig import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.ui.activity.ChangePassPhraseActivity import com.flowcrypt.email.ui.activity.EmailManagerActivity @@ -39,7 +40,7 @@ class SystemNotificationManager(context: Context) : CustomNotificationManager(co cancel(NOTIFICATION_ID_PASSPHRASE_TOO_WEAK) } - val intent = if (account?.isRuleExist(AccountEntity.DomainRule.NO_PRV_BACKUP) == true) { + val intent = if (account?.isRuleExist(OrgRules.DomainRule.NO_PRV_BACKUP) == true) { Intent(context, EmailManagerActivity::class.java) } else { ChangePassPhraseActivity.newIntent(context) From a83a7c3a3ba876524f1518bd752ca30b28d97445 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Mon, 3 May 2021 15:54:03 +0300 Subject: [PATCH 02/41] Added some methods to OrgRules.| #1168 --- .../api/retrofit/response/model/OrgRules.kt | 172 +++++++++++++++++- 1 file changed, 169 insertions(+), 3 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt index a68f989c8c..1cb3905158 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -7,6 +7,7 @@ package com.flowcrypt.email.api.retrofit.response.model import android.os.Parcel import android.os.Parcelable +import com.flowcrypt.email.api.email.EmailUtil import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName @@ -25,7 +26,7 @@ data class OrgRules constructor( @SerializedName("disallow_attester_search_for_domains") @Expose val disallowAttesterSearchForDomains: List?, @SerializedName("enforce_keygen_algo") - @Expose val enforceKeygenAlgo: String?, + @Expose val enforceKeygenAlgo: KeyAlgo?, @SerializedName("enforce_keygen_expire_months") @Expose val enforceKeygenExpireMonths: Int? ) : Parcelable { @@ -35,7 +36,7 @@ data class OrgRules constructor( parcel.readString(), parcel.readString(), parcel.createStringArrayList(), - parcel.readString(), + parcel.readParcelable(KeyAlgo::class.java.classLoader), parcel.readValue(Int::class.java.classLoader) as? Int) override fun writeToParcel(parcel: Parcel, flagsList: Int) { @@ -43,7 +44,7 @@ data class OrgRules constructor( parcel.writeString(customKeyserverUrl) parcel.writeString(keyManagerUrl) parcel.writeStringList(disallowAttesterSearchForDomains) - parcel.writeString(enforceKeygenAlgo) + parcel.writeParcelable(enforceKeygenAlgo, flagsList) parcel.writeValue(enforceKeygenExpireMonths) } @@ -51,6 +52,152 @@ data class OrgRules constructor( return 0 } + /** + * Internal company SKS-like public key server to trust above Attester + */ + fun getCustomSksPubKeyServer(): String? { + return customKeyserverUrl + } + + /** + * an internal org FlowCrypt Email Key Manager instance, can manage both public and private keys + * use this method when using for PRV sync + */ + fun getKeyManagerUrlForPrivateKeys(): String? { + return keyManagerUrl + } + + /** + * an internal org FlowCrypt Email Key Manager instance, can manage both public and private keys + * use this method when using for PUB sync + */ + fun getKeyManagerUrlForPublicKeys(): String? { + if (flags?.first { it == DomainRule.NO_KEY_MANAGER_PUB_LOOKUP } != null) { + return null + } + return keyManagerUrl + } + + /** + * use when finding out if EKM is in use, + * to change functionality without actually needing the EKM + */ + fun usesKeyManager(): Boolean { + return keyManagerUrl != null + } + + /** + * Enforce a key algo for keygen, eg rsa2048,rsa4096,curve25519 + */ + fun getEnforcedKeygenAlgo(): KeyAlgo? { + return enforceKeygenAlgo + } + + /** + * Some orgs want to have newly generated keys include self-signatures that expire some time + * in the future. + */ + fun getEnforcedKeygenExpirationMonths(): Int? { + return enforceKeygenExpireMonths + } + + /** + * Some orgs expect 100% of their private keys to be imported from elsewhere + * (and forbid keygen in the extension) + */ + fun canCreateKeys(): Boolean { + return flags?.first { it == DomainRule.NO_PRV_CREATE } == null + } + + /** + * Some orgs want to forbid backing up of public keys (such as inbox or other methods) + */ + fun canBackupKeys(): Boolean { + return flags?.first { it == DomainRule.NO_PRV_BACKUP } == null + } + + /** + * (normally, during setup, if a public key is submitted to Attester and there is + * a conflicting key already submitted, the issue will be skipped) some orgs want to make sure + * that their public key gets submitted to attester and conflict errors are NOT ignored: + */ + fun mustSubmitToAttester(): Boolean { + return flags?.first { it == DomainRule.ENFORCE_ATTESTER_SUBMIT } != null + } + + /** + * Normally, during setup, "remember pass phrase" is unchecked + * This option will cause "remember pass phrase" option to be checked by default + * This behavior is also enabled as a byproduct of PASS_PHRASE_QUIET_AUTOGEN + */ + fun rememberPassPhraseByDefault(): Boolean { + return flags?.first { it == DomainRule.DEFAULT_REMEMBER_PASS_PHRASE } != null + || this.mustAutoGenPassPhraseQuietly() + } + + /** + * This is to be used for customers who run their own FlowCrypt Email Key Manager + * If a key can be found on FEKM, it will be auto imported + * If not, it will be autogenerated and stored there + */ + fun mustAutoImportOrAutoGenPrvWithKeyManager(): Boolean { + if (flags?.first { it == DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN } == null) { + return false + } + if (getKeyManagerUrlForPrivateKeys() != null) { + throw IllegalStateException( + "Wrong org rules config: using PRV_AUTOIMPORT_OR_AUTOGEN without key_manager_url") + } + return true + } + + /** + * When generating keys, user will not be prompted to choose a pass phrase + * Instead a pass phrase will be automatically generated, and stored locally + * The pass phrase will NOT be displayed to user, and it will never be asked of the user + * This creates the smoothest user experience, for organisations that use + * full-disk-encryption and don't need pass phrase protection + */ + fun mustAutoGenPassPhraseQuietly(): Boolean { + return flags?.first { it == DomainRule.PASS_PHRASE_QUIET_AUTOGEN } != null + } + + /** + * Some orgs prefer to forbid publishing public keys publicly + */ + fun canSubmitPubToAttester(): Boolean { + return flags?.first { it == DomainRule.NO_ATTESTER_SUBMIT } == null + } + + /** + * Some orgs have a list of email domains where they do NOT want such emails to be looked up on + * public sources (such as Attester). This is because they already have other means to obtain + * public keys for these domains, such as from their own internal keyserver + */ + fun canLookupThisRecipientOnAttester(emailAddr: String): Boolean { + val domain = EmailUtil.getDomain(emailAddr) + val domains = disallowAttesterSearchForDomains ?: emptyList() + return !domains.contains(if (domain.isEmpty()) "NONE" else domain) + } + + /** + * Some orgs use flows that are only implemented in POST /initial/legacy_submit + * and not in POST /pub/email@corp.co: -> enforcing that submitted keys match customer key server + * Until the newer endpoint is ready, this flag will point users in those orgs to + * the original endpoint + */ + fun useLegacyAttesterSubmit(): Boolean { + return flags?.first { it == DomainRule.USE_LEGACY_ATTESTER_SUBMIT } != null + } + + /** + * With this option, sent messages won't have any comment/version in armor, + * imported keys get imported without armor + */ + fun shouldHideArmorMeta(): Boolean { + return flags?.first { it == DomainRule.HIDE_ARMOR_META } != null + } + enum class DomainRule : Parcelable { NO_PRV_CREATE, NO_PRV_BACKUP, @@ -77,6 +224,25 @@ data class OrgRules constructor( } } + enum class KeyAlgo : Parcelable { + curve25519, + rsa2048, + rsa4096; + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): KeyAlgo = values()[parcel.readInt()] + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeInt(ordinal) + } + } + companion object CREATOR : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): OrgRules { return OrgRules(parcel) From 2bff55e29531357077c0bb04c35dc6996982a7ae Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Mon, 14 Jun 2021 14:48:00 +0300 Subject: [PATCH 03/41] Fixed errors after merge --- .../SignInActivityEnterpriseTest.kt | 114 ++++++++++-------- .../jetpack/viewmodel/PrivateKeysViewModel.kt | 1 - .../viewmodel/SubmitPubKeyViewModel.kt | 1 - .../activity/fragment/MainSignInFragment.kt | 1 - 4 files changed, 61 insertions(+), 56 deletions(-) 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 d0165c5886..ec7501d80d 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 @@ -63,10 +63,10 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { @get:Rule var ruleChain: TestRule = RuleChain - .outerRule(ClearAppSettingsRule()) - .around(RetryRule.DEFAULT) - .around(activityScenarioRule) - .around(ScreenshotTestRule()) + .outerRule(ClearAppSettingsRule()) + .around(RetryRule.DEFAULT) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) @Test fun testErrorLogin() { @@ -93,7 +93,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { intended(hasComponent(CreateOrImportKeyActivity::class.java.name)) onView(withId(R.id.buttonCreateNewKey)) - .check(matches(not(isDisplayed()))) + .check(matches(not(isDisplayed()))) } companion object { @@ -103,66 +103,74 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { const val EMAIL_DOMAIN_RULES_ERROR = "domain_rules_error@example.com" val LOGIN_API_ERROR_RESPONSE = LoginResponse( - ApiError( - 400, "Something wrong happened.", - "api input: missing key: token" - ), null + ApiError( + 400, "Something wrong happened.", + "api input: missing key: token" + ), null ) val DOMAIN_RULES_ERROR_RESPONSE = DomainRulesResponse( - ApiError( - 401, - "Not logged in or unknown account", "auth" - ), null + ApiError( + 401, + "Not logged in or unknown account", "auth" + ), null ) @get:ClassRule @JvmStatic val mockWebServerRule = - FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { - override fun dispatch(request: RecordedRequest): MockResponse { - val gson = - ApiHelper.getInstance(InstrumentationRegistry.getInstrumentation().targetContext).gson - val model = - gson.fromJson(InputStreamReader(request.body.inputStream()), LoginModel::class.java) - - if (request.path.equals("/account/login")) { - when (model.account) { - EMAIL_LOGIN_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LOGIN_API_ERROR_RESPONSE)) - - EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = false))) - - EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = true))) - - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = true))) - } + FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + val gson = + ApiHelper.getInstance(InstrumentationRegistry.getInstrumentation().targetContext).gson + val model = + gson.fromJson(InputStreamReader(request.body.inputStream()), LoginModel::class.java) + + if (request.path.equals("/account/login")) { + when (model.account) { + EMAIL_LOGIN_ERROR -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LOGIN_API_ERROR_RESPONSE)) + + EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = false))) + + EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + + EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = true))) } + } - if (request.path.equals("/account/get")) { - when (model.account) { - EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(DOMAIN_RULES_ERROR_RESPONSE)) - - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody( - gson.toJson( - DomainRulesResponse( - null, - DomainRules(listOf( - OrgRules.DomainRule.NO_PRV_CREATE, - OrgRules.DomainRule.NO_PRV_BACKUP)) - ) - ) + if (request.path.equals("/account/get")) { + when (model.account) { + EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(DOMAIN_RULES_ERROR_RESPONSE)) + + EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) + .setBody( + gson.toJson( + DomainRulesResponse( + apiError = null, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.NO_PRV_CREATE, + OrgRules.DomainRule.NO_PRV_BACKUP + ), + customKeyserverUrl = null, + keyManagerUrl = null, + disallowAttesterSearchForDomains = null, + enforceKeygenAlgo = null, + enforceKeygenExpireMonths = null + ) ) - } + ) + ) } - - return MockResponse().setResponseCode(404) } - }) + + return MockResponse().setResponseCode(404) + } + }) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt index 1215813959..63cebd7f45 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt @@ -27,7 +27,6 @@ import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel 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.model.node.NodeKeyDetails import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.ActionQueueEntity import com.flowcrypt.email.database.entity.KeyEntity diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt index 6ac92b54fe..95007c3625 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SubmitPubKeyViewModel.kt @@ -16,7 +16,6 @@ import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitRes 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.model.node.NodeKeyDetails import com.flowcrypt.email.database.FlowCryptRoomDatabase import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.ActionQueueEntity diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index d1bb38f326..4d2f68fff2 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -20,7 +20,6 @@ import com.flowcrypt.email.api.email.JavaEmailConstants import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse 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.model.node.NodeKeyDetails import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.jetpack.viewmodel.EnterpriseDomainRulesViewModel From 0f0fe48637d2fe2badd44889df90e37bbe965d1c Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 22 Jun 2021 17:17:14 +0300 Subject: [PATCH 04/41] Fixed OrgRules. Added skipping 'googlemail.com'.| #1168 --- .../email/api/email/JavaEmailConstants.kt | 1 + .../api/retrofit/response/model/OrgRules.kt | 25 ++++++++++--------- .../activity/fragment/MainSignInFragment.kt | 13 +++++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/JavaEmailConstants.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/JavaEmailConstants.kt index fea2b01c99..18ab4b5651 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/JavaEmailConstants.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/JavaEmailConstants.kt @@ -90,5 +90,6 @@ class JavaEmailConstants { const val FOLDER_FLAG_HAS_NO_CHILDREN = "\\HasNoChildren" const val EMAIL_PROVIDER_GMAIL = "gmail.com" + const val EMAIL_PROVIDER_GOOGLEMAIL = "googlemail.com" } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt index 1cb3905158..aa94b20241 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -72,7 +72,7 @@ data class OrgRules constructor( * use this method when using for PUB sync */ fun getKeyManagerUrlForPublicKeys(): String? { - if (flags?.first { it == DomainRule.NO_KEY_MANAGER_PUB_LOOKUP } != null) { + if (flags?.firstOrNull { it == DomainRule.NO_KEY_MANAGER_PUB_LOOKUP } != null) { return null } return keyManagerUrl @@ -106,14 +106,14 @@ data class OrgRules constructor( * (and forbid keygen in the extension) */ fun canCreateKeys(): Boolean { - return flags?.first { it == DomainRule.NO_PRV_CREATE } == null + return flags?.firstOrNull { it == DomainRule.NO_PRV_CREATE } == null } /** * Some orgs want to forbid backing up of public keys (such as inbox or other methods) */ fun canBackupKeys(): Boolean { - return flags?.first { it == DomainRule.NO_PRV_BACKUP } == null + return flags?.firstOrNull { it == DomainRule.NO_PRV_BACKUP } == null } /** @@ -122,7 +122,7 @@ data class OrgRules constructor( * that their public key gets submitted to attester and conflict errors are NOT ignored: */ fun mustSubmitToAttester(): Boolean { - return flags?.first { it == DomainRule.ENFORCE_ATTESTER_SUBMIT } != null + return flags?.firstOrNull { it == DomainRule.ENFORCE_ATTESTER_SUBMIT } != null } /** @@ -131,7 +131,7 @@ data class OrgRules constructor( * This behavior is also enabled as a byproduct of PASS_PHRASE_QUIET_AUTOGEN */ fun rememberPassPhraseByDefault(): Boolean { - return flags?.first { it == DomainRule.DEFAULT_REMEMBER_PASS_PHRASE } != null + return flags?.firstOrNull { it == DomainRule.DEFAULT_REMEMBER_PASS_PHRASE } != null || this.mustAutoGenPassPhraseQuietly() } @@ -141,12 +141,13 @@ data class OrgRules constructor( * If not, it will be autogenerated and stored there */ fun mustAutoImportOrAutoGenPrvWithKeyManager(): Boolean { - if (flags?.first { it == DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN } == null) { + if (flags?.firstOrNull { it == DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN } == null) { return false } if (getKeyManagerUrlForPrivateKeys() != null) { throw IllegalStateException( - "Wrong org rules config: using PRV_AUTOIMPORT_OR_AUTOGEN without key_manager_url") + "Wrong org rules config: using PRV_AUTOIMPORT_OR_AUTOGEN without key_manager_url" + ) } return true } @@ -159,14 +160,14 @@ data class OrgRules constructor( * full-disk-encryption and don't need pass phrase protection */ fun mustAutoGenPassPhraseQuietly(): Boolean { - return flags?.first { it == DomainRule.PASS_PHRASE_QUIET_AUTOGEN } != null + return flags?.firstOrNull { it == DomainRule.PASS_PHRASE_QUIET_AUTOGEN } != null } /** * Some orgs prefer to forbid publishing public keys publicly */ fun canSubmitPubToAttester(): Boolean { - return flags?.first { it == DomainRule.NO_ATTESTER_SUBMIT } == null + return flags?.firstOrNull { it == DomainRule.NO_ATTESTER_SUBMIT } == null } /** @@ -187,7 +188,7 @@ data class OrgRules constructor( * the original endpoint */ fun useLegacyAttesterSubmit(): Boolean { - return flags?.first { it == DomainRule.USE_LEGACY_ATTESTER_SUBMIT } != null + return flags?.firstOrNull { it == DomainRule.USE_LEGACY_ATTESTER_SUBMIT } != null } /** @@ -195,7 +196,7 @@ data class OrgRules constructor( * imported keys get imported without armor */ fun shouldHideArmorMeta(): Boolean { - return flags?.first { it == DomainRule.HIDE_ARMOR_META } != null + return flags?.firstOrNull { it == DomainRule.HIDE_ARMOR_META } != null } enum class DomainRule : Parcelable { @@ -252,4 +253,4 @@ data class OrgRules constructor( return arrayOfNulls(size) } } -} \ No newline at end of file +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 4d2f68fff2..1c2f86f62d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -197,7 +197,13 @@ class MainSignInFragment : BaseSingInFragment() { val account = googleSignInAccount?.account?.name ?: return val idToken = googleSignInAccount?.idToken ?: return uuid = SecurityUtils.generateRandomUUID() - if (JavaEmailConstants.EMAIL_PROVIDER_GMAIL.equals(EmailUtil.getDomain(account), true)) { + + val regularDomains = arrayOf( + JavaEmailConstants.EMAIL_PROVIDER_GMAIL, + JavaEmailConstants.EMAIL_PROVIDER_GOOGLEMAIL + ) + + if (EmailUtil.getDomain(account).toLowerCase(Locale.US) in regularDomains) { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { @@ -232,9 +238,10 @@ class MainSignInFragment : BaseSingInFragment() { if (existedAccount == null) { getTempAccount()?.let { - if (domainRules?.first { rule -> rule == OrgRules.DomainRule.NO_PRV_BACKUP } != null) { + if (domainRules?.firstOrNull { rule -> rule == OrgRules.DomainRule.NO_PRV_BACKUP } != null) { requireContext().startService( - Intent(requireContext(), CheckClipboardToFindKeyService::class.java)) + Intent(requireContext(), CheckClipboardToFindKeyService::class.java) + ) val intent = CreateOrImportKeyActivity.newIntent(requireContext(), it, true) startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY) } else { From b38d52457efdbdcd442f9e068ef567d83f3ca427 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 22 Jun 2021 17:20:28 +0300 Subject: [PATCH 05/41] Renamed EnterpriseDomainRulesViewModel to EkmLoginViewModel.| #1168 --- ...iseDomainRulesViewModel.kt => EkmLoginViewModel.kt} | 2 +- .../email/ui/activity/fragment/MainSignInFragment.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/{EnterpriseDomainRulesViewModel.kt => EkmLoginViewModel.kt} (96%) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EnterpriseDomainRulesViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt similarity index 96% rename from FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EnterpriseDomainRulesViewModel.kt rename to FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index d78ecf0306..e68c80ef3f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EnterpriseDomainRulesViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.launch * Time: 12:36 PM * E-mail: DenBond7@gmail.com */ -class EnterpriseDomainRulesViewModel(application: Application) : BaseAndroidViewModel(application) { +class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(application) { private val repository: ApiRepository = FlowcryptApiRepository() val domainRulesLiveData: MutableLiveData?> = MutableLiveData() diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 1c2f86f62d..4176cdeaef 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -22,7 +22,7 @@ import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.showInfoDialog -import com.flowcrypt.email.jetpack.viewmodel.EnterpriseDomainRulesViewModel +import com.flowcrypt.email.jetpack.viewmodel.EkmLoginViewModel import com.flowcrypt.email.model.KeyImportDetails import com.flowcrypt.email.security.SecurityUtils import com.flowcrypt.email.security.model.PgpKeyDetails @@ -62,7 +62,7 @@ class MainSignInFragment : BaseSingInFragment() { private var uuid: String? = null private var domainRules: List? = null - private val enterpriseDomainRulesViewModel: EnterpriseDomainRulesViewModel by viewModels() + private val ekmLoginViewModel: EkmLoginViewModel by viewModels() override val progressView: View? get() = view?.findViewById(R.id.progress) @@ -207,7 +207,7 @@ class MainSignInFragment : BaseSingInFragment() { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { - uuid?.let { enterpriseDomainRulesViewModel.getDomainRules(account, it, idToken) } + uuid?.let { ekmLoginViewModel.getDomainRules(account, it, idToken) } } } else { val error = task.exception @@ -379,7 +379,7 @@ class MainSignInFragment : BaseSingInFragment() { } private fun setupEnterpriseViewModel() { - enterpriseDomainRulesViewModel.domainRulesLiveData.observe(viewLifecycleOwner, { + ekmLoginViewModel.domainRulesLiveData.observe(viewLifecycleOwner, { it?.let { when (it.status) { Result.Status.LOADING -> { @@ -390,7 +390,7 @@ class MainSignInFragment : BaseSingInFragment() { val result = it.data as? DomainRulesResponse domainRules = result?.orgRules?.flags ?: emptyList() onSignSuccess(googleSignInAccount) - enterpriseDomainRulesViewModel.domainRulesLiveData.value = null + ekmLoginViewModel.domainRulesLiveData.value = null } Result.Status.ERROR -> { From 803db55b40a93d661bf5fe23c16adef2d6ebb972 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 22 Jun 2021 17:47:06 +0300 Subject: [PATCH 06/41] Modified EkmLoginViewModel. Added a dialog with 'retry' button in MainSignInFragment.| #1168 --- .../jetpack/viewmodel/EkmLoginViewModel.kt | 18 ++++--- .../activity/fragment/MainSignInFragment.kt | 54 +++++++++++++------ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index e68c80ef3f..637005dd74 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -34,13 +34,15 @@ import kotlinx.coroutines.launch */ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(application) { private val repository: ApiRepository = FlowcryptApiRepository() - val domainRulesLiveData: MutableLiveData?> = MutableLiveData() + val ekmLiveData: MutableLiveData?> = MutableLiveData() - fun getDomainRules(account: String, uuid: String, tokenId: String) { - domainRulesLiveData.value = Result.loading() + fun login(account: String, uuid: String, tokenId: String) { + ekmLiveData.value = Result.loading() val context: Context = getApplication() viewModelScope.launch { + ekmLiveData.value = + Result.loading(progressMsg = context.getString(R.string.loading)) val loginResult = repository.login( context, LoginRequest(ApiName.POST_LOGIN, LoginModel(account, uuid), tokenId) @@ -48,19 +50,21 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica when (loginResult.status) { Result.Status.ERROR, Result.Status.EXCEPTION -> { - domainRulesLiveData.value = loginResult + ekmLiveData.value = loginResult return@launch } Result.Status.SUCCESS -> { if (loginResult.data?.isVerified == true) { + ekmLiveData.value = + Result.loading(progressMsg = context.getString(R.string.loading_domain_rules)) val domainRulesResult = repository.getDomainRules( context, DomainRulesRequest(ApiName.POST_GET_DOMAIN_RULES, LoginModel(account, uuid)) ) - domainRulesLiveData.value = domainRulesResult + ekmLiveData.value = domainRulesResult } else { - domainRulesLiveData.value = Result.error( + ekmLiveData.value = Result.error( LoginResponse( ApiError( -1, @@ -72,7 +76,7 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica } else -> { - domainRulesLiveData.value = Result.exception(IllegalStateException("Unhandled error")) + ekmLiveData.value = Result.exception(IllegalStateException("Unhandled error")) } } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 4176cdeaef..a736e8d7a8 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -21,7 +21,10 @@ import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity +import com.flowcrypt.email.extensions.decrementSafely +import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.showInfoDialog +import com.flowcrypt.email.extensions.showTwoWayDialog import com.flowcrypt.email.jetpack.viewmodel.EkmLoginViewModel import com.flowcrypt.email.model.KeyImportDetails import com.flowcrypt.email.security.SecurityUtils @@ -33,6 +36,7 @@ import com.flowcrypt.email.ui.activity.CreateOrImportKeyActivity import com.flowcrypt.email.ui.activity.HtmlViewFromAssetsRawActivity import com.flowcrypt.email.ui.activity.SignInActivity import com.flowcrypt.email.ui.activity.fragment.base.BaseSingInFragment +import com.flowcrypt.email.ui.activity.fragment.dialog.TwoWayDialogFragment import com.flowcrypt.email.ui.activity.settings.FeedbackActivity import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.exception.AccountAlreadyAddedException @@ -119,6 +123,17 @@ class MainSignInFragment : BaseSingInFragment() { handleResultFromCheckKeysActivity(resultCode, data) } + REQUEST_CODE_RETRY_EKM_LOGIN -> { + when (resultCode) { + TwoWayDialogFragment.RESULT_OK -> { + val id = uuid ?: return + val account = googleSignInAccount?.account?.name ?: return + val idToken = googleSignInAccount?.idToken ?: return + ekmLoginViewModel.login(account, id, idToken) + } + } + } + else -> super.onActivityResult(requestCode, resultCode, data) } } @@ -207,7 +222,7 @@ class MainSignInFragment : BaseSingInFragment() { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { - uuid?.let { ekmLoginViewModel.getDomainRules(account, it, idToken) } + uuid?.let { ekmLoginViewModel.login(account, it, idToken) } } } else { val error = task.exception @@ -379,34 +394,38 @@ class MainSignInFragment : BaseSingInFragment() { } private fun setupEnterpriseViewModel() { - ekmLoginViewModel.domainRulesLiveData.observe(viewLifecycleOwner, { + ekmLoginViewModel.ekmLiveData.observe(viewLifecycleOwner, { it?.let { when (it.status) { Result.Status.LOADING -> { - showProgress(progressMsg = getString(R.string.loading_domain_rules)) + if (it.progressMsg == null) { + baseActivity.countingIdlingResource.incrementSafely() + } + showProgress(progressMsg = it.progressMsg) } Result.Status.SUCCESS -> { val result = it.data as? DomainRulesResponse domainRules = result?.orgRules?.flags ?: emptyList() onSignSuccess(googleSignInAccount) - ekmLoginViewModel.domainRulesLiveData.value = null - } - - Result.Status.ERROR -> { - showContent() - Toast.makeText( - requireContext(), it.data?.apiError?.msg - ?: getString(R.string.could_not_load_domain_rules), Toast.LENGTH_SHORT - ).show() + ekmLoginViewModel.ekmLiveData.value = null + baseActivity.countingIdlingResource.decrementSafely() } - Result.Status.EXCEPTION -> { + Result.Status.ERROR, Result.Status.EXCEPTION -> { showContent() - Toast.makeText( - requireContext(), it.exception?.message - ?: getString(R.string.could_not_load_domain_rules), Toast.LENGTH_SHORT - ).show() + val errorMsg = it.data?.apiError?.msg + ?: it.exception?.message + ?: getString(R.string.could_not_load_domain_rules) + showTwoWayDialog( + requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, + dialogTitle = "", + dialogMsg = errorMsg, + positiveButtonTitle = getString(R.string.retry), + negativeButtonTitle = getString(R.string.cancel), + isCancelable = true + ) + baseActivity.countingIdlingResource.decrementSafely() } } } @@ -463,5 +482,6 @@ class MainSignInFragment : BaseSingInFragment() { private const val REQUEST_CODE_RESOLVE_SIGN_IN_ERROR = 101 private const val REQUEST_CODE_CREATE_OR_IMPORT_KEY = 102 private const val REQUEST_CODE_CHECK_PRIVATE_KEYS_FROM_GMAIL = 103 + private const val REQUEST_CODE_RETRY_EKM_LOGIN = 104 } } From 7e843d2e303bf08ecd73ae4f5b51e0605671d5d7 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 22 Jun 2021 18:13:00 +0300 Subject: [PATCH 07/41] Handled a case when orgRules.usesKeyManager() == false.| #1168 --- .../jetpack/viewmodel/EkmLoginViewModel.kt | 16 +++++++-- .../activity/fragment/MainSignInFragment.kt | 36 +++++++++++-------- .../exception/EkmNotSupportedException.kt | 16 +++++++++ 3 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/EkmNotSupportedException.kt diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index 637005dd74..2de7c56758 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -22,6 +22,7 @@ import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.base.ApiError import com.flowcrypt.email.api.retrofit.response.base.ApiResponse import com.flowcrypt.email.api.retrofit.response.base.Result +import com.flowcrypt.email.util.exception.EkmNotSupportedException import kotlinx.coroutines.launch /** @@ -36,7 +37,7 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica private val repository: ApiRepository = FlowcryptApiRepository() val ekmLiveData: MutableLiveData?> = MutableLiveData() - fun login(account: String, uuid: String, tokenId: String) { + fun fetchPrvKeys(account: String, uuid: String, tokenId: String) { ekmLiveData.value = Result.loading() val context: Context = getApplication() @@ -62,7 +63,18 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica context, DomainRulesRequest(ApiName.POST_GET_DOMAIN_RULES, LoginModel(account, uuid)) ) - ekmLiveData.value = domainRulesResult + + if (domainRulesResult.status == Result.Status.SUCCESS) { + if (domainRulesResult.data?.orgRules?.usesKeyManager() == false) { + ekmLiveData.value = + Result.exception(EkmNotSupportedException(domainRulesResult.data.orgRules)) + return@launch + } + + ekmLiveData.value = domainRulesResult + } else { + ekmLiveData.value = domainRulesResult + } } else { ekmLiveData.value = Result.error( LoginResponse( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index a736e8d7a8..c299f5fdfe 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -40,6 +40,7 @@ import com.flowcrypt.email.ui.activity.fragment.dialog.TwoWayDialogFragment import com.flowcrypt.email.ui.activity.settings.FeedbackActivity import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.exception.AccountAlreadyAddedException +import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.ExceptionUtil import com.flowcrypt.email.util.google.GoogleApiClientHelper import com.google.android.gms.auth.api.signin.GoogleSignIn @@ -129,7 +130,7 @@ class MainSignInFragment : BaseSingInFragment() { val id = uuid ?: return val account = googleSignInAccount?.account?.name ?: return val idToken = googleSignInAccount?.idToken ?: return - ekmLoginViewModel.login(account, id, idToken) + ekmLoginViewModel.fetchPrvKeys(account, id, idToken) } } } @@ -222,7 +223,7 @@ class MainSignInFragment : BaseSingInFragment() { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { - uuid?.let { ekmLoginViewModel.login(account, it, idToken) } + uuid?.let { ekmLoginViewModel.fetchPrvKeys(account, it, idToken) } } } else { val error = task.exception @@ -408,23 +409,30 @@ class MainSignInFragment : BaseSingInFragment() { val result = it.data as? DomainRulesResponse domainRules = result?.orgRules?.flags ?: emptyList() onSignSuccess(googleSignInAccount) - ekmLoginViewModel.ekmLiveData.value = null + ekmLoginViewModel.ekmLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } Result.Status.ERROR, Result.Status.EXCEPTION -> { showContent() - val errorMsg = it.data?.apiError?.msg - ?: it.exception?.message - ?: getString(R.string.could_not_load_domain_rules) - showTwoWayDialog( - requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, - dialogTitle = "", - dialogMsg = errorMsg, - positiveButtonTitle = getString(R.string.retry), - negativeButtonTitle = getString(R.string.cancel), - isCancelable = true - ) + + if (it.exception is EkmNotSupportedException) { + domainRules = it.exception.orgRules.flags + onSignSuccess(googleSignInAccount) + } else { + val errorMsg = it.data?.apiError?.msg + ?: it.exception?.message + ?: getString(R.string.could_not_load_domain_rules) + showTwoWayDialog( + requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, + dialogTitle = "", + dialogMsg = errorMsg, + positiveButtonTitle = getString(R.string.retry), + negativeButtonTitle = getString(R.string.cancel), + isCancelable = true + ) + } + ekmLoginViewModel.ekmLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/EkmNotSupportedException.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/EkmNotSupportedException.kt new file mode 100644 index 0000000000..49df791b9d --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/EkmNotSupportedException.kt @@ -0,0 +1,16 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.util.exception + +import com.flowcrypt.email.api.retrofit.response.model.OrgRules + +/** + * @author Denis Bondarenko + * Date: 6/22/21 + * Time: 5:53 PM + * E-mail: DenBond7@gmail.com + */ +class EkmNotSupportedException(val orgRules: OrgRules) : FlowCryptException() From 8cb4d03a0aa04f702f8ce8361884fa806fd70a65 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 22 Jun 2021 20:20:38 +0300 Subject: [PATCH 08/41] Added handling of OrgRulesCombinationNotSupportedException.| #1168 --- .../api/retrofit/response/model/OrgRules.kt | 53 ++++--- .../jetpack/viewmodel/EkmLoginViewModel.kt | 139 ++++++++++++------ .../activity/fragment/MainSignInFragment.kt | 46 ++++-- ...rgRulesCombinationNotSupportedException.kt | 19 +++ FlowCrypt/src/main/res/values/strings.xml | 1 + 5 files changed, 183 insertions(+), 75 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt index aa94b20241..77de4e2b04 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -18,26 +18,27 @@ import com.google.gson.annotations.SerializedName * E-mail: DenBond7@gmail.com */ data class OrgRules constructor( - @Expose val flags: List?, - @SerializedName("custom_keyserver_url") - @Expose val customKeyserverUrl: String?, - @SerializedName("key_manager_url") - @Expose val keyManagerUrl: String?, - @SerializedName("disallow_attester_search_for_domains") - @Expose val disallowAttesterSearchForDomains: List?, - @SerializedName("enforce_keygen_algo") - @Expose val enforceKeygenAlgo: KeyAlgo?, - @SerializedName("enforce_keygen_expire_months") - @Expose val enforceKeygenExpireMonths: Int? + @Expose val flags: List?, + @SerializedName("custom_keyserver_url") + @Expose val customKeyserverUrl: String?, + @SerializedName("key_manager_url") + @Expose val keyManagerUrl: String?, + @SerializedName("disallow_attester_search_for_domains") + @Expose val disallowAttesterSearchForDomains: List?, + @SerializedName("enforce_keygen_algo") + @Expose val enforceKeygenAlgo: KeyAlgo?, + @SerializedName("enforce_keygen_expire_months") + @Expose val enforceKeygenExpireMonths: Int? ) : Parcelable { constructor(parcel: Parcel) : this( - parcel.createTypedArrayList(DomainRule.CREATOR), - parcel.readString(), - parcel.readString(), - parcel.createStringArrayList(), - parcel.readParcelable(KeyAlgo::class.java.classLoader), - parcel.readValue(Int::class.java.classLoader) as? Int) + parcel.createTypedArrayList(DomainRule.CREATOR), + parcel.readString(), + parcel.readString(), + parcel.createStringArrayList(), + parcel.readParcelable(KeyAlgo::class.java.classLoader), + parcel.readValue(Int::class.java.classLoader) as? Int + ) override fun writeToParcel(parcel: Parcel, flagsList: Int) { parcel.writeTypedList(flags) @@ -135,6 +136,18 @@ data class OrgRules constructor( || this.mustAutoGenPassPhraseQuietly() } + fun forbidStoringPassPhrase(): Boolean { + return flags?.firstOrNull { it == DomainRule.FORBID_STORING_PASS_PHRASE } != null + } + + fun passPhraseMustBeChosenByUser(): Boolean { + return true + } + + fun forbidCreatingPrivateKey(): Boolean { + return true + } + /** * This is to be used for customers who run their own FlowCrypt Email Key Manager * If a key can be found on FEKM, it will be auto imported @@ -144,7 +157,7 @@ data class OrgRules constructor( if (flags?.firstOrNull { it == DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN } == null) { return false } - if (getKeyManagerUrlForPrivateKeys() != null) { + if (getKeyManagerUrlForPrivateKeys() == null) { throw IllegalStateException( "Wrong org rules config: using PRV_AUTOIMPORT_OR_AUTOGEN without key_manager_url" ) @@ -209,7 +222,9 @@ data class OrgRules constructor( NO_KEY_MANAGER_PUB_LOOKUP, USE_LEGACY_ATTESTER_SUBMIT, DEFAULT_REMEMBER_PASS_PHRASE, - HIDE_ARMOR_META; + HIDE_ARMOR_META, + FORBID_STORING_PASS_PHRASE, + PASS_PHRASE_CHOSEN_BY_USER; companion object CREATOR : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): DomainRule = values()[parcel.readInt()] diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index 2de7c56758..96b2c2d3a0 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -22,7 +22,9 @@ import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.base.ApiError 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.util.exception.EkmNotSupportedException +import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedException import kotlinx.coroutines.launch /** @@ -42,55 +44,110 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica val context: Context = getApplication() viewModelScope.launch { - ekmLiveData.value = - Result.loading(progressMsg = context.getString(R.string.loading)) - val loginResult = repository.login( - context, - LoginRequest(ApiName.POST_LOGIN, LoginModel(account, uuid), tokenId) - ) - - when (loginResult.status) { - Result.Status.ERROR, Result.Status.EXCEPTION -> { - ekmLiveData.value = loginResult - return@launch - } + ekmLiveData.value = Result.loading(progressMsg = context.getString(R.string.loading)) + try { + val loginResult = repository.login( + context, + LoginRequest(ApiName.POST_LOGIN, LoginModel(account, uuid), tokenId) + ) - Result.Status.SUCCESS -> { - if (loginResult.data?.isVerified == true) { - ekmLiveData.value = - Result.loading(progressMsg = context.getString(R.string.loading_domain_rules)) - val domainRulesResult = repository.getDomainRules( - context, - DomainRulesRequest(ApiName.POST_GET_DOMAIN_RULES, LoginModel(account, uuid)) - ) - - if (domainRulesResult.status == Result.Status.SUCCESS) { - if (domainRulesResult.data?.orgRules?.usesKeyManager() == false) { - ekmLiveData.value = - Result.exception(EkmNotSupportedException(domainRulesResult.data.orgRules)) - return@launch - } + when (loginResult.status) { + Result.Status.ERROR, Result.Status.EXCEPTION -> { + ekmLiveData.value = loginResult + return@launch + } + + Result.Status.SUCCESS -> { + if (loginResult.data?.isVerified == true) { + ekmLiveData.value = Result.loading( + progressMsg = context.getString(R.string.loading_domain_rules) + ) + val domainRulesResult = repository.getDomainRules( + context, + DomainRulesRequest(ApiName.POST_GET_DOMAIN_RULES, LoginModel(account, uuid)) + ) + + if (domainRulesResult.status == Result.Status.SUCCESS) { + if (domainRulesResult.data?.orgRules == null) { + ekmLiveData.value = Result.exception( + IllegalArgumentException("${OrgRules::class.java.simpleName} is not specified") + ) + return@launch + } - ekmLiveData.value = domainRulesResult + if (!domainRulesResult.data.orgRules.usesKeyManager() || + !domainRulesResult.data.orgRules.mustAutoImportOrAutoGenPrvWithKeyManager() + ) { + ekmLiveData.value = Result.exception( + EkmNotSupportedException(domainRulesResult.data.orgRules) + ) + return@launch + } + + val notSupportedCombination = + checkNotSupportedOrgRulesCombination(domainRulesResult.data.orgRules) + + if (notSupportedCombination.isNotEmpty()) { + ekmLiveData.value = Result.exception( + OrgRulesCombinationNotSupportedException( + orgRules = domainRulesResult.data.orgRules, + combination = notSupportedCombination + ) + ) + return@launch + } + + ekmLiveData.value = domainRulesResult + } else { + ekmLiveData.value = domainRulesResult + } } else { - ekmLiveData.value = domainRulesResult - } - } else { - ekmLiveData.value = Result.error( - LoginResponse( - ApiError( - -1, - context.getString(R.string.user_not_verified) - ), false + ekmLiveData.value = Result.error( + LoginResponse( + ApiError( + -1, + context.getString(R.string.user_not_verified) + ), false + ) ) - ) + } } - } - else -> { - ekmLiveData.value = Result.exception(IllegalStateException("Unhandled error")) + else -> { + ekmLiveData.value = Result.exception(IllegalStateException("Unhandled error")) + } } + } catch (e: Exception) { + ekmLiveData.value = Result.exception(e) } } } + + private fun checkNotSupportedOrgRulesCombination(orgRules: OrgRules): + Map { + val notSupportedCombination = mutableMapOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN to true + ) + + when { + !orgRules.passPhraseMustBeChosenByUser() -> { + notSupportedCombination[OrgRules.DomainRule.PASS_PHRASE_CHOSEN_BY_USER] = false + } + + !orgRules.forbidStoringPassPhrase() -> { + notSupportedCombination[OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE] = false + } + + orgRules.mustSubmitToAttester() -> { + notSupportedCombination[OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT] = true + } + + !orgRules.forbidCreatingPrivateKey() -> { + notSupportedCombination[OrgRules.DomainRule.NO_PRV_CREATE] = false + } + + else -> notSupportedCombination.clear() + } + return notSupportedCombination + } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index c299f5fdfe..834e0211ef 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -42,6 +42,7 @@ import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.exception.AccountAlreadyAddedException import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.ExceptionUtil +import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedException import com.flowcrypt.email.util.google.GoogleApiClientHelper import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount @@ -416,21 +417,36 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.ERROR, Result.Status.EXCEPTION -> { showContent() - if (it.exception is EkmNotSupportedException) { - domainRules = it.exception.orgRules.flags - onSignSuccess(googleSignInAccount) - } else { - val errorMsg = it.data?.apiError?.msg - ?: it.exception?.message - ?: getString(R.string.could_not_load_domain_rules) - showTwoWayDialog( - requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, - dialogTitle = "", - dialogMsg = errorMsg, - positiveButtonTitle = getString(R.string.retry), - negativeButtonTitle = getString(R.string.cancel), - isCancelable = true - ) + when (it.exception) { + is EkmNotSupportedException -> { + domainRules = it.exception.orgRules.flags + onSignSuccess(googleSignInAccount) + } + + is OrgRulesCombinationNotSupportedException -> { + showInfoDialog( + dialogTitle = "", + dialogMsg = getString( + R.string.combination_of_org_rules_is_not_supported, + it.exception.combination + ), + isCancelable = true + ) + } + + else -> { + val errorMsg = it.data?.apiError?.msg + ?: it.exception?.message + ?: getString(R.string.could_not_load_domain_rules) + showTwoWayDialog( + requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, + dialogTitle = "", + dialogMsg = errorMsg, + positiveButtonTitle = getString(R.string.retry), + negativeButtonTitle = getString(R.string.cancel), + isCancelable = true + ) + } } ekmLoginViewModel.ekmLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt new file mode 100644 index 0000000000..df1bbe9b9d --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt @@ -0,0 +1,19 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.util.exception + +import com.flowcrypt.email.api.retrofit.response.model.OrgRules + +/** + * @author Denis Bondarenko + * Date: 6/22/21 + * Time: 7:40 PM + * E-mail: DenBond7@gmail.com + */ +class OrgRulesCombinationNotSupportedException( + val orgRules: OrgRules, + val combination: Map +) : FlowCryptException() diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index 0bf9ee09e8..2ffde8a019 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -516,4 +516,5 @@ Please provide a pass phrase for the following key Please provide a pass phrase for at least one of the following keys + Combination of the following OrgRules\n\n%1$s\n\nis not supported From 79b938a6ddf9e14da0edf56989ad3c356b6a3f53 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 23 Jun 2021 09:20:57 +0300 Subject: [PATCH 09/41] Modifed logic in EkmLoginViewModel.| #1168 --- .../email/api/retrofit/response/model/OrgRules.kt | 9 ++------- .../email/jetpack/viewmodel/EkmLoginViewModel.kt | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt index 77de4e2b04..ba53f7c5cb 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -140,12 +140,8 @@ data class OrgRules constructor( return flags?.firstOrNull { it == DomainRule.FORBID_STORING_PASS_PHRASE } != null } - fun passPhraseMustBeChosenByUser(): Boolean { - return true - } - fun forbidCreatingPrivateKey(): Boolean { - return true + return flags?.firstOrNull { it == DomainRule.NO_PRV_CREATE } != null } /** @@ -223,8 +219,7 @@ data class OrgRules constructor( USE_LEGACY_ATTESTER_SUBMIT, DEFAULT_REMEMBER_PASS_PHRASE, HIDE_ARMOR_META, - FORBID_STORING_PASS_PHRASE, - PASS_PHRASE_CHOSEN_BY_USER; + FORBID_STORING_PASS_PHRASE; companion object CREATOR : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): DomainRule = values()[parcel.readInt()] diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index 96b2c2d3a0..0b517bea50 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -130,8 +130,8 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica ) when { - !orgRules.passPhraseMustBeChosenByUser() -> { - notSupportedCombination[OrgRules.DomainRule.PASS_PHRASE_CHOSEN_BY_USER] = false + orgRules.mustAutoGenPassPhraseQuietly() -> { + notSupportedCombination[OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN] = true } !orgRules.forbidStoringPassPhrase() -> { From cda98e903a347aee3e877350956becd3cc38af32 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 23 Jun 2021 11:33:53 +0300 Subject: [PATCH 10/41] Added '/v1/keys/private' API things.| #1168 --- .../email/api/retrofit/ApiRepository.kt | 15 +++++- .../email/api/retrofit/ApiService.kt | 10 ++++ .../api/retrofit/FlowcryptApiRepository.kt | 12 +++++ .../response/api/EkmPrivateKeysResponse.kt | 48 +++++++++++++++++++ .../jetpack/viewmodel/EkmLoginViewModel.kt | 9 +++- 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt 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 635b21ff1b..4e27f7b220 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 @@ -12,11 +12,13 @@ import com.flowcrypt.email.api.retrofit.request.api.LoginRequest import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse import com.flowcrypt.email.api.retrofit.response.attester.PubResponse import com.flowcrypt.email.api.retrofit.response.attester.TestWelcomeResponse 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.google.gson.JsonObject @@ -55,7 +57,7 @@ interface ApiRepository : BaseApiRepository { /** * @param context Interface to global information about an application environment. - * @param model An instance of [PostLookUpEmailsModel]. + * @param model An instance of [InitialLegacySubmitModel]. */ suspend fun postInitialLegacySubmit( context: Context, @@ -103,4 +105,15 @@ interface ApiRepository : BaseApiRepository { context: Context, url: String ): Result + + /** + * Get private keys via "/v1/keys/private" + * + * @param context Interface to global information about an application environment. + * @param ekmUrl key_manager_url from [OrgRules]. + * @param tokenId OIDC token. + */ + suspend fun getPrivateKeysViaEkm( + context: Context, ekmUrl: String, tokenId: String + ): Result } 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 ecbd471c6f..8cf9879aeb 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 @@ -12,6 +12,7 @@ import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.request.model.PostHelpFeedbackModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse @@ -139,4 +140,13 @@ interface ApiService { @GET suspend fun getOpenIdConfiguration(@Url url: String): Response + + /** + * Get private keys via "/v1/keys/private" + */ + @GET + suspend fun getPrivateKeysViaEkm( + @Url ekmUrl: String, + @Header("Authorization") tokenId: String + ): Response } 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 d186650a57..8002689ad7 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 @@ -12,6 +12,7 @@ import com.flowcrypt.email.api.retrofit.request.api.LoginRequest import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse import com.flowcrypt.email.api.retrofit.response.attester.PubResponse @@ -136,4 +137,15 @@ class FlowcryptApiRepository : ApiRepository { apiService.getOpenIdConfiguration(url) } } + + override suspend fun getPrivateKeysViaEkm( + context: Context, + ekmUrl: String, + tokenId: String + ): Result = + withContext(Dispatchers.IO) { + val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) + val url = if (ekmUrl.endsWith("/")) ekmUrl else "$ekmUrl/" + getResult { apiService.getPrivateKeysViaEkm("${url}v1/keys/private", "Bearer $tokenId") } + } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt new file mode 100644 index 0000000000..58cdbb80aa --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt @@ -0,0 +1,48 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.api.retrofit.response.api + +import android.os.Parcel +import android.os.Parcelable +import com.flowcrypt.email.api.retrofit.response.base.ApiError +import com.flowcrypt.email.api.retrofit.response.base.ApiResponse +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +/** + * @author Denis Bondarenko + * Date: 6/23/21 + * Time: 9:50 AM + * E-mail: DenBond7@gmail.com + */ +class EkmPrivateKeysResponse constructor( + @SerializedName("error") + @Expose override val apiError: ApiError? +) : ApiResponse { + constructor(parcel: Parcel) : this( + parcel.readParcelable(ApiError::class.java.classLoader) + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + with(parcel) { + writeParcelable(apiError, flags) + } + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): EkmPrivateKeysResponse { + return EkmPrivateKeysResponse(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index 0b517bea50..723ac05fa0 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -97,7 +97,14 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica return@launch } - ekmLiveData.value = domainRulesResult + val ekmPrivateResult = repository.getPrivateKeysViaEkm( + context = context, + ekmUrl = domainRulesResult.data.orgRules.keyManagerUrl + ?: throw java.lang.IllegalArgumentException("key_manager_url is empty"), + tokenId = tokenId + ) + + ekmLiveData.value = ekmPrivateResult } else { ekmLiveData.value = domainRulesResult } From 3b01aec18eeabb0847f2c91dfe075cea639ffd0e Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 10:55:31 +0300 Subject: [PATCH 11/41] Fixed compilation errors after merge.| #1168 --- .../fragment/ChangePassphraseOfImportedKeysFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ChangePassphraseOfImportedKeysFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ChangePassphraseOfImportedKeysFragment.kt index bfb01bd5ab..293f6c9246 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ChangePassphraseOfImportedKeysFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ChangePassphraseOfImportedKeysFragment.kt @@ -14,7 +14,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result -import com.flowcrypt.email.database.entity.AccountEntity +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.databinding.FragmentChangePassphraseOfImportedKeysBinding import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.exceptionMsg @@ -111,7 +111,7 @@ class ChangePassphraseOfImportedKeysFragment : BaseFragment(), ProgressBehaviour Result.Status.SUCCESS -> { isPassphraseChanged = true - if (args.accountEntity.isRuleExist(AccountEntity.DomainRule.NO_PRV_BACKUP)) { + if (args.accountEntity.isRuleExist(OrgRules.DomainRule.NO_PRV_BACKUP)) { //making backups is not allowed by OrgRules. isBackEnabled = true showContent() From 3400d8c48ca5c1c0d5f843a024680135703aee5f Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 14:41:40 +0300 Subject: [PATCH 12/41] Added handling errors for EkmPrivateKeysResponse.| #1168 --- .../email/api/retrofit/FlowcryptApiRepository.kt | 5 ++++- .../retrofit/response/api/EkmPrivateKeysResponse.kt | 10 +++++++--- .../email/ui/activity/fragment/MainSignInFragment.kt | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) 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 8002689ad7..5e0bf59c3f 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 @@ -146,6 +146,9 @@ class FlowcryptApiRepository : ApiRepository { withContext(Dispatchers.IO) { val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) val url = if (ekmUrl.endsWith("/")) ekmUrl else "$ekmUrl/" - getResult { apiService.getPrivateKeysViaEkm("${url}v1/keys/private", "Bearer $tokenId") } + getResult( + context = context, + expectedResultClass = EkmPrivateKeysResponse::class.java + ) { apiService.getPrivateKeysViaEkm("${url}v1/keys/private", "Bearer $tokenId") } } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt index 58cdbb80aa..75a36358cd 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt @@ -10,7 +10,6 @@ import android.os.Parcelable import com.flowcrypt.email.api.retrofit.response.base.ApiError import com.flowcrypt.email.api.retrofit.response.base.ApiResponse import com.google.gson.annotations.Expose -import com.google.gson.annotations.SerializedName /** * @author Denis Bondarenko @@ -19,8 +18,8 @@ import com.google.gson.annotations.SerializedName * E-mail: DenBond7@gmail.com */ class EkmPrivateKeysResponse constructor( - @SerializedName("error") - @Expose override val apiError: ApiError? + @Expose val code: Int? = null, + @Expose val message: String? = null ) : ApiResponse { constructor(parcel: Parcel) : this( parcel.readParcelable(ApiError::class.java.classLoader) @@ -32,6 +31,11 @@ class EkmPrivateKeysResponse constructor( } } + override val apiError: ApiError? + get() = if (code != null) { + ApiError(code = code, msg = message) + } else null + override fun describeContents(): Int { return 0 } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 834e0211ef..012830e449 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -437,7 +437,7 @@ class MainSignInFragment : BaseSingInFragment() { else -> { val errorMsg = it.data?.apiError?.msg ?: it.exception?.message - ?: getString(R.string.could_not_load_domain_rules) + ?: getString(R.string.unknown_error) showTwoWayDialog( requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, dialogTitle = "", From 3258a6ea7dda84ca8e5fa868210ba616164105f5 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 14:54:26 +0300 Subject: [PATCH 13/41] Added 'Key' model. Modifed EkmPrivateKeysResponse.| #1168 --- .../response/api/EkmPrivateKeysResponse.kt | 16 +++++--- .../email/api/retrofit/response/model/Key.kt | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/Key.kt diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt index 75a36358cd..d585e0bc91 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt @@ -9,6 +9,7 @@ import android.os.Parcel import android.os.Parcelable import com.flowcrypt.email.api.retrofit.response.base.ApiError import com.flowcrypt.email.api.retrofit.response.base.ApiResponse +import com.flowcrypt.email.api.retrofit.response.model.Key import com.google.gson.annotations.Expose /** @@ -17,18 +18,21 @@ import com.google.gson.annotations.Expose * Time: 9:50 AM * E-mail: DenBond7@gmail.com */ -class EkmPrivateKeysResponse constructor( +data class EkmPrivateKeysResponse constructor( @Expose val code: Int? = null, - @Expose val message: String? = null + @Expose val message: String? = null, + @Expose val privateKeys: List? = null ) : ApiResponse { constructor(parcel: Parcel) : this( - parcel.readParcelable(ApiError::class.java.classLoader) + parcel.readValue(Int::class.java.classLoader) as? Int, + parcel.readString(), + parcel.createTypedArrayList(Key) ) override fun writeToParcel(parcel: Parcel, flags: Int) { - with(parcel) { - writeParcelable(apiError, flags) - } + parcel.writeValue(code) + parcel.writeString(message) + parcel.writeTypedList(privateKeys) } override val apiError: ApiError? diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/Key.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/Key.kt new file mode 100644 index 0000000000..a58228fbb9 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/Key.kt @@ -0,0 +1,38 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.api.retrofit.response.model + +import android.os.Parcel +import android.os.Parcelable +import com.google.gson.annotations.Expose + +/** + * @author Denis Bondarenko + * Date: 6/30/21 + * Time: 2:48 PM + * E-mail: DenBond7@gmail.com + */ +data class Key constructor(@Expose val decryptedPrivateKey: String? = null) : Parcelable { + constructor(parcel: Parcel) : this(parcel.readString()) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(decryptedPrivateKey) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Key { + return Key(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} From 3f7b5aee9e4dfef49ea7d853a03a4e5ccdbb86f4 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 15:00:39 +0300 Subject: [PATCH 14/41] Added showing an error if there are no private keys for a user(EKM).| #1168 --- .../flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt | 4 ++++ FlowCrypt/src/main/res/values/strings.xml | 1 + 2 files changed, 5 insertions(+) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt index 723ac05fa0..550dd43b36 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt @@ -104,6 +104,10 @@ class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(applica tokenId = tokenId ) + if (ekmPrivateResult.data?.privateKeys?.isEmpty() != true) { + throw java.lang.IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) + } + ekmLiveData.value = ekmPrivateResult } else { ekmLiveData.value = domainRulesResult diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index 6b6440c62e..29651b0ed0 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -521,4 +521,5 @@ Your pass phrase was changed. From now, the new backups will be protected with a provided pass phrase. Processing, please wait… Could not change the pass phrase + There are no private keys configured for you. Please ask your systems administrator or help desk From f072e84a490dad23fe773b0eee097397ba28ab46 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 17:04:29 +0300 Subject: [PATCH 15/41] Refactored code.| #1168 --- .../email/ui/activity/fragment/MainSignInFragment.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 012830e449..918724a9ba 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -65,7 +65,7 @@ import java.util.* class MainSignInFragment : BaseSingInFragment() { private lateinit var client: GoogleSignInClient private var googleSignInAccount: GoogleSignInAccount? = null - private var uuid: String? = null + private var uuid: String = SecurityUtils.generateRandomUUID() private var domainRules: List? = null private val ekmLoginViewModel: EkmLoginViewModel by viewModels() @@ -128,10 +128,9 @@ class MainSignInFragment : BaseSingInFragment() { REQUEST_CODE_RETRY_EKM_LOGIN -> { when (resultCode) { TwoWayDialogFragment.RESULT_OK -> { - val id = uuid ?: return val account = googleSignInAccount?.account?.name ?: return val idToken = googleSignInAccount?.idToken ?: return - ekmLoginViewModel.fetchPrvKeys(account, id, idToken) + ekmLoginViewModel.fetchPrvKeys(account, uuid, idToken) } } } @@ -215,16 +214,16 @@ class MainSignInFragment : BaseSingInFragment() { val idToken = googleSignInAccount?.idToken ?: return uuid = SecurityUtils.generateRandomUUID() - val regularDomains = arrayOf( + val publicEmailDomains = arrayOf( JavaEmailConstants.EMAIL_PROVIDER_GMAIL, JavaEmailConstants.EMAIL_PROVIDER_GOOGLEMAIL ) - if (EmailUtil.getDomain(account).toLowerCase(Locale.US) in regularDomains) { + if (EmailUtil.getDomain(account).toLowerCase(Locale.US) in publicEmailDomains) { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { - uuid?.let { ekmLoginViewModel.fetchPrvKeys(account, it, idToken) } + ekmLoginViewModel.fetchPrvKeys(account, uuid, idToken) } } else { val error = task.exception From fd2b9fdb732a351a5efbb43229cbebb2e64c350a Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 19:01:00 +0300 Subject: [PATCH 16/41] Rename EkmLoginViewModel to LoginViewModel.| #1168 --- .../{EkmLoginViewModel.kt => LoginViewModel.kt} | 5 +++-- .../ui/activity/fragment/MainSignInFragment.kt | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) rename FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/{EkmLoginViewModel.kt => LoginViewModel.kt} (96%) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt similarity index 96% rename from FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt rename to FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt index 550dd43b36..4d88a292cd 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmLoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt @@ -10,6 +10,7 @@ package com.flowcrypt.email.jetpack.viewmodel import android.app.Application import android.content.Context import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.ApiName @@ -28,14 +29,14 @@ import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedExcepti import kotlinx.coroutines.launch /** - * This [androidx.lifecycle.ViewModel] can be used to resolve domain rules for enterprise users. + * This [ViewModel] can be used to login enterprise users. * * @author Denis Bondarenko * Date: 10/23/19 * Time: 12:36 PM * E-mail: DenBond7@gmail.com */ -class EkmLoginViewModel(application: Application) : BaseAndroidViewModel(application) { +class LoginViewModel(application: Application) : BaseAndroidViewModel(application) { private val repository: ApiRepository = FlowcryptApiRepository() val ekmLiveData: MutableLiveData?> = MutableLiveData() diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 918724a9ba..a50a36d54c 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -25,7 +25,7 @@ import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.extensions.showTwoWayDialog -import com.flowcrypt.email.jetpack.viewmodel.EkmLoginViewModel +import com.flowcrypt.email.jetpack.viewmodel.LoginViewModel import com.flowcrypt.email.model.KeyImportDetails import com.flowcrypt.email.security.SecurityUtils import com.flowcrypt.email.security.model.PgpKeyDetails @@ -68,7 +68,7 @@ class MainSignInFragment : BaseSingInFragment() { private var uuid: String = SecurityUtils.generateRandomUUID() private var domainRules: List? = null - private val ekmLoginViewModel: EkmLoginViewModel by viewModels() + private val loginViewModel: LoginViewModel by viewModels() override val progressView: View? get() = view?.findViewById(R.id.progress) @@ -130,7 +130,7 @@ class MainSignInFragment : BaseSingInFragment() { TwoWayDialogFragment.RESULT_OK -> { val account = googleSignInAccount?.account?.name ?: return val idToken = googleSignInAccount?.idToken ?: return - ekmLoginViewModel.fetchPrvKeys(account, uuid, idToken) + loginViewModel.fetchPrvKeys(account, uuid, idToken) } } } @@ -223,7 +223,7 @@ class MainSignInFragment : BaseSingInFragment() { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { - ekmLoginViewModel.fetchPrvKeys(account, uuid, idToken) + loginViewModel.fetchPrvKeys(account, uuid, idToken) } } else { val error = task.exception @@ -395,7 +395,7 @@ class MainSignInFragment : BaseSingInFragment() { } private fun setupEnterpriseViewModel() { - ekmLoginViewModel.ekmLiveData.observe(viewLifecycleOwner, { + loginViewModel.ekmLiveData.observe(viewLifecycleOwner, { it?.let { when (it.status) { Result.Status.LOADING -> { @@ -409,7 +409,7 @@ class MainSignInFragment : BaseSingInFragment() { val result = it.data as? DomainRulesResponse domainRules = result?.orgRules?.flags ?: emptyList() onSignSuccess(googleSignInAccount) - ekmLoginViewModel.ekmLiveData.value = Result.none() + loginViewModel.ekmLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } @@ -447,7 +447,7 @@ class MainSignInFragment : BaseSingInFragment() { ) } } - ekmLoginViewModel.ekmLiveData.value = Result.none() + loginViewModel.ekmLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } } From 1ea598ec54e5e725c4d389f4f2f8189aa3504234 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Wed, 30 Jun 2021 20:13:43 +0300 Subject: [PATCH 17/41] Exported logic to separate ViewModel(s). Refactored code.| #1168 --- .../AddNewAccountActivityEnterpriseTest.kt | 26 +-- .../SignInActivityEnterpriseTest.kt | 10 +- .../flowcrypt/email/api/retrofit/ApiName.kt | 11 +- .../email/api/retrofit/ApiRepository.kt | 22 ++- .../email/api/retrofit/ApiService.kt | 4 +- .../api/retrofit/FlowcryptApiRepository.kt | 27 ++- .../request/api/DomainRulesRequest.kt | 24 --- .../api/retrofit/request/api/LoginRequest.kt | 24 --- ...sResponse.kt => DomainOrgRulesResponse.kt} | 23 ++- .../viewmodel/DomainOrgRulesViewModel.kt | 49 +++++ .../email/jetpack/viewmodel/EkmViewModel.kt | 104 ++++++++++ .../email/jetpack/viewmodel/LoginViewModel.kt | 136 +------------ .../activity/fragment/MainSignInFragment.kt | 187 +++++++++++++----- FlowCrypt/src/main/res/values/strings.xml | 2 + 14 files changed, 370 insertions(+), 279 deletions(-) delete mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/DomainRulesRequest.kt delete mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/LoginRequest.kt rename FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/{DomainRulesResponse.kt => DomainOrgRulesResponse.kt} (59%) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DomainOrgRulesViewModel.kt create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt 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 5ef15dd6b6..31cfcff3fe 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 @@ -19,7 +19,7 @@ import com.flowcrypt.email.R import com.flowcrypt.email.TestConstants import com.flowcrypt.email.api.retrofit.ApiHelper import com.flowcrypt.email.api.retrofit.request.model.LoginModel -import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.junit.annotations.NotReadyForCI @@ -97,17 +97,19 @@ class AddNewAccountActivityEnterpriseTest : BaseSignActivityTest() { when (model.account) { EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) .setBody(gson.toJson( - DomainRulesResponse( - apiError = null, - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.NO_PRV_CREATE, - OrgRules.DomainRule.NO_PRV_BACKUP), - customKeyserverUrl = null, - keyManagerUrl = null, - disallowAttesterSearchForDomains = null, - enforceKeygenAlgo = null, - enforceKeygenExpireMonths = null) + DomainOrgRulesResponse( + apiError = null, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.NO_PRV_CREATE, + OrgRules.DomainRule.NO_PRV_BACKUP + ), + customKeyserverUrl = null, + keyManagerUrl = null, + disallowAttesterSearchForDomains = null, + enforceKeygenAlgo = null, + enforceKeygenExpireMonths = null + ) ))) } } 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 ec7501d80d..5b63bc19e0 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 @@ -19,7 +19,7 @@ import com.flowcrypt.email.R import com.flowcrypt.email.TestConstants import com.flowcrypt.email.api.retrofit.ApiHelper import com.flowcrypt.email.api.retrofit.request.model.LoginModel -import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.base.ApiError import com.flowcrypt.email.api.retrofit.response.model.OrgRules @@ -83,7 +83,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { @Test fun testErrorGetDomainRules() { setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_DOMAIN_RULES_ERROR)) - isToastDisplayed(DOMAIN_RULES_ERROR_RESPONSE.apiError?.msg!!) + isToastDisplayed(DOMAIN_ORG_RULES_ERROR_RESPONSE.apiError?.msg!!) } @Test @@ -109,7 +109,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { ), null ) - val DOMAIN_RULES_ERROR_RESPONSE = DomainRulesResponse( + val DOMAIN_ORG_RULES_ERROR_RESPONSE = DomainOrgRulesResponse( ApiError( 401, "Not logged in or unknown account", "auth" @@ -145,12 +145,12 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { if (request.path.equals("/account/get")) { when (model.account) { EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(DOMAIN_RULES_ERROR_RESPONSE)) + .setBody(gson.toJson(DOMAIN_ORG_RULES_ERROR_RESPONSE)) EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) .setBody( gson.toJson( - DomainRulesResponse( + DomainOrgRulesResponse( apiError = null, orgRules = OrgRules( flags = listOf( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiName.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiName.kt index 05b052268b..99188b2460 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiName.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiName.kt @@ -15,13 +15,8 @@ package com.flowcrypt.email.api.retrofit */ //todo-denbond7 need to remove this class after refactoring enum class ApiName { - /*flowcrypt.com/attester*/ - GET_PUB, - - /*flowcrypt.com/api*/ + /*flowcrypt.com/api*/ POST_HELP_FEEDBACK, - POST_LINK_MESSAGE, - POST_MESSAGE_REPLY, - POST_LOGIN, - POST_GET_DOMAIN_RULES + POST_LINK_MESSAGE, + POST_MESSAGE_REPLY } 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 4e27f7b220..326301f193 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 @@ -7,11 +7,10 @@ package com.flowcrypt.email.api.retrofit import android.content.Context import com.flowcrypt.email.api.retrofit.base.BaseApiRepository -import com.flowcrypt.email.api.retrofit.request.api.DomainRulesRequest -import com.flowcrypt.email.api.retrofit.request.api.LoginRequest import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel +import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel -import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse @@ -33,18 +32,23 @@ import com.google.gson.JsonObject interface ApiRepository : BaseApiRepository { /** * @param context Interface to global information about an application environment. - * @param request An instance of [LoginRequest]. + * @param loginModel An instance of [LoginModel]. + * @param tokenId OIDC token. */ - suspend fun login(context: Context, request: LoginRequest): Result + suspend fun login( + context: Context, + loginModel: LoginModel, + tokenId: String + ): Result /** * @param context Interface to global information about an application environment. - * @param request An instance of [DomainRulesRequest]. + * @param loginModel An instance of [LoginModel]. */ - suspend fun getDomainRules( + suspend fun getDomainOrgRules( context: Context, - request: DomainRulesRequest - ): Result + loginModel: LoginModel + ): Result /** * @param context Interface to global information about an application environment. 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 8cf9879aeb..9b3dfc20b4 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 @@ -11,7 +11,7 @@ import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.request.model.PostHelpFeedbackModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel -import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse @@ -107,7 +107,7 @@ interface ApiService { * @param body POJO model for requests */ @POST(BuildConfig.API_URL + "account/get") - suspend fun getDomainRules(@Body body: LoginModel): Response + suspend fun getDomainOrgRules(@Body body: LoginModel): Response /** * This method calls API "https://flowcrypt.com/attester/initial/legacy_submit" via coroutines 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 5e0bf59c3f..8fcb96415f 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 @@ -7,11 +7,10 @@ package com.flowcrypt.email.api.retrofit import android.content.Context import com.flowcrypt.email.R -import com.flowcrypt.email.api.retrofit.request.api.DomainRulesRequest -import com.flowcrypt.email.api.retrofit.request.api.LoginRequest import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel +import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel -import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse @@ -32,19 +31,29 @@ import kotlinx.coroutines.withContext * E-mail: DenBond7@gmail.com */ class FlowcryptApiRepository : ApiRepository { - override suspend fun login(context: Context, request: LoginRequest): Result = + override suspend fun login( + context: Context, + loginModel: LoginModel, + tokenId: String + ): Result = withContext(Dispatchers.IO) { val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) - getResult { apiService.postLogin(request.requestModel, "Bearer ${request.tokenId}") } + getResult( + context = context, + expectedResultClass = LoginResponse::class.java + ) { apiService.postLogin(loginModel, "Bearer $tokenId") } } - override suspend fun getDomainRules( + override suspend fun getDomainOrgRules( context: Context, - request: DomainRulesRequest - ): Result = + loginModel: LoginModel + ): Result = withContext(Dispatchers.IO) { val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) - getResult { apiService.getDomainRules(request.requestModel) } + getResult( + context = context, + expectedResultClass = DomainOrgRulesResponse::class.java + ) { apiService.getDomainOrgRules(loginModel) } } override suspend fun submitPubKey( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/DomainRulesRequest.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/DomainRulesRequest.kt deleted file mode 100644 index ee274237fe..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/DomainRulesRequest.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.api.retrofit.request.api - -import com.flowcrypt.email.api.retrofit.ApiName -import com.flowcrypt.email.api.retrofit.request.BaseRequest -import com.flowcrypt.email.api.retrofit.request.model.LoginModel - -/** - * This class describes a request to the https://flowcrypt.com/api/account/get. - * - * @author Denis Bondarenko - * Date: 10/29/19 - * Time: 11:21 AM - * E-mail: DenBond7@gmail.com - */ -//todo-denbond7 this class is redundant. We can refactor code and delete this -class DomainRulesRequest( - override val apiName: ApiName = ApiName.POST_GET_DOMAIN_RULES, - override val requestModel: LoginModel -) : BaseRequest diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/LoginRequest.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/LoginRequest.kt deleted file mode 100644 index f03d733aea..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/api/LoginRequest.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.api.retrofit.request.api - -import com.flowcrypt.email.api.retrofit.ApiName -import com.flowcrypt.email.api.retrofit.request.BaseRequest -import com.flowcrypt.email.api.retrofit.request.model.LoginModel - -/** - * This class describes a request to the https://flowcrypt.com/api/account/login. - * - * @author Denis Bondarenko - * Date: 10/23/19 - * Time: 3:53 PM - * E-mail: DenBond7@gmail.com - */ -//todo-denbond7 this class is redundant. We can refactor code and delete this -class LoginRequest( - override val apiName: ApiName = ApiName.POST_LOGIN, - override val requestModel: LoginModel, val tokenId: String -) : BaseRequest diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt similarity index 59% rename from FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt rename to FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt index 3e8d840c02..93e2525513 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainRulesResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt @@ -21,13 +21,16 @@ import com.google.gson.annotations.SerializedName * Time: 11:25 AM * E-mail: DenBond7@gmail.com */ -data class DomainRulesResponse constructor(@SerializedName("error") - @Expose override val apiError: ApiError?, - @SerializedName("domain_org_rules") - @Expose val orgRules: OrgRules?) : ApiResponse { +data class DomainOrgRulesResponse constructor( + @SerializedName("error") + @Expose override val apiError: ApiError?, + @SerializedName("domain_org_rules") + @Expose val orgRules: OrgRules? +) : ApiResponse { constructor(parcel: Parcel) : this( - parcel.readParcelable(ApiError::class.java.classLoader), - parcel.readParcelable(OrgRules::class.java.classLoader)) + parcel.readParcelable(ApiError::class.java.classLoader), + parcel.readParcelable(OrgRules::class.java.classLoader) + ) override fun writeToParcel(parcel: Parcel, flags: Int) { with(parcel) { @@ -40,12 +43,12 @@ data class DomainRulesResponse constructor(@SerializedName("error") return 0 } - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): DomainRulesResponse { - return DomainRulesResponse(parcel) + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): DomainOrgRulesResponse { + return DomainOrgRulesResponse(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DomainOrgRulesViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DomainOrgRulesViewModel.kt new file mode 100644 index 0000000000..af710640aa --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DomainOrgRulesViewModel.kt @@ -0,0 +1,49 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.jetpack.viewmodel + +import android.app.Application +import android.content.Context +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository +import com.flowcrypt.email.api.retrofit.request.model.LoginModel +import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse +import com.flowcrypt.email.api.retrofit.response.base.Result +import kotlinx.coroutines.launch + +/** + * This [ViewModel] can be used to resolve domain rules for enterprise users. + * + * @author Denis Bondarenko + * Date: 6/30/21 + * Time: 6:56 PM + * E-mail: DenBond7@gmail.com + */ +class DomainOrgRulesViewModel(application: Application) : BaseAndroidViewModel(application) { + private val repository = FlowcryptApiRepository() + val domainOrgRulesLiveData: MutableLiveData> = + MutableLiveData(Result.none()) + + fun fetchOrgRules(account: String, uuid: String) { + viewModelScope.launch { + val context: Context = getApplication() + domainOrgRulesLiveData.value = + Result.loading(progressMsg = context.getString(R.string.loading_domain_rules)) + + try { + domainOrgRulesLiveData.value = repository.getDomainOrgRules( + context = context, + loginModel = LoginModel(account, uuid) + ) + } catch (e: Exception) { + domainOrgRulesLiveData.value = Result.exception(e) + } + } + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt new file mode 100644 index 0000000000..d34714dba3 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -0,0 +1,104 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.jetpack.viewmodel + +import android.app.Application +import android.content.Context +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository +import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse +import com.flowcrypt.email.api.retrofit.response.base.Result +import com.flowcrypt.email.api.retrofit.response.model.OrgRules +import com.flowcrypt.email.util.exception.EkmNotSupportedException +import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedException +import kotlinx.coroutines.launch + +/** + * This [ViewModel] can be used to apply EKM functionality for enterprise users. + * + * @author Denis Bondarenko + * Date: 6/30/21 + * Time: 6:56 PM + * E-mail: DenBond7@gmail.com + */ +class EkmViewModel(application: Application) : BaseAndroidViewModel(application) { + private val repository = FlowcryptApiRepository() + val ekmLiveData: MutableLiveData> = MutableLiveData(Result.none()) + + fun fetchPrvKeys(orgRules: OrgRules, tokenId: String) { + viewModelScope.launch { + val context: Context = getApplication() + ekmLiveData.value = Result.loading(progressMsg = context.getString(R.string.fetching_keys)) + try { + if (!orgRules.usesKeyManager() || !orgRules.mustAutoImportOrAutoGenPrvWithKeyManager()) { + ekmLiveData.value = Result.exception( + EkmNotSupportedException(orgRules) + ) + return@launch + } + + val notSupportedCombination: Map = + checkNotSupportedOrgRulesCombination(orgRules) + + if (notSupportedCombination.isNotEmpty()) { + ekmLiveData.value = Result.exception( + OrgRulesCombinationNotSupportedException( + orgRules = orgRules, + combination = notSupportedCombination + ) + ) + return@launch + } + + val ekmPrivateResult = repository.getPrivateKeysViaEkm( + context = context, + ekmUrl = orgRules.keyManagerUrl + ?: throw java.lang.IllegalArgumentException("key_manager_url is empty"), + tokenId = tokenId + ) + + if (ekmPrivateResult.data?.privateKeys?.isEmpty() == true) { + throw java.lang.IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) + } + + ekmLiveData.value = ekmPrivateResult + } catch (e: Exception) { + ekmLiveData.value = Result.exception(e) + } + } + } + + private fun checkNotSupportedOrgRulesCombination(orgRules: OrgRules): + Map { + val notSupportedCombination = mutableMapOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN to true + ) + + when { + orgRules.mustAutoGenPassPhraseQuietly() -> { + notSupportedCombination[OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN] = true + } + + !orgRules.forbidStoringPassPhrase() -> { + notSupportedCombination[OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE] = false + } + + orgRules.mustSubmitToAttester() -> { + notSupportedCombination[OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT] = true + } + + !orgRules.forbidCreatingPrivateKey() -> { + notSupportedCombination[OrgRules.DomainRule.NO_PRV_CREATE] = false + } + + else -> notSupportedCombination.clear() + } + return notSupportedCombination + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt index 4d88a292cd..9d8f9d61a6 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LoginViewModel.kt @@ -13,19 +13,10 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.flowcrypt.email.R -import com.flowcrypt.email.api.retrofit.ApiName -import com.flowcrypt.email.api.retrofit.ApiRepository import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository -import com.flowcrypt.email.api.retrofit.request.api.DomainRulesRequest -import com.flowcrypt.email.api.retrofit.request.api.LoginRequest import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.response.api.LoginResponse -import com.flowcrypt.email.api.retrofit.response.base.ApiError -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.util.exception.EkmNotSupportedException -import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedException import kotlinx.coroutines.launch /** @@ -37,129 +28,22 @@ import kotlinx.coroutines.launch * E-mail: DenBond7@gmail.com */ class LoginViewModel(application: Application) : BaseAndroidViewModel(application) { - private val repository: ApiRepository = FlowcryptApiRepository() - val ekmLiveData: MutableLiveData?> = MutableLiveData() - - fun fetchPrvKeys(account: String, uuid: String, tokenId: String) { - ekmLiveData.value = Result.loading() - val context: Context = getApplication() + private val repository = FlowcryptApiRepository() + val loginLiveData: MutableLiveData> = MutableLiveData(Result.none()) + fun login(account: String, uuid: String, tokenId: String) { viewModelScope.launch { - ekmLiveData.value = Result.loading(progressMsg = context.getString(R.string.loading)) + val context: Context = getApplication() + loginLiveData.value = Result.loading(progressMsg = context.getString(R.string.loading)) try { - val loginResult = repository.login( - context, - LoginRequest(ApiName.POST_LOGIN, LoginModel(account, uuid), tokenId) + loginLiveData.value = repository.login( + context = context, + loginModel = LoginModel(account, uuid), + tokenId = tokenId ) - - when (loginResult.status) { - Result.Status.ERROR, Result.Status.EXCEPTION -> { - ekmLiveData.value = loginResult - return@launch - } - - Result.Status.SUCCESS -> { - if (loginResult.data?.isVerified == true) { - ekmLiveData.value = Result.loading( - progressMsg = context.getString(R.string.loading_domain_rules) - ) - val domainRulesResult = repository.getDomainRules( - context, - DomainRulesRequest(ApiName.POST_GET_DOMAIN_RULES, LoginModel(account, uuid)) - ) - - if (domainRulesResult.status == Result.Status.SUCCESS) { - if (domainRulesResult.data?.orgRules == null) { - ekmLiveData.value = Result.exception( - IllegalArgumentException("${OrgRules::class.java.simpleName} is not specified") - ) - return@launch - } - - if (!domainRulesResult.data.orgRules.usesKeyManager() || - !domainRulesResult.data.orgRules.mustAutoImportOrAutoGenPrvWithKeyManager() - ) { - ekmLiveData.value = Result.exception( - EkmNotSupportedException(domainRulesResult.data.orgRules) - ) - return@launch - } - - val notSupportedCombination = - checkNotSupportedOrgRulesCombination(domainRulesResult.data.orgRules) - - if (notSupportedCombination.isNotEmpty()) { - ekmLiveData.value = Result.exception( - OrgRulesCombinationNotSupportedException( - orgRules = domainRulesResult.data.orgRules, - combination = notSupportedCombination - ) - ) - return@launch - } - - val ekmPrivateResult = repository.getPrivateKeysViaEkm( - context = context, - ekmUrl = domainRulesResult.data.orgRules.keyManagerUrl - ?: throw java.lang.IllegalArgumentException("key_manager_url is empty"), - tokenId = tokenId - ) - - if (ekmPrivateResult.data?.privateKeys?.isEmpty() != true) { - throw java.lang.IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) - } - - ekmLiveData.value = ekmPrivateResult - } else { - ekmLiveData.value = domainRulesResult - } - } else { - ekmLiveData.value = Result.error( - LoginResponse( - ApiError( - -1, - context.getString(R.string.user_not_verified) - ), false - ) - ) - } - } - - else -> { - ekmLiveData.value = Result.exception(IllegalStateException("Unhandled error")) - } - } } catch (e: Exception) { - ekmLiveData.value = Result.exception(e) + loginLiveData.value = Result.exception(e) } } } - - private fun checkNotSupportedOrgRulesCombination(orgRules: OrgRules): - Map { - val notSupportedCombination = mutableMapOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN to true - ) - - when { - orgRules.mustAutoGenPassPhraseQuietly() -> { - notSupportedCombination[OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN] = true - } - - !orgRules.forbidStoringPassPhrase() -> { - notSupportedCombination[OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE] = false - } - - orgRules.mustSubmitToAttester() -> { - notSupportedCombination[OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT] = true - } - - !orgRules.forbidCreatingPrivateKey() -> { - notSupportedCombination[OrgRules.DomainRule.NO_PRV_CREATE] = false - } - - else -> notSupportedCombination.clear() - } - return notSupportedCombination - } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index a50a36d54c..58c08588ad 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -17,7 +17,7 @@ import com.flowcrypt.email.Constants import com.flowcrypt.email.R import com.flowcrypt.email.api.email.EmailUtil import com.flowcrypt.email.api.email.JavaEmailConstants -import com.flowcrypt.email.api.retrofit.response.api.DomainRulesResponse +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.database.entity.AccountEntity @@ -25,6 +25,9 @@ import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.extensions.showTwoWayDialog +import com.flowcrypt.email.extensions.toast +import com.flowcrypt.email.jetpack.viewmodel.DomainOrgRulesViewModel +import com.flowcrypt.email.jetpack.viewmodel.EkmViewModel import com.flowcrypt.email.jetpack.viewmodel.LoginViewModel import com.flowcrypt.email.model.KeyImportDetails import com.flowcrypt.email.security.SecurityUtils @@ -69,6 +72,8 @@ class MainSignInFragment : BaseSingInFragment() { private var domainRules: List? = null private val loginViewModel: LoginViewModel by viewModels() + private val domainOrgRulesViewModel: DomainOrgRulesViewModel by viewModels() + private val ekmViewModel: EkmViewModel by viewModels() override val progressView: View? get() = view?.findViewById(R.id.progress) @@ -92,7 +97,7 @@ class MainSignInFragment : BaseSingInFragment() { subscribeToAuthorizeAndSearchBackups() initAddNewAccountLiveData() - setupEnterpriseViewModel() + initEnterpriseViewModels() initSavePrivateKeysLiveData() } @@ -130,7 +135,7 @@ class MainSignInFragment : BaseSingInFragment() { TwoWayDialogFragment.RESULT_OK -> { val account = googleSignInAccount?.account?.name ?: return val idToken = googleSignInAccount?.idToken ?: return - loginViewModel.fetchPrvKeys(account, uuid, idToken) + loginViewModel.login(account, uuid, idToken) } } } @@ -223,7 +228,7 @@ class MainSignInFragment : BaseSingInFragment() { domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { - loginViewModel.fetchPrvKeys(account, uuid, idToken) + loginViewModel.login(account, uuid, idToken) } } else { val error = task.exception @@ -394,67 +399,149 @@ class MainSignInFragment : BaseSingInFragment() { } } - private fun setupEnterpriseViewModel() { - loginViewModel.ekmLiveData.observe(viewLifecycleOwner, { - it?.let { - when (it.status) { - Result.Status.LOADING -> { - if (it.progressMsg == null) { - baseActivity.countingIdlingResource.incrementSafely() + private fun initEnterpriseViewModels() { + initLoginViewModel() + initDomainOrgRulesViewModel() + initEkmViewModel() + } + + private fun initLoginViewModel() { + loginViewModel.loginLiveData.observe(viewLifecycleOwner, { + when (it.status) { + Result.Status.LOADING -> { + baseActivity.countingIdlingResource.incrementSafely() + showProgress(progressMsg = it.progressMsg) + } + + Result.Status.SUCCESS -> { + if (it.data?.isVerified == true) { + val account = googleSignInAccount?.account?.name + if (account != null) { + domainOrgRulesViewModel.fetchOrgRules(account, uuid) + } else { + showContent() + askUserToReLogin() } - showProgress(progressMsg = it.progressMsg) + } else { + showInfoDialog( + dialogTitle = "", + dialogMsg = getString(R.string.user_not_verified), + isCancelable = true + ) } - Result.Status.SUCCESS -> { - val result = it.data as? DomainRulesResponse - domainRules = result?.orgRules?.flags ?: emptyList() - onSignSuccess(googleSignInAccount) - loginViewModel.ekmLiveData.value = Result.none() - baseActivity.countingIdlingResource.decrementSafely() - } + loginViewModel.loginLiveData.value = Result.none() + baseActivity.countingIdlingResource.decrementSafely() + } - Result.Status.ERROR, Result.Status.EXCEPTION -> { + Result.Status.ERROR, Result.Status.EXCEPTION -> { + showContent() + showDialogWithRetryButton(it) + loginViewModel.loginLiveData.value = Result.none() + baseActivity.countingIdlingResource.decrementSafely() + } + } + }) + } + + private fun initDomainOrgRulesViewModel() { + domainOrgRulesViewModel.domainOrgRulesLiveData.observe(viewLifecycleOwner, { + when (it.status) { + Result.Status.LOADING -> { + baseActivity.countingIdlingResource.incrementSafely() + showProgress(progressMsg = it.progressMsg) + } + + Result.Status.SUCCESS -> { + val idToken = googleSignInAccount?.idToken + if (it.data?.orgRules != null && idToken != null) { + ekmViewModel.fetchPrvKeys(it.data.orgRules, idToken) + } else { showContent() + askUserToReLogin() + } + domainOrgRulesViewModel.domainOrgRulesLiveData.value = Result.none() + baseActivity.countingIdlingResource.decrementSafely() + } - when (it.exception) { - is EkmNotSupportedException -> { - domainRules = it.exception.orgRules.flags - onSignSuccess(googleSignInAccount) - } + Result.Status.ERROR, Result.Status.EXCEPTION -> { + showContent() + showDialogWithRetryButton(it) + domainOrgRulesViewModel.domainOrgRulesLiveData.value = Result.none() + baseActivity.countingIdlingResource.decrementSafely() + } + } + }) + } - is OrgRulesCombinationNotSupportedException -> { - showInfoDialog( - dialogTitle = "", - dialogMsg = getString( - R.string.combination_of_org_rules_is_not_supported, - it.exception.combination - ), - isCancelable = true - ) - } + private fun initEkmViewModel() { + ekmViewModel.ekmLiveData.observe(viewLifecycleOwner, { + when (it.status) { + Result.Status.LOADING -> { + baseActivity.countingIdlingResource.incrementSafely() + showProgress(progressMsg = it.progressMsg) + } - else -> { - val errorMsg = it.data?.apiError?.msg - ?: it.exception?.message - ?: getString(R.string.unknown_error) - showTwoWayDialog( - requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, - dialogTitle = "", - dialogMsg = errorMsg, - positiveButtonTitle = getString(R.string.retry), - negativeButtonTitle = getString(R.string.cancel), - isCancelable = true - ) - } + Result.Status.SUCCESS -> { + showContent() + toast("Debug: received = ${it.data?.privateKeys?.size} key(s)") + //encrypt the received keys with the pass phrase. + ekmViewModel.ekmLiveData.value = Result.none() + baseActivity.countingIdlingResource.decrementSafely() + } + + Result.Status.ERROR, Result.Status.EXCEPTION -> { + showContent() + when (it.exception) { + is EkmNotSupportedException -> { + domainRules = it.exception.orgRules.flags + onSignSuccess(googleSignInAccount) + } + + is OrgRulesCombinationNotSupportedException -> { + showInfoDialog( + dialogTitle = "", + dialogMsg = getString( + R.string.combination_of_org_rules_is_not_supported, + it.exception.combination + ), + isCancelable = true + ) + } + + else -> { + showDialogWithRetryButton(it) } - loginViewModel.ekmLiveData.value = Result.none() - baseActivity.countingIdlingResource.decrementSafely() } + ekmViewModel.ekmLiveData.value = Result.none() + baseActivity.countingIdlingResource.decrementSafely() } } }) } + private fun showDialogWithRetryButton(it: Result) { + val errorMsg = it.data?.apiError?.msg + ?: it.exception?.message + ?: getString(R.string.unknown_error) + showTwoWayDialog( + requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, + dialogTitle = "", + dialogMsg = errorMsg, + positiveButtonTitle = getString(R.string.retry), + negativeButtonTitle = getString(R.string.cancel), + isCancelable = true + ) + } + + private fun askUserToReLogin() { + showInfoDialog( + dialogTitle = "", + dialogMsg = getString(R.string.please_login_again_to_continue), + isCancelable = true + ) + } + private fun handleResultFromCheckKeysActivity(resultCode: Int, data: Intent?) { when (resultCode) { Activity.RESULT_OK, CheckKeysActivity.RESULT_SKIP_REMAINING_KEYS -> { diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index 29651b0ed0..a64e8d0f07 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -522,4 +522,6 @@ Processing, please wait… Could not change the pass phrase There are no private keys configured for you. Please ask your systems administrator or help desk + Please login again to continue + Fetching keys, please wait… From 1bd10a40286438feebdce37d5164846ec752c5a8 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 10:25:16 +0300 Subject: [PATCH 18/41] Improved retry police.| #1168 --- .../activity/fragment/MainSignInFragment.kt | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 58c08588ad..9d43ee69eb 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -69,7 +69,7 @@ class MainSignInFragment : BaseSingInFragment() { private lateinit var client: GoogleSignInClient private var googleSignInAccount: GoogleSignInAccount? = null private var uuid: String = SecurityUtils.generateRandomUUID() - private var domainRules: List? = null + private var orgRules: OrgRules? = null private val loginViewModel: LoginViewModel by viewModels() private val domainOrgRulesViewModel: DomainOrgRulesViewModel by viewModels() @@ -130,22 +130,41 @@ class MainSignInFragment : BaseSingInFragment() { handleResultFromCheckKeysActivity(resultCode, data) } - REQUEST_CODE_RETRY_EKM_LOGIN -> { + REQUEST_CODE_RETRY_LOGIN -> { when (resultCode) { TwoWayDialogFragment.RESULT_OK -> { val account = googleSignInAccount?.account?.name ?: return val idToken = googleSignInAccount?.idToken ?: return + orgRules = null loginViewModel.login(account, uuid, idToken) } } } + REQUEST_CODE_RETRY_GET_DOMAIN_ORG_RULES -> { + when (resultCode) { + TwoWayDialogFragment.RESULT_OK -> { + val account = googleSignInAccount?.account?.name ?: return + domainOrgRulesViewModel.fetchOrgRules(account, uuid) + } + } + } + + REQUEST_CODE_RETRY_FETCH_PRV_KEYS_VIA_EKM -> { + when (resultCode) { + TwoWayDialogFragment.RESULT_OK -> { + val idToken = googleSignInAccount?.idToken ?: return + orgRules?.let { ekmViewModel.fetchPrvKeys(it, idToken) } + } + } + } + else -> super.onActivityResult(requestCode, resultCode, data) } } override fun getTempAccount(): AccountEntity? { - return googleSignInAccount?.let { AccountEntity(it, uuid, domainRules) } + return googleSignInAccount?.let { AccountEntity(it, uuid, orgRules?.flags ?: emptyList()) } } override fun returnResultOk() { @@ -225,9 +244,9 @@ class MainSignInFragment : BaseSingInFragment() { ) if (EmailUtil.getDomain(account).toLowerCase(Locale.US) in publicEmailDomains) { - domainRules = emptyList() onSignSuccess(googleSignInAccount) } else { + orgRules = null loginViewModel.login(account, uuid, idToken) } } else { @@ -259,7 +278,7 @@ class MainSignInFragment : BaseSingInFragment() { if (existedAccount == null) { getTempAccount()?.let { - if (domainRules?.firstOrNull { rule -> rule == OrgRules.DomainRule.NO_PRV_BACKUP } != null) { + if (orgRules?.flags?.firstOrNull { rule -> rule == OrgRules.DomainRule.NO_PRV_BACKUP } != null) { requireContext().startService( Intent(requireContext(), CheckClipboardToFindKeyService::class.java) ) @@ -436,7 +455,7 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.ERROR, Result.Status.EXCEPTION -> { showContent() - showDialogWithRetryButton(it) + showDialogWithRetryButton(it, REQUEST_CODE_RETRY_LOGIN) loginViewModel.loginLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } @@ -455,6 +474,7 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.SUCCESS -> { val idToken = googleSignInAccount?.idToken if (it.data?.orgRules != null && idToken != null) { + orgRules = it.data.orgRules ekmViewModel.fetchPrvKeys(it.data.orgRules, idToken) } else { showContent() @@ -466,7 +486,7 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.ERROR, Result.Status.EXCEPTION -> { showContent() - showDialogWithRetryButton(it) + showDialogWithRetryButton(it, REQUEST_CODE_RETRY_GET_DOMAIN_ORG_RULES) domainOrgRulesViewModel.domainOrgRulesLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } @@ -494,7 +514,7 @@ class MainSignInFragment : BaseSingInFragment() { showContent() when (it.exception) { is EkmNotSupportedException -> { - domainRules = it.exception.orgRules.flags + orgRules = it.exception.orgRules onSignSuccess(googleSignInAccount) } @@ -510,7 +530,7 @@ class MainSignInFragment : BaseSingInFragment() { } else -> { - showDialogWithRetryButton(it) + showDialogWithRetryButton(it, REQUEST_CODE_RETRY_FETCH_PRV_KEYS_VIA_EKM) } } ekmViewModel.ekmLiveData.value = Result.none() @@ -520,12 +540,12 @@ class MainSignInFragment : BaseSingInFragment() { }) } - private fun showDialogWithRetryButton(it: Result) { + private fun showDialogWithRetryButton(it: Result, resultCode: Int) { val errorMsg = it.data?.apiError?.msg ?: it.exception?.message ?: getString(R.string.unknown_error) showTwoWayDialog( - requestCode = REQUEST_CODE_RETRY_EKM_LOGIN, + requestCode = resultCode, dialogTitle = "", dialogMsg = errorMsg, positiveButtonTitle = getString(R.string.retry), @@ -592,6 +612,8 @@ class MainSignInFragment : BaseSingInFragment() { private const val REQUEST_CODE_RESOLVE_SIGN_IN_ERROR = 101 private const val REQUEST_CODE_CREATE_OR_IMPORT_KEY = 102 private const val REQUEST_CODE_CHECK_PRIVATE_KEYS_FROM_GMAIL = 103 - private const val REQUEST_CODE_RETRY_EKM_LOGIN = 104 + private const val REQUEST_CODE_RETRY_LOGIN = 104 + private const val REQUEST_CODE_RETRY_GET_DOMAIN_ORG_RULES = 105 + private const val REQUEST_CODE_RETRY_FETCH_PRV_KEYS_VIA_EKM = 106 } } From 26354850a6957471ce8608807b3269352ad02d1c Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 11:06:53 +0300 Subject: [PATCH 19/41] Simplified logic of cheking for unsupported OrgRules combination.| #1168 --- .../api/retrofit/response/model/OrgRules.kt | 29 +++++----- .../email/jetpack/viewmodel/EkmViewModel.kt | 57 +++++++++---------- .../activity/fragment/MainSignInFragment.kt | 7 +-- ...ion.kt => UnsupportedOrgRulesException.kt} | 7 +-- 4 files changed, 48 insertions(+), 52 deletions(-) rename FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/{OrgRulesCombinationNotSupportedException.kt => UnsupportedOrgRulesException.kt} (56%) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt index ba53f7c5cb..e960b8180e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -73,7 +73,7 @@ data class OrgRules constructor( * use this method when using for PUB sync */ fun getKeyManagerUrlForPublicKeys(): String? { - if (flags?.firstOrNull { it == DomainRule.NO_KEY_MANAGER_PUB_LOOKUP } != null) { + if (hasRule(DomainRule.NO_KEY_MANAGER_PUB_LOOKUP)) { return null } return keyManagerUrl @@ -107,14 +107,14 @@ data class OrgRules constructor( * (and forbid keygen in the extension) */ fun canCreateKeys(): Boolean { - return flags?.firstOrNull { it == DomainRule.NO_PRV_CREATE } == null + return !hasRule(DomainRule.NO_PRV_CREATE) } /** * Some orgs want to forbid backing up of public keys (such as inbox or other methods) */ fun canBackupKeys(): Boolean { - return flags?.firstOrNull { it == DomainRule.NO_PRV_BACKUP } == null + return !hasRule(DomainRule.NO_PRV_BACKUP) } /** @@ -123,7 +123,7 @@ data class OrgRules constructor( * that their public key gets submitted to attester and conflict errors are NOT ignored: */ fun mustSubmitToAttester(): Boolean { - return flags?.firstOrNull { it == DomainRule.ENFORCE_ATTESTER_SUBMIT } != null + return hasRule(DomainRule.ENFORCE_ATTESTER_SUBMIT) } /** @@ -132,16 +132,15 @@ data class OrgRules constructor( * This behavior is also enabled as a byproduct of PASS_PHRASE_QUIET_AUTOGEN */ fun rememberPassPhraseByDefault(): Boolean { - return flags?.firstOrNull { it == DomainRule.DEFAULT_REMEMBER_PASS_PHRASE } != null - || this.mustAutoGenPassPhraseQuietly() + return hasRule(DomainRule.DEFAULT_REMEMBER_PASS_PHRASE) || this.mustAutoGenPassPhraseQuietly() } fun forbidStoringPassPhrase(): Boolean { - return flags?.firstOrNull { it == DomainRule.FORBID_STORING_PASS_PHRASE } != null + return hasRule(DomainRule.FORBID_STORING_PASS_PHRASE) } fun forbidCreatingPrivateKey(): Boolean { - return flags?.firstOrNull { it == DomainRule.NO_PRV_CREATE } != null + return hasRule(DomainRule.NO_PRV_CREATE) } /** @@ -150,7 +149,7 @@ data class OrgRules constructor( * If not, it will be autogenerated and stored there */ fun mustAutoImportOrAutoGenPrvWithKeyManager(): Boolean { - if (flags?.firstOrNull { it == DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN } == null) { + if (!hasRule(DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN)) { return false } if (getKeyManagerUrlForPrivateKeys() == null) { @@ -169,14 +168,14 @@ data class OrgRules constructor( * full-disk-encryption and don't need pass phrase protection */ fun mustAutoGenPassPhraseQuietly(): Boolean { - return flags?.firstOrNull { it == DomainRule.PASS_PHRASE_QUIET_AUTOGEN } != null + return hasRule(DomainRule.PASS_PHRASE_QUIET_AUTOGEN) } /** * Some orgs prefer to forbid publishing public keys publicly */ fun canSubmitPubToAttester(): Boolean { - return flags?.firstOrNull { it == DomainRule.NO_ATTESTER_SUBMIT } == null + return !hasRule(DomainRule.NO_ATTESTER_SUBMIT) } /** @@ -197,7 +196,7 @@ data class OrgRules constructor( * the original endpoint */ fun useLegacyAttesterSubmit(): Boolean { - return flags?.firstOrNull { it == DomainRule.USE_LEGACY_ATTESTER_SUBMIT } != null + return hasRule(DomainRule.USE_LEGACY_ATTESTER_SUBMIT) } /** @@ -205,7 +204,11 @@ data class OrgRules constructor( * imported keys get imported without armor */ fun shouldHideArmorMeta(): Boolean { - return flags?.firstOrNull { it == DomainRule.HIDE_ARMOR_META } != null + return hasRule(DomainRule.HIDE_ARMOR_META) + } + + fun hasRule(domainRule: DomainRule): Boolean { + return flags?.firstOrNull { it == domainRule } != null } enum class DomainRule : Parcelable { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index d34714dba3..61924aba1c 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -16,7 +16,7 @@ import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.util.exception.EkmNotSupportedException -import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedException +import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException import kotlinx.coroutines.launch /** @@ -43,16 +43,9 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) return@launch } - val notSupportedCombination: Map = - checkNotSupportedOrgRulesCombination(orgRules) - - if (notSupportedCombination.isNotEmpty()) { - ekmLiveData.value = Result.exception( - OrgRulesCombinationNotSupportedException( - orgRules = orgRules, - combination = notSupportedCombination - ) - ) + val unsupportedOrgRulesException = checkForUnsupportedOrgRulesCombination(orgRules) + if (unsupportedOrgRulesException != null) { + ekmLiveData.value = Result.exception(unsupportedOrgRulesException) return@launch } @@ -74,31 +67,37 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) } } - private fun checkNotSupportedOrgRulesCombination(orgRules: OrgRules): - Map { - val notSupportedCombination = mutableMapOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN to true - ) - - when { - orgRules.mustAutoGenPassPhraseQuietly() -> { - notSupportedCombination[OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN] = true + private fun checkForUnsupportedOrgRulesCombination(orgRules: OrgRules): + UnsupportedOrgRulesException? { + if (orgRules.hasRule(OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN)) { + if (orgRules.hasRule(OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN)) { + return UnsupportedOrgRulesException( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + " + + OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN + ) } - !orgRules.forbidStoringPassPhrase() -> { - notSupportedCombination[OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE] = false + if (!orgRules.hasRule(OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE)) { + return UnsupportedOrgRulesException( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE + ) } - orgRules.mustSubmitToAttester() -> { - notSupportedCombination[OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT] = true + if (orgRules.hasRule(OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT)) { + return UnsupportedOrgRulesException( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + " + + OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT + ) } - !orgRules.forbidCreatingPrivateKey() -> { - notSupportedCombination[OrgRules.DomainRule.NO_PRV_CREATE] = false + if (!orgRules.hasRule(OrgRules.DomainRule.NO_PRV_CREATE)) { + return UnsupportedOrgRulesException( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + + OrgRules.DomainRule.NO_PRV_CREATE + ) } - - else -> notSupportedCombination.clear() } - return notSupportedCombination + return null } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 9d43ee69eb..c1ba64977e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -45,7 +45,7 @@ import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.exception.AccountAlreadyAddedException import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.ExceptionUtil -import com.flowcrypt.email.util.exception.OrgRulesCombinationNotSupportedException +import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException import com.flowcrypt.email.util.google.GoogleApiClientHelper import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount @@ -514,16 +514,15 @@ class MainSignInFragment : BaseSingInFragment() { showContent() when (it.exception) { is EkmNotSupportedException -> { - orgRules = it.exception.orgRules onSignSuccess(googleSignInAccount) } - is OrgRulesCombinationNotSupportedException -> { + is UnsupportedOrgRulesException -> { showInfoDialog( dialogTitle = "", dialogMsg = getString( R.string.combination_of_org_rules_is_not_supported, - it.exception.combination + it.exception.message ), isCancelable = true ) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/UnsupportedOrgRulesException.kt similarity index 56% rename from FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt rename to FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/UnsupportedOrgRulesException.kt index df1bbe9b9d..0c56889458 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/OrgRulesCombinationNotSupportedException.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/UnsupportedOrgRulesException.kt @@ -5,15 +5,10 @@ package com.flowcrypt.email.util.exception -import com.flowcrypt.email.api.retrofit.response.model.OrgRules - /** * @author Denis Bondarenko * Date: 6/22/21 * Time: 7:40 PM * E-mail: DenBond7@gmail.com */ -class OrgRulesCombinationNotSupportedException( - val orgRules: OrgRules, - val combination: Map -) : FlowCryptException() +class UnsupportedOrgRulesException(message: String) : FlowCryptException(message) From 484f5ba79bd1cf5299eafeba7e7592ba1a0fa56c Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 11:12:12 +0300 Subject: [PATCH 20/41] Refactored code.| #1168 --- .../email/jetpack/viewmodel/EkmViewModel.kt | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index 61924aba1c..bf49fbc8f1 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -15,6 +15,7 @@ import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse 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.model.OrgRules.DomainRule import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException import kotlinx.coroutines.launch @@ -52,12 +53,12 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) val ekmPrivateResult = repository.getPrivateKeysViaEkm( context = context, ekmUrl = orgRules.keyManagerUrl - ?: throw java.lang.IllegalArgumentException("key_manager_url is empty"), + ?: throw IllegalArgumentException("key_manager_url is empty"), tokenId = tokenId ) if (ekmPrivateResult.data?.privateKeys?.isEmpty() == true) { - throw java.lang.IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) + throw IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) } ekmLiveData.value = ekmPrivateResult @@ -69,32 +70,29 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) private fun checkForUnsupportedOrgRulesCombination(orgRules: OrgRules): UnsupportedOrgRulesException? { - if (orgRules.hasRule(OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN)) { - if (orgRules.hasRule(OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN)) { + if (orgRules.hasRule(DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN)) { + if (orgRules.hasRule(DomainRule.PASS_PHRASE_QUIET_AUTOGEN)) { return UnsupportedOrgRulesException( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + " + - OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN + DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + " + DomainRule.PASS_PHRASE_QUIET_AUTOGEN ) } - if (!orgRules.hasRule(OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE)) { + if (!orgRules.hasRule(DomainRule.FORBID_STORING_PASS_PHRASE)) { return UnsupportedOrgRulesException( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + - OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE + DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + + DomainRule.FORBID_STORING_PASS_PHRASE ) } - if (orgRules.hasRule(OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT)) { + if (orgRules.hasRule(DomainRule.ENFORCE_ATTESTER_SUBMIT)) { return UnsupportedOrgRulesException( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + " + - OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT + DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + " + DomainRule.ENFORCE_ATTESTER_SUBMIT ) } - if (!orgRules.hasRule(OrgRules.DomainRule.NO_PRV_CREATE)) { + if (!orgRules.hasRule(DomainRule.NO_PRV_CREATE)) { return UnsupportedOrgRulesException( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + - OrgRules.DomainRule.NO_PRV_CREATE + DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + DomainRule.NO_PRV_CREATE ) } } From dcfa651c7359c7fa3b34e5304eb2d56f258264af Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 13:34:42 +0300 Subject: [PATCH 21/41] Changed logic in RecheckProvidedPassphraseFragment to return a new passphrase to the caller. Refactored code.| #1168 --- .../flowcrypt/email/extensions/FragmentExt.kt | 7 ++++ .../activity/fragment/BackupKeysFragment.kt | 24 +++++++++++++- .../RecheckProvidedPassphraseFragment.kt | 33 ++++++++++--------- .../preferences/SecuritySettingsFragment.kt | 26 ++++++++++++++- .../src/main/res/navigation/nav_graph.xml | 9 +++-- 5 files changed, 79 insertions(+), 20 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/FragmentExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/FragmentExt.kt index 80314b0437..c2ea4b93ac 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/FragmentExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/FragmentExt.kt @@ -49,6 +49,13 @@ fun androidx.fragment.app.Fragment.setNavigationResult(key: String, value: T previousOnResultSavedStateHandle?.set(key, value) } +fun androidx.fragment.app.Fragment.getOnResultSavedStateHandle(destinationId: Int? = null) = + if (destinationId == null) { + null + } else { + navController?.getBackStackEntry(destinationId)?.savedStateHandle + } + fun androidx.fragment.app.Fragment.getNavigationResult( key: String, onResult: (result: T) -> Unit diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt index 21cf7af944..eb1f4c8b22 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt @@ -20,6 +20,7 @@ import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.databinding.FragmentBackupKeysBinding import com.flowcrypt.email.extensions.decrementSafely +import com.flowcrypt.email.extensions.getNavigationResult import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.toast @@ -89,6 +90,7 @@ class BackupKeysFragment : BaseFragment(), ProgressBehaviour { initViews() setupPrivateKeysViewModel() setupBackupsViewModel() + observeOnResultLiveData() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -303,7 +305,7 @@ class BackupKeysFragment : BaseFragment(), ProgressBehaviour { navController?.navigate( BackupKeysFragmentDirections .actionBackupKeysFragmentToCheckPassphraseStrengthFragment( - popBackStackIdIfSuccess = navController?.currentDestination?.id ?: 0, + popBackStackIdIfSuccess = R.id.backupKeysFragment, title = getString(R.string.change_pass_phrase) ) ) @@ -338,6 +340,26 @@ class BackupKeysFragment : BaseFragment(), ProgressBehaviour { } } + private fun observeOnResultLiveData() { + getNavigationResult>(RecheckProvidedPassphraseFragment.KEY_ACCEPTED_PASSPHRASE_RESULT) { + if (it.isSuccess) { + val passphrase = it.getOrNull() as? CharArray ?: return@getNavigationResult + account?.let { accountEntity -> + navController?.navigate( + BackupKeysFragmentDirections + .actionBackupKeysFragmentToChangePassphraseOfImportedKeysFragment( + popBackStackIdIfSuccess = R.id.backupKeysFragment, + title = getString(R.string.pass_phrase_changed), + subTitle = getString(R.string.passphrase_was_changed), + passphrase = String(passphrase), + accountEntity = accountEntity + ) + ) + } + } + } + } + companion object { private const val REQUEST_CODE_GET_URI_FOR_SAVING_PRIVATE_KEY = 10 } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt index 8cf86f86ef..1fe12b8ff6 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt @@ -14,9 +14,11 @@ import android.view.inputmethod.EditorInfo import androidx.navigation.fragment.navArgs import com.flowcrypt.email.R import com.flowcrypt.email.databinding.FragmentRecheckProvidedPassphraseBinding +import com.flowcrypt.email.extensions.getOnResultSavedStateHandle import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment import com.flowcrypt.email.ui.notifications.SystemNotificationManager +import com.flowcrypt.email.util.GeneralUtil import com.google.android.material.snackbar.Snackbar /** @@ -55,21 +57,21 @@ class RecheckProvidedPassphraseFragment : BaseFragment() { binding?.eTRepeatedPassphrase?.setOnEditorActionListener { _, actionId, _ -> return@setOnEditorActionListener when (actionId) { EditorInfo.IME_ACTION_DONE -> { - checkAndMoveOn() + checkAndReturnResult() true } else -> false } } binding?.btConfirmPassphrase?.setOnClickListener { - checkAndMoveOn() + checkAndReturnResult() } binding?.btUseAnotherPassphrase?.setOnClickListener { navController?.navigateUp() } } - private fun checkAndMoveOn() { + private fun checkAndReturnResult() { if (binding?.eTRepeatedPassphrase?.text?.isEmpty() == true) { showInfoSnackbar( view = binding?.root, @@ -79,18 +81,12 @@ class RecheckProvidedPassphraseFragment : BaseFragment() { } else { snackBar?.dismiss() if (binding?.eTRepeatedPassphrase?.text.toString() == args.passphrase) { - account?.let { accountEntity -> - navController?.navigate( - RecheckProvidedPassphraseFragmentDirections - .actionRecheckProvidedPassphraseFragmentToChangePassphraseOfImportedKeysFragment( - popBackStackIdIfSuccess = args.popBackStackIdIfSuccess, - title = getString(R.string.pass_phrase_changed), - subTitle = getString(R.string.passphrase_was_changed), - passphrase = args.passphrase, - accountEntity = accountEntity - ) - ) - } + getOnResultSavedStateHandle(args.popBackStackIdIfSuccess)?.set( + KEY_ACCEPTED_PASSPHRASE_RESULT, + Result.success(binding?.eTRepeatedPassphrase?.text?.toString()?.toCharArray()) + ) + + navController?.popBackStack(args.popBackStackIdIfSuccess, false) } else { showInfoSnackbar( view = binding?.root, @@ -100,5 +96,12 @@ class RecheckProvidedPassphraseFragment : BaseFragment() { } } } + + companion object { + val KEY_ACCEPTED_PASSPHRASE_RESULT = GeneralUtil.generateUniqueExtraKey( + "KEY_ACCEPTED_PASSPHRASE_RESULT", + RecheckProvidedPassphraseFragment::class.java + ) + } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt index af498ccc13..64cf93fb30 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt @@ -12,9 +12,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.preference.Preference import com.flowcrypt.email.Constants import com.flowcrypt.email.R +import com.flowcrypt.email.extensions.getNavigationResult import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.showNeedPassphraseDialog import com.flowcrypt.email.security.KeysStorageImpl +import com.flowcrypt.email.ui.activity.fragment.RecheckProvidedPassphraseFragment import com.flowcrypt.email.ui.activity.fragment.base.BasePreferenceFragment import com.flowcrypt.email.ui.activity.fragment.dialog.FixNeedPassphraseIssueDialogFragment import com.flowcrypt.email.util.UIUtil @@ -32,6 +34,8 @@ class SecuritySettingsFragment : BasePreferenceFragment(), Preference.OnPreferen super.onViewCreated(view, savedInstanceState) (activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.security_and_privacy) + + observeOnResultLiveData() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -82,11 +86,31 @@ class SecuritySettingsFragment : BasePreferenceFragment(), Preference.OnPreferen } } + private fun observeOnResultLiveData() { + getNavigationResult>(RecheckProvidedPassphraseFragment.KEY_ACCEPTED_PASSPHRASE_RESULT) { + if (it.isSuccess) { + val passphrase = it.getOrNull() as? CharArray ?: return@getNavigationResult + account?.let { accountEntity -> + navController?.navigate( + SecuritySettingsFragmentDirections + .actionSecuritySettingsFragmentToChangePassphraseOfImportedKeysFragment( + popBackStackIdIfSuccess = R.id.securitySettingsFragment, + title = getString(R.string.pass_phrase_changed), + subTitle = getString(R.string.passphrase_was_changed), + passphrase = String(passphrase), + accountEntity = accountEntity + ) + ) + } + } + } + } + private fun navigateToCheckPassphraseStrengthFragment() { navController?.navigate( SecuritySettingsFragmentDirections .actionSecuritySettingsFragmentToCheckPassphraseStrengthFragment( - popBackStackIdIfSuccess = navController?.currentDestination?.id ?: 0, + popBackStackIdIfSuccess = R.id.securitySettingsFragment, title = getString(R.string.change_pass_phrase) ) ) diff --git a/FlowCrypt/src/main/res/navigation/nav_graph.xml b/FlowCrypt/src/main/res/navigation/nav_graph.xml index 83e94302d9..76397914f0 100644 --- a/FlowCrypt/src/main/res/navigation/nav_graph.xml +++ b/FlowCrypt/src/main/res/navigation/nav_graph.xml @@ -52,6 +52,9 @@ + - + Date: Thu, 1 Jul 2021 16:10:05 +0300 Subject: [PATCH 22/41] Migrated SignInActivity to Navigation.| #1168 --- FlowCrypt/src/main/AndroidManifest.xml | 4 +- .../email/ui/activity/CheckKeysActivity.kt | 3 +- .../email/ui/activity/SignInActivity.kt | 37 +++++++------- .../fragment/AddOtherAccountFragment.kt | 39 ++++++--------- .../AuthorizeAndSearchBackupsFragment.kt | 49 ++++++------------ .../activity/fragment/MainSignInFragment.kt | 25 +++------- .../src/main/res/layout/activity_sign_in.xml | 8 ++- .../src/main/res/navigation/sing_in_graph.xml | 50 +++++++++++++++++++ 8 files changed, 116 insertions(+), 99 deletions(-) create mode 100644 FlowCrypt/src/main/res/navigation/sing_in_graph.xml diff --git a/FlowCrypt/src/main/AndroidManifest.xml b/FlowCrypt/src/main/AndroidManifest.xml index 2fba9ff57e..e76b58b3b7 100644 --- a/FlowCrypt/src/main/AndroidManifest.xml +++ b/FlowCrypt/src/main/AndroidManifest.xml @@ -54,7 +54,9 @@ + android:screenOrientation="portrait"> + + = mutableListOf() private val unlockedKeys: ArrayList = ArrayList() diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/SignInActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/SignInActivity.kt index 074a7306c4..324d37878d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/SignInActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/SignInActivity.kt @@ -11,11 +11,14 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.View +import androidx.navigation.findNavController import com.flowcrypt.email.BuildConfig import com.flowcrypt.email.R +import com.flowcrypt.email.SingInGraphDirections +import com.flowcrypt.email.ui.activity.base.BaseActivity import com.flowcrypt.email.ui.activity.fragment.AddOtherAccountFragment -import com.flowcrypt.email.ui.activity.fragment.MainSignInFragment import com.flowcrypt.email.ui.activity.fragment.UserRecoverableAuthExceptionFragment +import com.flowcrypt.email.ui.activity.fragment.base.BaseOAuthFragment import com.flowcrypt.email.ui.notifications.ErrorNotificationManager import com.flowcrypt.email.util.GeneralUtil @@ -27,7 +30,7 @@ import com.flowcrypt.email.util.GeneralUtil * Time: 14:50 * E-mail: DenBond7@gmail.com */ -class SignInActivity : BaseNodeActivity() { +class SignInActivity : BaseActivity() { private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null private val resultBundle: Bundle? = null @@ -51,20 +54,9 @@ class SignInActivity : BaseNodeActivity() { if (savedInstanceState == null) { when (intent.action) { ACTION_UPDATE_OAUTH_ACCOUNT -> { - supportFragmentManager.beginTransaction().add( - R.id.fragmentContainerView, - UserRecoverableAuthExceptionFragment().apply { - arguments = intent.extras - }, - UserRecoverableAuthExceptionFragment::class.java.simpleName - ).commitNow() + findNavController(R.id.fragmentContainerView) + .navigate(SingInGraphDirections.actionGlobalUserRecoverableAuthExceptionFragment()) } - - else -> supportFragmentManager.beginTransaction().add( - R.id.fragmentContainerView, - MainSignInFragment(), - MainSignInFragment::class.java.simpleName - ).commitNow() } } } @@ -89,19 +81,24 @@ class SignInActivity : BaseNodeActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) + val fragments = + supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments val fragment = when (intent?.action) { ACTION_UPDATE_OAUTH_ACCOUNT -> { - supportFragmentManager - .findFragmentByTag(UserRecoverableAuthExceptionFragment::class.java.simpleName) as UserRecoverableAuthExceptionFragment? + fragments?.firstOrNull { + it is UserRecoverableAuthExceptionFragment + } } else -> { - supportFragmentManager - .findFragmentByTag(AddOtherAccountFragment::class.java.simpleName) as AddOtherAccountFragment? + fragments?.firstOrNull { + it is AddOtherAccountFragment + } } } - fragment?.handleOAuth2Intent(intent) + val oAuthFragment = fragment as? BaseOAuthFragment + oAuthFragment?.handleOAuth2Intent(intent) } companion object { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AddOtherAccountFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AddOtherAccountFragment.kt index e670b7ae2b..ece6fac9ae 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AddOtherAccountFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AddOtherAccountFragment.kt @@ -37,6 +37,7 @@ import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.addInputFilter import com.flowcrypt.email.extensions.hideKeyboard +import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.extensions.showTwoWayDialog import com.flowcrypt.email.model.KeyImportDetails @@ -132,7 +133,7 @@ class AddOtherAccountFragment : BaseSingInFragment(), AdapterView.OnItemSelected Activity.RESULT_OK -> if (existedAccounts.isEmpty()) runEmailManagerActivity() else returnResultOk() CreateOrImportKeyActivity.RESULT_CODE_USE_ANOTHER_ACCOUNT -> { - parentFragmentManager.popBackStack() + navController?.navigateUp() } CreateOrImportKeyActivity.RESULT_CODE_HANDLE_RESOLVED_KEYS -> handleResultFromCheckKeysActivity( @@ -500,24 +501,17 @@ class AddOtherAccountFragment : BaseSingInFragment(), AdapterView.OnItemSelected smtpPassword = authCredentials.peekSmtpPassword() ) - val nextFrag = AuthorizeAndSearchBackupsFragment.newInstance(account) - activity?.supportFragmentManager?.beginTransaction() - ?.replace( - R.id.fragmentContainerView, - nextFrag, - AuthorizeAndSearchBackupsFragment::class.java.simpleName - ) - ?.addToBackStack(null) - ?.commit() + navController?.navigate( + AddOtherAccountFragmentDirections + .actionAddOtherAccountFragmentToAuthorizeAndSearchBackupsFragment(account) + ) return@let } else { showContent() showInfoSnackbar( - msgText = getString( - R.string.template_email_already_added, - existedAccount.email - ), duration = Snackbar.LENGTH_LONG + msgText = getString(R.string.template_email_already_added, existedAccount.email), + duration = Snackbar.LENGTH_LONG ) } } @@ -651,7 +645,7 @@ class AddOtherAccountFragment : BaseSingInFragment(), AdapterView.OnItemSelected view, getString(R.string.less_secure_login_is_not_allowed), getString(android.R.string.ok), Snackbar.LENGTH_LONG ) { - parentFragmentManager.popBackStack() + navController?.navigateUp() } } @@ -790,15 +784,10 @@ class AddOtherAccountFragment : BaseSingInFragment(), AdapterView.OnItemSelected authCreds = generateAuthCreds() authCreds?.let { authCredentials -> val account = AccountEntity(authCredentials) - val nextFrag = AuthorizeAndSearchBackupsFragment.newInstance(account) - activity?.supportFragmentManager?.beginTransaction() - ?.replace( - R.id.fragmentContainerView, - nextFrag, - AuthorizeAndSearchBackupsFragment::class.java.simpleName - ) - ?.addToBackStack(null) - ?.commit() + navController?.navigate( + AddOtherAccountFragmentDirections + .actionAddOtherAccountFragmentToAuthorizeAndSearchBackupsFragment(account) + ) } } } @@ -832,7 +821,7 @@ class AddOtherAccountFragment : BaseSingInFragment(), AdapterView.OnItemSelected Activity.RESULT_CANCELED -> showContent() CheckKeysActivity.RESULT_NEGATIVE -> { - parentFragmentManager.popBackStack() + navController?.navigateUp() } } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt index 6762f0cfa8..bfd0f667fe 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt @@ -11,12 +11,13 @@ import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResult import androidx.fragment.app.viewModels import androidx.lifecycle.Observer +import androidx.navigation.fragment.navArgs import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.incrementSafely -import com.flowcrypt.email.extensions.toast +import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.jetpack.viewmodel.CheckEmailSettingsViewModel import com.flowcrypt.email.jetpack.viewmodel.LoadPrivateKeysViewModel import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment @@ -30,11 +31,10 @@ import com.flowcrypt.email.util.GeneralUtil * E-mail: DenBond7@gmail.com */ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { + private val args by navArgs() private val checkEmailSettingsViewModel: CheckEmailSettingsViewModel by viewModels() private val loadPrivateKeysViewModel: LoadPrivateKeysViewModel by viewModels() - private lateinit var accountEntity: AccountEntity - override val progressView: View? get() = view?.findViewById(R.id.progress) override val contentView: View? @@ -56,24 +56,18 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { } private fun fetchBackups() { - if (arguments?.containsKey(KEY_ACCOUNT) == true) { - accountEntity = arguments?.getParcelable(KEY_ACCOUNT) ?: return - when (accountEntity.accountType) { - AccountEntity.ACCOUNT_TYPE_GOOGLE -> { - if (accountEntity.useAPI) { - loadPrivateKeysViewModel.fetchAvailableKeys(accountEntity) - } else { - checkEmailSettingsViewModel.checkAccount(accountEntity) - } + when (args.account.accountType) { + AccountEntity.ACCOUNT_TYPE_GOOGLE -> { + if (args.account.useAPI) { + loadPrivateKeysViewModel.fetchAvailableKeys(args.account) + } else { + checkEmailSettingsViewModel.checkAccount(args.account) } + } - else -> { - checkEmailSettingsViewModel.checkAccount(accountEntity) - } + else -> { + checkEmailSettingsViewModel.checkAccount(args.account) } - } else { - toast("Account is null!") - parentFragmentManager.popBackStack() } } @@ -87,7 +81,7 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { } Result.Status.SUCCESS -> { - loadPrivateKeysViewModel.fetchAvailableKeys(accountEntity) + loadPrivateKeysViewModel.fetchAvailableKeys(args.account) baseActivity.countingIdlingResource.decrementSafely() } @@ -97,7 +91,7 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { bundleOf(KEY_CHECK_ACCOUNT_SETTINGS_RESULT to it) ) baseActivity.countingIdlingResource.decrementSafely() - parentFragmentManager.popBackStack() + navController?.navigateUp() } } } @@ -118,7 +112,7 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { bundleOf(KEY_PRIVATE_KEY_BACKUPS_RESULT to it) ) baseActivity.countingIdlingResource.decrementSafely() - parentFragmentManager.popBackStack() + navController?.navigateUp() } } }) @@ -141,18 +135,5 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { "KEY_CHECK_ACCOUNT_SETTINGS_RESULT", AuthorizeAndSearchBackupsFragment::class.java ) - - private val KEY_ACCOUNT = GeneralUtil.generateUniqueExtraKey( - "KEY_ACCOUNT", - AuthorizeAndSearchBackupsFragment::class.java - ) - - fun newInstance(accountEntity: AccountEntity): AuthorizeAndSearchBackupsFragment { - val fragment = AuthorizeAndSearchBackupsFragment() - val args = Bundle() - args.putParcelable(KEY_ACCOUNT, accountEntity) - fragment.arguments = args - return fragment - } } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index c1ba64977e..2883d26568 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -23,6 +23,7 @@ import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.incrementSafely +import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.extensions.showTwoWayDialog import com.flowcrypt.email.extensions.toast @@ -185,15 +186,9 @@ class MainSignInFragment : BaseSingInFragment() { } view.findViewById(R.id.buttonOtherEmailProvider)?.setOnClickListener { - val addOtherAccountFragment = AddOtherAccountFragment.newInstance() - activity?.supportFragmentManager?.beginTransaction() - ?.replace( - R.id.fragmentContainerView, - addOtherAccountFragment, - AddOtherAccountFragment::class.java.simpleName - ) - ?.addToBackStack(null) - ?.commit() + navController?.navigate( + MainSignInFragmentDirections.actionMainSignInFragmentToAddOtherAccountFragment() + ) } view.findViewById(R.id.buttonPrivacy)?.setOnClickListener { @@ -285,15 +280,11 @@ class MainSignInFragment : BaseSingInFragment() { val intent = CreateOrImportKeyActivity.newIntent(requireContext(), it, true) startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY) } else { - val nextFrag = AuthorizeAndSearchBackupsFragment.newInstance(it) - activity?.supportFragmentManager?.beginTransaction() - ?.replace( - R.id.fragmentContainerView, - nextFrag, - AuthorizeAndSearchBackupsFragment::class.java.simpleName + navController?.navigate( + MainSignInFragmentDirections.actionMainSignInFragmentToAuthorizeAndSearchBackupsFragment( + it ) - ?.addToBackStack(null) - ?.commit() + ) } } } else { diff --git a/FlowCrypt/src/main/res/layout/activity_sign_in.xml b/FlowCrypt/src/main/res/layout/activity_sign_in.xml index b6e0fece26..4693ca0b7a 100644 --- a/FlowCrypt/src/main/res/layout/activity_sign_in.xml +++ b/FlowCrypt/src/main/res/layout/activity_sign_in.xml @@ -16,9 +16,15 @@ + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navGraph="@navigation/sing_in_graph" /> diff --git a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml new file mode 100644 index 0000000000..8dbcab8639 --- /dev/null +++ b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + From 5e33d910d73bf3f9883ed5b3e950aa452ce47414 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 16:11:47 +0300 Subject: [PATCH 23/41] Refactored code.| #1168 --- .../activity/fragment/AuthorizeAndSearchBackupsFragment.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt index bfd0f667fe..d405c040bf 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/AuthorizeAndSearchBackupsFragment.kt @@ -10,7 +10,6 @@ import android.view.View import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResult import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer import androidx.navigation.fragment.navArgs import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result @@ -72,7 +71,7 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { } private fun setupCheckEmailSettingsViewModel() { - checkEmailSettingsViewModel.checkEmailSettingsLiveData.observe(viewLifecycleOwner, Observer { + checkEmailSettingsViewModel.checkEmailSettingsLiveData.observe(viewLifecycleOwner, { it?.let { when (it.status) { Result.Status.LOADING -> { @@ -99,7 +98,7 @@ class AuthorizeAndSearchBackupsFragment : BaseFragment(), ProgressBehaviour { } private fun setupLoadPrivateKeysViewModel() { - loadPrivateKeysViewModel.privateKeysLiveData.observe(viewLifecycleOwner, Observer { + loadPrivateKeysViewModel.privateKeysLiveData.observe(viewLifecycleOwner, { when (it.status) { Result.Status.LOADING -> { baseActivity.countingIdlingResource.incrementSafely() From ad49bc8f6eff6c5a5b711769b1f5ff7488cda4f5 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 16:57:56 +0300 Subject: [PATCH 24/41] Fixed navigation in sing_in_graph.xml| #1168 --- .../CheckPassphraseStrengthFragment.kt | 4 +- .../activity/fragment/MainSignInFragment.kt | 20 ++- .../RecheckProvidedPassphraseFragment.kt | 4 +- .../src/main/res/navigation/sing_in_graph.xml | 152 ++++++++++++++++++ 4 files changed, 176 insertions(+), 4 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt index 8eb222ab75..a4291922dd 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt @@ -36,6 +36,7 @@ import com.flowcrypt.email.jetpack.viewmodel.PasswordStrengthViewModel import com.flowcrypt.email.security.pgp.PgpPwd import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment import com.flowcrypt.email.ui.notifications.SystemNotificationManager +import com.flowcrypt.email.util.UIUtil import com.google.android.material.snackbar.Snackbar import org.apache.commons.io.IOUtils import java.nio.charset.StandardCharsets @@ -101,10 +102,11 @@ class CheckPassphraseStrengthFragment : BaseFragment() { } } - binding?.eTPassphrase?.setOnEditorActionListener { _, actionId, _ -> + binding?.eTPassphrase?.setOnEditorActionListener { v, actionId, _ -> return@setOnEditorActionListener when (actionId) { EditorInfo.IME_ACTION_DONE -> { checkAndMoveOn() + UIUtil.hideSoftInput(requireContext(), v) true } else -> false diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index 2883d26568..b94aac319b 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -22,6 +22,7 @@ import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.decrementSafely +import com.flowcrypt.email.extensions.getNavigationResult import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.showInfoDialog @@ -96,6 +97,7 @@ class MainSignInFragment : BaseSingInFragment() { subscribeToCheckAccountSettings() subscribeToAuthorizeAndSearchBackups() + observeOnResultLiveData() initAddNewAccountLiveData() initEnterpriseViewModels() @@ -377,6 +379,15 @@ class MainSignInFragment : BaseSingInFragment() { } } + private fun observeOnResultLiveData() { + getNavigationResult>(RecheckProvidedPassphraseFragment.KEY_ACCEPTED_PASSPHRASE_RESULT) { + if (it.isSuccess) { + val passphrase = it.getOrNull() as? CharArray ?: return@getNavigationResult + toast(String(passphrase)) + } + } + } + private fun onFetchKeysCompleted(keyDetailsList: ArrayList?) { if (keyDetailsList.isNullOrEmpty()) { getTempAccount()?.let { @@ -495,8 +506,13 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.SUCCESS -> { showContent() - toast("Debug: received = ${it.data?.privateKeys?.size} key(s)") - //encrypt the received keys with the pass phrase. + navController?.navigate( + MainSignInFragmentDirections + .actionMainSignInFragmentToCheckPassphraseStrengthFragment( + popBackStackIdIfSuccess = R.id.mainSignInFragment, + title = getString(R.string.set_up_flow_crypt) + ) + ) ekmViewModel.ekmLiveData.value = Result.none() baseActivity.countingIdlingResource.decrementSafely() } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt index 1fe12b8ff6..d1722f9c37 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/RecheckProvidedPassphraseFragment.kt @@ -19,6 +19,7 @@ import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment import com.flowcrypt.email.ui.notifications.SystemNotificationManager import com.flowcrypt.email.util.GeneralUtil +import com.flowcrypt.email.util.UIUtil import com.google.android.material.snackbar.Snackbar /** @@ -54,10 +55,11 @@ class RecheckProvidedPassphraseFragment : BaseFragment() { private fun initViews() { binding?.tVTitle?.text = args.title - binding?.eTRepeatedPassphrase?.setOnEditorActionListener { _, actionId, _ -> + binding?.eTRepeatedPassphrase?.setOnEditorActionListener { v, actionId, _ -> return@setOnEditorActionListener when (actionId) { EditorInfo.IME_ACTION_DONE -> { checkAndReturnResult() + UIUtil.hideSoftInput(requireContext(), v) true } else -> false diff --git a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml index 8dbcab8639..beecc9ec31 100644 --- a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml +++ b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml @@ -20,6 +20,9 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8b8ae884ea4e32c7be5b48d14dcf3433ec784b10 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 1 Jul 2021 19:17:36 +0300 Subject: [PATCH 25/41] Added saving prv keys that were received from EKM to the local database.| #1168 --- .../response/api/EkmPrivateKeysResponse.kt | 8 ++++++-- .../email/jetpack/viewmodel/EkmViewModel.kt | 19 ++++++++++++++++++- .../jetpack/viewmodel/PrivateKeysViewModel.kt | 16 +++++++++++++++- .../email/security/model/PgpKeyDetails.kt | 9 +++------ .../activity/fragment/MainSignInFragment.kt | 12 ++++++++++-- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt index d585e0bc91..020685101f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/EkmPrivateKeysResponse.kt @@ -10,6 +10,7 @@ import android.os.Parcelable import com.flowcrypt.email.api.retrofit.response.base.ApiError import com.flowcrypt.email.api.retrofit.response.base.ApiResponse import com.flowcrypt.email.api.retrofit.response.model.Key +import com.flowcrypt.email.security.model.PgpKeyDetails import com.google.gson.annotations.Expose /** @@ -21,18 +22,21 @@ import com.google.gson.annotations.Expose data class EkmPrivateKeysResponse constructor( @Expose val code: Int? = null, @Expose val message: String? = null, - @Expose val privateKeys: List? = null + @Expose val privateKeys: List? = null, + val pgpKeyDetailsList: List? = null ) : ApiResponse { constructor(parcel: Parcel) : this( parcel.readValue(Int::class.java.classLoader) as? Int, parcel.readString(), - parcel.createTypedArrayList(Key) + parcel.createTypedArrayList(Key), + parcel.createTypedArrayList(PgpKeyDetails) ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeValue(code) parcel.writeString(message) parcel.writeTypedList(privateKeys) + parcel.writeTypedList(pgpKeyDetailsList) } override val apiError: ApiError? diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index bf49fbc8f1..23ab2b99df 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -16,6 +16,9 @@ import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse 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.model.OrgRules.DomainRule +import com.flowcrypt.email.database.entity.KeyEntity +import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.toPgpKeyDetails +import com.flowcrypt.email.security.pgp.PgpKey import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException import kotlinx.coroutines.launch @@ -61,7 +64,21 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) throw IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) } - ekmLiveData.value = ekmPrivateResult + val combinedSource = requireNotNull( + ekmPrivateResult.data?.privateKeys?.map { it.decryptedPrivateKey } + ?.joinToString(separator = "\n")) + val pgpKeyDetailsList = PgpKey.parseKeys(combinedSource, false).pgpKeyRingCollection + .pgpSecretKeyRingCollection.map { + it.toPgpKeyDetails().copy(passphraseType = KeyEntity.PassphraseType.RAM) + } + + if (pgpKeyDetailsList.isEmpty()) { + throw IllegalStateException(context.getString(R.string.could_not_load_private_keys)) + } + + ekmLiveData.value = ekmPrivateResult.copy( + data = ekmPrivateResult.data?.copy(pgpKeyDetailsList = pgpKeyDetailsList) + ) } catch (e: Exception) { ekmLiveData.value = Result.exception(e) } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt index 4c2c580fdf..91a9cae88e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt @@ -175,6 +175,11 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl } } + /** + * Encrypt sensitive info of [PgpKeyDetails] via AndroidKeyStore and save it in + * the local database. Please note if we found a fully decrypted key we will protect it + * with a provided pass phrase before saving. + */ fun encryptAndSaveKeysToDatabase( accountEntity: AccountEntity?, keys: List, sourceType: KeyImportDetails.SourceType, addAccountIfNotExist: Boolean = false @@ -210,8 +215,17 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl KeyStoreCryptoManager.encryptSuspend(passphrase) } else null + val protectedPrvKey = if (keyDetails.isFullyDecrypted) { + PgpKey.encryptKey( + requireNotNull(keyDetails.privateKey), + Passphrase(requireNotNull(keyDetails.tempPassphrase)) + ) + } else { + keyDetails.privateKey + } + val encryptedPrvKey = - KeyStoreCryptoManager.encryptSuspend(keyDetails.privateKey).toByteArray() + KeyStoreCryptoManager.encryptSuspend(protectedPrvKey).toByteArray() val keyEntity = keyDetails.toKeyEntity(accountEntity).copy( source = sourceType.toPrivateKeySourceTypeString(), diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/model/PgpKeyDetails.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/model/PgpKeyDetails.kt index d2a1afe2e5..80d94b6b6d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/model/PgpKeyDetails.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/model/PgpKeyDetails.kt @@ -220,11 +220,8 @@ data class PgpKeyDetails constructor( return result } - companion object { - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): PgpKeyDetails = PgpKeyDetails(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): PgpKeyDetails = PgpKeyDetails(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index b94aac319b..c3bf214046 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -21,13 +21,13 @@ 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.database.entity.AccountEntity +import com.flowcrypt.email.database.entity.KeyEntity import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.getNavigationResult import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.extensions.showTwoWayDialog -import com.flowcrypt.email.extensions.toast import com.flowcrypt.email.jetpack.viewmodel.DomainOrgRulesViewModel import com.flowcrypt.email.jetpack.viewmodel.EkmViewModel import com.flowcrypt.email.jetpack.viewmodel.LoginViewModel @@ -383,7 +383,13 @@ class MainSignInFragment : BaseSingInFragment() { getNavigationResult>(RecheckProvidedPassphraseFragment.KEY_ACCEPTED_PASSPHRASE_RESULT) { if (it.isSuccess) { val passphrase = it.getOrNull() as? CharArray ?: return@getNavigationResult - toast(String(passphrase)) + importCandidates.forEach { pgpKeyDetails -> + pgpKeyDetails.passphraseType = KeyEntity.PassphraseType.RAM + pgpKeyDetails.tempPassphrase = passphrase + } + getTempAccount()?.let { account -> + accountViewModel.addNewAccount(account) + } } } } @@ -506,6 +512,8 @@ class MainSignInFragment : BaseSingInFragment() { Result.Status.SUCCESS -> { showContent() + importCandidates.clear() + importCandidates.addAll(it?.data?.pgpKeyDetailsList ?: emptyList()) navController?.navigate( MainSignInFragmentDirections .actionMainSignInFragmentToCheckPassphraseStrengthFragment( From 8d04ce912ae511b0540522ee2c49782c38096907 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 10:03:16 +0300 Subject: [PATCH 26/41] Added configurable lost passphrase hint.| #1168 --- .../email/ui/activity/fragment/BackupKeysFragment.kt | 3 ++- .../ui/activity/fragment/CheckPassphraseStrengthFragment.kt | 1 + .../fragment/preferences/SecuritySettingsFragment.kt | 3 ++- .../main/res/layout/fragment_check_passphrase_strength.xml | 4 ++-- FlowCrypt/src/main/res/navigation/nav_graph.xml | 5 +++++ FlowCrypt/src/main/res/navigation/sing_in_graph.xml | 5 +++++ 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt index eb1f4c8b22..36e21f4b41 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/BackupKeysFragment.kt @@ -306,7 +306,8 @@ class BackupKeysFragment : BaseFragment(), ProgressBehaviour { BackupKeysFragmentDirections .actionBackupKeysFragmentToCheckPassphraseStrengthFragment( popBackStackIdIfSuccess = R.id.backupKeysFragment, - title = getString(R.string.change_pass_phrase) + title = getString(R.string.change_pass_phrase), + lostPassphraseTitle = getString(R.string.loss_of_this_pass_phrase_cannot_be_recovered) ) ) } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt index a4291922dd..83114267e4 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CheckPassphraseStrengthFragment.kt @@ -80,6 +80,7 @@ class CheckPassphraseStrengthFragment : BaseFragment() { private fun initViews() { binding?.tVTitle?.text = args.title + binding?.tVLostPassphraseWarning?.text = args.lostPassphraseTitle binding?.iBShowPasswordHint?.setOnClickListener { navController?.navigate( NavGraphDirections.actionGlobalInfoDialogFragment( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt index 64cf93fb30..625a6fea3f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/SecuritySettingsFragment.kt @@ -111,7 +111,8 @@ class SecuritySettingsFragment : BasePreferenceFragment(), Preference.OnPreferen SecuritySettingsFragmentDirections .actionSecuritySettingsFragmentToCheckPassphraseStrengthFragment( popBackStackIdIfSuccess = R.id.securitySettingsFragment, - title = getString(R.string.change_pass_phrase) + title = getString(R.string.change_pass_phrase), + lostPassphraseTitle = getString(R.string.loss_of_this_pass_phrase_cannot_be_recovered) ) ) } diff --git a/FlowCrypt/src/main/res/layout/fragment_check_passphrase_strength.xml b/FlowCrypt/src/main/res/layout/fragment_check_passphrase_strength.xml index 5bc3a33a55..734f3d83b1 100644 --- a/FlowCrypt/src/main/res/layout/fragment_check_passphrase_strength.xml +++ b/FlowCrypt/src/main/res/layout/fragment_check_passphrase_strength.xml @@ -104,12 +104,12 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/default_margin_content_big" android:gravity="center" - android:text="@string/loss_of_this_pass_phrase_cannot_be_recovered" android:textColor="@color/gray" android:textSize="@dimen/default_text_size_medium" app:layout_constraintEnd_toStartOf="@+id/guidelineRight" app:layout_constraintStart_toStartOf="@+id/guidelineLeft" - app:layout_constraintTop_toBottomOf="@+id/btSetPassphrase" /> + app:layout_constraintTop_toBottomOf="@+id/btSetPassphrase" + tools:text="@string/loss_of_this_pass_phrase_cannot_be_recovered" /> + diff --git a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml index beecc9ec31..9acf9be3dd 100644 --- a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml +++ b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml @@ -59,6 +59,11 @@ + From 6bf6f2c43ab3934b587fa6685319cf5a8d46134b Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 10:26:21 +0300 Subject: [PATCH 27/41] EkmViewModel. Added checking that all fetched keys is fully decrypted.| #1168 --- .../flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt | 10 ++++++++++ FlowCrypt/src/main/res/values/strings.xml | 1 + 2 files changed, 11 insertions(+) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index 23ab2b99df..5111465bda 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -76,6 +76,16 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) throw IllegalStateException(context.getString(R.string.could_not_load_private_keys)) } + //check that all keys were fully decrypted when we fetched them. + // If any is encrypted at all, that's an unexpected error, we should throw an exception. + pgpKeyDetailsList.forEach { + if (!it.isFullyDecrypted) { + throw IllegalStateException( + context.getString(R.string.found_not_fully_decrypted_key_ask_admin, it.fingerprint) + ) + } + } + ekmLiveData.value = ekmPrivateResult.copy( data = ekmPrivateResult.data?.copy(pgpKeyDetailsList = pgpKeyDetailsList) ) diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index a64e8d0f07..26c4874651 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -524,4 +524,5 @@ There are no private keys configured for you. Please ask your systems administrator or help desk Please login again to continue Fetching keys, please wait… + Error. Key %1$s is not fully decrypted. Please ask your systems administrator or help desk From d615485ede57b05427e6fb4879bb09ba31597f48 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 14:42:39 +0300 Subject: [PATCH 28/41] Modified logi of saving prv keys to the local database.| #1168 --- .../email/jetpack/viewmodel/EkmViewModel.kt | 9 +--- .../jetpack/viewmodel/PrivateKeysViewModel.kt | 50 ++++++++++++------- .../flowcrypt/email/security/pgp/PgpKey.kt | 21 ++++++++ .../activity/fragment/MainSignInFragment.kt | 44 +++++++++++++--- .../fragment/base/BaseSingInFragment.kt | 17 ++----- FlowCrypt/src/main/res/values/strings.xml | 3 ++ 6 files changed, 99 insertions(+), 45 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index 5111465bda..93bce94ab3 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -16,8 +16,6 @@ import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse 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.model.OrgRules.DomainRule -import com.flowcrypt.email.database.entity.KeyEntity -import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.toPgpKeyDetails import com.flowcrypt.email.security.pgp.PgpKey import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException @@ -67,13 +65,10 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) val combinedSource = requireNotNull( ekmPrivateResult.data?.privateKeys?.map { it.decryptedPrivateKey } ?.joinToString(separator = "\n")) - val pgpKeyDetailsList = PgpKey.parseKeys(combinedSource, false).pgpKeyRingCollection - .pgpSecretKeyRingCollection.map { - it.toPgpKeyDetails().copy(passphraseType = KeyEntity.PassphraseType.RAM) - } + val pgpKeyDetailsList = PgpKey.parsePrivateKeys(combinedSource) if (pgpKeyDetailsList.isEmpty()) { - throw IllegalStateException(context.getString(R.string.could_not_load_private_keys)) + throw IllegalStateException(context.getString(R.string.could_not_parse_one_of_ekm_key)) } //check that all keys were fully decrypted when we fetched them. diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt index 91a9cae88e..0ff82f09cc 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt @@ -76,6 +76,7 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl val parseKeysLiveData = MutableLiveData>() val createPrivateKeyLiveData = MutableLiveData>() val deleteKeysLiveData = MutableLiveData>() + val protectPrivateKeysLiveData = MutableLiveData>>(Result.none()) val parseKeysResultLiveData: LiveData>> = keysStorage.secretKeyRingsLiveData.switchMap { list -> @@ -177,8 +178,7 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl /** * Encrypt sensitive info of [PgpKeyDetails] via AndroidKeyStore and save it in - * the local database. Please note if we found a fully decrypted key we will protect it - * with a provided pass phrase before saving. + * the local database. */ fun encryptAndSaveKeysToDatabase( accountEntity: AccountEntity?, keys: List, @@ -187,10 +187,17 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl requireNotNull(accountEntity) viewModelScope.launch { + val context: Context = getApplication() savePrivateKeysLiveData.value = Result.loading() try { for (keyDetails in keys) { val fingerprint = keyDetails.fingerprint + if (!keyDetails.isFullyEncrypted) { + throw IllegalStateException( + context.getString(R.string.found_not_fully_encrypted_key, fingerprint) + ) + } + if (roomDatabase.keysDao().getKeyByAccountAndFingerprintSuspend( accountEntity.email.toLowerCase(Locale.US), fingerprint @@ -206,26 +213,13 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl val encryptedPassphrase = if (keyDetails.passphraseType == KeyEntity.PassphraseType.DATABASE) { - val passphrase = if (keyDetails.isFullyDecrypted) { - "" - } else { - keyDetails.tempPassphrase?.let { String(it) } ?: "" - } - - KeyStoreCryptoManager.encryptSuspend(passphrase) + KeyStoreCryptoManager.encryptSuspend( + String(requireNotNull(keyDetails.tempPassphrase)) + ) } else null - val protectedPrvKey = if (keyDetails.isFullyDecrypted) { - PgpKey.encryptKey( - requireNotNull(keyDetails.privateKey), - Passphrase(requireNotNull(keyDetails.tempPassphrase)) - ) - } else { - keyDetails.privateKey - } - val encryptedPrvKey = - KeyStoreCryptoManager.encryptSuspend(protectedPrvKey).toByteArray() + KeyStoreCryptoManager.encryptSuspend(keyDetails.privateKey).toByteArray() val keyEntity = keyDetails.toKeyEntity(accountEntity).copy( source = sourceType.toPrivateKeySourceTypeString(), @@ -389,6 +383,24 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl } } + fun protectPrivateKeys(privateKeys: List, passphrase: Passphrase) { + viewModelScope.launch { + protectPrivateKeysLiveData.value = Result.loading() + try { + val encryptedKeysSource = privateKeys.map { pgpKeyDetails -> + PgpKey.encryptKeySuspend(requireNotNull(pgpKeyDetails.privateKey), passphrase) + }.joinToString(separator = "\n") + + protectPrivateKeysLiveData.value = + Result.success(PgpKey.parsePrivateKeys(encryptedKeysSource) + .map { it.copy(tempPassphrase = passphrase.chars) }) + } catch (e: Exception) { + e.printStackTrace() + protectPrivateKeysLiveData.value = Result.exception(e) + } + } + } + private suspend fun savePrivateKeyToDatabase( accountEntity: AccountEntity, pgpKeyDetails: PgpKeyDetails, diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt index e63f7c0843..9d32306e1d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt @@ -5,8 +5,12 @@ package com.flowcrypt.email.security.pgp +import com.flowcrypt.email.database.entity.KeyEntity import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.armor import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.toPgpKeyDetails +import com.flowcrypt.email.security.model.PgpKeyDetails +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing import org.pgpainless.PGPainless @@ -16,6 +20,16 @@ import java.io.InputStream @Suppress("unused") object PgpKey { + /** + * Encrypt the given key. + * + * @param armored Should be a single private key. + */ + suspend fun encryptKeySuspend(armored: String, passphrase: Passphrase): String = + withContext(Dispatchers.IO) { + return@withContext encryptKey(extractSecretKeyRing(armored), passphrase).armor() + } + /** * Encrypt the given key. * @@ -78,6 +92,13 @@ object PgpKey { .done() } + suspend fun parsePrivateKeys(source: String): List = withContext(Dispatchers.IO) { + parseKeys(source, false).pgpKeyRingCollection + .pgpSecretKeyRingCollection.map { + it.toPgpKeyDetails().copy(passphraseType = KeyEntity.PassphraseType.RAM) + } + } + private fun encryptKey(key: PGPSecretKeyRing, passphrase: Passphrase): PGPSecretKeyRing { return PGPainless.modifyKeyRing(key) .changePassphraseFromOldPassphrase(null) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt index c3bf214046..413d0f05a9 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/MainSignInFragment.kt @@ -23,6 +23,7 @@ import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.KeyEntity import com.flowcrypt.email.extensions.decrementSafely +import com.flowcrypt.email.extensions.exceptionMsg import com.flowcrypt.email.extensions.getNavigationResult import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.navController @@ -58,6 +59,7 @@ import com.google.android.gms.tasks.Task import com.google.android.material.snackbar.Snackbar import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException import com.sun.mail.util.MailConnectException +import org.pgpainless.util.Passphrase import java.net.SocketTimeoutException import java.util.* @@ -102,6 +104,7 @@ class MainSignInFragment : BaseSingInFragment() { initAddNewAccountLiveData() initEnterpriseViewModels() initSavePrivateKeysLiveData() + initProtectPrivateKeysLiveData() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -383,13 +386,7 @@ class MainSignInFragment : BaseSingInFragment() { getNavigationResult>(RecheckProvidedPassphraseFragment.KEY_ACCEPTED_PASSPHRASE_RESULT) { if (it.isSuccess) { val passphrase = it.getOrNull() as? CharArray ?: return@getNavigationResult - importCandidates.forEach { pgpKeyDetails -> - pgpKeyDetails.passphraseType = KeyEntity.PassphraseType.RAM - pgpKeyDetails.tempPassphrase = passphrase - } - getTempAccount()?.let { account -> - accountViewModel.addNewAccount(account) - } + privateKeysViewModel.protectPrivateKeys(importCandidates, Passphrase(passphrase)) } } } @@ -554,6 +551,39 @@ class MainSignInFragment : BaseSingInFragment() { }) } + private fun initProtectPrivateKeysLiveData() { + privateKeysViewModel.protectPrivateKeysLiveData.observe(viewLifecycleOwner, { + when (it.status) { + Result.Status.LOADING -> { + baseActivity.countingIdlingResource.incrementSafely() + showProgress(getString(R.string.processing)) + } + + Result.Status.SUCCESS -> { + importCandidates.clear() + importCandidates.addAll(it.data ?: emptyList()) + importCandidates.forEach { pgpKeyDetails -> + pgpKeyDetails.passphraseType = KeyEntity.PassphraseType.RAM + } + getTempAccount()?.let { account -> + accountViewModel.addNewAccount(account) + } + baseActivity.countingIdlingResource.decrementSafely() + } + + Result.Status.ERROR, Result.Status.EXCEPTION -> { + showContent() + showInfoDialog( + dialogTitle = "", + dialogMsg = it.exceptionMsg, + isCancelable = true + ) + baseActivity.countingIdlingResource.decrementSafely() + } + } + }) + } + private fun showDialogWithRetryButton(it: Result, resultCode: Int) { val errorMsg = it.data?.apiError?.msg ?: it.exception?.message diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/base/BaseSingInFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/base/BaseSingInFragment.kt index 19149fd110..334266e868 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/base/BaseSingInFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/base/BaseSingInFragment.kt @@ -7,13 +7,13 @@ package com.flowcrypt.email.ui.activity.fragment.base import android.app.Activity import android.content.Intent -import android.os.Bundle -import android.view.View import androidx.fragment.app.viewModels import androidx.work.WorkManager import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.database.entity.AccountEntity +import com.flowcrypt.email.extensions.decrementSafely +import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.observeOnce import com.flowcrypt.email.extensions.showInfoDialog import com.flowcrypt.email.jetpack.viewmodel.PrivateKeysViewModel @@ -41,26 +41,18 @@ abstract class BaseSingInFragment : BaseOAuthFragment(), ProgressBehaviour { abstract fun getTempAccount(): AccountEntity? - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initAllAccountsLiveData() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initAddNewAccountLiveData() - } - protected fun initSavePrivateKeysLiveData() { privateKeysViewModel.savePrivateKeysLiveData.observe(viewLifecycleOwner, { it?.let { when (it.status) { Result.Status.LOADING -> { + baseActivity.countingIdlingResource.incrementSafely() showProgress(getString(R.string.processing)) } Result.Status.SUCCESS -> { if (existedAccounts.isEmpty()) runEmailManagerActivity() else returnResultOk() + baseActivity.countingIdlingResource.decrementSafely() } Result.Status.ERROR, Result.Status.EXCEPTION -> { @@ -87,6 +79,7 @@ abstract class BaseSingInFragment : BaseOAuthFragment(), ProgressBehaviour { ?: getString(R.string.unknown_error) ) } + baseActivity.countingIdlingResource.decrementSafely() } } } diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index 26c4874651..3af2e3fcb4 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -524,5 +524,8 @@ There are no private keys configured for you. Please ask your systems administrator or help desk Please login again to continue Fetching keys, please wait… + Error. Key %1$s is not fully decrypted. Error. Key %1$s is not fully decrypted. Please ask your systems administrator or help desk + Error. Key %1$s is not fully encrypted + Couldn\'t parse one of the keys received from EKM From 4f2e9f5d9f89621f72c8b6d55a3c6defe8d6725f Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 14:50:31 +0300 Subject: [PATCH 29/41] Fixed a bug in PgpKey after code refactoring.| #1168 --- .../com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt | 2 ++ .../src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index 93bce94ab3..3a10ae8bd0 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -16,6 +16,7 @@ import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse 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.model.OrgRules.DomainRule +import com.flowcrypt.email.database.entity.KeyEntity import com.flowcrypt.email.security.pgp.PgpKey import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException @@ -66,6 +67,7 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) ekmPrivateResult.data?.privateKeys?.map { it.decryptedPrivateKey } ?.joinToString(separator = "\n")) val pgpKeyDetailsList = PgpKey.parsePrivateKeys(combinedSource) + .map { it.copy(passphraseType = KeyEntity.PassphraseType.RAM) } if (pgpKeyDetailsList.isEmpty()) { throw IllegalStateException(context.getString(R.string.could_not_parse_one_of_ekm_key)) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt index 9d32306e1d..115c1f8035 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt @@ -5,7 +5,6 @@ package com.flowcrypt.email.security.pgp -import com.flowcrypt.email.database.entity.KeyEntity import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.armor import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.toPgpKeyDetails import com.flowcrypt.email.security.model.PgpKeyDetails @@ -94,9 +93,7 @@ object PgpKey { suspend fun parsePrivateKeys(source: String): List = withContext(Dispatchers.IO) { parseKeys(source, false).pgpKeyRingCollection - .pgpSecretKeyRingCollection.map { - it.toPgpKeyDetails().copy(passphraseType = KeyEntity.PassphraseType.RAM) - } + .pgpSecretKeyRingCollection.map { it.toPgpKeyDetails().copy() } } private fun encryptKey(key: PGPSecretKeyRing, passphrase: Passphrase): PGPSecretKeyRing { From 535ebef60cb785e816e2dc15f083ca8e80a4d97c Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 14:52:18 +0300 Subject: [PATCH 30/41] Fixed typo.| #1168 --- .../src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt index 115c1f8035..e8ec0a2ea7 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt @@ -93,7 +93,7 @@ object PgpKey { suspend fun parsePrivateKeys(source: String): List = withContext(Dispatchers.IO) { parseKeys(source, false).pgpKeyRingCollection - .pgpSecretKeyRingCollection.map { it.toPgpKeyDetails().copy() } + .pgpSecretKeyRingCollection.map { it.toPgpKeyDetails() } } private fun encryptKey(key: PGPSecretKeyRing, passphrase: Passphrase): PGPSecretKeyRing { From af5d257f10433d6c00956d60653610554101abc5 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 16:13:45 +0300 Subject: [PATCH 31/41] Improved parsing keys in EkmViewModel.| #1168 --- .../email/jetpack/viewmodel/EkmViewModel.kt | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index 3a10ae8bd0..d11334fc59 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -17,6 +17,7 @@ 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.model.OrgRules.DomainRule import com.flowcrypt.email.database.entity.KeyEntity +import com.flowcrypt.email.security.model.PgpKeyDetails import com.flowcrypt.email.security.pgp.PgpKey import com.flowcrypt.email.util.exception.EkmNotSupportedException import com.flowcrypt.email.util.exception.UnsupportedOrgRulesException @@ -63,23 +64,27 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) throw IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) } - val combinedSource = requireNotNull( - ekmPrivateResult.data?.privateKeys?.map { it.decryptedPrivateKey } - ?.joinToString(separator = "\n")) - val pgpKeyDetailsList = PgpKey.parsePrivateKeys(combinedSource) - .map { it.copy(passphraseType = KeyEntity.PassphraseType.RAM) } + val pgpKeyDetailsList = mutableListOf() + ekmPrivateResult.data?.privateKeys?.forEach { key -> + val parsedList = PgpKey.parsePrivateKeys(requireNotNull(key.decryptedPrivateKey)) + .map { it.copy(passphraseType = KeyEntity.PassphraseType.RAM) } - if (pgpKeyDetailsList.isEmpty()) { - throw IllegalStateException(context.getString(R.string.could_not_parse_one_of_ekm_key)) - } - - //check that all keys were fully decrypted when we fetched them. - // If any is encrypted at all, that's an unexpected error, we should throw an exception. - pgpKeyDetailsList.forEach { - if (!it.isFullyDecrypted) { - throw IllegalStateException( - context.getString(R.string.found_not_fully_decrypted_key_ask_admin, it.fingerprint) - ) + if (parsedList.isEmpty()) { + throw IllegalStateException(context.getString(R.string.could_not_parse_one_of_ekm_key)) + } else { + //check that all keys were fully decrypted when we fetched them. + // If any is encrypted at all, that's an unexpected error, we should throw an exception. + pgpKeyDetailsList.forEach { + if (!it.isFullyDecrypted) { + throw IllegalStateException( + context.getString( + R.string.found_not_fully_decrypted_key_ask_admin, + it.fingerprint + ) + ) + } + } + pgpKeyDetailsList.addAll(parsedList) } } From 2bccb7b8a2c031394d1845c63fb3ed7360fda641 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 16:15:17 +0300 Subject: [PATCH 32/41] Fixed typo.| #1168 --- .../java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index d11334fc59..3f435f2115 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -74,7 +74,7 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) } else { //check that all keys were fully decrypted when we fetched them. // If any is encrypted at all, that's an unexpected error, we should throw an exception. - pgpKeyDetailsList.forEach { + parsedList.forEach { if (!it.isFullyDecrypted) { throw IllegalStateException( context.getString( From 48a62d8d2d15b95dd8eec526cc3a446d7cd622cd Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 2 Jul 2021 20:18:01 +0300 Subject: [PATCH 33/41] Fixed AddOtherAccountFragmentTest.| #1168 --- .../activity/AddOtherAccountFragmentTest.kt | 24 ++++++++----------- FlowCrypt/src/devTest/AndroidManifest.xml | 24 +++++++++++++++++++ .../src/main/res/navigation/sing_in_graph.xml | 12 +++++----- 3 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 FlowCrypt/src/devTest/AndroidManifest.xml diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt index 372ebc28c6..c6436878b0 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt @@ -46,6 +46,7 @@ import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.util.AccountDaoManager import com.flowcrypt.email.util.AuthCredentialsManager import com.flowcrypt.email.util.PrivateKeysManager +import com.flowcrypt.email.util.TestGeneralUtil import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.anyOf @@ -53,7 +54,6 @@ import org.hamcrest.Matchers.instanceOf import org.hamcrest.Matchers.isEmptyString import org.hamcrest.Matchers.not import org.hamcrest.Matchers.startsWith -import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -62,11 +62,6 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith /** - * Note: This test works well on com.google.android.material:material:1.1.0-rc02 and lower. - * it seems there is a bug on releases higher than 1.1.0-rc02. If we will upgrade - * com.google.android.material:material to a newer version we will have to remove - * app:passwordToggleEnabled="true" for layoutPassword to be able to run this test. - * * @author Denis Bondarenko * Date: 01.02.2018 * Time: 13:28 @@ -76,7 +71,11 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AddOtherAccountFragmentTest : BaseTest() { override val useIntents: Boolean = true - override val activityScenarioRule = activityScenarioRule() + override val activityScenarioRule = activityScenarioRule( + TestGeneralUtil.genIntentForNavigationComponent( + uri = "flowcrypt://email.flowcrypt.com/sing-in/other" + ) + ) @get:Rule var ruleChain: TestRule = RuleChain @@ -87,12 +86,6 @@ class AddOtherAccountFragmentTest : BaseTest() { private val authCreds: AuthCredentials = AuthCredentialsManager.getLocalWithOneBackupAuthCreds() - @Before - fun openAddOtherAccountFragment() { - onView(withId(R.id.buttonOtherEmailProvider)) - .perform(click()) - } - @Test fun testShowSnackBarIfFieldEmpty() { onView(withId(R.id.checkBoxAdvancedMode)) @@ -367,7 +360,10 @@ class AddOtherAccountFragmentTest : BaseTest() { fun testWhenNoAccountsAndHasBackup() { val prvKey = PrivateKeysManager .getPgpKeyDetailsFromAssets("pgp/default@flowcrypt.test_fisrtKey_prv_default.asc") - .copy(passphraseType = KeyEntity.PassphraseType.DATABASE) + .copy( + passphraseType = KeyEntity.PassphraseType.DATABASE, + tempPassphrase = TestConstants.DEFAULT_PASSWORD.toCharArray() + ) intending(hasComponent(ComponentName(getTargetContext(), CheckKeysActivity::class.java))) .respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent().apply { diff --git a/FlowCrypt/src/devTest/AndroidManifest.xml b/FlowCrypt/src/devTest/AndroidManifest.xml new file mode 100644 index 0000000000..b7af08e17e --- /dev/null +++ b/FlowCrypt/src/devTest/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml index 9acf9be3dd..2cbec5c189 100644 --- a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml +++ b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml @@ -23,6 +23,9 @@ + + - - Date: Fri, 2 Jul 2021 20:28:15 +0300 Subject: [PATCH 34/41] Fixed SignInActivityEnterpriseTest.| #1168 --- .../enterprise/SignInActivityEnterpriseTest.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 5b63bc19e0..b7622cce5a 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 @@ -26,11 +26,11 @@ import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.junit.annotations.NotReadyForCI import com.flowcrypt.email.rules.ClearAppSettingsRule import com.flowcrypt.email.rules.FlowCryptMockWebServerRule -import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.CreateOrImportKeyActivity import com.flowcrypt.email.ui.activity.SignInActivity import com.flowcrypt.email.ui.activity.base.BaseSignActivityTest +import com.flowcrypt.email.util.TestGeneralUtil import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest @@ -54,7 +54,11 @@ import java.io.InputStreamReader @RunWith(AndroidJUnit4::class) class SignInActivityEnterpriseTest : BaseSignActivityTest() { override val useIntents: Boolean = true - override val activityScenarioRule = activityScenarioRule() + override val activityScenarioRule = activityScenarioRule( + TestGeneralUtil.genIntentForNavigationComponent( + uri = "flowcrypt://email.flowcrypt.com/sing-in/gmail" + ) + ) @Before fun waitWhileToastWillBeDismissed() { @@ -64,26 +68,26 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { @get:Rule var ruleChain: TestRule = RuleChain .outerRule(ClearAppSettingsRule()) - .around(RetryRule.DEFAULT) + //.around(RetryRule.DEFAULT) .around(activityScenarioRule) .around(ScreenshotTestRule()) @Test fun testErrorLogin() { setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_LOGIN_ERROR)) - isToastDisplayed(LOGIN_API_ERROR_RESPONSE.apiError?.msg!!) + isDialogWithTextDisplayed(decorView, LOGIN_API_ERROR_RESPONSE.apiError?.msg!!) } @Test fun testSuccessLoginNotVerified() { setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_LOGIN_NOT_VERIFIED)) - isToastDisplayed(getResString(R.string.user_not_verified)) + isDialogWithTextDisplayed(decorView, getResString(R.string.user_not_verified)) } @Test fun testErrorGetDomainRules() { setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_DOMAIN_RULES_ERROR)) - isToastDisplayed(DOMAIN_ORG_RULES_ERROR_RESPONSE.apiError?.msg!!) + isDialogWithTextDisplayed(decorView, DOMAIN_ORG_RULES_ERROR_RESPONSE.apiError?.msg!!) } @Test From 2ee68ca8f61717164b1d80032560e551bb728c02 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Mon, 5 Jul 2021 09:08:57 +0300 Subject: [PATCH 35/41] Fixed JUnit tests.| #1168 --- .../test/java/com/flowcrypt/email/ParcelableTest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/ParcelableTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/ParcelableTest.kt index 2c1da4f293..97f9a655a2 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/ParcelableTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/ParcelableTest.kt @@ -10,6 +10,7 @@ import android.os.Parcel import android.os.Parcelable import com.flextrade.jfixture.JFixture import com.flowcrypt.email.api.email.model.OutgoingMessageInfo +import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.api.retrofit.response.model.node.GenericMsgBlock import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlock import com.flowcrypt.email.jfixture.MsgBlockGenerationCustomization @@ -69,6 +70,18 @@ class ParcelableTest(val name: String, private val currentClass: Class Date: Mon, 5 Jul 2021 09:47:47 +0300 Subject: [PATCH 36/41] Fixed lint warnings.| #1168 --- FlowCrypt/src/main/res/values/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index 3af2e3fcb4..6ddab17bbf 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -388,7 +388,6 @@ Couldn\'t load private keys Internal Node initializing error Error. The loader result is empty - Couldn\'t load domain rules Could\'t import data One of the provided Private Keys is a partially encrypted private key (some packets are encrypted, some not). Such keys are not supported. Please only import fully encrypted keys, with the same pass phrase for all secret packets. The file is too big @@ -524,7 +523,6 @@ There are no private keys configured for you. Please ask your systems administrator or help desk Please login again to continue Fetching keys, please wait… - Error. Key %1$s is not fully decrypted. Error. Key %1$s is not fully decrypted. Please ask your systems administrator or help desk Error. Key %1$s is not fully encrypted Couldn\'t parse one of the keys received from EKM From c29ffcc6df1f6ef8ccfbf16ba97d070d7427e6f0 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 6 Jul 2021 09:18:36 +0300 Subject: [PATCH 37/41] Fixed typo in names.| #1168 --- .../email/ui/activity/AddOtherAccountFragmentTest.kt | 2 +- .../ui/activity/enterprise/SignInActivityEnterpriseTest.kt | 2 +- FlowCrypt/src/main/res/navigation/sing_in_graph.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt index c6436878b0..d632dbaa81 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/AddOtherAccountFragmentTest.kt @@ -73,7 +73,7 @@ class AddOtherAccountFragmentTest : BaseTest() { override val useIntents: Boolean = true override val activityScenarioRule = activityScenarioRule( TestGeneralUtil.genIntentForNavigationComponent( - uri = "flowcrypt://email.flowcrypt.com/sing-in/other" + uri = "flowcrypt://email.flowcrypt.com/sign-in/other" ) ) 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 b7622cce5a..fdc5e9b5f4 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 @@ -56,7 +56,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { override val useIntents: Boolean = true override val activityScenarioRule = activityScenarioRule( TestGeneralUtil.genIntentForNavigationComponent( - uri = "flowcrypt://email.flowcrypt.com/sing-in/gmail" + uri = "flowcrypt://email.flowcrypt.com/sign-in/gmail" ) ) diff --git a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml index 2cbec5c189..51087ad0f1 100644 --- a/FlowCrypt/src/main/res/navigation/sing_in_graph.xml +++ b/FlowCrypt/src/main/res/navigation/sing_in_graph.xml @@ -25,7 +25,7 @@ app:destination="@id/checkPassphraseStrengthFragment" /> + app:uri="flowcrypt://email.flowcrypt.com/sign-in/gmail" /> + app:uri="flowcrypt://email.flowcrypt.com/sign-in/other" /> Date: Tue, 6 Jul 2021 09:25:20 +0300 Subject: [PATCH 38/41] Fixed retry policy in some tests --- .../ui/activity/ChangePassphraseOfImportedKeysFragmentTest.kt | 3 ++- .../ui/activity/enterprise/SignInActivityEnterpriseTest.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ChangePassphraseOfImportedKeysFragmentTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ChangePassphraseOfImportedKeysFragmentTest.kt index 354fc123c6..720b33c494 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ChangePassphraseOfImportedKeysFragmentTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/ChangePassphraseOfImportedKeysFragmentTest.kt @@ -19,6 +19,7 @@ import com.flowcrypt.email.junit.annotations.DependsOnMailServer import com.flowcrypt.email.rules.AddAccountToDatabaseRule import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.settings.SettingsActivity import com.flowcrypt.email.util.TestGeneralUtil @@ -57,7 +58,7 @@ class ChangePassphraseOfImportedKeysFragmentTest : BaseTest() { .outerRule(ClearAppSettingsRule()) .around(addAccountToDatabaseRule) .around(AddPrivateKeyToDatabaseRule()) - //.around(RetryRule.DEFAULT) + .around(RetryRule.DEFAULT) .around(activityScenarioRule) .around(ScreenshotTestRule()) 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 fdc5e9b5f4..6a03534210 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 @@ -26,6 +26,7 @@ import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.junit.annotations.NotReadyForCI import com.flowcrypt.email.rules.ClearAppSettingsRule import com.flowcrypt.email.rules.FlowCryptMockWebServerRule +import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.CreateOrImportKeyActivity import com.flowcrypt.email.ui.activity.SignInActivity @@ -68,7 +69,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { @get:Rule var ruleChain: TestRule = RuleChain .outerRule(ClearAppSettingsRule()) - //.around(RetryRule.DEFAULT) + .around(RetryRule.DEFAULT) .around(activityScenarioRule) .around(ScreenshotTestRule()) From b1a0b64de4bae649bd380d95b83db3428e1a6262 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 6 Jul 2021 10:17:24 +0300 Subject: [PATCH 39/41] Added some tests in SignInActivityEnterpriseTest.| #1168 --- .../SignInActivityEnterpriseTest.kt | 173 ++++++++++++++++-- .../response/api/DomainOrgRulesResponse.kt | 2 +- .../api/retrofit/response/model/OrgRules.kt | 12 +- .../email/jetpack/viewmodel/EkmViewModel.kt | 4 +- 4 files changed, 167 insertions(+), 24 deletions(-) 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 6a03534210..f6eb5fe3dc 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 @@ -11,6 +11,7 @@ import androidx.test.espresso.intent.Intents.intended import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent 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.rules.activityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest @@ -77,6 +78,8 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { fun testErrorLogin() { setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_LOGIN_ERROR)) isDialogWithTextDisplayed(decorView, LOGIN_API_ERROR_RESPONSE.apiError?.msg!!) + onView(withText(R.string.retry)) + .check(matches(isDisplayed())) } @Test @@ -87,8 +90,78 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { @Test fun testErrorGetDomainRules() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_DOMAIN_RULES_ERROR)) + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_DOMAIN_ORG_RULES_ERROR)) isDialogWithTextDisplayed(decorView, DOMAIN_ORG_RULES_ERROR_RESPONSE.apiError?.msg!!) + onView(withText(R.string.retry)) + .check(matches(isDisplayed())) + } + + @Test + fun testOrgRulesCombinationNotSupportedForMustAutogenPassPhraseQuietlyExisted() { + setupAndClickSignInButton( + genMockGoogleSignInAccountJson( + EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED + ) + ) + isDialogWithTextDisplayed( + decorView = decorView, + message = getResString( + R.string.combination_of_org_rules_is_not_supported, + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + + " + " + OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN + ) + ) + } + + @Test + fun testOrgRulesCombinationNotSupportedForForbidStoringPassPhraseMissing() { + setupAndClickSignInButton( + genMockGoogleSignInAccountJson( + EMAIL_FORBID_STORING_PASS_PHRASE_MISSING + ) + ) + isDialogWithTextDisplayed( + decorView = decorView, + message = getResString( + R.string.combination_of_org_rules_is_not_supported, + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing " + + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE + ) + ) + } + + @Test + fun testOrgRulesCombinationNotSupportedForMustSubmitToAttesterExisted() { + setupAndClickSignInButton( + genMockGoogleSignInAccountJson( + EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED + ) + ) + isDialogWithTextDisplayed( + decorView = decorView, + message = getResString( + R.string.combination_of_org_rules_is_not_supported, + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + + " + " + OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT + ) + ) + } + + @Test + fun testOrgRulesCombinationNotSupportedForForbidCreatingPrivateKeyMissing() { + setupAndClickSignInButton( + genMockGoogleSignInAccountJson( + EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING + ) + ) + isDialogWithTextDisplayed( + decorView = decorView, + message = getResString( + R.string.combination_of_org_rules_is_not_supported, + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing " + + OrgRules.DomainRule.NO_PRV_CREATE + ) + ) } @Test @@ -102,19 +175,28 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { } companion object { - const val EMAIL_WITH_NO_PRV_CREATE_RULE = "no_prv_create@example.com" - const val EMAIL_LOGIN_ERROR = "login_error@example.com" - const val EMAIL_LOGIN_NOT_VERIFIED = "login_not_verified@example.com" - const val EMAIL_DOMAIN_RULES_ERROR = "domain_rules_error@example.com" + private const val EMAIL_EKM_URL = "https://localhost:1212/ekm/" + private const val EMAIL_WITH_NO_PRV_CREATE_RULE = "no_prv_create@flowcrypt.test" + private const val EMAIL_LOGIN_ERROR = "login_error@flowcrypt.test" + private const val EMAIL_LOGIN_NOT_VERIFIED = "login_not_verified@flowcrypt.test" + private const val EMAIL_DOMAIN_ORG_RULES_ERROR = "domain_org_rules_error@flowcrypt.test" + private const val EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED = + "must_autogen_pass_phrase_quietly_existed@flowcrypt.test" + private const val EMAIL_FORBID_STORING_PASS_PHRASE_MISSING = + "forbid_storing_pass_phrase_missing@flowcrypt.test" + private const val EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED = + "must_submit_to_attester_existed@flowcrypt.test" + private const val EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING = + "forbid_creating_private_key_missing@flowcrypt.test" - val LOGIN_API_ERROR_RESPONSE = LoginResponse( + private val LOGIN_API_ERROR_RESPONSE = LoginResponse( ApiError( 400, "Something wrong happened.", "api input: missing key: token" ), null ) - val DOMAIN_ORG_RULES_ERROR_RESPONSE = DomainOrgRulesResponse( + private val DOMAIN_ORG_RULES_ERROR_RESPONSE = DomainOrgRulesResponse( ApiError( 401, "Not logged in or unknown account", "auth" @@ -139,34 +221,95 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(200) .setBody(gson.toJson(LoginResponse(null, isVerified = false))) - EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) + EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) .setBody(gson.toJson(LoginResponse(null, isVerified = true))) EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + + else -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = true))) } } if (request.path.equals("/account/get")) { when (model.account) { - EMAIL_DOMAIN_RULES_ERROR -> return MockResponse().setResponseCode(200) + EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) .setBody(gson.toJson(DOMAIN_ORG_RULES_ERROR_RESPONSE)) EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) .setBody( gson.toJson( DomainOrgRulesResponse( - apiError = null, orgRules = OrgRules( flags = listOf( OrgRules.DomainRule.NO_PRV_CREATE, OrgRules.DomainRule.NO_PRV_BACKUP + ) + ) + ) + ) + ) + + EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED -> return MockResponse() + .setResponseCode(200) + .setBody( + gson.toJson( + DomainOrgRulesResponse( + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN + ), + keyManagerUrl = EMAIL_EKM_URL, + ) + ) + ) + ) + + EMAIL_FORBID_STORING_PASS_PHRASE_MISSING -> return MockResponse() + .setResponseCode(200) + .setBody( + gson.toJson( + DomainOrgRulesResponse( + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN + ), + keyManagerUrl = EMAIL_EKM_URL, + ) + ) + ) + ) + + EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED -> return MockResponse() + .setResponseCode(200) + .setBody( + gson.toJson( + DomainOrgRulesResponse( + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE, + OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT + ), + keyManagerUrl = EMAIL_EKM_URL, + ) + ) + ) + ) + + EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING -> return MockResponse() + .setResponseCode(200) + .setBody( + gson.toJson( + DomainOrgRulesResponse( + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE ), - customKeyserverUrl = null, - keyManagerUrl = null, - disallowAttesterSearchForDomains = null, - enforceKeygenAlgo = null, - enforceKeygenExpireMonths = null + keyManagerUrl = EMAIL_EKM_URL, ) ) ) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt index 93e2525513..e6fc9512bc 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/DomainOrgRulesResponse.kt @@ -23,7 +23,7 @@ import com.google.gson.annotations.SerializedName */ data class DomainOrgRulesResponse constructor( @SerializedName("error") - @Expose override val apiError: ApiError?, + @Expose override val apiError: ApiError? = null, @SerializedName("domain_org_rules") @Expose val orgRules: OrgRules? ) : ApiResponse { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt index e960b8180e..59ff0455e4 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/model/OrgRules.kt @@ -18,17 +18,17 @@ import com.google.gson.annotations.SerializedName * E-mail: DenBond7@gmail.com */ data class OrgRules constructor( - @Expose val flags: List?, + @Expose val flags: List? = null, @SerializedName("custom_keyserver_url") - @Expose val customKeyserverUrl: String?, + @Expose val customKeyserverUrl: String? = null, @SerializedName("key_manager_url") - @Expose val keyManagerUrl: String?, + @Expose val keyManagerUrl: String? = null, @SerializedName("disallow_attester_search_for_domains") - @Expose val disallowAttesterSearchForDomains: List?, + @Expose val disallowAttesterSearchForDomains: List? = null, @SerializedName("enforce_keygen_algo") - @Expose val enforceKeygenAlgo: KeyAlgo?, + @Expose val enforceKeygenAlgo: KeyAlgo? = null, @SerializedName("enforce_keygen_expire_months") - @Expose val enforceKeygenExpireMonths: Int? + @Expose val enforceKeygenExpireMonths: Int? = null ) : Parcelable { constructor(parcel: Parcel) : this( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index 3f435f2115..f8506be705 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -108,7 +108,7 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) if (!orgRules.hasRule(DomainRule.FORBID_STORING_PASS_PHRASE)) { return UnsupportedOrgRulesException( - DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + + DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing " + DomainRule.FORBID_STORING_PASS_PHRASE ) } @@ -121,7 +121,7 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) if (!orgRules.hasRule(DomainRule.NO_PRV_CREATE)) { return UnsupportedOrgRulesException( - DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing" + DomainRule.NO_PRV_CREATE + DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN.name + " + missing " + DomainRule.NO_PRV_CREATE ) } } From 01f2b494555968623d857167649d4056af3bf19b Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 6 Jul 2021 10:40:43 +0300 Subject: [PATCH 40/41] Added final tests in SignInActivityEnterpriseTest. Refactored code.| #1168 --- ...ted_prv_key@flowcrypt.test_prv_default.asc | 16 ++ .../SignInActivityEnterpriseTest.kt | 241 +++++++++++++----- .../email/jetpack/viewmodel/EkmViewModel.kt | 5 + 3 files changed, 194 insertions(+), 68 deletions(-) create mode 100644 FlowCrypt/src/androidTest/assets/pgp/keys/user_with_not_fully_decrypted_prv_key@flowcrypt.test_prv_default.asc diff --git a/FlowCrypt/src/androidTest/assets/pgp/keys/user_with_not_fully_decrypted_prv_key@flowcrypt.test_prv_default.asc b/FlowCrypt/src/androidTest/assets/pgp/keys/user_with_not_fully_decrypted_prv_key@flowcrypt.test_prv_default.asc new file mode 100644 index 0000000000..9819763093 --- /dev/null +++ b/FlowCrypt/src/androidTest/assets/pgp/keys/user_with_not_fully_decrypted_prv_key@flowcrypt.test_prv_default.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: PGPainless + +lFgEYOQXpxYJKwYBBAHaRw8BAQdAkTAf7NB9KTdPKURfpVJtGFFHxdC8MiRf48p9 +suMQNE0AAQDcmTu8h1zk+AzVsF7xk9wo+IQox9s2zEbqmfMTg7Ei4BLwtA91c2Vy +QGRvbWFpbi5jb22IeAQTFgoAIAUCYOQXpwIbAwUWAgMBAAQLCQgHBRUKCQgLAh4B +AhkBAAoJENxCZpSKy9MXkdYA/jMYDCabqraBwyPaI1Rj2Np6FaBf5tnzqLhPSlGr +SPk2AQCGHy17hSqROQRGZ7N2gjcK9t+YryXpKPLUFvfGg7udA5yLBGDkF6cSCisG +AQQBl1UBBQEBB0DVSw+JMSP6jmrBUcU5L+JeQBlhyP3JHab29+viwiLyEQMBCAf+ +CQMCelkJSZcOGtZgbFPBvgL4yA6y6Y1SZdXYJIVJzNIg++sRg4Yn8VVGOdI4IvOT +2lmIaj1eHSXjB4LklJYolsjukwUsD52y4b+WUL9NYhQuNIh1BBgWCgAdBQJg5Ben +AhsMBRYCAwEABAsJCAcFFQoJCAsCHgEACgkQ3EJmlIrL0xecUAD/bu30+EqLdDTF +m6ovhH8vgqzYnqSlV5cGQSAu8fxcqdIA/37BPHtpD3zf6aT+QGluYp2RthgCDU1x +LpFAAR/Km+8I +=MxfA +-----END PGP PRIVATE KEY BLOCK----- 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 f6eb5fe3dc..134efcbbc7 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 @@ -21,8 +21,10 @@ import com.flowcrypt.email.TestConstants import com.flowcrypt.email.api.retrofit.ApiHelper import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse +import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.base.ApiError +import com.flowcrypt.email.api.retrofit.response.model.Key import com.flowcrypt.email.api.retrofit.response.model.OrgRules import com.flowcrypt.email.junit.annotations.NotReadyForCI import com.flowcrypt.email.rules.ClearAppSettingsRule @@ -32,7 +34,9 @@ import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.activity.CreateOrImportKeyActivity import com.flowcrypt.email.ui.activity.SignInActivity import com.flowcrypt.email.ui.activity.base.BaseSignActivityTest +import com.flowcrypt.email.util.PrivateKeysManager import com.flowcrypt.email.util.TestGeneralUtil +import com.google.gson.Gson import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest @@ -164,6 +168,42 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { ) } + @Test + fun testErrorGetPrvKeysViaEkm() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_GET_KEYS_VIA_EKM_ERROR)) + isDialogWithTextDisplayed(decorView, EKM_ERROR) + onView(withText(R.string.retry)) + .check(matches(isDisplayed())) + } + + @Test + fun testErrorGetPrvKeysViaEkmEmptyList() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_GET_KEYS_VIA_EKM_EMPTY_LIST)) + isDialogWithTextDisplayed(decorView, getResString(R.string.no_prv_keys_ask_admin)) + onView(withText(R.string.retry)) + .check(matches(isDisplayed())) + } + + @Test + fun testErrorGetPrvKeysViaEkmNotFullyDecryptedKey() { + setupAndClickSignInButton( + genMockGoogleSignInAccountJson( + EMAIL_GET_KEYS_VIA_EKM_NOT_FULLY_DECRYPTED + ) + ) + val pgpKeyDetails = PrivateKeysManager.getPgpKeyDetailsFromAssets( + "pgp/keys/user_with_not_fully_decrypted_prv_key@flowcrypt.test_prv_default.asc" + ) + isDialogWithTextDisplayed( + decorView, getResString( + R.string.found_not_fully_decrypted_key_ask_admin, + pgpKeyDetails.fingerprint + ) + ) + onView(withText(R.string.retry)) + .check(matches(isDisplayed())) + } + @Test @NotReadyForCI fun testNoPrvCreateRule() { @@ -175,7 +215,11 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { } companion object { - private const val EMAIL_EKM_URL = "https://localhost:1212/ekm/" + private const val EMAIL_EKM_URL_SUCCESS = "https://localhost:1212/ekm/" + private const val EMAIL_EKM_URL_SUCCESS_EMPTY_LIST = "https://localhost:1212/ekm/empty/" + private const val EMAIL_EKM_URL_SUCCESS_NOT_FULLY_DECRYPTED_KEY = + "https://localhost:1212/ekm/not_fully_decrypted_key/" + private const val EMAIL_EKM_URL_ERROR = "https://localhost:1212/ekm/error/" private const val EMAIL_WITH_NO_PRV_CREATE_RULE = "no_prv_create@flowcrypt.test" private const val EMAIL_LOGIN_ERROR = "login_error@flowcrypt.test" private const val EMAIL_LOGIN_NOT_VERIFIED = "login_not_verified@flowcrypt.test" @@ -188,6 +232,16 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { "must_submit_to_attester_existed@flowcrypt.test" private const val EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING = "forbid_creating_private_key_missing@flowcrypt.test" + private const val EMAIL_GET_KEYS_VIA_EKM_ERROR = "keys_via_ekm_error@flowcrypt.test" + private const val EMAIL_GET_KEYS_VIA_EKM_EMPTY_LIST = "keys_via_ekm_empty_list@flowcrypt.test" + private const val EMAIL_GET_KEYS_VIA_EKM_NOT_FULLY_DECRYPTED = + "user_with_not_fully_decrypted_prv_key@flowcrypt.test" + + private val ACCEPTED_ORG_RULES = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE, + OrgRules.DomainRule.NO_PRV_CREATE + ) private val LOGIN_API_ERROR_RESPONSE = LoginResponse( ApiError( @@ -203,6 +257,12 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { ), null ) + private const val EKM_ERROR = "some error" + private val EKM_ERROR_RESPONSE = EkmPrivateKeysResponse( + code = 400, + message = EKM_ERROR + ) + @get:ClassRule @JvmStatic val mockWebServerRule = @@ -210,6 +270,40 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { override fun dispatch(request: RecordedRequest): MockResponse { val gson = ApiHelper.getInstance(InstrumentationRegistry.getInstrumentation().targetContext).gson + + if (request.path.equals("/ekm/error/v1/keys/private")) { + return MockResponse().setResponseCode(200) + .setBody(gson.toJson(EKM_ERROR_RESPONSE)) + } + + if (request.path.equals("/ekm/empty/v1/keys/private")) { + return MockResponse().setResponseCode(200) + .setBody( + gson.toJson( + EkmPrivateKeysResponse( + privateKeys = emptyList() + ) + ) + ) + } + + if (request.path.equals("/ekm/not_fully_decrypted_key/v1/keys/private")) { + return MockResponse().setResponseCode(200) + .setBody( + gson.toJson( + EkmPrivateKeysResponse( + privateKeys = listOf( + Key( + TestGeneralUtil.readFileFromAssetsAsString( + "pgp/keys/user_with_not_fully_decrypted_prv_key@flowcrypt.test_prv_default.asc" + ) + ) + ) + ) + ) + ) + } + val model = gson.fromJson(InputStreamReader(request.body.inputStream()), LoginModel::class.java) @@ -237,88 +331,99 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) .setBody(gson.toJson(DOMAIN_ORG_RULES_ERROR_RESPONSE)) - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody( - gson.toJson( - DomainOrgRulesResponse( - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.NO_PRV_CREATE, - OrgRules.DomainRule.NO_PRV_BACKUP - ) - ) - ) + EMAIL_WITH_NO_PRV_CREATE_RULE -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.NO_PRV_CREATE, + OrgRules.DomainRule.NO_PRV_BACKUP ) ) + ) - EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED -> return MockResponse() - .setResponseCode(200) - .setBody( - gson.toJson( - DomainOrgRulesResponse( - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, - OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN - ), - keyManagerUrl = EMAIL_EKM_URL, - ) - ) - ) + EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED + -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, ) + ) - EMAIL_FORBID_STORING_PASS_PHRASE_MISSING -> return MockResponse() - .setResponseCode(200) - .setBody( - gson.toJson( - DomainOrgRulesResponse( - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN - ), - keyManagerUrl = EMAIL_EKM_URL, - ) - ) - ) + EMAIL_FORBID_STORING_PASS_PHRASE_MISSING -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, ) + ) - EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED -> return MockResponse() - .setResponseCode(200) - .setBody( - gson.toJson( - DomainOrgRulesResponse( - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, - OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE, - OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT - ), - keyManagerUrl = EMAIL_EKM_URL, - ) - ) - ) + EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE, + OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, ) + ) - EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING -> return MockResponse() - .setResponseCode(200) - .setBody( - gson.toJson( - DomainOrgRulesResponse( - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, - OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE - ), - keyManagerUrl = EMAIL_EKM_URL, - ) - ) - ) + EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, + ) + ) + + EMAIL_GET_KEYS_VIA_EKM_ERROR -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = ACCEPTED_ORG_RULES, + keyManagerUrl = EMAIL_EKM_URL_ERROR, + ) + ) + + EMAIL_GET_KEYS_VIA_EKM_EMPTY_LIST -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = ACCEPTED_ORG_RULES, + keyManagerUrl = EMAIL_EKM_URL_SUCCESS_EMPTY_LIST, + ) + ) + + EMAIL_GET_KEYS_VIA_EKM_NOT_FULLY_DECRYPTED -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = ACCEPTED_ORG_RULES, + keyManagerUrl = EMAIL_EKM_URL_SUCCESS_NOT_FULLY_DECRYPTED_KEY, ) + ) } } return MockResponse().setResponseCode(404) } }) + + private fun successMockResponseForOrgRules(gson: Gson, orgRules: OrgRules) = + MockResponse().setResponseCode(200) + .setBody( + gson.toJson( + DomainOrgRulesResponse( + orgRules = orgRules + ) + ) + ) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt index f8506be705..f8a4fc2d1d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EkmViewModel.kt @@ -60,6 +60,11 @@ class EkmViewModel(application: Application) : BaseAndroidViewModel(application) tokenId = tokenId ) + if (ekmPrivateResult.status != Result.Status.SUCCESS) { + ekmLiveData.value = ekmPrivateResult + return@launch + } + if (ekmPrivateResult.data?.privateKeys?.isEmpty() == true) { throw IllegalStateException(context.getString(R.string.no_prv_keys_ask_admin)) } From 6130fc3c9ea2bda38b73ab017637a4cfa06d2e06 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 6 Jul 2021 12:08:36 +0300 Subject: [PATCH 41/41] SignInActivityEnterpriseTest. Refactored code.| #1168 --- .../SignInActivityEnterpriseTest.kt | 198 +++++++++--------- 1 file changed, 104 insertions(+), 94 deletions(-) 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 134efcbbc7..cf42903975 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 @@ -308,113 +308,123 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() { gson.fromJson(InputStreamReader(request.body.inputStream()), LoginModel::class.java) if (request.path.equals("/account/login")) { - when (model.account) { - EMAIL_LOGIN_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LOGIN_API_ERROR_RESPONSE)) + return handleLoginAPI(model, gson) + } - EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = false))) + if (request.path.equals("/account/get")) { + return handleGetDomainRulesAPI(model, gson) + } - EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + return MockResponse().setResponseCode(404) + } + }) - EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + private fun handleGetDomainRulesAPI(model: LoginModel, gson: Gson): MockResponse { + when (model.account) { + EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(DOMAIN_ORG_RULES_ERROR_RESPONSE)) + + EMAIL_WITH_NO_PRV_CREATE_RULE -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.NO_PRV_CREATE, + OrgRules.DomainRule.NO_PRV_BACKUP + ) + ) + ) - else -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(LoginResponse(null, isVerified = true))) - } - } + EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED + -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, + ) + ) - if (request.path.equals("/account/get")) { - when (model.account) { - EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) - .setBody(gson.toJson(DOMAIN_ORG_RULES_ERROR_RESPONSE)) - - EMAIL_WITH_NO_PRV_CREATE_RULE -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.NO_PRV_CREATE, - OrgRules.DomainRule.NO_PRV_BACKUP - ) - ) - ) + EMAIL_FORBID_STORING_PASS_PHRASE_MISSING -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, + ) + ) - EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED - -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, - OrgRules.DomainRule.PASS_PHRASE_QUIET_AUTOGEN - ), - keyManagerUrl = EMAIL_EKM_URL_SUCCESS, - ) - ) + EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE, + OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, + ) + ) - EMAIL_FORBID_STORING_PASS_PHRASE_MISSING -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN - ), - keyManagerUrl = EMAIL_EKM_URL_SUCCESS, - ) - ) + EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = listOf( + OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, + OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE + ), + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, + ) + ) - EMAIL_MUST_SUBMIT_TO_ATTESTER_EXISTED -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, - OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE, - OrgRules.DomainRule.ENFORCE_ATTESTER_SUBMIT - ), - keyManagerUrl = EMAIL_EKM_URL_SUCCESS, - ) - ) + EMAIL_GET_KEYS_VIA_EKM_ERROR -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = ACCEPTED_ORG_RULES, + keyManagerUrl = EMAIL_EKM_URL_ERROR, + ) + ) - EMAIL_FORBID_CREATING_PRIVATE_KEY_MISSING -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = listOf( - OrgRules.DomainRule.PRV_AUTOIMPORT_OR_AUTOGEN, - OrgRules.DomainRule.FORBID_STORING_PASS_PHRASE - ), - keyManagerUrl = EMAIL_EKM_URL_SUCCESS, - ) - ) + EMAIL_GET_KEYS_VIA_EKM_EMPTY_LIST -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = ACCEPTED_ORG_RULES, + keyManagerUrl = EMAIL_EKM_URL_SUCCESS_EMPTY_LIST, + ) + ) - EMAIL_GET_KEYS_VIA_EKM_ERROR -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = ACCEPTED_ORG_RULES, - keyManagerUrl = EMAIL_EKM_URL_ERROR, - ) - ) + EMAIL_GET_KEYS_VIA_EKM_NOT_FULLY_DECRYPTED -> return successMockResponseForOrgRules( + gson = gson, + orgRules = OrgRules( + flags = ACCEPTED_ORG_RULES, + keyManagerUrl = EMAIL_EKM_URL_SUCCESS_NOT_FULLY_DECRYPTED_KEY, + ) + ) - EMAIL_GET_KEYS_VIA_EKM_EMPTY_LIST -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = ACCEPTED_ORG_RULES, - keyManagerUrl = EMAIL_EKM_URL_SUCCESS_EMPTY_LIST, - ) - ) + else -> return MockResponse().setResponseCode(404) + } + } - EMAIL_GET_KEYS_VIA_EKM_NOT_FULLY_DECRYPTED -> return successMockResponseForOrgRules( - gson = gson, - orgRules = OrgRules( - flags = ACCEPTED_ORG_RULES, - keyManagerUrl = EMAIL_EKM_URL_SUCCESS_NOT_FULLY_DECRYPTED_KEY, - ) - ) - } - } + private fun handleLoginAPI(model: LoginModel, gson: Gson): MockResponse { + when (model.account) { + EMAIL_LOGIN_ERROR -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LOGIN_API_ERROR_RESPONSE)) - return MockResponse().setResponseCode(404) - } - }) + EMAIL_LOGIN_NOT_VERIFIED -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = false))) + + EMAIL_DOMAIN_ORG_RULES_ERROR -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + + EMAIL_WITH_NO_PRV_CREATE_RULE -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + + else -> return MockResponse().setResponseCode(200) + .setBody(gson.toJson(LoginResponse(null, isVerified = true))) + } + } private fun successMockResponseForOrgRules(gson: Gson, orgRules: OrgRules) = MockResponse().setResponseCode(200)