1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Compare commits

...

7 Commits

Author SHA1 Message Date
ariskotsomitopoulos
f0d9b3f3ef Merge branch 'develop' into feature/aris/crypto_dehydration
# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
2022-05-12 16:02:01 +03:00
ariskotsomitopoulos
90162b54ba Invoke device dehydration on logout when there are no more active devices left 2022-05-11 17:34:21 +03:00
ariskotsomitopoulos
b22b0bae92 Implement device rehydration 2022-05-11 17:34:00 +03:00
ariskotsomitopoulos
9c78a71717 Implement device dehydration 2022-05-10 22:14:26 +03:00
ariskotsomitopoulos
145b77d911 Add feature flag for dehydration 2022-05-10 22:11:20 +03:00
ariskotsomitopoulos
a83b905139 Add keys/upload/{deviceId) Api call 2022-05-09 16:29:36 +03:00
ariskotsomitopoulos
87d56e5a46 Add dehydration API calls and DTOs 2022-05-09 13:36:13 +03:00
20 changed files with 721 additions and 6 deletions

View File

@@ -65,6 +65,10 @@ data class MatrixConfiguration(
* Thread messages default enable/disabled value
*/
val threadMessagesEnabledDefault: Boolean = false,
/**
* Indicates whether or not dehydration functionality is enabled
*/
val isDehydrationEnabled: Boolean = false
) {
/**

View File

@@ -38,5 +38,5 @@ interface SignOutService {
* Sign out, and release the session, clear all the session data, including crypto data
* @param signOutFromHomeserver true if the sign out request has to be done
*/
suspend fun signOut(signOutFromHomeserver: Boolean)
suspend fun signOut(signOutFromHomeserver: Boolean, numberOfActiveDevices: Int? = null)
}

View File

@@ -89,6 +89,12 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.ClaimDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.DefaultClaimDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.DefaultGetDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.DefaultUploadDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.GetDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.UploadDehydratedDeviceTask
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
@@ -251,4 +257,14 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
@Binds
abstract fun bindClaimDehydratedDeviceTask(task: DefaultClaimDehydratedDeviceTask): ClaimDehydratedDeviceTask
@Binds
abstract fun bindGetDehydratedDeviceTask(task: DefaultGetDehydratedDeviceTask): GetDehydratedDeviceTask
@Binds
abstract fun bindUploadDehydratedDeviceTask(task: DefaultUploadDehydratedDeviceTask): UploadDehydratedDeviceTask
}

View File

@@ -28,7 +28,12 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.UploadDehydratedDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.UploadDehydratedDeviceResponse
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.ClaimDehydratedDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.ClaimDehydratedDeviceResponse
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.GetDehydratedDeviceResponse
import org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.http.Body
import retrofit2.http.GET
@@ -63,6 +68,24 @@ internal interface CryptoApi {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload")
suspend fun uploadKeys(@Body body: KeysUploadBody): KeysUploadResponse
/**
* Upload device and/or one-time keys, for a specific deviceId
*
* Overloading the existing uploadKeys() to enhance our request with the deviceId
*
* Note: Synapse already supports POST /keys/upload/{device_id} as this was used in some old clients.
* However, synapse requires that the given device ID matches the device ID of the client that made the call.
* So this will be changed to allow uploading keys for the dehydrated device.
* @param body the keys to be sent.
* @param deviceId the deviceId that keys will be uploaded
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload/{deviceId}")
suspend fun uploadKeys(
@Body body: KeysUploadBody,
@Path("deviceId") deviceId: String
): KeysUploadResponse
/**
* Download device keys.
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-query
@@ -153,4 +176,35 @@ internal interface CryptoApi {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes")
suspend fun getKeyChanges(@Query("from") oldToken: String,
@Query("to") newToken: String): KeyChangesResponse
/**
* Uploads the given dehydrated device. Each user has at most one dehydrated device.
* Uploading a new dehydrated device will remove any previously-set dehydrated device.
* set given device as dehydrated for the account.
*
* @param dehydratedDeviceBody data of the dehydrated device.
*
* Ref: https://github.com/uhoreg/matrix-doc/blob/dehydration/proposals/2697-device-dehydration.md#dehydrating-a-device
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2697.v2/dehydrated_device")
suspend fun uploadDehydratedDevice(@Body dehydratedDeviceBody: UploadDehydratedDeviceBody): UploadDehydratedDeviceResponse
/**
* Get current dehydrated device of the account, If no dehydrated device is available, the server responds with a 404.
*
* Ref: https://github.com/uhoreg/matrix-doc/blob/dehydration/proposals/2697-device-dehydration.md#rehydrating-a-device
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2697.v2/dehydrated_device")
suspend fun getDehydratedDevice(): GetDehydratedDeviceResponse
/**
* claim the current dehydrated device.
* Note: Clients should not call any other endpoints before rehydrating a device.
* In particular, if a client calls /sync while rehydrating, the client should not expect the /sync to return sensible information.
* For example, it could contain a mix of to-device messages sent to the old device ID and the new device ID.
*
* Ref: https://github.com/uhoreg/matrix-doc/blob/dehydration/proposals/2697-device-dehydration.md#rehydrating-a-device
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2697.v2/dehydrated_device/claim")
suspend fun claimDehydratedDevice(@Body claimDehydratedDeviceBody: ClaimDehydratedDeviceBody): ClaimDehydratedDeviceResponse
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright 2022 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.crypto.dehydration
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.MXCryptoAlgorithms
import org.matrix.android.sdk.internal.crypto.ObjectSigner
import org.matrix.android.sdk.internal.crypto.crosssigning.canonicalSignable
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.DehydratedDeviceData
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.UploadDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.ClaimDehydratedDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.dehydration.GetDehydratedDeviceTask
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.olm.OlmAccount
import timber.log.Timber
import javax.inject.Inject
/**
* The class is responsible for handling the dehydration mechanism
*/
private val loggerTag = LoggerTag("DehydrationManager", LoggerTag.CRYPTO)
@SessionScope
internal class DehydrationManager @Inject constructor(
matrixConfiguration: MatrixConfiguration,
private val uploadDehydratedDeviceTask: UploadDehydratedDeviceTask,
private val getDehydratedDeviceTask: GetDehydratedDeviceTask,
private val claimDehydratedDeviceTask: ClaimDehydratedDeviceTask,
private val cryptoCoroutineScope: CoroutineScope,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val objectSigner: ObjectSigner,
private val uploadKeysTask: UploadKeysTask,
private val credentials: Credentials
) {
private companion object {
private const val dehydratedDeviceAlgorithm = "org.matrix.msc2697.v1.olm.libolm_pickle"
private const val deviceDisplayName: String = "Dehydrated device"
}
private val isDehydrationEnabled = matrixConfiguration.isDehydrationEnabled
private var inProgress = false
/**
* Upload and replace any existing dehydrated device
* @param dehydrationKey must be the SSSS cross signing
*/
suspend fun dehydrateDevice(dehydrationKey: ByteArray): Boolean {
if (!isDehydrationEnabled) {
Timber.tag(loggerTag.value).d("Dehydration is disabled")
return false
}
if (inProgress) {
Timber.tag(loggerTag.value).d("Dehydration already in progress")
return false
}
Timber.tag(loggerTag.value).d("Device dehydration started!")
val result = runCatching {
val account = createOlmAccount()
val errorMessage = StringBuffer()
val pickledAccount = account.pickle(dehydrationKey, errorMessage)
if (errorMessage.isNotBlank()) {
Timber.tag(loggerTag.value).d("Failed to create pickled account")
return@runCatching false
}
val dehydratedDevice = UploadDehydratedDeviceTask.Params(
displayName = deviceDisplayName,
deviceData = DehydratedDeviceData(
algorithm = dehydratedDeviceAlgorithm,
account = pickledAccount.toString(Charsets.UTF_8)
)
)
// Upload the dehydrated device
val dehydratedDeviceId = uploadDehydratedDeviceTask.execute(dehydratedDevice).deviceId
try {
Timber.tag(loggerTag.value).d("Generating and uploading device keys")
val deviceKeys = generateDeviceKeys(dehydratedDeviceId, account)
val oneTimeJson = generateOneTimeKeys(account)
val fallbackJson = generateFallbackKeys(account)
val uploadDeviceKeysParams = UploadKeysTask.Params(deviceKeys, oneTimeJson, fallbackJson, dehydratedDeviceId)
uploadKeysTask.execute(uploadDeviceKeysParams)
Timber.tag(loggerTag.value).d("Device dehydrated successfully with dehydratedDeviceId: $dehydratedDeviceId")
return@runCatching true
} catch (ex: Throwable) {
Timber.tag(loggerTag.value).e(ex, "Generating & uploading device keys failed")
return@runCatching false
}
}.onFailure {
Timber.tag(loggerTag.value).e(it, "Failed to upload dehydration device")
}.getOrNull()
return result ?: false
}
/**
* Generate one time keys from the given account
*/
private fun generateOneTimeKeys(account: OlmAccount): Map<String, Any> {
// Create one time keys
val oneTimeJson = mutableMapOf<String, Any>()
// ------
// ????? Why it was here account.oneTimeKeys() in the PR
// -----
val curve25519Map = account.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY].orEmpty()
curve25519Map.forEach { (key_id, value) ->
val k = mutableMapOf<String, Any>()
k["key"] = value
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
k["signatures"] = objectSigner.signObject(canonicalJson)
oneTimeJson["signed_curve25519:$key_id"] = k
}
return oneTimeJson
}
/**
* Generate one fallback keys from the given account
*/
private fun generateFallbackKeys(account: OlmAccount): Map<String, Any> {
val fallbackJson = mutableMapOf<String, Any>()
val fallbackCurve25519Map = account.fallbackKey()[OlmAccount.JSON_KEY_ONE_TIME_KEY].orEmpty()
fallbackCurve25519Map.forEach { (key_id, key) ->
val k = mutableMapOf<String, Any>()
k["key"] = key
k["fallback"] = true
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
k["signatures"] = objectSigner.signObject(canonicalJson)
fallbackJson["signed_curve25519:$key_id"] = k
}
return fallbackJson
}
/**
* Generates device keys
* @param dehydratedDeviceId the dehydrated device id
* @param account the newly created olm account
*/
private fun generateDeviceKeys(dehydratedDeviceId: String, account: OlmAccount): DeviceKeys {
val deviceInfo = generateDeviceInfo(dehydratedDeviceId, account)
val signature = account.signMessage(deviceInfo.canonicalSignable())
return DeviceKeys(
userId = deviceInfo.userId,
deviceId = deviceInfo.deviceId,
keys = deviceInfo.keys,
algorithms = deviceInfo.algorithms,
signatures = mapOf(
credentials.userId to mapOf(
"ed25519:$dehydratedDeviceId" to signature
)
)
)
}
/**
* Generates a CryptoDeviceInfo object from provided dehydratedDeviceId
*/
private fun generateDeviceInfo(dehydratedDeviceId: String, account: OlmAccount): CryptoDeviceInfo {
val e2eKeys = account.identityKeys()
return CryptoDeviceInfo(
userId = credentials.userId,
deviceId = dehydratedDeviceId,
keys = mapOf(
"ed25519:$dehydratedDeviceId" to e2eKeys["ed25519"].toString(),
"curve25519:$dehydratedDeviceId" to e2eKeys["curve25519"].toString()
),
algorithms = MXCryptoAlgorithms.supportedAlgorithms()
)
}
/**
* Creates a new OlmAccount
*/
private fun createOlmAccount(): OlmAccount {
val account = OlmAccount()
val maxKeys = account.maxOneTimeKeys().toInt()
account.generateOneTimeKeys(maxKeys / 2)
account.generateFallbackKey()
return account
}
/**
* Rehydrate an existing dehydrated device
* @param dehydrationKey must be the SSSS cross signing
*/
suspend fun rehydrateDevice(dehydrationKey: ByteArray): Boolean {
if (!isDehydrationEnabled) {
Timber.tag(loggerTag.value).d("Dehydration is disabled")
return false
}
// Get the dehydration device if found
val (dehydratedDeviceId, deviceData) = try {
getDehydratedDeviceTask.execute(Unit)
} catch (ex: Throwable) {
Timber.tag(loggerTag.value).e(ex, "Failed to get a dehydration device")
return false
}
if (dehydratedDeviceId.isNullOrBlank()) {
Timber.tag(loggerTag.value).d("No dehydrated device found.")
return false
}
if (deviceData.algorithm != dehydratedDeviceAlgorithm) {
Timber.e("Invalid dehydrated device algorithm.")
return false
}
Timber.tag(loggerTag.value).d("Dehydrated device found with dehydratedDeviceId: $dehydratedDeviceId")
/*val olmAccount = */createOlmAccountFromDehydrationKey(dehydrationKey, deviceData.account) ?: return false
// Lets claim the dehydrated device
val (success) = try {
claimDehydratedDeviceTask.execute(ClaimDehydratedDeviceTask.Params(dehydratedDeviceId))
} catch (ex: Throwable) {
Timber.tag(loggerTag.value).e(ex, "Device already claimed.")
return false
}
if (!success) {
Timber.tag(loggerTag.value).d("Failed to claim dehydrated deviceId:$dehydratedDeviceId")
return false
}
// // Exporting dehydrated device
// val tmpCredentials = credentials.copy(deviceId = dehydratedDeviceId)
// val dehydratedAccount = deviceData.account
Timber.tag(loggerTag.value).d("Dehydrated device with dehydratedDeviceId: $dehydratedDeviceId claimed!")
return true
}
/**
* Unpickles an account
*/
private fun createOlmAccountFromDehydrationKey(dehydrationKey: ByteArray, account: String): OlmAccount? {
return runCatching {
val olmAccount = OlmAccount()
// val key = Base64.decode(dehydrationKey, Base64.DEFAULT)
olmAccount.unpickle(account.toByteArray(), dehydrationKey)
Timber.tag(loggerTag.value).d("Account unpickled identityKeys: ${olmAccount.identityKeys()}")
olmAccount
}.onFailure {
Timber.tag(loggerTag.value).e(it, "Failed to unpickle account")
}.getOrNull()
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 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.crypto.model.rest.dehydration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/uhoreg/matrix-doc/blob/dehydration/proposals/2697-device-dehydration.md#rehydrating-a-device
*/
@JsonClass(generateAdapter = true)
internal data class ClaimDehydratedDeviceBody(
/**
* Dehydrated device id to claim.
*/
@Json(name = "device_id")
val deviceId: String
)

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2022 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.crypto.model.rest.dehydration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class describes the response of claiming a dehydrated device
*/
@JsonClass(generateAdapter = true)
internal data class ClaimDehydratedDeviceResponse(
/**
* True if the dehydrated device has been properly claimed. False otherwise.
*/
@Json(name = "success")
val success: Boolean
)

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2022 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.crypto.model.rest.dehydration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class DehydratedDeviceData(
/**
* Type of the algorithm used for pickling the account data. Should be "m.dehydration.v1.olm"
*/
@Json(name = "algorithm")
val algorithm: String,
/**
* Pickled account data of the device
*/
@Json(name = "account")
val account: String,
/**
* Optionally the passphrase used to pickle the data
*/
@Json(name = "passphrase")
val passphrase: String? = null
)

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2022 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.crypto.model.rest.dehydration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class describes the response of getting the current dehydrated device
*/
@JsonClass(generateAdapter = true)
internal data class GetDehydratedDeviceResponse(
/**
* ID of the dehydrated device
*/
@Json(name = "device_id")
val deviceId: String?,
/**
* Data of the dehydrated device.
*/
@Json(name = "device_data")
val deviceData: DehydratedDeviceData
)

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 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.crypto.model.rest.dehydration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/uhoreg/matrix-doc/blob/dehydration/proposals/2697-device-dehydration.md#dehydrating-a-device
*/
@JsonClass(generateAdapter = true)
internal data class UploadDehydratedDeviceBody(
/**
* Initial device display name of the dehydrated device.
*/
@Json(name = "initial_device_display_name")
val displayName: String,
/**
* Data of the dehydrated device.
*/
@Json(name = "device_data")
val deviceData: DehydratedDeviceData
)

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2022 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.crypto.model.rest.dehydration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class describes the response of uploading a dehydrated device
*/
@JsonClass(generateAdapter = true)
internal data class UploadDehydratedDeviceResponse(
/**
* ID of the dehydrated device.
*/
@Json(name = "device_id")
val deviceId: String
)

View File

@@ -33,7 +33,8 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
val deviceKeys: DeviceKeys?,
// the one-time keys to send.
val oneTimeKeys: JsonDict?,
val fallbackKeys: JsonDict?
val fallbackKeys: JsonDict?,
val deviceId: String? = null
)
}
@@ -52,7 +53,11 @@ internal class DefaultUploadKeysTask @Inject constructor(
Timber.i("## Uploading device keys -> $body")
return executeRequest(globalErrorReceiver) {
cryptoApi.uploadKeys(body)
if (params.deviceId == null) {
cryptoApi.uploadKeys(body)
} else {
cryptoApi.uploadKeys(body, deviceId = params.deviceId)
}
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2022 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.crypto.tasks.dehydration
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.ClaimDehydratedDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.ClaimDehydratedDeviceResponse
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.DehydratedDeviceData
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.GetDehydratedDeviceResponse
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.UploadDehydratedDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.UploadDehydratedDeviceResponse
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import javax.inject.Inject
internal interface ClaimDehydratedDeviceTask : Task<ClaimDehydratedDeviceTask.Params, ClaimDehydratedDeviceResponse> {
data class Params(
val deviceId: String
)
}
internal class DefaultClaimDehydratedDeviceTask @Inject constructor(
private val cryptoApi: CryptoApi,
private val globalErrorReceiver: GlobalErrorReceiver
) : ClaimDehydratedDeviceTask {
override suspend fun execute(params: ClaimDehydratedDeviceTask.Params): ClaimDehydratedDeviceResponse {
return executeRequest(globalErrorReceiver) {
cryptoApi.claimDehydratedDevice(ClaimDehydratedDeviceBody(params.deviceId))
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2022 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.crypto.tasks.dehydration
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.GetDehydratedDeviceResponse
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetDehydratedDeviceTask : Task<Unit, GetDehydratedDeviceResponse>
internal class DefaultGetDehydratedDeviceTask @Inject constructor(
private val cryptoApi: CryptoApi,
private val globalErrorReceiver: GlobalErrorReceiver
) : GetDehydratedDeviceTask {
override suspend fun execute(params: Unit): GetDehydratedDeviceResponse {
return executeRequest(globalErrorReceiver) {
cryptoApi.getDehydratedDevice()
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2022 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.crypto.tasks.dehydration
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.DehydratedDeviceData
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.UploadDehydratedDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.dehydration.UploadDehydratedDeviceResponse
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import javax.inject.Inject
internal interface UploadDehydratedDeviceTask : Task<UploadDehydratedDeviceTask.Params, UploadDehydratedDeviceResponse> {
data class Params(
val displayName: String,
val deviceData: DehydratedDeviceData
)
}
internal class DefaultUploadDehydratedDeviceTask @Inject constructor(
private val cryptoApi: CryptoApi,
private val globalErrorReceiver: GlobalErrorReceiver
) : UploadDehydratedDeviceTask {
override suspend fun execute(params: UploadDehydratedDeviceTask.Params): UploadDehydratedDeviceResponse {
val body = UploadDehydratedDeviceBody(
displayName = params.displayName,
deviceData = params.deviceData
)
Timber.i("## Uploading dehydrated device device")
return executeRequest(globalErrorReceiver) {
cryptoApi.uploadDehydratedDevice(body)
}
}
}

View File

@@ -17,12 +17,16 @@
package org.matrix.android.sdk.internal.session.signout
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.crypto.dehydration.DehydrationManager
import timber.log.Timber
import javax.inject.Inject
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
private val signInAgainTask: SignInAgainTask,
private val dehydrationManager: DehydrationManager,
private val sessionParamsStore: SessionParamsStore
) : SignOutService {
@@ -34,7 +38,10 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask
sessionParamsStore.updateCredentials(credentials)
}
override suspend fun signOut(signOutFromHomeserver: Boolean) {
override suspend fun signOut(signOutFromHomeserver: Boolean, numberOfActiveDevices: Int?) {
if (numberOfActiveDevices == 1) {
dehydrationManager.dehydrateDevice("dehydrationKey".toByteArray())
}
return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver))
}
}

View File

@@ -7,5 +7,6 @@
-->
<bool name="feature_threads_beta_feedback_enabled">true</bool>
<bool name="feature_dehydration_enabled">true</bool>
</resources>

View File

@@ -29,6 +29,7 @@ import dagger.hilt.components.SingletonComponent
import im.vector.app.BuildConfig
import im.vector.app.EmojiCompatWrapper
import im.vector.app.EmojiSpanify
import im.vector.app.R
import im.vector.app.config.analyticsConfig
import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter
@@ -117,12 +118,14 @@ object VectorStaticModule {
@Provides
fun providesMatrixConfiguration(
resources: Resources,
vectorPreferences: VectorPreferences,
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
return MatrixConfiguration(
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
isDehydrationEnabled = resources.getBoolean(R.bool.feature_dehydration_enabled)
)
}

View File

@@ -160,7 +160,8 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
args.clearCredentials -> {
lifecycleScope.launch {
try {
session.signOutService().signOut(!args.isUserLoggedOut)
val numberOfActiveDevices = session.cryptoService().getMyDevicesInfo().size
session.signOutService().signOut(!args.isUserLoggedOut,numberOfActiveDevices)
} catch (failure: Throwable) {
displayError(failure)
return@launch

View File

@@ -133,7 +133,8 @@ class LinkHandlerActivity : VectorBaseActivity<ActivityProgressBinding>() {
} else {
lifecycleScope.launch {
try {
session.signOutService().signOut(true)
val numberOfActiveDevices = session.cryptoService().getMyDevicesInfo().size
session.signOutService().signOut(true, numberOfActiveDevices)
Timber.d("## displayAlreadyLoginPopup(): logout succeeded")
sessionHolder.clearActiveSession()
startLoginActivity(uri)