mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-17 04:20:00 +03:00
Merge pull request #7920 from vector-im/hughns/msc3824-oidc-aware
Implementation of MSC3824 to make the client OIDC-aware
This commit is contained in:
commit
4cc2daa5ef
40 changed files with 303 additions and 64 deletions
1
changelog.d/6367.feature
Normal file
1
changelog.d/6367.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Adds MSC3824 OIDC-awareness when talking to an OIDC-enabled homeservers
|
|
@ -1063,6 +1063,9 @@
|
|||
<string name="settings_discovery_category">Discovery</string>
|
||||
<string name="settings_discovery_manage">Manage your discovery settings.</string>
|
||||
|
||||
<string name="settings_external_account_management_title">Account</string>
|
||||
<string name="settings_external_account_management">Your account details are managed separately at %1$s.</string>
|
||||
|
||||
<!-- analytics -->
|
||||
<string name="settings_analytics">Analytics</string>
|
||||
<string name="settings_opt_in_of_analytics">Send analytics data</string>
|
||||
|
|
|
@ -44,7 +44,7 @@ interface AuthenticationService {
|
|||
/**
|
||||
* Get a SSO url.
|
||||
*/
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String?
|
||||
|
||||
/**
|
||||
* Get the sign in or sign up fallback URL.
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.auth
|
||||
|
||||
/**
|
||||
* See https://github.com/matrix-org/matrix-spec-proposals/pull/3824
|
||||
*/
|
||||
enum class SSOAction {
|
||||
LOGIN,
|
||||
REGISTER;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/2965
|
||||
* <pre>
|
||||
* {
|
||||
* "issuer": "https://id.server.org",
|
||||
* "account": "https://id.server.org/my-account",
|
||||
* }
|
||||
* </pre>
|
||||
* .
|
||||
*/
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DelegatedAuthConfig(
|
||||
@Json(name = "issuer")
|
||||
val issuer: String,
|
||||
|
||||
@Json(name = "account")
|
||||
val accountManagementUrl: String,
|
||||
)
|
|
@ -22,6 +22,7 @@ data class LoginFlowResult(
|
|||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String,
|
||||
val isOutdatedHomeserver: Boolean,
|
||||
val hasOidcCompatibilityFlow: Boolean,
|
||||
val isLogoutDevicesSupported: Boolean,
|
||||
val isLoginWithQrSupported: Boolean,
|
||||
)
|
||||
|
|
|
@ -54,5 +54,11 @@ data class WellKnown(
|
|||
val identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: JsonDict? = null
|
||||
val integrations: JsonDict? = null,
|
||||
|
||||
/**
|
||||
* For delegation of auth via OIDC as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
|
||||
*/
|
||||
@Json(name = "org.matrix.msc2965.authentication")
|
||||
val unstableDelegatedAuthConfig: DelegatedAuthConfig? = null,
|
||||
)
|
||||
|
|
|
@ -80,6 +80,11 @@ data class HomeServerCapabilities(
|
|||
* True if the home server supports event redaction with relations.
|
||||
*/
|
||||
var canRedactEventWithRelations: Boolean = false,
|
||||
|
||||
/**
|
||||
* External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
|
||||
*/
|
||||
val externalAccountManagementUrl: String? = null,
|
||||
) {
|
||||
|
||||
enum class RoomCapabilitySupport {
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.MatrixPatterns
|
|||
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.LoginType
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
|
@ -88,7 +89,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
return getLoginFlow(homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String? {
|
||||
val homeServerUrlBase = getHomeServerUrlBase() ?: return null
|
||||
|
||||
return buildString {
|
||||
|
@ -103,6 +104,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
|
||||
// unstable MSC3824 action param
|
||||
appendParamToUrl("org.matrix.msc3824.action", action.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,12 +296,18 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
val loginFlowResponse = executeRequest(null) {
|
||||
authAPI.getLoginFlows()
|
||||
}
|
||||
|
||||
// If an m.login.sso flow is present that is flagged as being for MSC3824 OIDC compatibility then we only return that flow
|
||||
val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibilty == true }
|
||||
val flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows
|
||||
|
||||
return LoginFlowResult(
|
||||
supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||
ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
||||
supportedLoginTypes = flows.orEmpty().mapNotNull { it.type },
|
||||
ssoIdentityProviders = flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
||||
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl = homeServerUrl,
|
||||
isOutdatedHomeserver = !versions.isSupportedBySdk(),
|
||||
hasOidcCompatibilityFlow = oidcCompatibilityFlow != null,
|
||||
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices(),
|
||||
isLoginWithQrSupported = versions.doesServerSupportQrCodeLogin(),
|
||||
)
|
||||
|
|
|
@ -43,6 +43,13 @@ internal data class LoginFlow(
|
|||
* See MSC #2858
|
||||
*/
|
||||
@Json(name = "identity_providers")
|
||||
val ssoIdentityProvider: List<SsoIdentityProvider>? = null
|
||||
val ssoIdentityProvider: List<SsoIdentityProvider>? = null,
|
||||
|
||||
/**
|
||||
* Whether this login flow is preferred for OIDC-aware clients.
|
||||
*
|
||||
* See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824)
|
||||
*/
|
||||
@Json(name = "org.matrix.msc3824.delegated_oidc_compatibility")
|
||||
val delegatedOidcCompatibilty: Boolean? = null
|
||||
)
|
||||
|
|
|
@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
|
|||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo051
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||
import javax.inject.Inject
|
||||
|
@ -75,7 +76,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
private val normalizer: Normalizer
|
||||
) : MatrixRealmMigration(
|
||||
dbName = "Session",
|
||||
schemaVersion = 50L,
|
||||
schemaVersion = 51L,
|
||||
) {
|
||||
/**
|
||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||
|
@ -135,5 +136,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
|
||||
if (oldVersion < 49) MigrateSessionTo049(realm).perform()
|
||||
if (oldVersion < 50) MigrateSessionTo050(realm).perform()
|
||||
if (oldVersion < 51) MigrateSessionTo051(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ internal object HomeServerCapabilitiesMapper {
|
|||
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
|
||||
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
|
||||
canRedactEventWithRelations = entity.canRedactEventWithRelations,
|
||||
externalAccountManagementUrl = entity.externalAccountManagementUrl,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
internal class MigrateSessionTo051(realm: DynamicRealm) : RealmMigrator(realm, 51) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java)
|
||||
?.forceRefreshOfHomeServerCapabilities()
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ internal open class HomeServerCapabilitiesEntity(
|
|||
var canUseThreadReadReceiptsAndNotifications: Boolean = false,
|
||||
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
|
||||
var canRedactEventWithRelations: Boolean = false,
|
||||
var externalAccountManagementUrl: String? = null,
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
|
|
|
@ -167,6 +167,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
Timber.v("Extracted integration config : $config")
|
||||
realm.insertOrUpdate(config)
|
||||
}
|
||||
homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl
|
||||
}
|
||||
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import androidx.browser.customtabs.CustomTabsSession
|
|||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
|
||||
abstract class AbstractSSOLoginFragment<VB : ViewBinding> : AbstractLoginFragment<VB>() {
|
||||
|
||||
|
@ -90,7 +91,8 @@ abstract class AbstractSSOLoginFragment<VB : ViewBinding> : AbstractLoginFragmen
|
|||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = null
|
||||
providerId = null,
|
||||
action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { prefetchUrl(it) }
|
||||
}
|
||||
|
|
|
@ -69,7 +69,8 @@ sealed class LoginAction : VectorViewModelAction {
|
|||
data class SetupSsoForSessionRecovery(
|
||||
val homeServerUrl: String,
|
||||
val deviceId: String,
|
||||
val ssoIdentityProviders: List<SsoIdentityProvider>?
|
||||
val ssoIdentityProviders: List<SsoIdentityProvider>?,
|
||||
val hasOidcCompatibilityFlow: Boolean
|
||||
) : LoginAction()
|
||||
|
||||
data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
|
||||
|
|
|
@ -46,6 +46,7 @@ import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
|||
import im.vector.app.features.onboarding.AuthenticationDescription
|
||||
import im.vector.app.features.pin.UnlockedActivity
|
||||
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
|
||||
|
@ -300,6 +301,7 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
|
|||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = null,
|
||||
action = SSOAction.LOGIN
|
||||
)?.let { ssoUrl ->
|
||||
openUrlInChromeCustomTab(this, null, ssoUrl)
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ 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.SSOAction
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
|
@ -200,11 +201,12 @@ class LoginFragment :
|
|||
|
||||
if (state.loginMode is LoginMode.SsoAndPassword) {
|
||||
views.loginSocialLoginContainer.isVisible = true
|
||||
views.loginSocialLoginButtons.render(state.loginMode.ssoState, ssoMode(state)) { provider ->
|
||||
views.loginSocialLoginButtons.render(state.loginMode, ssoMode(state)) { provider ->
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = provider?.id
|
||||
providerId = provider?.id,
|
||||
action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ sealed class LoginMode : Parcelable { // Parcelable because persist state
|
|||
|
||||
@Parcelize object Unknown : LoginMode()
|
||||
@Parcelize object Password : LoginMode()
|
||||
@Parcelize data class Sso(val ssoState: SsoState) : LoginMode()
|
||||
@Parcelize data class SsoAndPassword(val ssoState: SsoState) : LoginMode()
|
||||
@Parcelize data class Sso(val ssoState: SsoState, val hasOidcCompatibilityFlow: Boolean) : LoginMode()
|
||||
@Parcelize data class SsoAndPassword(val ssoState: SsoState, val hasOidcCompatibilityFlow: Boolean) : LoginMode()
|
||||
@Parcelize object Unsupported : LoginMode()
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.R
|
|||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
|
||||
import im.vector.app.features.login.SocialLoginButtonsView.Mode
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to sign up or to sign in to the homeserver.
|
||||
|
@ -75,11 +76,12 @@ class LoginSignUpSignInSelectionFragment :
|
|||
when (state.loginMode) {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||
views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider ->
|
||||
views.loginSignupSigninSocialLoginButtons.render(state.loginMode, Mode.MODE_CONTINUE) { provider ->
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = provider?.id
|
||||
providerId = provider?.id,
|
||||
action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
|
@ -111,7 +113,8 @@ class LoginSignUpSignInSelectionFragment :
|
|||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = null
|
||||
providerId = null,
|
||||
action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
} else {
|
||||
|
|
|
@ -39,6 +39,7 @@ import kotlinx.coroutines.launch
|
|||
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
|
@ -224,7 +225,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||
setState {
|
||||
copy(
|
||||
signMode = SignMode.SignIn,
|
||||
loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState()),
|
||||
loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState(), action.hasOidcCompatibilityFlow),
|
||||
homeServerUrlFromUser = action.homeServerUrl,
|
||||
homeServerUrl = action.homeServerUrl,
|
||||
deviceId = action.deviceId
|
||||
|
@ -817,8 +818,11 @@ 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.toSsoState())
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState())
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(
|
||||
data.ssoIdentityProviders.toSsoState(),
|
||||
data.hasOidcCompatibilityFlow
|
||||
)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState(), data.hasOidcCompatibilityFlow)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
@ -845,8 +849,8 @@ class LoginViewModel @AssistedInject constructor(
|
|||
return loginConfig?.homeServerUrl
|
||||
}
|
||||
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId)
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String? {
|
||||
return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId, action)
|
||||
}
|
||||
|
||||
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
|
||||
|
|
|
@ -56,6 +56,14 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
}
|
||||
}
|
||||
|
||||
var hasOidcCompatibilityFlow: Boolean = false
|
||||
set(value) {
|
||||
if (value != hasOidcCompatibilityFlow) {
|
||||
field = value
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
var listener: InteractionListener? = null
|
||||
|
||||
private fun update() {
|
||||
|
@ -70,7 +78,8 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
transformationMethod = null
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
}.let {
|
||||
it.text = getButtonTitle(context.getString(R.string.login_social_sso))
|
||||
it.text = if (hasOidcCompatibilityFlow) context.getString(R.string.login_continue)
|
||||
else getButtonTitle(context.getString(R.string.login_social_sso))
|
||||
it.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
it.setOnClickListener {
|
||||
listener?.onProviderSelected(null)
|
||||
|
@ -160,11 +169,14 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
}
|
||||
}
|
||||
|
||||
fun SocialLoginButtonsView.render(state: SsoState, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
|
||||
fun SocialLoginButtonsView.render(loginMode: LoginMode, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
|
||||
this.mode = mode
|
||||
val state = loginMode.ssoState()
|
||||
this.ssoIdentityProviders = when (state) {
|
||||
SsoState.Fallback -> null
|
||||
is SsoState.IdentityProviders -> state.providers.sorted()
|
||||
}
|
||||
this.hasOidcCompatibilityFlow = (loginMode is LoginMode.Sso && loginMode.hasOidcCompatibilityFlow) ||
|
||||
(loginMode is LoginMode.SsoAndPassword && loginMode.hasOidcCompatibilityFlow)
|
||||
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.matrix.android.sdk.api.MatrixPatterns
|
|||
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
|
@ -841,12 +842,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
|
||||
fun getDefaultHomeserverUrl() = defaultHomeserverUrl
|
||||
|
||||
fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? {
|
||||
fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?, action: SSOAction): String? {
|
||||
setState {
|
||||
val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType())
|
||||
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
|
||||
}
|
||||
return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id)
|
||||
return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id, action)
|
||||
}
|
||||
|
||||
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
|
||||
|
|
|
@ -75,6 +75,7 @@ data class SelectedHomeserverState(
|
|||
val upstreamUrl: String? = null,
|
||||
val preferredLoginMode: LoginMode = LoginMode.Unknown,
|
||||
val supportedLoginTypes: List<String> = emptyList(),
|
||||
val hasOidcCompatibilityFlow: Boolean = false,
|
||||
val isLogoutDevicesSupported: Boolean = false,
|
||||
val isLoginWithQrSupported: Boolean = false,
|
||||
) : Parcelable
|
||||
|
|
|
@ -47,13 +47,17 @@ class StartAuthenticationFlowUseCase @Inject constructor(
|
|||
upstreamUrl = authFlow.homeServerUrl,
|
||||
preferredLoginMode = preferredLoginMode,
|
||||
supportedLoginTypes = authFlow.supportedLoginTypes,
|
||||
hasOidcCompatibilityFlow = authFlow.hasOidcCompatibilityFlow,
|
||||
isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported,
|
||||
isLoginWithQrSupported = authFlow.isLoginWithQrSupported,
|
||||
isLoginWithQrSupported = authFlow.isLoginWithQrSupported
|
||||
)
|
||||
|
||||
private fun LoginFlowResult.findPreferredLoginMode() = when {
|
||||
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState())
|
||||
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState())
|
||||
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(
|
||||
ssoIdentityProviders.toSsoState(),
|
||||
hasOidcCompatibilityFlow
|
||||
)
|
||||
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState(), hasOidcCompatibilityFlow)
|
||||
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ 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.ssoState
|
||||
import im.vector.app.features.onboarding.OnboardingFlow
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
|
||||
abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthFragment<VB>() {
|
||||
|
||||
|
@ -93,7 +95,8 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthF
|
|||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
provider = null
|
||||
provider = null,
|
||||
action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { prefetchUrl(it) }
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import im.vector.app.features.VectorFeatures
|
|||
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.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.login.qr.QrCodeLoginType
|
||||
import im.vector.app.features.login.render
|
||||
|
@ -50,6 +49,7 @@ 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.SSOAction
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -153,11 +153,11 @@ class FtueAuthCombinedLoginFragment :
|
|||
when (state.selectedHomeserver.preferredLoginMode) {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
showUsernamePassword()
|
||||
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
|
||||
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode)
|
||||
}
|
||||
is LoginMode.Sso -> {
|
||||
hideUsernamePassword()
|
||||
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
|
||||
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode)
|
||||
}
|
||||
else -> {
|
||||
showUsernamePassword()
|
||||
|
@ -166,14 +166,15 @@ class FtueAuthCombinedLoginFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) {
|
||||
private fun renderSsoProviders(deviceId: String?, loginMode: LoginMode) {
|
||||
views.ssoGroup.isVisible = true
|
||||
views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible()
|
||||
views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
||||
views.ssoButtons.render(loginMode, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
||||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = deviceId,
|
||||
provider = id
|
||||
provider = id,
|
||||
action = SSOAction.LOGIN
|
||||
)?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ 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
|
||||
|
@ -53,6 +52,7 @@ 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.SSOAction
|
||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||
|
@ -207,18 +207,19 @@ class FtueAuthCombinedRegisterFragment :
|
|||
}
|
||||
|
||||
when (state.selectedHomeserver.preferredLoginMode) {
|
||||
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState)
|
||||
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode)
|
||||
else -> hideSsoProviders()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) {
|
||||
private fun renderSsoProviders(deviceId: String?, loginMode: LoginMode) {
|
||||
views.ssoGroup.isVisible = true
|
||||
views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
|
||||
views.ssoButtons.render(loginMode, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
|
||||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = deviceId,
|
||||
provider = provider
|
||||
provider = provider,
|
||||
action = SSOAction.REGISTER
|
||||
)?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ 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.SSOAction
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
|
||||
|
@ -215,11 +216,12 @@ class FtueAuthLoginFragment :
|
|||
|
||||
if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) {
|
||||
views.loginSocialLoginContainer.isVisible = true
|
||||
views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider ->
|
||||
views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode, ssoMode(state)) { provider ->
|
||||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
provider = provider
|
||||
provider = provider,
|
||||
action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
|
|
|
@ -34,7 +34,9 @@ import im.vector.app.features.login.SignMode
|
|||
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.OnboardingFlow
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to sign up or to sign in to the homeserver.
|
||||
|
@ -81,11 +83,12 @@ class FtueAuthSignUpSignInSelectionFragment :
|
|||
when (state.selectedHomeserver.preferredLoginMode) {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||
views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider ->
|
||||
views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode, Mode.MODE_CONTINUE) { provider ->
|
||||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
provider = provider
|
||||
provider = provider,
|
||||
action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
|
@ -110,7 +113,8 @@ class FtueAuthSignUpSignInSelectionFragment :
|
|||
when (state.selectedHomeserver.preferredLoginMode) {
|
||||
is LoginMode.Sso -> {
|
||||
// change to only one button that is sign in with sso
|
||||
views.loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
|
||||
views.loginSignupSigninSubmit.text =
|
||||
if (state.selectedHomeserver.hasOidcCompatibilityFlow) getString(R.string.login_continue) else getString(R.string.login_signin_sso)
|
||||
views.loginSignupSigninSignIn.isVisible = false
|
||||
}
|
||||
else -> {
|
||||
|
@ -125,7 +129,8 @@ class FtueAuthSignUpSignInSelectionFragment :
|
|||
viewModel.fetchSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
provider = null
|
||||
provider = null,
|
||||
action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.REGISTER else SSOAction.LOGIN
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
} else {
|
||||
|
@ -144,5 +149,7 @@ class FtueAuthSignUpSignInSelectionFragment :
|
|||
override fun updateWithState(state: OnboardingViewState) {
|
||||
render(state)
|
||||
setupButtons(state)
|
||||
// if talking to OIDC enabled homeserver in compatibility mode then immediately start SSO
|
||||
if (state.selectedHomeserver.hasOidcCompatibilityFlow) submit()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ class VectorPreferences @Inject constructor(
|
|||
const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
|
||||
const val SETTINGS_DISCOVERY_PREFERENCE_KEY = "SETTINGS_DISCOVERY_PREFERENCE_KEY"
|
||||
const val SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY = "SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY"
|
||||
const val SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY = "SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY"
|
||||
|
||||
const val SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY"
|
||||
const val SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY"
|
||||
|
|
|
@ -48,6 +48,7 @@ import im.vector.app.core.preference.VectorPreference
|
|||
import im.vector.app.core.preference.VectorSwitchPreference
|
||||
import im.vector.app.core.utils.TextUtils
|
||||
import im.vector.app.core.utils.getSizeOfFiles
|
||||
import im.vector.app.core.utils.openUrlInExternalBrowser
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.databinding.DialogChangePasswordBinding
|
||||
import im.vector.app.features.MainActivity
|
||||
|
@ -71,6 +72,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS
|
|||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.flow.unwrap
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -101,6 +103,9 @@ class VectorSettingsGeneralFragment :
|
|||
private val mIdentityServerPreference by lazy {
|
||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY)!!
|
||||
}
|
||||
private val mExternalAccountManagementPreference by lazy {
|
||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY)!!
|
||||
}
|
||||
|
||||
// Local contacts
|
||||
private val mContactSettingsCategory by lazy {
|
||||
|
@ -204,6 +209,24 @@ class VectorSettingsGeneralFragment :
|
|||
|
||||
mIdentityServerPreference.onPreferenceClickListener = openDiscoveryScreenPreferenceClickListener
|
||||
|
||||
// External account management URL for delegated OIDC auth
|
||||
// Hide the preference if no URL is given by server
|
||||
if (homeServerCapabilities.externalAccountManagementUrl != null) {
|
||||
mExternalAccountManagementPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
openUrlInExternalBrowser(it.context, homeServerCapabilities.externalAccountManagementUrl)
|
||||
true
|
||||
}
|
||||
|
||||
val hostname = URL(homeServerCapabilities.externalAccountManagementUrl).host
|
||||
|
||||
mExternalAccountManagementPreference.summary = requireContext().getString(
|
||||
R.string.settings_external_account_management,
|
||||
hostname
|
||||
)
|
||||
} else {
|
||||
mExternalAccountManagementPreference.isVisible = false
|
||||
}
|
||||
|
||||
// Advanced settings
|
||||
|
||||
// user account
|
||||
|
|
|
@ -66,7 +66,8 @@ class SoftLogoutFragment :
|
|||
LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId,
|
||||
mode.ssoState.providersOrNull()
|
||||
mode.ssoState.providersOrNull(),
|
||||
mode.hasOidcCompatibilityFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -75,7 +76,8 @@ class SoftLogoutFragment :
|
|||
LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId,
|
||||
mode.ssoState.providersOrNull()
|
||||
mode.ssoState.providersOrNull(),
|
||||
mode.hasOidcCompatibilityFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -85,7 +87,8 @@ class SoftLogoutFragment :
|
|||
LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId,
|
||||
null
|
||||
null,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -118,8 +118,11 @@ 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.toSsoState())
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState())
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(
|
||||
data.ssoIdentityProviders.toSsoState(),
|
||||
data.hasOidcCompatibilityFlow
|
||||
)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState(), data.hasOidcCompatibilityFlow)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
|
|
@ -33,6 +33,12 @@
|
|||
android:summary="@string/settings_discovery_manage"
|
||||
android:title="@string/settings_discovery_category" />
|
||||
|
||||
<im.vector.app.core.preference.VectorPreference
|
||||
android:key="SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY"
|
||||
android:persistent="false"
|
||||
android:summary="@string/settings_external_account_management"
|
||||
android:title="@string/settings_external_account_management_title" />
|
||||
|
||||
</im.vector.app.core.preference.VectorPreferenceCategory>
|
||||
|
||||
<im.vector.app.core.preference.VectorPreferenceCategory
|
||||
|
@ -117,4 +123,4 @@
|
|||
|
||||
</im.vector.app.core.preference.VectorPreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
|
|
@ -57,6 +57,7 @@ import org.amshove.kluent.shouldBeEqualTo
|
|||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
|
@ -1088,9 +1089,9 @@ class OnboardingViewModelTest {
|
|||
fun `given returns Sso url, when fetching Sso url, then updates authentication state and returns supplied Sso url`() = runTest {
|
||||
val test = viewModel.test()
|
||||
val provider = SsoIdentityProvider(id = "provider_id", null, null, null)
|
||||
fakeAuthenticationService.givenSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider.id, result = A_SSO_URL)
|
||||
fakeAuthenticationService.givenSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider.id, SSOAction.LOGIN, result = A_SSO_URL)
|
||||
|
||||
val result = viewModel.fetchSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider)
|
||||
val result = viewModel.fetchSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider, SSOAction.LOGIN)
|
||||
|
||||
result shouldBeEqualTo A_SSO_URL
|
||||
test
|
||||
|
|
|
@ -70,7 +70,7 @@ class StartAuthenticationFlowUseCaseTest {
|
|||
|
||||
result shouldBeEqualTo expectedResult(
|
||||
supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES,
|
||||
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback),
|
||||
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback, false),
|
||||
)
|
||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class StartAuthenticationFlowUseCaseTest {
|
|||
|
||||
result shouldBeEqualTo expectedResult(
|
||||
supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES,
|
||||
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)),
|
||||
preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS), false),
|
||||
)
|
||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class StartAuthenticationFlowUseCaseTest {
|
|||
|
||||
result shouldBeEqualTo expectedResult(
|
||||
supportedLoginTypes = SSO_LOGIN_TYPE,
|
||||
preferredLoginMode = LoginMode.Sso(SsoState.Fallback),
|
||||
preferredLoginMode = LoginMode.Sso(SsoState.Fallback, false),
|
||||
)
|
||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ class StartAuthenticationFlowUseCaseTest {
|
|||
|
||||
result shouldBeEqualTo expectedResult(
|
||||
supportedLoginTypes = SSO_LOGIN_TYPE,
|
||||
preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)),
|
||||
preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS), false),
|
||||
)
|
||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
@ -131,31 +131,50 @@ class StartAuthenticationFlowUseCaseTest {
|
|||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given identity providers and login supports SSO with OIDC compatibility then prefers Sso for compatibility`() = runTest {
|
||||
val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = SSO_IDENTITY_PROVIDERS, hasOidcCompatibilityFlow = true)
|
||||
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), hasOidcCompatibilityFlow = true),
|
||||
hasOidcCompatibilityFlow = true
|
||||
)
|
||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
||||
private fun aLoginResult(
|
||||
supportedLoginTypes: List<String>,
|
||||
ssoProviders: List<SsoIdentityProvider> = FALLBACK_SSO_IDENTITY_PROVIDERS
|
||||
ssoProviders: List<SsoIdentityProvider> = FALLBACK_SSO_IDENTITY_PROVIDERS,
|
||||
hasOidcCompatibilityFlow: Boolean = false
|
||||
) = LoginFlowResult(
|
||||
supportedLoginTypes = supportedLoginTypes,
|
||||
ssoIdentityProviders = ssoProviders,
|
||||
isLoginAndRegistrationSupported = true,
|
||||
homeServerUrl = A_DECLARED_HOMESERVER_URL,
|
||||
isOutdatedHomeserver = false,
|
||||
hasOidcCompatibilityFlow = hasOidcCompatibilityFlow,
|
||||
isLogoutDevicesSupported = false,
|
||||
isLoginWithQrSupported = false
|
||||
isLoginWithQrSupported = false,
|
||||
)
|
||||
|
||||
private fun expectedResult(
|
||||
isHomeserverOutdated: Boolean = false,
|
||||
preferredLoginMode: LoginMode = LoginMode.Unsupported,
|
||||
supportedLoginTypes: List<String> = emptyList(),
|
||||
homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString()
|
||||
homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString(),
|
||||
hasOidcCompatibilityFlow: Boolean = false
|
||||
) = StartAuthenticationResult(
|
||||
isHomeserverOutdated,
|
||||
SelectedHomeserverState(
|
||||
userFacingUrl = homeserverSourceUrl,
|
||||
upstreamUrl = A_DECLARED_HOMESERVER_URL,
|
||||
preferredLoginMode = preferredLoginMode,
|
||||
supportedLoginTypes = supportedLoginTypes
|
||||
supportedLoginTypes = supportedLoginTypes,
|
||||
hasOidcCompatibilityFlow = hasOidcCompatibilityFlow
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.mockk.coVerify
|
|||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.SSOAction
|
||||
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.login.LoginWizard
|
||||
|
@ -78,7 +79,7 @@ class FakeAuthenticationService : AuthenticationService by mockk() {
|
|||
coVerify { cancelPendingLoginOrRegistration() }
|
||||
}
|
||||
|
||||
fun givenSsoUrl(redirectUri: String, deviceId: String, providerId: String, result: String) {
|
||||
coEvery { getSsoUrl(redirectUri, deviceId, providerId) } returns result
|
||||
fun givenSsoUrl(redirectUri: String, deviceId: String, providerId: String, action: SSOAction, result: String) {
|
||||
coEvery { getSsoUrl(redirectUri, deviceId, providerId, action) } returns result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ fun aHomeServerCapabilities(
|
|||
defaultIdentityServerUrl: String? = null,
|
||||
roomVersions: RoomVersionCapabilities? = null,
|
||||
canRemotelyTogglePushNotificationsOfDevices: Boolean = true,
|
||||
externalAccountManagementUrl: String? = null,
|
||||
) = HomeServerCapabilities(
|
||||
canChangePassword = canChangePassword,
|
||||
canChangeDisplayName = canChangeDisplayName,
|
||||
|
@ -39,4 +40,5 @@ fun aHomeServerCapabilities(
|
|||
defaultIdentityServerUrl = defaultIdentityServerUrl,
|
||||
roomVersions = roomVersions,
|
||||
canRemotelyTogglePushNotificationsOfDevices = canRemotelyTogglePushNotificationsOfDevices,
|
||||
externalAccountManagementUrl = externalAccountManagementUrl,
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue