1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Realm: migrate identity db to realm-kotlin

This commit is contained in:
ganfra
2022-07-28 12:16:05 +02:00
parent 0fe189b3dd
commit ed135bc4fc
24 changed files with 316 additions and 372 deletions

View File

@@ -32,7 +32,7 @@ interface IdentityService {
/** /**
* Return the current identity server URL used by this account. Returns null if no identity server is configured. * Return the current identity server URL used by this account. Returns null if no identity server is configured.
*/ */
fun getCurrentIdentityServerUrl(): String? suspend fun getCurrentIdentityServerUrl(): String?
/** /**
* Check if the identity server is valid. * Check if the identity server is valid.
@@ -105,7 +105,7 @@ interface IdentityService {
* @return the value stored using [setUserConsent] or false if [setUserConsent] has never been called, or if the identity server * @return the value stored using [setUserConsent] or false if [setUserConsent] has never been called, or if the identity server
* has been changed * has been changed
*/ */
fun getUserConsent(): Boolean suspend fun getUserConsent(): Boolean
/** /**
* Set the user consent to the provided value. Application MUST explicitly ask for the user consent to send their private data * Set the user consent to the provided value. Application MUST explicitly ask for the user consent to send their private data
@@ -113,7 +113,7 @@ interface IdentityService {
* Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
* @param newValue true if the user explicitly give their consent, false if the user wants to revoke their consent. * @param newValue true if the user explicitly give their consent, false if the user wants to revoke their consent.
*/ */
fun setUserConsent(newValue: Boolean) suspend fun setUserConsent(newValue: Boolean)
/** /**
* Get the status of the current user's threePid. * Get the status of the current user's threePid.

View File

@@ -51,7 +51,7 @@ internal class RealmInstance(
suspend fun open() { suspend fun open() {
coroutineScope.launch { coroutineScope.launch {
realm.join() realm.await()
}.join() }.join()
} }

View File

@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.network package org.matrix.android.sdk.internal.network
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
@@ -26,12 +27,13 @@ internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTok
var request = chain.request() var request = chain.request()
// Add the access token to all requests if it is set // Add the access token to all requests if it is set
accessTokenProvider.getToken()?.let { token -> runBlocking {
accessTokenProvider.getToken()
}?.let { token ->
val newRequestBuilder = request.newBuilder() val newRequestBuilder = request.newBuilder()
newRequestBuilder.header(HttpHeaders.Authorization, "Bearer $token") newRequestBuilder.header(HttpHeaders.Authorization, "Bearer $token")
request = newRequestBuilder.build() request = newRequestBuilder.build()
} }
return chain.proceed(request) return chain.proceed(request)
} }
} }

View File

@@ -17,5 +17,5 @@
package org.matrix.android.sdk.internal.network.token package org.matrix.android.sdk.internal.network.token
internal interface AccessTokenProvider { internal interface AccessTokenProvider {
fun getToken(): String? suspend fun getToken(): String?
} }

View File

@@ -24,5 +24,5 @@ internal class HomeserverAccessTokenProvider @Inject constructor(
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val sessionParamsStore: SessionParamsStore private val sessionParamsStore: SessionParamsStore
) : AccessTokenProvider { ) : AccessTokenProvider {
override fun getToken() = sessionParamsStore.get(sessionId)?.credentials?.accessToken override suspend fun getToken() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
} }

View File

@@ -90,7 +90,6 @@ internal class DefaultSession @Inject constructor(
override val coroutineDispatchers: MatrixCoroutineDispatchers, override val coroutineDispatchers: MatrixCoroutineDispatchers,
@SessionDatabase private val realmConfiguration: RealmConfiguration, @SessionDatabase private val realmConfiguration: RealmConfiguration,
@CryptoDatabase private val realmConfigurationCrypto: RealmConfiguration, @CryptoDatabase private val realmConfigurationCrypto: RealmConfiguration,
@IdentityDatabase private val realmConfigurationIdentity: RealmConfiguration,
@ContentScannerDatabase private val realmConfigurationContentScanner: RealmConfiguration, @ContentScannerDatabase private val realmConfigurationContentScanner: RealmConfiguration,
private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>,
private val sessionListeners: SessionListeners, private val sessionListeners: SessionListeners,
@@ -266,7 +265,7 @@ internal class DefaultSession @Inject constructor(
override fun logDbUsageInfo() { override fun logDbUsageInfo() {
RealmDebugTools(realmConfiguration).logInfo("Session") RealmDebugTools(realmConfiguration).logInfo("Session")
RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto") RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto")
RealmDebugTools(realmConfigurationIdentity).logInfo("Identity") //RealmDebugTools(realmConfigurationIdentity).logInfo("Identity")
RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner")
} }
@@ -274,7 +273,7 @@ internal class DefaultSession @Inject constructor(
return listOf( return listOf(
realmConfiguration, realmConfiguration,
realmConfigurationCrypto, realmConfigurationCrypto,
realmConfigurationIdentity, //realmConfigurationIdentity,
realmConfigurationContentScanner, realmConfigurationContentScanner,
) )
} }

View File

@@ -20,6 +20,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LifecycleRegistry
import dagger.Lazy import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
@@ -41,6 +43,7 @@ import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.di.SessionCoroutineScope
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.extensions.observeNotNull import org.matrix.android.sdk.internal.extensions.observeNotNull
import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.RetrofitFactory
@@ -81,7 +84,8 @@ internal class DefaultIdentityService @Inject constructor(
private val accountDataDataSource: UserAccountDataDataSource, private val accountDataDataSource: UserAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService, private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val sign3pidInvitationTask: Sign3pidInvitationTask, private val sign3pidInvitationTask: Sign3pidInvitationTask,
private val sessionParams: SessionParams private val sessionParams: SessionParams,
@SessionCoroutineScope private val coroutineScope: CoroutineScope,
) : IdentityService, SessionLifecycleObserver { ) : IdentityService, SessionLifecycleObserver {
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
@@ -95,14 +99,17 @@ internal class DefaultIdentityService @Inject constructor(
accountDataDataSource accountDataDataSource
.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_IDENTITY_SERVER) .getLiveAccountDataEvent(UserAccountDataTypes.TYPE_IDENTITY_SERVER)
.observeNotNull(lifecycleOwner) { .observeNotNull(lifecycleOwner) {
notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel<IdentityServerContent>()?.baseUrl) coroutineScope.notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel<IdentityServerContent>()?.baseUrl)
} }
// Init identityApi // Init identityApi
updateIdentityAPI(identityStore.getIdentityData()?.identityServerUrl) coroutineScope.launch {
val url = identityStore.getIdentityData()?.identityServerUrl
updateIdentityAPI(url)
}
} }
private fun notifyIdentityServerUrlChange(baseUrl: String?) { private fun CoroutineScope.notifyIdentityServerUrlChange(baseUrl: String?) = launch {
// This is maybe not a real change (echo of account data we are just setting) // This is maybe not a real change (echo of account data we are just setting)
if (identityStore.getIdentityData()?.identityServerUrl == baseUrl) { if (identityStore.getIdentityData()?.identityServerUrl == baseUrl) {
Timber.d("Echo of local identity server url change, or no change") Timber.d("Echo of local identity server url change, or no change")
@@ -129,7 +136,7 @@ internal class DefaultIdentityService @Inject constructor(
?: homeServerCapabilitiesService.getHomeServerCapabilities().defaultIdentityServerUrl ?: homeServerCapabilitiesService.getHomeServerCapabilities().defaultIdentityServerUrl
} }
override fun getCurrentIdentityServerUrl(): String? { override suspend fun getCurrentIdentityServerUrl(): String? {
return identityStore.getIdentityData()?.identityServerUrl return identityStore.getIdentityData()?.identityServerUrl
} }
@@ -225,11 +232,11 @@ internal class DefaultIdentityService @Inject constructor(
) )
} }
override fun getUserConsent(): Boolean { override suspend fun getUserConsent(): Boolean {
return identityStore.getIdentityData()?.userConsent.orFalse() return identityStore.getIdentityData()?.userConsent.orFalse()
} }
override fun setUserConsent(newValue: Boolean) { override suspend fun setUserConsent(newValue: Boolean) {
identityStore.setUserConsent(newValue) identityStore.setUserConsent(newValue)
} }

View File

@@ -23,5 +23,5 @@ import javax.inject.Inject
internal class IdentityAccessTokenProvider @Inject constructor( internal class IdentityAccessTokenProvider @Inject constructor(
private val identityStore: IdentityStore private val identityStore: IdentityStore
) : AccessTokenProvider { ) : AccessTokenProvider {
override fun getToken() = identityStore.getIdentityData()?.token override suspend fun getToken() = identityStore.getIdentityData()?.token
} }

View File

@@ -19,13 +19,17 @@ package org.matrix.android.sdk.internal.session.identity
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.realm.RealmConfiguration import io.realm.kotlin.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.di.IdentityDatabase import org.matrix.android.sdk.internal.di.IdentityDatabase
import org.matrix.android.sdk.internal.di.MatrixCoroutineScope
import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.di.UserMd5 import org.matrix.android.sdk.internal.di.UserMd5
@@ -35,7 +39,7 @@ import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
import org.matrix.android.sdk.internal.session.SessionModule import org.matrix.android.sdk.internal.session.SessionModule
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
import org.matrix.android.sdk.internal.session.identity.db.IdentityRealmModule import org.matrix.android.sdk.internal.session.identity.db.IDENTITY_REALM_SCHEMA
import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore
import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStoreMigration import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStoreMigration
import java.io.File import java.io.File
@@ -64,25 +68,38 @@ internal abstract class IdentityModule {
@JvmStatic @JvmStatic
@Provides @Provides
@IdentityDatabase @IdentityDatabase
@SessionScope fun providesRealmConfiguration(
fun providesIdentityRealmConfiguration(
realmKeysUtils: RealmKeysUtils, realmKeysUtils: RealmKeysUtils,
realmIdentityStoreMigration: RealmIdentityStoreMigration, identityStoreMigration: RealmIdentityStoreMigration,
@SessionFilesDirectory directory: File, @SessionFilesDirectory directory: File,
@UserMd5 userMd5: String @UserMd5 userMd5: String
): RealmConfiguration { ): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder(IDENTITY_REALM_SCHEMA)
.directory(directory) .directory(directory.path)
.name("matrix-sdk-identity.realm")
.apply { .apply {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
} }
.schemaVersion(realmIdentityStoreMigration.schemaVersion) .name("matrix-sdk-global.realm")
.migration(realmIdentityStoreMigration) .schemaVersion(identityStoreMigration.schemaVersion)
.allowWritesOnUiThread(true) .migration(identityStoreMigration)
.modules(IdentityRealmModule())
.build() .build()
} }
@JvmStatic
@Provides
@IdentityDatabase
@SessionScope
fun providesRealmInstance(
@IdentityDatabase realmConfiguration: RealmConfiguration,
@MatrixCoroutineScope matrixCoroutineScope: CoroutineScope,
matrixCoroutineDispatchers: MatrixCoroutineDispatchers
): RealmInstance {
return RealmInstance(
coroutineScope = matrixCoroutineScope,
realmConfiguration = realmConfiguration,
coroutineDispatcher = matrixCoroutineDispatchers.io
)
}
} }
@Binds @Binds

View File

@@ -21,26 +21,26 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityHashDetail
internal interface IdentityStore { internal interface IdentityStore {
fun getIdentityData(): IdentityData? suspend fun getIdentityData(): IdentityData?
fun setUrl(url: String?) suspend fun setUrl(url: String?)
fun setToken(token: String?) suspend fun setToken(token: String?)
fun setUserConsent(consent: Boolean) suspend fun setUserConsent(consent: Boolean)
fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) suspend fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
/** /**
* Store details about a current binding. * Store details about a current binding.
*/ */
fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding) suspend fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding)
fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding? suspend fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding?
fun deletePendingBinding(threePid: ThreePid) suspend fun deletePendingBinding(threePid: ThreePid)
} }
internal fun IdentityStore.getIdentityServerUrlWithoutProtocol(): String? { internal suspend fun IdentityStore.getIdentityServerUrlWithoutProtocol(): String? {
return getIdentityData()?.identityServerUrl?.substringAfter("://") return getIdentityData()?.identityServerUrl?.substringAfter("://")
} }

View File

@@ -16,18 +16,14 @@
package org.matrix.android.sdk.internal.session.identity.db package org.matrix.android.sdk.internal.session.identity.db
import io.realm.RealmList import io.realm.kotlin.ext.realmListOf
import io.realm.RealmModel import io.realm.kotlin.types.RealmList
import io.realm.annotations.RealmClass import io.realm.kotlin.types.RealmObject
@RealmClass internal class IdentityDataEntity : RealmObject {
internal open class IdentityDataEntity( var identityServerUrl: String? = null
var identityServerUrl: String? = null, var token: String? = null
var token: String? = null, var hashLookupPepper: String? = null
var hashLookupPepper: String? = null, var hashLookupAlgorithm: RealmList<String> = realmListOf()
var hashLookupAlgorithm: RealmList<String> = RealmList(),
var userConsent: Boolean = false var userConsent: Boolean = false
) : RealmModel {
companion object
} }

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.identity.db
import io.realm.Realm
import io.realm.RealmList
import io.realm.kotlin.createObject
import io.realm.kotlin.where
/**
* Only one object can be stored at a time.
*/
internal fun IdentityDataEntity.Companion.get(realm: Realm): IdentityDataEntity? {
return realm.where<IdentityDataEntity>().findFirst()
}
private fun IdentityDataEntity.Companion.getOrCreate(realm: Realm): IdentityDataEntity {
return get(realm) ?: realm.createObject()
}
internal fun IdentityDataEntity.Companion.setUrl(
realm: Realm,
url: String?
) {
realm.where<IdentityDataEntity>().findAll().deleteAllFromRealm()
// Delete all pending binding if any
IdentityPendingBindingEntity.deleteAll(realm)
if (url != null) {
getOrCreate(realm).apply {
identityServerUrl = url
}
}
}
internal fun IdentityDataEntity.Companion.setToken(
realm: Realm,
newToken: String?
) {
get(realm)?.apply {
token = newToken
}
}
internal fun IdentityDataEntity.Companion.setUserConsent(
realm: Realm,
newConsent: Boolean
) {
get(realm)?.apply {
userConsent = newConsent
}
}
internal fun IdentityDataEntity.Companion.setHashDetails(
realm: Realm,
pepper: String,
algorithms: List<String>
) {
get(realm)?.apply {
hashLookupPepper = pepper
hashLookupAlgorithm = RealmList<String>().apply { addAll(algorithms) }
}
}

View File

@@ -16,22 +16,22 @@
package org.matrix.android.sdk.internal.session.identity.db package org.matrix.android.sdk.internal.session.identity.db
import io.realm.RealmModel import io.realm.kotlin.types.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.kotlin.types.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.identity.toMedium import org.matrix.android.sdk.api.session.identity.toMedium
@RealmClass internal class IdentityPendingBindingEntity : RealmObject {
internal open class IdentityPendingBindingEntity( @PrimaryKey var threePid: String = ""
@PrimaryKey var threePid: String = "",
/* Managed by Riot */ /* Managed by Riot */
var clientSecret: String = "", var clientSecret: String = ""
/* Managed by Riot */ /* Managed by Riot */
var sendAttempt: Int = 0, var sendAttempt: Int = 0
/* Provided by the identity server */ /* Provided by the identity server */
var sid: String = "" var sid: String = ""
) : RealmModel {
companion object { companion object {
fun ThreePid.toPrimaryKey() = "${toMedium()}_$value" fun ThreePid.toPrimaryKey() = "${toMedium()}_$value"

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.identity.db
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.deleteFromRealm
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.identity.ThreePid
internal fun IdentityPendingBindingEntity.Companion.get(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity? {
return realm.where<IdentityPendingBindingEntity>()
.equalTo(IdentityPendingBindingEntityFields.THREE_PID, threePid.toPrimaryKey())
.findFirst()
}
internal fun IdentityPendingBindingEntity.Companion.getOrCreate(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity {
return get(realm, threePid) ?: realm.createObject(threePid.toPrimaryKey())
}
internal fun IdentityPendingBindingEntity.Companion.delete(realm: Realm, threePid: ThreePid) {
get(realm, threePid)?.deleteFromRealm()
}
internal fun IdentityPendingBindingEntity.Companion.deleteAll(realm: Realm) {
realm.where<IdentityPendingBindingEntity>()
.findAll()
.deleteAllFromRealm()
}

View File

@@ -16,16 +16,7 @@
package org.matrix.android.sdk.internal.session.identity.db package org.matrix.android.sdk.internal.session.identity.db
import io.realm.annotations.RealmModule internal val IDENTITY_REALM_SCHEMA = setOf(
/**
* Realm module for identity server classes.
*/
@RealmModule(
library = true,
classes = [
IdentityDataEntity::class, IdentityDataEntity::class,
IdentityPendingBindingEntity::class IdentityPendingBindingEntity::class,
]
) )
internal class IdentityRealmModule

View File

@@ -16,84 +16,102 @@
package org.matrix.android.sdk.internal.session.identity.db package org.matrix.android.sdk.internal.session.identity.db
import io.realm.Realm import io.realm.kotlin.UpdatePolicy
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.await
import org.matrix.android.sdk.internal.di.IdentityDatabase import org.matrix.android.sdk.internal.di.IdentityDatabase
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.identity.data.IdentityData import org.matrix.android.sdk.internal.session.identity.data.IdentityData
import org.matrix.android.sdk.internal.session.identity.data.IdentityPendingBinding import org.matrix.android.sdk.internal.session.identity.data.IdentityPendingBinding
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
import org.matrix.android.sdk.internal.session.identity.db.IdentityPendingBindingEntity.Companion.toPrimaryKey
import org.matrix.android.sdk.internal.session.identity.model.IdentityHashDetailResponse import org.matrix.android.sdk.internal.session.identity.model.IdentityHashDetailResponse
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
internal class RealmIdentityStore @Inject constructor( internal class RealmIdentityStore @Inject constructor(
@IdentityDatabase @IdentityDatabase
private val realmConfiguration: RealmConfiguration private val realmInstance: RealmInstance,
) : IdentityStore { ) : IdentityStore {
override fun getIdentityData(): IdentityData? { override suspend fun getIdentityData(): IdentityData? {
return Realm.getInstance(realmConfiguration).use { realm -> return getIdentityDataEntity()
IdentityDataEntity.get(realm)?.let { IdentityMapper.map(it) } ?.let {
IdentityMapper.map(it)
} }
} }
override fun setUrl(url: String?) { override suspend fun setUrl(url: String?) {
Realm.getInstance(realmConfiguration).use { val identityData = getIdentityDataEntity() ?: return
it.executeTransaction { realm -> realmInstance.write {
IdentityDataEntity.setUrl(realm, url) findLatest(identityData)?.identityServerUrl = url
}
}
override suspend fun setToken(token: String?) {
val identityData = getIdentityDataEntity() ?: return
realmInstance.write {
findLatest(identityData)?.token = token
}
}
override suspend fun setUserConsent(consent: Boolean) {
val identityData = getIdentityDataEntity() ?: return
realmInstance.write {
findLatest(identityData)?.userConsent = consent
}
}
override suspend fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) {
val identityData = getIdentityDataEntity() ?: return
realmInstance.write {
findLatest(identityData)?.apply {
hashLookupAlgorithm.clear()
hashLookupAlgorithm.addAll(hashDetailResponse.algorithms)
hashLookupPepper = hashDetailResponse.pepper
} }
} }
} }
override fun setToken(token: String?) { override suspend fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding) {
Realm.getInstance(realmConfiguration).use { realmInstance.write {
it.executeTransaction { realm -> val pendingBindingEntity = IdentityPendingBindingEntity().apply {
IdentityDataEntity.setToken(realm, token) this.threePid = threePid.toPrimaryKey()
clientSecret = data.clientSecret
sendAttempt = data.sendAttempt
sid = data.sid
}
copyToRealm(pendingBindingEntity, updatePolicy = UpdatePolicy.ALL)
}
}
override suspend fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding? {
return getPendingBindingEntity(threePid)?.let {
IdentityMapper.map(it)
}
}
override suspend fun deletePendingBinding(threePid: ThreePid) {
val pendingBindingEntity = getPendingBindingEntity(threePid) ?: return
realmInstance.write {
findLatest(pendingBindingEntity)?.also {
delete(it)
} }
} }
} }
override fun setUserConsent(consent: Boolean) { private suspend fun getPendingBindingEntity(threePid: ThreePid): IdentityPendingBindingEntity? {
Realm.getInstance(realmConfiguration).use { return realmInstance.getRealm()
it.executeTransaction { realm -> .query(IdentityPendingBindingEntity::class, "threePid == $0", threePid.toPrimaryKey())
IdentityDataEntity.setUserConsent(realm, consent) .first()
} .await()
}
} }
override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) { private suspend fun getIdentityDataEntity(): IdentityDataEntity? {
Realm.getInstance(realmConfiguration).use { return realmInstance.getRealm()
it.executeTransaction { realm -> .query(IdentityDataEntity::class)
IdentityDataEntity.setHashDetails(realm, hashDetailResponse.pepper, hashDetailResponse.algorithms) .first()
} .await()
}
}
override fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityPendingBindingEntity.getOrCreate(realm, threePid).let { entity ->
entity.clientSecret = data.clientSecret
entity.sendAttempt = data.sendAttempt
entity.sid = data.sid
}
}
}
}
override fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding? {
return Realm.getInstance(realmConfiguration).use { realm ->
IdentityPendingBindingEntity.get(realm, threePid)?.let { IdentityMapper.map(it) }
}
}
override fun deletePendingBinding(threePid: ThreePid) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityPendingBindingEntity.delete(realm, threePid)
}
}
} }
} }

View File

@@ -16,15 +16,16 @@
package org.matrix.android.sdk.internal.session.identity.db package org.matrix.android.sdk.internal.session.identity.db
import io.realm.DynamicRealm import io.realm.kotlin.migration.AutomaticSchemaMigration
import org.matrix.android.sdk.internal.database.MatrixAutomaticSchemaMigration
import org.matrix.android.sdk.internal.session.identity.db.migration.MigrateIdentityTo001 import org.matrix.android.sdk.internal.session.identity.db.migration.MigrateIdentityTo001
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject import javax.inject.Inject
internal class RealmIdentityStoreMigration @Inject constructor() : MatrixRealmMigration( internal class RealmIdentityStoreMigration @Inject constructor() : MatrixAutomaticSchemaMigration(
dbName = "Identity", dbName = "Identity",
schemaVersion = 1L, schemaVersion = 1L,
) { ) {
/** /**
* Forces all RealmIdentityStoreMigration instances to be equal. * Forces all RealmIdentityStoreMigration instances to be equal.
* Avoids Realm throwing when multiple instances of the migration are set. * Avoids Realm throwing when multiple instances of the migration are set.
@@ -32,7 +33,7 @@ internal class RealmIdentityStoreMigration @Inject constructor() : MatrixRealmMi
override fun equals(other: Any?) = other is RealmIdentityStoreMigration override fun equals(other: Any?) = other is RealmIdentityStoreMigration
override fun hashCode() = 3000 override fun hashCode() = 3000
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { override fun doMigrate(oldVersion: Long, migrationContext: AutomaticSchemaMigration.MigrationContext) {
if (oldVersion < 1) MigrateIdentityTo001(realm).perform() if (oldVersion < 1) MigrateIdentityTo001(migrationContext).perform()
} }
} }

View File

@@ -16,16 +16,13 @@
package org.matrix.android.sdk.internal.session.identity.db.migration package org.matrix.android.sdk.internal.session.identity.db.migration
import io.realm.DynamicRealm import io.realm.kotlin.migration.AutomaticSchemaMigration
import org.matrix.android.sdk.internal.session.identity.db.IdentityDataEntityFields import org.matrix.android.sdk.internal.database.KotlinRealmMigrator
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber import timber.log.Timber
internal class MigrateIdentityTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) { internal class MigrateIdentityTo001(migrationContext: AutomaticSchemaMigration.MigrationContext) : KotlinRealmMigrator(migrationContext, 1) {
override fun doMigrate(realm: DynamicRealm) { override fun doMigrate(migrationContext: AutomaticSchemaMigration.MigrationContext) {
Timber.d("Add field userConsent (Boolean) and set the value to false") Timber.d("Add field userConsent (Boolean) and set the value to false")
realm.schema.get("IdentityDataEntity")
?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java)
} }
} }

View File

@@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.fetchIdentityServerWithTerms import im.vector.app.features.discovery.fetchIdentityServerWithTerms
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
@@ -63,15 +64,19 @@ class ContactsBookViewModel @AssistedInject constructor(
} }
private fun loadContacts() { private fun loadContacts() {
viewModelScope.launch(Dispatchers.IO) {
val identityServerUrl = session.identityService().getCurrentIdentityServerUrl()
val userConsent = session.identityService().getUserConsent()
setState { setState {
copy( copy(
mappedContacts = Loading(), mappedContacts = Loading(),
identityServerUrl = session.identityService().getCurrentIdentityServerUrl(), identityServerUrl = identityServerUrl,
userConsent = session.identityService().getUserConsent() userConsent = userConsent
) )
} }
viewModelScope.launch(Dispatchers.IO) {
allContacts = contactsDataSource.getContacts( allContacts = contactsDataSource.getContacts(
withEmails = true, withEmails = true,
// Do not handle phone numbers for the moment // Do not handle phone numbers for the moment
@@ -90,11 +95,10 @@ class ContactsBookViewModel @AssistedInject constructor(
} }
} }
private fun performLookup(contacts: List<MappedContact>) { private suspend fun performLookup(contacts: List<MappedContact>) = coroutineScope {
if (!session.identityService().getUserConsent()) { if (!session.identityService().getUserConsent()) {
return return@coroutineScope
} }
viewModelScope.launch {
val threePids = contacts.flatMap { contact -> val threePids = contacts.flatMap { contact ->
contact.emails.map { ThreePid.Email(it.email) } + contact.emails.map { ThreePid.Email(it.email) } +
contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) } contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) }
@@ -111,7 +115,7 @@ class ContactsBookViewModel @AssistedInject constructor(
copy(userConsent = false) copy(userConsent = false)
} }
} }
return@launch return@coroutineScope
} }
mappedContacts = allContacts.map { contactModel -> mappedContacts = allContacts.map { contactModel ->
@@ -141,7 +145,6 @@ class ContactsBookViewModel @AssistedInject constructor(
updateFilteredMappedContacts() updateFilteredMappedContacts()
} }
}
private fun updateFilteredMappedContacts() = withState { state -> private fun updateFilteredMappedContacts() = withState { state ->
val filteredMappedContacts = mappedContacts val filteredMappedContacts = mappedContacts
@@ -159,6 +162,7 @@ class ContactsBookViewModel @AssistedInject constructor(
} }
override fun handle(action: ContactsBookAction) { override fun handle(action: ContactsBookAction) {
when (action) { when (action) {
is ContactsBookAction.FilterWith -> handleFilterWith(action) is ContactsBookAction.FilterWith -> handleFilterWith(action)
is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action) is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
@@ -180,15 +184,15 @@ class ContactsBookViewModel @AssistedInject constructor(
} }
private fun handleUserConsentGranted() { private fun handleUserConsentGranted() {
viewModelScope.launch {
session.identityService().setUserConsent(true) session.identityService().setUserConsent(true)
setState { setState {
copy(userConsent = true) copy(userConsent = true)
} }
// Perform the lookup // Perform the lookup
performLookup(allContacts) performLookup(allContacts)
} }
}
private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) { private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) {
setState { setState {

View File

@@ -60,10 +60,11 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
runCatching { fetchIdentityServerWithTerms() }.fold( runCatching { fetchIdentityServerWithTerms() }.fold(
onSuccess = { onSuccess = {
val currentIS = state.identityServer() val currentIS = state.identityServer()
val userConsent = identityService.getUserConsent()
setState { setState {
copy( copy(
identityServer = Success(it), identityServer = Success(it),
userConsent = identityService.getUserConsent() userConsent = userConsent
) )
} }
if (currentIS != it) retrieveBinding() if (currentIS != it) retrieveBinding()
@@ -75,16 +76,24 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
} }
init { init {
setState { loadInitialState()
copy(
identityServer = Success(identityService.getCurrentIdentityServerUrl()?.let { ServerAndPolicies(it, emptyList()) }),
userConsent = identityService.getUserConsent()
)
}
startListenToIdentityManager() startListenToIdentityManager()
observeThreePids() observeThreePids()
} }
private fun loadInitialState() {
viewModelScope.launch {
val identityServer = identityService.getCurrentIdentityServerUrl()?.let { ServerAndPolicies(it, emptyList()) }
val userConsent = identityService.getUserConsent()
setState {
copy(
identityServer = Success(identityServer),
userConsent = userConsent
)
}
}
}
private fun observeThreePids() { private fun observeThreePids() {
session.flow() session.flow()
.liveThreePIds(true) .liveThreePIds(true)
@@ -116,13 +125,14 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
} }
private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) { private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) {
viewModelScope.launch {
identityService.setUserConsent(action.newConsent) identityService.setUserConsent(action.newConsent)
setState { copy(userConsent = action.newConsent) } setState { copy(userConsent = action.newConsent) }
} }
}
private fun disconnectIdentityServer() { private fun disconnectIdentityServer() {
setState { copy(identityServer = Loading()) } setState { copy(identityServer = Loading()) }
viewModelScope.launch { viewModelScope.launch {
try { try {
session.identityService().disconnect() session.identityService().disconnect()

View File

@@ -293,11 +293,13 @@ class VectorSettingsGeneralFragment @Inject constructor(
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
// Refresh identity server summary // Refresh identity server summary
mIdentityServerPreference.summary = session.identityService().getCurrentIdentityServerUrl() ?: getString(R.string.identity_server_not_defined) mIdentityServerPreference.summary = session.identityService().getCurrentIdentityServerUrl() ?: getString(R.string.identity_server_not_defined)
refreshIntegrationManagerSettings() refreshIntegrationManagerSettings()
session.integrationManagerService().addListener(integrationServiceListener) session.integrationManagerService().addListener(integrationServiceListener)
} }
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()

View File

@@ -52,7 +52,9 @@ class LegalsViewModel @AssistedInject constructor(
} }
} }
private fun loadData() = withState { state -> private fun loadData() {
viewModelScope.launch {
val state = awaitState()
loadHomeserver(state) loadHomeserver(state)
val url = session.identityService().getCurrentIdentityServerUrl() val url = session.identityService().getCurrentIdentityServerUrl()
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
@@ -62,6 +64,7 @@ class LegalsViewModel @AssistedInject constructor(
loadIdentityServer(state) loadIdentityServer(state)
} }
} }
}
private fun loadHomeserver(state: LegalsState) { private fun loadHomeserver(state: LegalsState) {
if (state.homeServer !is Success) { if (state.homeServer !is Success) {

View File

@@ -53,6 +53,7 @@ class CreateSpaceViewModel @AssistedInject constructor(
private val identityServerManagerListener = object : IdentityServiceListener { private val identityServerManagerListener = object : IdentityServiceListener {
override fun onIdentityServerChange() { override fun onIdentityServerChange() {
viewModelScope.launch {
val identityServerUrl = identityService.getCurrentIdentityServerUrl() val identityServerUrl = identityService.getCurrentIdentityServerUrl()
setState { setState {
copy( copy(
@@ -61,8 +62,20 @@ class CreateSpaceViewModel @AssistedInject constructor(
} }
} }
} }
}
init { init {
loadInitialState()
startListenToIdentityManager()
}
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<CreateSpaceViewModel, CreateSpaceState> {
override fun create(initialState: CreateSpaceState): CreateSpaceViewModel
}
private fun loadInitialState() {
viewModelScope.launch {
val identityServerUrl = identityService.getCurrentIdentityServerUrl() val identityServerUrl = identityService.getCurrentIdentityServerUrl()
setState { setState {
copy( copy(
@@ -70,12 +83,7 @@ class CreateSpaceViewModel @AssistedInject constructor(
canInviteByMail = identityServerUrl != null canInviteByMail = identityServerUrl != null
) )
} }
startListenToIdentityManager()
} }
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<CreateSpaceViewModel, CreateSpaceState> {
override fun create(initialState: CreateSpaceState): CreateSpaceViewModel
} }
private fun startListenToIdentityManager() { private fun startListenToIdentityManager() {

View File

@@ -31,6 +31,7 @@ import im.vector.app.core.extensions.toggle
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.fetchIdentityServerWithTerms import im.vector.app.features.discovery.fetchIdentityServerWithTerms
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@@ -73,26 +74,32 @@ class UserListViewModel @AssistedInject constructor(
private val identityServerListener = object : IdentityServiceListener { private val identityServerListener = object : IdentityServiceListener {
override fun onIdentityServerChange() { override fun onIdentityServerChange() {
withState { viewModelScope.launch {
identityServerUsersSearch.tryEmit(UserSearch(it.searchTerm)) identityServerUsersSearch.tryEmit(UserSearch(awaitState().searchTerm))
val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) updateConfiguredIdentityServer()
setState {
copy(configuredIdentityServer = identityServerURL)
}
} }
} }
} }
init { init {
loadInitialState()
observeUsers() observeUsers()
setState {
copy(
configuredIdentityServer = cleanISURL(session.identityService().getCurrentIdentityServerUrl())
)
}
session.identityService().addListener(identityServerListener) session.identityService().addListener(identityServerListener)
} }
private fun loadInitialState() {
viewModelScope.launch {
updateConfiguredIdentityServer()
}
}
private suspend fun updateConfiguredIdentityServer() = coroutineScope {
val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl())
setState {
copy(configuredIdentityServer = identityServerURL)
}
}
private fun cleanISURL(url: String?): String? { private fun cleanISURL(url: String?): String? {
return url?.removePrefix("https://") return url?.removePrefix("https://")
} }
@@ -128,11 +135,13 @@ class UserListViewModel @AssistedInject constructor(
} }
private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) { private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) {
viewModelScope.launch {
session.identityService().setUserConsent(action.consent) session.identityService().setUserConsent(action.consent)
withState { withState {
retryUserSearch(it) retryUserSearch(it)
} }
} }
}
private fun handleResumed() { private fun handleResumed() {
withState { withState {