Merge pull request #6834 from vector-im/feature/adm/ftue-empty-identity-providers

Allow empty SSO `identity_providers`
This commit is contained in:
Adam Brown 2022-08-22 08:44:31 +01:00 committed by GitHub
commit d405a66443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 172 additions and 113 deletions

1
changelog.d/6827.bugfix Normal file
View File

@ -0,0 +1 @@
Fixing sign in/up for homeservers that rely on the SSO fallback url

View File

@ -85,7 +85,7 @@ abstract class AbstractSSOLoginFragment<VB : ViewBinding> : AbstractLoginFragmen
private fun prefetchIfNeeded() {
withState(loginViewModel) { state ->
if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) {
if (state.loginMode.hasSso() && state.loginMode.ssoState().isFallback()) {
// in this case we can prefetch (not other cases for privacy concerns)
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,

View File

@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword
@ -100,13 +99,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
}
}
private fun setupSocialLoginButtons(state: LoginViewState) {
views.loginSocialLoginButtons.mode = when (state.signMode) {
SignMode.Unknown -> error("developer error")
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
SignMode.SignIn,
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
}
private fun ssoMode(state: LoginViewState) = when (state.signMode) {
SignMode.Unknown -> error("developer error")
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
SignMode.SignIn,
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
}
private fun submit() {
@ -201,16 +198,13 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
if (state.loginMode is LoginMode.SsoAndPassword) {
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
views.loginSocialLoginButtons.render(state.loginMode.ssoState, ssoMode(state)) { provider ->
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
} else {
views.loginSocialLoginContainer.isVisible = false
@ -272,7 +266,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
setupUi(state)
setupAutoFill(state)
setupSocialLoginButtons(state)
setupButtons(state)
when (state.asyncLoginAction) {

View File

@ -18,22 +18,21 @@ package im.vector.app.features.login
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
sealed class LoginMode : Parcelable { // Parcelable because persist state
@Parcelize object Unknown : LoginMode()
@Parcelize object Password : LoginMode()
@Parcelize data class Sso(val ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginMode()
@Parcelize data class SsoAndPassword(val ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginMode()
@Parcelize data class Sso(val ssoState: SsoState) : LoginMode()
@Parcelize data class SsoAndPassword(val ssoState: SsoState) : LoginMode()
@Parcelize object Unsupported : LoginMode()
}
fun LoginMode.ssoIdentityProviders(): List<SsoIdentityProvider>? {
fun LoginMode.ssoState(): SsoState {
return when (this) {
is LoginMode.Sso -> ssoIdentityProviders
is LoginMode.SsoAndPassword -> ssoIdentityProviders
else -> null
is LoginMode.Sso -> ssoState
is LoginMode.SsoAndPassword -> ssoState
else -> SsoState.Fallback
}
}

View File

@ -25,7 +25,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import im.vector.app.features.login.SocialLoginButtonsView.Mode
import javax.inject.Inject
/**
@ -73,16 +73,13 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
when (state.loginMode) {
is LoginMode.SsoAndPassword -> {
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider ->
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
}
else -> {

View File

@ -223,7 +223,7 @@ class LoginViewModel @AssistedInject constructor(
setState {
copy(
signMode = SignMode.SignIn,
loginMode = LoginMode.Sso(action.ssoIdentityProviders),
loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState()),
homeServerUrlFromUser = action.homeServerUrl,
homeServerUrl = action.homeServerUrl,
deviceId = action.deviceId
@ -816,8 +816,8 @@ class LoginViewModel @AssistedInject constructor(
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState())
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState())
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}

View File

@ -160,8 +160,11 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
}
}
fun SocialLoginButtonsView.render(ssoProviders: List<SsoIdentityProvider>?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
fun SocialLoginButtonsView.render(state: SsoState, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
this.mode = mode
this.ssoIdentityProviders = ssoProviders?.sorted()
this.ssoIdentityProviders = when (state) {
SsoState.Fallback -> null
is SsoState.IdentityProviders -> state.providers.sorted()
}
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.login
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
sealed interface SsoState : Parcelable {
@Parcelize
data class IdentityProviders(val providers: List<SsoIdentityProvider>) : SsoState
@Parcelize
object Fallback : SsoState
fun isFallback() = this == Fallback
fun providersOrNull() = when (this) {
Fallback -> null
is IdentityProviders -> providers.takeIf { it.isNotEmpty() }
}
}
fun List<SsoIdentityProvider>?.toSsoState() = this
?.takeIf { it.isNotEmpty() }
?.let { SsoState.IdentityProviders(it) }
?: SsoState.Fallback

View File

@ -18,6 +18,7 @@ package im.vector.app.features.onboarding
import im.vector.app.core.extensions.containsAllItems
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.toSsoState
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -50,8 +51,8 @@ class StartAuthenticationFlowUseCase @Inject constructor(
)
private fun LoginFlowResult.findPreferredLoginMode() = when {
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState())
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState())
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}

View File

@ -26,7 +26,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.hasSso
import im.vector.app.features.login.ssoIdentityProviders
import im.vector.app.features.login.ssoState
abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthFragment<VB>() {
@ -88,7 +88,7 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthF
private fun prefetchIfNeeded() {
withState(viewModel) { state ->
if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) {
if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoState().isFallback()) {
// in this case we can prefetch (not other cases for privacy concerns)
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,

View File

@ -38,13 +38,13 @@ import im.vector.app.databinding.FragmentFtueCombinedLoginBinding
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.SsoState
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject
@ -125,11 +125,11 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
when (state.selectedHomeserver.preferredLoginMode) {
is LoginMode.SsoAndPassword -> {
showUsernamePassword()
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
}
is LoginMode.Sso -> {
hideUsernamePassword()
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
}
else -> {
showUsernamePassword()
@ -138,10 +138,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
}
}
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) {
views.ssoGroup.isVisible = true
views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible()
views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,
@ -163,6 +163,8 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
views.loginEntryGroup.isVisible = true
}
private fun isUsernameAndPasswordVisible() = views.loginEntryGroup.isVisible
private fun setupAutoFill() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
views.loginInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)

View File

@ -44,6 +44,7 @@ import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.SsoState
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
@ -51,7 +52,6 @@ import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername
@ -205,14 +205,14 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
}
when (state.selectedHomeserver.preferredLoginMode) {
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
else -> hideSsoProviders()
}
}
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) {
views.ssoGroup.isVisible = true
views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,

View File

@ -37,7 +37,8 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode
import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.SocialLoginButtonsView.Mode
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState
@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
@ -111,13 +111,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
}
}
private fun setupSocialLoginButtons(state: OnboardingViewState) {
views.loginSocialLoginButtons.mode = when (state.signMode) {
SignMode.Unknown -> error("developer error")
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
SignMode.SignIn,
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
}
private fun ssoMode(state: OnboardingViewState) = when (state.signMode) {
SignMode.Unknown -> error("developer error")
SignMode.SignUp -> Mode.MODE_SIGN_UP
SignMode.SignIn,
SignMode.SignInWithMatrixId -> Mode.MODE_SIGN_IN
}
private fun submit() {
@ -215,16 +213,13 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) {
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
provider = provider
)
?.let { openInCustomTab(it) }
}
views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider ->
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
provider = provider
)
?.let { openInCustomTab(it) }
}
} else {
views.loginSocialLoginContainer.isVisible = false
@ -305,7 +300,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
setupUi(state)
setupAutoFill(state)
setupSocialLoginButtons(state)
setupButtons(state)
if (state.isLoading) {

View File

@ -30,11 +30,10 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode
import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.ssoIdentityProviders
import im.vector.app.features.login.SocialLoginButtonsView.Mode
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewState
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject
/**
@ -80,16 +79,13 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
when (state.selectedHomeserver.preferredLoginMode) {
is LoginMode.SsoAndPassword -> {
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
provider = provider
)
?.let { openInCustomTab(it) }
}
views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider ->
viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
provider = provider
)
?.let { openInCustomTab(it) }
}
}
else -> {

View File

@ -63,7 +63,7 @@ class SoftLogoutFragment @Inject constructor(
LoginAction.SetupSsoForSessionRecovery(
softLogoutViewState.homeServerUrl,
softLogoutViewState.deviceId,
mode.ssoIdentityProviders
mode.ssoState.providersOrNull()
)
)
}
@ -72,7 +72,7 @@ class SoftLogoutFragment @Inject constructor(
LoginAction.SetupSsoForSessionRecovery(
softLogoutViewState.homeServerUrl,
softLogoutViewState.deviceId,
mode.ssoIdentityProviders
mode.ssoState.providersOrNull()
)
)
}

View File

@ -33,6 +33,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.toSsoState
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.LoginType
@ -115,8 +116,8 @@ class SoftLogoutViewModel @AssistedInject constructor(
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState())
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState())
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}

View File

@ -17,6 +17,7 @@
package im.vector.app.features.onboarding
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SsoState
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import im.vector.app.test.fakes.FakeAuthenticationService
import im.vector.app.test.fakes.FakeUri
@ -32,7 +33,11 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
private const val A_DECLARED_HOMESERVER_URL = "https://foo.bar"
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance)
private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
private val FALLBACK_SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
private val SSO_IDENTITY_PROVIDERS = listOf(SsoIdentityProvider(id = "id", "name", null, "sso-brand"))
private val SSO_LOGIN_TYPE = listOf(LoginFlowTypes.SSO)
private val SSO_AND_PASSWORD_LOGIN_TYPES = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD)
private val PASSWORD_LOGIN_TYPE = listOf(LoginFlowTypes.PASSWORD)
class StartAuthenticationFlowUseCaseTest {
@ -47,7 +52,7 @@ class StartAuthenticationFlowUseCaseTest {
@Test
fun `given empty login result when starting authentication flow then returns empty result`() = runTest {
val loginResult = aLoginResult()
val loginResult = aLoginResult(supportedLoginTypes = emptyList())
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
@ -57,55 +62,81 @@ class StartAuthenticationFlowUseCaseTest {
}
@Test
fun `given login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest {
val supportedLoginTypes = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD)
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
fun `given empty sso providers and login supports SSO and Password when starting authentication flow then prefers fallback SsoAndPassword`() = runTest {
val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = emptyList())
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = supportedLoginTypes,
preferredLoginMode = LoginMode.SsoAndPassword(SSO_IDENTITY_PROVIDERS),
supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES,
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback),
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given login supports SSO when starting authentication flow then prefers Sso`() = runTest {
val supportedLoginTypes = listOf(LoginFlowTypes.SSO)
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
fun `given sso providers and login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest {
val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = SSO_IDENTITY_PROVIDERS)
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = supportedLoginTypes,
preferredLoginMode = LoginMode.Sso(SSO_IDENTITY_PROVIDERS),
supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES,
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)),
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given empty sso providers and login supports SSO when starting authentication flow then prefers fallback Sso`() = runTest {
val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = emptyList())
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = SSO_LOGIN_TYPE,
preferredLoginMode = LoginMode.Sso(SsoState.Fallback),
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given identity providers and login supports SSO when starting authentication flow then prefers Sso`() = runTest {
val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = SSO_IDENTITY_PROVIDERS)
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = SSO_LOGIN_TYPE,
preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)),
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given login supports Password when starting authentication flow then prefers Password`() = runTest {
val supportedLoginTypes = listOf(LoginFlowTypes.PASSWORD)
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
val loginResult = aLoginResult(supportedLoginTypes = PASSWORD_LOGIN_TYPE)
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = supportedLoginTypes,
supportedLoginTypes = PASSWORD_LOGIN_TYPE,
preferredLoginMode = LoginMode.Password,
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
private fun aLoginResult(
supportedLoginTypes: List<String> = emptyList()
supportedLoginTypes: List<String>,
ssoProviders: List<SsoIdentityProvider> = FALLBACK_SSO_IDENTITY_PROVIDERS
) = LoginFlowResult(
supportedLoginTypes = supportedLoginTypes,
ssoIdentityProviders = SSO_IDENTITY_PROVIDERS,
ssoIdentityProviders = ssoProviders,
isLoginAndRegistrationSupported = true,
homeServerUrl = A_DECLARED_HOMESERVER_URL,
isOutdatedHomeserver = false,