Password encoding and decoding handing in TwoFactorLoginNavigation (#3951)

This commit is contained in:
David Perez 2024-09-20 12:04:57 -05:00 committed by GitHub
parent 37b5dc7de3
commit bcdbea13bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 23 additions and 33 deletions

View file

@ -111,7 +111,7 @@ fun NavGraphBuilder.authGraph(
onNavigateToTwoFactorLogin = { emailAddress ->
navController.navigateToTwoFactorLogin(
emailAddress = emailAddress,
base64EncodedPassword = null,
password = null,
)
},
)
@ -154,7 +154,7 @@ fun NavGraphBuilder.authGraph(
onNavigateToTwoFactorLogin = { emailAddress, password ->
navController.navigateToTwoFactorLogin(
emailAddress = emailAddress,
base64EncodedPassword = password,
password = password,
)
},
)
@ -163,7 +163,7 @@ fun NavGraphBuilder.authGraph(
onNavigateToTwoFactorLogin = {
navController.navigateToTwoFactorLogin(
emailAddress = it,
base64EncodedPassword = null,
password = null,
)
},
)

View file

@ -94,7 +94,7 @@ fun LoginScreen(
}
is LoginEvent.NavigateToTwoFactorLogin -> {
onNavigateToTwoFactorLogin(event.emailAddress, event.base64EncodedPassword)
onNavigateToTwoFactorLogin(event.emailAddress, event.password)
}
is LoginEvent.ShowToast -> {

View file

@ -12,7 +12,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
@ -160,9 +159,7 @@ class LoginViewModel @Inject constructor(
sendEvent(
LoginEvent.NavigateToTwoFactorLogin(
emailAddress = state.emailAddress,
// Base64 URL encode the password to prevent corruption of escapable chars
// when sending via navArgs.
base64EncodedPassword = state.passwordInput.base64UrlEncode(),
password = state.passwordInput,
),
)
}
@ -345,7 +342,7 @@ sealed class LoginEvent {
*/
data class NavigateToTwoFactorLogin(
val emailAddress: String,
val base64EncodedPassword: String?,
val password: String?,
) : LoginEvent()
/**

View file

@ -28,7 +28,7 @@ fun NavGraphBuilder.trustedDeviceGraph(navController: NavHostController) {
onNavigateToTwoFactorLogin = {
navController.navigateToTwoFactorLogin(
emailAddress = it,
base64EncodedPassword = null,
password = null,
)
},
)

View file

@ -5,6 +5,8 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val EMAIL_ADDRESS = "email_address"
@ -15,14 +17,12 @@ private const val TWO_FACTOR_LOGIN_ROUTE =
/**
* Class to retrieve Two-Factor Login arguments from the [SavedStateHandle].
*
* @property base64EncodedPassword Base64 URL encoded password input.
*/
@OmitFromCoverage
data class TwoFactorLoginArgs(val emailAddress: String, val base64EncodedPassword: String?) {
data class TwoFactorLoginArgs(val emailAddress: String, val password: String?) {
constructor(savedStateHandle: SavedStateHandle) : this(
emailAddress = checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
base64EncodedPassword = savedStateHandle[PASSWORD],
password = savedStateHandle.get<String>(PASSWORD)?.base64UrlDecodeOrNull(),
)
}
@ -31,11 +31,11 @@ data class TwoFactorLoginArgs(val emailAddress: String, val base64EncodedPasswor
*/
fun NavController.navigateToTwoFactorLogin(
emailAddress: String,
base64EncodedPassword: String?,
password: String?,
navOptions: NavOptions? = null,
) {
this.navigate(
route = "$TWO_FACTOR_LOGIN_PREFIX/$emailAddress?$PASSWORD=$base64EncodedPassword",
route = "$TWO_FACTOR_LOGIN_PREFIX/$emailAddress?$PASSWORD=${password?.base64UrlEncode()}",
navOptions = navOptions,
)
}

View file

@ -22,7 +22,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForWebAuth
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.button
@ -72,7 +71,7 @@ class TwoFactorLoginViewModel @Inject constructor(
isRememberMeEnabled = false,
captchaToken = null,
email = args.emailAddress,
password = args.base64EncodedPassword?.base64UrlDecodeOrNull(),
password = args.password,
)
},
) {

View file

@ -12,7 +12,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
@ -62,7 +61,7 @@ class LoginViewModelTest : BaseViewModelTest() {
@AfterEach
fun tearDown() {
unmockkStatic(::generateUriForCaptcha, String::base64UrlEncode)
unmockkStatic(::generateUriForCaptcha)
}
@Test
@ -340,34 +339,30 @@ class LoginViewModelTest : BaseViewModelTest() {
@Test
fun `LoginButtonClick login returns TwoFactorRequired should base64 URL encode password and emit NavigateToTwoFactorLogin`() =
runTest {
mockkStatic(String::base64UrlEncode)
val decodedPassword = "password"
val encodedPassword = "base64EncodedPassword"
every { decodedPassword.base64UrlEncode() } returns encodedPassword
val password = "password"
coEvery {
authRepository.login(
email = EMAIL,
password = decodedPassword,
password = password,
captchaToken = null,
)
} returns LoginResult.TwoFactorRequired
val viewModel = createViewModel(
state = DEFAULT_STATE.copy(
passwordInput = decodedPassword,
passwordInput = password,
),
)
viewModel.eventFlow.test {
viewModel.trySendAction(LoginAction.LoginButtonClick)
verify { decodedPassword.base64UrlEncode() }
assertEquals(
DEFAULT_STATE.copy(passwordInput = decodedPassword),
DEFAULT_STATE.copy(passwordInput = password),
viewModel.stateFlow.value,
)
assertEquals(
LoginEvent.NavigateToTwoFactorLogin(
EMAIL,
encodedPassword,
emailAddress = EMAIL,
password = password,
),
awaitItem(),
)
@ -375,7 +370,7 @@ class LoginViewModelTest : BaseViewModelTest() {
coVerify {
authRepository.login(
email = EMAIL,
password = decodedPassword,
password = password,
captchaToken = null,
)
}

View file

@ -18,7 +18,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForWebAuth
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
@ -66,7 +65,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
mockkStatic(
::generateUriForCaptcha,
::generateUriForWebAuth,
String::base64UrlEncode,
String::base64UrlDecodeOrNull,
)
mockkStatic(Uri::class)
every {