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() {
|
private fun prefetchIfNeeded() {
|
||||||
withState(loginViewModel) { state ->
|
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)
|
// in this case we can prefetch (not other cases for privacy concerns)
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
|
@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
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.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||||
@ -100,14 +99,12 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSocialLoginButtons(state: LoginViewState) {
|
private fun ssoMode(state: LoginViewState) = when (state.signMode) {
|
||||||
views.loginSocialLoginButtons.mode = when (state.signMode) {
|
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
|
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
|
||||||
SignMode.SignIn,
|
SignMode.SignIn,
|
||||||
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
|
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun submit() {
|
private fun submit() {
|
||||||
cleanupUi()
|
cleanupUi()
|
||||||
@ -201,9 +198,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
|||||||
|
|
||||||
if (state.loginMode is LoginMode.SsoAndPassword) {
|
if (state.loginMode is LoginMode.SsoAndPassword) {
|
||||||
views.loginSocialLoginContainer.isVisible = true
|
views.loginSocialLoginContainer.isVisible = true
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
views.loginSocialLoginButtons.render(state.loginMode.ssoState, ssoMode(state)) { provider ->
|
||||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
|
||||||
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
@ -211,7 +206,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
|||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
views.loginSocialLoginContainer.isVisible = false
|
views.loginSocialLoginContainer.isVisible = false
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = null
|
views.loginSocialLoginButtons.ssoIdentityProviders = null
|
||||||
@ -272,7 +266,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
|||||||
|
|
||||||
setupUi(state)
|
setupUi(state)
|
||||||
setupAutoFill(state)
|
setupAutoFill(state)
|
||||||
setupSocialLoginButtons(state)
|
|
||||||
setupButtons(state)
|
setupButtons(state)
|
||||||
|
|
||||||
when (state.asyncLoginAction) {
|
when (state.asyncLoginAction) {
|
||||||
|
@ -18,22 +18,21 @@ package im.vector.app.features.login
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
|
||||||
|
|
||||||
sealed class LoginMode : Parcelable { // Parcelable because persist state
|
sealed class LoginMode : Parcelable { // Parcelable because persist state
|
||||||
|
|
||||||
@Parcelize object Unknown : LoginMode()
|
@Parcelize object Unknown : LoginMode()
|
||||||
@Parcelize object Password : LoginMode()
|
@Parcelize object Password : LoginMode()
|
||||||
@Parcelize data class Sso(val ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginMode()
|
@Parcelize data class Sso(val ssoState: SsoState) : LoginMode()
|
||||||
@Parcelize data class SsoAndPassword(val ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginMode()
|
@Parcelize data class SsoAndPassword(val ssoState: SsoState) : LoginMode()
|
||||||
@Parcelize object Unsupported : LoginMode()
|
@Parcelize object Unsupported : LoginMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoginMode.ssoIdentityProviders(): List<SsoIdentityProvider>? {
|
fun LoginMode.ssoState(): SsoState {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is LoginMode.Sso -> ssoIdentityProviders
|
is LoginMode.Sso -> ssoState
|
||||||
is LoginMode.SsoAndPassword -> ssoIdentityProviders
|
is LoginMode.SsoAndPassword -> ssoState
|
||||||
else -> null
|
else -> SsoState.Fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ import com.airbnb.mvrx.withState
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.toReducedUrl
|
import im.vector.app.core.extensions.toReducedUrl
|
||||||
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,9 +73,7 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
|||||||
when (state.loginMode) {
|
when (state.loginMode) {
|
||||||
is LoginMode.SsoAndPassword -> {
|
is LoginMode.SsoAndPassword -> {
|
||||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||||
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
|
views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider ->
|
||||||
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
|
||||||
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
@ -84,7 +82,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
|||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
// SSO only is managed without container as well as No sso
|
// SSO only is managed without container as well as No sso
|
||||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
||||||
|
@ -223,7 +223,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
signMode = SignMode.SignIn,
|
signMode = SignMode.SignIn,
|
||||||
loginMode = LoginMode.Sso(action.ssoIdentityProviders),
|
loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState()),
|
||||||
homeServerUrlFromUser = action.homeServerUrl,
|
homeServerUrlFromUser = action.homeServerUrl,
|
||||||
homeServerUrl = action.homeServerUrl,
|
homeServerUrl = action.homeServerUrl,
|
||||||
deviceId = action.deviceId
|
deviceId = action.deviceId
|
||||||
@ -816,8 +816,8 @@ class LoginViewModel @AssistedInject constructor(
|
|||||||
val loginMode = when {
|
val loginMode = when {
|
||||||
// SSO login is taken first
|
// SSO login is taken first
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
|
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState())
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState())
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||||
else -> LoginMode.Unsupported
|
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.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) }
|
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.core.extensions.containsAllItems
|
||||||
import im.vector.app.features.login.LoginMode
|
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.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||||
@ -50,8 +51,8 @@ class StartAuthenticationFlowUseCase @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun LoginFlowResult.findPreferredLoginMode() = when {
|
private fun LoginFlowResult.findPreferredLoginMode() = when {
|
||||||
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
|
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState())
|
||||||
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
|
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState())
|
||||||
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||||
else -> LoginMode.Unsupported
|
else -> LoginMode.Unsupported
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import com.airbnb.mvrx.withState
|
|||||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||||
import im.vector.app.features.login.SSORedirectRouterActivity
|
import im.vector.app.features.login.SSORedirectRouterActivity
|
||||||
import im.vector.app.features.login.hasSso
|
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>() {
|
abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthFragment<VB>() {
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthF
|
|||||||
|
|
||||||
private fun prefetchIfNeeded() {
|
private fun prefetchIfNeeded() {
|
||||||
withState(viewModel) { state ->
|
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)
|
// in this case we can prefetch (not other cases for privacy concerns)
|
||||||
viewModel.fetchSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
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.LoginMode
|
||||||
import im.vector.app.features.login.SSORedirectRouterActivity
|
import im.vector.app.features.login.SSORedirectRouterActivity
|
||||||
import im.vector.app.features.login.SocialLoginButtonsView
|
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.login.render
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
import im.vector.app.features.onboarding.OnboardingViewState
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
|
||||||
import reactivecircus.flowbinding.android.widget.textChanges
|
import reactivecircus.flowbinding.android.widget.textChanges
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -125,11 +125,11 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
|||||||
when (state.selectedHomeserver.preferredLoginMode) {
|
when (state.selectedHomeserver.preferredLoginMode) {
|
||||||
is LoginMode.SsoAndPassword -> {
|
is LoginMode.SsoAndPassword -> {
|
||||||
showUsernamePassword()
|
showUsernamePassword()
|
||||||
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
|
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
|
||||||
}
|
}
|
||||||
is LoginMode.Sso -> {
|
is LoginMode.Sso -> {
|
||||||
hideUsernamePassword()
|
hideUsernamePassword()
|
||||||
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
|
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
showUsernamePassword()
|
showUsernamePassword()
|
||||||
@ -138,10 +138,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
|
private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) {
|
||||||
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
views.ssoGroup.isVisible = true
|
||||||
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
|
views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible()
|
||||||
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
||||||
viewModel.fetchSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
@ -163,6 +163,8 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
|||||||
views.loginEntryGroup.isVisible = true
|
views.loginEntryGroup.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isUsernameAndPasswordVisible() = views.loginEntryGroup.isVisible
|
||||||
|
|
||||||
private fun setupAutoFill() {
|
private fun setupAutoFill() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
views.loginInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
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.LoginMode
|
||||||
import im.vector.app.features.login.SSORedirectRouterActivity
|
import im.vector.app.features.login.SSORedirectRouterActivity
|
||||||
import im.vector.app.features.login.SocialLoginButtonsView
|
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.login.render
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
|
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 im.vector.app.features.onboarding.OnboardingViewState
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
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.isHomeserverUnavailable
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||||
@ -205,14 +205,14 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (state.selectedHomeserver.preferredLoginMode) {
|
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()
|
else -> hideSsoProviders()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
|
private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) {
|
||||||
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
views.ssoGroup.isVisible = true
|
||||||
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
|
views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
|
||||||
viewModel.fetchSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = deviceId,
|
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.SSORedirectRouterActivity
|
||||||
import im.vector.app.features.login.ServerType
|
import im.vector.app.features.login.ServerType
|
||||||
import im.vector.app.features.login.SignMode
|
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.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
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.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
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.isInvalidPassword
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||||
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
|
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
|
||||||
@ -111,13 +111,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSocialLoginButtons(state: OnboardingViewState) {
|
private fun ssoMode(state: OnboardingViewState) = when (state.signMode) {
|
||||||
views.loginSocialLoginButtons.mode = when (state.signMode) {
|
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
|
SignMode.SignUp -> Mode.MODE_SIGN_UP
|
||||||
SignMode.SignIn,
|
SignMode.SignIn,
|
||||||
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
|
SignMode.SignInWithMatrixId -> Mode.MODE_SIGN_IN
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submit() {
|
private fun submit() {
|
||||||
@ -215,9 +213,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
|||||||
|
|
||||||
if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) {
|
if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) {
|
||||||
views.loginSocialLoginContainer.isVisible = true
|
views.loginSocialLoginContainer.isVisible = true
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted()
|
views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider ->
|
||||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
|
||||||
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
|
||||||
viewModel.fetchSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
@ -225,7 +221,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
|||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
views.loginSocialLoginContainer.isVisible = false
|
views.loginSocialLoginContainer.isVisible = false
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = null
|
views.loginSocialLoginButtons.ssoIdentityProviders = null
|
||||||
@ -305,7 +300,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
|||||||
|
|
||||||
setupUi(state)
|
setupUi(state)
|
||||||
setupAutoFill(state)
|
setupAutoFill(state)
|
||||||
setupSocialLoginButtons(state)
|
|
||||||
setupButtons(state)
|
setupButtons(state)
|
||||||
|
|
||||||
if (state.isLoading) {
|
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.SSORedirectRouterActivity
|
||||||
import im.vector.app.features.login.ServerType
|
import im.vector.app.features.login.ServerType
|
||||||
import im.vector.app.features.login.SignMode
|
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.ssoIdentityProviders
|
import im.vector.app.features.login.render
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
import im.vector.app.features.onboarding.OnboardingViewState
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,9 +79,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
|
|||||||
when (state.selectedHomeserver.preferredLoginMode) {
|
when (state.selectedHomeserver.preferredLoginMode) {
|
||||||
is LoginMode.SsoAndPassword -> {
|
is LoginMode.SsoAndPassword -> {
|
||||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||||
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted()
|
views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider ->
|
||||||
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
|
||||||
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
|
||||||
viewModel.fetchSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
@ -91,7 +88,6 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
|
|||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
// SSO only is managed without container as well as No sso
|
// SSO only is managed without container as well as No sso
|
||||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
||||||
|
@ -63,7 +63,7 @@ class SoftLogoutFragment @Inject constructor(
|
|||||||
LoginAction.SetupSsoForSessionRecovery(
|
LoginAction.SetupSsoForSessionRecovery(
|
||||||
softLogoutViewState.homeServerUrl,
|
softLogoutViewState.homeServerUrl,
|
||||||
softLogoutViewState.deviceId,
|
softLogoutViewState.deviceId,
|
||||||
mode.ssoIdentityProviders
|
mode.ssoState.providersOrNull()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ class SoftLogoutFragment @Inject constructor(
|
|||||||
LoginAction.SetupSsoForSessionRecovery(
|
LoginAction.SetupSsoForSessionRecovery(
|
||||||
softLogoutViewState.homeServerUrl,
|
softLogoutViewState.homeServerUrl,
|
||||||
softLogoutViewState.deviceId,
|
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.extensions.hasUnsavedKeys
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.login.LoginMode
|
import im.vector.app.features.login.LoginMode
|
||||||
|
import im.vector.app.features.login.toSsoState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.LoginType
|
import org.matrix.android.sdk.api.auth.LoginType
|
||||||
@ -115,8 +116,8 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
val loginMode = when {
|
val loginMode = when {
|
||||||
// SSO login is taken first
|
// SSO login is taken first
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
|
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState())
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState())
|
||||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||||
else -> LoginMode.Unsupported
|
else -> LoginMode.Unsupported
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.app.features.onboarding
|
package im.vector.app.features.onboarding
|
||||||
|
|
||||||
import im.vector.app.features.login.LoginMode
|
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.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
||||||
import im.vector.app.test.fakes.FakeAuthenticationService
|
import im.vector.app.test.fakes.FakeAuthenticationService
|
||||||
import im.vector.app.test.fakes.FakeUri
|
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 const val A_DECLARED_HOMESERVER_URL = "https://foo.bar"
|
||||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance)
|
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 {
|
class StartAuthenticationFlowUseCaseTest {
|
||||||
|
|
||||||
@ -47,7 +52,7 @@ class StartAuthenticationFlowUseCaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given empty login result when starting authentication flow then returns empty result`() = runTest {
|
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)
|
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
||||||
|
|
||||||
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
||||||
@ -57,55 +62,81 @@ class StartAuthenticationFlowUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest {
|
fun `given empty sso providers and login supports SSO and Password when starting authentication flow then prefers fallback SsoAndPassword`() = runTest {
|
||||||
val supportedLoginTypes = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD)
|
val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = emptyList())
|
||||||
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
|
|
||||||
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
||||||
|
|
||||||
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
||||||
|
|
||||||
result shouldBeEqualTo expectedResult(
|
result shouldBeEqualTo expectedResult(
|
||||||
supportedLoginTypes = supportedLoginTypes,
|
supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES,
|
||||||
preferredLoginMode = LoginMode.SsoAndPassword(SSO_IDENTITY_PROVIDERS),
|
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback),
|
||||||
)
|
)
|
||||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given login supports SSO when starting authentication flow then prefers Sso`() = runTest {
|
fun `given sso providers and login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest {
|
||||||
val supportedLoginTypes = listOf(LoginFlowTypes.SSO)
|
val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = SSO_IDENTITY_PROVIDERS)
|
||||||
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
|
|
||||||
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
||||||
|
|
||||||
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
||||||
|
|
||||||
result shouldBeEqualTo expectedResult(
|
result shouldBeEqualTo expectedResult(
|
||||||
supportedLoginTypes = supportedLoginTypes,
|
supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES,
|
||||||
preferredLoginMode = LoginMode.Sso(SSO_IDENTITY_PROVIDERS),
|
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)
|
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given login supports Password when starting authentication flow then prefers Password`() = runTest {
|
fun `given login supports Password when starting authentication flow then prefers Password`() = runTest {
|
||||||
val supportedLoginTypes = listOf(LoginFlowTypes.PASSWORD)
|
val loginResult = aLoginResult(supportedLoginTypes = PASSWORD_LOGIN_TYPE)
|
||||||
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
|
|
||||||
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
|
||||||
|
|
||||||
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
val result = useCase.execute(A_HOMESERVER_CONFIG)
|
||||||
|
|
||||||
result shouldBeEqualTo expectedResult(
|
result shouldBeEqualTo expectedResult(
|
||||||
supportedLoginTypes = supportedLoginTypes,
|
supportedLoginTypes = PASSWORD_LOGIN_TYPE,
|
||||||
preferredLoginMode = LoginMode.Password,
|
preferredLoginMode = LoginMode.Password,
|
||||||
)
|
)
|
||||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aLoginResult(
|
private fun aLoginResult(
|
||||||
supportedLoginTypes: List<String> = emptyList()
|
supportedLoginTypes: List<String>,
|
||||||
|
ssoProviders: List<SsoIdentityProvider> = FALLBACK_SSO_IDENTITY_PROVIDERS
|
||||||
) = LoginFlowResult(
|
) = LoginFlowResult(
|
||||||
supportedLoginTypes = supportedLoginTypes,
|
supportedLoginTypes = supportedLoginTypes,
|
||||||
ssoIdentityProviders = SSO_IDENTITY_PROVIDERS,
|
ssoIdentityProviders = ssoProviders,
|
||||||
isLoginAndRegistrationSupported = true,
|
isLoginAndRegistrationSupported = true,
|
||||||
homeServerUrl = A_DECLARED_HOMESERVER_URL,
|
homeServerUrl = A_DECLARED_HOMESERVER_URL,
|
||||||
isOutdatedHomeserver = false,
|
isOutdatedHomeserver = false,
|
||||||
|
Loading…
Reference in New Issue
Block a user