Change password function implemented.

Fixes #528
This commit is contained in:
onurays 2020-03-11 00:16:37 +03:00 committed by Benoit Marty
parent 391d3cb6b5
commit f00db49bda
11 changed files with 230 additions and 5 deletions

View file

@ -2,6 +2,7 @@ Changes in RiotX 0.19.0 (2020-XX-XX)
===================================================
Features ✨:
- Change password (#528)
- Cross-Signing | Support SSSS secret sharing (#944)
- Cross-Signing | Verify new session from existing session (#1134)
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)

View file

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.password.PasswordWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
@ -89,4 +90,9 @@ interface AuthenticationService {
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable
/**
* Return a PasswordWizard, to update password.
*/
fun getPasswordWizard(): PasswordWizard
}

View file

@ -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.auth.password
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface PasswordWizard {
/**
* Ask the homeserver to update the password with the provided new password.
*/
fun updatePassword(sessionId: String, userId: String, oldPassword: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
}

View file

@ -31,3 +31,8 @@ fun Throwable.shouldBeRetried(): Boolean {
return this is Failure.NetworkConnection
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
}
fun Throwable.isInvalidPassword(): Boolean {
return this is Failure.ServerError
&& error.message == "Invalid password"
}

View file

@ -22,6 +22,7 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
import im.vector.matrix.android.internal.auth.data.UpdatePasswordParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegistrationParams
@ -102,4 +103,10 @@ internal interface AuthAPI {
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed): Call<Unit>
/**
* Ask the homeserver to update the password with the provided new password.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
fun updatePassword(@Body params: UpdatePasswordParams): Call<Unit>
}

View file

@ -28,6 +28,7 @@ import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.password.PasswordWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session
@ -37,6 +38,7 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
import im.vector.matrix.android.internal.auth.password.DefaultPasswordWizard
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.RetrofitFactory
@ -66,6 +68,7 @@ internal class DefaultAuthenticationService @Inject constructor(
private var currentLoginWizard: LoginWizard? = null
private var currentRegistrationWizard: RegistrationWizard? = null
private var currentPasswordWizard: PasswordWizard? = null
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
@ -221,6 +224,22 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun getPasswordWizard(): PasswordWizard {
return currentPasswordWizard
?: let {
sessionParamsStore.getLast()?.homeServerConnectionConfig?.let {
DefaultPasswordWizard(
okHttpClient,
retrofitFactory,
coroutineDispatchers,
it
).also {
currentPasswordWizard = it
}
} ?: error("HomeServerConnectionConfig is null")
}
}
override fun cancelPendingLoginOrRegistration() {
currentLoginWizard = null
currentRegistrationWizard = null

View file

@ -0,0 +1,42 @@
/*
* 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.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
/**
* Class to pass request parameters to update the password.
*/
@JsonClass(generateAdapter = true)
internal data class UpdatePasswordParams(
@Json(name = "auth")
val auth: UserPasswordAuth? = null,
@Json(name = "new_password")
val newPassword: String? = null
) {
companion object {
fun create(sessionId: String, userId: String, oldPassword: String, newPassword: String): UpdatePasswordParams {
return UpdatePasswordParams(
auth = UserPasswordAuth(session = sessionId, user = userId, password = oldPassword),
newPassword = newPassword
)
}
}
}

View file

@ -0,0 +1,87 @@
/*
* 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.auth.password
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.password.PasswordWizard
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.auth.data.UpdatePasswordParams
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import okhttp3.OkHttpClient
import timber.log.Timber
internal class DefaultPasswordWizard(
okHttpClient: Lazy<OkHttpClient>,
retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val homeServerConnectionConfig: HomeServerConnectionConfig
) : PasswordWizard {
private val authAPI = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
.create(AuthAPI::class.java)
override fun updatePassword(sessionId: String, userId: String, oldPassword: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
updatePasswordInternal(sessionId, userId, oldPassword, newPassword)
}
}
private suspend fun updatePasswordInternal(sessionId: String, userId: String, oldPassword: String, newPassword: String) {
val params = UpdatePasswordParams.create(sessionId, userId, oldPassword, newPassword)
try {
executeRequest<Unit>(null) {
apiCall = authAPI.updatePassword(params)
}
} catch (throwable: Throwable) {
if (throwable is Failure.OtherServerError
&& throwable.httpCode == 401
/* Avoid infinite loop */
&& params.auth?.session == null) {
try {
MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java)
.fromJson(throwable.errorBody)
} catch (e: Exception) {
null
}?.let {
// Retry with authentication
try {
executeRequest<Unit>(null) {
apiCall = authAPI.updatePassword(
params.copy(auth = params.auth?.copy(session = it.session))
)
}
return
} catch (failure: Throwable) {
throw failure
}
}
}
throw throwable
}
}
}

View file

@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
/**
* This class provides the authentication data to delete a device
* This class provides the authentication data by using user and password
*/
@JsonClass(generateAdapter = true)
data class UserPasswordAuth(

View file

@ -74,6 +74,7 @@ import im.vector.riotx.features.roomprofile.RoomProfileFragment
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsGeneralFragment
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
import im.vector.riotx.features.settings.VectorSettingsLabsFragment
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
@ -280,6 +281,11 @@ interface FragmentModule {
@FragmentKey(VectorSettingsDevicesFragment::class)
fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VectorSettingsGeneralFragment::class)
fun bindVectorSettingsGeneralFragment(fragment: VectorSettingsGeneralFragment): Fragment
@Binds
@IntoMap
@FragmentKey(PublicRoomsFragment::class)

View file

@ -35,6 +35,9 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.cache.DiskCache
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.failure.isInvalidPassword
import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.showPassword
@ -56,8 +59,11 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import javax.inject.Inject
class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
class VectorSettingsGeneralFragment @Inject constructor(
private val authenticationService: AuthenticationService
) : VectorSettingsBaseFragment() {
override var titleRes = R.string.settings_general_title
override val preferenceXmlRes = R.xml.vector_settings_general
@ -109,8 +115,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
// Password
mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
notImplemented()
// onPasswordUpdateClick()
onPasswordUpdateClick()
false
}
@ -771,7 +776,26 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
val oldPwd = oldPasswordText.text.toString().trim()
val newPwd = newPasswordText.text.toString().trim()
notImplemented()
authenticationService.getLastAuthenticatedSession()?.let {
showPasswordLoadingView(true)
authenticationService.getPasswordWizard().updatePassword(it.sessionId, it.myUserId, oldPwd, newPwd, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
showPasswordLoadingView(false)
dialog.dismiss()
activity.toast(R.string.settings_password_updated)
}
override fun onFailure(failure: Throwable) {
showPasswordLoadingView(false)
if (failure.isInvalidPassword()) {
activity.toast(R.string.settings_fail_to_update_password_invalid_current_password)
} else {
activity.toast(R.string.settings_fail_to_update_password)
}
}
})
}
/* TODO
showPasswordLoadingView(true)