mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 18:05:36 +03:00
Merge pull request #179 from vector-im/feature/cryptoFinalization
Crypto: Delete device
This commit is contained in:
commit
02ef1172ce
10 changed files with 197 additions and 31 deletions
|
@ -31,7 +31,10 @@ import java.io.IOException
|
|||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
data class ServerError(val error: MatrixError) : Failure(RuntimeException(error.toString()))
|
||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||
|
||||
data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
|
||||
|
||||
abstract class FeatureFailure : Failure()
|
||||
|
|
|
@ -16,8 +16,18 @@
|
|||
|
||||
package im.vector.matrix.android.internal.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* An interactive authentication flow.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class LoginFlow(val type: String,
|
||||
val stages: List<String>)
|
||||
internal data class InteractiveAuthenticationFlow(
|
||||
|
||||
@Json(name = "type")
|
||||
val type: String? = null,
|
||||
|
||||
@Json(name = "stages")
|
||||
val stages: List<String>? = null
|
||||
)
|
|
@ -16,7 +16,11 @@
|
|||
|
||||
package im.vector.matrix.android.internal.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class LoginFlowResponse(val flows: List<LoginFlow>)
|
||||
internal data class LoginFlowResponse(
|
||||
@Json(name = "flows")
|
||||
val flows: List<InteractiveAuthenticationFlow>
|
||||
)
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2019 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.registration
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class RegistrationFlowResponse(
|
||||
|
||||
/**
|
||||
* The list of flows.
|
||||
*/
|
||||
@Json(name = "flows")
|
||||
var flows: List<InteractiveAuthenticationFlow>? = null,
|
||||
|
||||
/**
|
||||
* The list of stages the client has completed successfully.
|
||||
*/
|
||||
@Json(name = "completed")
|
||||
var completedStages: List<String>? = null,
|
||||
|
||||
/**
|
||||
* The session identifier that the client must pass back to the home server, if one is provided,
|
||||
* in subsequent attempts to authenticate in the same API call.
|
||||
*/
|
||||
@Json(name = "session")
|
||||
var session: String? = null,
|
||||
|
||||
/**
|
||||
* The information that the client will need to know in order to use a given type of authentication.
|
||||
* For each login stage type presented, that type may be present as a key in this dictionary.
|
||||
* For example, the public key of reCAPTCHA stage could be given here.
|
||||
*/
|
||||
@Json(name = "params")
|
||||
var params: JsonDict? = null
|
||||
)
|
|
@ -227,7 +227,7 @@ internal class CryptoModule {
|
|||
DefaultClaimOneTimeKeysForUsersDevice(get()) as ClaimOneTimeKeysForUsersDeviceTask
|
||||
}
|
||||
scope(DefaultSession.SCOPE) {
|
||||
DefaultDeleteDeviceTask(get()) as DeleteDeviceTask
|
||||
DefaultDeleteDeviceTask(get(), get()) as DeleteDeviceTask
|
||||
}
|
||||
scope(DefaultSession.SCOPE) {
|
||||
DefaultDownloadKeysForUsers(get()) as DownloadKeysForUsersTask
|
||||
|
|
|
@ -19,7 +19,7 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* This class provides the
|
||||
* This class provides the authentication data to delete a device
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DeleteDeviceAuth(
|
||||
|
@ -32,6 +32,9 @@ data class DeleteDeviceAuth(
|
|||
@Json(name = "type")
|
||||
var type: String? = null,
|
||||
|
||||
@Json(name = "user")
|
||||
var user: String? = null,
|
||||
|
||||
@Json(name = "password")
|
||||
var password: String? = null
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
|
@ -22,5 +23,6 @@ import com.squareup.moshi.JsonClass
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DeleteDeviceParams(
|
||||
var auth: DeleteDeviceAuth? = null
|
||||
@Json(name = "auth")
|
||||
var deleteDeviceAuth: DeleteDeviceAuth? = null
|
||||
)
|
||||
|
|
|
@ -17,10 +17,19 @@
|
|||
package im.vector.matrix.android.internal.crypto.tasks
|
||||
|
||||
import arrow.core.Try
|
||||
import arrow.core.failure
|
||||
import arrow.core.recoverWith
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import timber.log.Timber
|
||||
|
||||
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
||||
data class Params(
|
||||
|
@ -29,15 +38,84 @@ internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
|||
)
|
||||
}
|
||||
|
||||
internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi)
|
||||
internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi,
|
||||
private val credentials: Credentials)
|
||||
: DeleteDeviceTask {
|
||||
|
||||
override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> {
|
||||
return executeRequest {
|
||||
apiCall = cryptoApi.deleteDevice(params.deviceId,
|
||||
DeleteDeviceParams())
|
||||
}
|
||||
return executeRequest<Unit> {
|
||||
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
||||
}.recoverWith { throwable ->
|
||||
if (throwable is Failure.OtherServerError && throwable.httpCode == 401) {
|
||||
// Replay the request with passing the credentials
|
||||
|
||||
// TODO Recover error, see legacy code MXSession.deleteDevice()
|
||||
// Parse to get a RegistrationFlowResponse
|
||||
val registrationFlowResponseAdapter = MoshiProvider.providesMoshi().adapter(RegistrationFlowResponse::class.java)
|
||||
val registrationFlowResponse = try {
|
||||
registrationFlowResponseAdapter.fromJson(throwable.errorBody)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
// check if the server response can be casted
|
||||
if (registrationFlowResponse?.flows?.isNotEmpty() == true) {
|
||||
val stages = ArrayList<String>()
|
||||
|
||||
// Get all stages
|
||||
registrationFlowResponse.flows?.forEach {
|
||||
stages.addAll(it.stages ?: emptyList())
|
||||
}
|
||||
|
||||
Timber.v("## deleteDevice() : supported stages $stages")
|
||||
|
||||
deleteDeviceRecursive(registrationFlowResponse.session, params, stages)
|
||||
} else {
|
||||
throwable.failure()
|
||||
}
|
||||
|
||||
} else {
|
||||
// Other error
|
||||
throwable.failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDeviceRecursive(authSession: String?,
|
||||
params: DeleteDeviceTask.Params,
|
||||
remainingStages: MutableList<String>): Try<Unit> {
|
||||
// Pick the first stage
|
||||
val stage = remainingStages.first()
|
||||
|
||||
val newParams = DeleteDeviceParams()
|
||||
.apply {
|
||||
deleteDeviceAuth = DeleteDeviceAuth()
|
||||
.apply {
|
||||
type = stage
|
||||
session = authSession
|
||||
user = credentials.userId
|
||||
password = params.accountPassword
|
||||
}
|
||||
}
|
||||
|
||||
return executeRequest<Unit> {
|
||||
apiCall = cryptoApi.deleteDevice(params.deviceId, newParams)
|
||||
}.recoverWith { throwable ->
|
||||
if (throwable is Failure.ServerError
|
||||
&& throwable.httpCode == 401
|
||||
&& (throwable.error.code == MatrixError.FORBIDDEN || throwable.error.code == MatrixError.UNKNOWN)) {
|
||||
if (remainingStages.size > 1) {
|
||||
// Try next stage
|
||||
val otherStages = remainingStages.subList(1, remainingStages.size)
|
||||
|
||||
deleteDeviceRecursive(authSession, params, otherStages)
|
||||
} else {
|
||||
// No more stage remaining
|
||||
throwable.failure()
|
||||
}
|
||||
} else {
|
||||
// Other error
|
||||
throwable.failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,14 @@ import arrow.effects.IO
|
|||
import arrow.effects.fix
|
||||
import arrow.effects.instances.io.async.async
|
||||
import arrow.integrations.retrofit.adapter.runAsync
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
internal inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
|
||||
|
@ -44,23 +46,38 @@ internal class Request<DATA> {
|
|||
if (response.isSuccessful) {
|
||||
response.body() ?: throw IllegalStateException("The request returned a null body")
|
||||
} else {
|
||||
throw manageFailure(response.errorBody())
|
||||
throw manageFailure(response.errorBody(), response.code())
|
||||
}
|
||||
}.recoverWith {
|
||||
when (it) {
|
||||
is IOException -> Failure.NetworkConnection(it)
|
||||
is Failure.ServerError -> it
|
||||
else -> Failure.Unknown(it)
|
||||
is IOException -> Failure.NetworkConnection(it)
|
||||
is Failure.ServerError,
|
||||
is Failure.OtherServerError -> it
|
||||
else -> Failure.Unknown(it)
|
||||
}.failure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun manageFailure(errorBody: ResponseBody?): Throwable {
|
||||
val matrixError = errorBody?.let {
|
||||
val matrixErrorAdapter = moshi.adapter(MatrixError::class.java)
|
||||
matrixErrorAdapter.fromJson(errorBody.source())
|
||||
} ?: return RuntimeException("Matrix error should not be null")
|
||||
return Failure.ServerError(matrixError)
|
||||
}
|
||||
private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable {
|
||||
if (errorBody == null) {
|
||||
return RuntimeException("Error body should not be null")
|
||||
}
|
||||
|
||||
val errorBodyStr = errorBody.string()
|
||||
|
||||
val matrixErrorAdapter = moshi.adapter(MatrixError::class.java)
|
||||
|
||||
try {
|
||||
val matrixError = matrixErrorAdapter.fromJson(errorBodyStr)
|
||||
|
||||
if (matrixError != null) {
|
||||
return Failure.ServerError(matrixError, httpCode)
|
||||
}
|
||||
} catch (ex: JsonDataException) {
|
||||
// This is not a MatrixError
|
||||
Timber.w("The error returned by the server is not a MatrixError")
|
||||
}
|
||||
|
||||
return Failure.OtherServerError(errorBodyStr, httpCode)
|
||||
}
|
||||
}
|
|
@ -2489,15 +2489,12 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||
* @param deviceId the device id
|
||||
*/
|
||||
private fun deleteDevice(deviceId: String) {
|
||||
notImplemented()
|
||||
|
||||
// We have to manage registration flow first, to handle what is necessary to delete a devive
|
||||
/*
|
||||
displayLoadingView()
|
||||
session.deleteDevice(deviceId, mAccountPassword, object : MatrixCallback<Unit> {
|
||||
mSession.deleteDevice(deviceId, mAccountPassword, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
hideLoadingView()
|
||||
refreshDevicesList() // force settings update
|
||||
// force settings update
|
||||
refreshDevicesList()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
|
@ -2505,7 +2502,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||
onCommonDone(failure.localizedMessage)
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue