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

View file

@ -94,7 +94,7 @@ fun LoginScreen(
} }
is LoginEvent.NavigateToTwoFactorLogin -> { is LoginEvent.NavigateToTwoFactorLogin -> {
onNavigateToTwoFactorLogin(event.emailAddress, event.base64EncodedPassword) onNavigateToTwoFactorLogin(event.emailAddress, event.password)
} }
is LoginEvent.ShowToast -> { 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.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha 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.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
@ -160,9 +159,7 @@ class LoginViewModel @Inject constructor(
sendEvent( sendEvent(
LoginEvent.NavigateToTwoFactorLogin( LoginEvent.NavigateToTwoFactorLogin(
emailAddress = state.emailAddress, emailAddress = state.emailAddress,
// Base64 URL encode the password to prevent corruption of escapable chars password = state.passwordInput,
// when sending via navArgs.
base64EncodedPassword = state.passwordInput.base64UrlEncode(),
), ),
) )
} }
@ -345,7 +342,7 @@ sealed class LoginEvent {
*/ */
data class NavigateToTwoFactorLogin( data class NavigateToTwoFactorLogin(
val emailAddress: String, val emailAddress: String,
val base64EncodedPassword: String?, val password: String?,
) : LoginEvent() ) : LoginEvent()
/** /**

View file

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

View file

@ -5,6 +5,8 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage 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 import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val EMAIL_ADDRESS = "email_address" 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]. * Class to retrieve Two-Factor Login arguments from the [SavedStateHandle].
*
* @property base64EncodedPassword Base64 URL encoded password input.
*/ */
@OmitFromCoverage @OmitFromCoverage
data class TwoFactorLoginArgs(val emailAddress: String, val base64EncodedPassword: String?) { data class TwoFactorLoginArgs(val emailAddress: String, val password: String?) {
constructor(savedStateHandle: SavedStateHandle) : this( constructor(savedStateHandle: SavedStateHandle) : this(
emailAddress = checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String, 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( fun NavController.navigateToTwoFactorLogin(
emailAddress: String, emailAddress: String,
base64EncodedPassword: String?, password: String?,
navOptions: NavOptions? = null, navOptions: NavOptions? = null,
) { ) {
this.navigate( this.navigate(
route = "$TWO_FACTOR_LOGIN_PREFIX/$emailAddress?$PASSWORD=$base64EncodedPassword", route = "$TWO_FACTOR_LOGIN_PREFIX/$emailAddress?$PASSWORD=${password?.base64UrlEncode()}",
navOptions = navOptions, 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.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForWebAuth import com.x8bit.bitwarden.data.auth.repository.util.generateUriForWebAuth
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult 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.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.button import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.button
@ -72,7 +71,7 @@ class TwoFactorLoginViewModel @Inject constructor(
isRememberMeEnabled = false, isRememberMeEnabled = false,
captchaToken = null, captchaToken = null,
email = args.emailAddress, 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.model.UserState
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha 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.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
@ -62,7 +61,7 @@ class LoginViewModelTest : BaseViewModelTest() {
@AfterEach @AfterEach
fun tearDown() { fun tearDown() {
unmockkStatic(::generateUriForCaptcha, String::base64UrlEncode) unmockkStatic(::generateUriForCaptcha)
} }
@Test @Test
@ -340,34 +339,30 @@ class LoginViewModelTest : BaseViewModelTest() {
@Test @Test
fun `LoginButtonClick login returns TwoFactorRequired should base64 URL encode password and emit NavigateToTwoFactorLogin`() = fun `LoginButtonClick login returns TwoFactorRequired should base64 URL encode password and emit NavigateToTwoFactorLogin`() =
runTest { runTest {
mockkStatic(String::base64UrlEncode) val password = "password"
val decodedPassword = "password"
val encodedPassword = "base64EncodedPassword"
every { decodedPassword.base64UrlEncode() } returns encodedPassword
coEvery { coEvery {
authRepository.login( authRepository.login(
email = EMAIL, email = EMAIL,
password = decodedPassword, password = password,
captchaToken = null, captchaToken = null,
) )
} returns LoginResult.TwoFactorRequired } returns LoginResult.TwoFactorRequired
val viewModel = createViewModel( val viewModel = createViewModel(
state = DEFAULT_STATE.copy( state = DEFAULT_STATE.copy(
passwordInput = decodedPassword, passwordInput = password,
), ),
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(LoginAction.LoginButtonClick) viewModel.trySendAction(LoginAction.LoginButtonClick)
verify { decodedPassword.base64UrlEncode() }
assertEquals( assertEquals(
DEFAULT_STATE.copy(passwordInput = decodedPassword), DEFAULT_STATE.copy(passwordInput = password),
viewModel.stateFlow.value, viewModel.stateFlow.value,
) )
assertEquals( assertEquals(
LoginEvent.NavigateToTwoFactorLogin( LoginEvent.NavigateToTwoFactorLogin(
EMAIL, emailAddress = EMAIL,
encodedPassword, password = password,
), ),
awaitItem(), awaitItem(),
) )
@ -375,7 +370,7 @@ class LoginViewModelTest : BaseViewModelTest() {
coVerify { coVerify {
authRepository.login( authRepository.login(
email = EMAIL, email = EMAIL,
password = decodedPassword, password = password,
captchaToken = null, 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.repository.util.generateUriForWebAuth
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult 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.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.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
@ -66,7 +65,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
mockkStatic( mockkStatic(
::generateUriForCaptcha, ::generateUriForCaptcha,
::generateUriForWebAuth, ::generateUriForWebAuth,
String::base64UrlEncode, String::base64UrlDecodeOrNull,
) )
mockkStatic(Uri::class) mockkStatic(Uri::class)
every { every {