Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit 0810273

Browse files
Valodimmsfjarvisfmeum
authored
Autofill: Extract AutofillParser into separate subproject (#1101)
Co-authored-by: Harsh Shandilya <[email protected]> Co-authored-by: Fabian Henneke <[email protected]>
1 parent 4ba3b75 commit 0810273

36 files changed

+623
-536
lines changed

.github/workflows/update_publicsuffix_data.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
git config user.email [email protected]
1717
1818
- name: Download new publicsuffix data
19-
run: curl -L https://github.com/mozilla-mobile/android-components/raw/master/components/lib/publicsuffixlist/src/main/assets/publicsuffixes -o app/src/main/assets/publicsuffixes
19+
run: curl -L https://github.com/mozilla-mobile/android-components/raw/master/components/lib/publicsuffixlist/src/main/assets/publicsuffixes -o autofill-parser/src/main/assets/publicsuffixes
2020

2121
- name: Compare list changes
2222
run: if [[ $(git diff --binary --stat) != '' ]]; then echo "::set-env name=UPDATED::true"; fi

.idea/gradle.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ android {
8989
dependencies {
9090
compileOnly(Dependencies.AndroidX.annotation)
9191
implementation(Dependencies.AndroidX.activity_ktx)
92-
implementation(Dependencies.AndroidX.autofill)
9392
implementation(Dependencies.AndroidX.appcompat)
9493
implementation(Dependencies.AndroidX.biometric)
9594
implementation(Dependencies.AndroidX.constraint_layout)
@@ -109,6 +108,7 @@ dependencies {
109108
implementation(Dependencies.Kotlin.Coroutines.android)
110109
implementation(Dependencies.Kotlin.Coroutines.core)
111110

111+
implementation(project(Dependencies.FirstParty.autofill_parser))
112112
implementation(Dependencies.FirstParty.openpgp_ktx)
113113
implementation(Dependencies.FirstParty.zxing_android_embedded)
114114

app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import com.github.ajalt.timberkt.d
3535
import com.github.ajalt.timberkt.e
3636
import com.github.ajalt.timberkt.i
3737
import com.github.ajalt.timberkt.w
38+
import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel
39+
import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel
3840
import com.github.michaelbull.result.fold
3941
import com.github.michaelbull.result.getOr
4042
import com.github.michaelbull.result.onFailure
@@ -43,8 +45,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
4345
import com.google.android.material.snackbar.Snackbar
4446
import com.google.android.material.textfield.TextInputEditText
4547
import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
46-
import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel
47-
import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel
4848
import com.zeapo.pwdstore.crypto.BasePgpActivity.Companion.getLongName
4949
import com.zeapo.pwdstore.crypto.DecryptActivity
5050
import com.zeapo.pwdstore.crypto.PasswordCreationActivity

app/src/main/java/com/zeapo/pwdstore/UserPreference.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ import androidx.preference.SwitchPreferenceCompat
3636
import com.github.ajalt.timberkt.Timber.tag
3737
import com.github.ajalt.timberkt.d
3838
import com.github.ajalt.timberkt.w
39+
import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel
40+
import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel
3941
import com.github.michaelbull.result.getOr
4042
import com.github.michaelbull.result.onFailure
4143
import com.github.michaelbull.result.runCatching
4244
import com.google.android.material.dialog.MaterialAlertDialogBuilder
4345
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
44-
import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel
45-
import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel
4646
import com.zeapo.pwdstore.crypto.BasePgpActivity
4747
import com.zeapo.pwdstore.git.GitConfigActivity
4848
import com.zeapo.pwdstore.git.GitServerConfigActivity

app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillMatcher.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.github.ajalt.timberkt.w
1414
import com.github.michaelbull.result.Err
1515
import com.github.michaelbull.result.Ok
1616
import com.github.michaelbull.result.Result
17+
import com.github.androidpasswordstore.autofillparser.FormOrigin
18+
import com.github.androidpasswordstore.autofillparser.computeCertificatesHash
1719
import com.zeapo.pwdstore.R
1820
import java.io.File
1921

app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package com.zeapo.pwdstore.autofill.oreo
77
import android.content.Context
88
import android.os.Build
99
import androidx.annotation.RequiresApi
10+
import com.github.androidpasswordstore.autofillparser.Credentials
11+
import com.zeapo.pwdstore.model.PasswordEntry
1012
import com.zeapo.pwdstore.utils.sharedPrefs
1113
import java.io.File
1214
import java.nio.file.Paths
@@ -121,4 +123,17 @@ object AutofillPreferences {
121123
val value = context.sharedPrefs.getString(DirectoryStructure.PREFERENCE, null)
122124
return DirectoryStructure.fromValue(value)
123125
}
126+
127+
fun credentialsFromStoreEntry(
128+
context: Context,
129+
file: File,
130+
entry: PasswordEntry,
131+
directoryStructure: DirectoryStructure
132+
): Credentials {
133+
// Always give priority to a username stored in the encrypted extras
134+
val username = entry.username
135+
?: directoryStructure.getUsernameFor(file)
136+
?: context.getDefaultUsername()
137+
return Credentials(username, entry.password, entry.calculateTotpCode())
138+
}
124139
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
package com.zeapo.pwdstore.autofill.oreo
6+
7+
import android.content.Context
8+
import android.content.IntentSender
9+
import android.os.Build
10+
import android.os.Bundle
11+
import android.service.autofill.Dataset
12+
import android.service.autofill.FillCallback
13+
import android.service.autofill.FillResponse
14+
import android.service.autofill.SaveInfo
15+
import android.widget.RemoteViews
16+
import androidx.annotation.RequiresApi
17+
import com.github.ajalt.timberkt.e
18+
import com.github.michaelbull.result.fold
19+
import com.github.androidpasswordstore.autofillparser.AutofillAction
20+
import com.github.androidpasswordstore.autofillparser.AutofillScenario
21+
import com.github.androidpasswordstore.autofillparser.Credentials
22+
import com.github.androidpasswordstore.autofillparser.FillableForm
23+
import com.github.androidpasswordstore.autofillparser.fillWith
24+
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillDecryptActivity
25+
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView
26+
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity
27+
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity
28+
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSmsActivity
29+
import java.io.File
30+
31+
@RequiresApi(Build.VERSION_CODES.O)
32+
class AutofillResponseBuilder(form: FillableForm) {
33+
private val formOrigin = form.formOrigin
34+
private val scenario = form.scenario
35+
private val ignoredIds = form.ignoredIds
36+
private val saveFlags = form.saveFlags
37+
private val clientState = form.toClientState()
38+
39+
// We do not offer save when the only relevant field is a username field or there is no field.
40+
private val scenarioSupportsSave =
41+
scenario.fieldsToSave.minus(listOfNotNull(scenario.username)).isNotEmpty()
42+
private val canBeSaved = saveFlags != null && scenarioSupportsSave
43+
44+
private fun makePlaceholderDataset(
45+
remoteView: RemoteViews,
46+
intentSender: IntentSender,
47+
action: AutofillAction
48+
): Dataset {
49+
return Dataset.Builder(remoteView).run {
50+
fillWith(scenario, action, credentials = null)
51+
setAuthentication(intentSender)
52+
build()
53+
}
54+
}
55+
56+
private fun makeMatchDataset(context: Context, file: File): Dataset? {
57+
if (scenario.fieldsToFillOn(AutofillAction.Match).isEmpty()) return null
58+
val remoteView = makeFillMatchRemoteView(context, file, formOrigin)
59+
val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)
60+
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Match)
61+
}
62+
63+
private fun makeSearchDataset(context: Context): Dataset? {
64+
if (scenario.fieldsToFillOn(AutofillAction.Search).isEmpty()) return null
65+
val remoteView = makeSearchAndFillRemoteView(context, formOrigin)
66+
val intentSender =
67+
AutofillFilterView.makeMatchAndDecryptFileIntentSender(context, formOrigin)
68+
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Search)
69+
}
70+
71+
private fun makeGenerateDataset(context: Context): Dataset? {
72+
if (scenario.fieldsToFillOn(AutofillAction.Generate).isEmpty()) return null
73+
val remoteView = makeGenerateAndFillRemoteView(context, formOrigin)
74+
val intentSender = AutofillSaveActivity.makeSaveIntentSender(context, null, formOrigin)
75+
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Generate)
76+
}
77+
78+
private fun makeFillOtpFromSmsDataset(context: Context): Dataset? {
79+
if (scenario.fieldsToFillOn(AutofillAction.FillOtpFromSms).isEmpty()) return null
80+
if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
81+
val remoteView = makeFillOtpFromSmsRemoteView(context, formOrigin)
82+
val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context)
83+
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.FillOtpFromSms)
84+
}
85+
86+
private fun makePublisherChangedDataset(
87+
context: Context,
88+
publisherChangedException: AutofillPublisherChangedException
89+
): Dataset {
90+
val remoteView = makeWarningRemoteView(context)
91+
val intentSender = AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
92+
context, publisherChangedException
93+
)
94+
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Match)
95+
}
96+
97+
private fun makePublisherChangedResponse(
98+
context: Context,
99+
publisherChangedException: AutofillPublisherChangedException
100+
): FillResponse {
101+
return FillResponse.Builder().run {
102+
addDataset(makePublisherChangedDataset(context, publisherChangedException))
103+
setIgnoredIds(*ignoredIds.toTypedArray())
104+
build()
105+
}
106+
}
107+
108+
// TODO: Support multi-step authentication flows in apps via FLAG_DELAY_SAVE
109+
// See: https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
110+
private fun makeSaveInfo(): SaveInfo? {
111+
if (!canBeSaved) return null
112+
check(saveFlags != null)
113+
val idsToSave = scenario.fieldsToSave.map { it.autofillId }.toTypedArray()
114+
if (idsToSave.isEmpty()) return null
115+
var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD
116+
if (scenario.username != null) {
117+
saveDataTypes = saveDataTypes or SaveInfo.SAVE_DATA_TYPE_USERNAME
118+
}
119+
return SaveInfo.Builder(saveDataTypes, idsToSave).run {
120+
setFlags(saveFlags)
121+
build()
122+
}
123+
}
124+
125+
private fun makeFillResponse(context: Context, matchedFiles: List<File>): FillResponse? {
126+
var hasDataset = false
127+
return FillResponse.Builder().run {
128+
for (file in matchedFiles) {
129+
makeMatchDataset(context, file)?.let {
130+
hasDataset = true
131+
addDataset(it)
132+
}
133+
}
134+
makeSearchDataset(context)?.let {
135+
hasDataset = true
136+
addDataset(it)
137+
}
138+
makeGenerateDataset(context)?.let {
139+
hasDataset = true
140+
addDataset(it)
141+
}
142+
makeFillOtpFromSmsDataset(context)?.let {
143+
hasDataset = true
144+
addDataset(it)
145+
}
146+
if (!hasDataset) return null
147+
makeSaveInfo()?.let { setSaveInfo(it) }
148+
setClientState(clientState)
149+
setIgnoredIds(*ignoredIds.toTypedArray())
150+
build()
151+
}
152+
}
153+
154+
/**
155+
* Creates and returns a suitable [FillResponse] to the Autofill framework.
156+
*/
157+
fun fillCredentials(context: Context, callback: FillCallback) {
158+
AutofillMatcher.getMatchesFor(context, formOrigin).fold(
159+
success = { matchedFiles ->
160+
callback.onSuccess(makeFillResponse(context, matchedFiles))
161+
},
162+
failure = { e ->
163+
e(e)
164+
callback.onSuccess(makePublisherChangedResponse(context, e))
165+
}
166+
)
167+
}
168+
169+
companion object {
170+
fun makeFillInDataset(
171+
context: Context,
172+
credentials: Credentials,
173+
clientState: Bundle,
174+
action: AutofillAction
175+
): Dataset {
176+
val remoteView = makePlaceholderRemoteView(context)
177+
val scenario = AutofillScenario.fromBundle(clientState)
178+
// Before Android P, Datasets used for fill-in had to come with a RemoteViews, even
179+
// though they are never shown.
180+
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
181+
Dataset.Builder()
182+
} else {
183+
Dataset.Builder(remoteView)
184+
}
185+
return builder.run {
186+
if (scenario != null) fillWith(scenario, action, credentials)
187+
else e { "Failed to recover scenario from client state" }
188+
build()
189+
}
190+
}
191+
}
192+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
package com.zeapo.pwdstore.autofill.oreo
6+
7+
import android.content.Context
8+
import android.widget.RemoteViews
9+
import com.github.androidpasswordstore.autofillparser.FormOrigin
10+
import com.zeapo.pwdstore.R
11+
import com.zeapo.pwdstore.utils.PasswordRepository
12+
import java.io.File
13+
14+
private fun makeRemoteView(
15+
context: Context,
16+
title: String,
17+
summary: String,
18+
iconRes: Int
19+
): RemoteViews {
20+
return RemoteViews(context.packageName, R.layout.oreo_autofill_dataset).apply {
21+
setTextViewText(R.id.title, title)
22+
setTextViewText(R.id.summary, summary)
23+
setImageViewResource(R.id.icon, iconRes)
24+
}
25+
}
26+
27+
fun makeFillMatchRemoteView(context: Context, file: File, formOrigin: FormOrigin): RemoteViews {
28+
val title = formOrigin.getPrettyIdentifier(context, untrusted = false)
29+
val directoryStructure = AutofillPreferences.directoryStructure(context)
30+
val relativeFile = file.relativeTo(PasswordRepository.getRepositoryDirectory())
31+
val summary = directoryStructure.getUsernameFor(relativeFile)
32+
?: directoryStructure.getPathToIdentifierFor(relativeFile) ?: ""
33+
val iconRes = R.drawable.ic_person_black_24dp
34+
return makeRemoteView(context, title, summary, iconRes)
35+
}
36+
37+
fun makeSearchAndFillRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews {
38+
val title = formOrigin.getPrettyIdentifier(context, untrusted = true)
39+
val summary = context.getString(R.string.oreo_autofill_search_in_store)
40+
val iconRes = R.drawable.ic_search_black_24dp
41+
return makeRemoteView(context, title, summary, iconRes)
42+
}
43+
44+
fun makeGenerateAndFillRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews {
45+
val title = formOrigin.getPrettyIdentifier(context, untrusted = true)
46+
val summary = context.getString(R.string.oreo_autofill_generate_password)
47+
val iconRes = R.drawable.ic_autofill_new_password
48+
return makeRemoteView(context, title, summary, iconRes)
49+
}
50+
51+
fun makeFillOtpFromSmsRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews {
52+
val title = formOrigin.getPrettyIdentifier(context, untrusted = true)
53+
val summary = context.getString(R.string.oreo_autofill_fill_otp_from_sms)
54+
val iconRes = R.drawable.ic_autofill_sms
55+
return makeRemoteView(context, title, summary, iconRes)
56+
}
57+
58+
fun makePlaceholderRemoteView(context: Context): RemoteViews {
59+
return makeRemoteView(context, "PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher)
60+
}
61+
62+
fun makeWarningRemoteView(context: Context): RemoteViews {
63+
val title = context.getString(R.string.oreo_autofill_warning_publisher_dataset_title)
64+
val summary = context.getString(R.string.oreo_autofill_warning_publisher_dataset_summary)
65+
val iconRes = R.drawable.ic_warning_red_24dp
66+
return makeRemoteView(context, title, summary, iconRes)
67+
}

0 commit comments

Comments
 (0)