From 35155e55841bc87b2c0e0dc577e1433041b44bc5 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Sun, 9 Jan 2022 17:50:22 +0530 Subject: [PATCH] Make PGPainless backend feature flag runtime configurable (#1654) * Make feature flags runtime configurable * Add a settings entry for PGPainless feature flag * Add changelog entry --- CHANGELOG.md | 1 + .../aps/ui/autofill/AutofillFilterView.kt | 9 ++++++-- .../aps/ui/autofill/AutofillSaveActivity.kt | 11 ++++++++-- .../aps/ui/crypto/BasePgpActivity.kt | 7 +++++-- .../aps/ui/passwords/PasswordStore.kt | 9 +++++--- .../msfjarvis/aps/ui/settings/PGPSettings.kt | 6 ++++++ .../autofill/Api30AutofillResponseBuilder.kt | 20 +++++++++++++++--- .../util/autofill/AutofillResponseBuilder.kt | 20 +++++++++++++++--- .../msfjarvis/aps/util/features/Feature.kt | 18 ++++++++++++++++ .../msfjarvis/aps/util/features/Features.kt | 21 +++++++++++++++++++ .../aps/util/services/OreoAutofillService.kt | 11 ++++++++-- 11 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/dev/msfjarvis/aps/util/features/Feature.kt create mode 100644 app/src/main/java/dev/msfjarvis/aps/util/features/Features.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d61673c..9ade72cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt index bb6d1056..55f3fc11 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt @@ -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) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt index 49b14ea4..f79ad6a8 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt @@ -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 { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt index 7c74a621..6ce1c057 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt @@ -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) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt index b42c30e9..d80959b8 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt @@ -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) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/PGPSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/PGPSettings.kt index 81418ccb..86f23c32 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/PGPSettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/PGPSettings.kt @@ -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 diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt index fd8faa7f..22f2db85 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt @@ -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) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt index 723347f2..5c1c91d6 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt @@ -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) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/features/Feature.kt b/app/src/main/java/dev/msfjarvis/aps/util/features/Feature.kt new file mode 100644 index 00000000..7ae2e642 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/util/features/Feature.kt @@ -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"), +} diff --git a/app/src/main/java/dev/msfjarvis/aps/util/features/Features.kt b/app/src/main/java/dev/msfjarvis/aps/util/features/Features.kt new file mode 100644 index 00000000..39b2c3a8 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/util/features/Features.kt @@ -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) + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt index a52caff0..7b804f3e 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt @@ -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) } }