feat(auth): Add common pass check.
This commit is contained in:
parent
e4627b6ff3
commit
9115a3a20a
|
@ -14,6 +14,7 @@ public final class me/proton/core/auth/dagger/BuildConfig {
|
|||
}
|
||||
|
||||
public abstract interface class me/proton/core/auth/dagger/CoreAuthFeaturesModule {
|
||||
public abstract fun bindIsCommonPasswordCheckEnabled (Lme/proton/core/auth/data/usecase/IsCommonPasswordCheckEnabledImpl;)Lme/proton/core/auth/domain/IsCommonPasswordCheckEnabled;
|
||||
public abstract fun bindIsCredentialLessEnabled (Lme/proton/core/auth/data/usecase/IsCredentialLessEnabledImpl;)Lme/proton/core/auth/domain/usecase/IsCredentialLessEnabled;
|
||||
public abstract fun provideIsSsoCustomTabEnabled (Lme/proton/core/auth/data/usecase/IsSsoCustomTabEnabledImpl;)Lme/proton/core/auth/domain/usecase/IsSsoCustomTabEnabled;
|
||||
public abstract fun provideIsSsoEnabled (Lme/proton/core/auth/data/usecase/IsSsoEnabledImpl;)Lme/proton/core/auth/domain/usecase/IsSsoEnabled;
|
||||
|
|
|
@ -25,11 +25,13 @@ import dagger.Provides
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.core.auth.data.usecase.IsCommonPasswordCheckEnabledImpl
|
||||
import me.proton.core.auth.data.MissingScopeListenerImpl
|
||||
import me.proton.core.auth.data.repository.AuthRepositoryImpl
|
||||
import me.proton.core.auth.data.usecase.IsCredentialLessEnabledImpl
|
||||
import me.proton.core.auth.data.usecase.IsSsoCustomTabEnabledImpl
|
||||
import me.proton.core.auth.data.usecase.IsSsoEnabledImpl
|
||||
import me.proton.core.auth.domain.IsCommonPasswordCheckEnabled
|
||||
import me.proton.core.auth.domain.repository.AuthRepository
|
||||
import me.proton.core.auth.domain.usecase.IsCredentialLessEnabled
|
||||
import me.proton.core.auth.domain.usecase.IsSsoCustomTabEnabled
|
||||
|
@ -74,4 +76,9 @@ public interface CoreAuthFeaturesModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
public fun bindIsCredentialLessEnabled(impl: IsCredentialLessEnabledImpl): IsCredentialLessEnabled
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
public fun bindIsCommonPasswordCheckEnabled(impl: IsCommonPasswordCheckEnabledImpl): IsCommonPasswordCheckEnabled
|
||||
|
||||
}
|
||||
|
|
|
@ -631,6 +631,21 @@ public final class me/proton/core/auth/data/repository/AuthRepositoryImpl : me/p
|
|||
public fun validatePhone (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class me/proton/core/auth/data/usecase/IsCommonPasswordCheckEnabledImpl : me/proton/core/auth/domain/IsCommonPasswordCheckEnabled {
|
||||
public fun <init> (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)V
|
||||
public fun invoke (Lme/proton/core/domain/entity/UserId;)Z
|
||||
public fun isLocalEnabled ()Z
|
||||
public fun isRemoteEnabled (Lme/proton/core/domain/entity/UserId;)Z
|
||||
}
|
||||
|
||||
public final class me/proton/core/auth/data/usecase/IsCommonPasswordCheckEnabledImpl_Factory : dagger/internal/Factory {
|
||||
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/auth/data/usecase/IsCommonPasswordCheckEnabledImpl_Factory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/auth/data/usecase/IsCommonPasswordCheckEnabledImpl;
|
||||
public static fun newInstance (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)Lme/proton/core/auth/data/usecase/IsCommonPasswordCheckEnabledImpl;
|
||||
}
|
||||
|
||||
public final class me/proton/core/auth/data/usecase/IsCredentialLessEnabledImpl : me/proton/core/auth/domain/usecase/IsCredentialLessEnabled {
|
||||
public fun <init> (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)V
|
||||
public fun awaitIsRemoteDisabled-8Mi8wO0 (Lme/proton/core/domain/entity/UserId;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
|
|
@ -29,8 +29,8 @@ protonBuild {
|
|||
}
|
||||
|
||||
protonCoverage {
|
||||
branchCoveragePercentage.set(56)
|
||||
lineCoveragePercentage.set(56)
|
||||
branchCoveragePercentage.set(61)
|
||||
lineCoveragePercentage.set(58)
|
||||
}
|
||||
|
||||
publishOption.shouldBePublishedAsLib = true
|
||||
|
@ -42,6 +42,7 @@ android {
|
|||
dependencies {
|
||||
api(
|
||||
project(Module.authDomain),
|
||||
project(Module.challengeData),
|
||||
project(Module.challengeDomain),
|
||||
project(Module.cryptoCommon),
|
||||
project(Module.domain),
|
||||
|
@ -55,7 +56,6 @@ dependencies {
|
|||
)
|
||||
|
||||
implementation(
|
||||
project(Module.challengeData),
|
||||
project(Module.kotlinUtil),
|
||||
`coroutines-core`,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.auth.data.usecase
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import me.proton.core.auth.data.R
|
||||
import me.proton.core.auth.domain.IsCommonPasswordCheckEnabled
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.domain.ExperimentalProtonFeatureFlag
|
||||
import me.proton.core.featureflag.domain.FeatureFlagManager
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import javax.inject.Inject
|
||||
|
||||
class IsCommonPasswordCheckEnabledImpl @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val featureFlagManager: FeatureFlagManager
|
||||
) : IsCommonPasswordCheckEnabled {
|
||||
|
||||
override fun invoke(userId: UserId?): Boolean {
|
||||
return isLocalEnabled() && isRemoteEnabled(userId = userId)
|
||||
}
|
||||
|
||||
override fun isLocalEnabled(): Boolean {
|
||||
return context.resources.getBoolean(R.bool.core_feature_common_password_check_enabled)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalProtonFeatureFlag::class)
|
||||
override fun isRemoteEnabled(userId: UserId?): Boolean {
|
||||
return featureFlagManager.getValue(userId = null, featureId)
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
val featureId = FeatureId("SignUpCommonPasswordCheck")
|
||||
}
|
||||
}
|
|
@ -20,4 +20,5 @@
|
|||
<bool name="core_feature_auth_sso_enabled">true</bool>
|
||||
<bool name="core_feature_auth_sso_custom_tab_enabled">true</bool>
|
||||
<bool name="core_feature_credential_less_enabled">false</bool>
|
||||
<bool name="core_feature_common_password_check_enabled">true</bool>
|
||||
</resources>
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
<public name="core_feature_auth_sso_enabled" type="bool" />
|
||||
<public name="core_feature_auth_sso_custom_tab_enabled" type="bool" />
|
||||
<public name="core_feature_credential_less_enabled" type="bool" />
|
||||
<public name="core_feature_common_password_check_enabled" type="bool" />
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.auth.data.usecase
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import me.proton.core.auth.data.R
|
||||
import me.proton.core.featureflag.domain.ExperimentalProtonFeatureFlag
|
||||
import me.proton.core.featureflag.domain.FeatureFlagManager
|
||||
import org.junit.Assert.*
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
|
||||
@OptIn(ExperimentalProtonFeatureFlag::class)
|
||||
class IsCommonPasswordCheckEnabledImplTest {
|
||||
@MockK
|
||||
private lateinit var context: Context
|
||||
|
||||
@MockK
|
||||
private lateinit var featureFlagManager: FeatureFlagManager
|
||||
|
||||
@MockK
|
||||
private lateinit var resources: Resources
|
||||
|
||||
private val featureId = IsCommonPasswordCheckEnabledImpl.featureId
|
||||
|
||||
private lateinit var tested: IsCommonPasswordCheckEnabledImpl
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
every { context.resources } returns resources
|
||||
every { featureFlagManager.getValue(any(), any()) } returns false
|
||||
tested = IsCommonPasswordCheckEnabledImpl(context, featureFlagManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun commonPasswordCheckEnabled() {
|
||||
every { resources.getBoolean(R.bool.core_feature_common_password_check_enabled) } returns true
|
||||
every { featureFlagManager.getValue(any(), featureId) } returns true
|
||||
assertTrue(tested(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun commonPasswordCheckDisabled() {
|
||||
every { resources.getBoolean(R.bool.core_feature_common_password_check_enabled) } returns false
|
||||
every { featureFlagManager.getValue(any(), featureId) } returns false
|
||||
kotlin.test.assertFalse(tested(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun commonPasswordCheckDisabledRemoteDisabled() {
|
||||
every { resources.getBoolean(R.bool.core_feature_common_password_check_enabled) } returns true
|
||||
every { featureFlagManager.getValue(any(), featureId) } returns false
|
||||
kotlin.test.assertFalse(tested(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun commonPasswordCheckDisabledLocalDisabled() {
|
||||
every { resources.getBoolean(R.bool.core_feature_common_password_check_enabled) } returns false
|
||||
every { featureFlagManager.getValue(any(), featureId) } returns true
|
||||
kotlin.test.assertFalse(tested(null))
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
public abstract interface class me/proton/core/auth/domain/IsCommonPasswordCheckEnabled : me/proton/core/featureflag/domain/IsFeatureFlagEnabled {
|
||||
}
|
||||
|
||||
public final class me/proton/core/auth/domain/LogTag {
|
||||
public static final field INSTANCE Lme/proton/core/auth/domain/LogTag;
|
||||
public static final field INVALID_SRP_PROOF Ljava/lang/String;
|
||||
|
|
|
@ -42,6 +42,7 @@ dependencies {
|
|||
project(Module.challengeDomain),
|
||||
project(Module.cryptoCommon),
|
||||
project(Module.domain),
|
||||
project(Module.featureFlagDomain),
|
||||
project(Module.networkDomain),
|
||||
project(Module.paymentDomain),
|
||||
project(Module.planDomain),
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Proton AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.auth.domain
|
||||
|
||||
import me.proton.core.featureflag.domain.IsFeatureFlagEnabled
|
||||
|
||||
interface IsCommonPasswordCheckEnabled : IsFeatureFlagEnabled
|
|
@ -1815,6 +1815,7 @@ public abstract interface class me/proton/core/auth/presentation/ui/signup/Choos
|
|||
public final class me/proton/core/auth/presentation/ui/signup/ChoosePasswordFragment : me/proton/core/auth/presentation/ui/signup/SignupFragment {
|
||||
public fun <init> ()V
|
||||
public fun onBackPressed ()V
|
||||
public fun onCreate (Landroid/os/Bundle;)V
|
||||
public fun onViewCreated (Landroid/view/View;Landroid/os/Bundle;)V
|
||||
}
|
||||
|
||||
|
@ -1822,6 +1823,14 @@ public abstract interface class me/proton/core/auth/presentation/ui/signup/Choos
|
|||
public abstract fun injectChoosePasswordFragment (Lme/proton/core/auth/presentation/ui/signup/ChoosePasswordFragment;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/auth/presentation/ui/signup/ChoosePasswordFragment_MembersInjector : dagger/MembersInjector {
|
||||
public fun <init> (Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;)Ldagger/MembersInjector;
|
||||
public static fun injectIsCommonPasswordCheckEnabled (Lme/proton/core/auth/presentation/ui/signup/ChoosePasswordFragment;Lme/proton/core/auth/domain/IsCommonPasswordCheckEnabled;)V
|
||||
public synthetic fun injectMembers (Ljava/lang/Object;)V
|
||||
public fun injectMembers (Lme/proton/core/auth/presentation/ui/signup/ChoosePasswordFragment;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/auth/presentation/ui/signup/ChooseUsernameFragment : me/proton/core/auth/presentation/ui/signup/SignupFragment {
|
||||
public static final field ARG_INPUT_CANCELLABLE Ljava/lang/String;
|
||||
public static final field Companion Lme/proton/core/auth/presentation/ui/signup/ChooseUsernameFragment$Companion;
|
||||
|
|
|
@ -51,6 +51,7 @@ dependencies {
|
|||
project(Module.countryDomain),
|
||||
project(Module.cryptoCommon),
|
||||
project(Module.domain),
|
||||
project(Module.featureFlagDomain),
|
||||
project(Module.humanVerificationDomain),
|
||||
project(Module.humanVerificationPresentation),
|
||||
project(Module.networkData),
|
||||
|
@ -58,8 +59,10 @@ dependencies {
|
|||
project(Module.observabilityDomain),
|
||||
project(Module.paymentDomain),
|
||||
project(Module.paymentPresentation),
|
||||
project(Module.planDomain),
|
||||
project(Module.planPresentation),
|
||||
project(Module.presentation),
|
||||
project(Module.telemetryDomain),
|
||||
project(Module.telemetryPresentation),
|
||||
project(Module.userDomain),
|
||||
project(Module.userSettingsDomain),
|
||||
|
@ -72,12 +75,17 @@ dependencies {
|
|||
`lifecycle-common`,
|
||||
`lifecycle-savedState`,
|
||||
lottie,
|
||||
material
|
||||
material,
|
||||
okhttp,
|
||||
`startup-runtime`
|
||||
)
|
||||
|
||||
implementation(
|
||||
project(Module.challengeData),
|
||||
project(Module.countryPresentation),
|
||||
project(Module.deviceUtil),
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.sharedPreferencesUtil),
|
||||
activity,
|
||||
`androidx-browser`,
|
||||
`android-ktx`,
|
||||
|
|
|
@ -21,17 +21,22 @@ package me.proton.core.auth.presentation.ui.signup
|
|||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.core.account.domain.entity.AccountType
|
||||
import me.proton.core.auth.domain.IsCommonPasswordCheckEnabled
|
||||
import me.proton.core.auth.presentation.R
|
||||
import me.proton.core.auth.presentation.databinding.FragmentSignupChoosePasswordBinding
|
||||
import me.proton.core.auth.presentation.viewmodel.signup.SignupViewModel
|
||||
import me.proton.core.observability.domain.metrics.SignupScreenViewTotalV1
|
||||
import me.proton.core.presentation.utils.InvalidPasswordProvider
|
||||
import me.proton.core.presentation.utils.hideKeyboard
|
||||
import me.proton.core.presentation.utils.launchOnScreenView
|
||||
import me.proton.core.presentation.utils.onClick
|
||||
import me.proton.core.presentation.utils.onFailure
|
||||
import me.proton.core.presentation.utils.onSuccess
|
||||
import me.proton.core.presentation.utils.validateInvalidPassword
|
||||
import me.proton.core.presentation.utils.validatePasswordMatch
|
||||
import me.proton.core.presentation.utils.validatePasswordMinLength
|
||||
import me.proton.core.presentation.utils.viewBinding
|
||||
|
@ -41,6 +46,7 @@ import me.proton.core.telemetry.presentation.annotation.ScreenClosed
|
|||
import me.proton.core.telemetry.presentation.annotation.ScreenDisplayed
|
||||
import me.proton.core.telemetry.presentation.annotation.ViewClicked
|
||||
import me.proton.core.telemetry.presentation.annotation.ViewFocused
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ProductMetrics(
|
||||
|
@ -67,13 +73,24 @@ import me.proton.core.telemetry.presentation.annotation.ViewFocused
|
|||
)
|
||||
class ChoosePasswordFragment : SignupFragment(R.layout.fragment_signup_choose_password) {
|
||||
|
||||
@Inject
|
||||
internal lateinit var isCommonPasswordCheckEnabled: IsCommonPasswordCheckEnabled
|
||||
|
||||
private val signupViewModel by activityViewModels<SignupViewModel>()
|
||||
private val binding by viewBinding(FragmentSignupChoosePasswordBinding::bind)
|
||||
private val invalidPasswordProvider by lazy { InvalidPasswordProvider(requireContext()) }
|
||||
|
||||
override fun onBackPressed() {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
invalidPasswordProvider.init()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
@ -111,12 +128,26 @@ class ChoosePasswordFragment : SignupFragment(R.layout.fragment_signup_choose_pa
|
|||
binding.passwordInput.apply {
|
||||
val result = validatePasswordMinLength()
|
||||
.onFailure { setInputError(getString(R.string.auth_signup_validation_password_length)) }
|
||||
.onSuccess { password -> validateConfirmPasswordField(password) }
|
||||
.onSuccess { _ -> validateInvalidPassword() }
|
||||
signupViewModel.onInputValidationResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateConfirmPasswordField(password: String) = with(binding) {
|
||||
private fun validateInvalidPassword() = with(binding) {
|
||||
val onSuccess = { validateConfirmPasswordField() }
|
||||
passwordInput.apply {
|
||||
if (isCommonPasswordCheckEnabled(userId = null)) {
|
||||
val result = validateInvalidPassword(invalidPasswordProvider)
|
||||
.onFailure { setInputError(getString(R.string.auth_signup_password_not_allowed)) }
|
||||
.onSuccess { _ -> onSuccess() }
|
||||
signupViewModel.onInputValidationResult(result)
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateConfirmPasswordField() = with(binding) {
|
||||
val confirmedPassword = confirmPasswordInput.text.toString()
|
||||
val result = passwordInput.validatePasswordMatch(confirmedPassword)
|
||||
.onFailure {
|
||||
|
|
|
@ -36,9 +36,9 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import me.proton.core.account.domain.entity.AccountType
|
||||
import me.proton.core.auth.domain.usecase.PerformLogin
|
||||
import me.proton.core.auth.domain.usecase.signup.SetCreateAccountSuccess
|
||||
import me.proton.core.auth.domain.usecase.signup.PerformCreateExternalEmailUser
|
||||
import me.proton.core.auth.domain.usecase.signup.PerformCreateUser
|
||||
import me.proton.core.auth.domain.usecase.signup.SetCreateAccountSuccess
|
||||
import me.proton.core.auth.domain.usecase.signup.SignupChallengeConfig
|
||||
import me.proton.core.auth.domain.usecase.userAlreadyExists
|
||||
import me.proton.core.auth.presentation.entity.signup.RecoveryMethod
|
||||
|
|
|
@ -135,6 +135,7 @@
|
|||
<string name="auth_signup_error_username_blank">Username must not be blank.</string>
|
||||
<string name="auth_signup_error_passwords_do_not_match">Passwords do not match.</string>
|
||||
<string name="auth_signup_validation_password_length">Password should be at least 8 characters long.</string>
|
||||
<string name="auth_signup_password_not_allowed">Password not allowed.</string>
|
||||
<string name="auth_signup_validation_password">Password must not be empty.</string>
|
||||
<string name="auth_signup_your_account_is_being_setup">Your account is being created…</string>
|
||||
<string name="auth_signup_please_wait">It usually takes no more than a minute.</string>
|
||||
|
|
|
@ -32,9 +32,9 @@ import me.proton.core.account.domain.entity.AccountType
|
|||
import me.proton.core.auth.domain.repository.AuthRepository
|
||||
import me.proton.core.auth.domain.usecase.GetPrimaryUser
|
||||
import me.proton.core.auth.domain.usecase.PerformLogin
|
||||
import me.proton.core.auth.domain.usecase.signup.SetCreateAccountSuccess
|
||||
import me.proton.core.auth.domain.usecase.signup.PerformCreateExternalEmailUser
|
||||
import me.proton.core.auth.domain.usecase.signup.PerformCreateUser
|
||||
import me.proton.core.auth.domain.usecase.signup.SetCreateAccountSuccess
|
||||
import me.proton.core.auth.domain.usecase.signup.SignupChallengeConfig
|
||||
import me.proton.core.auth.presentation.entity.signup.RecoveryMethod
|
||||
import me.proton.core.auth.presentation.entity.signup.RecoveryMethodType
|
||||
|
|
|
@ -30,7 +30,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(
|
||||
api(
|
||||
project(Module.featureFlagData),
|
||||
project(Module.featureFlagDomain)
|
||||
)
|
||||
|
|
|
@ -45,14 +45,15 @@ dependencies {
|
|||
project(Module.dataRoom),
|
||||
project(Module.domain),
|
||||
project(Module.featureFlagDomain),
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.accountManagerDomain),
|
||||
project(Module.observabilityDomain),
|
||||
project(Module.networkData),
|
||||
)
|
||||
|
||||
implementation(
|
||||
project(Module.accountDomain),
|
||||
project(Module.data),
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.networkDomain),
|
||||
|
||||
`android-work-runtime`,
|
||||
|
|
|
@ -877,16 +877,18 @@ public final class me/proton/core/presentation/utils/InputValidationResult {
|
|||
public static final field MAX_YEAR I
|
||||
public static final field MIN_MONTH I
|
||||
public static final field YEAR_2000 I
|
||||
public fun <init> (Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;Lme/proton/core/presentation/utils/InvalidPasswordProvider;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;Lme/proton/core/presentation/utils/InvalidPasswordProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component2 ()Lme/proton/core/presentation/utils/ValidationType;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static synthetic fun copy$default (Lme/proton/core/presentation/utils/InputValidationResult;Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public final fun component4 ()Lme/proton/core/presentation/utils/InvalidPasswordProvider;
|
||||
public final fun copy (Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;Lme/proton/core/presentation/utils/InvalidPasswordProvider;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static synthetic fun copy$default (Lme/proton/core/presentation/utils/InputValidationResult;Ljava/lang/String;Lme/proton/core/presentation/utils/ValidationType;Ljava/lang/String;Lme/proton/core/presentation/utils/InvalidPasswordProvider;ILjava/lang/Object;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getAdditionalText ()Ljava/lang/String;
|
||||
public final fun getCardType ()Lme/proton/core/presentation/utils/CardType;
|
||||
public final fun getProvider ()Lme/proton/core/presentation/utils/InvalidPasswordProvider;
|
||||
public final fun getText ()Ljava/lang/String;
|
||||
public final fun getValidationType ()Lme/proton/core/presentation/utils/ValidationType;
|
||||
public fun hashCode ()I
|
||||
|
@ -898,6 +900,17 @@ public final class me/proton/core/presentation/utils/InputValidationResult {
|
|||
public final class me/proton/core/presentation/utils/InputValidationResult$Companion {
|
||||
}
|
||||
|
||||
public final class me/proton/core/presentation/utils/InvalidPasswordProvider {
|
||||
public static final field Companion Lme/proton/core/presentation/utils/InvalidPasswordProvider$Companion;
|
||||
public static final field FILE_NAME_COMMON_PASSWORDS Ljava/lang/String;
|
||||
public fun <init> (Landroid/content/Context;)V
|
||||
public final fun init (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public final fun isPasswordCommon (Ljava/lang/String;)Z
|
||||
}
|
||||
|
||||
public final class me/proton/core/presentation/utils/InvalidPasswordProvider$Companion {
|
||||
}
|
||||
|
||||
public final class me/proton/core/presentation/utils/LocaleUtilsKt {
|
||||
public static final fun currentLocale (Landroid/content/res/Configuration;)Ljava/util/Locale;
|
||||
}
|
||||
|
@ -1137,6 +1150,7 @@ public final class me/proton/core/presentation/utils/ValidationType : java/lang/
|
|||
public static final field CreditCardCVC Lme/proton/core/presentation/utils/ValidationType;
|
||||
public static final field CreditCardExpirationDate Lme/proton/core/presentation/utils/ValidationType;
|
||||
public static final field Email Lme/proton/core/presentation/utils/ValidationType;
|
||||
public static final field InvalidPassword Lme/proton/core/presentation/utils/ValidationType;
|
||||
public static final field NotBlank Lme/proton/core/presentation/utils/ValidationType;
|
||||
public static final field Password Lme/proton/core/presentation/utils/ValidationType;
|
||||
public static final field PasswordMatch Lme/proton/core/presentation/utils/ValidationType;
|
||||
|
@ -1160,6 +1174,7 @@ public final class me/proton/core/presentation/utils/ValidationUtilsKt {
|
|||
public static final fun validateCreditCardCVC (Lme/proton/core/presentation/ui/view/ProtonInput;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static final fun validateEmail (Lme/proton/core/presentation/ui/view/ProtonInput;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static final fun validateExpirationDate (Lme/proton/core/presentation/ui/view/ProtonInput;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static final fun validateInvalidPassword (Lme/proton/core/presentation/ui/view/ProtonInput;Lme/proton/core/presentation/utils/InvalidPasswordProvider;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static final fun validatePassword (Lme/proton/core/presentation/ui/view/ProtonInput;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static final fun validatePasswordMatch (Lme/proton/core/presentation/ui/view/ProtonInput;Ljava/lang/String;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
public static final fun validatePasswordMinLength (Lme/proton/core/presentation/ui/view/ProtonInput;)Lme/proton/core/presentation/utils/InputValidationResult;
|
||||
|
|
|
@ -43,9 +43,11 @@ android {
|
|||
|
||||
dependencies {
|
||||
api(
|
||||
project(Module.networkDomain),
|
||||
activity,
|
||||
appcompat,
|
||||
`constraint-layout`,
|
||||
coordinatorlayout,
|
||||
`coroutines-core`,
|
||||
fragment,
|
||||
`javax-inject`,
|
||||
|
@ -53,17 +55,19 @@ dependencies {
|
|||
`lifecycle-savedState`,
|
||||
`lifecycle-viewModel`,
|
||||
material,
|
||||
okhttp,
|
||||
recyclerview,
|
||||
)
|
||||
|
||||
implementation(
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.networkData),
|
||||
project(Module.networkDomain),
|
||||
`android-ktx`,
|
||||
`core-splashscreen`,
|
||||
drawerLayout,
|
||||
`hilt-android`,
|
||||
`lifecycle-livedata-core`,
|
||||
`lifecycle-runtime`,
|
||||
`lifecycle-process`,
|
||||
)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.presentation.utils
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class InvalidPasswordProvider(private val context: Context) {
|
||||
|
||||
private var commonPasswords: Set<String>? = null
|
||||
|
||||
suspend fun init() {
|
||||
readPasswords()
|
||||
}
|
||||
|
||||
fun isPasswordCommon(password: String): Boolean = commonPasswords?.contains(password) ?: false
|
||||
|
||||
private suspend fun readPasswords() = withContext(Dispatchers.IO) {
|
||||
if (commonPasswords == null) {
|
||||
commonPasswords = context.readFromAssets(FILE_NAME_COMMON_PASSWORDS)
|
||||
.split("\n")
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.readFromAssets(resource: String): String =
|
||||
assets.open(resource).reader().use { it.readText() }
|
||||
|
||||
companion object {
|
||||
const val FILE_NAME_COMMON_PASSWORDS = "ignis_10k.txt"
|
||||
}
|
||||
}
|
|
@ -36,6 +36,9 @@ fun ProtonInput.validateUsername() =
|
|||
fun ProtonInput.validatePassword() =
|
||||
InputValidationResult(this.text.orEmpty(), ValidationType.Password)
|
||||
|
||||
fun ProtonInput.validateInvalidPassword(provider: InvalidPasswordProvider) =
|
||||
InputValidationResult(text = this.text.orEmpty(), validationType = ValidationType.InvalidPassword, provider = provider)
|
||||
|
||||
fun ProtonInput.validatePasswordMinLength() =
|
||||
InputValidationResult(this.text.orEmpty(), ValidationType.PasswordMinLength)
|
||||
|
||||
|
@ -58,6 +61,7 @@ enum class ValidationType(val minLong: Int = Int.MIN_VALUE, val maxLong: Int = I
|
|||
NotBlank,
|
||||
Username,
|
||||
Password,
|
||||
InvalidPassword,
|
||||
PasswordMinLength(8),
|
||||
PasswordMatch,
|
||||
Email,
|
||||
|
@ -77,7 +81,8 @@ enum class CardType(val regex: String) {
|
|||
data class InputValidationResult(
|
||||
val text: String,
|
||||
val validationType: ValidationType = ValidationType.NotBlank,
|
||||
val additionalText: String? = null
|
||||
val additionalText: String? = null,
|
||||
val provider: InvalidPasswordProvider? = null
|
||||
) {
|
||||
var cardType: CardType? = null
|
||||
|
||||
|
@ -87,6 +92,10 @@ data class InputValidationResult(
|
|||
ValidationType.Password -> validatePassword()
|
||||
ValidationType.PasswordMinLength -> validatePasswordMinLength(validationType.minLong)
|
||||
ValidationType.PasswordMatch -> validateNotBlank() && text == additionalText
|
||||
ValidationType.InvalidPassword -> when {
|
||||
provider == null -> true
|
||||
else -> !provider.isPasswordCommon(text)
|
||||
}
|
||||
ValidationType.Email -> validateEmail()
|
||||
ValidationType.CreditCard -> {
|
||||
cardType = validateCreditCard()
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package me.proton.core.presentation.utils
|
||||
|
||||
import android.content.Context
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import java.io.ByteArrayInputStream
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class InvalidPasswordProviderTest {
|
||||
@MockK
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var tested: InvalidPasswordProvider
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
tested = InvalidPasswordProvider(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty passwords file`() = runTest {
|
||||
mockCommonPasswordAssets("")
|
||||
|
||||
tested.init()
|
||||
assertFalse(tested.isPasswordCommon("password"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `passwords not initialized`() = runTest {
|
||||
mockCommonPasswordAssets("password")
|
||||
|
||||
// Call to `tested.init()` skipped
|
||||
assertFalse(tested.isPasswordCommon("password"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single password`() = runTest {
|
||||
mockCommonPasswordAssets("password")
|
||||
|
||||
tested.init()
|
||||
assertTrue(tested.isPasswordCommon("password"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple passwords`() = runTest {
|
||||
mockCommonPasswordAssets("password\n\n \nqwerty\n asdf\n")
|
||||
|
||||
tested.init()
|
||||
assertTrue(tested.isPasswordCommon("password"))
|
||||
assertTrue(tested.isPasswordCommon("qwerty"))
|
||||
assertTrue(tested.isPasswordCommon("asdf"))
|
||||
}
|
||||
|
||||
private fun mockCommonPasswordAssets(contents: String) {
|
||||
every { context.assets } returns mockk {
|
||||
every { open(any()) } returns ByteArrayInputStream(contents.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ dependencies {
|
|||
project(Module.eventManagerDomain),
|
||||
project(Module.networkData),
|
||||
project(Module.pushDomain),
|
||||
project(Module.kotlinUtil),
|
||||
`coroutines-core`,
|
||||
`javax-inject`,
|
||||
)
|
||||
|
@ -54,7 +55,6 @@ dependencies {
|
|||
implementation(
|
||||
project(Module.userData),
|
||||
project(Module.data),
|
||||
project(Module.kotlinUtil),
|
||||
project(Module.networkDomain),
|
||||
retrofit,
|
||||
`room-ktx`,
|
||||
|
@ -70,6 +70,7 @@ dependencies {
|
|||
project(Module.cryptoCommon),
|
||||
project(Module.keyDomain),
|
||||
project(Module.kotlinTest),
|
||||
project(Module.userDomain),
|
||||
`android-test-core`,
|
||||
`android-work-testing`,
|
||||
`coroutines-test`,
|
||||
|
|
|
@ -19,41 +19,33 @@
|
|||
package me.proton.core.push.data.remote.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.work.Data
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.WorkerFactory
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.testing.TestListenableWorkerBuilder
|
||||
import dagger.hilt.android.testing.BindValue
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.MockKStubScope
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.eventmanager.domain.EventManager
|
||||
import me.proton.core.eventmanager.domain.EventManagerProvider
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.push.domain.entity.PushId
|
||||
import me.proton.core.push.domain.entity.PushObjectType
|
||||
import me.proton.core.push.domain.remote.PushRemoteDataSource
|
||||
import me.proton.core.push.domain.repository.PushRepository
|
||||
import me.proton.core.push.domain.usecase.DeletePushRemote
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import javax.inject.Inject
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@HiltAndroidTest
|
||||
@Config(application = HiltTestApplication::class)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
internal class DeletePushWorkerTest {
|
||||
|
@ -63,23 +55,15 @@ internal class DeletePushWorkerTest {
|
|||
private val testUserId = UserId("test-user-id")
|
||||
private val testPushType = PushObjectType.Messages
|
||||
|
||||
@get:Rule
|
||||
val hiltRule = HiltAndroidRule(this)
|
||||
@MockK
|
||||
private lateinit var pushRepository: PushRepository
|
||||
|
||||
@BindValue
|
||||
@JvmField
|
||||
internal val pushRepository: PushRepository = mockk()
|
||||
|
||||
@BindValue
|
||||
@JvmField
|
||||
internal val deletePushRemote: DeletePushRemote = mockk()
|
||||
|
||||
@Inject
|
||||
internal lateinit var hiltWorkerFactory: HiltWorkerFactory
|
||||
@MockK
|
||||
private lateinit var deletePushRemote: DeletePushRemote
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
hiltRule.inject()
|
||||
MockKAnnotations.init(this)
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
}
|
||||
|
||||
|
@ -106,7 +90,7 @@ internal class DeletePushWorkerTest {
|
|||
@Test
|
||||
fun `missing input data`() {
|
||||
val worker = TestListenableWorkerBuilder<DeletePushWorker>(context, Data.EMPTY)
|
||||
.setWorkerFactory(hiltWorkerFactory)
|
||||
.setWorkerFactory(makeWorkerFactory())
|
||||
.build()
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { worker.doWork() }
|
||||
|
@ -121,8 +105,16 @@ internal class DeletePushWorkerTest {
|
|||
|
||||
val inputData = DeletePushWorker.makeInputData(testUserId, testPushId, testPushType.value)
|
||||
val worker = TestListenableWorkerBuilder<DeletePushWorker>(context, inputData)
|
||||
.setWorkerFactory(hiltWorkerFactory)
|
||||
.setWorkerFactory(makeWorkerFactory())
|
||||
.build()
|
||||
return runBlocking { worker.doWork() }
|
||||
}
|
||||
|
||||
private fun makeWorkerFactory() = object : WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
): ListenableWorker = DeletePushWorker(appContext, workerParameters, pushRepository, deletePushRemote)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue