Skip to content

Commit 95e1bcb

Browse files
authored
Merge pull request #8868 from element-hq/feature/fga/authenticated_media
Feature/fga/authenticated media
2 parents fd1a949 + 59f3c6e commit 95e1bcb

35 files changed

+438
-108
lines changed

dependencies.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def markwon = "4.6.2"
1919
def moshi = "1.15.1"
2020
def lifecycle = "2.8.3"
2121
def flowBinding = "1.2.0"
22-
def flipper = "0.190.0"
22+
def flipper = "0.259.0"
2323
def epoxy = "5.0.0"
2424
def mavericks = "3.0.9"
2525
def glide = "4.16.0"

matrix-sdk-android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ buildscript {
1717
}
1818
}
1919
dependencies {
20-
classpath "io.realm:realm-gradle-plugin:10.16.0"
20+
classpath "io.realm:realm-gradle-plugin:10.18.0"
2121
}
2222
}
2323

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

+5
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ interface Session {
296296
*/
297297
fun getOkHttpClient(): OkHttpClient
298298

299+
/**
300+
* Same as [getOkHttpClient] but will add the access token to the request.
301+
*/
302+
fun getAuthenticatedOkHttpClient(): OkHttpClient
303+
299304
/**
300305
* A global session listener to get notified for some events.
301306
*/

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ interface ContentUrlResolver {
6161
*/
6262
fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String?
6363

64+
fun requiresAuthentication(resolvedUrl: String): Boolean
65+
6466
sealed class ResolvedMethod {
6567
data class GET(val url: String) : ResolvedMethod()
6668
data class POST(val url: String, val jsonBody: String) : ResolvedMethod()

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

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ data class HomeServerCapabilities(
9595
* If set to true, the SDK will not use the network constraint when configuring Worker for the WorkManager, provided in Wellknown.
9696
*/
9797
val disableNetworkConstraint: Boolean? = null,
98+
/**
99+
* True if the home server supports authenticated media.
100+
*/
101+
val canUseAuthenticatedMedia: Boolean = false,
98102
) {
99103

100104
enum class RoomCapabilitySupport {

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
3535
import org.matrix.android.sdk.internal.network.executeRequest
3636
import org.matrix.android.sdk.internal.session.content.DefaultContentUrlResolver
3737
import org.matrix.android.sdk.internal.session.contentscanner.DisabledContentScannerService
38+
import org.matrix.android.sdk.internal.session.media.IsAuthenticatedMediaSupported
3839

3940
internal class DefaultLoginWizard(
4041
private val authAPI: AuthAPI,
@@ -45,8 +46,14 @@ internal class DefaultLoginWizard(
4546
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
4647

4748
private val getProfileTask: GetProfileTask = DefaultGetProfileTask(
48-
authAPI,
49-
DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig, DisabledContentScannerService())
49+
authAPI = authAPI,
50+
contentUrlResolver = DefaultContentUrlResolver(
51+
homeServerConnectionConfig = pendingSessionData.homeServerConnectionConfig,
52+
scannerService = DisabledContentScannerService(),
53+
isAuthenticatedMediaSupported = object : IsAuthenticatedMediaSupported {
54+
override fun invoke() = false
55+
}
56+
)
5057
)
5158

5259
override suspend fun getProfileInfo(matrixId: String): LoginProfileInfo {

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

+1
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,6 @@ internal data class HomeServerVersion(
6161
val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1)
6262
val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0)
6363
val v1_4_0 = HomeServerVersion(major = 1, minor = 4, patch = 0)
64+
val v1_11_0 = HomeServerVersion(major = 1, minor = 11, patch = 0)
6465
}
6566
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
5454
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
5555
private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
5656
private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
57+
5758
@Deprecated("The availability of stable get_login_token is now exposed as a capability and part of login flow")
5859
private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882"
5960
private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771"
@@ -142,6 +143,15 @@ internal fun Versions.doesServerSupportLogoutDevices(): Boolean {
142143
return getMaxVersion() >= HomeServerVersion.r0_6_1
143144
}
144145

146+
/**
147+
* Indicate if the server supports MSC3916 : https://github.com/matrix-org/matrix-spec-proposals/pull/3916
148+
*
149+
* @return true if authenticated media is supported
150+
*/
151+
internal fun Versions.doesServerSupportAuthenticatedMedia(): Boolean {
152+
return getMaxVersion() >= HomeServerVersion.v1_11_0
153+
}
154+
145155
private fun Versions.getMaxVersion(): HomeServerVersion {
146156
return supportedVersions
147157
?.mapNotNull { HomeServerVersion.parse(it) }

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo052
7272
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo053
7373
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo054
7474
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo055
75+
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo056
7576
import org.matrix.android.sdk.internal.util.Normalizer
7677
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
7778
import javax.inject.Inject
@@ -80,7 +81,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
8081
private val normalizer: Normalizer
8182
) : MatrixRealmMigration(
8283
dbName = "Session",
83-
schemaVersion = 55L,
84+
schemaVersion = 56L,
8485
) {
8586
/**
8687
* Forces all RealmSessionStoreMigration instances to be equal.
@@ -145,5 +146,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
145146
if (oldVersion < 53) MigrateSessionTo053(realm).perform()
146147
if (oldVersion < 54) MigrateSessionTo054(realm).perform()
147148
if (oldVersion < 55) MigrateSessionTo055(realm).perform()
149+
if (oldVersion < 56) MigrateSessionTo056(realm).perform()
148150
}
149151
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ internal object HomeServerCapabilitiesMapper {
5151
externalAccountManagementUrl = entity.externalAccountManagementUrl,
5252
authenticationIssuer = entity.authenticationIssuer,
5353
disableNetworkConstraint = entity.disableNetworkConstraint,
54+
canUseAuthenticatedMedia = entity.canUseAuthenticatedMedia,
5455
)
5556
}
5657

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2024 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.database.migration
18+
19+
import io.realm.DynamicRealm
20+
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
21+
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
22+
import org.matrix.android.sdk.internal.util.database.RealmMigrator
23+
24+
internal class MigrateSessionTo056(realm: DynamicRealm) : RealmMigrator(realm, 56) {
25+
override fun doMigrate(realm: DynamicRealm) {
26+
realm.schema.get("HomeServerCapabilitiesEntity")
27+
?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_AUTHENTICATED_MEDIA, Boolean::class.java)
28+
?.forceRefreshOfHomeServerCapabilities()
29+
}
30+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ internal open class HomeServerCapabilitiesEntity(
3838
var externalAccountManagementUrl: String? = null,
3939
var authenticationIssuer: String? = null,
4040
var disableNetworkConstraint: Boolean? = null,
41+
var canUseAuthenticatedMedia: Boolean = false,
4142
) : RealmObject() {
4243

4344
companion object

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/AccessTokenInterceptor.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.network
1818

1919
import okhttp3.Interceptor
2020
import okhttp3.Response
21+
import org.matrix.android.sdk.internal.network.httpclient.addAuthenticationHeader
2122
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
2223

2324
internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTokenProvider) : Interceptor {
@@ -28,7 +29,7 @@ internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTok
2829
// Add the access token to all requests if it is set
2930
accessTokenProvider.getToken()?.let { token ->
3031
val newRequestBuilder = request.newBuilder()
31-
newRequestBuilder.header(HttpHeaders.Authorization, "Bearer $token")
32+
newRequestBuilder.addAuthenticationHeader(token)
3233
request = newRequestBuilder.build()
3334
}
3435

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt

+9
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package org.matrix.android.sdk.internal.network.httpclient
1818

1919
import okhttp3.OkHttpClient
20+
import okhttp3.Request
2021
import org.matrix.android.sdk.api.MatrixConfiguration
2122
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
2223
import org.matrix.android.sdk.internal.network.AccessTokenInterceptor
24+
import org.matrix.android.sdk.internal.network.HttpHeaders
2325
import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor
2426
import org.matrix.android.sdk.internal.network.ssl.CertUtil
2527
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
@@ -66,3 +68,10 @@ internal fun OkHttpClient.Builder.applyMatrixConfiguration(matrixConfiguration:
6668

6769
return this
6870
}
71+
72+
fun Request.Builder.addAuthenticationHeader(accessToken: String?): Request.Builder {
73+
if (accessToken != null) {
74+
header(HttpHeaders.Authorization, "Bearer $accessToken")
75+
}
76+
return this
77+
}

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

+16-7
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
3434
import org.matrix.android.sdk.api.session.file.FileService
3535
import org.matrix.android.sdk.api.util.md5
3636
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
37+
import org.matrix.android.sdk.internal.di.Authenticated
3738
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
3839
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
40+
import org.matrix.android.sdk.internal.network.httpclient.addAuthenticationHeader
41+
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
3942
import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
4043
import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
4144
import org.matrix.android.sdk.internal.util.time.Clock
@@ -54,6 +57,7 @@ internal class DefaultFileService @Inject constructor(
5457
private val okHttpClient: OkHttpClient,
5558
private val coroutineDispatchers: MatrixCoroutineDispatchers,
5659
private val clock: Clock,
60+
@Authenticated private val accessTokenProvider: AccessTokenProvider,
5761
) : FileService {
5862

5963
// Legacy folder, will be deleted
@@ -124,21 +128,26 @@ internal class DefaultFileService @Inject constructor(
124128
val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
125129

126130
if (!cachedFiles.file.exists()) {
127-
val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
131+
val resolvedMethod = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
128132

129-
val request = when (resolvedUrl) {
133+
val request = when (resolvedMethod) {
130134
is ContentUrlResolver.ResolvedMethod.GET -> {
131-
Request.Builder()
132-
.url(resolvedUrl.url)
135+
val requestBuilder = Request.Builder()
136+
.url(resolvedMethod.url)
133137
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
134-
.build()
138+
139+
if (contentUrlResolver.requiresAuthentication(resolvedMethod.url)) {
140+
val accessToken = accessTokenProvider.getToken()
141+
requestBuilder.addAuthenticationHeader(accessToken)
142+
}
143+
requestBuilder.build()
135144
}
136145

137146
is ContentUrlResolver.ResolvedMethod.POST -> {
138147
Request.Builder()
139-
.url(resolvedUrl.url)
148+
.url(resolvedMethod.url)
140149
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
141-
.post(resolvedUrl.jsonBody.toRequestBody("application/json".toMediaType()))
150+
.post(resolvedMethod.jsonBody.toRequestBody("application/json".toMediaType()))
142151
.build()
143152
}
144153
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import org.matrix.android.sdk.api.util.appendParamToUrl
6767
import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH
6868
import org.matrix.android.sdk.internal.auth.SessionParamsStore
6969
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
70+
import org.matrix.android.sdk.internal.di.Authenticated
7071
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
7172
import org.matrix.android.sdk.internal.di.CryptoDatabase
7273
import org.matrix.android.sdk.internal.di.IdentityDatabase
@@ -131,6 +132,8 @@ internal class DefaultSession @Inject constructor(
131132
private val eventStreamService: Lazy<EventStreamService>,
132133
@UnauthenticatedWithCertificate
133134
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>,
135+
@Authenticated
136+
private val authenticatedOkHttpClient: Lazy<OkHttpClient>,
134137
private val sessionState: SessionState,
135138
) : Session,
136139
GlobalErrorHandler.Listener {
@@ -234,6 +237,10 @@ internal class DefaultSession @Inject constructor(
234237
return unauthenticatedWithCertificateOkHttpClient.get()
235238
}
236239

240+
override fun getAuthenticatedOkHttpClient(): OkHttpClient {
241+
return authenticatedOkHttpClient.get()
242+
}
243+
237244
override fun addListener(listener: Session.Listener) {
238245
sessionListeners.addListener(listener)
239246
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import org.matrix.android.sdk.internal.session.events.DefaultEventService
8383
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
8484
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
8585
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
86+
import org.matrix.android.sdk.internal.session.media.DefaultIsAuthenticatedMediaSupported
8687
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
8788
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
8889
import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
@@ -365,6 +366,10 @@ internal abstract class SessionModule {
365366
@IntoSet
366367
abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver
367368

369+
@Binds
370+
@IntoSet
371+
abstract fun bindIsMediaAuthenticated(observer: DefaultIsAuthenticatedMediaSupported): SessionLifecycleObserver
372+
368373
@Binds
369374
@IntoSet
370375
abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt

+10-3
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
2525
import org.matrix.android.sdk.internal.network.NetworkConstants
2626
import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
2727
import org.matrix.android.sdk.internal.session.contentscanner.model.toJson
28+
import org.matrix.android.sdk.internal.session.media.IsAuthenticatedMediaSupported
2829
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
2930
import javax.inject.Inject
3031

3132
internal class DefaultContentUrlResolver @Inject constructor(
3233
homeServerConnectionConfig: HomeServerConnectionConfig,
33-
private val scannerService: ContentScannerService
34+
private val scannerService: ContentScannerService,
35+
private val isAuthenticatedMediaSupported: IsAuthenticatedMediaSupported,
3436
) : ContentUrlResolver {
3537

3638
private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
37-
39+
private val authenticatedMediaApiPath = baseUrl + NetworkConstants.URI_API_PREFIX_PATH_V1 + "media/"
3840
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
3941

4042
override fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt?): ContentUrlResolver.ResolvedMethod? {
@@ -80,15 +82,20 @@ internal class DefaultContentUrlResolver @Inject constructor(
8082
}
8183
}
8284

85+
override fun requiresAuthentication(resolvedUrl: String): Boolean {
86+
return resolvedUrl.startsWith(authenticatedMediaApiPath)
87+
}
88+
8389
private fun resolve(
8490
contentUrl: String,
8591
toThumbnail: Boolean,
8692
params: String = ""
8793
): String {
8894
var serverAndMediaId = contentUrl.removeMxcPrefix()
89-
9095
val apiPath = if (scannerService.isScannerEnabled()) {
9196
NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE
97+
} else if (isAuthenticatedMediaSupported()) {
98+
NetworkConstants.URI_API_PREFIX_PATH_V1 + "media/"
9299
} else {
93100
NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0
94101
}

0 commit comments

Comments
 (0)