From 52560828a81f6bcea4198a4182a8be89ff31d6b5 Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Fri, 20 Sep 2024 13:02:35 +0100 Subject: [PATCH] Add feature flag for importing passwords via Google Password Manager --- .../autofill/api/AutofillFeature.kt | 9 +++ .../feature/AutofillImportPasswordSettings.kt | 63 ++++++++++++++++ ...tofillImportPasswordConfigStoreImplTest.kt | 71 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordSettings.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordConfigStoreImplTest.kt diff --git a/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt b/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt index c37e4a264cb0..0244965bc6d2 100644 --- a/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt +++ b/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt @@ -97,4 +97,13 @@ interface AutofillFeature { */ @Toggle.DefaultValue(false) fun showDisableDialogAutofillPrompt(): Toggle + + /** + * Remote Flag that enables the ability to import passwords directly from Google Password Manager + * @return `true` when the remote config has "canImportFromGooglePasswordManager" autofill sub-feature flag enabled + * If the remote feature is not present defaults to `false` + */ + @InternalAlwaysEnabled + @Toggle.DefaultValue(false) + fun canImportFromGooglePasswordManager(): Toggle } diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordSettings.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordSettings.kt new file mode 100644 index 000000000000..9fa7b124efa8 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordSettings.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.autofill.impl.importing.gpm.feature + +import com.duckduckgo.autofill.api.AutofillFeature +import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject +import kotlinx.coroutines.withContext + +interface AutofillImportPasswordConfigStore { + suspend fun getConfig(): AutofillImportPasswordSettings +} + +data class AutofillImportPasswordSettings( + val canImportFromGooglePasswords: Boolean, + val launchUrlGooglePasswords: String, + val javascriptConfigGooglePasswords: String, +) + +@ContributesBinding(AppScope::class) +class AutofillImportPasswordConfigStoreImpl @Inject constructor( + private val autofillFeature: AutofillFeature, + private val dispatchers: DispatcherProvider, +) : AutofillImportPasswordConfigStore { + + override suspend fun getConfig(): AutofillImportPasswordSettings { + return withContext(dispatchers.io()) { + val config = autofillFeature.canImportFromGooglePasswordManager().getConfig() + val launchUrl = config[LAUNCH_URL_KEY] ?: LAUNCH_URL_DEFAULT + val javascriptConfig = config[JAVASCRIPT_CONFIG_KEY] ?: JAVASCRIPT_CONFIG_DEFAULT + + AutofillImportPasswordSettings( + canImportFromGooglePasswords = autofillFeature.canImportFromGooglePasswordManager().isEnabled(), + launchUrlGooglePasswords = launchUrl, + javascriptConfigGooglePasswords = javascriptConfig, + ) + } + } + + companion object { + private const val JAVASCRIPT_CONFIG_KEY = "javascriptConfig" + const val JAVASCRIPT_CONFIG_DEFAULT = "\"{}\"" + + private const val LAUNCH_URL_KEY = "launchUrl" + const val LAUNCH_URL_DEFAULT = "https://passwords.google.com/options?ep=1" + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordConfigStoreImplTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordConfigStoreImplTest.kt new file mode 100644 index 000000000000..7a2db4c7bdb3 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/importing/gpm/feature/AutofillImportPasswordConfigStoreImplTest.kt @@ -0,0 +1,71 @@ +package com.duckduckgo.autofill.impl.importing.gpm.feature + +import android.annotation.SuppressLint +import com.duckduckgo.autofill.api.AutofillFeature +import com.duckduckgo.autofill.impl.importing.gpm.feature.AutofillImportPasswordConfigStoreImpl.Companion.JAVASCRIPT_CONFIG_DEFAULT +import com.duckduckgo.autofill.impl.importing.gpm.feature.AutofillImportPasswordConfigStoreImpl.Companion.LAUNCH_URL_DEFAULT +import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory +import com.duckduckgo.feature.toggles.api.Toggle.State +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test + +class AutofillImportPasswordConfigStoreImplTest { + + @get:Rule + val coroutineTestRule: CoroutineTestRule = CoroutineTestRule() + + private val autofillFeature = FakeFeatureToggleFactory.create(AutofillFeature::class.java) + private val testee = AutofillImportPasswordConfigStoreImpl( + autofillFeature = autofillFeature, + dispatchers = coroutineTestRule.testDispatcherProvider, + ) + + @Test + fun whenFeatureFlagEnabledThenCanImportGooglePasswordsConfigIsEnabled() = runTest { + configureFeature(true) + assertTrue(testee.getConfig().canImportFromGooglePasswords) + } + + @Test + fun whenFeatureFlagEnabledThenCanImportGooglePasswordsConfigIsDisabled() = runTest { + configureFeature(false) + assertFalse(testee.getConfig().canImportFromGooglePasswords) + } + + @Test + fun whenLaunchUrlNotSpecifiedInConfigThenDefaultUsed() = runTest { + configureFeature(config = emptyMap()) + assertEquals(LAUNCH_URL_DEFAULT, testee.getConfig().launchUrlGooglePasswords) + } + + @Test + fun whenLaunchUrlSpecifiedInConfigThenOverridesDefault() = runTest { + configureFeature(config = mapOf("launchUrl" to "https://example.com")) + assertEquals("https://example.com", testee.getConfig().launchUrlGooglePasswords) + } + + @Test + fun whenJavascriptConfigNotSpecifiedInConfigThenDefaultUsed() = runTest { + configureFeature(config = emptyMap()) + assertEquals(JAVASCRIPT_CONFIG_DEFAULT, testee.getConfig().javascriptConfigGooglePasswords) + } + + @Test + fun whenJavascriptConfigSpecifiedInConfigThenOverridesDefault() = runTest { + configureFeature(config = mapOf("javascriptConfig" to """{"key": "value"}""")) + assertEquals("""{"key": "value"}""", testee.getConfig().javascriptConfigGooglePasswords) + } + + @SuppressLint("DenyListedApi") + private fun configureFeature(enabled: Boolean = true, config: Map = emptyMap()) { + autofillFeature.canImportFromGooglePasswordManager().setRawStoredState( + State( + remoteEnableState = enabled, + config = config, + ), + ) + } +}