mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 19:36:08 +03:00
Identity: bind/unbnd WIP
This commit is contained in:
parent
637f4a8350
commit
3e808dec90
30 changed files with 807 additions and 230 deletions
|
@ -39,12 +39,30 @@ interface IdentityService {
|
||||||
*/
|
*/
|
||||||
fun setNewIdentityServer(url: String?, callback: MatrixCallback<String?>): Cancelable
|
fun setNewIdentityServer(url: String?, callback: MatrixCallback<String?>): Cancelable
|
||||||
|
|
||||||
fun startBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<ThreePid>)
|
/**
|
||||||
fun finalizeBindSessionFor3PID(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>)
|
* This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid,
|
||||||
fun submitValidationToken(pid: ThreePid, code: String, matrixCallback: MatrixCallback<Unit>)
|
* and then the threePid will be associated with the matrix account
|
||||||
|
*/
|
||||||
|
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun startUnBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<Pair<Boolean, ThreePid?>>)
|
/**
|
||||||
|
* This will perform the actual association of ThreePid and Matrix account
|
||||||
|
*/
|
||||||
|
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param code the code sent to the user phone number
|
||||||
|
*/
|
||||||
|
fun submitValidationToken(pid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request will actually be done on the homeserver
|
||||||
|
*/
|
||||||
|
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search MatrixId of users providing email and phone numbers
|
||||||
|
*/
|
||||||
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||||
|
|
||||||
fun addListener(listener: IdentityServiceListener)
|
fun addListener(listener: IdentityServiceListener)
|
||||||
|
|
|
@ -20,4 +20,6 @@ sealed class IdentityServiceError(cause: Throwable? = null) : Throwable(cause =
|
||||||
object NoIdentityServerConfigured : IdentityServiceError(null)
|
object NoIdentityServerConfigured : IdentityServiceError(null)
|
||||||
object TermsNotSignedException : IdentityServiceError(null)
|
object TermsNotSignedException : IdentityServiceError(null)
|
||||||
object BulkLookupSha256NotSupported : IdentityServiceError(null)
|
object BulkLookupSha256NotSupported : IdentityServiceError(null)
|
||||||
|
object BindingError : IdentityServiceError(null)
|
||||||
|
object NoCurrentBindingError : IdentityServiceError(null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,16 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.identity
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
|
||||||
|
|
||||||
sealed class ThreePid(open val value: String) {
|
sealed class ThreePid(open val value: String) {
|
||||||
data class Email(val email: String) : ThreePid(email)
|
data class Email(val email: String) : ThreePid(email)
|
||||||
data class Msisdn(val msisdn: String, val countryCode: String? = null) : ThreePid(msisdn)
|
data class Msisdn(val msisdn: String, val countryCode: String? = null) : ThreePid(msisdn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ThreePid.toMedium(): String {
|
||||||
|
return when (this) {
|
||||||
|
is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
|
||||||
|
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
data class ThreePidStatus(
|
||||||
|
val threePid: ThreePid,
|
||||||
|
val shareState: SharedState
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class SharedState {
|
||||||
|
SHARED,
|
||||||
|
NOT_SHARED,
|
||||||
|
BINDING_IN_PROGRESS
|
||||||
|
}
|
|
@ -175,23 +175,6 @@ internal abstract class SessionModule {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Provides
|
|
||||||
@IdentityDatabase
|
|
||||||
@SessionScope
|
|
||||||
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
|
||||||
@SessionFilesDirectory directory: File,
|
|
||||||
@UserMd5 userMd5: String): RealmConfiguration {
|
|
||||||
return RealmConfiguration.Builder()
|
|
||||||
.directory(directory)
|
|
||||||
.name("matrix-sdk-identity.realm")
|
|
||||||
.apply {
|
|
||||||
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
|
||||||
}
|
|
||||||
.modules(IdentityRealmModule())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
|
|
@ -21,14 +21,15 @@ import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.api.session.identity.FoundThreePid
|
import im.vector.matrix.android.api.session.identity.FoundThreePid
|
||||||
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.toMedium
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments.base64ToBase64Url
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments.base64ToBase64Url
|
||||||
import im.vector.matrix.android.internal.crypto.tools.withOlmUtility
|
import im.vector.matrix.android.internal.crypto.tools.withOlmUtility
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStore
|
import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStore
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Params
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Params
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Response
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Response
|
||||||
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
|
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -41,12 +42,13 @@ internal interface BulkLookupTask : Task<BulkLookupTask.Params, List<FoundThreeP
|
||||||
|
|
||||||
internal class DefaultBulkLookupTask @Inject constructor(
|
internal class DefaultBulkLookupTask @Inject constructor(
|
||||||
private val identityApiProvider: IdentityApiProvider,
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
private val identityServiceStore: IdentityServiceStore
|
private val identityServiceStore: IdentityServiceStore,
|
||||||
|
@UserId private val userId: String
|
||||||
) : BulkLookupTask {
|
) : BulkLookupTask {
|
||||||
|
|
||||||
override suspend fun execute(params: BulkLookupTask.Params): List<FoundThreePid> {
|
override suspend fun execute(params: BulkLookupTask.Params): List<FoundThreePid> {
|
||||||
val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
|
val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
|
||||||
val entity = identityServiceStore.get() ?: throw IdentityServiceError.NoIdentityServerConfigured
|
val entity = identityServiceStore.getIdentityServerDetails() ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
val pepper = entity.hashLookupPepper
|
val pepper = entity.hashLookupPepper
|
||||||
val hashDetailResponse = if (pepper == null) {
|
val hashDetailResponse = if (pepper == null) {
|
||||||
// We need to fetch the hash details first
|
// We need to fetch the hash details first
|
||||||
|
@ -128,11 +130,4 @@ internal class DefaultBulkLookupTask @Inject constructor(
|
||||||
FoundThreePid(threePids[hashedAddresses.indexOf(hashedAddress)], identityLookUpV2Response.mappings[hashedAddress] ?: error(""))
|
FoundThreePid(threePids[hashedAddresses.indexOf(hashedAddress)], identityLookUpV2Response.mappings[hashedAddress] ?: error(""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ThreePid.toMedium(): String {
|
|
||||||
return when (this) {
|
|
||||||
is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
|
|
||||||
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@ import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStor
|
||||||
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
||||||
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
|
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
|
||||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||||
|
import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
|
||||||
|
import im.vector.matrix.android.internal.session.profile.UnbindThreePidsTask
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
|
@ -59,6 +61,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
private val bulkLookupTask: BulkLookupTask,
|
private val bulkLookupTask: BulkLookupTask,
|
||||||
private val identityRegisterTask: IdentityRegisterTask,
|
private val identityRegisterTask: IdentityRegisterTask,
|
||||||
private val identityDisconnectTask: IdentityDisconnectTask,
|
private val identityDisconnectTask: IdentityDisconnectTask,
|
||||||
|
private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask,
|
||||||
@Unauthenticated
|
@Unauthenticated
|
||||||
private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
|
private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
|
||||||
@AuthenticatedIdentity
|
@AuthenticatedIdentity
|
||||||
|
@ -66,6 +69,8 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
private val retrofitFactory: RetrofitFactory,
|
private val retrofitFactory: RetrofitFactory,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
|
private val bindThreePidsTask: BindThreePidsTask,
|
||||||
|
private val unbindThreePidsTask: UnbindThreePidsTask,
|
||||||
private val identityApiProvider: IdentityApiProvider,
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
private val accountDataDataSource: AccountDataDataSource
|
private val accountDataDataSource: AccountDataDataSource
|
||||||
) : IdentityService {
|
) : IdentityService {
|
||||||
|
@ -85,12 +90,12 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init identityApi
|
// Init identityApi
|
||||||
updateIdentityAPI(identityServiceStore.get()?.identityServerUrl)
|
updateIdentityAPI(identityServiceStore.getIdentityServerDetails()?.identityServerUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyIdentityServerUrlChange(baseUrl: String?) {
|
private fun notifyIdentityServerUrlChange(baseUrl: String?) {
|
||||||
// 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 (identityServiceStore.get()?.identityServerUrl == baseUrl) {
|
if (identityServiceStore.getIdentityServerDetails()?.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")
|
||||||
} else {
|
} else {
|
||||||
// Url has changed, we have to reset our store, update internal configuration and notify listeners
|
// Url has changed, we have to reset our store, update internal configuration and notify listeners
|
||||||
|
@ -111,23 +116,29 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCurrentIdentityServer(): String? {
|
override fun getCurrentIdentityServer(): String? {
|
||||||
return identityServiceStore.get()?.identityServerUrl
|
return identityServiceStore.getIdentityServerDetails()?.identityServerUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<ThreePid>) {
|
override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun submitValidationToken(pid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finalizeBindSessionFor3PID(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>) {
|
override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
TODO("Not yet implemented")
|
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
}
|
unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
|
||||||
|
}
|
||||||
override fun submitValidationToken(pid: ThreePid, code: String, matrixCallback: MatrixCallback<Unit>) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun startUnBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<Pair<Boolean, ThreePid?>>) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setNewIdentityServer(url: String?, callback: MatrixCallback<String?>): Cancelable {
|
override fun setNewIdentityServer(url: String?, callback: MatrixCallback<String?>): Cancelable {
|
||||||
|
@ -148,7 +159,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
Timber.d("Same URL, nothing to do")
|
Timber.d("Same URL, nothing to do")
|
||||||
null -> {
|
null -> {
|
||||||
// Disconnect previous one if any
|
// Disconnect previous one if any
|
||||||
identityServiceStore.get()?.let {
|
identityServiceStore.getIdentityServerDetails()?.let {
|
||||||
if (it.identityServerUrl != null && it.token != null) {
|
if (it.identityServerUrl != null && it.token != null) {
|
||||||
// Disconnect, ignoring any error
|
// Disconnect, ignoring any error
|
||||||
runCatching {
|
runCatching {
|
||||||
|
@ -216,7 +227,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ensureToken() {
|
private suspend fun ensureToken() {
|
||||||
val entity = identityServiceStore.get() ?: throw IdentityServiceError.NoIdentityServerConfigured
|
val entity = identityServiceStore.getIdentityServerDetails() ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
val url = entity.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured
|
val url = entity.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
if (entity.token == null) {
|
if (entity.token == null) {
|
||||||
|
@ -246,7 +257,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
private fun updateIdentityAPI(url: String?) {
|
private fun updateIdentityAPI(url: String?) {
|
||||||
identityApiProvider.identityApi = url
|
identityApiProvider.identityApi = url
|
||||||
?.let { retrofitFactory.create(okHttpClient, it) }
|
?.let { retrofitFactory.create(okHttpClient, it) }
|
||||||
?.let { it.create(IdentityAPI::class.java) }
|
?.create(IdentityAPI::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ import im.vector.matrix.android.internal.session.identity.model.IdentityHashDeta
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Params
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Params
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Response
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpV2Response
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
@ -62,12 +65,22 @@ internal interface IdentityAPI {
|
||||||
fun bulkLookupV2(@Body body: IdentityLookUpV2Params): Call<IdentityLookUpV2Response>
|
fun bulkLookupV2(@Body body: IdentityLookUpV2Params): Call<IdentityLookUpV2Response>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the ownership validation of an email address or a phone number previously set
|
* Create a session to change the bind status of an email to an identity server
|
||||||
* by [ProfileApi.requestEmailValidation]
|
* The identity server will also send an email
|
||||||
*
|
*
|
||||||
* @param medium the medium of the 3pid
|
* @param body
|
||||||
|
* @return the sid
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken")
|
||||||
fun requestOwnershipValidationV2(@Path("medium") medium: String?,
|
fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): Call<IdentityRequestTokenResponse>
|
||||||
@Body body: IdentityRequestOwnershipParams): Call<SuccessResult>
|
|
||||||
|
/**
|
||||||
|
* Create a session to change the bind status of an phone number to an identity server
|
||||||
|
* The identity server will also send an SMS on the ThreePid provided
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @return the sid
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken")
|
||||||
|
fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call<IdentityRequestTokenResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,5 @@ import javax.inject.Inject
|
||||||
internal class IdentityAccessTokenProvider @Inject constructor(
|
internal class IdentityAccessTokenProvider @Inject constructor(
|
||||||
private val identityServiceStore: IdentityServiceStore
|
private val identityServiceStore: IdentityServiceStore
|
||||||
) : AccessTokenProvider {
|
) : AccessTokenProvider {
|
||||||
override fun getToken() = identityServiceStore.get()?.token
|
override fun getToken() = identityServiceStore.getIdentityServerDetails()?.token
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,23 @@ package im.vector.matrix.android.internal.session.identity
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||||
|
import im.vector.matrix.android.internal.di.IdentityDatabase
|
||||||
|
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||||
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
|
import im.vector.matrix.android.internal.session.SessionModule
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.identity.db.IdentityRealmModule
|
||||||
import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStore
|
import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStore
|
||||||
import im.vector.matrix.android.internal.session.identity.db.RealmIdentityServerStore
|
import im.vector.matrix.android.internal.session.identity.db.RealmIdentityServiceStore
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
internal abstract class IdentityModule {
|
internal abstract class IdentityModule {
|
||||||
|
@ -56,6 +64,26 @@ internal abstract class IdentityModule {
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@IdentityDatabase
|
||||||
|
@SessionScope
|
||||||
|
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||||
|
@SessionFilesDirectory directory: File,
|
||||||
|
@UserMd5 userMd5: String): RealmConfiguration {
|
||||||
|
return RealmConfiguration.Builder()
|
||||||
|
.directory(directory)
|
||||||
|
.name("matrix-sdk-identity.realm")
|
||||||
|
.apply {
|
||||||
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
|
}
|
||||||
|
.modules(IdentityRealmModule())
|
||||||
|
// TODO Handle migration properly
|
||||||
|
.deleteRealmIfMigrationNeeded()
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
|
@ -63,11 +91,14 @@ internal abstract class IdentityModule {
|
||||||
abstract fun bindAccessTokenProvider(provider: IdentityAccessTokenProvider): AccessTokenProvider
|
abstract fun bindAccessTokenProvider(provider: IdentityAccessTokenProvider): AccessTokenProvider
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindIdentityServiceStore(store: RealmIdentityServerStore): IdentityServiceStore
|
abstract fun bindIdentityServiceStore(store: RealmIdentityServiceStore): IdentityServiceStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindIdentityRegisterTask(task: DefaultIdentityRegisterTask): IdentityRegisterTask
|
abstract fun bindIdentityRegisterTask(task: DefaultIdentityRegisterTask): IdentityRegisterTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityRequestTokenForBindingTask(task: DefaultIdentityRequestTokenForBindingTask): IdentityRequestTokenForBindingTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindBulkLookupTask(task: DefaultBulkLookupTask): BulkLookupTask
|
abstract fun bindBulkLookupTask(task: DefaultBulkLookupTask): BulkLookupTask
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.db.RealmIdentityServiceStore
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface IdentityRequestTokenForBindingTask : Task<IdentityRequestTokenForBindingTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val threePid: ThreePid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
|
||||||
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
|
private val identityServiceStore: RealmIdentityServiceStore,
|
||||||
|
@UserId private val userId: String
|
||||||
|
) : IdentityRequestTokenForBindingTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: IdentityRequestTokenForBindingTask.Params) {
|
||||||
|
val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
|
||||||
|
|
||||||
|
val clientSecret = UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
val tokenResponse = executeRequest<IdentityRequestTokenResponse>(null) {
|
||||||
|
apiCall = when (params.threePid) {
|
||||||
|
is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody(
|
||||||
|
clientSecret = clientSecret,
|
||||||
|
sendAttempt = 1,
|
||||||
|
email = params.threePid.email
|
||||||
|
))
|
||||||
|
is ThreePid.Msisdn -> identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody(
|
||||||
|
clientSecret = clientSecret,
|
||||||
|
sendAttempt = 1,
|
||||||
|
phoneNumber = params.threePid.msisdn,
|
||||||
|
countryCode = params.threePid.countryCode
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenResponse.success) {
|
||||||
|
throw IdentityServiceError.BindingError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store client secret and sid
|
||||||
|
identityServiceStore.storePendingBinding(
|
||||||
|
params.threePid,
|
||||||
|
clientSecret,
|
||||||
|
tokenResponse.sid)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityAccountResponse
|
||||||
|
|
||||||
|
internal suspend fun getIdentityApiAndEnsureTerms(identityApiProvider: IdentityApiProvider, userId: String): IdentityAPI {
|
||||||
|
val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
|
// Always check that we have access to the service (regarding terms)
|
||||||
|
val identityAccountResponse = executeRequest<IdentityAccountResponse>(null) {
|
||||||
|
apiCall = identityAPI.getAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(userId == identityAccountResponse.userId)
|
||||||
|
|
||||||
|
return identityAPI
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.db
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
internal open class IdentityPendingBindingEntity(
|
||||||
|
var threePidValue: String = "",
|
||||||
|
var medium: String = "",
|
||||||
|
var clientSecret: String = "",
|
||||||
|
var sid: String = ""
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.db
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.toMedium
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun IdentityPendingBindingEntity.Companion.get(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity? {
|
||||||
|
return realm.where<IdentityPendingBindingEntity>()
|
||||||
|
.equalTo(IdentityPendingBindingEntityFields.THREE_PID_VALUE, threePid.value)
|
||||||
|
.equalTo(IdentityPendingBindingEntityFields.MEDIUM, threePid.toMedium())
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IdentityPendingBindingEntity.Companion.getOrCreate(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity {
|
||||||
|
return get(realm, threePid) ?: realm.createObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IdentityPendingBindingEntity.Companion.delete(realm: Realm, threePid: ThreePid) {
|
||||||
|
realm.where<IdentityPendingBindingEntity>()
|
||||||
|
.equalTo(IdentityPendingBindingEntityFields.THREE_PID_VALUE, threePid.value)
|
||||||
|
.equalTo(IdentityPendingBindingEntityFields.MEDIUM, threePid.toMedium())
|
||||||
|
.findAll()
|
||||||
|
.deleteAllFromRealm()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IdentityPendingBindingEntity.Companion.deleteAll(realm: Realm) {
|
||||||
|
realm.where<IdentityPendingBindingEntity>()
|
||||||
|
.findAll()
|
||||||
|
.deleteAllFromRealm()
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import io.realm.annotations.RealmModule
|
||||||
*/
|
*/
|
||||||
@RealmModule(library = true,
|
@RealmModule(library = true,
|
||||||
classes = [
|
classes = [
|
||||||
IdentityServerEntity::class
|
IdentityServerEntity::class,
|
||||||
|
IdentityPendingBindingEntity::class
|
||||||
])
|
])
|
||||||
internal class IdentityRealmModule
|
internal class IdentityRealmModule
|
||||||
|
|
|
@ -35,6 +35,8 @@ private fun IdentityServerEntity.Companion.getOrCreate(realm: Realm): IdentitySe
|
||||||
internal fun IdentityServerEntity.Companion.setUrl(realm: Realm,
|
internal fun IdentityServerEntity.Companion.setUrl(realm: Realm,
|
||||||
url: String?) {
|
url: String?) {
|
||||||
realm.where<IdentityServerEntity>().findAll().deleteAllFromRealm()
|
realm.where<IdentityServerEntity>().findAll().deleteAllFromRealm()
|
||||||
|
// Delete all pending binding if any
|
||||||
|
IdentityPendingBindingEntity.deleteAll(realm)
|
||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
getOrCreate(realm).apply {
|
getOrCreate(realm).apply {
|
||||||
|
|
|
@ -16,15 +16,27 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.identity.db
|
package im.vector.matrix.android.internal.session.identity.db
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||||
|
|
||||||
internal interface IdentityServiceStore {
|
internal interface IdentityServiceStore {
|
||||||
|
|
||||||
fun get(): IdentityServerEntity?
|
fun getIdentityServerDetails(): IdentityServerEntity?
|
||||||
|
|
||||||
fun setUrl(url: String?)
|
fun setUrl(url: String?)
|
||||||
|
|
||||||
fun setToken(token: String?)
|
fun setToken(token: String?)
|
||||||
|
|
||||||
fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
|
fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store details about a current binding
|
||||||
|
*/
|
||||||
|
fun storePendingBinding(threePid: ThreePid,
|
||||||
|
clientSecret: String,
|
||||||
|
sid: String)
|
||||||
|
|
||||||
|
fun getPendingBinding(threePid: ThreePid): IdentityPendingBindingEntity?
|
||||||
|
|
||||||
|
fun deletePendingBinding(threePid: ThreePid)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.identity.db
|
package im.vector.matrix.android.internal.session.identity.db
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.toMedium
|
||||||
import im.vector.matrix.android.internal.di.IdentityDatabase
|
import im.vector.matrix.android.internal.di.IdentityDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||||
|
@ -24,12 +26,12 @@ import io.realm.RealmConfiguration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class RealmIdentityServerStore @Inject constructor(
|
internal class RealmIdentityServiceStore @Inject constructor(
|
||||||
@IdentityDatabase
|
@IdentityDatabase
|
||||||
private val realmConfiguration: RealmConfiguration
|
private val realmConfiguration: RealmConfiguration
|
||||||
) : IdentityServiceStore {
|
) : IdentityServiceStore {
|
||||||
|
|
||||||
override fun get(): IdentityServerEntity? {
|
override fun getIdentityServerDetails(): IdentityServerEntity? {
|
||||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
IdentityServerEntity.get(realm)?.let { realm.copyFromRealm(it) }
|
IdentityServerEntity.get(realm)?.let { realm.copyFromRealm(it) }
|
||||||
}
|
}
|
||||||
|
@ -58,4 +60,31 @@ internal class RealmIdentityServerStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun storePendingBinding(threePid: ThreePid, clientSecret: String, sid: String) {
|
||||||
|
Realm.getInstance(realmConfiguration).use {
|
||||||
|
it.executeTransaction { realm ->
|
||||||
|
IdentityPendingBindingEntity.getOrCreate(realm, threePid).let { entity ->
|
||||||
|
entity.threePidValue = threePid.value
|
||||||
|
entity.medium = threePid.toMedium()
|
||||||
|
entity.clientSecret = clientSecret
|
||||||
|
entity.sid = sid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPendingBinding(threePid: ThreePid): IdentityPendingBindingEntity? {
|
||||||
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
|
IdentityPendingBindingEntity.get(realm, threePid)?.let { realm.copyFromRealm(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deletePendingBinding(threePid: ThreePid) {
|
||||||
|
Realm.getInstance(realmConfiguration).use {
|
||||||
|
it.executeTransaction { realm ->
|
||||||
|
IdentityPendingBindingEntity.delete(realm, threePid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
// Just to consider common parameters
|
||||||
|
private interface IdentityRequestTokenBody {
|
||||||
|
val clientSecret: String
|
||||||
|
val sendAttempt: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class IdentityRequestTokenForEmailBody(
|
||||||
|
@Json(name = "client_secret")
|
||||||
|
override val clientSecret: String,
|
||||||
|
|
||||||
|
@Json(name = "send_attempt")
|
||||||
|
override val sendAttempt: Int,
|
||||||
|
|
||||||
|
@Json(name = "email")
|
||||||
|
val email: String
|
||||||
|
) : IdentityRequestTokenBody
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class IdentityRequestTokenForMsisdnBody(
|
||||||
|
@Json(name = "client_secret")
|
||||||
|
override val clientSecret: String,
|
||||||
|
|
||||||
|
@Json(name = "send_attempt")
|
||||||
|
override val sendAttempt: Int,
|
||||||
|
|
||||||
|
@Json(name = "phone_number")
|
||||||
|
val phoneNumber: String,
|
||||||
|
|
||||||
|
@Json(name = "country")
|
||||||
|
val countryCode: String?
|
||||||
|
) : IdentityRequestTokenBody
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class IdentityRequestTokenResponse(
|
||||||
|
@Json(name = "sid")
|
||||||
|
val sid: String,
|
||||||
|
|
||||||
|
@Json(name = "success")
|
||||||
|
val success: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class BindThreePidBody(
|
||||||
|
/**
|
||||||
|
* Required. The client secret used in the session with the identity server.
|
||||||
|
*/
|
||||||
|
@Json(name = "client_secret")
|
||||||
|
val clientSecret: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The identity server to use. (without "https://")
|
||||||
|
*/
|
||||||
|
@Json(name = "id_server")
|
||||||
|
var idServer: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. An access token previously registered with the identity server.
|
||||||
|
*/
|
||||||
|
@Json(name = "id_access_token")
|
||||||
|
var idAccessToken: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The session identifier given by the identity server.
|
||||||
|
*/
|
||||||
|
@Json(name = "sid")
|
||||||
|
var sid: String
|
||||||
|
)
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStore
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal abstract class BindThreePidsTask : Task<BindThreePidsTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val threePid: ThreePid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultBindThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
|
||||||
|
private val identityServiceStore: IdentityServiceStore,
|
||||||
|
private val eventBus: EventBus) : BindThreePidsTask() {
|
||||||
|
override suspend fun execute(params: Params) {
|
||||||
|
val idServer = identityServiceStore.getIdentityServerDetails()?.identityServerUrl?.substringAfter("://") ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
val idToken = identityServiceStore.getIdentityServerDetails()?.token ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
val pendingThreePid = identityServiceStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
|
||||||
|
|
||||||
|
executeRequest<Unit>(eventBus) {
|
||||||
|
apiCall = profileAPI.bindThreePid(
|
||||||
|
BindThreePidBody(
|
||||||
|
clientSecret = pendingThreePid.clientSecret,
|
||||||
|
idServer = idServer,
|
||||||
|
idAccessToken = idToken,
|
||||||
|
sid = pendingThreePid.sid
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binding is over, cleanup the store
|
||||||
|
identityServiceStore.deletePendingBinding(params.threePid)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,9 @@ package im.vector.matrix.android.internal.session.profile
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
internal interface ProfileAPI {
|
internal interface ProfileAPI {
|
||||||
|
@ -39,4 +41,18 @@ internal interface ProfileAPI {
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid")
|
||||||
fun getThreePIDs(): Call<AccountThreePidsResponse>
|
fun getThreePIDs(): Call<AccountThreePidsResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind a threePid
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind")
|
||||||
|
fun bindThreePid(@Body body: BindThreePidBody): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbind a threePid
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-unbind
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind")
|
||||||
|
fun unbindThreePid(@Body body: UnbindThreePidBody): Call<UnbindThreePidResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,4 +45,10 @@ internal abstract class ProfileModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRefreshUserThreePidsTask(task: DefaultRefreshUserThreePidsTask): RefreshUserThreePidsTask
|
abstract fun bindRefreshUserThreePidsTask(task: DefaultRefreshUserThreePidsTask): RefreshUserThreePidsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindBindThreePidsTask(task: DefaultBindThreePidsTask): BindThreePidsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUnbindThreePidsTask(task: DefaultUnbindThreePidsTask): UnbindThreePidsTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class UnbindThreePidBody(
|
||||||
|
/**
|
||||||
|
* The identity server to unbind from. If not provided, the homeserver MUST use the id_server the identifier was added through.
|
||||||
|
* If the homeserver does not know the original id_server, it MUST return a id_server_unbind_result of no-support.
|
||||||
|
*/
|
||||||
|
@Json(name = "id_server")
|
||||||
|
val idServer: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The medium of the third party identifier being removed. One of: ["email", "msisdn"]
|
||||||
|
*/
|
||||||
|
@Json(name = "medium")
|
||||||
|
val medium: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The third party address being removed.
|
||||||
|
*/
|
||||||
|
@Json(name = "address")
|
||||||
|
val address: String
|
||||||
|
)
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class UnbindThreePidResponse(
|
||||||
|
@Json(name = "id_server_unbind_result")
|
||||||
|
val idServerUnbindResult: String?
|
||||||
|
) {
|
||||||
|
fun isSuccess() = idServerUnbindResult == "success"
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.toMedium
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStore
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal abstract class UnbindThreePidsTask : Task<UnbindThreePidsTask.Params, Boolean> {
|
||||||
|
data class Params(
|
||||||
|
val threePid: ThreePid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultUnbindThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
|
||||||
|
private val identityServiceStore: IdentityServiceStore,
|
||||||
|
private val eventBus: EventBus) : UnbindThreePidsTask() {
|
||||||
|
override suspend fun execute(params: Params): Boolean {
|
||||||
|
val idServer = identityServiceStore.getIdentityServerDetails()?.identityServerUrl?.substringAfter("://") ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
|
return executeRequest<UnbindThreePidResponse>(eventBus) {
|
||||||
|
apiCall = profileAPI.unbindThreePid(
|
||||||
|
UnbindThreePidBody(
|
||||||
|
idServer,
|
||||||
|
params.threePid.toMedium(),
|
||||||
|
params.threePid.value
|
||||||
|
))
|
||||||
|
}.isSuccess()
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import com.airbnb.mvrx.Incomplete
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||||
|
import im.vector.matrix.android.api.session.identity.SharedState
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.loadingItem
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
@ -116,20 +118,19 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
is Success -> when (piState.isShared()) {
|
is Success -> when (piState.isShared()) {
|
||||||
PidInfo.SharedState.SHARED,
|
SharedState.SHARED,
|
||||||
PidInfo.SharedState.NOT_SHARED -> {
|
SharedState.NOT_SHARED -> {
|
||||||
checked(piState.isShared() == PidInfo.SharedState.SHARED)
|
checked(piState.isShared() == SharedState.SHARED)
|
||||||
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
|
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
|
||||||
switchChangeListener { _, checked ->
|
switchChangeListener { _, checked ->
|
||||||
if (checked) {
|
if (checked) {
|
||||||
listener?.onTapShareMsisdn(piState.threePid.value)
|
listener?.onTapShare(piState.threePid)
|
||||||
} else {
|
} else {
|
||||||
listener?.onTapRevokeMsisdn(piState.threePid.value)
|
listener?.onTapRevoke(piState.threePid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
|
SharedState.BINDING_IN_PROGRESS -> {
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
|
|
||||||
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
|
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
|
||||||
buttonTitle("")
|
buttonTitle("")
|
||||||
}
|
}
|
||||||
|
@ -137,21 +138,20 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (piState.isShared()) {
|
when (piState.isShared()) {
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
|
SharedState.BINDING_IN_PROGRESS -> {
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
|
|
||||||
settingsItemText {
|
settingsItemText {
|
||||||
id("tverif" + piState.threePid.value)
|
id("tverif" + piState.threePid.value)
|
||||||
descriptionText(stringProvider.getString(R.string.settings_text_message_sent, phoneNumber))
|
descriptionText(stringProvider.getString(R.string.settings_text_message_sent, phoneNumber))
|
||||||
interactionListener(object : SettingsItemText.Listener {
|
interactionListener(object : SettingsItemText.Listener {
|
||||||
override fun onValidate(code: String) {
|
override fun onValidate(code: String) {
|
||||||
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND
|
if (piState.threePid is ThreePid.Msisdn) {
|
||||||
listener?.checkMsisdnVerification(piState.threePid.value, code, bind)
|
listener?.checkMsisdnVerification(piState.threePid, code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> Unit
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,27 +202,27 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
is Success -> when (piState.isShared()) {
|
is Success -> when (piState.isShared()) {
|
||||||
PidInfo.SharedState.SHARED,
|
SharedState.SHARED,
|
||||||
PidInfo.SharedState.NOT_SHARED -> {
|
SharedState.NOT_SHARED -> {
|
||||||
checked(piState.isShared() == PidInfo.SharedState.SHARED)
|
checked(piState.isShared() == SharedState.SHARED)
|
||||||
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
|
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
|
||||||
switchChangeListener { _, checked ->
|
switchChangeListener { _, checked ->
|
||||||
if (checked) {
|
if (checked) {
|
||||||
listener?.onTapShareEmail(piState.threePid.value)
|
listener?.onTapShare(piState.threePid)
|
||||||
} else {
|
} else {
|
||||||
listener?.onTapRevokeEmail(piState.threePid.value)
|
listener?.onTapRevoke(piState.threePid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
|
SharedState.BINDING_IN_PROGRESS -> {
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
|
|
||||||
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
|
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
|
||||||
buttonTitleId(R.string._continue)
|
buttonTitleId(R.string._continue)
|
||||||
infoMessageTintColorId(R.color.vector_info_color)
|
infoMessageTintColorId(R.color.vector_info_color)
|
||||||
infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.threePid.value))
|
infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.threePid.value))
|
||||||
buttonClickListener(View.OnClickListener {
|
buttonClickListener(View.OnClickListener {
|
||||||
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND
|
if (piState.threePid is ThreePid.Email) {
|
||||||
listener?.checkEmailVerification(piState.threePid.value, bind)
|
listener?.checkEmailVerification(piState.threePid)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,12 +296,10 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onSelectIdentityServer()
|
fun onSelectIdentityServer()
|
||||||
fun onTapRevokeEmail(email: String)
|
fun onTapRevoke(threePid: ThreePid)
|
||||||
fun onTapShareEmail(email: String)
|
fun onTapShare(threePid: ThreePid)
|
||||||
fun checkEmailVerification(email: String, bind: Boolean)
|
fun checkEmailVerification(threePid: ThreePid.Email)
|
||||||
fun checkMsisdnVerification(msisdn: String, code: String, bind: Boolean)
|
fun checkMsisdnVerification(threePid: ThreePid.Msisdn, code: String)
|
||||||
fun onTapRevokeMsisdn(msisdn: String)
|
|
||||||
fun onTapShareMsisdn(msisdn: String)
|
|
||||||
fun onTapChangeIdentityServer()
|
fun onTapChangeIdentityServer()
|
||||||
fun onTapDisconnectIdentityServer()
|
fun onTapDisconnectIdentityServer()
|
||||||
fun onTapRetryToRetrieveBindings()
|
fun onTapRetryToRetrieveBindings()
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.matrix.android.api.session.identity.SharedState
|
||||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.matrix.android.api.session.terms.TermsService
|
import im.vector.matrix.android.api.session.terms.TermsService
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -113,34 +114,26 @@ class DiscoverySettingsFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTapRevokeEmail(email: String) {
|
override fun onTapRevoke(threePid: ThreePid) {
|
||||||
viewModel.handle(DiscoverySettingsAction.RevokeThreePid(ThreePid.Email(email)))
|
viewModel.handle(DiscoverySettingsAction.RevokeThreePid(threePid))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTapShareEmail(email: String) {
|
override fun onTapShare(threePid: ThreePid) {
|
||||||
viewModel.handle(DiscoverySettingsAction.ShareThreePid(ThreePid.Email(email)))
|
viewModel.handle(DiscoverySettingsAction.ShareThreePid(threePid))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkEmailVerification(email: String, bind: Boolean) {
|
override fun checkEmailVerification(threePid: ThreePid.Email) {
|
||||||
viewModel.handle(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Email(email), bind))
|
viewModel.handle(DiscoverySettingsAction.FinalizeBind3pid(threePid))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkMsisdnVerification(msisdn: String, code: String, bind: Boolean) {
|
override fun checkMsisdnVerification(threePid: ThreePid.Msisdn, code: String) {
|
||||||
viewModel.handle(DiscoverySettingsAction.SubmitMsisdnToken(msisdn, code, bind))
|
viewModel.handle(DiscoverySettingsAction.SubmitMsisdnToken(threePid, code))
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTapRevokeMsisdn(msisdn: String) {
|
|
||||||
viewModel.handle(DiscoverySettingsAction.RevokeThreePid(ThreePid.Msisdn(msisdn)))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTapShareMsisdn(msisdn: String) {
|
|
||||||
viewModel.handle(DiscoverySettingsAction.ShareThreePid(ThreePid.Msisdn(msisdn)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTapChangeIdentityServer() = withState(viewModel) { state ->
|
override fun onTapChangeIdentityServer() = withState(viewModel) { state ->
|
||||||
//we should prompt if there are bound items with current is
|
//we should prompt if there are bound items with current is
|
||||||
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
|
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
|
||||||
val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
|
val hasBoundIds = pidList.any { it.isShared() == SharedState.SHARED }
|
||||||
|
|
||||||
if (hasBoundIds) {
|
if (hasBoundIds) {
|
||||||
//we should prompt
|
//we should prompt
|
||||||
|
@ -160,7 +153,7 @@ class DiscoverySettingsFragment @Inject constructor(
|
||||||
//we should prompt if there are bound items with current is
|
//we should prompt if there are bound items with current is
|
||||||
withState(viewModel) { state ->
|
withState(viewModel) { state ->
|
||||||
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
|
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
|
||||||
val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
|
val hasBoundIds = pidList.any { it.isShared() == SharedState.SHARED }
|
||||||
|
|
||||||
if (hasBoundIds) {
|
if (hasBoundIds) {
|
||||||
//we should prompt
|
//we should prompt
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.identity.FoundThreePid
|
import im.vector.matrix.android.api.session.identity.FoundThreePid
|
||||||
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
import im.vector.matrix.android.api.session.identity.IdentityServiceListener
|
import im.vector.matrix.android.api.session.identity.IdentityServiceListener
|
||||||
|
import im.vector.matrix.android.api.session.identity.SharedState
|
||||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.core.extensions.exhaustive
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
|
@ -44,14 +45,7 @@ data class PidInfo(
|
||||||
val threePid: ThreePid,
|
val threePid: ThreePid,
|
||||||
// Retrieved from IdentityServer, or transient state
|
// Retrieved from IdentityServer, or transient state
|
||||||
val isShared: Async<SharedState>
|
val isShared: Async<SharedState>
|
||||||
) {
|
)
|
||||||
enum class SharedState {
|
|
||||||
SHARED,
|
|
||||||
NOT_SHARED,
|
|
||||||
NOT_VERIFIED_FOR_BIND,
|
|
||||||
NOT_VERIFIED_FOR_UNBIND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class DiscoverySettingsState(
|
data class DiscoverySettingsState(
|
||||||
val identityServer: Async<String?> = Uninitialized,
|
val identityServer: Async<String?> = Uninitialized,
|
||||||
|
@ -68,8 +62,8 @@ sealed class DiscoverySettingsAction : VectorViewModelAction {
|
||||||
data class ChangeIdentityServer(val url: String?) : DiscoverySettingsAction()
|
data class ChangeIdentityServer(val url: String?) : DiscoverySettingsAction()
|
||||||
data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||||
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||||
data class FinalizeBind3pid(val threePid: ThreePid, val bind: Boolean) : DiscoverySettingsAction()
|
data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||||
data class SubmitMsisdnToken(val msisdn: String, val code: String, val bind: Boolean) : DiscoverySettingsAction()
|
data class SubmitMsisdnToken(val threePid: ThreePid.Msisdn, val code: String) : DiscoverySettingsAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class DiscoverySettingsViewEvents : VectorViewEvents {
|
sealed class DiscoverySettingsViewEvents : VectorViewEvents {
|
||||||
|
@ -169,136 +163,96 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shareThreePid(action: DiscoverySettingsAction.ShareThreePid) {
|
private fun shareThreePid(action: DiscoverySettingsAction.ShareThreePid) = withState { state ->
|
||||||
when (action.threePid) {
|
|
||||||
is ThreePid.Email -> shareEmail(action.threePid.email)
|
|
||||||
is ThreePid.Msisdn -> shareMsisdn(action.threePid.msisdn)
|
|
||||||
}.exhaustive
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shareEmail(email: String) = withState { state ->
|
|
||||||
if (state.identityServer() == null) return@withState
|
if (state.identityServer() == null) return@withState
|
||||||
changeMailState(email, Loading())
|
changeThreePidState(action.threePid, Loading())
|
||||||
|
|
||||||
identityService.startBindSession(ThreePid.Email(email), null,
|
val threePid = if (action.threePid is ThreePid.Msisdn && action.threePid.countryCode == null) {
|
||||||
object : MatrixCallback<ThreePid> {
|
// Ensure we have a country code
|
||||||
override fun onSuccess(data: ThreePid) {
|
|
||||||
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND)/* TODO , data*/)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
val phoneNumber = PhoneNumberUtil.getInstance()
|
||||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
.parse("+${action.threePid.msisdn}", null)
|
||||||
|
action.threePid.copy(countryCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
action.threePid
|
||||||
|
}
|
||||||
|
|
||||||
changeMailState(email, Fail(failure))
|
identityService.startBindThreePid(threePid, object : MatrixCallback<Unit> {
|
||||||
}
|
override fun onSuccess(data: Unit) {
|
||||||
})
|
changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||||
|
changeThreePidState(action.threePid, Fail(failure))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeMailState(address: String, state: Async<PidInfo.SharedState>) {
|
private fun changeThreePidState(threePid: ThreePid, state: Async<SharedState>) {
|
||||||
setState {
|
setState {
|
||||||
val currentMails = emailList() ?: emptyList()
|
val currentMails = emailList() ?: emptyList()
|
||||||
|
val phones = phoneNumbersList() ?: emptyList()
|
||||||
copy(emailList = Success(
|
copy(emailList = Success(
|
||||||
currentMails.map {
|
currentMails.map {
|
||||||
if (it.threePid.value == address) {
|
if (it.threePid == threePid) {
|
||||||
it.copy(isShared = state)
|
it.copy(isShared = state)
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
),
|
||||||
}
|
phoneNumbersList = Success(
|
||||||
}
|
phones.map {
|
||||||
|
if (it.threePid == threePid) {
|
||||||
private fun changeMsisdnState(address: String, state: Async<PidInfo.SharedState>) {
|
it.copy(isShared = state)
|
||||||
setState {
|
} else {
|
||||||
val phones = phoneNumbersList() ?: emptyList()
|
it
|
||||||
copy(phoneNumbersList = Success(
|
}
|
||||||
phones.map {
|
}
|
||||||
if (it.threePid.value == address) {
|
)
|
||||||
it.copy(isShared = state)
|
)
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun revokeThreePid(action: DiscoverySettingsAction.RevokeThreePid) {
|
private fun revokeThreePid(action: DiscoverySettingsAction.RevokeThreePid) {
|
||||||
when (action.threePid) {
|
when (action.threePid) {
|
||||||
is ThreePid.Email -> revokeEmail(action.threePid.email)
|
is ThreePid.Email -> revokeEmail(action.threePid)
|
||||||
is ThreePid.Msisdn -> revokeMsisdn(action.threePid.msisdn)
|
is ThreePid.Msisdn -> revokeMsisdn(action.threePid)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun revokeEmail(email: String) = withState { state ->
|
private fun revokeEmail(threePid: ThreePid.Email) = withState { state ->
|
||||||
if (state.identityServer() == null) return@withState
|
if (state.identityServer() == null) return@withState
|
||||||
if (state.emailList() == null) return@withState
|
if (state.emailList() == null) return@withState
|
||||||
changeMailState(email, Loading())
|
changeThreePidState(threePid, Loading())
|
||||||
|
|
||||||
identityService.startUnBindSession(ThreePid.Email(email), null, object : MatrixCallback<Pair<Boolean, ThreePid?>> {
|
identityService.unbindThreePid(threePid, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
|
override fun onSuccess(data: Unit) {
|
||||||
if (data.first) {
|
changeThreePidState(threePid, Success(SharedState.NOT_SHARED))
|
||||||
// requires mail validation
|
|
||||||
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND) /* TODO , data.second */)
|
|
||||||
} else {
|
|
||||||
changeMailState(email, Success(PidInfo.SharedState.NOT_SHARED))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||||
|
changeThreePidState(threePid, Fail(failure))
|
||||||
changeMailState(email, Fail(failure))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun revokeMsisdn(msisdn: String) = withState { state ->
|
private fun revokeMsisdn(threePid: ThreePid.Msisdn) = withState { state ->
|
||||||
if (state.identityServer() == null) return@withState
|
if (state.identityServer() == null) return@withState
|
||||||
if (state.emailList() == null) return@withState
|
if (state.phoneNumbersList() == null) return@withState
|
||||||
changeMsisdnState(msisdn, Loading())
|
changeThreePidState(threePid, Loading())
|
||||||
|
|
||||||
val phoneNumber = PhoneNumberUtil.getInstance()
|
identityService.unbindThreePid(threePid, object : MatrixCallback<Unit> {
|
||||||
.parse("+$msisdn", null)
|
override fun onSuccess(data: Unit) {
|
||||||
val countryCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
|
changeThreePidState(threePid, Success(SharedState.NOT_SHARED))
|
||||||
|
|
||||||
identityService.startUnBindSession(ThreePid.Msisdn(msisdn, countryCode), null, object : MatrixCallback<Pair<Boolean, ThreePid?>> {
|
|
||||||
override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
|
|
||||||
if (data.first /*requires mail validation */) {
|
|
||||||
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND) /* TODO , data.second */)
|
|
||||||
} else {
|
|
||||||
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_SHARED))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||||
|
changeThreePidState(threePid, Fail(failure))
|
||||||
changeMsisdnState(msisdn, Fail(failure))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shareMsisdn(msisdn: String) = withState { state ->
|
|
||||||
if (state.identityServer() == null) return@withState
|
|
||||||
changeMsisdnState(msisdn, Loading())
|
|
||||||
|
|
||||||
val phoneNumber = PhoneNumberUtil.getInstance()
|
|
||||||
.parse("+$msisdn", null)
|
|
||||||
val countryCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
|
|
||||||
|
|
||||||
|
|
||||||
identityService.startBindSession(ThreePid.Msisdn(msisdn, countryCode), null, object : MatrixCallback<ThreePid> {
|
|
||||||
override fun onSuccess(data: ThreePid) {
|
|
||||||
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND) /* TODO , data */)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
|
||||||
|
|
||||||
changeMsisdnState(msisdn, Fail(failure))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -364,59 +318,52 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
val hasMatrixId = foundThreePids.any { it.threePid == threePid }
|
val hasMatrixId = foundThreePids.any { it.threePid == threePid }
|
||||||
PidInfo(
|
PidInfo(
|
||||||
threePid = threePid,
|
threePid = threePid,
|
||||||
isShared = Success(PidInfo.SharedState.SHARED.takeIf { hasMatrixId } ?: PidInfo.SharedState.NOT_SHARED)
|
isShared = Success(SharedState.SHARED.takeIf { hasMatrixId } ?: SharedState.NOT_SHARED)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submitMsisdnToken(action: DiscoverySettingsAction.SubmitMsisdnToken) = withState { state ->
|
private fun submitMsisdnToken(action: DiscoverySettingsAction.SubmitMsisdnToken) = withState { state ->
|
||||||
val pid = state.phoneNumbersList()?.find { it.threePid.value == action.msisdn }?.threePid ?: return@withState
|
if (state.identityServer().isNullOrBlank()) return@withState
|
||||||
|
|
||||||
identityService.submitValidationToken(pid,
|
identityService.submitValidationToken(action.threePid,
|
||||||
action.code,
|
action.code,
|
||||||
object : MatrixCallback<Unit> {
|
object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Msisdn(action.msisdn), action.bind))
|
// TODO This should be done in the task
|
||||||
|
finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(action.threePid))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||||
changeMsisdnState(action.msisdn, Fail(failure))
|
changeThreePidState(action.threePid, Fail(failure))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finalizeBind3pid(action: DiscoverySettingsAction.FinalizeBind3pid) = withState { state ->
|
private fun finalizeBind3pid(action: DiscoverySettingsAction.FinalizeBind3pid) = withState { state ->
|
||||||
val _3pid = when (action.threePid) {
|
val threePid = when (action.threePid) {
|
||||||
is ThreePid.Email -> {
|
is ThreePid.Email -> {
|
||||||
changeMailState(action.threePid.email, Loading())
|
changeThreePidState(action.threePid, Loading())
|
||||||
state.emailList()?.find { it.threePid.value == action.threePid.email }?.threePid ?: return@withState
|
state.emailList()?.find { it.threePid.value == action.threePid.email }?.threePid ?: return@withState
|
||||||
}
|
}
|
||||||
is ThreePid.Msisdn -> {
|
is ThreePid.Msisdn -> {
|
||||||
changeMsisdnState(action.threePid.msisdn, Loading())
|
changeThreePidState(action.threePid, Loading())
|
||||||
state.phoneNumbersList()?.find { it.threePid.value == action.threePid.msisdn }?.threePid ?: return@withState
|
state.phoneNumbersList()?.find { it.threePid.value == action.threePid.msisdn }?.threePid ?: return@withState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identityService.finalizeBindSessionFor3PID(_3pid, object : MatrixCallback<Unit> {
|
identityService.finalizeBindThreePid(threePid, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
val sharedState = Success(if (action.bind) PidInfo.SharedState.SHARED else PidInfo.SharedState.NOT_SHARED)
|
changeThreePidState(action.threePid, Success(SharedState.SHARED))
|
||||||
when (action.threePid) {
|
|
||||||
is ThreePid.Email -> changeMailState(action.threePid.email, sharedState)
|
|
||||||
is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||||
|
|
||||||
// Restore previous state after an error
|
// Restore previous state after an error
|
||||||
val sharedState = Success(if (action.bind) PidInfo.SharedState.NOT_VERIFIED_FOR_BIND else PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND)
|
changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS))
|
||||||
when (action.threePid) {
|
|
||||||
is ThreePid.Email -> changeMailState(action.threePid.email, sharedState)
|
|
||||||
is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -425,9 +372,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
private fun refreshPendingEmailBindings() = withState { state ->
|
private fun refreshPendingEmailBindings() = withState { state ->
|
||||||
state.emailList()?.forEach { info ->
|
state.emailList()?.forEach { info ->
|
||||||
when (info.isShared()) {
|
when (info.isShared()) {
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid, true))
|
SharedState.BINDING_IN_PROGRESS -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid))
|
||||||
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid, false))
|
else -> Unit
|
||||||
else -> Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue