mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #6834 from vector-im/feature/adm/ftue-empty-identity-providers
Allow empty SSO `identity_providers`
This commit is contained in:
commit
d405a66443
1
changelog.d/6827.bugfix
Normal file
1
changelog.d/6827.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fixing sign in/up for homeservers that rely on the SSO fallback url
|
@ -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,
|
||||
|
@ -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,14 +99,12 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSocialLoginButtons(state: LoginViewState) {
|
||||
views.loginSocialLoginButtons.mode = when (state.signMode) {
|
||||
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() {
|
||||
cleanupUi()
|
||||
@ -201,9 +198,7 @@ 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?) {
|
||||
views.loginSocialLoginButtons.render(state.loginMode.ssoState, ssoMode(state)) { provider ->
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
@ -211,7 +206,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
views.loginSocialLoginContainer.isVisible = false
|
||||
views.loginSocialLoginButtons.ssoIdentityProviders = null
|
||||
@ -272,7 +266,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||
|
||||
setupUi(state)
|
||||
setupAutoFill(state)
|
||||
setupSocialLoginButtons(state)
|
||||
setupButtons(state)
|
||||
|
||||
when (state.asyncLoginAction) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,9 +73,7 @@ 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?) {
|
||||
views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider ->
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
@ -84,7 +82,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// SSO only is managed without container as well as No sso
|
||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
private fun ssoMode(state: OnboardingViewState) = when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
|
||||
SignMode.SignUp -> Mode.MODE_SIGN_UP
|
||||
SignMode.SignIn,
|
||||
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
|
||||
}
|
||||
SignMode.SignInWithMatrixId -> Mode.MODE_SIGN_IN
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
@ -215,9 +213,7 @@ 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?) {
|
||||
views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider ->
|
||||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
@ -225,7 +221,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
views.loginSocialLoginContainer.isVisible = false
|
||||
views.loginSocialLoginButtons.ssoIdentityProviders = null
|
||||
@ -305,7 +300,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
||||
|
||||
setupUi(state)
|
||||
setupAutoFill(state)
|
||||
setupSocialLoginButtons(state)
|
||||
setupButtons(state)
|
||||
|
||||
if (state.isLoading) {
|
||||
|
@ -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,9 +79,7 @@ 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?) {
|
||||
views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider ->
|
||||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
@ -91,7 +88,6 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// SSO only is managed without container as well as No sso
|
||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
||||
|
@ -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()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user