diff --git a/CHANGES.md b/CHANGES.md index 1ffb6bcad0..b96337097b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in Element 1.0.6 (2020-XX-XX) =================================================== Features ✨: - - + - List phone numbers and emails added to the Matrix account, and add Email to account (#44) Improvements 🙌: - You can now join room through permalink and within room directory search diff --git a/docs/add_email.md b/docs/add_email.md index 64227418a3..06b01b3026 100644 --- a/docs/add_email.md +++ b/docs/add_email.md @@ -12,7 +12,7 @@ } ``` -### The email is already adding to an account +### The email is already added to an account 400 @@ -84,6 +84,8 @@ User clicks on CONTINUE POST https://homeserver.org/_matrix/client/r0/account/3pid/add +TODO: Remove "identifier"? + ```json { "sid": "bxyDHuJKsdkjMlTJ", diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index 45efd125ee..55ede52c0c 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -18,9 +18,13 @@ package org.matrix.android.sdk.rx import androidx.paging.PagedList +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.functions.Function3 import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo @@ -43,10 +47,6 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import io.reactivex.Observable -import io.reactivex.Single -import io.reactivex.functions.Function3 class RxSession(private val session: Session) { @@ -110,6 +110,11 @@ class RxSession(private val session: Session) { .startWithCallable { session.getThreePids() } } + fun livePendingThreePIds(): Observable> { + return session.getPendingThreePidsLive().asObservable() + .startWithCallable { session.getPendingThreePids() } + } + fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder { session.createRoom(roomParams, it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index 449c670983..95f142e877 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt @@ -83,4 +83,32 @@ interface ProfileService { * @param refreshData set to true to fetch data from the homeserver */ fun getThreePidsLive(refreshData: Boolean): LiveData> + + /** + * Get the pending 3Pids, i.e. ThreePids that have requested a token, but not yet validated by the user. + */ + fun getPendingThreePids(): List + + /** + * Get the pending 3Pids Live + */ + fun getPendingThreePidsLive(): LiveData> + + /** + * Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list. + */ + fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable + + /** + * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid + */ + fun finalizeAddingThreePid(threePid: ThreePid, + uiaSession: String?, + accountPassword: String?, + matrixCallback: MatrixCallback): Cancelable + + /** + * Delete a 3Pids. + */ + fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 7d2a4ea581..00c4fdac84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -20,6 +20,7 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm import io.realm.RealmMigration import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -32,6 +33,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 2) migrateTo3(realm) + if (oldVersion <= 3) migrateTo4(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -63,4 +65,17 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0) } } + + private fun migrateTo4(realm: DynamicRealm) { + Timber.d("Step 3 -> 4") + realm.schema.create("PendingThreePidEntity") + .addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java) + .setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true) + .addField(PendingThreePidEntityFields.EMAIL, String::class.java) + .addField(PendingThreePidEntityFields.MSISDN, String::class.java) + .addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java) + .setRequired(PendingThreePidEntityFields.SEND_ATTEMPT, true) + .addField(PendingThreePidEntityFields.SID, String::class.java) + .setRequired(PendingThreePidEntityFields.SID, true) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt new file mode 100644 index 0000000000..bf2f11dedd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt @@ -0,0 +1,31 @@ +/* + * 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.database.model + +import io.realm.RealmObject + +/** + * This class is used to store pending threePid data, when user wants to add a threePid to his account + */ +internal open class PendingThreePidEntity( + var email: String? = null, + var msisdn: String? = null, + var clientSecret: String = "", + var sendAttempt: Int = 0, + var sid: String = "" +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index ea466db352..2c45cfcdbf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule RoomSummaryEntity::class, RoomTagEntity::class, SyncEntity::class, + PendingThreePidEntity::class, UserEntity::class, IgnoredUserEntity::class, BreadcrumbsEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddEmailBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddEmailBody.kt new file mode 100644 index 0000000000..ff81ad6a5c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddEmailBody.kt @@ -0,0 +1,47 @@ +/* + * 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 AddEmailBody( + /** + * 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 email address to validate. + */ + @Json(name = "email") + val email: String, + + /** + * Required. The server will only send an email if the send_attempt is a number greater than the most + * recent one which it has seen, scoped to that email + client_secret pair. This is to avoid repeatedly + * sending the same email 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 email (e.g. a reminder) to be sent. + * If they do not, the server should respond with success but not resend the email. + */ + @Json(name = "send_attempt") + val sendAttempt: Int +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidResponse.kt new file mode 100644 index 0000000000..109b3d5343 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidResponse.kt @@ -0,0 +1,30 @@ +/* + * 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 AddThreePidResponse( + /** + * 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 +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt new file mode 100644 index 0000000000..75c829c785 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt @@ -0,0 +1,70 @@ +/* + * 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.zhuinden.monarchy.Monarchy +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import java.util.UUID +import javax.inject.Inject + +internal abstract class AddThreePidTask : Task { + data class Params( + val threePid: ThreePid + ) +} + +internal class DefaultAddThreePidTask @Inject constructor( + private val profileAPI: ProfileAPI, + @SessionDatabase private val monarchy: Monarchy, + private val pendingThreePidMapper: PendingThreePidMapper, + private val eventBus: EventBus) : AddThreePidTask() { + + override suspend fun execute(params: Params) { + val clientSecret = UUID.randomUUID().toString() + val sendAttempt = 1 + val result = when (params.threePid) { + is ThreePid.Email -> + executeRequest(eventBus) { + val body = AddEmailBody( + email = params.threePid.email, + sendAttempt = sendAttempt, + clientSecret = clientSecret + ) + apiCall = profileAPI.addEmail(body) + } + is ThreePid.Msisdn -> TODO() + } + + // Store as a pending three pid + monarchy.awaitTransaction { realm -> + PendingThreePid( + threePid = params.threePid, + clientSecret = clientSecret, + sendAttempt = sendAttempt, + sid = result.sid + ) + .let { pendingThreePidMapper.map(it) } + .let { realm.copyToRealm(it) } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 633b047994..cd81496814 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.UserThreePidEntity import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.content.FileUploader @@ -44,6 +45,10 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto private val getProfileInfoTask: GetProfileInfoTask, private val setDisplayNameTask: SetDisplayNameTask, private val setAvatarUrlTask: SetAvatarUrlTask, + private val addThreePidTask: AddThreePidTask, + private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask, + private val deleteThreePidTask: DeleteThreePidTask, + private val pendingThreePidMapper: PendingThreePidMapper, private val fileUploader: FileUploader) : ProfileService { override fun getDisplayName(userId: String, matrixCallback: MatrixCallback>): Cancelable { @@ -116,9 +121,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto override fun getThreePidsLive(refreshData: Boolean): LiveData> { if (refreshData) { // Force a refresh of the values - refreshUserThreePidsTask - .configureWith() - .executeBy(taskExecutor) + refreshThreePids() } return monarchy.findAllMappedWithChanges( @@ -126,6 +129,69 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto { it.asDomain() } ) } + + private fun refreshThreePids() { + refreshUserThreePidsTask + .configureWith() + .executeBy(taskExecutor) + } + + override fun getPendingThreePids(): List { + return monarchy.fetchAllMappedSync( + { it.where() }, + { pendingThreePidMapper.map(it).threePid } + ) + } + + override fun getPendingThreePidsLive(): LiveData> { + return monarchy.findAllMappedWithChanges( + { it.where() }, + { pendingThreePidMapper.map(it).threePid } + ) + } + + override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable { + return addThreePidTask + .configureWith(AddThreePidTask.Params(threePid)) { + callback = matrixCallback + } + .executeBy(taskExecutor) + } + + override fun finalizeAddingThreePid(threePid: ThreePid, + uiaSession: String?, + accountPassword: String?, + matrixCallback: MatrixCallback): Cancelable { + return finalizeAddingThreePidTask + .configureWith(FinalizeAddingThreePidTask.Params(threePid, uiaSession, accountPassword)) { + callback = alsoRefresh(matrixCallback) + } + .executeBy(taskExecutor) + } + + /** + * Wrap the callback to fetch 3Pids from the server in case of success + */ + private fun alsoRefresh(callback: MatrixCallback): MatrixCallback { + return object : MatrixCallback { + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + + override fun onSuccess(data: Unit) { + refreshThreePids() + callback.onSuccess(data) + } + } + } + + override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable { + return deleteThreePidTask + .configureWith(DeleteThreePidTask.Params(threePid)) { + callback = alsoRefresh(matrixCallback) + } + .executeBy(taskExecutor) + } } private fun UserThreePidEntity.asDomain(): ThreePid { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidBody.kt new file mode 100644 index 0000000000..e7d4568f8b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidBody.kt @@ -0,0 +1,32 @@ +/* + * 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 DeleteThreePidBody( + /** + * 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 +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidResponse.kt new file mode 100644 index 0000000000..3817277a9d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidResponse.kt @@ -0,0 +1,32 @@ +/* + * 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 DeleteThreePidResponse( + /** + * Required. An indicator as to whether or not the homeserver was able to unbind the 3PID from + * the identity server. success indicates that the identity server has unbound the identifier + * whereas no-support indicates that the identity server refuses to support the request or the + * homeserver was not able to determine an identity server to unbind from. One of: ["no-support", "success"] + */ + @Json(name = "id_server_unbind_result") + val idServerUnbindResult: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt new file mode 100644 index 0000000000..69ff7d82da --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt @@ -0,0 +1,48 @@ +/* + * 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 org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.identity.toMedium +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class DeleteThreePidTask : Task { + data class Params( + val threePid: ThreePid + ) +} + +internal class DefaultDeleteThreePidTask @Inject constructor( + private val profileAPI: ProfileAPI, + private val eventBus: EventBus) : DeleteThreePidTask() { + + override suspend fun execute(params: Params) { + executeRequest(eventBus) { + val body = DeleteThreePidBody( + medium = params.threePid.toMedium(), + address = params.threePid.value + ) + apiCall = profileAPI.deleteThreePid(body) + } + + // We do not really care about the result for the moment + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt new file mode 100644 index 0000000000..73e9b39cea --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt @@ -0,0 +1,42 @@ +/* + * 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 +import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth + +@JsonClass(generateAdapter = true) +internal data class FinalizeAddThreePidBody( + /** + * Required. The client secret used in the session with the homeserver. + */ + @Json(name = "client_secret") + val clientSecret: String, + + /** + * Required. The session identifier given by the homeserver. + */ + @Json(name = "sid") + val sid: String, + + /** + * Additional authentication information for the user-interactive authentication API. + */ + @Json(name = "auth") + val auth: UserPasswordAuth? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt new file mode 100644 index 0000000000..e6f4141ab1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt @@ -0,0 +1,90 @@ +/* + * 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.zhuinden.monarchy.Monarchy +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth +import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity +import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal abstract class FinalizeAddingThreePidTask : Task { + data class Params( + val threePid: ThreePid, + val session: String?, + val accountPassword: String? + ) +} + +internal class DefaultFinalizeAddingThreePidTask @Inject constructor( + private val profileAPI: ProfileAPI, + @SessionDatabase private val monarchy: Monarchy, + private val pendingThreePidMapper: PendingThreePidMapper, + @UserId private val userId: String, + private val eventBus: EventBus) : FinalizeAddingThreePidTask() { + + override suspend fun execute(params: Params) { + // Get the required pending data + val pendingThreePids = monarchy.fetchAllMappedSync( + { it.where(PendingThreePidEntity::class.java) }, + { pendingThreePidMapper.map(it) } + ) + .firstOrNull { it.threePid == params.threePid } + ?: throw IllegalArgumentException("unknown threepid") + + try { + executeRequest(eventBus) { + val body = FinalizeAddThreePidBody( + clientSecret = pendingThreePids.clientSecret, + sid = pendingThreePids.sid, + auth = if (params.session != null && params.accountPassword != null) { + UserPasswordAuth( + session = params.session, + user = userId, + password = params.accountPassword + ) + } else null + ) + apiCall = profileAPI.finalizeAddThreePid(body) + } + } catch (throwable: Throwable) { + throw throwable.toRegistrationFlowResponse() + ?.let { Failure.RegistrationFlowError(it) } + ?: throwable + } + + // Delete the pending three pid + monarchy.awaitTransaction { realm -> + realm.where(PendingThreePidEntity::class.java) + .equalTo(PendingThreePidEntityFields.EMAIL, params.threePid.value) + .or() + .equalTo(PendingThreePidEntityFields.MSISDN, params.threePid.value) + .findAll() + .deleteAllFromRealm() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePid.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePid.kt new file mode 100644 index 0000000000..d9c3a5d656 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePid.kt @@ -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 org.matrix.android.sdk.internal.session.profile + +import org.matrix.android.sdk.api.session.identity.ThreePid + +internal data class PendingThreePid( + val threePid: ThreePid, + val clientSecret: String, + val sendAttempt: Int, + val sid: String +) + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePidMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePidMapper.kt new file mode 100644 index 0000000000..c9d6381bd3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePidMapper.kt @@ -0,0 +1,45 @@ +/* + * 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 org.matrix.android.sdk.internal.session.profile + +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity +import javax.inject.Inject + +internal class PendingThreePidMapper @Inject constructor() { + + fun map(entity: PendingThreePidEntity): PendingThreePid { + return PendingThreePid( + threePid = entity.email?.let { ThreePid.Email(it) } + ?: entity.msisdn?.let { ThreePid.Msisdn(it) } + ?: error("Invalid data"), + clientSecret = entity.clientSecret, + sendAttempt = entity.sendAttempt, + sid = entity.sid + ) + } + + fun map(domain: PendingThreePid): PendingThreePidEntity { + return PendingThreePidEntity( + email = domain.threePid.takeIf { it is ThreePid.Email }?.value, + msisdn = domain.threePid.takeIf { it is ThreePid.Msisdn }?.value, + clientSecret = domain.clientSecret, + sendAttempt = domain.sendAttempt, + sid = domain.sid + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index 31e1f09bbd..125b1a47e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -28,7 +28,6 @@ import retrofit2.http.PUT import retrofit2.http.Path internal interface ProfileAPI { - /** * Get the combined profile information for this user. * This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers. @@ -71,4 +70,22 @@ internal interface ProfileAPI { */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind") fun unbindThreePid(@Body body: UnbindThreePidBody): Call + + /** + * 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 + + /** + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add") + fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call + + /** + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete") + fun deleteThreePid(@Body body: DeleteThreePidBody): Call } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt index 57a86d03e0..baeaf9fd58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt @@ -58,4 +58,13 @@ internal abstract class ProfileModule { @Binds abstract fun bindSetAvatarUrlTask(task: DefaultSetAvatarUrlTask): SetAvatarUrlTask + + @Binds + abstract fun bindAddThreePidTask(task: DefaultAddThreePidTask): AddThreePidTask + + @Binds + abstract fun bindFinalizeAddingThreePidTask(task: DefaultFinalizeAddingThreePidTask): FinalizeAddingThreePidTask + + @Binds + abstract fun bindDeleteThreePidTask(task: DefaultDeleteThreePidTask): DeleteThreePidTask } diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 591d1c0474..d0e4c938cd 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -103,6 +103,7 @@ import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragmen import im.vector.app.features.settings.locale.LocalePickerFragment import im.vector.app.features.settings.push.PushGatewaysFragment import im.vector.app.features.settings.push.PushRulesFragment +import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment import im.vector.app.features.share.IncomingShareFragment import im.vector.app.features.signout.soft.SoftLogoutFragment import im.vector.app.features.terms.ReviewTermsFragment @@ -313,6 +314,11 @@ interface FragmentModule { @FragmentKey(VectorSettingsDevicesFragment::class) fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment + @Binds + @IntoMap + @FragmentKey(ThreePidsSettingsFragment::class) + fun bindThreePidsSettingsFragment(fragment: ThreePidsSettingsFragment): Fragment + @Binds @IntoMap @FragmentKey(PublicRoomsFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 051847321a..14939eaff9 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -59,35 +59,39 @@ class DefaultErrorFormatter @Inject constructor( } is Failure.ServerError -> { when { - throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> { + throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> { // Special case for terms and conditions stringProvider.getString(R.string.error_terms_not_accepted) } - throwable.isInvalidPassword() -> { + throwable.isInvalidPassword() -> { stringProvider.getString(R.string.auth_invalid_login_param) } - throwable.error.code == MatrixError.M_USER_IN_USE -> { + throwable.error.code == MatrixError.M_USER_IN_USE -> { stringProvider.getString(R.string.login_signup_error_user_in_use) } - throwable.error.code == MatrixError.M_BAD_JSON -> { + throwable.error.code == MatrixError.M_BAD_JSON -> { stringProvider.getString(R.string.login_error_bad_json) } - throwable.error.code == MatrixError.M_NOT_JSON -> { + throwable.error.code == MatrixError.M_NOT_JSON -> { stringProvider.getString(R.string.login_error_not_json) } - throwable.error.code == MatrixError.M_THREEPID_DENIED -> { + throwable.error.code == MatrixError.M_THREEPID_DENIED -> { stringProvider.getString(R.string.login_error_threepid_denied) } - throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> { + throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> { limitExceededError(throwable.error) } - throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> { + throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> { stringProvider.getString(R.string.login_reset_password_error_not_found) } - throwable.error.code == MatrixError.M_USER_DEACTIVATED -> { + throwable.error.code == MatrixError.M_USER_DEACTIVATED -> { stringProvider.getString(R.string.auth_invalid_login_deactivated_account) } - else -> { + throwable.error.code == MatrixError.M_THREEPID_IN_USE + && throwable.error.message == "Email is already in use" -> { + stringProvider.getString(R.string.account_email_already_used_error) + } + else -> { throwable.error.message.takeIf { it.isNotEmpty() } ?: throwable.error.code.takeIf { it.isNotEmpty() } } diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt index 832250fab4..492df9eb00 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt @@ -70,6 +70,9 @@ abstract class GenericItem : VectorEpoxyModel() { @EpoxyAttribute var buttonAction: Action? = null + @EpoxyAttribute + var destructiveButtonAction: Action? = null + @EpoxyAttribute var itemClickAction: Action? = null @@ -109,6 +112,11 @@ abstract class GenericItem : VectorEpoxyModel() { buttonAction?.perform?.run() } + holder.destructiveButton.setTextOrHide(destructiveButtonAction?.title) + holder.destructiveButton.setOnClickListener { + destructiveButtonAction?.perform?.run() + } + holder.root.setOnClickListener { itemClickAction?.perform?.run() } @@ -122,5 +130,6 @@ abstract class GenericItem : VectorEpoxyModel() { val accessoryImage by bind(R.id.item_generic_accessory_image) val progressBar by bind(R.id.item_generic_progress_bar) val actionButton by bind