1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-05 15:52:47 +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.
*/
fun getCurrentIdentityServerUrl(): String?
suspend fun getCurrentIdentityServerUrl(): String?
/**
* 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
* 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
@@ -113,7 +113,7 @@ interface IdentityService {
* 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.
*/
fun setUserConsent(newValue: Boolean)
suspend fun setUserConsent(newValue: Boolean)
/**
* Get the status of the current user's threePid.

View File

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

View File

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

View File

@@ -17,5 +17,5 @@
package org.matrix.android.sdk.internal.network.token
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,
private val sessionParamsStore: SessionParamsStore
) : 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,
@SessionDatabase private val realmConfiguration: RealmConfiguration,
@CryptoDatabase private val realmConfigurationCrypto: RealmConfiguration,
@IdentityDatabase private val realmConfigurationIdentity: RealmConfiguration,
@ContentScannerDatabase private val realmConfigurationContentScanner: RealmConfiguration,
private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>,
private val sessionListeners: SessionListeners,
@@ -266,7 +265,7 @@ internal class DefaultSession @Inject constructor(
override fun logDbUsageInfo() {
RealmDebugTools(realmConfiguration).logInfo("Session")
RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto")
RealmDebugTools(realmConfigurationIdentity).logInfo("Identity")
//RealmDebugTools(realmConfigurationIdentity).logInfo("Identity")
RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner")
}
@@ -274,7 +273,7 @@ internal class DefaultSession @Inject constructor(
return listOf(
realmConfiguration,
realmConfigurationCrypto,
realmConfigurationIdentity,
//realmConfigurationIdentity,
realmConfigurationContentScanner,
)
}

View File

@@ -20,6 +20,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
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.model.SignInvitationResult
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.extensions.observeNotNull
import org.matrix.android.sdk.internal.network.RetrofitFactory
@@ -81,7 +84,8 @@ internal class DefaultIdentityService @Inject constructor(
private val accountDataDataSource: UserAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val sign3pidInvitationTask: Sign3pidInvitationTask,
private val sessionParams: SessionParams
private val sessionParams: SessionParams,
@SessionCoroutineScope private val coroutineScope: CoroutineScope,
) : IdentityService, SessionLifecycleObserver {
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
@@ -95,14 +99,17 @@ internal class DefaultIdentityService @Inject constructor(
accountDataDataSource
.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_IDENTITY_SERVER)
.observeNotNull(lifecycleOwner) {
notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel<IdentityServerContent>()?.baseUrl)
coroutineScope.notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel<IdentityServerContent>()?.baseUrl)
}
// 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)
if (identityStore.getIdentityData()?.identityServerUrl == baseUrl) {
Timber.d("Echo of local identity server url change, or no change")
@@ -129,7 +136,7 @@ internal class DefaultIdentityService @Inject constructor(
?: homeServerCapabilitiesService.getHomeServerCapabilities().defaultIdentityServerUrl
}
override fun getCurrentIdentityServerUrl(): String? {
override suspend fun getCurrentIdentityServerUrl(): String? {
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()
}
override fun setUserConsent(newValue: Boolean) {
override suspend fun setUserConsent(newValue: Boolean) {
identityStore.setUserConsent(newValue)
}

View File

@@ -23,5 +23,5 @@ import javax.inject.Inject
internal class IdentityAccessTokenProvider @Inject constructor(
private val identityStore: IdentityStore
) : 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.Module
import dagger.Provides
import io.realm.RealmConfiguration
import io.realm.kotlin.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import okhttp3.OkHttpClient
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.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
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.UnauthenticatedWithCertificate
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.SessionScope
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.RealmIdentityStoreMigration
import java.io.File
@@ -64,25 +68,38 @@ internal abstract class IdentityModule {
@JvmStatic
@Provides
@IdentityDatabase
@SessionScope
fun providesIdentityRealmConfiguration(
fun providesRealmConfiguration(
realmKeysUtils: RealmKeysUtils,
realmIdentityStoreMigration: RealmIdentityStoreMigration,
identityStoreMigration: RealmIdentityStoreMigration,
@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String
): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
.name("matrix-sdk-identity.realm")
return RealmConfiguration.Builder(IDENTITY_REALM_SCHEMA)
.directory(directory.path)
.apply {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
}
.schemaVersion(realmIdentityStoreMigration.schemaVersion)
.migration(realmIdentityStoreMigration)
.allowWritesOnUiThread(true)
.modules(IdentityRealmModule())
.name("matrix-sdk-global.realm")
.schemaVersion(identityStoreMigration.schemaVersion)
.migration(identityStoreMigration)
.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

View File

@@ -21,26 +21,26 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityHashDetail
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.
*/
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("://")
}

View File

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

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
import io.realm.RealmModel
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.identity.toMedium
@RealmClass
internal open class IdentityPendingBindingEntity(
@PrimaryKey var threePid: String = "",
/* Managed by Riot */
var clientSecret: String = "",
/* Managed by Riot */
var sendAttempt: Int = 0,
/* Provided by the identity server */
var sid: String = ""
) : RealmModel {
internal class IdentityPendingBindingEntity : RealmObject {
@PrimaryKey var threePid: String = ""
/* Managed by Riot */
var clientSecret: String = ""
/* Managed by Riot */
var sendAttempt: Int = 0
/* Provided by the identity server */
var sid: String = ""
companion object {
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
import io.realm.annotations.RealmModule
/**
* Realm module for identity server classes.
*/
@RealmModule(
library = true,
classes = [
IdentityDataEntity::class,
IdentityPendingBindingEntity::class
]
internal val IDENTITY_REALM_SCHEMA = setOf(
IdentityDataEntity::class,
IdentityPendingBindingEntity::class,
)
internal class IdentityRealmModule

View File

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

View File

@@ -16,15 +16,16 @@
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.util.database.MatrixRealmMigration
import javax.inject.Inject
internal class RealmIdentityStoreMigration @Inject constructor() : MatrixRealmMigration(
internal class RealmIdentityStoreMigration @Inject constructor() : MatrixAutomaticSchemaMigration(
dbName = "Identity",
schemaVersion = 1L,
) {
/**
* Forces all RealmIdentityStoreMigration instances to be equal.
* 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 hashCode() = 3000
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) {
if (oldVersion < 1) MigrateIdentityTo001(realm).perform()
override fun doMigrate(oldVersion: Long, migrationContext: AutomaticSchemaMigration.MigrationContext) {
if (oldVersion < 1) MigrateIdentityTo001(migrationContext).perform()
}
}

View File

@@ -16,16 +16,13 @@
package org.matrix.android.sdk.internal.session.identity.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.session.identity.db.IdentityDataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import io.realm.kotlin.migration.AutomaticSchemaMigration
import org.matrix.android.sdk.internal.database.KotlinRealmMigrator
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")
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.features.discovery.fetchIdentityServerWithTerms
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
@@ -63,15 +64,19 @@ class ContactsBookViewModel @AssistedInject constructor(
}
private fun loadContacts() {
setState {
copy(
mappedContacts = Loading(),
identityServerUrl = session.identityService().getCurrentIdentityServerUrl(),
userConsent = session.identityService().getUserConsent()
)
}
viewModelScope.launch(Dispatchers.IO) {
val identityServerUrl = session.identityService().getCurrentIdentityServerUrl()
val userConsent = session.identityService().getUserConsent()
setState {
copy(
mappedContacts = Loading(),
identityServerUrl = identityServerUrl,
userConsent = userConsent
)
}
allContacts = contactsDataSource.getContacts(
withEmails = true,
// Do not handle phone numbers for the moment
@@ -90,57 +95,55 @@ class ContactsBookViewModel @AssistedInject constructor(
}
}
private fun performLookup(contacts: List<MappedContact>) {
private suspend fun performLookup(contacts: List<MappedContact>) = coroutineScope {
if (!session.identityService().getUserConsent()) {
return
return@coroutineScope
}
val threePids = contacts.flatMap { contact ->
contact.emails.map { ThreePid.Email(it.email) } +
contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) }
}
viewModelScope.launch {
val threePids = contacts.flatMap { contact ->
contact.emails.map { ThreePid.Email(it.email) } +
contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) }
}
val data = try {
session.identityService().lookUp(threePids)
} catch (failure: Throwable) {
Timber.w(failure, "Unable to perform the lookup")
val data = try {
session.identityService().lookUp(threePids)
} catch (failure: Throwable) {
Timber.w(failure, "Unable to perform the lookup")
// Should not happen, but just to be sure
if (failure is IdentityServiceError.UserConsentNotProvided) {
setState {
copy(userConsent = false)
}
// Should not happen, but just to be sure
if (failure is IdentityServiceError.UserConsentNotProvided) {
setState {
copy(userConsent = false)
}
return@launch
}
mappedContacts = allContacts.map { contactModel ->
contactModel.copy(
emails = contactModel.emails.map { email ->
email.copy(
matrixId = data
.firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email }
?.matrixId
)
},
msisdns = contactModel.msisdns.map { msisdn ->
msisdn.copy(
matrixId = data
.firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber }
?.matrixId
)
}
)
}
setState {
copy(
isBoundRetrieved = true
)
}
updateFilteredMappedContacts()
return@coroutineScope
}
mappedContacts = allContacts.map { contactModel ->
contactModel.copy(
emails = contactModel.emails.map { email ->
email.copy(
matrixId = data
.firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email }
?.matrixId
)
},
msisdns = contactModel.msisdns.map { msisdn ->
msisdn.copy(
matrixId = data
.firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber }
?.matrixId
)
}
)
}
setState {
copy(
isBoundRetrieved = true
)
}
updateFilteredMappedContacts()
}
private fun updateFilteredMappedContacts() = withState { state ->
@@ -159,6 +162,7 @@ class ContactsBookViewModel @AssistedInject constructor(
}
override fun handle(action: ContactsBookAction) {
when (action) {
is ContactsBookAction.FilterWith -> handleFilterWith(action)
is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
@@ -180,14 +184,14 @@ class ContactsBookViewModel @AssistedInject constructor(
}
private fun handleUserConsentGranted() {
session.identityService().setUserConsent(true)
setState {
copy(userConsent = true)
viewModelScope.launch {
session.identityService().setUserConsent(true)
setState {
copy(userConsent = true)
}
// Perform the lookup
performLookup(allContacts)
}
// Perform the lookup
performLookup(allContacts)
}
private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) {

View File

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

View File

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

View File

@@ -52,14 +52,17 @@ class LegalsViewModel @AssistedInject constructor(
}
}
private fun loadData() = withState { state ->
loadHomeserver(state)
val url = session.identityService().getCurrentIdentityServerUrl()
if (url.isNullOrEmpty()) {
setState { copy(hasIdentityServer = false) }
} else {
setState { copy(hasIdentityServer = true) }
loadIdentityServer(state)
private fun loadData() {
viewModelScope.launch {
val state = awaitState()
loadHomeserver(state)
val url = session.identityService().getCurrentIdentityServerUrl()
if (url.isNullOrEmpty()) {
setState { copy(hasIdentityServer = false) }
} else {
setState { copy(hasIdentityServer = true) }
loadIdentityServer(state)
}
}
}

View File

@@ -53,23 +53,19 @@ class CreateSpaceViewModel @AssistedInject constructor(
private val identityServerManagerListener = object : IdentityServiceListener {
override fun onIdentityServerChange() {
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
setState {
copy(
canInviteByMail = identityServerUrl != null
)
viewModelScope.launch {
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
setState {
copy(
canInviteByMail = identityServerUrl != null
)
}
}
}
}
init {
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
setState {
copy(
homeServerName = session.myUserId.getServerName(),
canInviteByMail = identityServerUrl != null
)
}
loadInitialState()
startListenToIdentityManager()
}
@@ -78,6 +74,18 @@ class CreateSpaceViewModel @AssistedInject constructor(
override fun create(initialState: CreateSpaceState): CreateSpaceViewModel
}
private fun loadInitialState() {
viewModelScope.launch {
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
setState {
copy(
homeServerName = session.myUserId.getServerName(),
canInviteByMail = identityServerUrl != null
)
}
}
}
private fun startListenToIdentityManager() {
identityService.addListener(identityServerManagerListener)
}

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