-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ABW-3847] Integrate sargon logic for security problems in Android wallet #1274
Changes from 6 commits
bffd5e6
e51e774
8b88ccd
ef786d6
f686638
a1e89b4
00bbdef
211e801
d86bd2a
3bb6d6e
e7e7427
4262e63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package com.babylon.wallet.android.domain.usecases.securityproblems | ||
|
||
import com.babylon.wallet.android.domain.model.SecurityProblem | ||
import com.babylon.wallet.android.domain.model.toDomainModel | ||
import com.radixdlt.sargon.AddressesOfEntitiesInBadState | ||
import com.radixdlt.sargon.BackupResult | ||
import com.radixdlt.sargon.CheckSecurityProblemsInput | ||
import com.radixdlt.sargon.extensions.ProfileEntity | ||
import com.radixdlt.sargon.os.SargonOsManager | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.combine | ||
import rdx.works.core.domain.cloudbackup.BackupState | ||
import rdx.works.core.sargon.factorSourceId | ||
import rdx.works.core.sargon.isNotHidden | ||
import rdx.works.profile.domain.backup.GetBackupStateUseCase | ||
import javax.inject.Inject | ||
|
||
class GetSecurityProblemsUseCase @Inject constructor( | ||
private val getEntitiesWithSecurityPromptUseCase: GetEntitiesWithSecurityPromptUseCase, | ||
private val getBackupStateUseCase: GetBackupStateUseCase, | ||
private val sargonOsManager: SargonOsManager | ||
) { | ||
|
||
operator fun invoke(): Flow<Set<SecurityProblem>> = combine( | ||
getEntitiesWithSecurityPromptUseCase(), | ||
getBackupStateUseCase() | ||
) { entitiesWithSecurityPrompts, backupState -> | ||
|
||
// active personas that need cloud backup | ||
val activePersonasNeedCloudBackup = entitiesWithSecurityPrompts | ||
.count { entityWithSecurityPrompt -> | ||
entityWithSecurityPrompt.entity.isNotHidden() && | ||
entityWithSecurityPrompt.entity is ProfileEntity.PersonaEntity && | ||
( | ||
entityWithSecurityPrompt.prompts.contains(SecurityPromptType.CONFIGURATION_BACKUP_PROBLEM) || | ||
entityWithSecurityPrompt.prompts.contains(SecurityPromptType.WALLET_NOT_RECOVERABLE) || | ||
entityWithSecurityPrompt.prompts.contains(SecurityPromptType.CONFIGURATION_BACKUP_NOT_UPDATED) | ||
) | ||
} | ||
|
||
sargonOsManager.sargonOs.checkSecurityProblems( | ||
input = CheckSecurityProblemsInput( | ||
isCloudProfileSyncEnabled = backupState.isCloudBackupEnabled, | ||
unrecoverableEntities = findUnrecoverableEntities(entitiesWithSecurityPrompts), | ||
withoutControlEntities = findWithoutControlEntities(entitiesWithSecurityPrompts), | ||
lastCloudBackup = BackupResult( | ||
saveIdentifier = "string", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a dummy text because the field is only used by iOS? Maybe we'd benefit from having a comment here stating this if this is the case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. forgot to update this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sergiupuhalschi-rdx removed |
||
isCurrent = backupState.isCloudBackupSynced, | ||
isFailed = backupState is BackupState.CloudBackupEnabled && backupState.hasAnyErrors | ||
), | ||
lastManualBackup = BackupResult( | ||
saveIdentifier = "string", | ||
isCurrent = backupState.isManualBackupSynced, | ||
isFailed = false, | ||
) | ||
) | ||
).map { sargonSecurityProblem -> | ||
sargonSecurityProblem.toDomainModel( | ||
isAnyActivePersonaAffected = activePersonasNeedCloudBackup > 0, | ||
hasManualBackup = backupState.lastManualBackupTime != null | ||
) | ||
}.toSet() | ||
} | ||
|
||
private fun findUnrecoverableEntities(entitiesWithSecurityPrompts: List<EntityWithSecurityPrompt>): AddressesOfEntitiesInBadState { | ||
// entities that need to write down seed phrase | ||
val entitiesNeedBackup = entitiesWithSecurityPrompts | ||
.filter { entityWithSecurityPrompt -> | ||
entityWithSecurityPrompt.prompts.contains(SecurityPromptType.WRITE_DOWN_SEED_PHRASE) | ||
} | ||
val factorSourceIdsNeedBackup = entitiesNeedBackup.map { entityWithSecurityPrompt -> | ||
entityWithSecurityPrompt.entity.securityState.factorSourceId | ||
}.toSet() | ||
// not hidden and hidden accounts that need to write down seed phrase | ||
val (activeAccountAddressesNeedBackup, hiddenAccountAddressesNeedBackup) = entitiesNeedBackup | ||
.filter { entityWithSecurityPrompt -> | ||
entityWithSecurityPrompt.entity is ProfileEntity.AccountEntity && | ||
factorSourceIdsNeedBackup.contains(entityWithSecurityPrompt.entity.securityState.factorSourceId) | ||
} | ||
.map { entityWithSecurityPrompt -> entityWithSecurityPrompt.entity as ProfileEntity.AccountEntity } | ||
.partition { accountEntity -> accountEntity.isNotHidden() } | ||
.let { partitioned -> | ||
partitioned.first.map { it.accountAddress } to partitioned.second.map { it.accountAddress } | ||
} | ||
// not hidden and hidden personas that need to write down seed phrase | ||
val (activePersonaAddressesNeedBackup, hiddenPersonaAddressesNeedBackup) = entitiesNeedBackup | ||
.filter { entityWithSecurityPrompt -> | ||
entityWithSecurityPrompt.entity is ProfileEntity.PersonaEntity && | ||
factorSourceIdsNeedBackup.contains(entityWithSecurityPrompt.entity.securityState.factorSourceId) | ||
} | ||
.map { entityWithSecurityPrompt -> entityWithSecurityPrompt.entity as ProfileEntity.PersonaEntity } | ||
.partition { personaEntity -> personaEntity.isNotHidden() } | ||
.let { partitioned -> | ||
partitioned.first.map { it.identityAddress } to partitioned.second.map { it.identityAddress } | ||
} | ||
|
||
return AddressesOfEntitiesInBadState( | ||
accounts = activeAccountAddressesNeedBackup, | ||
hiddenAccounts = hiddenAccountAddressesNeedBackup, | ||
personas = activePersonaAddressesNeedBackup, | ||
hiddenPersonas = hiddenPersonaAddressesNeedBackup | ||
) | ||
} | ||
|
||
private fun findWithoutControlEntities(entitiesWithSecurityPrompts: List<EntityWithSecurityPrompt>): AddressesOfEntitiesInBadState { | ||
// entities that need recovery | ||
val entitiesNeedRecovery = | ||
entitiesWithSecurityPrompts.filter { it.prompts.contains(SecurityPromptType.RECOVERY_REQUIRED) } | ||
val (activeEntitiesNeedRecovery, hiddenEntitiesNeedRecovery) = entitiesNeedRecovery | ||
.partition { it.entity.isNotHidden() } | ||
val activeAccountAddressesNeedRecovery = activeEntitiesNeedRecovery | ||
.mapNotNull { (it.entity as? ProfileEntity.AccountEntity)?.accountAddress } | ||
val activePersonaAddressesNeedRecovery = activeEntitiesNeedRecovery | ||
.mapNotNull { (it.entity as? ProfileEntity.PersonaEntity)?.identityAddress } | ||
val hiddenAccountAddressesNeedRecovery = hiddenEntitiesNeedRecovery | ||
.mapNotNull { (it.entity as? ProfileEntity.AccountEntity)?.accountAddress } | ||
val hiddenPersonaAddressesNeedRecovery = hiddenEntitiesNeedRecovery | ||
.mapNotNull { (it.entity as? ProfileEntity.PersonaEntity)?.identityAddress } | ||
|
||
return AddressesOfEntitiesInBadState( | ||
accounts = activeAccountAddressesNeedRecovery, | ||
hiddenAccounts = hiddenAccountAddressesNeedRecovery, | ||
personas = activePersonaAddressesNeedRecovery, | ||
hiddenPersonas = hiddenPersonaAddressesNeedRecovery | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both
sargonOs
getter andcheckSecurityProblems
method on it are throwing. Please ensure the exception is properly handled.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes good catch! thank you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sergiupuhalschi-rdx what do you recommend about handling the exception?
adding a
runCatching
in the usecase and returnFlow<Result<Set<SecurityProblem>>>
or adding a
.catch { Timber.e("failed to get security problems...") }
in the viewmodel where I collect the flows?I’m leaning toward the second option, tbh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we should just propagate the error with no additional logic then
catch
looks like a better option. You might also consider addingcatch
inside theuseCase
instead of eachviewModel
using thisuseCase
. Either option sounds good.