Updated implementation including outbound link for account management

This commit is contained in:
Hugh Nimmo-Smith 2023-01-06 17:43:14 +00:00
parent bfed447b21
commit 26d71e214a
30 changed files with 179 additions and 52 deletions

View File

@ -1062,6 +1062,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">Manage your account at %1$s.</string>
<!-- analytics -->
<string name="settings_analytics">Analytics</string>
<string name="settings_opt_in_of_analytics">Send analytics data</string>

View File

@ -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,
)

View File

@ -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,
)

View File

@ -75,6 +75,10 @@ data class HomeServerCapabilities(
* True if the home server supports remote toggle of Pusher for a given device.
*/
val canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
/**
* External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
*/
val externalAccountManagementUrl: String? = null,
) {
enum class RoomCapabilitySupport {

View File

@ -105,7 +105,7 @@ internal class DefaultAuthenticationService @Inject constructor(
appendParamToUrl("device_id", it)
}
// MSC3824 action param
// unstable MSC3824 action param
appendParamToUrl("org.matrix.msc3824.action", action.toString())
}
}

View File

@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 47L,
schemaVersion = 48L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
}
}

View File

@ -47,6 +47,7 @@ internal object HomeServerCapabilitiesMapper {
canLoginWithQrCode = entity.canLoginWithQrCode,
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
externalAccountManagementUrl = entity.externalAccountManagementUrl,
)
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 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.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 MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 40) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java)
?.forceRefreshOfHomeServerCapabilities()
}
}

View File

@ -34,6 +34,7 @@ internal open class HomeServerCapabilitiesEntity(
var canLoginWithQrCode: Boolean = false,
var canUseThreadReadReceiptsAndNotifications: Boolean = false,
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
var externalAccountManagementUrl: String? = null,
) : RealmObject() {
companion object

View File

@ -164,6 +164,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
}

View File

@ -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()

View File

@ -45,8 +45,8 @@ import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.pin.UnlockedActivity
import org.matrix.android.sdk.api.auth.SSOAction
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

View File

@ -39,7 +39,6 @@ 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.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
@ -202,11 +201,11 @@ 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) }

View File

@ -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()
}

View File

@ -26,10 +26,8 @@ import dagger.hilt.android.AndroidEntryPoint
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.SSOAction
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject
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.
@ -78,11 +76,11 @@ 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) }

View File

@ -225,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
@ -818,8 +818,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.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
}

View File

@ -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,7 @@ 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 +168,13 @@ 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) }
}

View File

@ -48,13 +48,13 @@ class StartAuthenticationFlowUseCase @Inject constructor(
preferredLoginMode = preferredLoginMode,
supportedLoginTypes = authFlow.supportedLoginTypes,
hasOidcCompatibilityFlow = authFlow.hasOidcCompatibilityFlow,
isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported
isLoginWithQrSupported = authFlow.isLoginWithQrSupported,
isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported,
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
}

View File

@ -26,10 +26,9 @@ 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
import im.vector.app.features.onboarding.OnboardingFlow
import org.matrix.android.sdk.api.auth.SSOAction
import im.vector.app.features.login.ssoState
abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthFragment<VB>() {

View File

@ -40,7 +40,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,7 +49,6 @@ 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.auth.data.SsoIdentityProvider
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject
@ -154,11 +152,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()
@ -167,10 +165,10 @@ 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,

View File

@ -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
@ -54,7 +53,6 @@ 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.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
@ -209,14 +207,14 @@ 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,

View File

@ -48,7 +48,6 @@ 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.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
@ -217,11 +216,11 @@ 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) }

View File

@ -37,8 +37,6 @@ 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
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject
/**
* In this screen, the user is asked to sign up or to sign in to the homeserver.
@ -85,11 +83,11 @@ 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) }

View File

@ -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"

View File

@ -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

View File

@ -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
)
)
}

View File

@ -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
}

View File

@ -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>

View File

@ -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)
}

View File

@ -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,
)