Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of MSC3824 to make the client OIDC-aware #7920

Merged
merged 33 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c816b8f
Partial implementation of MSC3824
hughns Jun 22, 2022
4ae6365
Use unstable prefix for SSO redirect action param
hughns Jun 22, 2022
d1a9df7
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jun 27, 2022
d41d636
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Aug 8, 2022
11df717
Changelog
hughns Aug 8, 2022
c53e365
Lint fixes
hughns Aug 11, 2022
efe9832
Add missing action param
hughns Aug 14, 2022
ec4ed88
Fix lint errors
hughns Aug 14, 2022
21b41cd
Fix lint errors
hughns Aug 14, 2022
d0d75e7
Lint fix
hughns Aug 14, 2022
74146f4
Migrate SSOAction to api package hierachy
hughns Aug 14, 2022
f6016d7
Correct copyright on SDK file
hughns Aug 14, 2022
f18cc5e
Fix unit tests
hughns Aug 14, 2022
bfc58cb
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Aug 14, 2022
bfed447
Merge remote-tracking branch 'upstream/develop' into hughns/msc3824-o…
hughns Jan 5, 2023
26d71e2
Updated implementation including outbound link for account management
hughns Jan 6, 2023
4d6bbbb
Squashed commit of the following:
hughns Jan 6, 2023
7b3c3d0
Revert "Squashed commit of the following:"
hughns Jan 6, 2023
e375fa0
Detekt
hughns Jan 6, 2023
e0076c2
Fix test compilation
hughns Jan 6, 2023
53c3e89
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jan 9, 2023
f3772cb
Lint
hughns Jan 9, 2023
624e2ff
Fixes from initial review
hughns Jan 10, 2023
51f227a
Test case for OIDC compatibility
hughns Jan 11, 2023
1ac04b0
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jan 18, 2023
3333d86
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jan 18, 2023
5194990
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jan 18, 2023
8cf29f6
Design update
hughns Jan 23, 2023
dcf3872
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jan 25, 2023
a78f057
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Jan 25, 2023
b1cacb3
Actually configure migration 50 to be used
hughns Jan 27, 2023
10a1ea0
Merge branch 'develop' into hughns/msc3824-oidc-aware
hughns Feb 9, 2023
b1d7831
Session migration 51
hughns Feb 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/6367.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds MSC3824 OIDC-awareness when talking to an OIDC-enabled homeservers
3 changes: 3 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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())
}
}

Expand Down Expand Up @@ -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(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
hughns marked this conversation as resolved.
Show resolved Hide resolved
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ internal object HomeServerCapabilitiesMapper {
canLoginWithQrCode = entity.canLoginWithQrCode,
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
externalAccountManagementUrl = entity.externalAccountManagementUrl,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
hughns marked this conversation as resolved.
Show resolved Hide resolved

override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java)
?.forceRefreshOfHomeServerCapabilities()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>() {

Expand Down Expand Up @@ -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) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) }
}
Expand Down Expand Up @@ -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 {
Expand Down
Loading