Skip to content

Commit c1ed75b

Browse files
committed
Support for stable version of MSC3824 OAuth 2.0 aware
1 parent 12c798b commit c1ed75b

File tree

16 files changed

+221
-24
lines changed

16 files changed

+221
-24
lines changed

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: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2023 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+
import org.matrix.android.sdk.api.extensions.orFalse
22+
23+
/**
24+
* This is a subset of the server metadata discovery for the OAuth 2.0 API
25+
* https://spec.matrix.org/v1.16/client-server-api/#get_matrixclientv1auth_metadata
26+
*
27+
* Includes the values from MSC4191: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
28+
*
29+
* <pre>
30+
* {
31+
* "issuer": "https://id.server.org",
32+
* "account_management_uri": "https://id.server.org/my-account",
33+
* "account_management_actions_supported": ["org.matrix.profile", "org.matrix.devices_list"],
34+
* }
35+
* </pre>
36+
* .
37+
*/
38+
39+
@JsonClass(generateAdapter = true)
40+
data class AuthMetadata(
41+
@Json(name = "issuer")
42+
val issuer: String,
43+
44+
@Json(name = "account_management_uri")
45+
val accountManagementUri: String?,
46+
47+
@Json(name = "account_management_actions_supported")
48+
val accountManagementActionsSupported: List<String>?,
49+
)

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.matrix.android.sdk.api.session.homeserver
1818

19+
import org.matrix.android.sdk.api.extensions.orFalse
20+
1921
data class HomeServerCapabilities(
2022
/**
2123
* True if it is possible to change the password of the account.
@@ -82,12 +84,17 @@ data class HomeServerCapabilities(
8284
var canRedactRelatedEvents: Boolean = false,
8385

8486
/**
85-
* External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
87+
* External account management url for use with OAuth API, provided by MSC4191 /auth_metadata discovery or in unstable Wellknown.
8688
*/
8789
val externalAccountManagementUrl: String? = null,
8890

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

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

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+
* @deprecated Use {@link oauthAwarePreferred} instead.
61+
*/
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+
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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2025 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.auth.version.Versions
21+
import org.matrix.android.sdk.internal.network.NetworkConstants
22+
import retrofit2.http.GET
23+
24+
internal interface AuthMetadataAPI {
25+
/**
26+
* Request the homeserver OAuth 2.0 auth metadata.
27+
*/
28+
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "auth_metadata")
29+
suspend fun getAuthMetadata(): AuthMetadata
30+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2020 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 dagger.Binds
20+
import dagger.Module
21+
import dagger.Provides
22+
import org.matrix.android.sdk.internal.session.SessionScope
23+
import org.matrix.android.sdk.internal.wellknown.WellknownModule
24+
import retrofit2.Retrofit
25+
26+
@Module(includes = [WellknownModule::class])
27+
internal abstract class AuthMetadataModule {
28+
29+
@Module
30+
companion object {
31+
@Provides
32+
@JvmStatic
33+
@SessionScope
34+
fun providesAuthMetadataAPI(retrofit: Retrofit): AuthMetadataAPI {
35+
return retrofit.create(AuthMetadataAPI::class.java)
36+
}
37+
}
38+
39+
@Binds
40+
abstract fun bindGetHomeServerCapabilitiesTask(task: DefaultGetHomeServerCapabilitiesTask): GetHomeServerCapabilitiesTask
41+
}

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

Lines changed: 24 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,23 @@ 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+
if (authMetadata.accountManagementUri != null) {
196+
homeServerCapabilitiesEntity.externalAccountManagementUrl = authMetadata.accountManagementUri;
197+
}
198+
if (authMetadata.accountManagementActionsSupported != null) {
199+
homeServerCapabilitiesEntity.externalAccountManagementSupportedActions = authMetadata.accountManagementActionsSupported.joinToString(",")
200+
}
201+
}
202+
182203
homeServerCapabilitiesEntity.canLoginWithQrCode = canLoginWithQrCode(getCapabilitiesResult, getVersionResult)
183204

184205
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time

0 commit comments

Comments
 (0)