diff --git a/changelog.d/6367.feature b/changelog.d/6367.feature
new file mode 100644
index 00000000000..5d4b46ca991
--- /dev/null
+++ b/changelog.d/6367.feature
@@ -0,0 +1 @@
+Adds MSC3824 OIDC-awareness when talking to an OIDC-enabled homeservers
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 17fbadd776c..2058d13d1dc 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -1063,6 +1063,9 @@
Discovery
Manage your discovery settings.
+ Account
+ Your account details are managed separately at %1$s.
+
Analytics
Send analytics data
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
index e490311b916..c6fab7762f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
@@ -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.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt
new file mode 100644
index 00000000000..db2dd870d52
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt
@@ -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;
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt
new file mode 100644
index 00000000000..b57472ab7c9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt
@@ -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
+ *
+ * {
+ * "issuer": "https://id.server.org",
+ * "account": "https://id.server.org/my-account",
+ * }
+ *
+ * .
+ */
+
+@JsonClass(generateAdapter = true)
+data class DelegatedAuthConfig(
+ @Json(name = "issuer")
+ val issuer: String,
+
+ @Json(name = "account")
+ val accountManagementUrl: String,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
index 5de83033e1c..5d737b716bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
@@ -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,
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt
index 10c7d513923..95488bd6827 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt
@@ -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,
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index 96e52469c3c..4968df775ab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -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 {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index d9c2afcb408..d1dd0238bad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -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(),
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
index df10e110d13..971407388c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
@@ -43,6 +43,13 @@ internal data class LoginFlow(
* See MSC #2858
*/
@Json(name = "identity_providers")
- val ssoIdentityProvider: List? = null
+ val ssoIdentityProvider: List? = 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
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 45bcd792c2b..c5ececcddb3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -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()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index 83f3e87d05c..1c7a0591a18 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -48,6 +48,7 @@ internal object HomeServerCapabilitiesMapper {
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
canRedactEventWithRelations = entity.canRedactEventWithRelations,
+ externalAccountManagementUrl = entity.externalAccountManagementUrl,
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo051.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo051.kt
new file mode 100644
index 00000000000..3bed97073d9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo051.kt
@@ -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()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 9acdcde7e53..35a5c654de8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index 5a6107821dd..ec12695ecdc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -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
}
diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt
index ddab65d9810..77bcaed3fb0 100644
--- a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt
@@ -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 : AbstractLoginFragment() {
@@ -90,7 +91,8 @@ abstract class AbstractSSOLoginFragment : 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) }
}
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
index 5947fa0cb5c..984c3694e88 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
@@ -69,7 +69,8 @@ sealed class LoginAction : VectorViewModelAction {
data class SetupSsoForSessionRecovery(
val homeServerUrl: String,
val deviceId: String,
- val ssoIdentityProviders: List?
+ val ssoIdentityProviders: List?,
+ val hasOidcCompatibilityFlow: Boolean
) : LoginAction()
data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index 4e4df5d1aab..9dfae7ff5fa 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -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(), UnlockedA
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null,
+ action = SSOAction.LOGIN
)?.let { ssoUrl ->
openUrlInChromeCustomTab(this, null, ssoUrl)
}
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
index d61044d1015..01d15db3d18 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
@@ -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) }
}
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt
index 944b159441a..384108e6a89 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt
@@ -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()
}
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt
index dbcf674847c..5ed806622f2 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt
@@ -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 {
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
index 8d520628f05..4da022d4bb8 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
@@ -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? {
diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
index 816050420e3..4ac98d6f2dc 100644
--- a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
+++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
@@ -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) }
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index 04487c6198f..9c64b5ed872 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -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? {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
index ea0d940952f..58b28ac4e41 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
@@ -75,6 +75,7 @@ data class SelectedHomeserverState(
val upstreamUrl: String? = null,
val preferredLoginMode: LoginMode = LoginMode.Unknown,
val supportedLoginTypes: List = emptyList(),
+ val hasOidcCompatibilityFlow: Boolean = false,
val isLogoutDevicesSupported: Boolean = false,
val isLoginWithQrSupported: Boolean = false,
) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt
index 9b8f0a1cc44..14a3a9bfd07 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt
@@ -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
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt
index b1352db0cc2..211c6303200 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt
@@ -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 : AbstractFtueAuthFragment() {
@@ -93,7 +95,8 @@ abstract class AbstractSSOFtueAuthFragment : 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) }
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
index 2c016f7077c..69090172ea7 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
@@ -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) }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
index 66668f53039..83a9a9c00bb 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
@@ -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) }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
index 3fd8df6bb9d..8cf8dffaf38 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
@@ -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) }
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt
index b2f2eeb1675..cd387f5f6b6 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt
@@ -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()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index d405d458c08..f513a5ef012 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -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"
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
index c90f55d22a7..1fa07329f30 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
@@ -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(VectorPreferences.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY)!!
}
+ private val mExternalAccountManagementPreference by lazy {
+ findPreference(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
diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt
index 47670b486a5..af337b5be2f 100644
--- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt
@@ -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
)
)
}
diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
index 117c298878a..af84231af89 100644
--- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
@@ -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
}
diff --git a/vector/src/main/res/xml/vector_settings_general.xml b/vector/src/main/res/xml/vector_settings_general.xml
index 30ef4337dc8..8600dbc1bee 100644
--- a/vector/src/main/res/xml/vector_settings_general.xml
+++ b/vector/src/main/res/xml/vector_settings_general.xml
@@ -33,6 +33,12 @@
android:summary="@string/settings_discovery_manage"
android:title="@string/settings_discovery_category" />
+
+
-
\ No newline at end of file
+
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
index c570a75d99e..bbca6e2aa67 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
@@ -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
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt
index 9be22d7ea99..93bfc045ded 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt
@@ -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,
- ssoProviders: List = FALLBACK_SSO_IDENTITY_PROVIDERS
+ ssoProviders: List = 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 = 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
)
)
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
index af539131693..fa446537c88 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
@@ -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
}
}
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt
index c9f32c2cf20..f8e8a03706c 100644
--- a/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt
+++ b/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt
@@ -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,
)