Make PGPainless backend feature flag runtime configurable (#1654)

* Make feature flags runtime configurable

* Add a settings entry for PGPainless feature flag

* Add changelog entry
This commit is contained in:
Harsh Shandilya 2022-01-09 17:50:22 +05:30 committed by GitHub
parent 1738879fb3
commit 35155e5584
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 116 additions and 17 deletions

View file

@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
- Allow pinning shortcuts directly to the launcher home screen
- Another workaround for SteamGuard's non-standard OTP format
- Allow importing QR code from images
- Introduce a new opt-in PGP backend powered by [PGPainless](https://github.com/pgpainless/pgpainless) that does not require OpenKeychain
### Fixed

View file

@ -25,23 +25,27 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.androidpasswordstore.autofillparser.FormOrigin
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.password.PasswordItem
import dev.msfjarvis.aps.databinding.ActivityOreoAutofillFilterBinding
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
import dev.msfjarvis.aps.util.extensions.viewBinding
import dev.msfjarvis.aps.util.features.Feature
import dev.msfjarvis.aps.util.features.Features
import dev.msfjarvis.aps.util.viewmodel.FilterMode
import dev.msfjarvis.aps.util.viewmodel.ListMode
import dev.msfjarvis.aps.util.viewmodel.SearchMode
import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryAdapter
import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryViewModel
import javax.inject.Inject
import logcat.LogPriority.ERROR
import logcat.logcat
@TargetApi(Build.VERSION_CODES.O)
@AndroidEntryPoint
class AutofillFilterView : AppCompatActivity() {
companion object {
@ -76,6 +80,7 @@ class AutofillFilterView : AppCompatActivity() {
}
}
@Inject lateinit var features: Features
private lateinit var formOrigin: FormOrigin
private lateinit var directoryStructure: DirectoryStructure
private val binding by viewBinding(ActivityOreoAutofillFilterBinding::inflate)
@ -222,7 +227,7 @@ class AutofillFilterView : AppCompatActivity() {
AutofillMatcher.addMatchFor(applicationContext, formOrigin, item.file)
// intent?.extras? is checked to be non-null in onCreate
decryptAction.launch(
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) {
if (features.isEnabled(Feature.EnablePGPainlessBackend)) {
AutofillDecryptActivityV2.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
} else {
AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)

View file

@ -18,19 +18,23 @@ import androidx.core.os.bundleOf
import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.androidpasswordstore.autofillparser.FormOrigin
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.data.repo.PasswordRepository
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
import dev.msfjarvis.aps.util.extensions.unsafeLazy
import dev.msfjarvis.aps.util.features.Feature
import dev.msfjarvis.aps.util.features.Features
import java.io.File
import javax.inject.Inject
import logcat.LogPriority.ERROR
import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint
class AutofillSaveActivity : AppCompatActivity() {
companion object {
@ -105,11 +109,14 @@ class AutofillSaveActivity : AppCompatActivity() {
}
}
@Inject lateinit var features: Features
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val repo = PasswordRepository.getRepositoryDirectory()
val creationActivity =
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) PasswordCreationActivityV2::class.java
if (features.isEnabled(Feature.EnablePGPainlessBackend))
PasswordCreationActivityV2::class.java
else PasswordCreationActivity::class.java
val saveIntent =
Intent(this, creationActivity).apply {

View file

@ -24,13 +24,14 @@ import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.injection.prefs.SettingsPreferences
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.extensions.OPENPGP_PROVIDER
import dev.msfjarvis.aps.util.extensions.asLog
import dev.msfjarvis.aps.util.extensions.clipboard
import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.snackbar
import dev.msfjarvis.aps.util.extensions.unsafeLazy
import dev.msfjarvis.aps.util.features.Feature
import dev.msfjarvis.aps.util.features.Features
import dev.msfjarvis.aps.util.services.ClipboardService
import dev.msfjarvis.aps.util.settings.PreferenceKeys
import java.io.File
@ -63,6 +64,8 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
/** [SharedPreferences] instance used by subclasses to persist settings */
@SettingsPreferences @Inject lateinit var settings: SharedPreferences
@Inject lateinit var features: Features
/**
* Handle to the [OpenPgpApi] instance that is used by subclasses to interface with OpenKeychain.
*/
@ -127,7 +130,7 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
/** Method for subclasses to initiate binding with [OpenPgpServiceConnection]. */
fun bindToOpenKeychain(onBoundListener: OpenPgpServiceConnection.OnBound) {
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) return
if (features.isEnabled(Feature.EnablePGPainlessBackend)) return
val installed =
runCatching {
packageManager.getPackageInfo(OPENPGP_PROVIDER, 0)

View file

@ -47,7 +47,6 @@ import dev.msfjarvis.aps.ui.git.base.BaseGitActivity
import dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity
import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity
import dev.msfjarvis.aps.ui.settings.SettingsActivity
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
import dev.msfjarvis.aps.util.extensions.base64
import dev.msfjarvis.aps.util.extensions.commitChange
@ -57,6 +56,8 @@ import dev.msfjarvis.aps.util.extensions.isInsideRepository
import dev.msfjarvis.aps.util.extensions.isPermissionGranted
import dev.msfjarvis.aps.util.extensions.listFilesRecursively
import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.features.Feature
import dev.msfjarvis.aps.util.features.Features
import dev.msfjarvis.aps.util.settings.AuthMode
import dev.msfjarvis.aps.util.settings.PreferenceKeys
import dev.msfjarvis.aps.util.shortcuts.ShortcutHandler
@ -76,6 +77,7 @@ const val PASSWORD_FRAGMENT_TAG = "PasswordsList"
@AndroidEntryPoint
class PasswordStore : BaseGitActivity() {
@Inject lateinit var features: Features
@Inject lateinit var shortcutHandler: ShortcutHandler
private lateinit var searchItem: MenuItem
private val settings by lazy { sharedPrefs }
@ -428,7 +430,7 @@ class PasswordStore : BaseGitActivity() {
(authDecryptIntent.clone() as Intent).setComponent(
ComponentName(
this,
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) {
if (features.isEnabled(Feature.EnablePGPainlessBackend)) {
DecryptActivityV2::class.java
} else {
DecryptActivity::class.java
@ -458,7 +460,8 @@ class PasswordStore : BaseGitActivity() {
val currentDir = currentDir
logcat(INFO) { "Adding file to : ${currentDir.absolutePath}" }
val creationActivity =
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) PasswordCreationActivityV2::class.java
if (features.isEnabled(Feature.EnablePGPainlessBackend))
PasswordCreationActivityV2::class.java
else PasswordCreationActivity::class.java
val intent = Intent(this, creationActivity)
intent.putExtra(BasePgpActivity.EXTRA_FILE_PATH, currentDir.absolutePath)

View file

@ -7,15 +7,21 @@ package dev.msfjarvis.aps.ui.settings
import androidx.fragment.app.FragmentActivity
import de.Maxr1998.modernpreferences.PreferenceScreen
import de.Maxr1998.modernpreferences.helpers.checkBox
import de.Maxr1998.modernpreferences.helpers.onClick
import de.Maxr1998.modernpreferences.helpers.pref
import dev.msfjarvis.aps.ui.pgp.PGPKeyImportActivity
import dev.msfjarvis.aps.util.extensions.launchActivity
import dev.msfjarvis.aps.util.features.Feature
class PGPSettings(private val activity: FragmentActivity) : SettingsProvider {
override fun provideSettings(builder: PreferenceScreen.Builder) {
builder.apply {
checkBox(Feature.EnablePGPainlessBackend.configKey) {
title = "Enable new PGP backend"
persistent = true
}
pref("_") {
title = "Import PGP key"
persistent = false

View file

@ -19,13 +19,17 @@ import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.FillableForm
import com.github.androidpasswordstore.autofillparser.fillWith
import com.github.michaelbull.result.fold
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.aps.autofill.oreo.ui.AutofillSmsActivity
import dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivity
import dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivityV2
import dev.msfjarvis.aps.ui.autofill.AutofillFilterView
import dev.msfjarvis.aps.ui.autofill.AutofillPublisherChangedActivity
import dev.msfjarvis.aps.ui.autofill.AutofillSaveActivity
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.features.Feature
import dev.msfjarvis.aps.util.features.Features
import java.io.File
import logcat.LogPriority.ERROR
import logcat.asLog
@ -33,7 +37,17 @@ import logcat.logcat
/** Implements [AutofillResponseBuilder]'s methods for API 30 and above */
@RequiresApi(Build.VERSION_CODES.R)
class Api30AutofillResponseBuilder(form: FillableForm) {
class Api30AutofillResponseBuilder
@AssistedInject
constructor(
@Assisted form: FillableForm,
private val features: Features,
) {
@AssistedFactory
interface Factory {
fun create(form: FillableForm): Api30AutofillResponseBuilder
}
private val formOrigin = form.formOrigin
private val scenario = form.scenario
@ -73,7 +87,7 @@ class Api30AutofillResponseBuilder(form: FillableForm) {
if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
val metadata = makeFillMatchMetadata(context, file)
val intentSender =
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) {
if (features.isEnabled(Feature.EnablePGPainlessBackend)) {
AutofillDecryptActivityV2.makeDecryptFileIntentSender(file, context)
} else {
AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)

View file

@ -19,20 +19,34 @@ import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.androidpasswordstore.autofillparser.FillableForm
import com.github.androidpasswordstore.autofillparser.fillWith
import com.github.michaelbull.result.fold
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.aps.autofill.oreo.ui.AutofillSmsActivity
import dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivity
import dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivityV2
import dev.msfjarvis.aps.ui.autofill.AutofillFilterView
import dev.msfjarvis.aps.ui.autofill.AutofillPublisherChangedActivity
import dev.msfjarvis.aps.ui.autofill.AutofillSaveActivity
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.features.Feature
import dev.msfjarvis.aps.util.features.Features
import java.io.File
import logcat.LogPriority.ERROR
import logcat.asLog
import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
class AutofillResponseBuilder(form: FillableForm) {
class AutofillResponseBuilder
@AssistedInject
constructor(
@Assisted form: FillableForm,
private val features: Features,
) {
@AssistedFactory
interface Factory {
fun create(form: FillableForm): AutofillResponseBuilder
}
private val formOrigin = form.formOrigin
private val scenario = form.scenario
@ -61,7 +75,7 @@ class AutofillResponseBuilder(form: FillableForm) {
if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
val metadata = makeFillMatchMetadata(context, file)
val intentSender =
if (FeatureFlags.ENABLE_PGP_V2_BACKEND) {
if (features.isEnabled(Feature.EnablePGPainlessBackend)) {
AutofillDecryptActivityV2.makeDecryptFileIntentSender(file, context)
} else {
AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)

View file

@ -0,0 +1,18 @@
/*
* Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package dev.msfjarvis.aps.util.features
/** List of all feature flags for the app. */
enum class Feature(
/** Default value for the flag. */
val defaultValue: Boolean,
/** Key to retrieve the current value for the flag. */
val configKey: String,
) {
/** Opt into the new PGP backend powered by the PGPainless library. */
EnablePGPainlessBackend(false, "enable_pgp_v2_backend"),
}

View file

@ -0,0 +1,21 @@
/*
* Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package dev.msfjarvis.aps.util.features
import android.content.SharedPreferences
import dev.msfjarvis.aps.injection.prefs.SettingsPreferences
import javax.inject.Inject
class Features
@Inject
constructor(
@SettingsPreferences private val preferences: SharedPreferences,
) {
fun isEnabled(feature: Feature): Boolean {
return preferences.getBoolean(feature.configKey, feature.defaultValue)
}
}

View file

@ -23,6 +23,7 @@ import com.github.androidpasswordstore.autofillparser.cachePublicSuffixList
import com.github.androidpasswordstore.autofillparser.passwordValue
import com.github.androidpasswordstore.autofillparser.recoverNodes
import com.github.androidpasswordstore.autofillparser.usernameValue
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.BuildConfig
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.ui.autofill.AutofillSaveActivity
@ -32,10 +33,12 @@ import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.hasFlag
import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.settings.PreferenceKeys
import javax.inject.Inject
import logcat.LogPriority.ERROR
import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint
class OreoAutofillService : AutofillService() {
companion object {
@ -55,6 +58,9 @@ class OreoAutofillService : AutofillService() {
private const val DISABLE_AUTOFILL_DURATION_MS = 1000 * 60 * 60 * 24L
}
@Inject lateinit var api30ResponseBuilderFactory: Api30AutofillResponseBuilder.Factory
@Inject lateinit var responseBuilderFactory: AutofillResponseBuilder.Factory
override fun onCreate() {
super.onCreate()
cachePublicSuffixList(applicationContext)
@ -97,10 +103,11 @@ class OreoAutofillService : AutofillService() {
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Api30AutofillResponseBuilder(formToFill)
api30ResponseBuilderFactory
.create(formToFill)
.fillCredentials(this, request.inlineSuggestionsRequest, callback)
} else {
AutofillResponseBuilder(formToFill).fillCredentials(this, callback)
responseBuilderFactory.create(formToFill).fillCredentials(this, callback)
}
}