Implement: Adding MSISDN (WIP)

This commit is contained in:
Benoit Marty 2020-08-31 16:16:18 +02:00
parent 5a21249022
commit e309b30203
10 changed files with 193 additions and 19 deletions

View file

@ -77,5 +77,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
.setRequired(PendingThreePidEntityFields.SEND_ATTEMPT, true)
.addField(PendingThreePidEntityFields.SID, String::class.java)
.setRequired(PendingThreePidEntityFields.SID, true)
.addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
}
}

View file

@ -27,5 +27,6 @@ internal open class PendingThreePidEntity(
var msisdn: String? = null,
var clientSecret: String = "",
var sendAttempt: Int = 0,
var sid: String = ""
var sid: String = "",
var submitUrl: String? = null
) : RealmObject()

View file

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class AddThreePidResponse(
internal data class AddEmailResponse(
/**
* Required. The session ID. Session IDs are opaque strings that must consist entirely
* of the characters [0-9a-zA-Z.=_-]. Their length must not exceed 255 characters and they must not be empty.

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.profile
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class AddMsisdnBody(
/**
* Required. A unique string generated by the client, and used to identify the validation attempt.
* It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must not exceed
* 255 characters and it must not be empty.
*/
@Json(name = "client_secret")
val clientSecret: String,
/**
* Required. The two-letter uppercase ISO-3166-1 alpha-2 country code that the number in
* phone_number should be parsed as if it were dialled from.
*/
@Json(name = "country")
val country: String,
/**
* Required. The phone number to validate.
*/
@Json(name = "phone_number")
val phoneNumber: String,
/**
* Required. The server will only send an SMS if the send_attempt is a number greater than the most
* recent one which it has seen, scoped to that country + phone_number + client_secret triple. This
* is to avoid repeatedly sending the same SMS in the case of request retries between the POSTing user
* and the identity server. The client should increment this value if they desire a new SMS (e.g. a
* reminder) to be sent.
*/
@Json(name = "send_attempt")
val sendAttempt: Int
)

View file

@ -0,0 +1,55 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.profile
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class AddMsisdnResponse(
/**
* Required. The session ID. Session IDs are opaque strings that must consist entirely of the characters [0-9a-zA-Z.=_-].
* Their length must not exceed 255 characters and they must not be empty.
*/
@Json(name = "sid")
val sid: String,
/**
* An optional field containing a URL where the client must submit the validation token to, with identical parameters to the Identity
* Service API's POST /validate/email/submitToken endpoint (without the requirement for an access token).
* The homeserver must send this token to the user (if applicable), who should then be prompted to provide it to the client.
*
* If this field is not present, the client can assume that verification will happen without the client's involvement provided
* the homeserver advertises this specification version in the /versions response (ie: r0.5.0).
*/
@Json(name = "submit_url")
val submitUrl: String? = null,
/* ==========================================================================================
* It seems that the homeserver is sending more data, we may need it
* ========================================================================================== */
@Json(name = "msisdn")
val msisdn: String? = null,
@Json(name = "intl_fmt")
val formattedMsisdn: String? = null,
@Json(name = "success")
val success: Boolean? = null
)

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.profile
import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.zhuinden.monarchy.Monarchy
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.identity.ThreePid
@ -40,31 +41,76 @@ internal class DefaultAddThreePidTask @Inject constructor(
private val eventBus: EventBus) : AddThreePidTask() {
override suspend fun execute(params: Params) {
when (params.threePid) {
is ThreePid.Email -> addEmail(params.threePid)
is ThreePid.Msisdn -> addMsisdn(params.threePid)
}
}
private suspend fun addEmail(threePid: ThreePid.Email) {
val clientSecret = UUID.randomUUID().toString()
val sendAttempt = 1
val result = when (params.threePid) {
is ThreePid.Email ->
executeRequest<AddThreePidResponse>(eventBus) {
val body = AddEmailBody(
email = params.threePid.email,
sendAttempt = sendAttempt,
clientSecret = clientSecret
)
apiCall = profileAPI.addEmail(body)
}
is ThreePid.Msisdn -> TODO()
val result = executeRequest<AddEmailResponse>(eventBus) {
val body = AddEmailBody(
clientSecret = clientSecret,
email = threePid.email,
sendAttempt = sendAttempt
)
apiCall = profileAPI.addEmail(body)
}
// Store as a pending three pid
monarchy.awaitTransaction { realm ->
PendingThreePid(
threePid = params.threePid,
threePid = threePid,
clientSecret = clientSecret,
sendAttempt = sendAttempt,
sid = result.sid
sid = result.sid,
submitUrl = null
)
.let { pendingThreePidMapper.map(it) }
.let { realm.copyToRealm(it) }
}
}
private suspend fun addMsisdn(threePid: ThreePid.Msisdn) {
val clientSecret = UUID.randomUUID().toString()
val sendAttempt = 1
// Get country code from the phone number
val phoneNumber = threePid.msisdn
val parsedNumber = PhoneNumberUtil.getInstance().parse(phoneNumber, null)
val countryCode = parsedNumber.countryCode
val result = executeRequest<AddMsisdnResponse>(eventBus) {
val body = AddMsisdnBody(
clientSecret = clientSecret,
country = countryCode.asString(), // TODO Convert to String,
phoneNumber = parsedNumber.nationalNumber.toString(),
sendAttempt = sendAttempt
)
apiCall = profileAPI.addMsisdn(body)
}
// Store as a pending three pid
monarchy.awaitTransaction { realm ->
PendingThreePid(
threePid = threePid,
clientSecret = clientSecret,
sendAttempt = sendAttempt,
sid = result.sid,
submitUrl = result.submitUrl
)
.let { pendingThreePidMapper.map(it) }
.let { realm.copyToRealm(it) }
}
}
private fun Int.asString(): String {
// TODO
return ""
}
}

View file

@ -22,6 +22,9 @@ internal data class PendingThreePid(
val threePid: ThreePid,
val clientSecret: String,
val sendAttempt: Int,
val sid: String
// For Msisdn and Email
val sid: String,
// For Msisdn only
val submitUrl: String?
)

View file

@ -29,7 +29,8 @@ internal class PendingThreePidMapper @Inject constructor() {
?: error("Invalid data"),
clientSecret = entity.clientSecret,
sendAttempt = entity.sendAttempt,
sid = entity.sid
sid = entity.sid,
submitUrl = entity.submitUrl
)
}
@ -39,7 +40,8 @@ internal class PendingThreePidMapper @Inject constructor() {
msisdn = domain.threePid.takeIf { it is ThreePid.Msisdn }?.value,
clientSecret = domain.clientSecret,
sendAttempt = domain.sendAttempt,
sid = domain.sid
sid = domain.sid,
submitUrl = domain.submitUrl
)
}
}

View file

@ -75,7 +75,13 @@ internal interface ProfileAPI {
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken")
fun addEmail(@Body body: AddEmailBody): Call<AddThreePidResponse>
fun addEmail(@Body body: AddEmailBody): Call<AddEmailResponse>
/**
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-msisdn-requesttoken
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken")
fun addMsisdn(@Body body: AddMsisdnBody): Call<AddMsisdnResponse>
/**
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add

View file

@ -30,6 +30,7 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.isMsisdn
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
@ -123,6 +124,11 @@ class ThreePidsSettingsFragment @Inject constructor(
return
}
if (!msisdn.isMsisdn()) {
viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.AddingPhoneNumber(getString(R.string.login_msisdn_error_other))))
return
}
viewModel.handle(ThreePidsSettingsAction.AddThreePid(ThreePid.Msisdn(safeMsisdn)))
}