diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/UsernameGenerationOptions.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/UsernameGenerationOptions.kt index 0ae4409500..a5d1a6325a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/UsernameGenerationOptions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/UsernameGenerationOptions.kt @@ -52,6 +52,9 @@ data class UsernameGenerationOptions( @SerialName("simpleLoginApiKey") val simpleLoginApiKey: String? = null, + @SerialName("simpleLoginSelfHostServerUrl") + val simpleLoginSelfHostServerUrl: String? = null, + @SerialName("duckDuckGoApiKey") val duckDuckGoApiKey: String? = null, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt index 9bcae82a59..adc219c8f0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt @@ -530,6 +530,8 @@ private fun CoachMarkScope.ScrollContent( randomWordHandlers = randomWordHandlers, shouldShowSelfHostServerUrlField = state.shouldShowAnonAddySelfHostServerUrlField, + shouldShowSimpleLoginSelfHostServerUrlField = + state.shouldShowSimpleLoginSelfHostServerField, ) } } @@ -1104,6 +1106,7 @@ private fun UsernameTypeItems( catchAllEmailHandlers: CatchAllEmailHandlers, randomWordHandlers: RandomWordHandlers, shouldShowSelfHostServerUrlField: Boolean, + shouldShowSimpleLoginSelfHostServerUrlField: Boolean, modifier: Modifier = Modifier, ) { Column(modifier = modifier) { @@ -1129,6 +1132,8 @@ private fun UsernameTypeItems( usernameTypeState = selectedType, forwardedEmailAliasHandlers = forwardedEmailAliasHandlers, shouldShowSelfHostServerUrlField = shouldShowSelfHostServerUrlField, + shouldShowSimpleLoginSelfHostServerUrlField = + shouldShowSimpleLoginSelfHostServerUrlField, ) } @@ -1190,6 +1195,7 @@ private fun ForwardedEmailAliasTypeContent( usernameTypeState: GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias, forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers, shouldShowSelfHostServerUrlField: Boolean, + shouldShowSimpleLoginSelfHostServerUrlField: Boolean, modifier: Modifier = Modifier, ) { Column(modifier = modifier) { @@ -1329,6 +1335,22 @@ private fun ForwardedEmailAliasTypeContent( .standardHorizontalMargin() .fillMaxWidth(), ) + + if (shouldShowSimpleLoginSelfHostServerUrlField) { + Spacer(modifier = Modifier.height(8.dp)) + + BitwardenTextField( + label = stringResource(id = R.string.self_host_server_url), + value = usernameTypeState.selectedServiceType.selfHostServerUrl, + onValueChange = + forwardedEmailAliasHandlers.onSimpleLoginSelfHostServerUrlChange, + textFieldTestTag = "SimpleLoginSelfHostServerUrlEntry", + cardStyle = CardStyle.Full, + modifier = Modifier + .standardHorizontalMargin() + .fillMaxWidth(), + ) + } } null -> { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt index c6d7594ef0..2b72378b59 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt @@ -115,6 +115,8 @@ class GeneratorViewModel @Inject constructor( shouldShowAnonAddySelfHostServerUrlField = featureFlagManager.getFeatureFlag( FlagKey.AnonAddySelfHostAlias, ), + shouldShowSimpleLoginSelfHostServerField = + featureFlagManager.getFeatureFlag(FlagKey.SimpleLoginSelfHostAlias), ) }, ) { @@ -152,6 +154,15 @@ class GeneratorViewModel @Inject constructor( } .onEach(::sendAction) .launchIn(viewModelScope) + + featureFlagManager.getFeatureFlagFlow(FlagKey.SimpleLoginSelfHostAlias) + .map { shouldShowSimpleLoginSelfHostServerField -> + GeneratorAction.Internal.ShouldShowSimpleLoginSelfHostValueChangeReceive( + shouldShowSelfHostServerField = shouldShowSimpleLoginSelfHostServerField, + ) + } + .onEach(::sendAction) + .launchIn(viewModelScope) } override fun handleAction(action: GeneratorAction) { @@ -249,6 +260,10 @@ class GeneratorViewModel @Inject constructor( is GeneratorAction.MainType.Username.UsernameType.RandomWord -> { handleRandomWordSpecificAction(action) } + + is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.SimpleLogin.SelfHostServerUrlChange -> { + handleSimpleLoginSelfHostServerUrlChange(action) + } } } @@ -289,6 +304,10 @@ class GeneratorViewModel @Inject constructor( is GeneratorAction.Internal.ShouldShowAnonAddySelfHostValueChangeReceive -> { handleShouldShowAnonAddySelfHostValueChange(action) } + + is GeneratorAction.Internal.ShouldShowSimpleLoginSelfHostValueChangeReceive -> { + handleShouldShowSimpleLoginSelfHostValueChange(action) + } } } @@ -594,6 +613,8 @@ class GeneratorViewModel @Inject constructor( type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS, serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.SIMPLE_LOGIN, simpleLoginApiKey = forwardedEmailAlias.selectedServiceType.apiKey, + simpleLoginSelfHostServerUrl = + forwardedEmailAlias.selectedServiceType.selfHostServerUrl, ) is ForwardEmail -> options.copy( @@ -643,6 +664,7 @@ class GeneratorViewModel @Inject constructor( catchAllEmailDomain = "", firefoxRelayApiAccessToken = "", simpleLoginApiKey = "", + simpleLoginSelfHostServerUrl = "", duckDuckGoApiKey = "", fastMailApiKey = "", anonAddyApiAccessToken = "", @@ -813,6 +835,14 @@ class GeneratorViewModel @Inject constructor( } } + private fun handleShouldShowSimpleLoginSelfHostValueChange( + action: GeneratorAction.Internal.ShouldShowSimpleLoginSelfHostValueChangeReceive, + ) { + mutableStateFlow.update { + it.copy(shouldShowSimpleLoginSelfHostServerField = action.shouldShowSelfHostServerField) + } + } + private fun handlePasswordGeneratorPolicyReceive( action: GeneratorAction.Internal.PasswordGeneratorPolicyReceive, ) { @@ -1392,6 +1422,21 @@ class GeneratorViewModel @Inject constructor( } } + private fun handleSimpleLoginSelfHostServerUrlChange( + action: GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .SimpleLogin + .SelfHostServerUrlChange, + ) { + updateSimpleLoginServiceType { simpleLoginServiceType -> + val newServerUrl = action.url + simpleLoginServiceType.copy(selfHostServerUrl = newServerUrl) + } + } + //endregion SimpleLogin Service Specific Handlers //region Plus Addressed Email Specific Handlers @@ -1538,6 +1583,7 @@ class GeneratorViewModel @Inject constructor( ?.toUsernameGeneratorRequest( website = state.website, allowAddyIoSelfHostUrl = state.shouldShowAnonAddySelfHostServerUrlField, + allowSimpleLoginSelfHostUrl = state.shouldShowSimpleLoginSelfHostServerField, ) ?: run { mutableStateFlow.update { it.copy(generatedText = NO_GENERATED_TEXT) } @@ -1840,6 +1886,7 @@ data class GeneratorState( var passcodePolicyOverride: PasscodePolicyOverride? = null, private val shouldShowCoachMarkTour: Boolean, val shouldShowAnonAddySelfHostServerUrlField: Boolean, + val shouldShowSimpleLoginSelfHostServerField: Boolean, ) : Parcelable { /** @@ -2243,9 +2290,15 @@ data class GeneratorState( @Parcelize data class SimpleLogin( val apiKey: String = "", + val selfHostServerUrl: String = "", ) : ServiceType(), Parcelable { override val displayStringResId: Int get() = ServiceTypeOption.SIMPLE_LOGIN.labelRes + + @Suppress("UndocumentedPublicClass") + companion object { + const val DEFAULT_SIMPLE_LOGIN_URL = "https://app.simplelogin.io" + } } } } @@ -2586,6 +2639,13 @@ sealed class GeneratorAction { * @property apiKey The new api key text. */ data class ApiKeyTextChange(val apiKey: String) : SimpleLogin() + + /** + * Fired when the self host server url input text is changed. + * + * @property url The new self host server url text. + */ + data class SelfHostServerUrlChange(val url: String) : SimpleLogin() } } @@ -2704,6 +2764,13 @@ sealed class GeneratorAction { data class ShouldShowAnonAddySelfHostValueChangeReceive( val shouldShowAnonAddySelfHostServerUrlField: Boolean, ) : Internal() + + /** + * The value for the shouldShowSimpleLoginSelfHostServerField has changed. + */ + data class ShouldShowSimpleLoginSelfHostValueChangeReceive( + val shouldShowSelfHostServerField: Boolean, + ) : Internal() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/handlers/ForwardedEmailAliasHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/handlers/ForwardedEmailAliasHandlers.kt index e5abf4030f..9ed784df14 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/handlers/ForwardedEmailAliasHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/handlers/ForwardedEmailAliasHandlers.kt @@ -12,7 +12,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction.MainType.U * Each lambda corresponds to a specific user action, allowing for easy delegation of * logic when user input is detected. */ -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LongMethod") data class ForwardedEmailAliasHandlers( val onServiceChange: (ForwardedEmailAlias.ServiceTypeOption) -> Unit, val onAddyIoAccessTokenTextChange: (String) -> Unit, @@ -24,6 +24,7 @@ data class ForwardedEmailAliasHandlers( val onForwardEmailApiKeyTextChange: (String) -> Unit, val onForwardEmailDomainNameTextChange: (String) -> Unit, val onSimpleLoginApiKeyTextChange: (String) -> Unit, + val onSimpleLoginSelfHostServerUrlChange: (String) -> Unit, ) { @Suppress("UndocumentedPublicClass") companion object { @@ -93,6 +94,13 @@ data class ForwardedEmailAliasHandlers( ForwardedEmailAliasAction.SimpleLogin.ApiKeyTextChange(apiKey = newApiKey), ) }, + onSimpleLoginSelfHostServerUrlChange = { newServerUrl -> + viewModel.trySendAction( + ForwardedEmailAliasAction.SimpleLogin.SelfHostServerUrlChange( + url = newServerUrl, + ), + ) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensions.kt index 8ea2c1ce07..f27f47709e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensions.kt @@ -24,7 +24,12 @@ fun UsernameGenerationOptions.ForwardedEmailServiceType?.toServiceType( } UsernameGenerationOptions.ForwardedEmailServiceType.SIMPLE_LOGIN -> { - SimpleLogin(apiKey = options.simpleLoginApiKey.orEmpty()) + SimpleLogin( + apiKey = options.simpleLoginApiKey.orEmpty(), + selfHostServerUrl = options.simpleLoginSelfHostServerUrl + ?.prefixHttpsIfNecessary() + .orEmpty(), + ) } UsernameGenerationOptions.ForwardedEmailServiceType.DUCK_DUCK_GO -> { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt index 1183609d2e..c76372b224 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt @@ -13,6 +13,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Us fun ServiceType.toUsernameGeneratorRequest( website: String?, allowAddyIoSelfHostUrl: Boolean, + allowSimpleLoginSelfHostUrl: Boolean, ): UsernameGeneratorRequest.Forwarded? { return when (this) { is ServiceType.AddyIo -> { @@ -80,12 +81,17 @@ fun ServiceType.toUsernameGeneratorRequest( } is ServiceType.SimpleLogin -> { + val baseUrl = if (allowSimpleLoginSelfHostUrl && selfHostServerUrl.isNotBlank()) { + selfHostServerUrl.prefixHttpsIfNecessary() + } else { + ServiceType.SimpleLogin.DEFAULT_SIMPLE_LOGIN_URL + } this .apiKey .orNullIfBlank() ?.let { UsernameGeneratorRequest.Forwarded( - service = ForwarderServiceType.SimpleLogin(apiKey = it), + service = ForwarderServiceType.SimpleLogin(apiKey = it, baseUrl = baseUrl), website = website, ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceTest.kt index acc6153d02..899b9b6412 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceTest.kt @@ -53,6 +53,7 @@ class GeneratorDiskSourceTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", @@ -157,6 +158,7 @@ class GeneratorDiskSourceTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", @@ -195,6 +197,7 @@ class GeneratorDiskSourceTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", diff --git a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt index 207533d1e7..e8ea2a14be 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt @@ -645,6 +645,7 @@ class GeneratorRepositoryTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", @@ -698,6 +699,7 @@ class GeneratorRepositoryTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", @@ -729,6 +731,7 @@ class GeneratorRepositoryTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt index c7208ddd21..4e5db48c3c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt @@ -1365,6 +1365,93 @@ class GeneratorScreenTest : BaseComposeTest() { } } + @Suppress("MaxLineLength") + @Test + fun `in Username_ForwardedEmailAlias_SimpleLogin state, updating self host server url text input should send SelfHostServerUrlChange action`() { + updateState( + DEFAULT_STATE.copy( + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .SimpleLogin(), + ), + ), + ), + ) + + val newSelfHostServerUrl = "https://simplelogin.local" + + composeTestRule + .onNodeWithText("Self-host server URL") + .performScrollTo() + .performTextInput(newSelfHostServerUrl) + + verify { + viewModel.trySendAction( + GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .SimpleLogin + .SelfHostServerUrlChange(url = newSelfHostServerUrl), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in Username_ForwardedEmailAlias_SimpleLogin state, should display self host server url field based on state`() { + updateState( + DEFAULT_STATE.copy( + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .SimpleLogin(), + ), + ), + shouldShowSimpleLoginSelfHostServerField = true, + ), + ) + + composeTestRule + .onNodeWithText("Self-host server URL") + .performScrollTo() + .assertIsDisplayed() + + // Simulate disabling the feature flag. + updateState( + DEFAULT_STATE.copy( + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .SimpleLogin(), + ), + ), + shouldShowSimpleLoginSelfHostServerField = false, + ), + ) + + composeTestRule + .onNodeWithText("Self-host server URL") + .assertDoesNotExist() + } + //endregion SimpleLogin Service Type Tests //region ForwardEmail Service Type Tests @@ -1853,4 +1940,5 @@ private val DEFAULT_STATE = GeneratorState( currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = false, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt index 999feb6294..e91980ed29 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt @@ -122,11 +122,16 @@ class GeneratorViewModelTest : BaseViewModelTest() { every { shouldShowGeneratorCoachMarkFlow } returns mutableShouldShowGeneratorCoachMarkFlow } private val mutableAnonAddySelfHostAliasFlow = MutableStateFlow(true) + private val mutableShouldShowSimpleLoginSelfHostFlow = MutableStateFlow(true) private val featureFlagManager = mockk { every { getFeatureFlag(FlagKey.AnonAddySelfHostAlias) } returns true every { getFeatureFlagFlow(FlagKey.AnonAddySelfHostAlias) } returns mutableAnonAddySelfHostAliasFlow + every { getFeatureFlag(FlagKey.SimpleLoginSelfHostAlias) } returns true + every { + getFeatureFlagFlow(FlagKey.SimpleLoginSelfHostAlias) + } returns mutableShouldShowSimpleLoginSelfHostFlow } @Test @@ -145,6 +150,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { fun `initial state should be correct for username modal`() { val usernameGenerationOptions = UsernameGenerationOptions( type = UsernameGenerationOptions.UsernameType.RANDOM_WORD, + simpleLoginSelfHostServerUrl = "", ) fakeGeneratorRepository.saveUsernameGenerationOptions(usernameGenerationOptions) val expected = GeneratorState( @@ -160,6 +166,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { website = "", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) val viewModel = createViewModel( @@ -195,6 +202,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) val viewModel = createViewModel( @@ -930,6 +938,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { fakeGeneratorRepository.saveUsernameGenerationOptions( UsernameGenerationOptions( type = UsernameGenerationOptions.UsernameType.RANDOM_WORD, + simpleLoginSelfHostServerUrl = "", ), ) val expectedState = initialState.copy( @@ -1010,6 +1019,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { fakeGeneratorRepository.saveUsernameGenerationOptions( UsernameGenerationOptions( type = UsernameGenerationOptions.UsernameType.RANDOM_WORD, + simpleLoginSelfHostServerUrl = "", ), ) // When this action is handled there will be another call to `loadOptions()` @@ -2049,6 +2059,42 @@ class GeneratorViewModelTest : BaseViewModelTest() { assertEquals(expectedState, viewModel.stateFlow.value) } + + @Test + fun `SelfHostServerUrlChange should update self host server url correctly`() = runTest { + val newSelfHostServerUrl = "https://newSelfHostServerUrl" + val action = GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .SimpleLogin + .SelfHostServerUrlChange( + url = newSelfHostServerUrl, + ) + + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultSimpleLogin", + ), + ) + + viewModel.trySendAction(action) + + val expectedState = defaultSimpleLoginState.copy( + generatedText = "-", + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = ServiceType + .SimpleLogin( + selfHostServerUrl = newSelfHostServerUrl, + ), + ), + ), + ) + + assertEquals(expectedState, viewModel.stateFlow.value) + } } @Nested @@ -2336,6 +2382,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createPassphraseState( @@ -2356,6 +2403,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createUsernameModeState( @@ -2373,6 +2421,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createForwardedEmailAliasState( @@ -2390,6 +2439,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createAddyIoState( @@ -2406,6 +2456,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createDuckDuckGoState( @@ -2422,6 +2473,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createFastMailState( @@ -2438,6 +2490,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createFirefoxRelayState( @@ -2454,6 +2507,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createForwardEmailState( @@ -2470,6 +2524,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createSimpleLoginState( @@ -2486,6 +2541,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createPlusAddressedEmailState( @@ -2502,6 +2558,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createCatchAllEmailState( @@ -2518,6 +2575,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createRandomWordState( @@ -2536,6 +2594,7 @@ class GeneratorViewModelTest : BaseViewModelTest() { currentEmailAddress = "currentEmail", shouldShowCoachMarkTour = true, shouldShowAnonAddySelfHostServerUrlField = true, + shouldShowSimpleLoginSelfHostServerField = true, ) private fun createSavedStateHandleWithState(state: GeneratorState) = diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensionsTest.kt index 3c6ff0fb3a..21152019a9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensionsTest.kt @@ -18,6 +18,7 @@ class ForwardedEmailServiceTypeExtensionsTest { catchAllEmailDomain = "example.com", firefoxRelayApiAccessToken = "access_token_firefox_relay", simpleLoginApiKey = "api_key_simple_login", + simpleLoginSelfHostServerUrl = "https://simplelogin.local", duckDuckGoApiKey = "api_key_duck_duck_go", fastMailApiKey = "api_key_fast_mail", anonAddyApiAccessToken = "access_token_anon_addy", @@ -51,6 +52,7 @@ class ForwardedEmailServiceTypeExtensionsTest { catchAllEmailDomain = null, firefoxRelayApiAccessToken = null, simpleLoginApiKey = null, + simpleLoginSelfHostServerUrl = null, duckDuckGoApiKey = null, fastMailApiKey = null, anonAddyApiAccessToken = null, @@ -101,6 +103,9 @@ class ForwardedEmailServiceTypeExtensionsTest { apiKey = "api_key_simple_login" .takeUnless { useEmptyValues } .orEmpty(), + selfHostServerUrl = "https://simplelogin.local" + .takeUnless { useEmptyValues } + .orEmpty(), ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt index ee1ef22cd0..a6569593cb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt @@ -18,6 +18,7 @@ internal class ServiceTypeExtensionsTest { val request = addyIoServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -32,6 +33,7 @@ internal class ServiceTypeExtensionsTest { val request = addyIoServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -58,6 +60,7 @@ internal class ServiceTypeExtensionsTest { addyIoServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ), ) @@ -74,6 +77,7 @@ internal class ServiceTypeExtensionsTest { addyIoServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = false, + allowSimpleLoginSelfHostUrl = true, ), ) } @@ -89,6 +93,7 @@ internal class ServiceTypeExtensionsTest { val request = addyIoServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertEquals( @@ -110,6 +115,7 @@ internal class ServiceTypeExtensionsTest { val request = duckDuckGoServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -122,6 +128,7 @@ internal class ServiceTypeExtensionsTest { val request = duckDuckGoServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertEquals( @@ -139,6 +146,7 @@ internal class ServiceTypeExtensionsTest { val request = firefoxRelayServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -151,6 +159,7 @@ internal class ServiceTypeExtensionsTest { val request = firefoxRelayServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertEquals( @@ -168,6 +177,7 @@ internal class ServiceTypeExtensionsTest { val request = fastMailServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -182,6 +192,7 @@ internal class ServiceTypeExtensionsTest { val request = fastMailServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertEquals( @@ -202,6 +213,7 @@ internal class ServiceTypeExtensionsTest { val request = forwardMailServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -216,6 +228,7 @@ internal class ServiceTypeExtensionsTest { val request = forwardMailServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) @@ -231,6 +244,7 @@ internal class ServiceTypeExtensionsTest { val request = forwardEmailServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertEquals( @@ -251,23 +265,78 @@ internal class ServiceTypeExtensionsTest { val request = simpleLoginServiceType.toUsernameGeneratorRequest( website = null, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertNull(request) } + @Suppress("MaxLineLength") @Test - fun `toUsernameGeneratorRequest for SimpleLogin returns correct request`() { + fun `toUsernameGeneratorRequest for SimpleLogin returns correct request when allowSimpleLoginSelfHostServer is false and selfHostServerUrl is blank`() { val simpleLoginServiceType = ServiceType.SimpleLogin(apiKey = "testKey") val website = "bitwarden.com" val request = simpleLoginServiceType.toUsernameGeneratorRequest( website = website, allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, ) assertEquals( UsernameGeneratorRequest.Forwarded( - service = ForwarderServiceType.SimpleLogin(apiKey = "testKey"), + service = ForwarderServiceType.SimpleLogin( + apiKey = "testKey", + ServiceType.SimpleLogin.DEFAULT_SIMPLE_LOGIN_URL, + ), + website = website, + ), + request, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `toUsernameGeneratorRequest for SimpleLogin returns correct request when allowSimpleLoginSelfHostServer is true and selfHostServerUrl is blank`() { + val simpleLoginServiceType = ServiceType.SimpleLogin(apiKey = "testKey") + val website = "bitwarden.com" + val request = simpleLoginServiceType.toUsernameGeneratorRequest( + website = website, + allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, + ) + + assertEquals( + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.SimpleLogin( + apiKey = "testKey", + ServiceType.SimpleLogin.DEFAULT_SIMPLE_LOGIN_URL, + ), + website = website, + ), + request, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `toUsernameGeneratorRequest for SimpleLogin returns correct request when allowSimpleLoginSelfHostServer is true and selfHostServerUrl is not blank`() { + val simpleLoginServiceType = ServiceType.SimpleLogin( + apiKey = "testKey", + selfHostServerUrl = "https://simplelogin.local", + ) + val website = "bitwarden.com" + val request = simpleLoginServiceType.toUsernameGeneratorRequest( + website = website, + allowAddyIoSelfHostUrl = true, + allowSimpleLoginSelfHostUrl = true, + ) + + assertEquals( + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.SimpleLogin( + apiKey = "testKey", + baseUrl = "https://simplelogin.local", + ), website = website, ), request, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d46e756de..cb8c66c956 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ androidxSplash = "1.1.0-rc01" androidXAppCompat = "1.7.0" androdixAutofill = "1.1.0" androidxWork = "2.10.0" -bitwardenSdk = "1.0.0-20250203.153811-96" +bitwardenSdk = "1.0.0-20250213.181812-113" crashlytics = "3.0.2" detekt = "1.23.7" firebaseBom = "33.8.0"