Browse Source

Merge branch 'test/jme/base-path-migration' into feature/fga/update_rust_sdk_0.2.25

pull/3023/head
ganfra 3 months ago
parent
commit
1803e11942
  1. 41
      features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt
  2. 1
      features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt
  3. 18
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  4. 6
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt
  5. 37
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt
  6. 14
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
  7. 2
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt
  8. 1
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
  9. 13
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionDirectoryProvider.kt
  10. 2
      libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt
  11. 2
      libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt
  12. 4
      libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt
  13. BIN
      libraries/session-storage/impl/src/main/sqldelight/databases/8.db
  14. 4
      libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq
  15. 4
      libraries/session-storage/impl/src/main/sqldelight/migrations/7.sqm
  16. 1
      libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt

41
features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt

@ -0,0 +1,41 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* 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 io.element.android.features.migration.impl.migrations
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.sessionstorage.api.SessionStore
import java.io.File
import javax.inject.Inject
@ContributesMultibinding(AppScope::class)
class AppMigration05 @Inject constructor(
private val sessionStore: SessionStore,
private val baseDirectory: File,
) : AppMigration {
override val order: Int = 5
override suspend fun migrate() {
val allSessions = sessionStore.getAllSessions()
for (session in allSessions) {
if (session.sessionPath.isEmpty()) {
val sessionPath = File(baseDirectory, session.userId.replace(':', '_')).absolutePath
sessionStore.updateData(session.copy(sessionPath = sessionPath))
}
}
}
}

1
features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt

@ -51,5 +51,6 @@ fun aSessionData(
isTokenValid = isTokenValid, isTokenValid = isTokenValid,
loginType = LoginType.UNKNOWN, loginType = LoginType.UNKNOWN,
passphrase = null, passphrase = null,
sessionPath = "/a/path/to/a/session",
) )
} }

18
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

@ -63,7 +63,7 @@ import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService
import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.sync.RustSyncService
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.util.SessionDirectoryNameProvider import io.element.android.libraries.matrix.impl.util.SessionDirectoryProvider
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
@ -152,7 +152,7 @@ class RustMatrixClient(
sessionDispatcher = sessionDispatcher, sessionDispatcher = sessionDispatcher,
) )
private val sessionDirectoryNameProvider = SessionDirectoryNameProvider() private val sessionDirectoryProvider = SessionDirectoryProvider(sessionStore)
private val isLoggingOut = AtomicBoolean(false) private val isLoggingOut = AtomicBoolean(false)
@ -172,6 +172,7 @@ class RustMatrixClient(
isTokenValid = false, isTokenValid = false,
loginType = existingData.loginType, loginType = existingData.loginType,
passphrase = existingData.passphrase, passphrase = existingData.passphrase,
sessionPath = existingData.sessionPath,
) )
sessionStore.updateData(newData) sessionStore.updateData(newData)
Timber.d("Removed session data with token: '...$anonymizedToken'.") Timber.d("Removed session data with token: '...$anonymizedToken'.")
@ -199,6 +200,7 @@ class RustMatrixClient(
isTokenValid = true, isTokenValid = true,
loginType = existingData.loginType, loginType = existingData.loginType,
passphrase = existingData.passphrase, passphrase = existingData.passphrase,
sessionPath = existingData.sessionPath,
) )
sessionStore.updateData(newData) sessionStore.updateData(newData)
Timber.d("Saved new session data with token: '...$anonymizedToken'.") Timber.d("Saved new session data with token: '...$anonymizedToken'.")
@ -483,7 +485,7 @@ class RustMatrixClient(
override suspend fun clearCache() { override suspend fun clearCache() {
close() close()
baseDirectory.deleteSessionDirectory(deleteCryptoDb = false) deleteSessionDirectory(deleteCryptoDb = false)
} }
override suspend fun logout(ignoreSdkError: Boolean): String? = doLogout( override suspend fun logout(ignoreSdkError: Boolean): String? = doLogout(
@ -513,7 +515,7 @@ class RustMatrixClient(
} }
} }
close() close()
baseDirectory.deleteSessionDirectory(deleteCryptoDb = true) deleteSessionDirectory(deleteCryptoDb = true)
if (removeSession) { if (removeSession) {
sessionStore.removeSession(sessionId.value) sessionStore.removeSession(sessionId.value)
} }
@ -570,8 +572,7 @@ class RustMatrixClient(
private suspend fun File.getCacheSize( private suspend fun File.getCacheSize(
includeCryptoDb: Boolean = false, includeCryptoDb: Boolean = false,
): Long = withContext(sessionDispatcher) { ): Long = withContext(sessionDispatcher) {
val sessionDirectoryName = sessionDirectoryNameProvider.provides(sessionId) val sessionDirectory = sessionDirectoryProvider.provides(sessionId) ?: return@withContext 0L
val sessionDirectory = File(this@getCacheSize, sessionDirectoryName)
if (includeCryptoDb) { if (includeCryptoDb) {
sessionDirectory.getSizeOfFiles() sessionDirectory.getSizeOfFiles()
} else { } else {
@ -587,11 +588,10 @@ class RustMatrixClient(
} }
} }
private suspend fun File.deleteSessionDirectory( private suspend fun deleteSessionDirectory(
deleteCryptoDb: Boolean = false, deleteCryptoDb: Boolean = false,
): Boolean = withContext(sessionDispatcher) { ): Boolean = withContext(sessionDispatcher) {
val sessionDirectoryName = sessionDirectoryNameProvider.provides(sessionId) val sessionDirectory = sessionDirectoryProvider.provides(sessionId) ?: return@withContext false
val sessionDirectory = File(this@deleteSessionDirectory, sessionDirectoryName)
if (deleteCryptoDb) { if (deleteCryptoDb) {
// Delete the folder and all its content // Delete the folder and all its content
sessionDirectory.deleteRecursively() sessionDirectory.deleteRecursively()

6
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt

@ -46,7 +46,7 @@ class RustMatrixClientFactory @Inject constructor(
private val utdTracker: UtdTracker, private val utdTracker: UtdTracker,
) { ) {
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
val client = getBaseClientBuilder() val client = getBaseClientBuilder(sessionData.sessionPath)
.homeserverUrl(sessionData.homeserverUrl) .homeserverUrl(sessionData.homeserverUrl)
.username(sessionData.userId) .username(sessionData.userId)
.passphrase(sessionData.passphrase) .passphrase(sessionData.passphrase)
@ -71,9 +71,9 @@ class RustMatrixClientFactory @Inject constructor(
) )
} }
internal fun getBaseClientBuilder(): ClientBuilder { internal fun getBaseClientBuilder(sessionPath: String): ClientBuilder {
return ClientBuilder() return ClientBuilder()
.basePath(baseDirectory.absolutePath) .sessionPath(sessionPath)
.userAgent(userAgentProvider.provide()) .userAgent(userAgentProvider.provide())
.addRootCertificates(userCertificatesProvider.provides()) .addRootCertificates(userCertificatesProvider.provides())
.serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5"))

37
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt

@ -18,19 +18,26 @@ package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.api.auth.OidcConfig import io.element.android.libraries.matrix.api.auth.OidcConfig
import org.matrix.rustcomponents.sdk.OidcConfiguration import org.matrix.rustcomponents.sdk.OidcConfiguration
import java.io.File
import javax.inject.Inject
val oidcConfiguration: OidcConfiguration = OidcConfiguration( class OidConfigurationProvider @Inject constructor(
clientName = "Element", private val baseDirectory: File,
redirectUri = OidcConfig.REDIRECT_URI, ) {
clientUri = "https://element.io", fun get(): OidcConfiguration = OidcConfiguration(
logoUri = "https://element.io/mobile-icon.png", clientName = "Element",
tosUri = "https://element.io/acceptable-use-policy-terms", redirectUri = OidcConfig.REDIRECT_URI,
policyUri = "https://element.io/privacy", clientUri = "https://element.io",
contacts = listOf( logoUri = "https://element.io/mobile-icon.png",
"support@element.io", tosUri = "https://element.io/acceptable-use-policy-terms",
), policyUri = "https://element.io/privacy",
// Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually contacts = listOf(
staticRegistrations = mapOf( "support@element.io",
"https://id.thirdroom.io/realms/thirdroom" to "elementx", ),
), // Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually
) staticRegistrations = mapOf(
"https://id.thirdroom.io/realms/thirdroom" to "elementx",
),
dynamicRegistrationsFile = File(baseDirectory, "oidc/registrations.json").absolutePath,
)
}

14
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

@ -54,6 +54,7 @@ import org.matrix.rustcomponents.sdk.QrLoginProgressListener
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService
@ -68,17 +69,19 @@ class RustMatrixAuthenticationService @Inject constructor(
private val passphraseGenerator: PassphraseGenerator, private val passphraseGenerator: PassphraseGenerator,
userCertificatesProvider: UserCertificatesProvider, userCertificatesProvider: UserCertificatesProvider,
proxyProvider: ProxyProvider, proxyProvider: ProxyProvider,
private val oidConfigurationProvider: OidConfigurationProvider,
) : MatrixAuthenticationService { ) : MatrixAuthenticationService {
// Passphrase which will be used for new sessions. Existing sessions will use the passphrase // Passphrase which will be used for new sessions. Existing sessions will use the passphrase
// stored in the SessionData. // stored in the SessionData.
private val pendingPassphrase = getDatabasePassphrase() private val pendingPassphrase = getDatabasePassphrase()
private val sessionPath = File(baseDirectory, UUID.randomUUID().toString()).absolutePath
private val authService: RustAuthenticationService = RustAuthenticationService( private val authService: RustAuthenticationService = RustAuthenticationService(
basePath = baseDirectory.absolutePath, sessionPath = sessionPath,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
proxy = proxyProvider.provides(), proxy = proxyProvider.provides(),
userAgent = userAgentProvider.provide(), userAgent = userAgentProvider.provide(),
additionalRootCertificates = userCertificatesProvider.provides(), additionalRootCertificates = userCertificatesProvider.provides(),
oidcConfiguration = oidcConfiguration, oidcConfiguration = oidConfigurationProvider.get(),
customSlidingSyncProxy = null, customSlidingSyncProxy = null,
sessionDelegate = null, sessionDelegate = null,
crossProcessRefreshLockId = null, crossProcessRefreshLockId = null,
@ -148,6 +151,7 @@ class RustMatrixAuthenticationService @Inject constructor(
isTokenValid = true, isTokenValid = true,
loginType = LoginType.PASSWORD, loginType = LoginType.PASSWORD,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
sessionPath = sessionPath,
) )
} }
sessionStore.storeData(sessionData) sessionStore.storeData(sessionData)
@ -196,6 +200,7 @@ class RustMatrixAuthenticationService @Inject constructor(
isTokenValid = true, isTokenValid = true,
loginType = LoginType.OIDC, loginType = LoginType.OIDC,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
sessionPath = sessionPath,
) )
} }
pendingOidcAuthenticationData?.close() pendingOidcAuthenticationData?.close()
@ -211,11 +216,11 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
runCatching { runCatching {
val client = rustMatrixClientFactory.getBaseClientBuilder() val client = rustMatrixClientFactory.getBaseClientBuilder(sessionPath)
.passphrase(pendingPassphrase) .passphrase(pendingPassphrase)
.buildWithQrCode( .buildWithQrCode(
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
oidcConfiguration = oidcConfiguration, oidcConfiguration = oidConfigurationProvider.get(),
progressListener = object : QrLoginProgressListener { progressListener = object : QrLoginProgressListener {
override fun onUpdate(state: QrLoginProgress) { override fun onUpdate(state: QrLoginProgress) {
Timber.d("QR Code login progress: $state") Timber.d("QR Code login progress: $state")
@ -229,6 +234,7 @@ class RustMatrixAuthenticationService @Inject constructor(
isTokenValid = true, isTokenValid = true,
loginType = LoginType.QR, loginType = LoginType.QR,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
sessionPath = sessionPath,
) )
sessionStore.storeData(sessionData) sessionStore.storeData(sessionData)
SessionId(sessionData.userId) SessionId(sessionData.userId)

2
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt

@ -25,6 +25,7 @@ internal fun Session.toSessionData(
isTokenValid: Boolean, isTokenValid: Boolean,
loginType: LoginType, loginType: LoginType,
passphrase: String?, passphrase: String?,
sessionPath: String,
) = SessionData( ) = SessionData(
userId = userId, userId = userId,
deviceId = deviceId, deviceId = deviceId,
@ -37,4 +38,5 @@ internal fun Session.toSessionData(
isTokenValid = isTokenValid, isTokenValid = isTokenValid,
loginType = loginType, loginType = loginType,
passphrase = passphrase, passphrase = passphrase,
sessionPath = sessionPath,
) )

1
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt

@ -281,6 +281,7 @@ class RustTimeline(
messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content -> messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content ->
runCatching<Unit> { runCatching<Unit> {
inner.send(content) inner.send(content)
Unit
} }
} }
} }

13
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionDirectoryNameProvider.kt → libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionDirectoryProvider.kt

@ -17,10 +17,15 @@
package io.element.android.libraries.matrix.impl.util package io.element.android.libraries.matrix.impl.util
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.sessionstorage.api.SessionStore
import java.io.File
import javax.inject.Inject
class SessionDirectoryNameProvider { class SessionDirectoryProvider @Inject constructor(
// Rust sanitises the user ID replacing invalid characters with an _ private val sessionStore: SessionStore,
fun provides(sessionId: SessionId): String { ) {
return sessionId.value.replace(":", "_") suspend fun provides(sessionId: SessionId): File? {
val path = sessionStore.getSession(sessionId.value)?.sessionPath ?: return null
return File(path)
} }
} }

2
libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt

@ -44,4 +44,6 @@ data class SessionData(
val loginType: LoginType, val loginType: LoginType,
/** The optional passphrase used to encrypt data in the SDK local store. */ /** The optional passphrase used to encrypt data in the SDK local store. */
val passphrase: String?, val passphrase: String?,
/** The path to the session data stored in the filesystem. */
val sessionPath: String,
) )

2
libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt

@ -34,6 +34,7 @@ internal fun SessionData.toDbModel(): DbSessionData {
isTokenValid = if (isTokenValid) 1L else 0L, isTokenValid = if (isTokenValid) 1L else 0L,
loginType = loginType.name, loginType = loginType.name,
passphrase = passphrase, passphrase = passphrase,
sessionPath = sessionPath,
) )
} }
@ -50,5 +51,6 @@ internal fun DbSessionData.toApiModel(): SessionData {
isTokenValid = isTokenValid == 1L, isTokenValid = isTokenValid == 1L,
loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name), loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name),
passphrase = passphrase, passphrase = passphrase,
sessionPath = sessionPath,
) )
} }

4
libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt

@ -32,7 +32,9 @@ import io.element.encrypteddb.passphrase.RandomSecretPassphraseProvider
object SessionStorageModule { object SessionStorageModule {
@Provides @Provides
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
fun provideMatrixDatabase(@ApplicationContext context: Context): SessionDatabase { fun provideMatrixDatabase(
@ApplicationContext context: Context,
): SessionDatabase {
val name = "session_database" val name = "session_database"
val secretFile = context.getDatabasePath("$name.key") val secretFile = context.getDatabasePath("$name.key")

BIN
libraries/session-storage/impl/src/main/sqldelight/databases/8.db

Binary file not shown.

4
libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq

@ -23,7 +23,9 @@ CREATE TABLE SessionData (
isTokenValid INTEGER NOT NULL DEFAULT 1, isTokenValid INTEGER NOT NULL DEFAULT 1,
loginType TEXT, loginType TEXT,
-- added in version 5 -- added in version 5
passphrase TEXT passphrase TEXT,
-- added in version 6
sessionPath TEXT NOT NULL DEFAULT ""
); );

4
libraries/session-storage/impl/src/main/sqldelight/migrations/7.sqm

@ -0,0 +1,4 @@
-- Migrate DB from version 7
-- Add sessionPath so we can track the anonymized path for the session files dir
ALTER TABLE SessionData ADD COLUMN sessionPath TEXT NOT NULL DEFAULT "";

1
libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt

@ -36,5 +36,6 @@ fun aSessionData(
isTokenValid = isTokenValid, isTokenValid = isTokenValid,
loginType = LoginType.UNKNOWN, loginType = LoginType.UNKNOWN,
passphrase = null, passphrase = null,
sessionPath = "/a/path/to/a/session",
) )
} }

Loading…
Cancel
Save