1
0
Fork 0

Closes #7344: Login storage refactor

The a-c side of this work is in https://github.com/mozilla-mobile/android-components/pull/6128

This switches Fenix to use `SyncableLoginsStorage`, which caches a connection internally
on first access, and doesn't expose any lock/unlock APIs at the public boundary.
master
Grisha Kruglov 2020-02-28 16:21:12 -08:00 committed by Sebastian Kaspari
parent 6a75d822ca
commit e6e2dd94c7
10 changed files with 27 additions and 69 deletions

View File

@ -6,10 +6,9 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.experiments.Experiments import mozilla.components.service.experiments.Experiments
import mozilla.components.service.sync.logins.AsyncLoginsStorage
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -24,11 +23,10 @@ object GeckoProvider {
@Synchronized @Synchronized
fun getOrCreateRuntime( fun getOrCreateRuntime(
context: Context, context: Context,
storage: AsyncLoginsStorage, storage: LoginsStorage
securePreferences: SecureAbove22Preferences
): GeckoRuntime { ): GeckoRuntime {
if (runtime == null) { if (runtime == null) {
runtime = createRuntime(context, storage, securePreferences) runtime = createRuntime(context, storage)
} }
return runtime!! return runtime!!
@ -36,8 +34,7 @@ object GeckoProvider {
private fun createRuntime( private fun createRuntime(
context: Context, context: Context,
storage: AsyncLoginsStorage, storage: LoginsStorage
securePreferences: SecureAbove22Preferences
): GeckoRuntime { ): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder() val builder = GeckoRuntimeSettings.Builder()
@ -69,7 +66,6 @@ object GeckoProvider {
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings) val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate( val loginStorageDelegate = GeckoLoginStorageDelegate(
storage, storage,
securePreferences,
{ context.settings().shouldAutofillLogins && context.settings().shouldPromptToSaveLogins } { context.settings().shouldAutofillLogins && context.settings().shouldPromptToSaveLogins }
) )
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate) geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)

View File

@ -6,9 +6,8 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.sync.logins.AsyncLoginsStorage
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -23,11 +22,10 @@ object GeckoProvider {
@Synchronized @Synchronized
fun getOrCreateRuntime( fun getOrCreateRuntime(
context: Context, context: Context,
storage: AsyncLoginsStorage, storage: LoginsStorage
securePreferences: SecureAbove22Preferences
): GeckoRuntime { ): GeckoRuntime {
if (runtime == null) { if (runtime == null) {
runtime = createRuntime(context, storage, securePreferences) runtime = createRuntime(context, storage)
} }
return runtime!! return runtime!!
@ -35,8 +33,7 @@ object GeckoProvider {
private fun createRuntime( private fun createRuntime(
context: Context, context: Context,
storage: AsyncLoginsStorage, storage: LoginsStorage
securePreferences: SecureAbove22Preferences
): GeckoRuntime { ): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder() val builder = GeckoRuntimeSettings.Builder()
@ -61,7 +58,6 @@ object GeckoProvider {
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings) val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate( val loginStorageDelegate = GeckoLoginStorageDelegate(
storage, storage,
securePreferences,
{ context.settings().shouldAutofillLogins && context.settings().shouldPromptToSaveLogins } { context.settings().shouldAutofillLogins && context.settings().shouldPromptToSaveLogins }
) )
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate) geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)

View File

@ -328,8 +328,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
customTabId = customTabSessionId, customTabId = customTabSessionId,
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
loginValidationDelegate = DefaultLoginValidationDelegate( loginValidationDelegate = DefaultLoginValidationDelegate(
context.components.core.asyncPasswordsStorage, context.components.core.passwordsStorage
context.components.core.getSecureAbove22Preferences()
), ),
isSaveLoginEnabled = { isSaveLoginEnabled = {
context.settings().shouldPromptToSaveLogins context.settings().shouldPromptToSaveLogins

View File

@ -20,7 +20,6 @@ import mozilla.components.feature.accounts.push.SendTabFeature
import mozilla.components.feature.push.AutoPushFeature import mozilla.components.feature.push.AutoPushFeature
import mozilla.components.feature.push.PushConfig import mozilla.components.feature.push.PushConfig
import mozilla.components.lib.crash.CrashReporter import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.fxa.DeviceConfig import mozilla.components.service.fxa.DeviceConfig
import mozilla.components.service.fxa.ServerConfig import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.SyncConfig import mozilla.components.service.fxa.SyncConfig
@ -30,7 +29,7 @@ import mozilla.components.service.fxa.manager.SCOPE_SESSION
import mozilla.components.service.fxa.manager.SCOPE_SYNC import mozilla.components.service.fxa.manager.SCOPE_SYNC
import mozilla.components.service.fxa.manager.SyncEnginesStorage import mozilla.components.service.fxa.manager.SyncEnginesStorage
import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider
import mozilla.components.service.sync.logins.SyncableLoginsStore import mozilla.components.service.sync.logins.SyncableLoginsStorage
import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.FeatureFlags
@ -51,8 +50,7 @@ class BackgroundServices(
crashReporter: CrashReporter, crashReporter: CrashReporter,
historyStorage: PlacesHistoryStorage, historyStorage: PlacesHistoryStorage,
bookmarkStorage: PlacesBookmarksStorage, bookmarkStorage: PlacesBookmarksStorage,
passwordsStorage: SyncableLoginsStore, passwordsStorage: SyncableLoginsStorage
secureAbove22Preferences: SecureAbove22Preferences
) { ) {
fun defaultDeviceName(context: Context): String = fun defaultDeviceName(context: Context): String =
context.getString( context.getString(
@ -88,7 +86,7 @@ class BackgroundServices(
syncPeriodInMinutes = 240L) // four hours syncPeriodInMinutes = 240L) // four hours
} }
val pushService by lazy { FirebasePushService() } private val pushService by lazy { FirebasePushService() }
val push by lazy { makePushConfig()?.let { makePush(it) } } val push by lazy { makePushConfig()?.let { makePush(it) } }
@ -97,7 +95,6 @@ class BackgroundServices(
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage) GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage) GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to passwordsStorage) GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to passwordsStorage)
GlobalSyncableStoreProvider.configureKeyStorage(secureAbove22Preferences)
} }
private val telemetryAccountObserver = TelemetryAccountObserver( private val telemetryAccountObserver = TelemetryAccountObserver(

View File

@ -29,8 +29,7 @@ class Components(private val context: Context) {
analytics.crashReporter, analytics.crashReporter,
core.historyStorage, core.historyStorage,
core.bookmarksStorage, core.bookmarksStorage,
core.syncablePasswordsStorage, core.passwordsStorage
core.getSecureAbove22Preferences()
) )
} }
val services by lazy { Services(context, backgroundServices.accountManager) } val services by lazy { Services(context, backgroundServices.accountManager) }

View File

@ -9,7 +9,6 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import io.sentry.Sentry import io.sentry.Sentry
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -38,8 +37,7 @@ import mozilla.components.feature.webcompat.WebCompatFeature
import mozilla.components.feature.webnotifications.WebNotificationFeature import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.lib.dataprotect.SecureAbove22Preferences import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.lib.dataprotect.generateEncryptionKey import mozilla.components.lib.dataprotect.generateEncryptionKey
import mozilla.components.service.sync.logins.AsyncLoginsStorageAdapter import mozilla.components.service.sync.logins.SyncableLoginsStorage
import mozilla.components.service.sync.logins.SyncableLoginsStore
import org.mozilla.fenix.AppRequestInterceptor import org.mozilla.fenix.AppRequestInterceptor
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.FeatureFlags
@ -47,7 +45,6 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.test.Mockable import org.mozilla.fenix.test.Mockable
import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -76,9 +73,7 @@ class Core(private val context: Context) {
GeckoEngine( GeckoEngine(
context, context,
defaultSettings, defaultSettings,
GeckoProvider.getOrCreateRuntime( GeckoProvider.getOrCreateRuntime(context, passwordsStorage)
context, asyncPasswordsStorage, getSecureAbove22Preferences()
)
).also { ).also {
WebCompatFeature.install(it) WebCompatFeature.install(it)
} }
@ -90,11 +85,7 @@ class Core(private val context: Context) {
val client: Client by lazy { val client: Client by lazy {
GeckoViewFetchClient( GeckoViewFetchClient(
context, context,
GeckoProvider.getOrCreateRuntime( GeckoProvider.getOrCreateRuntime(context, passwordsStorage)
context,
asyncPasswordsStorage,
getSecureAbove22Preferences()
)
) )
} }
@ -201,6 +192,8 @@ class Core(private val context: Context) {
val bookmarksStorage by lazy { PlacesBookmarksStorage(context) } val bookmarksStorage by lazy { PlacesBookmarksStorage(context) }
val passwordsStorage by lazy { SyncableLoginsStorage(context, passwordsEncryptionKey) }
val tabCollectionStorage by lazy { TabCollectionStorage(context, sessionManager) } val tabCollectionStorage by lazy { TabCollectionStorage(context, sessionManager) }
val topSiteStorage by lazy { TopSiteStorage(context) } val topSiteStorage by lazy { TopSiteStorage(context) }
@ -209,36 +202,19 @@ class Core(private val context: Context) {
val webAppManifestStorage by lazy { ManifestStorage(context) } val webAppManifestStorage by lazy { ManifestStorage(context) }
val asyncPasswordsStorage by lazy {
AsyncLoginsStorageAdapter.forDatabase(
File(
context.filesDir,
"logins.sqlite"
).canonicalPath
)
}
val syncablePasswordsStorage by lazy {
SyncableLoginsStore(
asyncPasswordsStorage
) {
CompletableDeferred(passwordsEncryptionKey)
}
}
/** /**
* Shared Preferences that encrypt/decrypt using Android KeyStore and lib-dataprotect for 23+ * Shared Preferences that encrypt/decrypt using Android KeyStore and lib-dataprotect for 23+
* only on Nightly/Debug for now, otherwise simply stored. * only on Nightly/Debug for now, otherwise simply stored.
* See https://github.com/mozilla-mobile/fenix/issues/8324 * See https://github.com/mozilla-mobile/fenix/issues/8324
*/ */
fun getSecureAbove22Preferences() = private fun getSecureAbove22Preferences() =
SecureAbove22Preferences( SecureAbove22Preferences(
context = context, context = context,
name = KEY_STORAGE_NAME, name = KEY_STORAGE_NAME,
forceInsecure = !Config.channel.isNightlyOrDebug forceInsecure = !Config.channel.isNightlyOrDebug
) )
val passwordsEncryptionKey: String = private val passwordsEncryptionKey by lazy {
getSecureAbove22Preferences().getString(PASSWORDS_KEY) getSecureAbove22Preferences().getString(PASSWORDS_KEY)
?: generateEncryptionKey(KEY_STRENGTH).also { ?: generateEncryptionKey(KEY_STRENGTH).also {
if (context.settings().passwordsEncryptionKeyGenerated) { if (context.settings().passwordsEncryptionKeyGenerated) {
@ -248,6 +224,7 @@ class Core(private val context: Context) {
context.settings().recordPasswordsEncryptionKeyGenerated() context.settings().recordPasswordsEncryptionKeyGenerated()
getSecureAbove22Preferences().putString(PASSWORDS_KEY, it) getSecureAbove22Preferences().putString(PASSWORDS_KEY, it)
} }
}
val trackingProtectionPolicyFactory = TrackingProtectionPolicyFactory(context.settings()) val trackingProtectionPolicyFactory = TrackingProtectionPolicyFactory(context.settings())

View File

@ -94,9 +94,7 @@ class SavedLoginSiteInfoFragment : Fragment(R.layout.fragment_saved_login_site_i
var deleteLoginJob: Deferred<Boolean>? = null var deleteLoginJob: Deferred<Boolean>? = null
val deleteJob = lifecycleScope.launch(IO) { val deleteJob = lifecycleScope.launch(IO) {
deleteLoginJob = async { deleteLoginJob = async {
requireContext().components.core.syncablePasswordsStorage.withUnlocked { requireContext().components.core.passwordsStorage.delete(args.savedLoginItem.id)
it.delete(args.savedLoginItem.id).await()
}
} }
deleteLoginJob?.await() deleteLoginJob?.await()
withContext(Main) { withContext(Main) {

View File

@ -22,7 +22,7 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.appservices.logins.ServerPassword import mozilla.components.concept.storage.Login
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
@ -103,18 +103,16 @@ class SavedLoginsFragment : Fragment() {
} }
private fun loadAndMapLogins() { private fun loadAndMapLogins() {
var deferredLogins: Deferred<List<ServerPassword>>? = null var deferredLogins: Deferred<List<Login>>? = null
val fetchLoginsJob = lifecycleScope.launch(IO) { val fetchLoginsJob = lifecycleScope.launch(IO) {
deferredLogins = async { deferredLogins = async {
requireContext().components.core.syncablePasswordsStorage.withUnlocked { requireContext().components.core.passwordsStorage.list()
it.list().await()
}
} }
val logins = deferredLogins?.await() val logins = deferredLogins?.await()
logins?.let { logins?.let {
withContext(Main) { withContext(Main) {
savedLoginsStore.dispatch(SavedLoginsFragmentAction.UpdateLogins(logins.map { item -> savedLoginsStore.dispatch(SavedLoginsFragmentAction.UpdateLogins(logins.map { item ->
SavedLoginsItem(item.hostname, item.username, item.password, item.id) SavedLoginsItem(item.origin, item.username, item.password, item.guid!!)
})) }))
} }
} }

View File

@ -27,7 +27,7 @@ import org.mozilla.fenix.components.metrics.MetricController
class BackgroundServicesTest { class BackgroundServicesTest {
class TestableBackgroundServices( class TestableBackgroundServices(
val context: Context val context: Context
) : BackgroundServices(context, mockk(), mockk(), mockk(), mockk(), mockk()) { ) : BackgroundServices(context, mockk(), mockk(), mockk(), mockk()) {
override fun makeAccountManager( override fun makeAccountManager(
context: Context, context: Context,
serverConfig: ServerConfig, serverConfig: ServerConfig,

View File

@ -11,7 +11,6 @@ import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Client
import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
class TestCore(context: Context) : Core(context) { class TestCore(context: Context) : Core(context) {
@ -20,5 +19,4 @@ class TestCore(context: Context) : Core(context) {
override val store = mockk<BrowserStore>() override val store = mockk<BrowserStore>()
override val client = mockk<Client>() override val client = mockk<Client>()
override val webAppShortcutManager = mockk<WebAppShortcutManager>() override val webAppShortcutManager = mockk<WebAppShortcutManager>()
override fun getSecureAbove22Preferences() = mockk<SecureAbove22Preferences>(relaxed = true)
} }