Skip to content

Commit

Permalink
Merge branch 'master' into ios-unit-tests-settings-subviews
Browse files Browse the repository at this point in the history
  • Loading branch information
prybalko authored Jan 22, 2024
2 parents 06dc381 + b042fa8 commit f423939
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.parity.signer.dependencygraph.ServiceLocator
import io.parity.signer.domain.backend.OperationResult
import io.parity.signer.domain.usecases.ResetUseCase
import io.parity.signer.screens.error.ErrorStateDestinationState
import io.parity.signer.uniffi.*
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
Expand All @@ -27,17 +29,16 @@ class MainFlowViewModel() : ViewModel() {
val activity: FragmentActivity
get() = ServiceLocator.activityScope!!.activity

fun onUnlockClicked() {
viewModelScope.launch {
when (authentication.authenticate(activity)) {
suspend fun onUnlockClicked(): OperationResult<Unit, ErrorStateDestinationState> {
return when (authentication.authenticate(activity)) {
AuthResult.AuthSuccess -> resetUseCase.totalRefresh()
AuthResult.AuthError,
AuthResult.AuthFailed,
AuthResult.AuthUnavailable -> {
Timber.e("Signer", "Auth failed, not unlocked")
OperationResult.Ok(Unit)
}
}
}
}

val authenticated: StateFlow<Boolean> = authentication.auth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.WifiManager
import android.provider.Settings
import android.util.Log
import io.parity.signer.domain.backend.UniffiInteractor
import io.parity.signer.uniffi.historyAcknowledgeWarnings
import io.parity.signer.uniffi.historyGetWarnings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber


class NetworkExposedStateKeeper(
Expand Down Expand Up @@ -89,7 +89,6 @@ class NetworkExposedStateKeeper(
val intentFilter = IntentFilter("android.hardware.usb.action.USB_STATE")
val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.e("TAGG", "usb broadcast")
reactOnUsb(intent)
}
}
Expand Down Expand Up @@ -134,7 +133,7 @@ class NetworkExposedStateKeeper(

private fun reactOnUsb(usbIntent: Intent) {
if (FeatureFlags.isEnabled(FeatureOption.SKIP_USB_CHECK)) {
_usbDisconnected.value = false
_usbDisconnected.value = true
updateGeneralAirgapState()
return
}
Expand All @@ -149,7 +148,7 @@ class NetworkExposedStateKeeper(
updateGeneralAirgapState()
}
null -> {
Log.d("USB", "usb action intent doesn't have connection state")
Timber.d("USB", "usb action intent doesn't have connection state")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import android.security.keystore.UserNotAuthenticatedException
import timber.log.Timber
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import io.parity.signer.R
import io.parity.signer.domain.FeatureFlags
import io.parity.signer.domain.FeatureOption
import io.parity.signer.domain.backend.OperationResult
import io.parity.signer.screens.error.ErrorStateDestinationState
import io.parity.signer.uniffi.ErrorDisplayed
import io.parity.signer.uniffi.historySeedWasShown
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.security.UnrecoverableKeyException
import javax.crypto.AEADBadTagException


/**
Expand Down Expand Up @@ -42,7 +48,7 @@ class SeedStorage {
/**
* @throws UserNotAuthenticatedException
*/
fun init(appContext: Context) {
fun init(appContext: Context): OperationResult<Unit, ErrorStateDestinationState> {
hasStrongbox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
appContext
.packageManager
Expand Down Expand Up @@ -76,21 +82,26 @@ class SeedStorage {

Timber.e("ENCRY", "$appContext $KEYSTORE_NAME $masterKey")
//we need to be authenticated for this
sharedPreferences =
if (FeatureFlags.isEnabled(FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT)) {
appContext.getSharedPreferences(
"FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT",
Context.MODE_PRIVATE
)
} else {
EncryptedSharedPreferences(
appContext,
KEYSTORE_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
try {
sharedPreferences =
if (FeatureFlags.isEnabled(FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT)) {
appContext.getSharedPreferences(
"FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT",
Context.MODE_PRIVATE
)
} else {
EncryptedSharedPreferences(
appContext,
KEYSTORE_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
} catch (e: Exception) {
return OperationResult.Err(consumeStorageAuthError(e, appContext))
}
return OperationResult.Ok(Unit)
}


Expand Down Expand Up @@ -174,6 +185,38 @@ class SeedStorage {
fun wipe() {
sharedPreferences.edit().clear().commit() // No, not apply(), do it now!
}
}


private fun consumeStorageAuthError(e: Exception, context: Context): ErrorStateDestinationState {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
when (e) {
is AEADBadTagException,
is android.security.KeyStoreException,
is UnrecoverableKeyException -> {
return ErrorStateDestinationState(
argHeader = context.getString(R.string.error_secure_storage_title),
argDescription = context.getString(R.string.error_secure_storage_description),
argVerbose = e.stackTraceToString()
)
}
else -> throw e
}
} else {
when (e) {
is AEADBadTagException,
is UnrecoverableKeyException -> {
return ErrorStateDestinationState(
argHeader = context.getString(R.string.error_secure_storage_title),
argDescription = context.getString(R.string.error_secure_storage_description),
argVerbose = e.stackTraceToString()
)
}
else -> throw e
}
}
}





Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package io.parity.signer.domain.usecases

import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import io.parity.signer.R
import io.parity.signer.dependencygraph.ServiceLocator
import io.parity.signer.domain.AuthResult
import io.parity.signer.domain.Callback
import io.parity.signer.domain.backend.OperationResult
import io.parity.signer.domain.getDbNameFromContext
import io.parity.signer.domain.isDbCreatedAndOnboardingPassed
import io.parity.signer.domain.storage.DatabaseAssetsInteractor
import io.parity.signer.screens.error.ErrorStateDestinationState
import io.parity.signer.uniffi.historyInitHistoryNoCert
import io.parity.signer.uniffi.historyInitHistoryWithCert
import io.parity.signer.uniffi.initNavigation
Expand All @@ -22,26 +27,52 @@ class ResetUseCase {
private val activity: FragmentActivity
get() = ServiceLocator.activityScope!!.activity

fun wipeToFactoryWithAuth(onAfterWide: Callback) {
suspend fun wipeToFactoryWithAuth(onAfterWipe: Callback): OperationResult<Unit, ErrorStateDestinationState> {
val authentication = ServiceLocator.authentication
authentication.authenticate(activity) {
databaseAssetsInteractor.wipe()
totalRefresh()
onAfterWide()
return when (authentication.authenticate(activity)) {
AuthResult.AuthError,
AuthResult.AuthFailed ,
AuthResult.AuthUnavailable -> {
Toast.makeText(
activity.baseContext,
activity.baseContext.getString(R.string.auth_failed_message),
Toast.LENGTH_SHORT
).show()
OperationResult.Ok(Unit)
}
AuthResult.AuthSuccess -> {
databaseAssetsInteractor.wipe()
val result = totalRefresh()
onAfterWipe()
return result
}
}
}

/**
* Auth user and wipe Vault to state without general verifier certificate
*/
fun wipeNoGeneralCertWithAuth(onAfterWide: Callback) {
suspend fun wipeNoGeneralCertWithAuth(onAfterWide: Callback): OperationResult<Unit, ErrorStateDestinationState> {
val authentication = ServiceLocator.authentication
authentication.authenticate(activity) {
databaseAssetsInteractor.wipe()
databaseAssetsInteractor.copyAsset("")
totalRefresh()
historyInitHistoryNoCert()
onAfterWide()
return when (authentication.authenticate(activity)) {
AuthResult.AuthError,
AuthResult.AuthFailed,
AuthResult.AuthUnavailable -> {
Toast.makeText(
activity.baseContext,
activity.baseContext.getString(R.string.auth_failed_message),
Toast.LENGTH_SHORT
).show()
OperationResult.Ok(Unit)
}
AuthResult.AuthSuccess -> {
databaseAssetsInteractor.wipe()
databaseAssetsInteractor.copyAsset("")
val result = totalRefresh()
historyInitHistoryNoCert()
onAfterWide()
return result
}
}
}

Expand All @@ -67,14 +98,18 @@ class ResetUseCase {
* This returns the app into starting state;
* Do not forget to reset navigation UI state!
*/
fun totalRefresh() {
fun totalRefresh(): OperationResult<Unit, ErrorStateDestinationState> {
if (!seedStorage.isInitialized()) {
seedStorage.init(appContext)
val result = seedStorage.init(appContext)
if (result is OperationResult.Err) {
return result
}
}
if (!appContext.isDbCreatedAndOnboardingPassed()) {
initAssetsAndTotalRefresh()
} else {
totalRefreshDbExist()
}
return OperationResult.Ok(Unit)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ inline fun <reified T> UniffiResult<T>.handleErrorAppState(coreNavController: Na
return this.toOperationResult().handleErrorAppState(coreNavController)
}

data class ErrorStateDestinationState(
val argHeader: String,
val argDescription: String,
val argVerbose: String,
)

inline fun <reified T, E> OperationResult<T, E>.handleErrorAppState(
coreNavController: NavController
Expand All @@ -75,6 +80,13 @@ inline fun <reified T, E> OperationResult<T, E>.handleErrorAppState(
is OperationResult.Err -> {
coreNavController.navigate(
when (error) {
is ErrorStateDestinationState -> {
CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination(
argHeader = error.argHeader,
argDescription = error.argDescription,
argVerbose = error.argVerbose,
)
}
is NavigationError -> {
CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination(
argHeader = "Operation navigation error trying to get ${T::class.java}",
Expand All @@ -88,6 +100,7 @@ inline fun <reified T, E> OperationResult<T, E>.handleErrorAppState(
is ErrorDisplayed.DbSchemaMismatch -> {
CoreUnlockedNavSubgraph.errorWrongDbVersionUpdate
}

else -> {
CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination(
argHeader = "Operation error to get ${T::class.java}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph
* all subsequent interactions should be in modals or drop-down menus
*/
fun NavGraphBuilder.settingsFullSubgraph(
navController: NavController,
coreNavController: NavController,
) {
navigation(
route = CoreUnlockedNavSubgraph.settings,
Expand All @@ -40,32 +40,32 @@ fun NavGraphBuilder.settingsFullSubgraph(
enterTransition = { fadeIn(animationSpec = tween(700)) },
exitTransition = { fadeOut(animationSpec = tween(700)) },
) {
SettingsGeneralNavSubgraph(parentNavController = navController)
SettingsGeneralNavSubgraph(coreNavController = coreNavController)
}
composable(SettingsNavSubgraph.terms) {
Box(modifier = Modifier.statusBarsPadding()) {
TosScreen(onBack = {
navController.popBackStack(SettingsNavSubgraph.home, false)
coreNavController.popBackStack(SettingsNavSubgraph.home, false)
})
}
}
composable(SettingsNavSubgraph.privacyPolicy) {
Box(modifier = Modifier.statusBarsPadding()) {
PpScreen(onBack = {
navController.popBackStack(SettingsNavSubgraph.home, false)
coreNavController.popBackStack(SettingsNavSubgraph.home, false)
})
}
}
composable(SettingsNavSubgraph.backup) {
SeedBackupIntegratedScreen(navController) {
navController.popBackStack(SettingsNavSubgraph.home, false)
SeedBackupIntegratedScreen(coreNavController) {
coreNavController.popBackStack(SettingsNavSubgraph.home, false)
}
}
logsNavigationSubgraph(
navController = navController,
navController = coreNavController,
)
networkListDestination(navController)
verifierSettingsDestination(navController)
networkListDestination(coreNavController)
verifierSettingsDestination(coreNavController)
composable(
route = SettingsNavSubgraph.NetworkDetails.route,
arguments = listOf(
Expand All @@ -78,10 +78,10 @@ fun NavGraphBuilder.settingsFullSubgraph(
it.arguments?.getString(SettingsNavSubgraph.NetworkDetails.networkKey)!!
NetworkDetailsSubgraph(
networkKey,
navController,
coreNavController,
)
}
signSpecsDestination(navController)
signSpecsDestination(coreNavController)
}
}

Expand Down
Loading

0 comments on commit f423939

Please sign in to comment.