Skip to content

Commit

Permalink
Merge pull request #20 from nevissecurity/feature/NEVISACCESSAPP-5666-…
Browse files Browse the repository at this point in the history
…Password-authenticator

NEVISACCESSAPP-5666: Password authenticator and policy
  • Loading branch information
tamas-toth authored Aug 5, 2024
2 parents acf677c + 2d41939 commit f5ccc4f
Show file tree
Hide file tree
Showing 53 changed files with 2,207 additions and 636 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ For Kotlin coroutines, suspend functions, SDK calls have to be wrapped into a [s

The example application wraps the SDK calls in the use-case implementations in their `suspend fun execute(...)` functions. It runs the SDK call inside a `suspendCancellableCoroutine` block and caches the received `cancellableContinuation` object. When a interaction callback is called by the SDK the `cancellableContinuation` will be get from the cache and it will be resumed to resume the suspended block/function.

As an example check the [InBandAuthenticationUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/InBandAuthenticationUseCaseImpl.kt) and the [AuthenticationAuthenticatorSelectorImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/interaction/AuthenticationAuthenticatorSelectorImpl.kt).
As an example check the [InBandAuthenticationUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/InBandAuthenticationUseCaseImpl.kt) and the [AuthenticatorSelectorImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/interaction/AuthenticatorSelectorImpl.kt) classes.

## Integration Notes

Expand Down Expand Up @@ -272,6 +272,10 @@ The [DeregisterUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/dom

The change PIN operation is implemented in the [StartChangePinUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/StartChangePinUseCaseImpl.kt) and [ChangePinUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/ChangePinUseCaseImpl.kt) classes with which you can modify the PIN of a registered PIN authenticator for a given user.

#### Change Password

The change Password operation is implemented in the [StartChangePasswordUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/StartChangePasswordUseCaseImpl.kt) and [ChangePasswordUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/ChangePasswordUseCaseImpl.kt) classes with which you can modify the password of a registered Password authenticator for a given user.

#### Change device information

During registration, the device information can be provided that contains the name identifying your device, and also the Firebase Cloud Messaging registration token. Updating both the name and the token is implemented in the [ChangeDeviceInformationUseCaseImpl](app/src/main/java/ch/nevis/exampleapp/coroutines/domain/usecase/ChangeDeviceInformationUseCaseImpl.kt) class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,38 @@ import ch.nevis.exampleapp.coroutines.data.repository.OperationStateRepositoryIm
import ch.nevis.exampleapp.coroutines.domain.client.ClientProvider
import ch.nevis.exampleapp.coroutines.domain.client.ClientProviderImpl
import ch.nevis.exampleapp.coroutines.domain.interaction.*
import ch.nevis.exampleapp.coroutines.domain.interaction.password.PasswordChangerImpl
import ch.nevis.exampleapp.coroutines.domain.interaction.password.PasswordEnrollerImpl
import ch.nevis.exampleapp.coroutines.domain.interaction.password.PasswordUserVerifierImpl
import ch.nevis.exampleapp.coroutines.domain.interaction.pin.PinChangerImpl
import ch.nevis.exampleapp.coroutines.domain.interaction.pin.PinEnrollerImpl
import ch.nevis.exampleapp.coroutines.domain.interaction.pin.PinUserVerifierImpl
import ch.nevis.exampleapp.coroutines.domain.log.SdkLogger
import ch.nevis.exampleapp.coroutines.domain.log.SdkLoggerImpl
import ch.nevis.exampleapp.coroutines.domain.model.operation.Operation
import ch.nevis.exampleapp.coroutines.domain.model.state.ChangePasswordOperationState
import ch.nevis.exampleapp.coroutines.domain.model.state.ChangePinOperationState
import ch.nevis.exampleapp.coroutines.domain.model.state.UserInteractionOperationState
import ch.nevis.exampleapp.coroutines.domain.repository.LoginRepository
import ch.nevis.exampleapp.coroutines.domain.repository.OperationStateRepository
import ch.nevis.exampleapp.coroutines.domain.usecase.*
import ch.nevis.exampleapp.coroutines.domain.validation.AuthenticatorValidator
import ch.nevis.exampleapp.coroutines.domain.validation.AuthenticatorValidatorImpl
import ch.nevis.exampleapp.coroutines.domain.validation.PasswordPolicyImpl
import ch.nevis.mobile.sdk.api.Configuration
import ch.nevis.mobile.sdk.api.authorization.AuthorizationProvider
import ch.nevis.mobile.sdk.api.localdata.Authenticator.BIOMETRIC_AUTHENTICATOR_AAID
import ch.nevis.mobile.sdk.api.localdata.Authenticator.DEVICE_PASSCODE_AUTHENTICATOR_AAID
import ch.nevis.mobile.sdk.api.localdata.Authenticator.FINGERPRINT_AUTHENTICATOR_AAID
import ch.nevis.mobile.sdk.api.localdata.Authenticator.PASSWORD_AUTHENTICATOR_AAID
import ch.nevis.mobile.sdk.api.localdata.Authenticator.PIN_AUTHENTICATOR_AAID
import ch.nevis.mobile.sdk.api.operation.AuthenticationError
import ch.nevis.mobile.sdk.api.operation.OperationError
import ch.nevis.mobile.sdk.api.operation.authcloudapi.AuthCloudApiError
import ch.nevis.mobile.sdk.api.operation.password.PasswordChangeError
import ch.nevis.mobile.sdk.api.operation.password.PasswordChanger
import ch.nevis.mobile.sdk.api.operation.password.PasswordEnroller
import ch.nevis.mobile.sdk.api.operation.password.PasswordPolicy
import ch.nevis.mobile.sdk.api.operation.pin.PinChangeError
import ch.nevis.mobile.sdk.api.operation.pin.PinChanger
import ch.nevis.mobile.sdk.api.operation.pin.PinEnroller
Expand All @@ -53,6 +66,7 @@ import ch.nevis.mobile.sdk.api.operation.selection.AuthenticatorSelector
import ch.nevis.mobile.sdk.api.operation.userverification.BiometricUserVerifier
import ch.nevis.mobile.sdk.api.operation.userverification.DevicePasscodeUserVerifier
import ch.nevis.mobile.sdk.api.operation.userverification.FingerprintUserVerifier
import ch.nevis.mobile.sdk.api.operation.userverification.PasswordUserVerifier
import ch.nevis.mobile.sdk.api.operation.userverification.PinUserVerifier
import ch.nevis.mobile.sdk.api.util.Consumer
import dagger.Module
Expand Down Expand Up @@ -143,6 +157,7 @@ class ApplicationModule {
@Provides
fun provideAuthenticatorAllowlist(): List<String> = listOf(
PIN_AUTHENTICATOR_AAID,
PASSWORD_AUTHENTICATOR_AAID,
FINGERPRINT_AUTHENTICATOR_AAID,
BIOMETRIC_AUTHENTICATOR_AAID,
DEVICE_PASSCODE_AUTHENTICATOR_AAID
Expand Down Expand Up @@ -186,6 +201,10 @@ class ApplicationModule {
@Provides
@Singleton
fun provideAuthenticatorValidator(): AuthenticatorValidator = AuthenticatorValidatorImpl()

@Provides
fun providePasswordPolicy(@ApplicationContext context: Context): PasswordPolicy =
PasswordPolicyImpl(context)
//endregion

//region Data Sources
Expand All @@ -200,6 +219,10 @@ class ApplicationModule {
fun provideChangePinOperationStateCache(): Cache<ChangePinOperationState> =
CacheImpl()

@Provides
fun provideChangePasswordOperationStateCache(): Cache<ChangePasswordOperationState> =
CacheImpl()

@Provides
fun provideUserInteractionOperationStateCache(): Cache<UserInteractionOperationState> =
CacheImpl()
Expand All @@ -211,6 +234,11 @@ class ApplicationModule {
fun provideChangePinOperationStateRepository(cache: Cache<ChangePinOperationState>): OperationStateRepository<ChangePinOperationState> =
OperationStateRepositoryImpl(cache)

@Provides
@Singleton
fun provideChangePasswordOperationStateRepository(cache: Cache<ChangePasswordOperationState>): OperationStateRepository<ChangePasswordOperationState> =
OperationStateRepositoryImpl(cache)

@Provides
@Singleton
fun provideUserInteractionOperationStateRepository(cache: Cache<UserInteractionOperationState>): OperationStateRepository<UserInteractionOperationState> =
Expand Down Expand Up @@ -270,6 +298,24 @@ class ApplicationModule {
fun providePinEnroller(stateRepository: OperationStateRepository<UserInteractionOperationState>): PinEnroller =
PinEnrollerImpl(stateRepository)

@Provides
fun providePasswordChanger(
passwordPolicy: PasswordPolicy,
stateRepository: OperationStateRepository<ChangePasswordOperationState>
): PasswordChanger =
PasswordChangerImpl(passwordPolicy, stateRepository)

@Provides
fun providePasswordEnroller(
passwordPolicy: PasswordPolicy,
stateRepository: OperationStateRepository<UserInteractionOperationState>
): PasswordEnroller =
PasswordEnrollerImpl(passwordPolicy, stateRepository)

@Provides
fun providePasswordUserVerifier(stateRepository: OperationStateRepository<UserInteractionOperationState>): PasswordUserVerifier =
PasswordUserVerifierImpl(stateRepository)

@Provides
fun provideFingerprintUserVerifier(stateRepository: OperationStateRepository<UserInteractionOperationState>): FingerprintUserVerifier =
FingerprintUserVerifierImpl(stateRepository)
Expand Down Expand Up @@ -298,6 +344,10 @@ class ApplicationModule {
fun provideOnSuccessForChangePinOperation(stateRepository: OperationStateRepository<ChangePinOperationState>): Runnable =
OnSuccessImpl(stateRepository)

@Provides
fun provideOnSuccessForChangePasswordOperation(stateRepository: OperationStateRepository<ChangePasswordOperationState>): Runnable =
OnSuccessImpl(stateRepository)

@Provides
fun provideOnErrorForUserInteractionOperation(
stateRepository: OperationStateRepository<UserInteractionOperationState>,
Expand All @@ -310,6 +360,12 @@ class ApplicationModule {
errorHandlerChain: ErrorHandlerChain
): Consumer<PinChangeError> = OnErrorImpl(stateRepository, errorHandlerChain)

@Provides
fun provideOnErrorForChangePasswordOperation(
stateRepository: OperationStateRepository<ChangePasswordOperationState>,
errorHandlerChain: ErrorHandlerChain
): Consumer<PasswordChangeError> = OnErrorImpl(stateRepository, errorHandlerChain)

@Provides
fun provideOnAuthCloudApiError(
stateRepository: OperationStateRepository<UserInteractionOperationState>,
Expand Down Expand Up @@ -368,6 +424,7 @@ class ApplicationModule {
@Named(AUTHENTICATION_AUTHENTICATOR_SELECTOR)
authenticatorSelector: AuthenticatorSelector,
pinUserVerifier: PinUserVerifier,
passwordUserVerifier: PasswordUserVerifier,
fingerprintUserVerifier: FingerprintUserVerifier,
biometricUserVerifier: BiometricUserVerifier,
devicePasscodeUserVerifier: DevicePasscodeUserVerifier,
Expand All @@ -377,6 +434,7 @@ class ApplicationModule {
stateRepository,
authenticatorSelector,
pinUserVerifier,
passwordUserVerifier,
fingerprintUserVerifier,
biometricUserVerifier,
devicePasscodeUserVerifier,
Expand All @@ -392,6 +450,7 @@ class ApplicationModule {
@Named(AUTHENTICATION_AUTHENTICATOR_SELECTOR)
authenticatorSelector: AuthenticatorSelector,
pinUserVerifier: PinUserVerifier,
passwordUserVerifier: PasswordUserVerifier,
fingerprintUserVerifier: FingerprintUserVerifier,
biometricUserVerifier: BiometricUserVerifier,
devicePasscodeUserVerifier: DevicePasscodeUserVerifier,
Expand All @@ -401,6 +460,7 @@ class ApplicationModule {
stateRepository,
authenticatorSelector,
pinUserVerifier,
passwordUserVerifier,
fingerprintUserVerifier,
biometricUserVerifier,
devicePasscodeUserVerifier,
Expand Down Expand Up @@ -442,6 +502,32 @@ class ApplicationModule {
fun provideVerifyPinUseCase(stateRepository: OperationStateRepository<UserInteractionOperationState>): VerifyPinUseCase =
VerifyPinUseCaseImpl(stateRepository)

@Provides
fun provideSetPasswordUseCase(stateRepository: OperationStateRepository<UserInteractionOperationState>): SetPasswordUseCase =
SetPasswordUseCaseImpl(stateRepository)

@Provides
fun provideStartChangePasswordUseCase(
clientProvider: ClientProvider,
stateRepository: OperationStateRepository<ChangePasswordOperationState>,
passwordChanger: PasswordChanger,
onError: Consumer<PasswordChangeError>
): StartChangePasswordUseCase = StartChangePasswordUseCaseImpl(
clientProvider,
stateRepository,
passwordChanger,
provideOnSuccessForChangePasswordOperation(stateRepository),
onError
)

@Provides
fun provideChangePasswordUseCase(stateRepository: OperationStateRepository<ChangePasswordOperationState>): ChangePasswordUseCase =
ChangePasswordUseCaseImpl(stateRepository)

@Provides
fun provideVerifyPasswordUseCase(stateRepository: OperationStateRepository<UserInteractionOperationState>): VerifyPasswordUseCase =
VerifyPasswordUseCaseImpl(stateRepository)

@Provides
fun provideVerifyFingerprintUseCase(stateRepository: OperationStateRepository<UserInteractionOperationState>): VerifyFingerprintUseCase =
VerifyFingerprintUseCaseImpl(stateRepository)
Expand All @@ -465,7 +551,9 @@ class ApplicationModule {
@Named(AUTHENTICATION_AUTHENTICATOR_SELECTOR)
authenticationAuthenticatorSelector: AuthenticatorSelector,
pinEnroller: PinEnroller,
passwordEnroller: PasswordEnroller,
pinUserVerifier: PinUserVerifier,
passwordUserVerifier: PasswordUserVerifier,
fingerprintUserVerifier: FingerprintUserVerifier,
biometricUserVerifier: BiometricUserVerifier,
devicePasscodeUserVerifier: DevicePasscodeUserVerifier,
Expand All @@ -478,7 +566,9 @@ class ApplicationModule {
registrationAuthenticatorSelector,
authenticationAuthenticatorSelector,
pinEnroller,
passwordEnroller,
pinUserVerifier,
passwordUserVerifier,
fingerprintUserVerifier,
biometricUserVerifier,
devicePasscodeUserVerifier,
Expand All @@ -505,6 +595,7 @@ class ApplicationModule {
@Named(REGISTRATION_AUTHENTICATOR_SELECTOR)
authenticatorSelector: AuthenticatorSelector,
pinEnroller: PinEnroller,
passwordEnroller: PasswordEnroller,
fingerprintUserVerifier: FingerprintUserVerifier,
biometricUserVerifier: BiometricUserVerifier,
devicePasscodeUserVerifier: DevicePasscodeUserVerifier,
Expand All @@ -515,6 +606,7 @@ class ApplicationModule {
createDeviceInformationUseCase,
authenticatorSelector,
pinEnroller,
passwordEnroller,
fingerprintUserVerifier,
biometricUserVerifier,
devicePasscodeUserVerifier,
Expand All @@ -534,6 +626,7 @@ class ApplicationModule {
@Named(REGISTRATION_AUTHENTICATOR_SELECTOR)
authenticatorSelector: AuthenticatorSelector,
pinEnroller: PinEnroller,
passwordEnroller: PasswordEnroller,
fingerprintUserVerifier: FingerprintUserVerifier,
biometricUserVerifier: BiometricUserVerifier,
devicePasscodeUserVerifier: DevicePasscodeUserVerifier,
Expand All @@ -544,6 +637,7 @@ class ApplicationModule {
createDeviceInformationUseCase,
authenticatorSelector,
pinEnroller,
passwordEnroller,
fingerprintUserVerifier,
biometricUserVerifier,
devicePasscodeUserVerifier,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Nevis Mobile Authentication SDK Example App
*
* Copyright © 2024. Nevis Security AG. All rights reserved.
*/

package ch.nevis.exampleapp.coroutines.domain.interaction.password

import ch.nevis.exampleapp.coroutines.domain.model.error.BusinessException
import ch.nevis.exampleapp.coroutines.domain.model.response.ChangePasswordResponse
import ch.nevis.exampleapp.coroutines.domain.model.state.ChangePasswordOperationState
import ch.nevis.exampleapp.coroutines.domain.repository.OperationStateRepository
import ch.nevis.exampleapp.coroutines.timber.sdk
import ch.nevis.mobile.sdk.api.operation.password.PasswordChangeContext
import ch.nevis.mobile.sdk.api.operation.password.PasswordChangeHandler
import ch.nevis.mobile.sdk.api.operation.password.PasswordChanger
import ch.nevis.mobile.sdk.api.operation.password.PasswordPolicy
import timber.log.Timber
import kotlin.coroutines.resume

/**
* Default implementation of [PasswordChanger] interface. It stores the password change step context
* into its state and resumes the cancellableContinuation found in state with [ChangePasswordResponse]
* indicating that the running operation waiting for a new password.
*
* @constructor Creates a new instance.
* @param policy An instance of a [PasswordPolicy] interface implementation.
* @param stateRepository The state repository that stores the state of the running operation.
*/
class PasswordChangerImpl(
private val policy: PasswordPolicy,
private val stateRepository: OperationStateRepository<ChangePasswordOperationState>
) : PasswordChanger {

//region PasswordChanger
/** @suppress */
override fun changePassword(
context: PasswordChangeContext,
handler: PasswordChangeHandler
) {
if (context.lastRecoverableError().isPresent) {
Timber.asTree().sdk("Password change failed. Please try again.")
} else {
Timber.asTree().sdk("Please start Password change.")
}

val operationState = stateRepository.get() ?: throw BusinessException.invalidState()
operationState.passwordChangeHandler = handler

val cancellableContinuation =
operationState.cancellableContinuation ?: throw BusinessException.invalidState()

cancellableContinuation.resume(
ChangePasswordResponse(
context.authenticatorProtectionStatus(),
context.lastRecoverableError().orElse(null)
)
)
}

// You can add custom password policy by overriding the `passwordPolicy` getter
/** @suppress */
override fun passwordPolicy(): PasswordPolicy = policy
//endregion
}
Loading

0 comments on commit f5ccc4f

Please sign in to comment.