From cb81805dac6b658ea5f1bc876d1c7b31bbb831a9 Mon Sep 17 00:00:00 2001 From: gpanshu <91897496+gpanshu@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:36:24 -0500 Subject: [PATCH] fix(auth): Fix for checking current preferred type before setting the MFAType as enabled to ensure the current preference is not cleared ou (#2580) --- .../cognito/CognitoAuthExceptionConverter.kt | 2 +- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 100 ++- .../auth/cognito/UserMFAPreference.kt | 5 +- .../service/InvalidParameterException.kt | 4 +- .../auth/cognito/helpers/AuthHelper.kt | 4 +- .../cognito/RealAWSCognitoAuthPluginTest.kt | 794 +++++++++++++++++- 6 files changed, 849 insertions(+), 60 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt index 14c94d8415..2081ffc563 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt @@ -69,7 +69,7 @@ internal class CognitoAuthExceptionConverter { is InvalidPasswordException -> com.amplifyframework.auth.cognito.exceptions.service.InvalidPasswordException(error) is InvalidParameterException -> - com.amplifyframework.auth.cognito.exceptions.service.InvalidParameterException(error) + com.amplifyframework.auth.cognito.exceptions.service.InvalidParameterException(cause = error) is ExpiredCodeException -> CodeExpiredException(error) is CodeMismatchException -> com.amplifyframework.auth.cognito.exceptions.service.CodeMismatchException( error diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 2b8c6adb50..55f1adfdba 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -64,6 +64,7 @@ import com.amplifyframework.auth.cognito.exceptions.invalidstate.SignedInExcepti import com.amplifyframework.auth.cognito.exceptions.service.CodeDeliveryFailureException import com.amplifyframework.auth.cognito.exceptions.service.HostedUISignOutException import com.amplifyframework.auth.cognito.exceptions.service.InvalidAccountTypeException +import com.amplifyframework.auth.cognito.exceptions.service.InvalidParameterException import com.amplifyframework.auth.cognito.exceptions.service.UserCancelledException import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.HostedUIHelper @@ -2194,7 +2195,7 @@ internal class RealAWSCognitoAuthPlugin( var enabledSet: MutableSet? = null var preferred: MFAType? = null if (!response.userMfaSettingList.isNullOrEmpty()) { - enabledSet = mutableSetOf() + enabledSet = mutableSetOf() response.userMfaSettingList?.forEach { mfaType -> enabledSet.add(getMFAType(mfaType)) } @@ -2227,45 +2228,76 @@ internal class RealAWSCognitoAuthPlugin( onSuccess: Action, onError: Consumer ) { - authStateMachine.getCurrentState { authState -> - when (authState.authNState) { - is AuthenticationState.SignedIn -> { - GlobalScope.launch { - try { - val accessToken = getSession().userPoolTokensResult.value?.accessToken - accessToken?.let { token -> - authEnvironment.cognitoAuthService.cognitoIdentityProviderClient?.setUserMfaPreference { - this.accessToken = token - this.smsMfaSettings = sms?.let { - SmsMfaSettingsType.invoke { - enabled = it.mfaEnabled - it.mfaPreferred ?.let { preferred -> preferredMfa = preferred } - } - } - this.softwareTokenMfaSettings = totp?.let { - SoftwareTokenMfaSettingsType.invoke { - enabled = it.mfaEnabled - it.mfaPreferred ?.let { preferred -> preferredMfa = preferred } + if (sms == null && totp == null) { + onError.accept(InvalidParameterException("No mfa settings given")) + return + } + // If either of the params have preferred setting set then ignore fetched preference preferred property + val overridePreferredSetting: Boolean = !(sms?.mfaPreferred == true || totp?.mfaPreferred == true) + fetchMFAPreference({ userPreference -> + authStateMachine.getCurrentState { authState -> + when (authState.authNState) { + is AuthenticationState.SignedIn -> { + GlobalScope.launch { + try { + val accessToken = getSession().userPoolTokensResult.value?.accessToken + accessToken?.let { token -> + authEnvironment + .cognitoAuthService + .cognitoIdentityProviderClient + ?.setUserMfaPreference { + this.accessToken = token + this.smsMfaSettings = sms?.let { it -> + val preferredMFASetting = it.mfaPreferred + ?: ( + overridePreferredSetting && + userPreference.preferred == MFAType.SMS && + it.mfaEnabled + ) + SmsMfaSettingsType.invoke { + enabled = it.mfaEnabled + preferredMfa = preferredMFASetting + } + } + this.softwareTokenMfaSettings = totp?.let { it -> + val preferredMFASetting = it.mfaPreferred + ?: ( + overridePreferredSetting && + userPreference.preferred == MFAType.TOTP && + it.mfaEnabled + ) + SoftwareTokenMfaSettingsType.invoke { + enabled = it.mfaEnabled + preferredMfa = preferredMFASetting + } + } + }?.also { + onSuccess.call() } - } - }?.also { - onSuccess.call() - } - } ?: onError.accept(SignedOutException()) - } catch (error: Exception) { - onError.accept( - CognitoAuthExceptionConverter.lookup( - error, - "Amazon Cognito cannot update the MFA preferences" + } ?: onError.accept(SignedOutException()) + } catch (error: Exception) { + onError.accept( + CognitoAuthExceptionConverter.lookup( + error, + "Amazon Cognito cannot update the MFA preferences" + ) ) - ) + } } } + else -> onError.accept(InvalidStateException()) } - - else -> onError.accept(InvalidStateException()) } - } + }, { + onError.accept( + AuthException( + message = "Failed to fetch current MFA preferences " + + "which is a pre-requisite to update MFA preferences", + recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION, + cause = it + ) + ) + }) } private fun verifyTotp( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt index 8caac44a83..1f4700d457 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt @@ -30,7 +30,10 @@ data class UserMFAPreference( /** * Input for updating the MFA preference for a MFA Type */ -enum class MFAPreference(internal val mfaEnabled: Boolean, internal val mfaPreferred: Boolean? = null) { +enum class MFAPreference( + internal val mfaEnabled: Boolean, + internal val mfaPreferred: Boolean? = null +) { /** * MFA not enabled */ diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/InvalidParameterException.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/InvalidParameterException.kt index 74c6165227..810b9fb78f 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/InvalidParameterException.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/InvalidParameterException.kt @@ -20,5 +20,5 @@ import com.amplifyframework.auth.exceptions.ServiceException * Could not perform the action because there are incorrect parameters. * @param cause The underlying cause of this exception */ -open class InvalidParameterException(cause: Throwable?) : - ServiceException("One or more parameters are incorrect.", "Enter correct parameters.", cause) +open class InvalidParameterException(message: String? = null, cause: Throwable? = null) : + ServiceException(message ?: "One or more parameters are incorrect.", "Enter correct parameters.", cause) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt index 36b0c60a83..231014706d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt @@ -36,10 +36,10 @@ internal open class AuthHelper { fun getSecretHash(userId: String?, clientId: String?, clientSecret: String?): String? { return when { userId == null -> throw InvalidParameterException( - Exception("user ID cannot be null") + cause = Exception("user ID cannot be null") ) clientId == null -> throw InvalidParameterException( - Exception("client ID cannot be null") + cause = Exception("client ID cannot be null") ) clientSecret.isNullOrEmpty() -> null else -> diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt index 1f73082a4f..3fb992ce1f 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt @@ -29,9 +29,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswo import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmSignUpRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmSignUpResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType -import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeResponse -import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.ResendConfirmationCodeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ResendConfirmationCodeResponse @@ -42,12 +40,10 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.SignUpResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaNotFoundException import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType -import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType -import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifyUserAttributeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifyUserAttributeResponse import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthException @@ -513,7 +509,7 @@ class RealAWSCognitoAuthPluginTest { } coEvery { - authService.cognitoIdentityProviderClient?.getUser(any()) + authService.cognitoIdentityProviderClient?.getUser(any()) } returns GetUserResponse.invoke { this.userAttributes = userAttributes } @@ -916,7 +912,6 @@ class RealAWSCognitoAuthPluginTest { // GIVEN val onSuccess = mockk>() val onError = mockk>() - val userId = "123456" val username = "user" val resultCaptor = slot() @@ -1023,7 +1018,7 @@ class RealAWSCognitoAuthPluginTest { } coEvery { - authService.cognitoIdentityProviderClient?.updateUserAttributes(any()) + authService.cognitoIdentityProviderClient?.updateUserAttributes(any()) } returns UpdateUserAttributesResponse.invoke { } @@ -1071,7 +1066,7 @@ class RealAWSCognitoAuthPluginTest { } coEvery { - authService.cognitoIdentityProviderClient?.updateUserAttributes(any()) + authService.cognitoIdentityProviderClient?.updateUserAttributes(any()) } returns UpdateUserAttributesResponse.invoke { } @@ -1123,7 +1118,7 @@ class RealAWSCognitoAuthPluginTest { } coEvery { - authService.cognitoIdentityProviderClient?.updateUserAttributes(any()) + authService.cognitoIdentityProviderClient?.updateUserAttributes(any()) } returns UpdateUserAttributesResponse.invoke { codeDeliveryDetailsList = listOf( CodeDeliveryDetailsType.invoke { @@ -1295,7 +1290,7 @@ class RealAWSCognitoAuthPluginTest { val expectedException = CognitoIdentityProviderException("Some Cognito Message") coEvery { - authService.cognitoIdentityProviderClient?.verifyUserAttribute(any()) + authService.cognitoIdentityProviderClient?.verifyUserAttribute(any()) } answers { throw expectedException } @@ -1332,7 +1327,7 @@ class RealAWSCognitoAuthPluginTest { } coEvery { - authService.cognitoIdentityProviderClient?.verifyUserAttribute(any()) + authService.cognitoIdentityProviderClient?.verifyUserAttribute(any()) } returns VerifyUserAttributeResponse.invoke { } every { @@ -1441,7 +1436,7 @@ class RealAWSCognitoAuthPluginTest { val expectedException = CognitoIdentityProviderException("Some Cognito Message") coEvery { authService.cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - any() + any() ) } answers { throw expectedException @@ -1479,7 +1474,7 @@ class RealAWSCognitoAuthPluginTest { coEvery { authService.cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - any() + any() ) } returns GetUserAttributeVerificationCodeResponse.invoke { codeDeliveryDetails = CodeDeliveryDetailsType.invoke { @@ -1548,7 +1543,7 @@ class RealAWSCognitoAuthPluginTest { configJsonObject.put("Endpoint", invalidEndpoint) val expectedErrorMessage = "Invalid endpoint value $invalidEndpoint. Expected fully qualified hostname with " + "no scheme, no path and no query" - var message = try { + val message = try { UserPoolConfiguration.fromJson(configJsonObject).build() } catch (ex: Exception) { ex.message @@ -1566,7 +1561,7 @@ class RealAWSCognitoAuthPluginTest { configJsonObject.put("Endpoint", invalidEndpoint) val expectedErrorMessage = "Invalid endpoint value $invalidEndpoint. Expected fully qualified hostname with " + "no scheme, no path and no query" - var message = try { + val message = try { UserPoolConfiguration.fromJson(configJsonObject).build() } catch (ex: Exception) { ex.message @@ -1585,7 +1580,7 @@ class RealAWSCognitoAuthPluginTest { configJsonObject.put("Endpoint", invalidEndpoint) val expectedErrorMessage = "Invalid endpoint value $invalidEndpoint. Expected fully qualified hostname with " + "no scheme, no path and no query" - var message = try { + val message = try { UserPoolConfiguration.fromJson(configJsonObject).build() } catch (ex: Exception) { ex.message @@ -1716,8 +1711,6 @@ class RealAWSCognitoAuthPluginTest { val onSuccess = mockk() every { onSuccess.call() }.answers { listenLatch.countDown() } val onError = mockk>() - - val session = "SESSION" val code = "123456" val friendlyDeviceName = "DEVICE_NAME" coEvery { @@ -1760,9 +1753,7 @@ class RealAWSCognitoAuthPluginTest { val authException = slot() every { onError.accept(capture(authException)) }.answers { listenLatch.countDown() } - val session = "SESSION" val code = "123456" - val friendlyDeviceName = "DEVICE_NAME" val errorMessage = "Invalid code" coEvery { mockCognitoIPClient.verifySoftwareToken( @@ -1839,6 +1830,769 @@ class RealAWSCognitoAuthPluginTest { every { onSuccess.call() }.answers { listenLatch.countDown() } val onError = mockk>() val setUserMFAPreferenceRequest = slot() + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updateMFAPreferences when currentpreference is totp both provided sms and totp preference are enabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA", "SOFTWARE_TOKEN_MFA") + preferredMfaSetting = "SOFTWARE_TOKEN_MFA" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updateMFAPreferences when currentpreference is sms both provided sms and totp preference are enabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA", "SOFTWARE_TOKEN_MFA") + preferredMfaSetting = "SMS_MFA" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updateMFAPreferences when both provided sms and totp preference are null and cognito throws an exception`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + val onError = mockk>() + val authException = slot() + every { onError.accept(capture(authException)) }.answers { listenLatch.countDown() } + val setUserMFAPreferenceRequest = slot() + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + throw Exception() + } + plugin.updateMFAPreference(null, null, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(authException.isCaptured) + verify(exactly = 1) { onError.accept(any()) } + } + + @Test + fun `updateMFAPreferences when fetch preferences throws an exception`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + val onError = mockk>() + val authException = slot() + every { onError.accept(capture(authException)) }.answers { listenLatch.countDown() } + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + throw Exception() + } + plugin.updateMFAPreference(null, null, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(authException.isCaptured) + verify(exactly = 1) { onError.accept(any()) } + } + + @Test + fun `updatepref when currentpref is null and TOTP is enabled and SMS is enabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and TOTP is enabled and SMS is disabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and TOTP is disabled and SMS is enabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and SMS is preferred and TOTP is enabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and SMS is enabled and TOTP is preferred`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and TOTP is preferred and SMS is disabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.PREFERRED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and TOTP is disabled and SMS is preferred`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.DISABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is TOTP preferred and TOTP parameter is disabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SOFTWARE_TOKEN_MFA") + preferredMfaSetting = "SOFTWARE_TOKEN_MFA" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is SMS preferred and SMS parameter is disabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA") + preferredMfaSetting = "SMS_MFA" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref errors out when fetchMFA fails`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA") + preferredMfaSetting = "SMS_MFA" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is TOTP preferred and params include SMS preferred and TOTP enabled`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SOFTWARE_TOKEN_MFA") + preferredMfaSetting = "SOFTWARE_TOKEN_MFA" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is SMS preferred and params include SMS enabled and TOTP preferred`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA") + preferredMfaSetting = "SMS_MFA" + } + } + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke { }