mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
7 Commits
v1.6.44
...
feature/ar
Author | SHA1 | Date | |
---|---|---|---|
|
f0d9b3f3ef | ||
|
90162b54ba | ||
|
b22b0bae92 | ||
|
9c78a71717 | ||
|
145b77d911 | ||
|
a83b905139 | ||
|
87d56e5a46 |
@@ -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
|
||||
) {
|
||||
|
||||
/**
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
@@ -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
|
||||
)
|
@@ -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
|
||||
)
|
@@ -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
|
||||
)
|
@@ -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
|
||||
)
|
@@ -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
|
||||
)
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
@@ -7,5 +7,6 @@
|
||||
-->
|
||||
|
||||
<bool name="feature_threads_beta_feedback_enabled">true</bool>
|
||||
<bool name="feature_dehydration_enabled">true</bool>
|
||||
|
||||
</resources>
|
||||
|
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user