From 0e9aa4f1d9c1118223e700078674e827a074981a Mon Sep 17 00:00:00 2001 From: Craig Russell <1336281+CDRussell@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:30:05 +0100 Subject: [PATCH] Enable passkey support in browser for internal builds --- .../app/browser/BrowserTabFragment.kt | 8 +++ .../app/browser/DuckDuckGoWebView.kt | 4 ++ .../webauthn/WebViewPasskeyInitializer.kt | 70 +++++++++++++++++++ .../autofill/api/AutofillFeature.kt | 3 + .../src/main/AndroidManifest.xml | 2 + 5 files changed, 87 insertions(+) create mode 100644 app/src/main/java/com/duckduckgo/app/browser/webauthn/WebViewPasskeyInitializer.kt diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index c50bc30984eb..0b9dd929595f 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -176,6 +176,7 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState import com.duckduckgo.app.browser.viewstate.OmnibarViewState import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState +import com.duckduckgo.app.browser.webauthn.WebViewPasskeyInitializer import com.duckduckgo.app.browser.webshare.WebShareChooser import com.duckduckgo.app.browser.webview.WebContentDebugging import com.duckduckgo.app.browser.webview.WebViewBlobDownloadFeature @@ -583,6 +584,9 @@ class BrowserTabFragment : @Inject lateinit var androidBrowserConfigFeature: AndroidBrowserConfigFeature + @Inject + lateinit var passkeyInitializer: WebViewPasskeyInitializer + /** * We use this to monitor whether the user was seeing the in-context Email Protection signup prompt * This is needed because the activity stack will be cleared if an external link is opened in our browser @@ -3111,6 +3115,10 @@ class BrowserTabFragment : } WebView.setWebContentsDebuggingEnabled(webContentDebugging.isEnabled()) + + lifecycleScope.launch { + webView?.let { passkeyInitializer.configurePasskeySupport(it) } + } } private fun screenLock(data: JsCallbackData) { diff --git a/app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoWebView.kt b/app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoWebView.kt index 14b57efb0c79..18f6708899e7 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoWebView.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoWebView.kt @@ -412,6 +412,10 @@ class DuckDuckGoWebView : WebView, NestedScrollingChild3 { } } + fun isDestroyed(): Boolean { + return isDestroyed + } + @SuppressLint("RequiresFeature", "AddWebMessageListenerUsage") suspend fun safeAddWebMessageListener( webViewCapabilityChecker: WebViewCapabilityChecker, diff --git a/app/src/main/java/com/duckduckgo/app/browser/webauthn/WebViewPasskeyInitializer.kt b/app/src/main/java/com/duckduckgo/app/browser/webauthn/WebViewPasskeyInitializer.kt new file mode 100644 index 000000000000..61868f9761e9 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/browser/webauthn/WebViewPasskeyInitializer.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 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.app.browser.webauthn + +import android.annotation.SuppressLint +import androidx.webkit.WebSettingsCompat +import androidx.webkit.WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_FOR_BROWSER +import androidx.webkit.WebViewFeature +import androidx.webkit.WebViewFeature.WEB_AUTHENTICATION +import com.duckduckgo.app.browser.DuckDuckGoWebView +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 +import logcat.logcat + +interface WebViewPasskeyInitializer { + suspend fun configurePasskeySupport(webView: DuckDuckGoWebView) +} + +@ContributesBinding(AppScope::class) +class RealWebViewPasskeyInitializer @Inject constructor( + private val autofillFeature: AutofillFeature, + private val dispatchers: DispatcherProvider, +) : WebViewPasskeyInitializer { + + override suspend fun configurePasskeySupport(webView: DuckDuckGoWebView) { + if (featureFlagEnabled() && webViewCapable()) { + enablePasskeySupport(webView) + } + } + + @SuppressLint("RequiresFeature") + private suspend fun enablePasskeySupport(webView: DuckDuckGoWebView) { + withContext(dispatchers.main()) { + if (!webView.isDestroyed()) { + WebSettingsCompat.setWebAuthenticationSupport(webView.settings, WEB_AUTHENTICATION_SUPPORT_FOR_BROWSER) + logcat { "Autofill-passkey: WebView passkey support (WebAuthn) enabled" } + } + } + } + + private suspend fun featureFlagEnabled(): Boolean { + return withContext(dispatchers.io()) { + autofillFeature.passkeySupport().isEnabled() + } + } + + private suspend fun webViewCapable(): Boolean { + return withContext(dispatchers.main()) { + WebViewFeature.isFeatureSupported(WEB_AUTHENTICATION) + } + } +} 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 b244ccb535bb..6259bc16c7e4 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 @@ -154,4 +154,7 @@ interface AutofillFeature { @Toggle.DefaultValue(defaultValue = DefaultFeatureValue.TRUE) fun canShowImportOptionInAppSettings(): Toggle + + @Toggle.DefaultValue(defaultValue = DefaultFeatureValue.INTERNAL) + fun passkeySupport(): Toggle } diff --git a/autofill/autofill-impl/src/main/AndroidManifest.xml b/autofill/autofill-impl/src/main/AndroidManifest.xml index dc23da6b8cc3..92065bd64b75 100644 --- a/autofill/autofill-impl/src/main/AndroidManifest.xml +++ b/autofill/autofill-impl/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + +