mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
BITAU-112 Support deep link into add item flow from Authenticator app (#4128)
This commit is contained in:
parent
f1d7d1a530
commit
fa248243b6
15 changed files with 365 additions and 21 deletions
Binary file not shown.
|
@ -5,6 +5,7 @@ import android.os.Parcelable
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.bitwarden.vault.CipherView
|
import com.bitwarden.vault.CipherView
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
||||||
|
@ -23,6 +24,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
@ -33,6 +35,7 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -58,6 +61,7 @@ private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
accessibilitySelectionManager: AccessibilitySelectionManager,
|
accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||||
autofillSelectionManager: AutofillSelectionManager,
|
autofillSelectionManager: AutofillSelectionManager,
|
||||||
|
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
|
||||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||||
private val garbageCollectionManager: GarbageCollectionManager,
|
private val garbageCollectionManager: GarbageCollectionManager,
|
||||||
private val fido2CredentialManager: Fido2CredentialManager,
|
private val fido2CredentialManager: Fido2CredentialManager,
|
||||||
|
@ -234,7 +238,20 @@ class MainViewModel @Inject constructor(
|
||||||
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
||||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||||
val totpData = intent.getTotpDataOrNull()
|
val totpData: TotpData? =
|
||||||
|
// First grab TOTP URI directly from the intent data:
|
||||||
|
intent.getTotpDataOrNull()
|
||||||
|
?: run {
|
||||||
|
// Then check to see if the intent is coming from the Authenticator app:
|
||||||
|
if (intent.isAddTotpLoginItemFromAuthenticator()) {
|
||||||
|
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData.also {
|
||||||
|
// Clear pending add TOTP data so it is only handled once:
|
||||||
|
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
||||||
val hasVaultShortcut = intent.isMyVaultShortcut
|
val hasVaultShortcut = intent.isMyVaultShortcut
|
||||||
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
|
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.manager
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager for keeping track of requests from the Bitwarden Authenticator app to add a TOTP
|
||||||
|
* item.
|
||||||
|
*/
|
||||||
|
interface AddTotpItemFromAuthenticatorManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current pending [TotpData] to be added from the Authenticator app.
|
||||||
|
*/
|
||||||
|
var pendingAddTotpLoginItemData: TotpData?
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.manager
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default in memory implementation for [AddTotpItemFromAuthenticatorManager].
|
||||||
|
*/
|
||||||
|
class AddTotpItemFromAuthenticatorManagerImpl : AddTotpItemFromAuthenticatorManager {
|
||||||
|
|
||||||
|
override var pendingAddTotpLoginItemData: TotpData? = null
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServ
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManagerImpl
|
||||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
|
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
|
||||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManagerImpl
|
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManagerImpl
|
||||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager
|
import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager
|
||||||
|
@ -124,4 +126,9 @@ object AuthManagerModule {
|
||||||
vaultSdkSource = vaultSdkSource,
|
vaultSdkSource = vaultSdkSource,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesAddTotpItemFromAuthenticatorManager(): AddTotpItemFromAuthenticatorManager =
|
||||||
|
AddTotpItemFromAuthenticatorManagerImpl()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||||
|
@ -84,10 +85,14 @@ object PlatformManagerModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAuthenticatorBridgeProcessor(
|
fun provideAuthenticatorBridgeProcessor(
|
||||||
authenticatorBridgeRepository: AuthenticatorBridgeRepository,
|
authenticatorBridgeRepository: AuthenticatorBridgeRepository,
|
||||||
|
addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
|
||||||
|
@ApplicationContext context: Context,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
featureFlagManager: FeatureFlagManager,
|
featureFlagManager: FeatureFlagManager,
|
||||||
): AuthenticatorBridgeProcessor = AuthenticatorBridgeProcessorImpl(
|
): AuthenticatorBridgeProcessor = AuthenticatorBridgeProcessorImpl(
|
||||||
authenticatorBridgeRepository = authenticatorBridgeRepository,
|
authenticatorBridgeRepository = authenticatorBridgeRepository,
|
||||||
|
addTotpItemFromAuthenticatorManager = addTotpItemFromAuthenticatorManager,
|
||||||
|
context = context,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
package com.x8bit.bitwarden.data.platform.processor
|
package com.x8bit.bitwarden.data.platform.processor
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IInterface
|
import android.os.IInterface
|
||||||
import android.os.RemoteCallbackList
|
import android.os.RemoteCallbackList
|
||||||
|
import androidx.core.net.toUri
|
||||||
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
||||||
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeServiceCallback
|
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeServiceCallback
|
||||||
import com.bitwarden.authenticatorbridge.model.EncryptedAddTotpLoginItemData
|
import com.bitwarden.authenticatorbridge.model.EncryptedAddTotpLoginItemData
|
||||||
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyData
|
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyData
|
||||||
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyFingerprintData
|
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyFingerprintData
|
||||||
import com.bitwarden.authenticatorbridge.util.AUTHENTICATOR_BRIDGE_SDK_VERSION
|
import com.bitwarden.authenticatorbridge.util.AUTHENTICATOR_BRIDGE_SDK_VERSION
|
||||||
|
import com.bitwarden.authenticatorbridge.util.decrypt
|
||||||
import com.bitwarden.authenticatorbridge.util.encrypt
|
import com.bitwarden.authenticatorbridge.util.encrypt
|
||||||
import com.bitwarden.authenticatorbridge.util.toFingerprint
|
import com.bitwarden.authenticatorbridge.util.toFingerprint
|
||||||
import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData
|
import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository
|
import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.createAddTotpItemFromAuthenticatorIntent
|
||||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||||
|
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -26,10 +31,13 @@ import kotlinx.coroutines.launch
|
||||||
*/
|
*/
|
||||||
class AuthenticatorBridgeProcessorImpl(
|
class AuthenticatorBridgeProcessorImpl(
|
||||||
private val authenticatorBridgeRepository: AuthenticatorBridgeRepository,
|
private val authenticatorBridgeRepository: AuthenticatorBridgeRepository,
|
||||||
|
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
|
||||||
private val featureFlagManager: FeatureFlagManager,
|
private val featureFlagManager: FeatureFlagManager,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
|
context: Context,
|
||||||
) : AuthenticatorBridgeProcessor {
|
) : AuthenticatorBridgeProcessor {
|
||||||
|
|
||||||
|
private val applicationContext = context.applicationContext
|
||||||
private val callbacks by lazy { RemoteCallbackList<IAuthenticatorBridgeServiceCallback>() }
|
private val callbacks by lazy { RemoteCallbackList<IAuthenticatorBridgeServiceCallback>() }
|
||||||
private val scope by lazy { CoroutineScope(dispatcherManager.default) }
|
private val scope by lazy { CoroutineScope(dispatcherManager.default) }
|
||||||
|
|
||||||
|
@ -101,13 +109,18 @@ class AuthenticatorBridgeProcessorImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createAddTotpLoginItemIntent(): Intent {
|
override fun startAddTotpLoginItemFlow(data: EncryptedAddTotpLoginItemData): Boolean {
|
||||||
// TODO: BITAU-112
|
val symmetricEncryptionKey = symmetricEncryptionKeyData ?: return false
|
||||||
return Intent()
|
val intent = createAddTotpItemFromAuthenticatorIntent(context = applicationContext)
|
||||||
}
|
val totpData = data.decrypt(symmetricEncryptionKey)
|
||||||
|
.getOrNull()
|
||||||
override fun setPendingAddTotpLoginItemData(data: EncryptedAddTotpLoginItemData?) {
|
?.totpUri
|
||||||
// TODO: BITAU-112
|
?.toUri()
|
||||||
|
?.getTotpDataOrNull()
|
||||||
|
?: return false
|
||||||
|
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData = totpData
|
||||||
|
applicationContext.startActivity(intent)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
@file:OmitFromCoverage
|
||||||
|
|
||||||
|
package com.x8bit.bitwarden.data.platform.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
import com.x8bit.bitwarden.MainActivity
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
|
|
||||||
|
private const val ADD_TOTP_ITEM_FROM_AUTHENTICATOR_KEY = "add-totp-item-from-authenticator-key"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an intent for launching add TOTP item flow from the Authenticator app.
|
||||||
|
*/
|
||||||
|
fun createAddTotpItemFromAuthenticatorIntent(
|
||||||
|
context: Context,
|
||||||
|
): Intent =
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
MainActivity::class.java,
|
||||||
|
)
|
||||||
|
.apply {
|
||||||
|
putExtra(
|
||||||
|
ADD_TOTP_ITEM_FROM_AUTHENTICATOR_KEY,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
addFlags(FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
addFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the Intent was started by the Authenticator app to add a TOTP item. The TOTP
|
||||||
|
* item can be found in [AddTotpItemFromAuthenticatorManager].
|
||||||
|
*/
|
||||||
|
fun Intent.isAddTotpLoginItemFromAuthenticator(): Boolean =
|
||||||
|
getBooleanExtra(ADD_TOTP_ITEM_FROM_AUTHENTICATOR_KEY, false)
|
|
@ -6,6 +6,7 @@ import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.bitwarden.vault.CipherView
|
import com.bitwarden.vault.CipherView
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManagerImpl
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
|
@ -43,6 +44,7 @@ import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
|
@ -79,6 +81,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
||||||
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||||
AccessibilitySelectionManagerImpl()
|
AccessibilitySelectionManagerImpl()
|
||||||
|
private val addTotpItemAuthenticatorManager = AddTotpItemFromAuthenticatorManagerImpl()
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||||
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
||||||
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
||||||
|
@ -131,6 +134,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
Intent::getFido2AssertionRequestOrNull,
|
Intent::getFido2AssertionRequestOrNull,
|
||||||
Intent::getFido2CredentialRequestOrNull,
|
Intent::getFido2CredentialRequestOrNull,
|
||||||
Intent::getFido2GetCredentialsRequestOrNull,
|
Intent::getFido2GetCredentialsRequestOrNull,
|
||||||
|
Intent::isAddTotpLoginItemFromAuthenticator,
|
||||||
)
|
)
|
||||||
mockkStatic(
|
mockkStatic(
|
||||||
Intent::isMyVaultShortcut,
|
Intent::isMyVaultShortcut,
|
||||||
|
@ -150,6 +154,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
Intent::getFido2AssertionRequestOrNull,
|
Intent::getFido2AssertionRequestOrNull,
|
||||||
Intent::getFido2CredentialRequestOrNull,
|
Intent::getFido2CredentialRequestOrNull,
|
||||||
Intent::getFido2GetCredentialsRequestOrNull,
|
Intent::getFido2GetCredentialsRequestOrNull,
|
||||||
|
Intent::isAddTotpLoginItemFromAuthenticator,
|
||||||
)
|
)
|
||||||
unmockkStatic(
|
unmockkStatic(
|
||||||
Intent::isMyVaultShortcut,
|
Intent::isMyVaultShortcut,
|
||||||
|
@ -321,6 +326,38 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on ReceiveFirstIntent with TOTP data from Authenticator app should set the special circumstance to AddTotpLoginItem and clear pendingAddTotpLoginItemData`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val totpData = mockk<TotpData>()
|
||||||
|
val mockIntent = createMockIntent(
|
||||||
|
mockIsAddTotpLoginItemFromAuthenticator = true,
|
||||||
|
)
|
||||||
|
addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData = totpData
|
||||||
|
|
||||||
|
viewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = mockIntent))
|
||||||
|
assertEquals(
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData),
|
||||||
|
specialCircumstanceManager.specialCircumstance,
|
||||||
|
)
|
||||||
|
assertNull(addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on ReceiveFirstIntent when intent is from Authenticator app but pending item is null should not set special circumstance`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val mockIntent = createMockIntent(
|
||||||
|
mockIsAddTotpLoginItemFromAuthenticator = true,
|
||||||
|
)
|
||||||
|
addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData = null
|
||||||
|
|
||||||
|
viewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = mockIntent))
|
||||||
|
assertNull(specialCircumstanceManager.specialCircumstance)
|
||||||
|
assertNull(addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
|
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||||
|
@ -748,6 +785,38 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on ReceiveNewIntent with TOTP data from Authenticator app should set the special circumstance to AddTotpLoginItem and clear pendingAddTotpLoginItemData`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val totpData = mockk<TotpData>()
|
||||||
|
val mockIntent = createMockIntent(
|
||||||
|
mockIsAddTotpLoginItemFromAuthenticator = true,
|
||||||
|
)
|
||||||
|
addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData = totpData
|
||||||
|
|
||||||
|
viewModel.trySendAction(MainAction.ReceiveNewIntent(intent = mockIntent))
|
||||||
|
assertEquals(
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData),
|
||||||
|
specialCircumstanceManager.specialCircumstance,
|
||||||
|
)
|
||||||
|
assertNull(addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on ReceiveNewIntent when intent is from Authenticator app but pending item is null should not set special circumstance`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val mockIntent = createMockIntent(
|
||||||
|
mockIsAddTotpLoginItemFromAuthenticator = true,
|
||||||
|
)
|
||||||
|
addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData = null
|
||||||
|
|
||||||
|
viewModel.trySendAction(MainAction.ReceiveNewIntent(intent = mockIntent))
|
||||||
|
assertNull(specialCircumstanceManager.specialCircumstance)
|
||||||
|
assertNull(addTotpItemAuthenticatorManager.pendingAddTotpLoginItemData)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
||||||
|
@ -943,6 +1012,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
initialSpecialCircumstance: SpecialCircumstance? = null,
|
initialSpecialCircumstance: SpecialCircumstance? = null,
|
||||||
) = MainViewModel(
|
) = MainViewModel(
|
||||||
accessibilitySelectionManager = accessibilitySelectionManager,
|
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||||
|
addTotpItemFromAuthenticatorManager = addTotpItemAuthenticatorManager,
|
||||||
autofillSelectionManager = autofillSelectionManager,
|
autofillSelectionManager = autofillSelectionManager,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
garbageCollectionManager = garbageCollectionManager,
|
garbageCollectionManager = garbageCollectionManager,
|
||||||
|
@ -1013,6 +1083,7 @@ private fun createMockIntent(
|
||||||
mockIsMyVaultShortcut: Boolean = false,
|
mockIsMyVaultShortcut: Boolean = false,
|
||||||
mockIsPasswordGeneratorShortcut: Boolean = false,
|
mockIsPasswordGeneratorShortcut: Boolean = false,
|
||||||
mockIsAccountSecurityShortcut: Boolean = false,
|
mockIsAccountSecurityShortcut: Boolean = false,
|
||||||
|
mockIsAddTotpLoginItemFromAuthenticator: Boolean = false,
|
||||||
): Intent = mockk<Intent> {
|
): Intent = mockk<Intent> {
|
||||||
every { getTotpDataOrNull() } returns mockTotpData
|
every { getTotpDataOrNull() } returns mockTotpData
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns mockPasswordlessRequestData
|
every { getPasswordlessRequestDataIntentOrNull() } returns mockPasswordlessRequestData
|
||||||
|
@ -1025,6 +1096,7 @@ private fun createMockIntent(
|
||||||
every { isMyVaultShortcut } returns mockIsMyVaultShortcut
|
every { isMyVaultShortcut } returns mockIsMyVaultShortcut
|
||||||
every { isPasswordGeneratorShortcut } returns mockIsPasswordGeneratorShortcut
|
every { isPasswordGeneratorShortcut } returns mockIsPasswordGeneratorShortcut
|
||||||
every { isAccountSecurityShortcut } returns mockIsAccountSecurityShortcut
|
every { isAccountSecurityShortcut } returns mockIsAccountSecurityShortcut
|
||||||
|
every { isAddTotpLoginItemFromAuthenticator() } returns mockIsAddTotpLoginItemFromAuthenticator
|
||||||
}
|
}
|
||||||
|
|
||||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.manager
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
|
|
||||||
|
class AddTotpItemFromAuthenticatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `pendingAddTotpLoginItemData should start as null and keep value when set`() {
|
||||||
|
val manager = AddTotpItemFromAuthenticatorManagerImpl()
|
||||||
|
assertNull(manager.pendingAddTotpLoginItemData)
|
||||||
|
|
||||||
|
val totpData: TotpData = mockk()
|
||||||
|
manager.pendingAddTotpLoginItemData = totpData
|
||||||
|
assertEquals(
|
||||||
|
totpData,
|
||||||
|
manager.pendingAddTotpLoginItemData,
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.pendingAddTotpLoginItemData = null
|
||||||
|
assertNull(manager.pendingAddTotpLoginItemData)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,42 @@
|
||||||
package com.x8bit.bitwarden.data.platform.processor
|
package com.x8bit.bitwarden.data.platform.processor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.RemoteCallbackList
|
import android.os.RemoteCallbackList
|
||||||
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
||||||
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeServiceCallback
|
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeServiceCallback
|
||||||
|
import com.bitwarden.authenticatorbridge.model.AddTotpLoginItemData
|
||||||
|
import com.bitwarden.authenticatorbridge.model.EncryptedAddTotpLoginItemData
|
||||||
import com.bitwarden.authenticatorbridge.model.EncryptedSharedAccountData
|
import com.bitwarden.authenticatorbridge.model.EncryptedSharedAccountData
|
||||||
import com.bitwarden.authenticatorbridge.model.SharedAccountData
|
import com.bitwarden.authenticatorbridge.model.SharedAccountData
|
||||||
import com.bitwarden.authenticatorbridge.util.AUTHENTICATOR_BRIDGE_SDK_VERSION
|
import com.bitwarden.authenticatorbridge.util.AUTHENTICATOR_BRIDGE_SDK_VERSION
|
||||||
|
import com.bitwarden.authenticatorbridge.util.decrypt
|
||||||
import com.bitwarden.authenticatorbridge.util.encrypt
|
import com.bitwarden.authenticatorbridge.util.encrypt
|
||||||
import com.bitwarden.authenticatorbridge.util.generateSecretKey
|
import com.bitwarden.authenticatorbridge.util.generateSecretKey
|
||||||
import com.bitwarden.authenticatorbridge.util.toFingerprint
|
import com.bitwarden.authenticatorbridge.util.toFingerprint
|
||||||
import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData
|
import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData
|
||||||
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManagerImpl
|
||||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository
|
import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository
|
||||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.createAddTotpItemFromAuthenticatorIntent
|
||||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkConstructor
|
import io.mockk.mockkConstructor
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.runs
|
||||||
import io.mockk.unmockkStatic
|
import io.mockk.unmockkStatic
|
||||||
|
import io.mockk.verify
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
@ -37,23 +50,44 @@ import org.junit.jupiter.api.Test
|
||||||
class AuthenticatorBridgeProcessorTest {
|
class AuthenticatorBridgeProcessorTest {
|
||||||
|
|
||||||
private val featureFlagManager = mockk<FeatureFlagManager>()
|
private val featureFlagManager = mockk<FeatureFlagManager>()
|
||||||
|
private val addTotpItemFromAuthenticatorManager = AddTotpItemFromAuthenticatorManagerImpl()
|
||||||
private val authenticatorBridgeRepository = mockk<AuthenticatorBridgeRepository>()
|
private val authenticatorBridgeRepository = mockk<AuthenticatorBridgeRepository>()
|
||||||
|
private val context = mockk<Context> {
|
||||||
|
every { applicationContext } returns this@mockk
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var bridgeServiceProcessor: AuthenticatorBridgeProcessorImpl
|
private lateinit var bridgeServiceProcessor: AuthenticatorBridgeProcessorImpl
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
bridgeServiceProcessor = AuthenticatorBridgeProcessorImpl(
|
bridgeServiceProcessor = AuthenticatorBridgeProcessorImpl(
|
||||||
|
addTotpItemFromAuthenticatorManager = addTotpItemFromAuthenticatorManager,
|
||||||
authenticatorBridgeRepository = authenticatorBridgeRepository,
|
authenticatorBridgeRepository = authenticatorBridgeRepository,
|
||||||
|
context = context,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
dispatcherManager = FakeDispatcherManager(),
|
dispatcherManager = FakeDispatcherManager(),
|
||||||
)
|
)
|
||||||
|
mockkStatic(::createAddTotpItemFromAuthenticatorIntent)
|
||||||
|
mockkStatic(
|
||||||
|
SharedAccountData::encrypt,
|
||||||
|
EncryptedAddTotpLoginItemData::decrypt,
|
||||||
|
Uri::parse,
|
||||||
|
Uri::getTotpDataOrNull,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun teardown() {
|
fun teardown() {
|
||||||
unmockkStatic(::isBuildVersionBelow)
|
unmockkStatic(
|
||||||
unmockkStatic(SharedAccountData::encrypt)
|
::createAddTotpItemFromAuthenticatorIntent,
|
||||||
|
::isBuildVersionBelow,
|
||||||
|
)
|
||||||
|
unmockkStatic(
|
||||||
|
SharedAccountData::encrypt,
|
||||||
|
EncryptedAddTotpLoginItemData::decrypt,
|
||||||
|
Uri::parse,
|
||||||
|
Uri::getTotpDataOrNull,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -139,6 +173,80 @@ class AuthenticatorBridgeProcessorTest {
|
||||||
assertEquals(SYMMETRIC_KEY, binder.symmetricEncryptionKeyData)
|
assertEquals(SYMMETRIC_KEY, binder.symmetricEncryptionKeyData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `startAddTotpLoginItemFlow should return false when symmetricEncryptionKeyData is null`() {
|
||||||
|
val binder = getDefaultBinder()
|
||||||
|
every { authenticatorBridgeRepository.authenticatorSyncSymmetricKey } returns null
|
||||||
|
val data: EncryptedAddTotpLoginItemData = mockk()
|
||||||
|
assertFalse(binder.startAddTotpLoginItemFlow(data))
|
||||||
|
verify { authenticatorBridgeRepository.authenticatorSyncSymmetricKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `startAddTotpLoginItemFlow should return false when decryption fails`() {
|
||||||
|
val binder = getDefaultBinder()
|
||||||
|
val intent: Intent = mockk()
|
||||||
|
val data: EncryptedAddTotpLoginItemData = mockk()
|
||||||
|
every {
|
||||||
|
authenticatorBridgeRepository.authenticatorSyncSymmetricKey
|
||||||
|
} returns SYMMETRIC_KEY.symmetricEncryptionKey.byteArray
|
||||||
|
every { createAddTotpItemFromAuthenticatorIntent(context) } returns intent
|
||||||
|
every { data.decrypt(SYMMETRIC_KEY) } returns Result.failure(RuntimeException())
|
||||||
|
assertFalse(binder.startAddTotpLoginItemFlow(data))
|
||||||
|
verify { authenticatorBridgeRepository.authenticatorSyncSymmetricKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `startAddTotpLoginItemFlow should return false when getTotpDataOrNull returns null`() {
|
||||||
|
val binder = getDefaultBinder()
|
||||||
|
val intent: Intent = mockk()
|
||||||
|
val totpUri = "totpUri"
|
||||||
|
val uri: Uri = mockk()
|
||||||
|
every { Uri.parse(totpUri) } returns uri
|
||||||
|
val data: EncryptedAddTotpLoginItemData = mockk()
|
||||||
|
val decryptedData: AddTotpLoginItemData = mockk()
|
||||||
|
every {
|
||||||
|
authenticatorBridgeRepository.authenticatorSyncSymmetricKey
|
||||||
|
} returns SYMMETRIC_KEY.symmetricEncryptionKey.byteArray
|
||||||
|
every { createAddTotpItemFromAuthenticatorIntent(context) } returns intent
|
||||||
|
every { data.decrypt(SYMMETRIC_KEY) } returns Result.success(decryptedData)
|
||||||
|
every { decryptedData.totpUri } returns totpUri
|
||||||
|
every { uri.getTotpDataOrNull() } returns null
|
||||||
|
assertFalse(binder.startAddTotpLoginItemFlow(data))
|
||||||
|
verify { authenticatorBridgeRepository.authenticatorSyncSymmetricKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `startAddTotpLoginItemFlow should return true and set pendingAddTotpLoginItemData when getTotpDataOrNull succeeds`() {
|
||||||
|
val binder = getDefaultBinder()
|
||||||
|
val intent: Intent = mockk()
|
||||||
|
val totpUri = "totpUri"
|
||||||
|
val uri: Uri = mockk()
|
||||||
|
every { Uri.parse(totpUri) } returns uri
|
||||||
|
val expectedPendingData: TotpData = mockk()
|
||||||
|
val data: EncryptedAddTotpLoginItemData = mockk()
|
||||||
|
val decryptedData: AddTotpLoginItemData = mockk()
|
||||||
|
every {
|
||||||
|
authenticatorBridgeRepository.authenticatorSyncSymmetricKey
|
||||||
|
} returns SYMMETRIC_KEY.symmetricEncryptionKey.byteArray
|
||||||
|
every { createAddTotpItemFromAuthenticatorIntent(context) } returns intent
|
||||||
|
every { data.decrypt(SYMMETRIC_KEY) } returns Result.success(decryptedData)
|
||||||
|
every { decryptedData.totpUri } returns totpUri
|
||||||
|
every { uri.getTotpDataOrNull() } returns expectedPendingData
|
||||||
|
every { context.startActivity(intent) } just runs
|
||||||
|
assertTrue(binder.startAddTotpLoginItemFlow(data))
|
||||||
|
assertEquals(
|
||||||
|
expectedPendingData,
|
||||||
|
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData,
|
||||||
|
)
|
||||||
|
verify { context.startActivity(intent) }
|
||||||
|
verify { authenticatorBridgeRepository.authenticatorSyncSymmetricKey }
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class SyncAccountsTest {
|
inner class SyncAccountsTest {
|
||||||
|
|
||||||
|
|
|
@ -49,13 +49,8 @@ interface IAuthenticatorBridgeService {
|
||||||
// Add TOTP Item
|
// Add TOTP Item
|
||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
// Returns an intent that can be launched to navigate the user to the add Totp item flow
|
// Start the add TOTP item flow in the main Bitwarden app with the given data.
|
||||||
// in the main password manager app.
|
// Returns true if the flow was successfully launched and false otherwise.
|
||||||
Intent createAddTotpLoginItemIntent();
|
boolean startAddTotpLoginItemFlow(in EncryptedAddTotpLoginItemData data);
|
||||||
|
|
||||||
// Give the given TOTP item data to the main Bitwarden app before launching the add TOTP
|
|
||||||
// item flow. This should be called before launching the intent returned from
|
|
||||||
// createAddTotpLoginItemIntent().
|
|
||||||
void setPendingAddTotpLoginItemData(in EncryptedAddTotpLoginItemData data);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.bitwarden.authenticatorbridge.manager
|
package com.bitwarden.authenticatorbridge.manager
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
||||||
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
@ -14,4 +16,12 @@ interface AuthenticatorBridgeManager {
|
||||||
* State flow representing the current [AccountSyncState].
|
* State flow representing the current [AccountSyncState].
|
||||||
*/
|
*/
|
||||||
val accountSyncStateFlow: StateFlow<AccountSyncState>
|
val accountSyncStateFlow: StateFlow<AccountSyncState>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the add TOTP item flow in the main Bitwarden app with the given data.
|
||||||
|
*
|
||||||
|
* @param totpUri TOTP URI to add to the main Bitwarden app.
|
||||||
|
* @return true if the flow was successfully launched, false otherwise.
|
||||||
|
*/
|
||||||
|
fun startAddTotpLoginItemFlow(totpUri: String): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
@ -14,11 +15,13 @@ import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
|
||||||
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||||
import com.bitwarden.authenticatorbridge.manager.model.AuthenticatorBridgeConnectionType
|
import com.bitwarden.authenticatorbridge.manager.model.AuthenticatorBridgeConnectionType
|
||||||
import com.bitwarden.authenticatorbridge.manager.util.toPackageName
|
import com.bitwarden.authenticatorbridge.manager.util.toPackageName
|
||||||
|
import com.bitwarden.authenticatorbridge.model.AddTotpLoginItemData
|
||||||
import com.bitwarden.authenticatorbridge.model.EncryptedSharedAccountData
|
import com.bitwarden.authenticatorbridge.model.EncryptedSharedAccountData
|
||||||
import com.bitwarden.authenticatorbridge.provider.AuthenticatorBridgeCallbackProvider
|
import com.bitwarden.authenticatorbridge.provider.AuthenticatorBridgeCallbackProvider
|
||||||
import com.bitwarden.authenticatorbridge.provider.StubAuthenticatorBridgeCallbackProvider
|
import com.bitwarden.authenticatorbridge.provider.StubAuthenticatorBridgeCallbackProvider
|
||||||
import com.bitwarden.authenticatorbridge.provider.SymmetricKeyStorageProvider
|
import com.bitwarden.authenticatorbridge.provider.SymmetricKeyStorageProvider
|
||||||
import com.bitwarden.authenticatorbridge.util.decrypt
|
import com.bitwarden.authenticatorbridge.util.decrypt
|
||||||
|
import com.bitwarden.authenticatorbridge.util.encrypt
|
||||||
import com.bitwarden.authenticatorbridge.util.isBuildVersionBelow
|
import com.bitwarden.authenticatorbridge.util.isBuildVersionBelow
|
||||||
import com.bitwarden.authenticatorbridge.util.toFingerprint
|
import com.bitwarden.authenticatorbridge.util.toFingerprint
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -103,6 +106,21 @@ internal class AuthenticatorBridgeManagerImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun startAddTotpLoginItemFlow(totpUri: String): Boolean =
|
||||||
|
bridgeService
|
||||||
|
?.safeCall {
|
||||||
|
// Grab symmetric key data from local storage:
|
||||||
|
val symmetricKey = symmetricKeyStorageProvider.symmetricKey ?: return@safeCall false
|
||||||
|
// Encrypt the given URI:
|
||||||
|
val addTotpData = AddTotpLoginItemData(totpUri).encrypt(symmetricKey).getOrThrow()
|
||||||
|
return@safeCall this.startAddTotpLoginItemFlow(addTotpData)
|
||||||
|
}
|
||||||
|
?.fold(
|
||||||
|
onFailure = { false },
|
||||||
|
onSuccess = { true }
|
||||||
|
)
|
||||||
|
?: false
|
||||||
|
|
||||||
private fun bindService() {
|
private fun bindService() {
|
||||||
if (isBuildVersionBelow(Build.VERSION_CODES.S)) {
|
if (isBuildVersionBelow(Build.VERSION_CODES.S)) {
|
||||||
mutableSharedAccountsStateFlow.value = AccountSyncState.OsVersionNotSupported
|
mutableSharedAccountsStateFlow.value = AccountSyncState.OsVersionNotSupported
|
||||||
|
@ -119,11 +137,17 @@ internal class AuthenticatorBridgeManagerImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val flags = if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
|
||||||
|
Context.BIND_AUTO_CREATE
|
||||||
|
} else {
|
||||||
|
Context.BIND_AUTO_CREATE or Context.BIND_ALLOW_ACTIVITY_STARTS
|
||||||
|
}
|
||||||
|
|
||||||
val isBound = try {
|
val isBound = try {
|
||||||
applicationContext.bindService(
|
applicationContext.bindService(
|
||||||
intent,
|
intent,
|
||||||
bridgeServiceConnection,
|
bridgeServiceConnection,
|
||||||
Context.BIND_AUTO_CREATE,
|
flags,
|
||||||
)
|
)
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
unbindService()
|
unbindService()
|
||||||
|
|
|
@ -118,7 +118,7 @@ internal fun AddTotpLoginItemData.encrypt(
|
||||||
*
|
*
|
||||||
* @param symmetricEncryptionKeyData Symmetric key used for decryption.
|
* @param symmetricEncryptionKeyData Symmetric key used for decryption.
|
||||||
*/
|
*/
|
||||||
internal fun EncryptedAddTotpLoginItemData.decrypt(
|
fun EncryptedAddTotpLoginItemData.decrypt(
|
||||||
symmetricEncryptionKeyData: SymmetricEncryptionKeyData,
|
symmetricEncryptionKeyData: SymmetricEncryptionKeyData,
|
||||||
): Result<AddTotpLoginItemData> = runCatching {
|
): Result<AddTotpLoginItemData> = runCatching {
|
||||||
val encodedKey = symmetricEncryptionKeyData
|
val encodedKey = symmetricEncryptionKeyData
|
||||||
|
|
Loading…
Reference in a new issue