From b5214356c2f8ce4662323a6325ec3bbb4ce75d53 Mon Sep 17 00:00:00 2001 From: Tamas Toth Date: Tue, 14 Nov 2023 09:29:34 +0100 Subject: [PATCH] NEVISACCESSAPP-5283: Delete local data --- README.md | 9 ++-- .../coroutines/dagger/ApplicationModule.kt | 4 ++ .../domain/model/operation/Operation.kt | 8 +++- .../model/response/SelectAccountResponse.kt | 2 +- .../usecase/DeleteAuthenticatorsUseCase.kt | 26 ++++++++++ .../DeleteAuthenticatorsUseCaseImpl.kt | 48 +++++++++++++++++++ .../coroutines/ui/home/HomeFragment.kt | 4 ++ .../coroutines/ui/home/HomeViewModel.kt | 28 +++++++++++ app/src/main/res/layout/fragment_home.xml | 10 ++++ app/src/main/res/values/strings.xml | 6 ++- fastlane/Fastfile | 2 +- 11 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCase.kt create mode 100644 app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCaseImpl.kt diff --git a/README.md b/README.md index 74bcb62..2d2042f 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,7 @@ Clone the example application GitHub repository and open it with Android Studio. The example applications support two kinds of configuration: `authenticationCloud` and `identitySuite`. The following chapters describes how to change the base configuration to match your environment. The configuration could be changed by modifying the [ApplicationModule](app/src/main/java/ch/nevis/exampleapp/coroutines/dagger/ApplicationModule.kt) file which describes the dependency injection related configuration using the [Dagger Hilt](https://dagger.dev/hilt/) library and the [AndroidManifest.xml](app/src/main/AndroidManifest.xml). -> **_NOTE_** -> +> [!NOTE] > Only _build-time_ configuration change is supported. #### Authentication Cloud Configuration @@ -126,8 +125,7 @@ Change the `myaccessapp` scheme value in the following `intent-filter` with the ``` -> **_NOTE_**: -> +> [!NOTE] > For more information about deep links, web links visit the official [Android guide](https://developer.android.com/training/app-links). #### Facet ID @@ -154,8 +152,7 @@ object FacetIdCalculator { } ``` -> **_NOTE_:** -> +> [!NOTE] > An alternative way to set a constant Facet ID is to call `facetId(String facetId)` method of `ch.nevis.mobile.sdk.api.Configuration.Builder` in methods `provideAuthenticationCloudConfiguration` and `provideIdentitySuiteConfiguration` in [ApplicationModule](app/src/main/java/ch/nevis/exampleapp/coroutines/dagger/ApplicationModule.kt) file. > > The value of the facet ID depends on the certificate used to build the application, which can change during the development, that is why this method has been introduced: by providing a constant facet ID and having diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/dagger/ApplicationModule.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/dagger/ApplicationModule.kt index beb16da..124ce89 100644 --- a/app/src/main/java/ch/nevis/exampleapp/coroutines/dagger/ApplicationModule.kt +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/dagger/ApplicationModule.kt @@ -285,6 +285,10 @@ class ApplicationModule { fun provideGetAuthenticatorsUseCase(clientProvider: ClientProvider): GetAuthenticatorsUseCase = GetAuthenticatorsUseCaseImpl(clientProvider) + @Provides + fun provideDeleteAuthenticatorsUseCase(clientProvider: ClientProvider): DeleteAuthenticatorsUseCase = + DeleteAuthenticatorsUseCaseImpl(clientProvider) + @Provides fun provideCreateDeviceInformationUseCase(@ApplicationContext context: Context): CreateDeviceInformationUseCase = CreateDeviceInformationUseCaseImpl(context) diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/operation/Operation.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/operation/Operation.kt index 0bda1bb..7cba17e 100644 --- a/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/operation/Operation.kt +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/operation/Operation.kt @@ -63,5 +63,11 @@ enum class Operation( /** * Process out-of-band payload */ - PROCESS_OUT_OF_BAND_PAYLOAD(R.string.operation_process_oob_payload) + PROCESS_OUT_OF_BAND_PAYLOAD(R.string.operation_process_oob_payload), + + /** + * Local Data + */ + LOCAL_DATA(R.string.operation_local_data) + } \ No newline at end of file diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/response/SelectAccountResponse.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/response/SelectAccountResponse.kt index 93b8b49..c679ee6 100644 --- a/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/response/SelectAccountResponse.kt +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/model/response/SelectAccountResponse.kt @@ -23,7 +23,7 @@ class SelectAccountResponse( val operation: Operation, /** - * The set of available authenticators the user has to choose from. + * The set of enrolled accounts the user has to choose from. */ val accounts: Set, diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCase.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCase.kt new file mode 100644 index 0000000..4eba12a --- /dev/null +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCase.kt @@ -0,0 +1,26 @@ +/** + * Nevis Mobile Authentication SDK Example App + * + * Copyright © 2023. Nevis Security AG. All rights reserved. + */ + +package ch.nevis.exampleapp.coroutines.domain.usecase + +import ch.nevis.exampleapp.coroutines.domain.model.response.Response +import ch.nevis.mobile.sdk.api.localdata.Account + +/** + * Interface declaration of delete local authenticators use-case. + */ +interface DeleteAuthenticatorsUseCase { + + /** + * Executes use-case. + * + * @param accounts The set of enrolled accounts. + * @return A [Response] object, the result of the use-case execution. + */ + suspend fun execute( + accounts: Set + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCaseImpl.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCaseImpl.kt new file mode 100644 index 0000000..09a86c1 --- /dev/null +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/DeleteAuthenticatorsUseCaseImpl.kt @@ -0,0 +1,48 @@ +/** + * Nevis Mobile Authentication SDK Example App + * + * Copyright © 2023. Nevis Security AG. All rights reserved. + */ + +package ch.nevis.exampleapp.coroutines.domain.usecase + +import ch.nevis.exampleapp.coroutines.domain.client.ClientProvider +import ch.nevis.exampleapp.coroutines.domain.model.error.BusinessException +import ch.nevis.exampleapp.coroutines.domain.model.operation.Operation +import ch.nevis.exampleapp.coroutines.domain.model.response.CompletedResponse +import ch.nevis.exampleapp.coroutines.domain.model.response.Response +import ch.nevis.mobile.sdk.api.localdata.Account +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +class DeleteAuthenticatorsUseCaseImpl( + /** + * An instance of a [ClientProvider] implementation. + */ + private val clientProvider: ClientProvider +) : DeleteAuthenticatorsUseCase { + //region DeleteAuthenticatorsUseCase + override suspend fun execute(accounts: Set): Response { + for (account in accounts) { + deleteAuthenticators(account.username()) + } + + return CompletedResponse(Operation.LOCAL_DATA) + } + //endregion + + //region Private Interface + /** + * Deletes all local authenticators of an enrolled account. + * + * @param username The username that identifies the account whose authenticators must be deleted locally. + */ + private suspend fun deleteAuthenticators(username: String) { + return suspendCancellableCoroutine { cancellableContinuation -> + val client = clientProvider.get() ?: throw BusinessException.clientNotInitialized() + client.localData().deleteAuthenticator(username) + cancellableContinuation.resume(Unit) + } + } + //endregion +} \ No newline at end of file diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeFragment.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeFragment.kt index 9fd330a..fa05076 100644 --- a/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeFragment.kt +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeFragment.kt @@ -84,6 +84,10 @@ class HomeFragment : ResponseObserverFragment() { navController.navigate(action) } + binding.deleteAuthenticatorsButton.setOnClickListener { + viewModel.deleteAuthenticators() + } + binding.inBandRegistrationButton.setOnClickListener { val action = NavigationGraphDirections.actionGlobalLegacyLoginFragment() navController.navigate(action) diff --git a/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeViewModel.kt b/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeViewModel.kt index 98c2d4d..47b1923 100644 --- a/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeViewModel.kt +++ b/app/src/main/java/ch/nevis/exampleapp/coroutines/ui/home/HomeViewModel.kt @@ -12,6 +12,7 @@ import ch.nevis.exampleapp.coroutines.common.configuration.Environment import ch.nevis.exampleapp.coroutines.domain.model.error.BusinessException import ch.nevis.exampleapp.coroutines.domain.model.operation.Operation import ch.nevis.exampleapp.coroutines.domain.model.response.* +import ch.nevis.exampleapp.coroutines.domain.usecase.DeleteAuthenticatorsUseCase import ch.nevis.exampleapp.coroutines.domain.usecase.GetAccountsUseCase import ch.nevis.exampleapp.coroutines.domain.usecase.GetAuthenticatorsUseCase import ch.nevis.exampleapp.coroutines.domain.usecase.InitializeClientUseCase @@ -19,7 +20,9 @@ import ch.nevis.exampleapp.coroutines.ui.base.OutOfBandOperationViewModel import ch.nevis.mobile.sdk.api.localdata.Account import ch.nevis.mobile.sdk.api.localdata.Authenticator import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject /** @@ -46,6 +49,11 @@ class HomeViewModel @Inject constructor( * An instance of a [GetAuthenticatorsUseCase] implementation. */ private val getAuthenticatorsUseCase: GetAuthenticatorsUseCase, + + /** + * An instance of a [DeleteAuthenticatorsUseCase] implementation. + */ + private val deleteAuthenticatorsUseCase: DeleteAuthenticatorsUseCase, ) : OutOfBandOperationViewModel() { //region Public Interface @@ -152,5 +160,25 @@ class HomeViewModel @Inject constructor( } } } + + /** + * Starts deleting local authenticators of all registered users. + */ + fun deleteAuthenticators() { + viewModelScope.launch(errorHandler) { + var response = getAccountsUseCase.execute() + if (response is GetAccountsResponse) { + val accounts = response.accounts + response = if (accounts.isNotEmpty()) { + withContext(Dispatchers.IO) { + deleteAuthenticatorsUseCase.execute(accounts) + } + } else { + ErrorResponse(BusinessException.accountsNotFound()) + } + } + mutableResponseLiveData.postValue(response) + } + } //endregion } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index b534cda..29acf58 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -93,6 +93,16 @@ android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:text="@string/home_auth_cloud_registration_button_title" + app:layout_constraintBottom_toTopOf="@id/deleteAuthenticatorsButton" + app:layout_constraintEnd_toEndOf="@id/rightVerticalGuideline" + app:layout_constraintStart_toStartOf="@id/leftVerticalGuideline" /> + +