Skip to content

Commit da8c892

Browse files
committed
Authenticated media : makes usage of API when server supports it
1 parent 7ad3ccf commit da8c892

24 files changed

+351
-96
lines changed

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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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/internal/auth/login/DefaultLoginWizard.kt

Lines changed: 9 additions & 2 deletions
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/network/AccessTokenInterceptor.kt

Lines changed: 2 additions & 1 deletion
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 16 additions & 7 deletions
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

Lines changed: 7 additions & 0 deletions
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/content/DefaultContentUrlResolver.kt

Lines changed: 10 additions & 3 deletions
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
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
4040
import org.matrix.android.sdk.internal.network.executeRequest
4141
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerConfigExtractor
4242
import org.matrix.android.sdk.internal.session.media.GetMediaConfigResult
43-
import org.matrix.android.sdk.internal.session.media.MediaAPI
43+
import org.matrix.android.sdk.internal.session.media.MediaAPIProvider
44+
import org.matrix.android.sdk.internal.session.media.UnauthenticatedMediaAPI
4445
import org.matrix.android.sdk.internal.task.Task
4546
import org.matrix.android.sdk.internal.util.awaitTransaction
4647
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
@@ -56,7 +57,7 @@ internal interface GetHomeServerCapabilitiesTask : Task<GetHomeServerCapabilitie
5657

5758
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
5859
private val capabilitiesAPI: CapabilitiesAPI,
59-
private val mediaAPI: MediaAPI,
60+
private val mediaAPIProvider: MediaAPIProvider,
6061
@SessionDatabase private val monarchy: Monarchy,
6162
private val globalErrorReceiver: GlobalErrorReceiver,
6263
private val getWellknownTask: GetWellknownTask,
@@ -71,7 +72,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
7172
if (!doRequest) {
7273
monarchy.awaitTransaction { realm ->
7374
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
74-
7575
doRequest = homeServerCapabilitiesEntity.lastUpdatedTimestamp + MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS < Date().time
7676
}
7777
}
@@ -88,7 +88,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
8888

8989
val mediaConfig = runCatching {
9090
executeRequest(globalErrorReceiver) {
91-
mediaAPI.getMediaConfig()
91+
mediaAPIProvider.getMediaAPI().getMediaConfig()
9292
}
9393
}.getOrNull()
9494

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
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.media
18+
19+
import org.matrix.android.sdk.api.util.JsonDict
20+
import org.matrix.android.sdk.internal.network.NetworkConstants
21+
import retrofit2.http.GET
22+
import retrofit2.http.Query
23+
24+
/**
25+
* Implementation of the media repository API using the new Authenticated media API.
26+
*/
27+
internal interface AuthenticatedMediaAPI : MediaAPI {
28+
/**
29+
* Retrieve the configuration of the content repository
30+
* Ref: https://spec.matrix.org/v1.11/client-server-api/#get_matrixclientv1mediaconfig
31+
*/
32+
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "media/config")
33+
override suspend fun getMediaConfig(): GetMediaConfigResult
34+
35+
/**
36+
* Get information about a URL for the client. Typically this is called when a client
37+
* sees a URL in a message and wants to render a preview for the user.
38+
* Ref: https://spec.matrix.org/v1.11/client-server-api/#get_matrixclientv1mediapreview_url
39+
* @param url Required. The URL to get a preview of.
40+
* @param ts The preferred point in time to return a preview for. The server may return a newer version
41+
* if it does not have the requested version available.
42+
*/
43+
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "media/preview_url")
44+
override suspend fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): JsonDict
45+
}

0 commit comments

Comments
 (0)