From f2cc7b5eb2abe3db03165997f8ff694114daab8a Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 8 Jun 2023 13:36:07 +0200 Subject: [PATCH 01/11] feat: domain look up in sso login screen --- .gitignore | 1 + .../android/di/AuthServerConfigProvider.kt | 1 + .../com/wire/android/di/CoreLogicModule.kt | 9 --- .../com/wire/android/ui/WireActivity.kt | 17 ++-- .../wire/android/ui/WireActivityViewModel.kt | 27 ++++--- .../common/CreateAccountBaseViewModel.kt | 10 ++- .../CreatePersonalAccountViewModel.kt | 7 +- .../create/team/CreateTeamViewModel.kt | 7 +- .../ui/authentication/login/LoginState.kt | 8 +- .../ui/authentication/login/LoginViewModel.kt | 28 +++++-- .../login/email/LoginEmailViewModel.kt | 12 ++- .../login/sso/LoginSSOScreen.kt | 58 +++++++++----- .../login/sso/LoginSSOViewModel.kt | 80 +++++++++++++++++-- .../common/dialogs/CustomBEDeeplinkDialog.kt | 80 ------------------- .../ui/common/dialogs/CustomServerDialog.kt | 73 +++++++++++++++++ .../android/ui/WireActivityViewModelTest.kt | 9 ++- .../ui/authentication/LoginViewModelTest.kt | 7 +- .../login/email/LoginEmailViewModelTest.kt | 7 +- .../login/sso/LoginSSOViewModelTest.kt | 13 ++- kalium | 2 +- 20 files changed, 294 insertions(+), 162 deletions(-) delete mode 100644 app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomBEDeeplinkDialog.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt diff --git a/.gitignore b/.gitignore index dc5f18eaedf..381eea0081f 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,4 @@ lint/tmp/ # Autogenerated file with git hash information. app/src/main/assets/version.txt +/intellij.gdsl diff --git a/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt b/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt index 53da1d8fb9b..cfcff961df1 100644 --- a/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt +++ b/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt @@ -51,4 +51,5 @@ class AuthServerConfigProvider @Inject constructor() { fun updateAuthServer(serverConfig: ServerConfig) { _authServer.value = serverConfig.links } + fun defaultServerLinks() = defaultBackendConfigs } diff --git a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt index 2473da780ea..de13e9640b1 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -38,7 +38,6 @@ import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.LogoutUseCase -import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.FlipToBackCameraUseCase import com.wire.kalium.logic.feature.call.usecase.FlipToFrontCameraUseCase @@ -926,14 +925,6 @@ class UseCaseModule { ): ObserveSecurityClassificationLabelUseCase = coreLogic.getSessionScope(currentAccount).observeSecurityClassificationLabel - @ViewModelScoped - @Provides - fun provideAutoVersionAuthScopeUseCase( - @KaliumCoreLogic coreLogic: CoreLogic, - authServerConfigProvider: AuthServerConfigProvider - ): AutoVersionAuthScopeUseCase = - coreLogic.versionedAuthenticationScope(authServerConfigProvider.authServer.value) - @ViewModelScoped @Provides fun provideIsCallRunningUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt index c7b53b3b62d..ad24129dc32 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt @@ -59,7 +59,7 @@ import com.wire.android.ui.calling.ProximitySensorManager import com.wire.android.ui.common.WireDialog import com.wire.android.ui.common.WireDialogButtonProperties import com.wire.android.ui.common.WireDialogButtonType -import com.wire.android.ui.common.dialogs.CustomBEDeeplinkDialog +import com.wire.android.ui.common.dialogs.CustomServerDialog import com.wire.android.ui.common.topappbar.CommonTopAppBar import com.wire.android.ui.common.topappbar.CommonTopAppBarViewModel import com.wire.android.ui.common.wireDialogPropertiesBuilder @@ -214,7 +214,7 @@ class WireActivity : AppCompatActivity() { private fun handleDialogs() { updateAppDialog({ updateTheApp() }, viewModel.globalAppState.updateAppDialog) joinConversationDialog(viewModel.globalAppState.conversationJoinedDialog) - customBackendDialog(viewModel.globalAppState.customBackendDialog.shouldShowDialog) + customBackendDialog() maxAccountDialog(viewModel::openProfile, viewModel::dismissMaxAccountDialog, viewModel.globalAppState.maxAccountDialog) accountLoggedOutDialog(viewModel.globalAppState.blockUserUI) newClientDialog( @@ -266,9 +266,16 @@ class WireActivity : AppCompatActivity() { } @Composable - private fun customBackendDialog(shouldShow: Boolean) { - if (shouldShow) { - CustomBEDeeplinkDialog(viewModel) + private fun customBackendDialog() { + with(viewModel) { + if (globalAppState.customBackendDialog != null) { + CustomServerDialog( + serverLinksTitle = globalAppState.customBackendDialog!!.serverLinks.title, + serverLinksApi = globalAppState.customBackendDialog!!.serverLinks.api, + onDismiss = this::dismissCustomBackendDialog, + onConfirm = this::customBackendDialogProceedButtonClicked + ) + } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt index 1d51c0346f5..af297d02f55 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt @@ -40,7 +40,7 @@ import com.wire.android.navigation.NavigationItem import com.wire.android.navigation.NavigationManager import com.wire.android.services.ServicesManager import com.wire.android.ui.authentication.devices.model.displayName -import com.wire.android.ui.common.dialogs.CustomBEDeeplinkDialogState +import com.wire.android.ui.common.dialogs.CustomServerDialogState import com.wire.android.ui.joinConversation.JoinConversationViaCodeState import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager @@ -275,17 +275,19 @@ class WireActivityViewModel @Inject constructor( } fun dismissCustomBackendDialog() { - globalAppState = globalAppState.copy(customBackendDialog = CustomBEDeeplinkDialogState(shouldShowDialog = false)) + globalAppState = globalAppState.copy(customBackendDialog = null) } - fun customBackendDialogProceedButtonClicked(serverLinks: ServerConfig.Links) { - viewModelScope.launch { - dismissCustomBackendDialog() - authServerConfigProvider.updateAuthServer(serverLinks) - if (checkNumberOfSessions() == BuildConfig.MAX_ACCOUNTS) { - globalAppState = globalAppState.copy(maxAccountDialog = true) - } else { - navigateTo(NavigationCommand(NavigationItem.Welcome.getRouteWithArgs())) + fun customBackendDialogProceedButtonClicked() { + if (globalAppState.customBackendDialog != null) { + viewModelScope.launch { + authServerConfigProvider.updateAuthServer(globalAppState.customBackendDialog!!.serverLinks) + dismissCustomBackendDialog() + if (checkNumberOfSessions() == BuildConfig.MAX_ACCOUNTS) { + globalAppState = globalAppState.copy(maxAccountDialog = true) + } else { + navigateTo(NavigationCommand(NavigationItem.Welcome.getRouteWithArgs())) + } } } } @@ -364,8 +366,7 @@ class WireActivityViewModel @Inject constructor( private suspend fun onCustomServerConfig(result: DeepLinkResult.CustomServerConfig) { loadServerConfig(result.url)?.let { serverLinks -> globalAppState = globalAppState.copy( - customBackendDialog = CustomBEDeeplinkDialogState( - shouldShowDialog = true, + customBackendDialog = CustomServerDialogState( serverLinks = serverLinks ) ) @@ -527,7 +528,7 @@ sealed class NewClientData(open val date: String, open val deviceInfo: UIText) { } data class GlobalAppState( - val customBackendDialog: CustomBEDeeplinkDialogState = CustomBEDeeplinkDialogState(), + val customBackendDialog: CustomServerDialogState? = null, val maxAccountDialog: Boolean = false, val blockUserUI: CurrentSessionErrorState? = null, val updateAppDialog: Boolean = false, diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt index 118e621cc6d..42ca4d7624a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt @@ -29,6 +29,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.ClientScopeProvider +import com.wire.android.di.KaliumCoreLogic import com.wire.android.navigation.BackStackMode import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.NavigationItem @@ -42,6 +43,7 @@ import com.wire.android.ui.authentication.create.email.CreateAccountEmailViewSta import com.wire.android.ui.authentication.create.overview.CreateAccountOverviewViewModel import com.wire.android.ui.common.textfield.CodeFieldValue import com.wire.android.util.WillNeverOccurError +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase @@ -64,7 +66,7 @@ abstract class CreateAccountBaseViewModel( private val navigationManager: NavigationManager, private val validateEmailUseCase: ValidateEmailUseCase, private val validatePasswordUseCase: ValidatePasswordUseCase, - private val authScope: AutoVersionAuthScopeUseCase, + @KaliumCoreLogic private val coreLogic: CoreLogic, private val addAuthenticatedUser: AddAuthenticatedUserUseCase, private val clientScopeProviderFactory: ClientScopeProvider.Factory, private val authServerConfigProvider: AuthServerConfigProvider, @@ -157,7 +159,7 @@ abstract class CreateAccountBaseViewModel( final override fun onTermsAccept() { emailState = emailState.copy(loading = true, continueEnabled = false, termsDialogVisible = false, termsAccepted = true) viewModelScope.launch { - val authScope = authScope().let { + val authScope = coreLogic.versionedAuthenticationScope(serverConfig)().let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope @@ -251,7 +253,7 @@ abstract class CreateAccountBaseViewModel( final override fun resendCode() { codeState = codeState.copy(loading = true) viewModelScope.launch { - val authScope = authScope().let { + val authScope = coreLogic.versionedAuthenticationScope(serverConfig)().let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope @@ -278,7 +280,7 @@ abstract class CreateAccountBaseViewModel( private fun onCodeContinue() { codeState = codeState.copy(loading = true) viewModelScope.launch { - val authScope = authScope().let { + val authScope = coreLogic.versionedAuthenticationScope(serverConfig)().let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/personalaccount/CreatePersonalAccountViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/personalaccount/CreatePersonalAccountViewModel.kt index cdd72e38ed1..dc8b5c30bfa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/personalaccount/CreatePersonalAccountViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/personalaccount/CreatePersonalAccountViewModel.kt @@ -24,16 +24,17 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.ClientScopeProvider +import com.wire.android.di.KaliumCoreLogic import com.wire.android.navigation.BackStackMode import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.NavigationItem import com.wire.android.navigation.NavigationManager import com.wire.android.ui.authentication.create.common.CreateAccountBaseViewModel import com.wire.android.ui.authentication.create.common.CreateAccountFlowType +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase -import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.server.FetchApiVersionUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -47,7 +48,7 @@ class CreatePersonalAccountViewModel @Inject constructor( private val navigationManager: NavigationManager, validateEmailUseCase: ValidateEmailUseCase, validatePasswordUseCase: ValidatePasswordUseCase, - authScope: AutoVersionAuthScopeUseCase, + @KaliumCoreLogic coreLogic: CoreLogic, addAuthenticatedUserUseCase: AddAuthenticatedUserUseCase, clientScopeProviderFactory: ClientScopeProvider.Factory, authServerConfigProvider: AuthServerConfigProvider, @@ -58,7 +59,7 @@ class CreatePersonalAccountViewModel @Inject constructor( navigationManager, validateEmailUseCase, validatePasswordUseCase, - authScope, + coreLogic, addAuthenticatedUserUseCase, clientScopeProviderFactory, authServerConfigProvider, diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/team/CreateTeamViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/team/CreateTeamViewModel.kt index 0997613851c..36204b21ac9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/team/CreateTeamViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/team/CreateTeamViewModel.kt @@ -24,16 +24,17 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.ClientScopeProvider +import com.wire.android.di.KaliumCoreLogic import com.wire.android.navigation.BackStackMode import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.NavigationItem import com.wire.android.navigation.NavigationManager import com.wire.android.ui.authentication.create.common.CreateAccountBaseViewModel import com.wire.android.ui.authentication.create.common.CreateAccountFlowType +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase -import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.server.FetchApiVersionUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -47,7 +48,7 @@ class CreateTeamViewModel @Inject constructor( private val navigationManager: NavigationManager, validateEmailUseCase: ValidateEmailUseCase, validatePasswordUseCase: ValidatePasswordUseCase, - authScope: AutoVersionAuthScopeUseCase, + @KaliumCoreLogic coreLogic: CoreLogic, addAuthenticatedUserUseCase: AddAuthenticatedUserUseCase, clientScopeProviderFactory: ClientScopeProvider.Factory, authServerConfigProvider: AuthServerConfigProvider, @@ -58,7 +59,7 @@ class CreateTeamViewModel @Inject constructor( navigationManager, validateEmailUseCase, validatePasswordUseCase, - authScope, + coreLogic, addAuthenticatedUserUseCase, clientScopeProviderFactory, authServerConfigProvider, diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt index d8f8deaeb6c..85437ab32bc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt @@ -21,12 +21,13 @@ package com.wire.android.ui.authentication.login import androidx.compose.ui.text.input.TextFieldValue +import com.wire.android.ui.common.dialogs.CustomServerDialogState data class LoginState( val userIdentifier: TextFieldValue = TextFieldValue(""), val userIdentifierEnabled: Boolean = true, val password: TextFieldValue = TextFieldValue(""), - val ssoCode: TextFieldValue = TextFieldValue(""), + val userInput: TextFieldValue = TextFieldValue(""), val proxyIdentifier: TextFieldValue = TextFieldValue(""), val proxyPassword: TextFieldValue = TextFieldValue(""), val ssoLoginLoading: Boolean = false, @@ -35,7 +36,8 @@ data class LoginState( val emailLoginEnabled: Boolean = false, val isProxyAuthRequired: Boolean = false, val loginError: LoginError = LoginError.None, - val isProxyEnabled: Boolean = false + val isProxyEnabled: Boolean = false, + val customServerDialogState: CustomServerDialogState? = null, ) fun LoginState.updateEmailLoginEnabled() = @@ -45,4 +47,4 @@ fun LoginState.updateEmailLoginEnabled() = ) fun LoginState.updateSSOLoginEnabled() = - copy(ssoLoginEnabled = ssoCode.text.isNotEmpty() && !ssoLoginLoading) + copy(ssoLoginEnabled = userInput.text.isNotEmpty() && !ssoLoginLoading) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt index 1dd3d8ecd98..4ba63b2a03f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt @@ -31,18 +31,23 @@ import androidx.lifecycle.viewModelScope import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.ClientScopeProvider +import com.wire.android.di.KaliumCoreLogic import com.wire.android.navigation.BackStackMode -import com.wire.android.navigation.EXTRA_USER_HANDLE import com.wire.android.navigation.EXTRA_SSO_LOGIN_RESULT +import com.wire.android.navigation.EXTRA_USER_HANDLE import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.NavigationItem import com.wire.android.navigation.NavigationManager import com.wire.android.util.EMPTY import com.wire.android.util.deeplink.DeepLinkResult +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.data.client.ClientCapability import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.AuthenticationResult +import com.wire.kalium.logic.feature.auth.DomainLookupUseCase +import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.client.RegisterClientResult import com.wire.kalium.logic.feature.client.RegisterClientUseCase import dagger.hilt.android.lifecycle.HiltViewModel @@ -58,10 +63,22 @@ open class LoginViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val navigationManager: NavigationManager, private val clientScopeProviderFactory: ClientScopeProvider.Factory, - authServerConfigProvider: AuthServerConfigProvider, - private val userDataStoreProvider: UserDataStoreProvider + protected val authServerConfigProvider: AuthServerConfigProvider, + private val userDataStoreProvider: UserDataStoreProvider, + @KaliumCoreLogic protected val coreLogic: CoreLogic ) : ViewModel() { - val serverConfig = authServerConfigProvider.authServer.value + var serverConfig: ServerConfig.Links by mutableStateOf(authServerConfigProvider.authServer.value) + private set + + init { + viewModelScope.launch { + authServerConfigProvider.authServer.collect { + serverConfig = it + } + } + } + + protected suspend fun authScope(): AutoVersionAuthScopeUseCase.Result = coreLogic.versionedAuthenticationScope(serverConfig)() private val preFilledUserIdentifier: PreFilledUserIdentifierType = savedStateHandle.get(EXTRA_USER_HANDLE).let { if (it.isNullOrEmpty()) PreFilledUserIdentifierType.None else PreFilledUserIdentifierType.PreFilled(it) @@ -74,7 +91,7 @@ open class LoginViewModel @Inject constructor( var loginState by mutableStateOf( LoginState( - ssoCode = TextFieldValue(savedStateHandle[SSO_CODE_SAVED_STATE_KEY] ?: String.EMPTY), + userInput = TextFieldValue(savedStateHandle[SSO_CODE_SAVED_STATE_KEY] ?: String.EMPTY), userIdentifier = TextFieldValue( if (preFilledUserIdentifier is PreFilledUserIdentifierType.PreFilled) preFilledUserIdentifier.userIdentifier else savedStateHandle[USER_IDENTIFIER_SAVED_STATE_KEY] ?: String.EMPTY @@ -184,6 +201,7 @@ fun RegisterClientResult.Failure.toLoginError() = when (this) { is RegisterClientResult.Failure.PasswordAuthRequired -> LoginError.DialogError.PasswordNeededToRegisterClient } +fun DomainLookupUseCase.Result.Failure.toLoginError() = LoginError.DialogError.GenericError(this.coreFailure) fun AddAuthenticatedUserUseCase.Result.Failure.toLoginError(): LoginError = when (this) { is AddAuthenticatedUserUseCase.Result.Failure.Generic -> LoginError.DialogError.GenericError(this.genericFailure) AddAuthenticatedUserUseCase.Result.Failure.UserAlreadyExists -> LoginError.DialogError.UserAlreadyExists diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt index 14ff25de23d..93e549b2887 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt @@ -29,6 +29,7 @@ import androidx.lifecycle.viewModelScope import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.ClientScopeProvider +import com.wire.android.di.KaliumCoreLogic import com.wire.android.navigation.NavigationManager import com.wire.android.ui.authentication.login.LoginError import com.wire.android.ui.authentication.login.LoginViewModel @@ -37,6 +38,7 @@ import com.wire.android.ui.authentication.login.updateEmailLoginEnabled import com.wire.android.ui.authentication.verificationcode.VerificationCodeState import com.wire.android.ui.common.textfield.CodeFieldValue import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.auth.login.ProxyCredentials import com.wire.kalium.logic.data.auth.verification.VerifiableAction import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase @@ -53,20 +55,21 @@ import javax.inject.Inject @Suppress("LongParameterList", "ComplexMethod") @HiltViewModel class LoginEmailViewModel @Inject constructor( - private val authScope: AutoVersionAuthScopeUseCase, private val addAuthenticatedUser: AddAuthenticatedUserUseCase, clientScopeProviderFactory: ClientScopeProvider.Factory, private val savedStateHandle: SavedStateHandle, navigationManager: NavigationManager, authServerConfigProvider: AuthServerConfigProvider, userDataStoreProvider: UserDataStoreProvider, - private val dispatchers: DispatcherProvider, + @KaliumCoreLogic coreLogic: CoreLogic, + private val dispatchers: DispatcherProvider ) : LoginViewModel( savedStateHandle, navigationManager, clientScopeProviderFactory, authServerConfigProvider, - userDataStoreProvider + userDataStoreProvider, + coreLogic ) { var secondFactorVerificationCodeState by mutableStateOf( @@ -132,7 +135,8 @@ class LoginEmailViewModel @Inject constructor( } } - private suspend fun resolveCurrentAuthScope(): AuthenticationScope? = authScope( + private suspend fun resolveCurrentAuthScope(): AuthenticationScope? = + coreLogic.versionedAuthenticationScope(serverConfig).invoke( AutoVersionAuthScopeUseCase.ProxyAuthentication.UsernameAndPassword( ProxyCredentials(loginState.proxyIdentifier.text, loginState.proxyPassword.text) ) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt index 656cc781eff..6f590641cda 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt @@ -51,16 +51,15 @@ import com.wire.android.ui.authentication.login.LoginErrorDialog import com.wire.android.ui.authentication.login.LoginState import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WirePrimaryButton +import com.wire.android.ui.common.dialogs.CustomServerDialog import com.wire.android.ui.common.textfield.WireTextField import com.wire.android.ui.common.textfield.WireTextFieldState import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.util.CustomTabsHelper import com.wire.android.util.deeplink.DeepLinkResult -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch @Composable fun LoginSSOScreen( @@ -78,13 +77,14 @@ fun LoginSSOScreen( scrollState = scrollState, loginState = loginSSOViewModel.loginState, onCodeChange = loginSSOViewModel::onSSOCodeChange, - onDialogDismiss = loginSSOViewModel::onDialogDismiss, + onErrorDialogDismiss = loginSSOViewModel::onDialogDismiss, onRemoveDeviceOpen = loginSSOViewModel::onTooManyDevicesError, // TODO: replace with retrieved ServerConfig from sso login - onLoginButtonClick = suspend { loginSSOViewModel.login() }, - scope = scope, - ssoLoginResult, - serverTitle = loginSSOViewModel.serverConfig.title + onLoginButtonClick = loginSSOViewModel::login, + ssoLoginResult = ssoLoginResult, + serverTitle = loginSSOViewModel.serverConfig.title, + onCustomServerDialogDismiss = loginSSOViewModel::onCustomServerDialogDismiss, + onCustomServerDialogConfirm = loginSSOViewModel::onCustomServerDialogConfirm ) LaunchedEffect(loginSSOViewModel) { @@ -97,21 +97,26 @@ private fun LoginSSOContent( scrollState: ScrollState, loginState: LoginState, onCodeChange: (TextFieldValue) -> Unit, - onDialogDismiss: () -> Unit, + onErrorDialogDismiss: () -> Unit, onRemoveDeviceOpen: () -> Unit, - onLoginButtonClick: suspend () -> Unit, - scope: CoroutineScope, + onLoginButtonClick: () -> Unit, + onCustomServerDialogDismiss: () -> Unit, + onCustomServerDialogConfirm: () -> Unit, ssoLoginResult: DeepLinkResult.SSOLogin?, - // todo: temporary to show to pointing server serverTitle: String ) { Column( - modifier = Modifier.fillMaxHeight().verticalScroll(scrollState).padding(MaterialTheme.wireDimensions.spacing16x) + modifier = Modifier + .fillMaxHeight() + .verticalScroll(scrollState) + .padding(MaterialTheme.wireDimensions.spacing16x) ) { Spacer(modifier = Modifier.height(MaterialTheme.wireDimensions.spacing32x)) SSOCodeInput( - modifier = Modifier.fillMaxWidth().padding(bottom = MaterialTheme.wireDimensions.spacing16x), - ssoCode = loginState.ssoCode, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = MaterialTheme.wireDimensions.spacing16x), + ssoCode = loginState.userInput, onCodeChange = onCodeChange, error = when (loginState.loginError) { LoginError.TextFieldError.InvalidValue -> stringResource(R.string.login_error_invalid_sso_code_format) @@ -121,14 +126,26 @@ private fun LoginSSOContent( ) Spacer(modifier = Modifier.weight(1f)) LoginButton( - modifier = Modifier.fillMaxWidth(), loading = loginState.ssoLoginLoading, enabled = loginState.ssoLoginEnabled - ) { scope.launch { onLoginButtonClick() } } + modifier = Modifier.fillMaxWidth(), + loading = loginState.ssoLoginLoading, + enabled = loginState.ssoLoginEnabled, + onClick = onLoginButtonClick + ) } if (loginState.loginError is LoginError.DialogError) { - LoginErrorDialog(loginState.loginError, onDialogDismiss, {}, ssoLoginResult) + LoginErrorDialog(loginState.loginError, onErrorDialogDismiss, {}, ssoLoginResult) } else if (loginState.loginError is LoginError.TooManyDevicesError) { onRemoveDeviceOpen() } + + if (loginState.customServerDialogState != null) { + CustomServerDialog( + serverLinksTitle = loginState.customServerDialogState.serverLinks.title, + serverLinksApi = loginState.customServerDialogState.serverLinks.api, + onDismiss = onCustomServerDialogDismiss, + onConfirm = onCustomServerDialogConfirm + ) + } } @Composable @@ -137,7 +154,6 @@ private fun SSOCodeInput( ssoCode: TextFieldValue, error: String?, onCodeChange: (TextFieldValue) -> Unit, - // TODO: temporary to show to pointing server serverTitle: String ) { WireTextField( @@ -162,7 +178,9 @@ private fun LoginButton(modifier: Modifier, loading: Boolean, enabled: Boolean, state = if (enabled) WireButtonState.Default else WireButtonState.Disabled, loading = loading, interactionSource = interactionSource, - modifier = Modifier.fillMaxWidth().testTag("ssoLoginButton") + modifier = Modifier + .fillMaxWidth() + .testTag("ssoLoginButton") ) } } @@ -171,6 +189,6 @@ private fun LoginButton(modifier: Modifier, loading: Boolean, enabled: Boolean, @Composable fun PreviewLoginSSOScreen() { WireTheme(isPreview = true) { - LoginSSOContent(rememberScrollState(), LoginState(), {}, {}, {}, suspend {}, rememberCoroutineScope(), null, "Test Server") + LoginSSOContent(rememberScrollState(), LoginState(), {}, {}, {}, {}, {}, {}, null, "Test Server") } } diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt index f170ce8f4a4..238013f9701 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt @@ -27,14 +27,20 @@ import androidx.lifecycle.viewModelScope import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.ClientScopeProvider +import com.wire.android.di.KaliumCoreLogic import com.wire.android.navigation.NavigationManager import com.wire.android.ui.authentication.login.LoginError import com.wire.android.ui.authentication.login.LoginViewModel import com.wire.android.ui.authentication.login.toLoginError import com.wire.android.ui.authentication.login.updateSSOLoginEnabled +import com.wire.android.ui.common.dialogs.CustomServerDialogState import com.wire.android.util.deeplink.DeepLinkResult import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase +import com.wire.kalium.logic.feature.auth.AuthenticationScope +import com.wire.kalium.logic.feature.auth.DomainLookupUseCase +import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginResult import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginUseCase @@ -49,8 +55,9 @@ import javax.inject.Inject @HiltViewModel class LoginSSOViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, - private val authScope: AutoVersionAuthScopeUseCase, private val addAuthenticatedUser: AddAuthenticatedUserUseCase, + private val validateEmailUseCase: ValidateEmailUseCase, + @KaliumCoreLogic coreLogic: CoreLogic, clientScopeProviderFactory: ClientScopeProvider.Factory, navigationManager: NavigationManager, authServerConfigProvider: AuthServerConfigProvider, @@ -60,13 +67,69 @@ class LoginSSOViewModel @Inject constructor( navigationManager, clientScopeProviderFactory, authServerConfigProvider, - userDataStoreProvider + userDataStoreProvider, + coreLogic ) { var openWebUrl = MutableSharedFlow() fun login() { loginState = loginState.copy(ssoLoginLoading = true, loginError = LoginError.None).updateSSOLoginEnabled() + + loginState.userInput.text.also { + if (validateEmailUseCase(it)) { + domainLookupFlow() + } else { + ssoLoginWithCodeFlow() + } + } + } + + fun onCustomServerDialogDismiss() { + loginState = loginState.copy(customServerDialogState = null) + } + + fun onCustomServerDialogConfirm() { + if (loginState.customServerDialogState != null) { + authServerConfigProvider.updateAuthServer(loginState.customServerDialogState!!.serverLinks) + } + loginState = loginState.copy(customServerDialogState = null) + } + + private fun domainLookupFlow() { + viewModelScope.launch { + val defaultAuthScope: AuthenticationScope = coreLogic.versionedAuthenticationScope(authServerConfigProvider.defaultServerLinks()).invoke().let { + when (it) { + is AutoVersionAuthScopeUseCase.Result.Failure.Generic, + AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion, + AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> { + loginState = loginState.copy(loginError = LoginError.DialogError.ServerVersionNotSupported) + return@launch + } + + is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope + } + } + + defaultAuthScope.domainLookup(loginState.userInput.text).also { + when (it) { + is DomainLookupUseCase.Result.Failure -> { + loginState = loginState.copy(ssoLoginLoading = false, loginError = it.toLoginError()) + } + + is DomainLookupUseCase.Result.Success -> { + loginState = loginState.copy( + ssoLoginLoading = false, + loginError = LoginError.None, + customServerDialogState = CustomServerDialogState(it.serverLinks) + ) + } + } + } + } + } + + private fun ssoLoginWithCodeFlow() { viewModelScope.launch { val authScope = authScope().let { @@ -77,17 +140,19 @@ class LoginSSOViewModel @Inject constructor( loginState = loginState.copy(loginError = LoginError.DialogError.ServerVersionNotSupported) return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { loginState = loginState.copy(loginError = LoginError.DialogError.ClientUpdateRequired) return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { return@launch } } } - authScope.ssoLoginScope.initiate(SSOInitiateLoginUseCase.Param.WithRedirect(loginState.ssoCode.text)).let { result -> + authScope.ssoLoginScope.initiate(SSOInitiateLoginUseCase.Param.WithRedirect(loginState.userInput.text)).let { result -> when (result) { is SSOInitiateLoginResult.Failure -> updateSSOLoginError(result.toLoginSSOError()) is SSOInitiateLoginResult.Success -> openWebUrl(result.requestUrl) @@ -110,10 +175,12 @@ class LoginSSOViewModel @Inject constructor( loginState = loginState.copy(loginError = LoginError.DialogError.ServerVersionNotSupported) return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { loginState = loginState.copy(loginError = LoginError.DialogError.ClientUpdateRequired) return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { return@launch } @@ -125,6 +192,7 @@ class LoginSSOViewModel @Inject constructor( updateSSOLoginError(it.toLoginError()) return@launch } + is SSOLoginSessionResult.Success -> it } } @@ -140,6 +208,7 @@ class LoginSSOViewModel @Inject constructor( updateSSOLoginError(it.toLoginError()) return@launch } + is AddAuthenticatedUserUseCase.Result.Success -> it.userId } } @@ -148,6 +217,7 @@ class LoginSSOViewModel @Inject constructor( is RegisterClientResult.Success -> { navigateAfterRegisterClientSuccess(storedUserId) } + is RegisterClientResult.Failure -> { updateSSOLoginError(it.toLoginError()) return@launch @@ -159,10 +229,10 @@ class LoginSSOViewModel @Inject constructor( fun onSSOCodeChange(newText: TextFieldValue) { // in case an error is showing e.g. inline error is should be cleared - if (loginState.loginError is LoginError.TextFieldError && newText != loginState.ssoCode) { + if (loginState.loginError is LoginError.TextFieldError && newText != loginState.userInput) { clearSSOLoginError() } - loginState = loginState.copy(ssoCode = newText).updateSSOLoginEnabled() + loginState = loginState.copy(userInput = newText).updateSSOLoginEnabled() savedStateHandle.set(SSO_CODE_SAVED_STATE_KEY, newText.text) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomBEDeeplinkDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomBEDeeplinkDialog.kt deleted file mode 100644 index ed8823bf018..00000000000 --- a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomBEDeeplinkDialog.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Wire - * Copyright (C) 2023 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - * - */ - -package com.wire.android.ui.common.dialogs - -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.wire.android.R -import com.wire.android.ui.WireActivityViewModel -import com.wire.android.ui.common.WireDialog -import com.wire.android.ui.common.WireDialogButtonProperties -import com.wire.android.ui.common.WireDialogButtonType -import com.wire.android.ui.common.button.WireButtonState -import com.wire.android.ui.common.colorsScheme -import com.wire.android.ui.common.wireDialogPropertiesBuilder -import com.wire.android.ui.theme.wireTypography -import com.wire.android.util.ui.stringWithStyledArgs -import com.wire.kalium.logic.configuration.server.ServerConfig - -@Composable -internal fun CustomBEDeeplinkDialog( - wireActivityViewModel: WireActivityViewModel -) { - with(wireActivityViewModel) { - WireDialog( - title = stringResource(R.string.custom_backend_dialog_title), - properties = wireDialogPropertiesBuilder( - dismissOnBackPress = false, - dismissOnClickOutside = false - ), - text = LocalContext.current.resources.stringWithStyledArgs( - R.string.custom_backend_dialog_body, - MaterialTheme.wireTypography.body01, - MaterialTheme.wireTypography.body02, - colorsScheme().onBackground, - colorsScheme().onBackground, - wireActivityViewModel.globalAppState.customBackendDialog.serverLinks.title, - wireActivityViewModel.globalAppState.customBackendDialog.serverLinks.api - ), - - buttonsHorizontalAlignment = true, - onDismiss = { dismissCustomBackendDialog() }, - dismissButtonProperties = WireDialogButtonProperties( - onClick = { dismissCustomBackendDialog() }, - text = stringResource(id = R.string.label_cancel), - state = WireButtonState.Default - ), - optionButton1Properties = WireDialogButtonProperties( - onClick = { - customBackendDialogProceedButtonClicked(wireActivityViewModel.globalAppState.customBackendDialog.serverLinks) - }, - text = stringResource(id = R.string.label_proceed), - type = WireDialogButtonType.Primary, - state = - WireButtonState.Default - ) - ) - } -} - -data class CustomBEDeeplinkDialogState(val serverLinks: ServerConfig.Links = ServerConfig.DEFAULT, val shouldShowDialog: Boolean = false) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt new file mode 100644 index 00000000000..9db5fc7153e --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt @@ -0,0 +1,73 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + * + */ + +package com.wire.android.ui.common.dialogs + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import com.wire.android.R +import com.wire.android.ui.common.WireDialog +import com.wire.android.ui.common.WireDialogButtonProperties +import com.wire.android.ui.common.WireDialogButtonType +import com.wire.android.ui.common.button.WireButtonState +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.ui.stringWithStyledArgs +import com.wire.kalium.logic.configuration.server.ServerConfig + +@Composable +internal fun CustomServerDialog( + serverLinksTitle: String, + serverLinksApi: String, + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + WireDialog( + title = stringResource(R.string.custom_backend_dialog_title), + text = LocalContext.current.resources.stringWithStyledArgs( + R.string.custom_backend_dialog_body, + MaterialTheme.wireTypography.body01, + MaterialTheme.wireTypography.body02, + colorsScheme().onBackground, + colorsScheme().onBackground, + serverLinksTitle, + serverLinksApi + ), + + buttonsHorizontalAlignment = true, + onDismiss = onDismiss, + dismissButtonProperties = WireDialogButtonProperties( + onClick = onDismiss, + text = stringResource(id = R.string.label_cancel), + state = WireButtonState.Default + ), + optionButton1Properties = WireDialogButtonProperties( + onClick = onConfirm, + text = stringResource(id = R.string.label_proceed), + type = WireDialogButtonType.Primary, + state = + WireButtonState.Default + ) + ) +} + +data class CustomServerDialogState(val serverLinks: ServerConfig.Links) diff --git a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt index 6b043e4660b..b3a63004649 100644 --- a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt @@ -36,7 +36,7 @@ import com.wire.android.navigation.NavigationItem import com.wire.android.navigation.NavigationManager import com.wire.android.services.ServicesManager import com.wire.android.ui.authentication.devices.model.displayName -import com.wire.android.ui.common.dialogs.CustomBEDeeplinkDialogState +import com.wire.android.ui.common.dialogs.CustomServerDialogState import com.wire.android.ui.joinConversation.JoinConversationViaCodeState import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager @@ -45,6 +45,7 @@ import com.wire.android.util.deeplink.DeepLinkResult import com.wire.android.util.newServerConfig import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.user.UserId @@ -139,7 +140,7 @@ class WireActivityViewModelTest { coVerify(exactly = 0) { arrangement.navigationManager.navigate(any()) } assertEquals(NavigationItem.Home.getRouteWithArgs(), viewModel.startNavigationRoute()) - assertEquals(newServerConfig(1).links, viewModel.globalAppState.customBackendDialog.serverLinks) + assertEquals(newServerConfig(1).links, viewModel.globalAppState.customBackendDialog!!.serverLinks) } @Test @@ -153,7 +154,7 @@ class WireActivityViewModelTest { assertEquals(NavigationItem.Welcome.getRouteWithArgs(), viewModel.startNavigationRoute()) coVerify(exactly = 0) { arrangement.navigationManager.navigate(any()) } - assertEquals(newServerConfig(1).links, viewModel.globalAppState.customBackendDialog.serverLinks) + assertEquals(newServerConfig(1).links, viewModel.globalAppState.customBackendDialog!!.serverLinks) } @Test @@ -168,7 +169,7 @@ class WireActivityViewModelTest { assertEquals(NavigationItem.Migration.getRouteWithArgs(), viewModel.startNavigationRoute()) coVerify(exactly = 0) { arrangement.navigationManager.navigate(any()) } - assertEquals(CustomBEDeeplinkDialogState(), viewModel.globalAppState.customBackendDialog) + assertEquals(CustomServerDialogState(ServerConfig.STAGING), viewModel.globalAppState.customBackendDialog) } @Test diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/LoginViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/LoginViewModelTest.kt index 06df8bab32e..ea1c4faf68e 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/LoginViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/LoginViewModelTest.kt @@ -28,6 +28,7 @@ import com.wire.android.di.ClientScopeProvider import com.wire.android.navigation.NavigationManager import com.wire.android.ui.authentication.login.LoginViewModel import com.wire.android.util.newServerConfig +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.id.QualifiedIdMapper import io.mockk.MockKAnnotations @@ -62,6 +63,9 @@ class LoginViewModelTest { @MockK private lateinit var userDataStoreProvider: UserDataStoreProvider + @MockK + private lateinit var coreLogic: CoreLogic + private lateinit var loginViewModel: LoginViewModel @BeforeEach @@ -75,7 +79,8 @@ class LoginViewModelTest { navigationManager, clientScopeProviderFactory, authServerConfigProvider, - userDataStoreProvider + userDataStoreProvider, + coreLogic ) } diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModelTest.kt index ee9126c7316..106d23121e3 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModelTest.kt @@ -39,6 +39,7 @@ import com.wire.android.ui.common.textfield.CodeFieldValue import com.wire.android.util.EMPTY import com.wire.android.util.newServerConfig import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.configuration.server.CommonApiVersionType import com.wire.kalium.logic.configuration.server.ServerConfig @@ -112,6 +113,9 @@ class LoginEmailViewModelTest { @MockK private lateinit var autoVersionAuthScopeUseCase: AutoVersionAuthScopeUseCase + @MockK + private lateinit var coreLogic: CoreLogic + @MockK private lateinit var requestSecondFactorCodeUseCase: RequestSecondFactorVerificationCodeUseCase @@ -146,14 +150,15 @@ class LoginEmailViewModelTest { every { authenticationScope.login } returns loginUseCase every { authenticationScope.requestSecondFactorVerificationCode } returns requestSecondFactorCodeUseCase + every { coreLogic.versionedAuthenticationScope(any()) } returns autoVersionAuthScopeUseCase loginViewModel = LoginEmailViewModel( - autoVersionAuthScopeUseCase, addAuthenticatedUserUseCase, clientScopeProviderFactory, savedStateHandle, navigationManager, authServerConfigProvider, userDataStoreProvider, + coreLogic, TestDispatcherProvider() ) } diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index 3ff032d29ae..92b8b720bed 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -37,6 +37,7 @@ import com.wire.android.util.deeplink.DeepLinkResult import com.wire.android.util.deeplink.SSOFailureCodes import com.wire.android.util.newServerConfig import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.configuration.server.CommonApiVersionType import com.wire.kalium.logic.configuration.server.ServerConfig @@ -50,6 +51,7 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.AuthTokens import com.wire.kalium.logic.feature.auth.AuthenticationScope +import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.auth.sso.GetSSOLoginSessionUseCase import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginResult @@ -114,11 +116,18 @@ class LoginSSOViewModelTest { @MockK private lateinit var autoVersionAuthScopeUseCase: AutoVersionAuthScopeUseCase + @MockK + private lateinit var coreLogic: CoreLogic + @MockK private lateinit var authenticationScope: AuthenticationScope @MockK private lateinit var navigationManager: NavigationManager + + @MockK + private lateinit var validateEmailUseCase: ValidateEmailUseCase + private lateinit var loginViewModel: LoginSSOViewModel private val userId: QualifiedID = QualifiedID("userId", "domain") @@ -139,11 +148,13 @@ class LoginSSOViewModelTest { ) every { authenticationScope.ssoLoginScope.initiate } returns ssoInitiateLoginUseCase every { authenticationScope.ssoLoginScope.getLoginSession } returns getSSOLoginSessionUseCase + every { coreLogic.versionedAuthenticationScope(any()) } returns autoVersionAuthScopeUseCase loginViewModel = LoginSSOViewModel( savedStateHandle, - autoVersionAuthScopeUseCase, addAuthenticatedUserUseCase, + validateEmailUseCase, + coreLogic, clientScopeProviderFactory, navigationManager, authServerConfigProvider, diff --git a/kalium b/kalium index 3bf2c1089b8..049269a72ff 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 3bf2c1089b879ec1fb6008b765d8aa8eb32bde6d +Subproject commit 049269a72fff09a80cd215a62c71b212142deb51 From 48857bbd4ecf2c7aba649b2c4498aed4fe5ad116 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 8 Jun 2023 15:50:38 +0200 Subject: [PATCH 02/11] fix tests --- .../android/ui/WireActivityViewModelTest.kt | 8 +++--- .../login/sso/LoginSSOViewModelTest.kt | 25 ++++++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt index b3a63004649..dc1f39c3f88 100644 --- a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt @@ -36,7 +36,6 @@ import com.wire.android.navigation.NavigationItem import com.wire.android.navigation.NavigationManager import com.wire.android.services.ServicesManager import com.wire.android.ui.authentication.devices.model.displayName -import com.wire.android.ui.common.dialogs.CustomServerDialogState import com.wire.android.ui.joinConversation.JoinConversationViaCodeState import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager @@ -45,7 +44,6 @@ import com.wire.android.util.deeplink.DeepLinkResult import com.wire.android.util.newServerConfig import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.CoreLogic -import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.user.UserId @@ -163,13 +161,14 @@ class WireActivityViewModelTest { .withNoCurrentSession() .withMigrationRequired() .withDeepLinkResult(DeepLinkResult.CustomServerConfig("url")) + .withCurrentScreen(MutableStateFlow(CurrentScreen.Home)) .arrange() viewModel.handleDeepLink(mockedIntent()) assertEquals(NavigationItem.Migration.getRouteWithArgs(), viewModel.startNavigationRoute()) coVerify(exactly = 0) { arrangement.navigationManager.navigate(any()) } - assertEquals(CustomServerDialogState(ServerConfig.STAGING), viewModel.globalAppState.customBackendDialog) + assertEquals(null, viewModel.globalAppState.customBackendDialog) } @Test @@ -678,8 +677,7 @@ class WireActivityViewModelTest { @MockK lateinit var getSessionsUseCase: GetSessionsUseCase - @MockK - private lateinit var authServerConfigProvider: AuthServerConfigProvider + var authServerConfigProvider: AuthServerConfigProvider = AuthServerConfigProvider() @MockK private lateinit var switchAccount: AccountSwitchUseCase diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index 92b8b720bed..32f09c8d98d 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -107,8 +107,7 @@ class LoginSSOViewModelTest { @MockK private lateinit var getSSOLoginSessionUseCase: GetSSOLoginSessionUseCase - @MockK - private lateinit var authServerConfigProvider: AuthServerConfigProvider + private val authServerConfigProvider: AuthServerConfigProvider = AuthServerConfigProvider() @MockK private lateinit var userDataStoreProvider: UserDataStoreProvider @@ -140,7 +139,9 @@ class LoginSSOViewModelTest { every { savedStateHandle.set(any(), any()) } returns Unit every { clientScopeProviderFactory.create(any()).clientScope } returns clientScope every { clientScope.getOrRegister } returns getOrRegisterClientUseCase - every { authServerConfigProvider.authServer.value } returns newServerConfig(1).links + + authServerConfigProvider.updateAuthServer(newServerConfig(1).links) + coEvery { autoVersionAuthScopeUseCase() } returns AutoVersionAuthScopeUseCase.Result.Success( @@ -177,9 +178,10 @@ class LoginSSOViewModelTest { } @Test - fun `given button is clicked, when logging in, then show loading`() { + fun `given sso code and button is clicked, when logging in, then show loading`() { val scheduler = TestCoroutineScheduler() Dispatchers.setMain(StandardTestDispatcher(scheduler)) + every { validateEmailUseCase(any()) } returns false coEvery { ssoInitiateLoginUseCase(any()) } returns SSOInitiateLoginResult.Success("") loginViewModel.onSSOCodeChange(TextFieldValue("abc")) @@ -194,13 +196,14 @@ class LoginSSOViewModelTest { } @Test - fun `given button is clicked, when login returns Success, then open the web url from the response`() { + fun `given sso code and button is clicked, when login returns Success, then open the web url from the response`() { val scheduler = TestCoroutineScheduler() Dispatchers.setMain(StandardTestDispatcher(scheduler)) val ssoCode = "wire-fd994b20-b9af-11ec-ae36-00163e9b33ca" val param = SSOInitiateLoginUseCase.Param.WithRedirect(ssoCode) val url = "https://wire.com/sso" coEvery { ssoInitiateLoginUseCase(param) } returns SSOInitiateLoginResult.Success(url) + every { validateEmailUseCase(any()) } returns false loginViewModel.onSSOCodeChange(TextFieldValue(ssoCode)) runTest { @@ -212,8 +215,9 @@ class LoginSSOViewModelTest { } @Test - fun `given button is clicked, when login returns InvalidCodeFormat error, then InvalidCodeFormatError is passed`() { + fun `given sso code and button is clicked, when login returns InvalidCodeFormat error, then InvalidCodeFormatError is passed`() { coEvery { ssoInitiateLoginUseCase(any()) } returns SSOInitiateLoginResult.Failure.InvalidCodeFormat + every { validateEmailUseCase(any()) } returns false runTest { loginViewModel.login() } @@ -221,8 +225,9 @@ class LoginSSOViewModelTest { } @Test - fun `given button is clicked, when login returns InvalidCode error, then InvalidCodeError is passed`() { + fun `given sso code and button is clicked, when login returns InvalidCode error, then InvalidCodeError is passed`() { coEvery { ssoInitiateLoginUseCase(any()) } returns SSOInitiateLoginResult.Failure.InvalidCode + every { validateEmailUseCase(any()) } returns false runTest { loginViewModel.login() } @@ -230,8 +235,9 @@ class LoginSSOViewModelTest { } @Test - fun `given button is clicked, when login returns InvalidRequest error, then GenericError IllegalArgument is passed`() { + fun `given sso code and button is clicked, when login returns InvalidRequest error, then GenericError IllegalArgument is passed`() { coEvery { ssoInitiateLoginUseCase(any()) } returns SSOInitiateLoginResult.Failure.InvalidRedirect + every { validateEmailUseCase(any()) } returns false runTest { loginViewModel.login() } @@ -245,9 +251,10 @@ class LoginSSOViewModelTest { } @Test - fun `given button is clicked, when login returns Generic error, then GenericError is passed`() { + fun `given sso code and button is clicked, when login returns Generic error, then GenericError is passed`() { val networkFailure = NetworkFailure.NoNetworkConnection(null) coEvery { ssoInitiateLoginUseCase(any()) } returns SSOInitiateLoginResult.Failure.Generic(networkFailure) + every { validateEmailUseCase(any()) } returns false runTest { loginViewModel.login() } From 9a17d082734541d0ae9c3dd662082dedf74ce4cf Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 8 Jun 2023 17:31:49 +0200 Subject: [PATCH 03/11] add unit test --- .../ui/authentication/login/LoginViewModel.kt | 3 ++- .../login/sso/LoginSSOViewModel.kt | 3 ++- .../login/sso/LoginSSOViewModelTest.kt | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt index 4ba63b2a03f..e6d9e60e021 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt @@ -104,7 +104,8 @@ open class LoginViewModel @Inject constructor( isProxyEnabled = serverConfig.apiProxy != null ) ) - protected set + @VisibleForTesting + set open fun updateSSOLoginError(error: LoginError) { loginState = if (error is LoginError.None) { diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt index 238013f9701..63c94d0389d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt @@ -96,7 +96,8 @@ class LoginSSOViewModel @Inject constructor( loginState = loginState.copy(customServerDialogState = null) } - private fun domainLookupFlow() { + @VisibleForTesting + fun domainLookupFlow() { viewModelScope.launch { val defaultAuthScope: AuthenticationScope = coreLogic.versionedAuthenticationScope(authServerConfigProvider.defaultServerLinks()).invoke().let { when (it) { diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index 32f09c8d98d..b632cfe9765 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -32,6 +32,7 @@ import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.NavigationItemDestinationsRoutes import com.wire.android.navigation.NavigationManager import com.wire.android.ui.authentication.login.LoginError +import com.wire.android.ui.common.dialogs.CustomServerDialogState import com.wire.android.util.EMPTY import com.wire.android.util.deeplink.DeepLinkResult import com.wire.android.util.deeplink.SSOFailureCodes @@ -51,6 +52,7 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.AuthTokens import com.wire.kalium.logic.feature.auth.AuthenticationScope +import com.wire.kalium.logic.feature.auth.DomainLookupUseCase import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.auth.sso.GetSSOLoginSessionUseCase @@ -75,6 +77,7 @@ import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import kotlinx.datetime.Instant +import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeInstanceOf @@ -384,6 +387,30 @@ class LoginSSOViewModelTest { verify(exactly = 0) { loginViewModel.navigateAfterRegisterClientSuccess(any()) } } + @Test + fun `given email, when clicking login, then start the domain lookup flow`() { + val expected = newServerConfig(2).links + every { validateEmailUseCase(any()) } returns true + coEvery { authenticationScope.domainLookup(any()) } returns DomainLookupUseCase.Result.Success(expected) + loginViewModel.onSSOCodeChange(TextFieldValue("email@wire.com")) + + runTest { loginViewModel.domainLookupFlow() } + + coVerify(exactly = 1) { authenticationScope.domainLookup("email@wire.com") } + assertEquals(expected, loginViewModel.loginState.customServerDialogState!!.serverLinks) + } + + @Test + fun `given backend switch confirmed, then auth server provider is updated`() { + val expected = newServerConfig(2).links + every { validateEmailUseCase(any()) } returns true + loginViewModel.loginState = loginViewModel.loginState.copy(customServerDialogState = CustomServerDialogState(expected)) + + loginViewModel.onCustomServerDialogConfirm() + + assertEquals(authServerConfigProvider.authServer.value, expected) + } + companion object { val CLIENT_ID = ClientId("test") val CLIENT = Client( From 7477a7ebdf056cfb287aee73079c68c265e1b2e2 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 8 Jun 2023 18:19:41 +0200 Subject: [PATCH 04/11] detekt --- .../android/di/AuthServerConfigProvider.kt | 3 ++- .../common/CreateAccountBaseViewModel.kt | 16 +++++++++++++ .../login/sso/LoginSSOViewModel.kt | 23 +++++++++++-------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt b/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt index cfcff961df1..17f70de068e 100644 --- a/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt +++ b/app/src/main/kotlin/com/wire/android/di/AuthServerConfigProvider.kt @@ -51,5 +51,6 @@ class AuthServerConfigProvider @Inject constructor() { fun updateAuthServer(serverConfig: ServerConfig) { _authServer.value = serverConfig.links } - fun defaultServerLinks() = defaultBackendConfigs + + fun defaultServerLinks() = defaultBackendConfigs } diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt index 42ca4d7624a..d1d833ab743 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt @@ -131,10 +131,12 @@ abstract class CreateAccountBaseViewModel( emailState = emailState.copy(showServerVersionNotSupportedDialog = true) return@launch } + is FetchApiVersionResult.Failure.TooNewVersion -> { emailState = emailState.copy(showClientUpdateDialog = true) return@launch } + is FetchApiVersionResult.Failure.Generic -> { return@launch } @@ -167,10 +169,12 @@ abstract class CreateAccountBaseViewModel( // TODO: show dialog return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { // TODO: show dialog return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { return@launch } @@ -225,8 +229,10 @@ abstract class CreateAccountBaseViewModel( val detailsError = when { !validatePasswordUseCase(detailsState.password.text) -> CreateAccountDetailsViewState.DetailsError.TextFieldError.InvalidPasswordError + detailsState.password.text != detailsState.confirmPassword.text -> CreateAccountDetailsViewState.DetailsError.TextFieldError.PasswordsNotMatchingError + else -> CreateAccountDetailsViewState.DetailsError.None } detailsState = detailsState.copy( @@ -261,10 +267,12 @@ abstract class CreateAccountBaseViewModel( // TODO: show dialog return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { // TODO: show dialog return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { return@launch } @@ -288,10 +296,12 @@ abstract class CreateAccountBaseViewModel( // TODO: show dialog return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { // TODO: show dialog return@launch } + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { return@launch } @@ -306,6 +316,7 @@ abstract class CreateAccountBaseViewModel( updateCodeErrorState(it.toCodeError()) return@launch } + is RegisterResult.Success -> it } } @@ -321,6 +332,7 @@ abstract class CreateAccountBaseViewModel( updateCodeErrorState(it.toCodeError()) return@launch } + is AddAuthenticatedUserUseCase.Result.Success -> it.userId } } @@ -330,6 +342,7 @@ abstract class CreateAccountBaseViewModel( updateCodeErrorState(it.toCodeError()) return@launch } + is RegisterClientResult.Success -> { onCodeSuccess() } @@ -347,6 +360,7 @@ abstract class CreateAccountBaseViewModel( email = emailState.email.text.trim().lowercase(), emailActivationCode = codeState.code.text.text ) + CreateAccountFlowType.CreateTeam -> RegisterParam.Team( firstName = detailsState.firstName.text.trim(), @@ -423,6 +437,7 @@ private fun RegisterClientResult.Failure.toCodeError() = when (this) { is RegisterClientResult.Failure.Generic -> CreateAccountCodeViewState.CodeError.DialogError.GenericError(this.genericFailure) is RegisterClientResult.Failure.InvalidCredentials -> throw WillNeverOccurError("RegisterClient: wrong password when register client after creating a new account") + is RegisterClientResult.Failure.PasswordAuthRequired -> throw WillNeverOccurError("RegisterClient: password required to register client after creating new account with email") } @@ -441,5 +456,6 @@ private fun RegisterResult.Failure.toCodeError() = when (this) { private fun AddAuthenticatedUserUseCase.Result.Failure.toCodeError() = when (this) { is AddAuthenticatedUserUseCase.Result.Failure.Generic -> CreateAccountCodeViewState.CodeError.DialogError.GenericError(this.genericFailure) + AddAuthenticatedUserUseCase.Result.Failure.UserAlreadyExists -> CreateAccountCodeViewState.CodeError.DialogError.UserAlreadyExists } diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt index 63c94d0389d..e559a2a940d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt @@ -99,18 +99,21 @@ class LoginSSOViewModel @Inject constructor( @VisibleForTesting fun domainLookupFlow() { viewModelScope.launch { - val defaultAuthScope: AuthenticationScope = coreLogic.versionedAuthenticationScope(authServerConfigProvider.defaultServerLinks()).invoke().let { - when (it) { - is AutoVersionAuthScopeUseCase.Result.Failure.Generic, - AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion, - AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> { - loginState = loginState.copy(loginError = LoginError.DialogError.ServerVersionNotSupported) - return@launch - } + val defaultAuthScope: AuthenticationScope = + coreLogic.versionedAuthenticationScope( + authServerConfigProvider.defaultServerLinks() + )().let { + when (it) { + is AutoVersionAuthScopeUseCase.Result.Failure.Generic, + AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion, + AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> { + loginState = loginState.copy(loginError = LoginError.DialogError.ServerVersionNotSupported) + return@launch + } - is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope + is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope + } } - } defaultAuthScope.domainLookup(loginState.userInput.text).also { when (it) { From 5e657b60995254ad0c5556cc569cdb20b0f02e47 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 8 Jun 2023 18:20:17 +0200 Subject: [PATCH 05/11] detekt --- .../authentication/create/common/CreateAccountBaseViewModel.kt | 1 - .../wire/android/ui/authentication/login/sso/LoginSSOScreen.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt index d1d833ab743..9c986734f9e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/CreateAccountBaseViewModel.kt @@ -376,7 +376,6 @@ abstract class CreateAccountBaseViewModel( private fun updateCodeErrorState(codeError: CreateAccountCodeViewState.CodeError) { codeState = if (codeError is CreateAccountCodeViewState.CodeError.None) { codeState.copy(error = codeError) - } else { codeState.copy(loading = false, error = codeError) } diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt index 6f590641cda..e510c7e6e06 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt @@ -189,6 +189,6 @@ private fun LoginButton(modifier: Modifier, loading: Boolean, enabled: Boolean, @Composable fun PreviewLoginSSOScreen() { WireTheme(isPreview = true) { - LoginSSOContent(rememberScrollState(), LoginState(), {}, {}, {}, {}, {}, {}, null, "Test Server") + LoginSSOContent(rememberScrollState(), LoginState(), {}, {}, {}, {}, {}, {}, null, "Test Server") } } From 4abde3892e0ea26c73668c4f5ff60a5556570474 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Wed, 14 Jun 2023 14:43:08 +0200 Subject: [PATCH 06/11] feat: sso login with domain open login webpage (#1852) * redirect to the webpage * redirect to the webpage * cleanup * unit tests * update kalium * update kalium * Update app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt Co-authored-by: Alexandre Ferris --------- Co-authored-by: Alexandre Ferris --- .../login/sso/LoginSSOViewModel.kt | 43 +++++++++++++++++-- .../controlbuttons/MicrophoneButton.kt | 1 - .../login/sso/LoginSSOViewModelTest.kt | 37 +++++++++++++++- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt index e559a2a940d..4e4c86038fd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt @@ -42,6 +42,7 @@ import com.wire.kalium.logic.feature.auth.AuthenticationScope import com.wire.kalium.logic.feature.auth.DomainLookupUseCase import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase +import com.wire.kalium.logic.feature.auth.sso.FetchSSOSettingsUseCase import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginResult import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginUseCase import com.wire.kalium.logic.feature.auth.sso.SSOLoginSessionResult @@ -90,10 +91,46 @@ class LoginSSOViewModel @Inject constructor( } fun onCustomServerDialogConfirm() { - if (loginState.customServerDialogState != null) { - authServerConfigProvider.updateAuthServer(loginState.customServerDialogState!!.serverLinks) + viewModelScope.launch { + if (loginState.customServerDialogState != null) { + authServerConfigProvider.updateAuthServer(loginState.customServerDialogState!!.serverLinks) + + val authScope = coreLogic.versionedAuthenticationScope(loginState.customServerDialogState!!.serverLinks)().let { + when (it) { + is AutoVersionAuthScopeUseCase.Result.Failure.Generic, + AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion, + AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> { + loginState = loginState.copy(customServerDialogState = null) + return@launch + } + + is AutoVersionAuthScopeUseCase.Result.Success -> { + it.authenticationScope + } + } + } + + authScope.ssoLoginScope.fetchSSOSettings().also { + when (it) { + is FetchSSOSettingsUseCase.Result.Failure -> {} + is FetchSSOSettingsUseCase.Result.Success -> { + it.defaultSSOCode?.let { ssoCode -> + val ssoCodeWithPrefix = if (ssoCode.startsWith("wire-")) ssoCode else "wire-$ssoCode" + authScope.ssoLoginScope.initiate( + SSOInitiateLoginUseCase.Param.WithRedirect(ssoCodeWithPrefix) + ).let { result -> + when (result) { + is SSOInitiateLoginResult.Failure -> updateSSOLoginError(result.toLoginSSOError()) + is SSOInitiateLoginResult.Success -> openWebUrl(result.requestUrl) + } + } + } + } + } + } + loginState = loginState.copy(customServerDialogState = null) + } } - loginState = loginState.copy(customServerDialogState = null) } @VisibleForTesting diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/MicrophoneButton.kt b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/MicrophoneButton.kt index 328780625b4..a197636be56 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/MicrophoneButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/MicrophoneButton.kt @@ -29,7 +29,6 @@ import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index b632cfe9765..353c59a8b38 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -55,6 +55,7 @@ import com.wire.kalium.logic.feature.auth.AuthenticationScope import com.wire.kalium.logic.feature.auth.DomainLookupUseCase import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase +import com.wire.kalium.logic.feature.auth.sso.FetchSSOSettingsUseCase import com.wire.kalium.logic.feature.auth.sso.GetSSOLoginSessionUseCase import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginResult import com.wire.kalium.logic.feature.auth.sso.SSOInitiateLoginUseCase @@ -130,6 +131,9 @@ class LoginSSOViewModelTest { @MockK private lateinit var validateEmailUseCase: ValidateEmailUseCase + @MockK + private lateinit var fetchSSOSettings: FetchSSOSettingsUseCase + private lateinit var loginViewModel: LoginSSOViewModel private val userId: QualifiedID = QualifiedID("userId", "domain") @@ -142,7 +146,7 @@ class LoginSSOViewModelTest { every { savedStateHandle.set(any(), any()) } returns Unit every { clientScopeProviderFactory.create(any()).clientScope } returns clientScope every { clientScope.getOrRegister } returns getOrRegisterClientUseCase - + every { authenticationScope.ssoLoginScope.fetchSSOSettings } returns fetchSSOSettings authServerConfigProvider.updateAuthServer(newServerConfig(1).links) coEvery { @@ -411,6 +415,37 @@ class LoginSSOViewModelTest { assertEquals(authServerConfigProvider.authServer.value, expected) } + @Test + fun `given backend switch confirmed, when the new server have a default sso code, then initiate sso login`() { + val expected = newServerConfig(2).links + every { validateEmailUseCase(any()) } returns true + coEvery { fetchSSOSettings.invoke() } returns FetchSSOSettingsUseCase.Result.Success("ssoCode") + loginViewModel.loginState = loginViewModel.loginState.copy(customServerDialogState = CustomServerDialogState(expected)) + + loginViewModel.onCustomServerDialogConfirm() + + assertEquals(authServerConfigProvider.authServer.value, expected) + coVerify(exactly = 1) { + fetchSSOSettings.invoke() + ssoInitiateLoginUseCase.invoke("wire-ssoCode") + } + } + + @Test + fun `given backend switch confirmed, when the new server have NO default sso code, then initiate sso login`() { + val expected = newServerConfig(2).links + every { validateEmailUseCase(any()) } returns true + coEvery { fetchSSOSettings.invoke() } returns FetchSSOSettingsUseCase.Result.Success(null) + loginViewModel.loginState = loginViewModel.loginState.copy(customServerDialogState = CustomServerDialogState(expected)) + + loginViewModel.onCustomServerDialogConfirm() + + assertEquals(authServerConfigProvider.authServer.value, expected) + coVerify(exactly = 1) { fetchSSOSettings.invoke() } + + coVerify(exactly = 0) { ssoInitiateLoginUseCase.invoke(any()) } + } + companion object { val CLIENT_ID = ClientId("test") val CLIENT = Client( From 29cabe4766062610f0da2fcf090f8e15a10b4262 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Wed, 5 Jul 2023 14:35:42 +0200 Subject: [PATCH 07/11] update copy --- .../ui/authentication/login/sso/LoginSSOScreen.kt | 9 +++------ app/src/main/res/values/strings.xml | 3 +-- .../ui/authentication/login/sso/LoginSSOViewModelTest.kt | 2 ++ kalium | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt index e510c7e6e06..7bf4585ce8f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOScreen.kt @@ -121,8 +121,7 @@ private fun LoginSSOContent( error = when (loginState.loginError) { LoginError.TextFieldError.InvalidValue -> stringResource(R.string.login_error_invalid_sso_code_format) else -> null - }, - serverTitle = serverTitle + } ) Spacer(modifier = Modifier.weight(1f)) LoginButton( @@ -153,14 +152,12 @@ private fun SSOCodeInput( modifier: Modifier, ssoCode: TextFieldValue, error: String?, - onCodeChange: (TextFieldValue) -> Unit, - serverTitle: String + onCodeChange: (TextFieldValue) -> Unit ) { WireTextField( value = ssoCode, onValueChange = onCodeChange, - placeholderText = stringResource(R.string.login_sso_code_placeholder), - labelText = stringResource(R.string.login_sso_code_label) + " on $serverTitle", + labelText = stringResource(R.string.login_sso_code_label), state = if (error != null) WireTextFieldState.Error(error) else WireTextFieldState.Default, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Next), modifier = modifier.testTag("ssoCodeField") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1cc51f73597..c8fe2172882 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -239,8 +239,7 @@ PASSWORD EMAIL SSO LOGIN - wire- - SSO CODE + SSO CODE OR EMAIL Please enter a valid SSO code. Enter a valid SSO code Deleted account diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index 353c59a8b38..823122590a4 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -157,6 +157,7 @@ class LoginSSOViewModelTest { every { authenticationScope.ssoLoginScope.initiate } returns ssoInitiateLoginUseCase every { authenticationScope.ssoLoginScope.getLoginSession } returns getSSOLoginSessionUseCase every { coreLogic.versionedAuthenticationScope(any()) } returns autoVersionAuthScopeUseCase + every { authenticationScope.ssoLoginScope.fetchSSOSettings } returns fetchSSOSettings loginViewModel = LoginSSOViewModel( savedStateHandle, @@ -440,6 +441,7 @@ class LoginSSOViewModelTest { loginViewModel.onCustomServerDialogConfirm() + advanceUntilIdle() assertEquals(authServerConfigProvider.authServer.value, expected) coVerify(exactly = 1) { fetchSSOSettings.invoke() } diff --git a/kalium b/kalium index 049269a72ff..3bf2c1089b8 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 049269a72fff09a80cd215a62c71b212142deb51 +Subproject commit 3bf2c1089b879ec1fb6008b765d8aa8eb32bde6d From 476d726b8436955014cc181db68b0f9567f06ec2 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Fri, 7 Jul 2023 12:29:16 +0200 Subject: [PATCH 08/11] trigger CI From 721e1dcb79ce1002de50418cf1e25f733c1a9ed3 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Fri, 7 Jul 2023 12:29:24 +0200 Subject: [PATCH 09/11] =?UTF-8?q?u=C3=BCdate=20kalium?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 3bf2c1089b8..94a0d379311 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 3bf2c1089b879ec1fb6008b765d8aa8eb32bde6d +Subproject commit 94a0d3793116c782c0d3d6d2ccfaa42ab62908d2 From 7bee929632bc5d82b3c2f1a37ec250ac370c5260 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Fri, 7 Jul 2023 12:40:50 +0200 Subject: [PATCH 10/11] fix --- .../ui/authentication/login/sso/LoginSSOViewModelTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index 823122590a4..f025d67bba6 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -75,6 +75,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import kotlinx.datetime.Instant @@ -433,7 +434,7 @@ class LoginSSOViewModelTest { } @Test - fun `given backend switch confirmed, when the new server have NO default sso code, then initiate sso login`() { + fun `given backend switch confirmed, when the new server have NO default sso code, then initiate sso login`() = runTest { val expected = newServerConfig(2).links every { validateEmailUseCase(any()) } returns true coEvery { fetchSSOSettings.invoke() } returns FetchSSOSettingsUseCase.Result.Success(null) From 95e2cce62c1dbea7db3c16640552a53681257856 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Fri, 7 Jul 2023 12:45:53 +0200 Subject: [PATCH 11/11] unused import --- app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt index 4e11a1c5ce9..ff342042e0b 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -38,7 +38,6 @@ import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.LogoutUseCase -import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallOnConversationChangeUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.FlipToBackCameraUseCase