Skip to content

Commit 7099f39

Browse files
authored
Merge pull request #9102 from hughns/oauth-stable
Support for stable version of MSC3824 OAuth 2.0 aware
2 parents 12c798b + df2bf39 commit 7099f39

File tree

18 files changed

+421
-24
lines changed

18 files changed

+421
-24
lines changed

changelog.d/9102.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update OAuth-awareness to support the stable version of MSC3824.

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.auth
1919
/**
2020
* See https://github.com/matrix-org/matrix-spec-proposals/pull/3824
2121
*/
22-
enum class SSOAction {
23-
LOGIN,
24-
REGISTER;
22+
enum class SSOAction(val value: String) {
23+
LOGIN("login"),
24+
REGISTER("register");
2525
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.api.auth.data
18+
19+
import com.squareup.moshi.Json
20+
import com.squareup.moshi.JsonClass
21+
22+
/**
23+
* This is a subset of the server metadata discovery for the OAuth 2.0 API
24+
* https://spec.matrix.org/v1.16/client-server-api/#get_matrixclientv1auth_metadata
25+
*
26+
* Includes the values from MSC4191: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
27+
*
28+
* <pre>
29+
* {
30+
* "issuer": "https://id.server.org",
31+
* "account_management_uri": "https://id.server.org/my-account",
32+
* "account_management_actions_supported": ["org.matrix.profile", "org.matrix.devices_list"],
33+
* }
34+
* </pre>
35+
* .
36+
*/
37+
38+
@JsonClass(generateAdapter = true)
39+
data class AuthMetadata(
40+
@Json(name = "issuer")
41+
val issuer: String,
42+
43+
@Json(name = "account_management_uri")
44+
val accountManagementUri: String?,
45+
46+
@Json(name = "account_management_actions_supported")
47+
val accountManagementActionsSupported: List<String>?,
48+
)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,17 @@ data class HomeServerCapabilities(
8282
var canRedactRelatedEvents: Boolean = false,
8383

8484
/**
85-
* External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
85+
* External account management url for use with OAuth API, provided by MSC4191 /auth_metadata discovery or in unstable Wellknown.
8686
*/
8787
val externalAccountManagementUrl: String? = null,
8888

8989
/**
90-
* Authentication issuer for use with MSC3824 delegated OIDC, provided in Wellknown.
90+
* External account management supported actions for use with OAuth API, provided by MSC4191 /auth_metadata discovery.
91+
*/
92+
val externalAccountManagementSupportedActions: List<String>? = null,
93+
94+
/**
95+
* Authentication issuer for use with MSC3824 delegated OIDC, provided by /auth_metadata discovery or in unstable Wellknown.
9196
*/
9297
val authenticationIssuer: String? = null,
9398

@@ -162,4 +167,26 @@ data class HomeServerCapabilities(
162167
const val ROOM_CAP_KNOCK = "knock"
163168
const val ROOM_CAP_RESTRICTED = "restricted"
164169
}
170+
171+
fun getLogoutDeviceURL(deviceId: String): String? {
172+
if (externalAccountManagementUrl == null) {
173+
return null
174+
}
175+
176+
// default to the stable value:
177+
var action = "org.matrix.device_delete"
178+
externalAccountManagementSupportedActions?.also { actions ->
179+
if (actions.contains("org.matrix.device_delete")) {
180+
// server supports stable version so use it
181+
} else if (actions.contains("org.matrix.session_end")) {
182+
// earlier version of MSC4191:
183+
action = "org.matrix.session_end"
184+
} else if (actions.contains("session_end")) {
185+
// previous unspecified version
186+
action = "session_end"
187+
}
188+
}
189+
190+
return externalAccountManagementUrl.removeSuffix("/") + "?action=${action}&device_id=${deviceId}"
191+
}
165192
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ internal class DefaultAuthenticationService @Inject constructor(
106106
}
107107

108108
// unstable MSC3824 action param
109-
appendParamToUrl("org.matrix.msc3824.action", action.toString())
109+
// This can be removed once servers have been updated to support the stable one.
110+
appendParamToUrl("org.matrix.msc3824.action", action.value)
111+
112+
// stable param:
113+
appendParamToUrl("action", action.value)
110114
}
111115
}
112116

@@ -297,8 +301,8 @@ internal class DefaultAuthenticationService @Inject constructor(
297301
authAPI.getLoginFlows()
298302
}
299303

300-
// If an m.login.sso flow is present that is flagged as being for MSC3824 OIDC compatibility then we only return that flow
301-
val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibility == true }
304+
// If an m.login.sso flow is present that is flagged as being for MSC3824 OAuth compatibility then we only return that flow
305+
val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibility }
302306
val flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows
303307

304308
val supportsGetLoginTokenFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.token" && it.getLoginToken == true } != null

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.data
1919
import com.squareup.moshi.Json
2020
import com.squareup.moshi.JsonClass
2121
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
22+
import org.matrix.android.sdk.api.extensions.orFalse
2223

2324
@JsonClass(generateAdapter = true)
2425
internal data class LoginFlowResponse(
@@ -46,12 +47,20 @@ internal data class LoginFlow(
4647
val ssoIdentityProvider: List<SsoIdentityProvider>? = null,
4748

4849
/**
49-
* Whether this login flow is preferred for OIDC-aware clients.
50+
* Whether this login flow is preferred for OAuth 2.0-aware clients like we are.
5051
*
5152
* See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824)
5253
*/
54+
@Json(name = "oauth_aware_preferred")
55+
val oauthAwarePreferred: Boolean? = null,
56+
57+
/**
58+
* Unstable name from MSC3824.
59+
*
60+
*/
61+
@Deprecated("Use oauthAwarePreferred instead")
5362
@Json(name = "org.matrix.msc3824.delegated_oidc_compatibility")
54-
val delegatedOidcCompatibility: Boolean? = null,
63+
val unstableDelegatedOidcCompatibility: Boolean? = null,
5564

5665
/**
5766
* Whether a login flow of type m.login.token could accept a token issued using /login/get_token.
@@ -60,4 +69,7 @@ internal data class LoginFlow(
6069
*/
6170
@Json(name = "get_login_token")
6271
val getLoginToken: Boolean? = null
63-
)
72+
) {
73+
@Suppress("DEPRECATION") val delegatedOidcCompatibility: Boolean
74+
get() = this.oauthAwarePreferred.orFalse() || this.unstableDelegatedOidcCompatibility.orFalse()
75+
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ internal object HomeServerCapabilitiesMapper {
4949
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
5050
canRedactRelatedEvents = entity.canRedactEventWithRelations,
5151
externalAccountManagementUrl = entity.externalAccountManagementUrl,
52+
externalAccountManagementSupportedActions = entity.externalAccountManagementSupportedActions?.split(","),
5253
authenticationIssuer = entity.authenticationIssuer,
5354
disableNetworkConstraint = entity.disableNetworkConstraint,
5455
canUseAuthenticatedMedia = entity.canUseAuthenticatedMedia,

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal open class HomeServerCapabilitiesEntity(
3636
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
3737
var canRedactEventWithRelations: Boolean = false,
3838
var externalAccountManagementUrl: String? = null,
39+
var externalAccountManagementSupportedActions: String? = null,
3940
var authenticationIssuer: String? = null,
4041
var disableNetworkConstraint: Boolean? = null,
4142
var canUseAuthenticatedMedia: Boolean = false,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2026 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.internal.session.homeserver
18+
19+
import org.matrix.android.sdk.api.auth.data.AuthMetadata
20+
import org.matrix.android.sdk.internal.network.NetworkConstants
21+
import retrofit2.http.GET
22+
23+
internal interface AuthMetadataAPI {
24+
/**
25+
* Request the homeserver OAuth 2.0 auth metadata.
26+
*/
27+
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "auth_metadata")
28+
suspend fun getAuthMetadata(): AuthMetadata
29+
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.homeserver
1818

1919
import com.zhuinden.monarchy.Monarchy
2020
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
21+
import org.matrix.android.sdk.api.auth.data.AuthMetadata
2122
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
2223
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
2324
import org.matrix.android.sdk.api.extensions.orFalse
@@ -66,7 +67,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
6667
private val configExtractor: IntegrationManagerConfigExtractor,
6768
private val homeServerConnectionConfig: HomeServerConnectionConfig,
6869
@UserId
69-
private val userId: String
70+
private val userId: String,
71+
private val authMetadataAPI: AuthMetadataAPI,
7072
) : GetHomeServerCapabilitiesTask {
7173

7274
override suspend fun execute(params: GetHomeServerCapabilitiesTask.Params) {
@@ -104,6 +106,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
104106
}
105107
}.getOrNull()
106108

109+
val authMetadata = runCatching {
110+
executeRequest(globalErrorReceiver = null) {
111+
authMetadataAPI.getAuthMetadata()
112+
}
113+
}.getOrNull()
114+
107115
// Domain may include a port (eg, matrix.org:8080)
108116
// Per https://spec.matrix.org/latest/client-server-api/#well-known-uri we should extract the hostname from the server name
109117
// So we take everything before the last : as the domain for the well-known task.
@@ -117,14 +125,15 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
117125
)
118126
}.getOrNull()
119127

120-
insertInDb(capabilities, mediaConfig, versions, wellknownResult)
128+
insertInDb(capabilities, mediaConfig, versions, wellknownResult, authMetadata)
121129
}
122130

123131
private suspend fun insertInDb(
124132
getCapabilitiesResult: GetCapabilitiesResult?,
125133
getMediaConfigResult: GetMediaConfigResult?,
126134
getVersionResult: Versions?,
127-
getWellknownResult: WellknownResult?
135+
getWellknownResult: WellknownResult?,
136+
authMetadata: AuthMetadata?,
128137
) {
129138
monarchy.awaitTransaction { realm ->
130139
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
@@ -174,11 +183,19 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
174183
Timber.v("Extracted integration config : $config")
175184
realm.insertOrUpdate(config)
176185
}
186+
// Getting the OAuth 2.0 metadata from well-known was in unstable MSC:
177187
homeServerCapabilitiesEntity.authenticationIssuer = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.issuer
178188
homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl
179189
homeServerCapabilitiesEntity.disableNetworkConstraint = getWellknownResult.wellKnown.disableNetworkConstraint
180190
}
181191

192+
// If the server returns OAuth 2.0 metadata then prefer that over the well-known values:
193+
if (authMetadata != null) {
194+
homeServerCapabilitiesEntity.authenticationIssuer = authMetadata.issuer
195+
homeServerCapabilitiesEntity.externalAccountManagementUrl = authMetadata.accountManagementUri
196+
homeServerCapabilitiesEntity.externalAccountManagementSupportedActions = authMetadata.accountManagementActionsSupported?.joinToString(",")
197+
}
198+
182199
homeServerCapabilitiesEntity.canLoginWithQrCode = canLoginWithQrCode(getCapabilitiesResult, getVersionResult)
183200

184201
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time

0 commit comments

Comments
 (0)