Skip to content

Commit

Permalink
NEVISACCESSAPP-6287: Change the order Account Selection and Transacti…
Browse files Browse the repository at this point in the history
…on Confirmation screens are shown: the former now displayed before the latter

- Transaction Confirmation screen now only deals with a single Account as that should be selected before that
- Transaction Confirmation message is passed along Account Selection now
- Somewhat simplified/clarified logic in AccountSelectorImpl
- Pass Account directly around for private methods corresponding for different operations in SelectAccountViewModel instead of just the username
  • Loading branch information
balazs-gerlei committed Nov 7, 2024
1 parent ad8831f commit 5ce9ceb
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,36 +45,35 @@ class AccountSelectorImpl(
.sdk("Please select one of the received available accounts!")
try {
val accounts = validAccounts(context)
if (accounts.isEmpty()) {
throw BusinessException.accountsNotFound()
}

val transactionConfirmationData =
context.transactionConfirmationData().orElse(null)

transactionConfirmationData?.also {
navigationDispatcher.requestNavigation(
NavigationGraphDirections.actionGlobalTransactionConfirmationFragment(
TransactionConfirmationNavigationParameter(
Operation.OUT_OF_BAND_AUTHENTICATION,
accounts,
it.decodeToString(),
handler
)
)
)
} ?: run {
if (accounts.size == 1) {
when(accounts.size) {
0 -> throw BusinessException.accountsNotFound()
1 -> {
Timber.asTree()
.sdk("One account found, performing automatic selection!")
handler.username(accounts.first().username())
} else {
if (transactionConfirmationData != null) {
navigationDispatcher.requestNavigation(
NavigationGraphDirections.actionGlobalTransactionConfirmationFragment(
TransactionConfirmationNavigationParameter(
account = accounts.first(),
transactionConfirmationMessage = transactionConfirmationData.decodeToString(),
accountSelectionHandler = handler
)
)
)
} else {
handler.username(accounts.first().username())
}
}
else -> {
navigationDispatcher.requestNavigation(
NavigationGraphDirections.actionGlobalSelectAccountFragment(
SelectAccountNavigationParameter(
Operation.OUT_OF_BAND_AUTHENTICATION,
accounts,
handler
handler,
transactionConfirmationData?.decodeToString()
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Nevis Mobile Authentication SDK Example App
*
* Copyright © 2023. Nevis Security AG. All rights reserved.
* Copyright © 2023-2024. Nevis Security AG. All rights reserved.
*/

package ch.nevis.exampleapp.ui.selectAccount
Expand Down Expand Up @@ -97,10 +97,7 @@ class SelectAccountFragment : BaseFragment(),

//region AccountSelectedListener
override fun onAccountSelected(account: Account) {
viewModel.selectAccount(
navigationArguments.parameter.operation,
account.username()
)
viewModel.selectAccount(navigationArguments.parameter.operation, account)
}
//endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Nevis Mobile Authentication SDK Example App
*
* Copyright © 2023. Nevis Security AG. All rights reserved.
* Copyright © 2023-2024. Nevis Security AG. All rights reserved.
*/

package ch.nevis.exampleapp.ui.selectAccount
Expand All @@ -17,6 +17,8 @@ import ch.nevis.exampleapp.ui.base.CancellableOperationViewModel
import ch.nevis.exampleapp.ui.navigation.NavigationDispatcher
import ch.nevis.exampleapp.ui.result.parameter.ResultNavigationParameter
import ch.nevis.exampleapp.ui.selectAccount.parameter.SelectAccountNavigationParameter
import ch.nevis.exampleapp.ui.transactionConfirmation.parameter.TransactionConfirmationNavigationParameter
import ch.nevis.mobile.sdk.api.localdata.Account
import ch.nevis.mobile.sdk.api.operation.password.PasswordChanger
import ch.nevis.mobile.sdk.api.operation.pin.PinChanger
import ch.nevis.mobile.sdk.api.operation.selection.AccountSelectionHandler
Expand Down Expand Up @@ -67,6 +69,8 @@ class SelectAccountViewModel @Inject constructor(
* Select Account view to ask the user to select one of the available accounts to be able to continue the operation.
*/
private var accountSelectionHandler: AccountSelectionHandler? = null

private var transactionConfirmationMessage: String? = null
//endregion

//region Public Interface
Expand All @@ -78,6 +82,7 @@ class SelectAccountViewModel @Inject constructor(
*/
fun updateViewModel(parameter: SelectAccountNavigationParameter) {
accountSelectionHandler = parameter.accountSelectionHandler
transactionConfirmationMessage = parameter.message
}

/**
Expand All @@ -86,16 +91,23 @@ class SelectAccountViewModel @Inject constructor(
* [Operation.CHANGE_PASSWORD] and [Operation.OUT_OF_BAND_AUTHENTICATION].
*
* @param operation The operation the account selected for.
* @param username The username assigned to the selected account.
* @param account The selected account.
*/
fun selectAccount(operation: Operation, username: String) {
fun selectAccount(operation: Operation, account: Account) {
try {
transactionConfirmationMessage?.let {
// Transaction confirmation data is received from the SDK
// Show it to the user for confirmation or cancellation
// The AccountSelectionHandler will be invoked or cancelled there.
return confirm(it, account)
}

when (operation) {
Operation.AUTHENTICATION -> inBandAuthentication(username)
Operation.DEREGISTRATION -> inBandAuthenticationForDeregistration(username)
Operation.CHANGE_PIN -> changePin(username)
Operation.CHANGE_PASSWORD -> changePassword(username)
Operation.OUT_OF_BAND_AUTHENTICATION -> outOfBandAuthentication(username)
Operation.AUTHENTICATION -> inBandAuthenticate(account)
Operation.DEREGISTRATION -> inBandAuthenticationForDeregistration(account)
Operation.CHANGE_PIN -> changePin(account)
Operation.CHANGE_PASSWORD -> changePassword(account)
Operation.OUT_OF_BAND_AUTHENTICATION -> outOfBandAuthentication(account)
else -> throw BusinessException.invalidState()
}
} catch (exception: Exception) {
Expand All @@ -108,12 +120,12 @@ class SelectAccountViewModel @Inject constructor(
/**
* Starts PIN change.
*
* @param username The username that identifies the account the PIN change must be started for.
* @param account The account the PIN change must be started for.
*/
private fun changePin(username: String) {
private fun changePin(account: Account) {
val client = clientProvider.get() ?: throw BusinessException.clientNotInitialized()
client.operations().pinChange()
.username(username)
.username(account.username())
.pinChanger(pinChanger)
.onSuccess {
navigationDispatcher.requestNavigation(
Expand All @@ -129,12 +141,12 @@ class SelectAccountViewModel @Inject constructor(
/**
* Starts Password change.
*
* @param username The username that identifies the account the Password change must be started for.
* @param account The account the Password change must be started for.
*/
private fun changePassword(username: String) {
private fun changePassword(account: Account) {
val client = clientProvider.get() ?: throw BusinessException.clientNotInitialized()
client.operations().passwordChange()
.username(username)
.username(account.username())
.passwordChanger(passwordChanger)
.onSuccess {
navigationDispatcher.requestNavigation(
Expand All @@ -147,15 +159,33 @@ class SelectAccountViewModel @Inject constructor(
.execute()
}

/**
* Confirms the transaction.
*
* @param message: The transaction confirmation message that need to be confirmed or cancelled by the user.
* @param account: The current account.
*/
private fun confirm(message: String, account: Account) {
navigationDispatcher.requestNavigation(
NavigationGraphDirections.actionGlobalTransactionConfirmationFragment(
TransactionConfirmationNavigationParameter(
account = account,
transactionConfirmationMessage = message,
accountSelectionHandler = accountSelectionHandler
)
)
)
}

/**
* Starts an in-band authentication.
*
* @param username The username that identifies the account the in-band authentication must be started for.
* @param account: The account that must be used to authenticate.
*/
private fun inBandAuthentication(username: String) {
private fun inBandAuthenticate(account: Account) {
val client = clientProvider.get() ?: throw BusinessException.clientNotInitialized()
client.operations().authentication()
.username(username)
.username(account.username())
.authenticatorSelector(authenticatorSelector)
.pinUserVerifier(pinUserVerifier)
.passwordUserVerifier(passwordUserVerifier)
Expand All @@ -176,10 +206,11 @@ class SelectAccountViewModel @Inject constructor(
/**
* Starts an in-band authentication as a pre-step for an identity suite environment de-registration.
*
* @param username The username that identifies the account the de-registration must be started for.
* @param account: The account to deregister.
*/
private fun inBandAuthenticationForDeregistration(username: String) {
private fun inBandAuthenticationForDeregistration(account: Account) {
val client = clientProvider.get() ?: throw BusinessException.clientNotInitialized()
val username = account.username()
client.operations().authentication()
.username(username)
.authenticatorSelector(authenticatorSelector)
Expand Down Expand Up @@ -209,12 +240,12 @@ class SelectAccountViewModel @Inject constructor(
/**
* Continues an out-of-band authentication.
*
* @param username The username that identifies the account the out-of-band authentication must be started for.
* @param account The account the out-of-band authentication must be started for.
*/
private fun outOfBandAuthentication(username: String) {
private fun outOfBandAuthentication(account: Account) {
val accountSelectionHandler =
this.accountSelectionHandler ?: throw BusinessException.invalidState()
accountSelectionHandler.username(username)
accountSelectionHandler.username(account.username())
this.accountSelectionHandler = null
}
//endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Nevis Mobile Authentication SDK Example App
*
* Copyright © 2023. Nevis Security AG. All rights reserved.
* Copyright © 2023-2024. Nevis Security AG. All rights reserved.
*/

package ch.nevis.exampleapp.ui.selectAccount.parameter
Expand Down Expand Up @@ -41,5 +41,10 @@ data class SelectAccountNavigationParameter(
* [SelectAccountNavigationParameter.operation] must be [Operation.OUT_OF_BAND_AUTHENTICATION].
*/
@IgnoredOnParcel
val accountSelectionHandler: AccountSelectionHandler? = null
val accountSelectionHandler: AccountSelectionHandler? = null,

/**
* The message to confirm if there is any.
*/
val message: String? = null,
) : NavigationParameter
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/**
* Nevis Mobile Authentication SDK Example App
*
* Copyright © 2023. Nevis Security AG. All rights reserved.
* Copyright © 2023-2024. Nevis Security AG. All rights reserved.
*/

package ch.nevis.exampleapp.ui.transactionConfirmation

import ch.nevis.exampleapp.NavigationGraphDirections
import ch.nevis.exampleapp.common.error.ErrorHandler
import ch.nevis.exampleapp.domain.model.error.BusinessException
import ch.nevis.exampleapp.domain.model.operation.Operation
Expand Down Expand Up @@ -41,16 +40,9 @@ class TransactionConfirmationViewModel @Inject constructor(

//region Properties
/**
* The operation the account selection was requested for.
*
* This value will always be [Operation.OUT_OF_BAND_AUTHENTICATION].
*/
private var operation: Operation? = null

/**
* The list of available accounts the user can select from.
* The previously selected account.
*/
private var accounts: Set<Account>? = null
private var account: Account? = null

/**
* An instance of an [AccountSelectionHandler]. Transaction confirmation data received only in case an out-of-band authentication is started
Expand All @@ -69,37 +61,23 @@ class TransactionConfirmationViewModel @Inject constructor(
* @param parameter The [TransactionConfirmationNavigationParameter] that was received by the owner [TransactionConfirmationFragment].
*/
fun updateViewModel(parameter: TransactionConfirmationNavigationParameter) {
this.operation = parameter.operation
this.accounts = parameter.accounts
this.account = parameter.account
this.accountSelectionHandler = parameter.accountSelectionHandler

requestViewUpdate(TransactionConfirmationViewData(parameter.transactionConfirmationData))
requestViewUpdate(TransactionConfirmationViewData(parameter.transactionConfirmationMessage))
}

/**
* Confirms the transaction, the operation will be continued.
*/
fun confirm() {
try {
val accounts = this.accounts ?: throw BusinessException.invalidState()
val operation = this.operation ?: throw BusinessException.invalidState()
val account = this.account ?: throw BusinessException.invalidState()
val accountSelectionHandler =
this.accountSelectionHandler ?: throw BusinessException.invalidState()

if (accounts.size == 1) {
accountSelectionHandler.username(accounts.first().username())
this.accountSelectionHandler = null
} else {
navigationDispatcher.requestNavigation(
NavigationGraphDirections.actionGlobalSelectAccountFragment(
SelectAccountNavigationParameter(
operation,
accounts,
accountSelectionHandler
)
)
)
}
accountSelectionHandler.username(account.username())
this.accountSelectionHandler = null
} catch (exception: Exception) {
errorHandler.handle(exception)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Nevis Mobile Authentication SDK Example App
*
* Copyright © 2023. Nevis Security AG. All rights reserved.
* Copyright © 2023-2024. Nevis Security AG. All rights reserved.
*/

package ch.nevis.exampleapp.ui.transactionConfirmation.parameter
Expand All @@ -23,32 +23,24 @@ import kotlinx.parcelize.Parcelize
* multiple operations.
*
* @constructor Creates a new instance.
* @param operation The operation the account selection was requested for.
* @param accounts The list of available accounts the user can select from.
* @param transactionConfirmationData The transaction confirmation data/message that should be displayed
* @param account The previously selected account.
* @param transactionConfirmationMessage The transaction confirmation data/message that should be displayed
* on Transaction Confirmation view.
* @param accountSelectionHandler An instance of an [AccountSelectionHandler] implementation.
*/
@Parcelize
data class TransactionConfirmationNavigationParameter(

/**
* The operation the account selection was requested for.
*
* This value will always be [Operation.OUT_OF_BAND_AUTHENTICATION].
*/
val operation: Operation,

/**
* The list of available accounts the user can select from.
* The previously selected account.
*/
@IgnoredOnParcel
val accounts: Set<Account>? = null,
val account: Account? = null,

/**
* The transaction confirmation data/message that should be displayed on Transaction Confirmation view.
*/
val transactionConfirmationData: String,
val transactionConfirmationMessage: String,

/**
* An instance of an [AccountSelectionHandler]. Transaction confirmation data received only in case an out-of-band authentication is started
Expand Down

0 comments on commit 5ce9ceb

Please sign in to comment.