Convert IdentityService to suspend functions

Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
This commit is contained in:
Dominic Fischer 2021-03-27 18:41:29 +00:00
parent 2045a164c1
commit 501b870c35
5 changed files with 119 additions and 167 deletions

View file

@ -16,9 +16,6 @@
package org.matrix.android.sdk.api.session.identity
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
/**
* Provides access to the identity server configuration and services identity server can provide
*/
@ -40,55 +37,55 @@ interface IdentityService {
* See https://matrix.org/docs/spec/identity_service/latest#status-check
* RiotX SDK only supports identity server API v2
*/
fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
suspend fun isValidIdentityServer(url: String)
/**
* Update the identity server url.
* If successful, any previous identity server will be disconnected.
* In case of error, any previous identity server will remain configured.
* @param url the new url.
* @param callback will notify the user if change is successful. The String will be the final url of the identity server.
* @return The String will be the final url of the identity server.
* The SDK can prepend "https://" for instance.
*/
fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable
suspend fun setNewIdentityServer(url: String): String
/**
* Disconnect (logout) from the current identity server
*/
fun disconnect(callback: MatrixCallback<Unit>): Cancelable
suspend fun disconnect()
/**
* This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
*/
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
suspend fun startBindThreePid(threePid: ThreePid)
/**
* This will cancel a pending binding of threePid.
*/
fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
suspend fun cancelBindThreePid(threePid: ThreePid)
/**
* This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
*/
fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
suspend fun sendAgainValidationCode(threePid: ThreePid)
/**
* Submit the code that the identity server has sent to the user (in email or SMS)
* Once successful, you will have to call [finalizeBindThreePid]
* @param code the code sent to the user
*/
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
suspend fun submitValidationToken(threePid: ThreePid, code: String)
/**
* This will perform the actual association of ThreePid and Matrix account
*/
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
suspend fun finalizeBindThreePid(threePid: ThreePid)
/**
* Unbind a threePid
* The request will actually be done on the homeserver
*/
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
suspend fun unbindThreePid(threePid: ThreePid)
/**
* Search MatrixId of users providing email and phone numbers
@ -96,7 +93,7 @@ interface IdentityService {
* Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent]
* Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
*/
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid>
/**
* Return the current user consent for the current identity server, which has been stored using [setUserConsent].
@ -120,9 +117,9 @@ interface IdentityService {
* A lookup will be performed, but also pending binding state will be restored
*
* @param threePids the list of threePid the user owns (retrieved form the homeserver)
* @param callback onSuccess will be called with a map of ThreePid -> SharedState
* @return a map of ThreePid -> SharedState
*/
fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable
suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState>
fun addListener(listener: IdentityServiceListener)
fun removeListener(listener: IdentityServiceListener)

View file

@ -20,7 +20,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import dagger.Lazy
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
@ -33,8 +32,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.extensions.observeNotNull
@ -49,8 +46,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentitySe
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.ensureProtocol
import kotlinx.coroutines.withContext
@ -83,8 +78,7 @@ internal class DefaultIdentityService @Inject constructor(
private val identityApiProvider: IdentityApiProvider,
private val accountDataDataSource: AccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val sessionParams: SessionParams,
private val taskExecutor: TaskExecutor
private val sessionParams: SessionParams
) : IdentityService, SessionLifecycleObserver {
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
@ -136,101 +130,81 @@ internal class DefaultIdentityService @Inject constructor(
return identityStore.getIdentityData()?.identityServerUrl
}
override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
override suspend fun startBindThreePid(threePid: ThreePid) {
if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
callback.onFailure(IdentityServiceError.OutdatedHomeServer)
return NoOpCancellable
throw IdentityServiceError.OutdatedHomeServer
}
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
}
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
}
override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
identityStore.deletePendingBinding(threePid)
}
override suspend fun cancelBindThreePid(threePid: ThreePid) {
identityStore.deletePendingBinding(threePid)
}
override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
}
override suspend fun sendAgainValidationCode(threePid: ThreePid) {
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
}
override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
override suspend fun finalizeBindThreePid(threePid: ThreePid) {
if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
callback.onFailure(IdentityServiceError.OutdatedHomeServer)
return NoOpCancellable
throw IdentityServiceError.OutdatedHomeServer
}
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
}
bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
}
override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
}
override suspend fun submitValidationToken(threePid: ThreePid, code: String) {
submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
}
override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
override suspend fun unbindThreePid(threePid: ThreePid) {
if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
callback.onFailure(IdentityServiceError.OutdatedHomeServer)
return NoOpCancellable
throw IdentityServiceError.OutdatedHomeServer
}
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
}
unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
}
override fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
override suspend fun isValidIdentityServer(url: String) {
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
identityPingTask.execute(IdentityPingTask.Params(api))
}
identityPingTask.execute(IdentityPingTask.Params(api))
}
override fun disconnect(callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
identityDisconnectTask.execute(Unit)
override suspend fun disconnect() {
identityDisconnectTask.execute(Unit)
identityStore.setUrl(null)
updateIdentityAPI(null)
updateAccountData(null)
}
identityStore.setUrl(null)
updateIdentityAPI(null)
updateAccountData(null)
}
override fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable {
override suspend fun setNewIdentityServer(url: String): String {
val urlCandidate = url.ensureProtocol()
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
val current = getCurrentIdentityServerUrl()
if (urlCandidate == current) {
// Nothing to do
Timber.d("Same URL, nothing to do")
} else {
// Disconnect previous one if any, first, because the token will change.
// In case of error when configuring the new identity server, this is not a big deal,
// we will ask for a new token on the previous Identity server
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
val current = getCurrentIdentityServerUrl()
if (urlCandidate == current) {
// Nothing to do
Timber.d("Same URL, nothing to do")
} else {
// Disconnect previous one if any, first, because the token will change.
// In case of error when configuring the new identity server, this is not a big deal,
// we will ask for a new token on the previous Identity server
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
// Try to get a token
val token = getNewIdentityServerToken(urlCandidate)
// Try to get a token
val token = getNewIdentityServerToken(urlCandidate)
identityStore.setUrl(urlCandidate)
identityStore.setToken(token)
updateIdentityAPI(urlCandidate)
identityStore.setUrl(urlCandidate)
identityStore.setToken(token)
updateIdentityAPI(urlCandidate)
updateAccountData(urlCandidate)
}
urlCandidate
updateAccountData(urlCandidate)
}
return urlCandidate
}
private suspend fun updateAccountData(url: String?) {
@ -252,45 +226,38 @@ internal class DefaultIdentityService @Inject constructor(
identityStore.setUserConsent(newValue)
}
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
if (!getUserConsent()) {
callback.onFailure(IdentityServiceError.UserConsentNotProvided)
return NoOpCancellable
throw IdentityServiceError.UserConsentNotProvided
}
if (threePids.isEmpty()) {
callback.onSuccess(emptyList())
return NoOpCancellable
return emptyList()
}
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
lookUpInternal(true, threePids)
}
return lookUpInternal(true, threePids)
}
override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable {
override suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> {
// Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent
// to the home server, and not emails and phone numbers from the contact book of the user
if (threePids.isEmpty()) {
callback.onSuccess(emptyMap())
return NoOpCancellable
return emptyMap()
}
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
val lookupResult = lookUpInternal(true, threePids)
val lookupResult = lookUpInternal(true, threePids)
threePids.associateWith { threePid ->
// If not in lookup result, check if there is a pending binding
if (lookupResult.firstOrNull { it.threePid == threePid } == null) {
if (identityStore.getPendingBinding(threePid) == null) {
SharedState.NOT_SHARED
} else {
SharedState.BINDING_IN_PROGRESS
}
return threePids.associateWith { threePid ->
// If not in lookup result, check if there is a pending binding
if (lookupResult.firstOrNull { it.threePid == threePid } == null) {
if (identityStore.getPendingBinding(threePid) == null) {
SharedState.NOT_SHARED
} else {
SharedState.SHARED
SharedState.BINDING_IN_PROGRESS
}
} else {
SharedState.SHARED
}
}
}

View file

@ -33,9 +33,7 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.FoundThreePid
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.ThreePid
import timber.log.Timber
@ -101,56 +99,56 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
}
}
private fun performLookup(data: List<MappedContact>) {
private fun performLookup(contacts: List<MappedContact>) {
if (!session.identityService().getUserConsent()) {
return
}
viewModelScope.launch {
val threePids = data.flatMap { contact ->
val threePids = contacts.flatMap { contact ->
contact.emails.map { ThreePid.Email(it.email) } +
contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) }
}
session.identityService().lookUp(threePids, object : MatrixCallback<List<FoundThreePid>> {
override fun onFailure(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)
}
}
}
override fun onSuccess(data: List<FoundThreePid>) {
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
)
}
)
}
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(
isBoundRetrieved = true
)
copy(userConsent = false)
}
updateFilteredMappedContacts()
}
})
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()
}
}

View file

@ -35,7 +35,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx
class DiscoverySettingsViewModel @AssistedInject constructor(
@ -123,7 +122,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
awaitCallback<Unit> { session.identityService().disconnect(it) }
session.identityService().disconnect()
setState {
copy(
identityServer = Success(null),
@ -141,9 +140,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
val data = awaitCallback<String?> {
session.identityService().setNewIdentityServer(action.url, it)
}
val data = session.identityService().setNewIdentityServer(action.url)
setState {
copy(
identityServer = Success(data),
@ -163,7 +160,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
awaitCallback<Unit> { identityService.startBindThreePid(action.threePid, it) }
identityService.startBindThreePid(action.threePid)
changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS))
} catch (failure: Throwable) {
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
@ -240,7 +237,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
awaitCallback<Unit> { identityService.unbindThreePid(threePid, it) }
identityService.unbindThreePid(threePid)
changeThreePidState(threePid, Success(SharedState.NOT_SHARED))
} catch (failure: Throwable) {
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
@ -256,7 +253,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
awaitCallback<Unit> { identityService.unbindThreePid(threePid, it) }
identityService.unbindThreePid(threePid)
changeThreePidState(threePid, Success(SharedState.NOT_SHARED))
} catch (failure: Throwable) {
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
@ -268,7 +265,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
private fun cancelBinding(action: DiscoverySettingsAction.CancelBinding) {
viewModelScope.launch {
try {
awaitCallback<Unit> { identityService.cancelBindThreePid(action.threePid, it) }
identityService.cancelBindThreePid(action.threePid)
changeThreePidState(action.threePid, Success(SharedState.NOT_SHARED))
changeThreePidSubmitState(action.threePid, Uninitialized)
} catch (failure: Throwable) {
@ -304,9 +301,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
val data = awaitCallback<Map<ThreePid, SharedState>> {
identityService.getShareStatus(threePids, it)
}
val data = identityService.getShareStatus(threePids)
setState {
copy(
emailList = Success(data.filter { it.key is ThreePid.Email }.toPidInfoList()),
@ -346,9 +341,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
awaitCallback<Unit> {
identityService.submitValidationToken(action.threePid, action.code, it)
}
identityService.submitValidationToken(action.threePid, action.code)
changeThreePidSubmitState(action.threePid, Uninitialized)
finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(action.threePid), true)
} catch (failure: Throwable) {
@ -371,7 +364,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
awaitCallback<Unit> { identityService.finalizeBindThreePid(threePid, it) }
identityService.finalizeBindThreePid(threePid)
changeThreePidSubmitState(action.threePid, Uninitialized)
changeThreePidState(action.threePid, Success(SharedState.SHARED))
} catch (failure: Throwable) {

View file

@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.internal.util.awaitCallback
import java.net.UnknownHostException
class SetIdentityServerViewModel @AssistedInject constructor(
@ -97,9 +96,7 @@ class SetIdentityServerViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
// First ping the identity server v2 API
awaitCallback<Unit> {
mxSession.identityService().isValidIdentityServer(baseUrl, it)
}
mxSession.identityService().isValidIdentityServer(baseUrl)
// Ok, next step
checkTerms(baseUrl)
} catch (failure: Throwable) {