Handle null or blank auth urls for Duo 2FA (#1044)

This commit is contained in:
Caleb Derosier 2024-02-21 11:00:38 -07:00 committed by Álison Fernandes
parent 8eafb8e180
commit 2e2b80470c
4 changed files with 47 additions and 8 deletions

View file

@ -32,14 +32,13 @@ val GetTokenResponseJson.TwoFactorRequired?.preferredAuthMethod: TwoFactorAuthMe
/** /**
* If it exists, return the value of the Duo auth url. * If it exists, return the value of the Duo auth url.
*/ */
val GetTokenResponseJson.TwoFactorRequired?.twoFactorDuoAuthUrl: String val GetTokenResponseJson.TwoFactorRequired?.twoFactorDuoAuthUrl: String?
get() = this get() = this
?.authMethodsData ?.authMethodsData
?.duo ?.duo
?.get("AuthUrl") ?.get("AuthUrl")
?.jsonPrimitive ?.jsonPrimitive
?.contentOrNull ?.contentOrNull
.orEmpty()
/** /**
* If it exists, return the value to display for the email used with two-factor authentication. * If it exists, return the value to display for the email used with two-factor authentication.

View file

@ -160,9 +160,18 @@ class TwoFactorLoginViewModel @Inject constructor(
*/ */
private fun handleContinueButtonClick() { private fun handleContinueButtonClick() {
if (state.authMethod.isDuo) { if (state.authMethod.isDuo) {
val authUrl = authRepository.twoFactorResponse.twoFactorDuoAuthUrl
// The url should not be empty unless the environment is somehow not supported.
sendEvent( sendEvent(
event = TwoFactorLoginEvent.NavigateToDuo( event = authUrl
uri = Uri.parse(authRepository.twoFactorResponse.twoFactorDuoAuthUrl), ?.let {
TwoFactorLoginEvent.NavigateToDuo(
uri = Uri.parse(it),
)
}
?: TwoFactorLoginEvent.ShowToast(
// TODO BIT-1927 Update to use string resource
message = "Duo not yet supported".asText(),
), ),
) )
} else { } else {

View file

@ -6,6 +6,7 @@ import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class TwoFactorRequiredExtensionTest { class TwoFactorRequiredExtensionTest {
@ -62,7 +63,7 @@ class TwoFactorRequiredExtensionTest {
} }
@Test @Test
fun `twoFactorDuoAuthUrl returns empty string when no DUO AuthUrl is present`() { fun `twoFactorDuoAuthUrl returns null when no DUO AuthUrl is present`() {
val subject = GetTokenResponseJson.TwoFactorRequired( val subject = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = mapOf( authMethodsData = mapOf(
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("AuthUrl" to JsonNull)), TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("AuthUrl" to JsonNull)),
@ -70,7 +71,7 @@ class TwoFactorRequiredExtensionTest {
captchaToken = null, captchaToken = null,
ssoToken = null, ssoToken = null,
) )
assertEquals("", subject.twoFactorDuoAuthUrl) assertNull(subject.twoFactorDuoAuthUrl)
} }
@Test @Test

View file

@ -249,8 +249,9 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
} }
} }
@Suppress("MaxLineLength")
@Test @Test
fun `ContinueButtonClick login should emit NavigateToDuo when auth method is Duo`() = runTest { fun `ContinueButtonClick login should emit NavigateToDuo when auth method is Duo and authUrl is non-null`() = runTest {
val authMethodsData = mapOf( val authMethodsData = mapOf(
TwoFactorAuthMethod.DUO to JsonObject( TwoFactorAuthMethod.DUO to JsonObject(
mapOf("AuthUrl" to JsonPrimitive("bitwarden.com")), mapOf("AuthUrl" to JsonPrimitive("bitwarden.com")),
@ -281,6 +282,35 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
} }
} }
@Suppress("MaxLineLength")
@Test
fun `ContinueButtonClick login should emit ShowToast when auth method is Duo and authUrl is null`() = runTest {
val authMethodsData = mapOf(
TwoFactorAuthMethod.DUO to JsonObject(
mapOf("Nothing" to JsonPrimitive("Nothing")),
),
)
val response = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = authMethodsData,
captchaToken = null,
ssoToken = null,
)
every { authRepository.twoFactorResponse } returns response
val mockkUri = mockk<Uri>()
val viewModel = createViewModel(
state = DEFAULT_STATE.copy(
authMethod = TwoFactorAuthMethod.DUO,
),
)
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(TwoFactorLoginAction.ContinueButtonClick)
assertEquals(
TwoFactorLoginEvent.ShowToast("Duo not yet supported".asText()),
awaitItem(),
)
}
}
@Test @Test
fun `ContinueButtonClick login returns CaptchaRequired should emit NavigateToCaptcha`() = fun `ContinueButtonClick login returns CaptchaRequired should emit NavigateToCaptcha`() =
runTest { runTest {