From ae38b5d7ed72e3489ce248a683add388dd411f6e Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 22 Apr 2024 11:18:11 -0500 Subject: [PATCH] Add setup for WebAuthn (#1294) --- .../network/model/GetTokenResponseJson.kt | 3 + .../util/TwoFactorRequiredExtensions.kt | 53 ++++++++ .../twofactorlogin/TwoFactorLoginViewModel.kt | 8 +- .../util/TwoFactorAuthMethodExtensions.kt | 22 ++++ .../main/res/values/strings_non_localized.xml | 2 + .../network/service/IdentityServiceTest.kt | 4 +- .../util/TwoFactorRequiredExtensionTest.kt | 116 ++++++++++++++++++ .../auth/repository/AuthRepositoryTest.kt | 40 +++++- .../TwoFactorLoginViewModelTest.kt | 9 +- .../util/TwoFactorAuthMethodExtensionTest.kt | 23 +++- 10 files changed, 263 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt index 369782c413..15ba6c3fd8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt @@ -124,6 +124,9 @@ sealed class GetTokenResponseJson { @SerialName("TwoFactorProviders2") val authMethodsData: Map, + @SerialName("TwoFactorProviders") + val twoFactorProviders: List?, + @SerialName("CaptchaBypassToken") val captchaToken: String?, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensions.kt index 3ed674c34e..959c0865c5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensions.kt @@ -2,8 +2,11 @@ package com.x8bit.bitwarden.data.auth.datasource.network.util import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod +import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive /** @@ -58,3 +61,53 @@ val GetTokenResponseJson.TwoFactorRequired?.twoFactorDisplayEmail: String */ private val Map.duo: JsonObject? get() = get(TwoFactorAuthMethod.DUO) ?: get(TwoFactorAuthMethod.DUO_ORGANIZATION) + +/** + * If it exists, return the identifier for the relying party used with Web AuthN two-factor + * authentication. + */ +val GetTokenResponseJson.TwoFactorRequired?.webAuthRpId: String? + get() = this + ?.authMethodsData + ?.get(TwoFactorAuthMethod.WEB_AUTH) + ?.get("rpId") + ?.jsonPrimitive + ?.contentOrNull + +/** + * If it exists, return the type of user verification needed to complete the Web AuthN two-factor + * authentication. + */ +val GetTokenResponseJson.TwoFactorRequired?.webAuthUserVerification: String? + get() = this + ?.authMethodsData + ?.get(TwoFactorAuthMethod.WEB_AUTH) + ?.get("userVerification") + ?.jsonPrimitive + ?.contentOrNull + +/** + * If it exists, return the challenge that the authenticator need to solve to complete the + * Web AuthN two-factor authentication. + */ +val GetTokenResponseJson.TwoFactorRequired?.webAuthChallenge: String? + get() = this + ?.authMethodsData + ?.get(TwoFactorAuthMethod.WEB_AUTH) + ?.get("challenge") + ?.jsonPrimitive + ?.contentOrNull + +/** + * If it exists, return the credentials allowed to be used to solve the challenge to complete the + * Web AuthN two-factor authentication. + */ +val GetTokenResponseJson.TwoFactorRequired?.webAuthAllowCredentials: List? + get() = this + ?.authMethodsData + ?.get(TwoFactorAuthMethod.WEB_AUTH) + ?.get("allowCredentials") + ?.jsonArray + ?.mapNotNull { + it.jsonObject["id"]?.jsonPrimitive?.contentOrNull?.base64UrlDecodeOrNull() + } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt index 0f4f196d7c..3b23f96d35 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt @@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha import com.x8bit.bitwarden.data.auth.util.YubiKeyResult +import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.button import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.imageRes import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.isDuo import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.shouldUseNfc @@ -456,12 +457,7 @@ data class TwoFactorLoginState( /** * The text to display for the button given the [authMethod]. */ - val buttonText: Text - get() = if (authMethod.isDuo) { - R.string.launch_duo.asText() - } else { - R.string.continue_text.asText() - } + val buttonText: Text get() = authMethod.button /** * Indicates if the screen should be listening for NFC events from the operating system. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt index 102c20ce9e..740e2732ac 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt @@ -20,6 +20,7 @@ val TwoFactorAuthMethod.title: Text TwoFactorAuthMethod.EMAIL -> R.string.email.asText() TwoFactorAuthMethod.RECOVERY_CODE -> R.string.recovery_code_title.asText() + TwoFactorAuthMethod.WEB_AUTH -> R.string.fido2_authenticate_web_authn.asText() TwoFactorAuthMethod.YUBI_KEY -> R.string.yubi_key_title.asText() else -> "".asText() } @@ -38,10 +39,31 @@ fun TwoFactorAuthMethod.description(email: String): Text = when (this) { } TwoFactorAuthMethod.EMAIL -> R.string.enter_verification_code_email.asText(email) + TwoFactorAuthMethod.WEB_AUTH -> R.string.continue_to_complete_web_authn_verfication.asText() TwoFactorAuthMethod.YUBI_KEY -> R.string.yubi_key_instruction.asText() else -> "".asText() } +/** + * Get the button label for the given auth method. + */ +val TwoFactorAuthMethod.button: Text + get() = when (this) { + TwoFactorAuthMethod.DUO, + TwoFactorAuthMethod.DUO_ORGANIZATION, + -> R.string.launch_duo.asText() + + TwoFactorAuthMethod.AUTHENTICATOR_APP, + TwoFactorAuthMethod.EMAIL, + TwoFactorAuthMethod.YUBI_KEY, + TwoFactorAuthMethod.U2F, + TwoFactorAuthMethod.REMEMBER, + TwoFactorAuthMethod.RECOVERY_CODE, + -> R.string.continue_text.asText() + + TwoFactorAuthMethod.WEB_AUTH -> R.string.launch_web_authn.asText() + } + /** * Gets a boolean indicating if the given auth method uses Duo. */ diff --git a/app/src/main/res/values/strings_non_localized.xml b/app/src/main/res/values/strings_non_localized.xml index 88e54f210c..8faa49ff77 100644 --- a/app/src/main/res/values/strings_non_localized.xml +++ b/app/src/main/res/values/strings_non_localized.xml @@ -15,4 +15,6 @@ Password Protected This password will be used to export and import this file Autofill suggestion + Continue to complete WebAuthn verification. + Launch WebAuthn diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/IdentityServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/IdentityServiceTest.kt index d35280736f..b524c23400 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/IdentityServiceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/IdentityServiceTest.kt @@ -385,7 +385,8 @@ private const val TWO_FACTOR_BODY_JSON = """ { "TwoFactorProviders2": {"1": {"Email": "ex***@email.com"}, "0": {"Email": null}}, "SsoEmail2faSessionToken": "exampleToken", - "CaptchaBypassToken": "BWCaptchaBypass_ABCXYZ" + "CaptchaBypassToken": "BWCaptchaBypass_ABCXYZ", + "TwoFactorProviders": ["1", "3", "0"] } """ private val TWO_FACTOR_BODY = GetTokenResponseJson.TwoFactorRequired( @@ -395,6 +396,7 @@ private val TWO_FACTOR_BODY = GetTokenResponseJson.TwoFactorRequired( ), ssoToken = "exampleToken", captchaToken = "BWCaptchaBypass_ABCXYZ", + twoFactorProviders = listOf("1", "3", "0"), ) private const val LOGIN_SUCCESS_JSON = """ diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensionTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensionTest.kt index a225ec5657..26faa0b5a7 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensionTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/util/TwoFactorRequiredExtensionTest.kt @@ -2,10 +2,12 @@ package com.x8bit.bitwarden.data.auth.datasource.network.util import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test @@ -21,6 +23,7 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertEquals( listOf( @@ -43,6 +46,7 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertEquals("Bitwarden", subject.twoFactorDuoAuthUrl) } @@ -58,6 +62,7 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertEquals("Bitwarden", subject.twoFactorDuoAuthUrl) } @@ -70,6 +75,7 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertNull(subject.twoFactorDuoAuthUrl) } @@ -85,6 +91,7 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertEquals("ex***@email.com", subject.twoFactorDisplayEmail) } @@ -97,6 +104,7 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertEquals("", subject.twoFactorDisplayEmail) } @@ -112,7 +120,115 @@ class TwoFactorRequiredExtensionTest { ), captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) assertEquals(TwoFactorAuthMethod.AUTHENTICATOR_APP, subject.preferredAuthMethod) } + + @Test + fun `twoFactorDuoAuthUrl returns the expected value for DUO`() { + val authUrl = "vault.bitwarden.com" + val subject = GetTokenResponseJson.TwoFactorRequired( + authMethodsData = mapOf( + TwoFactorAuthMethod.DUO to JsonObject( + mapOf("AuthUrl" to JsonPrimitive(authUrl)), + ), + ), + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ) + assertEquals(authUrl, subject.twoFactorDuoAuthUrl) + } + + @Test + fun `twoFactorDuoAuthUrl returns the expected value for DUO_ORGANIZATION`() { + val authUrl = "vault.bitwarden.com" + val subject = GetTokenResponseJson.TwoFactorRequired( + authMethodsData = mapOf( + TwoFactorAuthMethod.DUO_ORGANIZATION to JsonObject( + mapOf("AuthUrl" to JsonPrimitive(authUrl)), + ), + ), + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ) + assertEquals(authUrl, subject.twoFactorDuoAuthUrl) + } + + @Test + fun `webAuthRpId returns the expected value`() { + val rpId = "vault.bitwarden.com" + val subject = GetTokenResponseJson.TwoFactorRequired( + authMethodsData = mapOf( + TwoFactorAuthMethod.WEB_AUTH to JsonObject( + mapOf("rpId" to JsonPrimitive(rpId)), + ), + ), + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ) + assertEquals(rpId, subject.webAuthRpId) + } + + @Test + fun `webAuthUserVerification returns the expected value`() { + val userVerification = "discouraged" + val subject = GetTokenResponseJson.TwoFactorRequired( + authMethodsData = mapOf( + TwoFactorAuthMethod.WEB_AUTH to JsonObject( + mapOf("userVerification" to JsonPrimitive(userVerification)), + ), + ), + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ) + assertEquals(userVerification, subject.webAuthUserVerification) + } + + @Test + fun `webAuthChallenge returns the expected value`() { + val challenge = "987t34478t9rxq7t8n" + val subject = GetTokenResponseJson.TwoFactorRequired( + authMethodsData = mapOf( + TwoFactorAuthMethod.WEB_AUTH to JsonObject( + mapOf("challenge" to JsonPrimitive(challenge)), + ), + ), + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ) + assertEquals(challenge, subject.webAuthChallenge) + } + + @Test + fun `webAuthAllowCredentials returns the expected value`() { + val credential = "98426435782" + val subject = GetTokenResponseJson.TwoFactorRequired( + authMethodsData = mapOf( + TwoFactorAuthMethod.WEB_AUTH to JsonObject( + mapOf( + "allowCredentials" to JsonArray( + listOf( + JsonObject( + mapOf( + "type" to JsonPrimitive("public-key"), + "id" to JsonPrimitive(credential), + ), + ), + ), + ), + ), + ), + ), + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ) + assertNotNull(subject.webAuthAllowCredentials) + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt index 976e1c61a9..6c0bbf518c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt @@ -1632,13 +1632,19 @@ class AuthRepositoryTest { authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) .asSuccess() val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) assertEquals(LoginResult.TwoFactorRequired, result) assertEquals( repository.twoFactorResponse, - GetTokenResponseJson.TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null), + GetTokenResponseJson.TwoFactorRequired( + authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, + twoFactorProviders = null, + captchaToken = null, + ssoToken = null, + ), ) assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value) coVerify { identityService.preLogin(email = EMAIL) } @@ -1675,6 +1681,7 @@ class AuthRepositoryTest { authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) .asSuccess() val firstResult = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) @@ -2068,7 +2075,12 @@ class AuthRepositoryTest { uniqueAppId = UNIQUE_APP_ID, ) } returns GetTokenResponseJson - .TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null) + .TwoFactorRequired( + authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, + twoFactorProviders = null, + captchaToken = null, + ssoToken = null, + ) .asSuccess() val result = repository.login( email = EMAIL, @@ -2082,7 +2094,12 @@ class AuthRepositoryTest { assertEquals(LoginResult.TwoFactorRequired, result) assertEquals( repository.twoFactorResponse, - GetTokenResponseJson.TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null), + GetTokenResponseJson.TwoFactorRequired( + authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, + twoFactorProviders = null, + captchaToken = null, + ssoToken = null, + ), ) assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value) coVerify { @@ -2115,7 +2132,12 @@ class AuthRepositoryTest { uniqueAppId = UNIQUE_APP_ID, ) } returns GetTokenResponseJson - .TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null) + .TwoFactorRequired( + authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, + twoFactorProviders = null, + captchaToken = null, + ssoToken = null, + ) .asSuccess() val firstResult = repository.login( email = EMAIL, @@ -2721,6 +2743,7 @@ class AuthRepositoryTest { authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) .asSuccess() val result = repository.login( @@ -2734,7 +2757,12 @@ class AuthRepositoryTest { assertEquals(LoginResult.TwoFactorRequired, result) assertEquals( repository.twoFactorResponse, - GetTokenResponseJson.TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null), + GetTokenResponseJson.TwoFactorRequired( + authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, + ), ) assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value) coVerify { @@ -2771,6 +2799,7 @@ class AuthRepositoryTest { authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) .asSuccess() @@ -4124,6 +4153,7 @@ class AuthRepositoryTest { authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) .asSuccess() val firstResult = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt index f8e7a968cf..bfe311d67d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt @@ -257,6 +257,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { authMethodsData = authMethodsData, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) every { authRepository.twoFactorResponse } returns response val mockkUri = mockk() @@ -291,6 +292,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { authMethodsData = authMethodsData, captchaToken = null, ssoToken = null, + twoFactorProviders = null, ) every { authRepository.twoFactorResponse } returns response val viewModel = createViewModel( @@ -625,9 +627,10 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { ) private val TWO_FACTOR_RESPONSE = GetTokenResponseJson.TwoFactorRequired( - TWO_FACTOR_AUTH_METHODS_DATA, - null, - null, + authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA, + captchaToken = null, + ssoToken = null, + twoFactorProviders = null, ) private val DEFAULT_STATE = TwoFactorLoginState( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt index 6f9be368f2..60e758d260 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt @@ -20,7 +20,7 @@ class TwoFactorAuthMethodExtensionTest { TwoFactorAuthMethod.DUO_ORGANIZATION to R.string.duo_org_title.asText( R.string.organization.asText(), ), - TwoFactorAuthMethod.WEB_AUTH to "".asText(), + TwoFactorAuthMethod.WEB_AUTH to R.string.fido2_authenticate_web_authn.asText(), TwoFactorAuthMethod.RECOVERY_CODE to R.string.recovery_code_title.asText(), ) .forEach { (type, title) -> @@ -48,7 +48,8 @@ class TwoFactorAuthMethodExtensionTest { .asText() .concat(" ".asText()) .concat(R.string.follow_the_steps_from_duo_to_finish_logging_in.asText()), - TwoFactorAuthMethod.WEB_AUTH to "".asText(), + TwoFactorAuthMethod.WEB_AUTH to + R.string.continue_to_complete_web_authn_verfication.asText(), TwoFactorAuthMethod.RECOVERY_CODE to "".asText(), ) .forEach { (type, title) -> @@ -59,6 +60,24 @@ class TwoFactorAuthMethodExtensionTest { } } + @Test + fun `button returns the expected value`() { + mapOf( + TwoFactorAuthMethod.AUTHENTICATOR_APP to R.string.continue_text.asText(), + TwoFactorAuthMethod.EMAIL to R.string.continue_text.asText(), + TwoFactorAuthMethod.DUO to R.string.launch_duo.asText(), + TwoFactorAuthMethod.YUBI_KEY to R.string.continue_text.asText(), + TwoFactorAuthMethod.U2F to R.string.continue_text.asText(), + TwoFactorAuthMethod.REMEMBER to R.string.continue_text.asText(), + TwoFactorAuthMethod.DUO_ORGANIZATION to R.string.launch_duo.asText(), + TwoFactorAuthMethod.WEB_AUTH to R.string.launch_web_authn.asText(), + TwoFactorAuthMethod.RECOVERY_CODE to R.string.continue_text.asText(), + ) + .forEach { (type, buttonLabel) -> + assertEquals(buttonLabel, type.button) + } + } + @Test fun `isDuo returns the expected value`() { mapOf(