-
Notifications
You must be signed in to change notification settings - Fork 927
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add custom webview for importing via GPM (#5097)
Task/Issue URL: https://app.asana.com/0/608920331025315/1207414915035673/f ### Description Adds the webflow which supports importing passwords directly from Google Password Manager (GPM). - This PR only adds the webflow and a way to invoke from autofill dev settings - The user-facing UI to start the flow and show the results of the import is in the next PR up in the stack The webflow can be augmented by visual nudges to help the user navigate the flow, which are governed by remote config. By default, there will be no nudges shown (you can test that flow too), so to show the nudges [apply the patch](https://app.asana.com/0/1208756808903423/1208756808903423/f) to use a fake remote config (needed until the remote config goes live). ### Steps to test this PR - [ ] [apply the remote config patch](https://app.asana.com/0/1208756808903423/1208756808903423/f) - [ ] Fresh install `internal` build, and go to autofill dev settings **Not signed into Google already / no password for Google saved in our passwords already** - [x] Tap on `Launch Google Passwords (import flow)` - [x] Verify the `Sign in` _pulses_ as way of a UI nudge on the first screen. Tap it. - [x] Sign into your Google account - [x] Verify the ⚙️ settings button _pulses_. Tap it. - [x] Verify the `Export passwords` button _pulses_. Tap it. Tap `Export` on the dialog. - [x] Re-enter your Google password when prompted - [x] Verify the number of passwords imported matches what you'd expect **Already signed into Google** Now that you're already signed into a Google account from previous test, let's use that for the next test. - [x] Tap on `Launch Google Passwords (import flow)` - [x] Verify you're already at the screen with the export button on it - [x] Verify the `Export passwords` button _pulses_. Tap it. Tap `Export` on the dialog. - [x] Re-enter your Google password when prompted, and verify the webflow ends as expected. **Signed out from Google / has Google password saved for autofilling** For this test, you need to add a login for google to our password manager and sign out from Google in the webview. - [x] Tap on `View saved logins`, and manually add your Google credentials (using `url = google.com`) - [x] Return to autofill dev settings - [x] Tap on `Launch Google Passwords (import flow)` - [x] Tap on profile icon up top-right and choose to `Sign out` - [x] Close the webflow You are now signed out, but have a credential ready to autofill. - [x] Tap on `Launch Google Passwords (import flow)` - [x] Tap `Sign in` button - [x] If it remembers your account, tap on your username. If not, verify we prompt to, and can, autofill your username - [x] Verify we prompt to, and can, autofill your password. _No more to do here._
- Loading branch information
Showing
27 changed files
with
1,500 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
...ill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/GoogleCsvLoginCredential.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* 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 | ||
|
||
/** | ||
* Data class representing the login credentials imported from a Google CSV file. | ||
*/ | ||
data class GoogleCsvLoginCredential( | ||
val url: String? = null, | ||
val username: String? = null, | ||
val password: String? = null, | ||
val title: String? = null, | ||
val notes: String? = null, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
...main/java/com/duckduckgo/autofill/impl/importing/blob/ImportGooglePasswordBlobConsumer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* 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.blob | ||
|
||
import android.annotation.SuppressLint | ||
import android.net.Uri | ||
import android.webkit.WebView | ||
import androidx.webkit.JavaScriptReplyProxy | ||
import androidx.webkit.WebMessageCompat | ||
import androidx.webkit.WebViewCompat | ||
import com.duckduckgo.app.di.AppCoroutineScope | ||
import com.duckduckgo.autofill.impl.importing.blob.GooglePasswordBlobConsumer.Callback | ||
import com.duckduckgo.common.utils.DispatcherProvider | ||
import com.duckduckgo.di.scopes.FragmentScope | ||
import com.squareup.anvil.annotations.ContributesBinding | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withContext | ||
import okio.ByteString.Companion.encode | ||
|
||
interface GooglePasswordBlobConsumer { | ||
suspend fun configureWebViewForBlobDownload( | ||
webView: WebView, | ||
callback: Callback, | ||
) | ||
|
||
suspend fun postMessageToConvertBlobToDataUri(url: String) | ||
|
||
interface Callback { | ||
suspend fun onCsvAvailable(csv: String) | ||
suspend fun onCsvError() | ||
} | ||
} | ||
|
||
@ContributesBinding(FragmentScope::class) | ||
class ImportGooglePasswordBlobConsumer @Inject constructor( | ||
private val webViewBlobDownloader: WebViewBlobDownloader, | ||
private val dispatchers: DispatcherProvider, | ||
@AppCoroutineScope private val appCoroutineScope: CoroutineScope, | ||
) : GooglePasswordBlobConsumer { | ||
|
||
// access to the flow which uses this be guarded against where these features aren't available | ||
@SuppressLint("RequiresFeature", "AddWebMessageListenerUsage") | ||
override suspend fun configureWebViewForBlobDownload( | ||
webView: WebView, | ||
callback: Callback, | ||
) { | ||
withContext(dispatchers.main()) { | ||
webViewBlobDownloader.addBlobDownloadSupport(webView) | ||
|
||
WebViewCompat.addWebMessageListener( | ||
webView, | ||
"ddgBlobDownloadObj", | ||
setOf("*"), | ||
) { _, message, sourceOrigin, _, replyProxy -> | ||
val data = message.data ?: return@addWebMessageListener | ||
appCoroutineScope.launch(dispatchers.io()) { | ||
processReceivedWebMessage(data, message, sourceOrigin, replyProxy, callback) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private suspend fun processReceivedWebMessage( | ||
data: String, | ||
message: WebMessageCompat, | ||
sourceOrigin: Uri, | ||
replyProxy: JavaScriptReplyProxy, | ||
callback: Callback, | ||
) { | ||
if (data.startsWith("data:")) { | ||
kotlin.runCatching { | ||
callback.onCsvAvailable(data) | ||
}.onFailure { callback.onCsvError() } | ||
} else if (message.data?.startsWith("Ping:") == true) { | ||
val locationRef = message.data.toString().encode().md5().toString() | ||
webViewBlobDownloader.storeReplyProxy(sourceOrigin.toString(), replyProxy, locationRef) | ||
} | ||
} | ||
|
||
override suspend fun postMessageToConvertBlobToDataUri(url: String) { | ||
webViewBlobDownloader.convertBlobToDataUri(url) | ||
} | ||
} |
Oops, something went wrong.