Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature flag for importing passwords via Google Password Manager #5096

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> = emptyMap()) {
autofillFeature.canImportFromGooglePasswordManager().setRawStoredState(
State(
remoteEnableState = enabled,
config = config,
),
)
}
}
Loading