mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 17:35:54 +03:00
Keys share request handling
This commit is contained in:
parent
a7c0e87f40
commit
480d197ffa
9 changed files with 316 additions and 6 deletions
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
|||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
|
||||
|
@ -99,4 +100,7 @@ interface CryptoService {
|
|||
fun getEncryptionAlgorithm(roomId: String): String?
|
||||
|
||||
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
||||
|
||||
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
|
||||
|
||||
}
|
|
@ -1050,12 +1050,22 @@ internal class CryptoManager(
|
|||
return unknownDevices
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* DEBUG INFO
|
||||
* ========================================================================================== */
|
||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||
deviceListManager
|
||||
.downloadKeys(userIds, forceDownload)
|
||||
.fold(
|
||||
{ callback.onFailure(it) },
|
||||
{ callback.onSuccess(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* DEBUG INFO
|
||||
* ========================================================================================== */
|
||||
|
||||
override fun toString(): String {
|
||||
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.squareup.moshi.JsonClass
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomKeyRequestBody(
|
||||
@Json(name = "algorithm")
|
||||
var algorithm: String? = null,
|
||||
|
||||
@Json(name = "room_id")
|
||||
|
|
|
@ -56,6 +56,7 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
|||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||
|
@ -390,6 +391,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
|||
return cryptoService.shouldEncryptForInvitedMembers(roomId)
|
||||
}
|
||||
|
||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
||||
cryptoService.downloadKeys(userIds, forceDownload, callback)
|
||||
}
|
||||
|
||||
// Private methods *****************************************************************************
|
||||
|
||||
private fun assertMainThread() {
|
||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.riotredesign.core.resources.LocaleProvider
|
|||
import im.vector.riotredesign.core.resources.StringArrayProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
||||
import im.vector.riotredesign.features.crypto.keysrequest.KeyRequestHandler
|
||||
import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
|
||||
import im.vector.riotredesign.features.home.group.SelectedGroupStore
|
||||
|
@ -87,6 +88,10 @@ class AppModule(private val context: Context) {
|
|||
Matrix.getInstance().currentSession!!
|
||||
}
|
||||
|
||||
single {
|
||||
KeyRequestHandler(context, get())
|
||||
}
|
||||
|
||||
single {
|
||||
IncomingVerificationRequestHandler(context, get())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright 2016 OpenMarket Ltd
|
||||
* Copyright 2017 Vector Creations Ltd
|
||||
* Copyright 2018 New Vector Ltd
|
||||
* 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.riotredesign.features.crypto.keysrequest
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.features.crypto.verification.SASVerificationActivity
|
||||
import im.vector.riotredesign.features.popup.PopupAlertManager
|
||||
import timber.log.Timber
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Manage the key share events.
|
||||
* Listens for incoming key request and display an alert to the user asking him to ignore / verify
|
||||
* calling device / or accept without verifying.
|
||||
* If several requests come from same user/device, a single alert is displayed (this alert will accept/reject all request
|
||||
* depending on user action)
|
||||
*/
|
||||
class KeyRequestHandler(val context: Context,
|
||||
val session: Session)
|
||||
: RoomKeysRequestListener,
|
||||
SasVerificationService.SasVerificationListener {
|
||||
|
||||
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
||||
|
||||
init {
|
||||
session.getSasVerificationService().addListener(this)
|
||||
|
||||
session.addRoomKeysRequestListener(this)
|
||||
}
|
||||
|
||||
fun ensureStarted() = Unit
|
||||
|
||||
/**
|
||||
* Handle incoming key request.
|
||||
*
|
||||
* @param request the key request.
|
||||
*/
|
||||
override fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
|
||||
val userId = request.userId
|
||||
val deviceId = request.deviceId
|
||||
val requestId = request.requestId
|
||||
|
||||
if (userId.isNullOrBlank() || deviceId.isNullOrBlank() || requestId.isNullOrBlank()) {
|
||||
Timber.e("## handleKeyRequest() : invalid parameters")
|
||||
return
|
||||
}
|
||||
|
||||
//Do we already have alerts for this user/device
|
||||
val mappingKey = keyForMap(deviceId, userId)
|
||||
if (alertsToRequests.containsKey(mappingKey)) {
|
||||
//just add the request, there is already an alert for this
|
||||
alertsToRequests[mappingKey]?.add(request)
|
||||
return
|
||||
}
|
||||
|
||||
alertsToRequests[mappingKey] = ArrayList<IncomingRoomKeyRequest>().apply { this.add(request) }
|
||||
|
||||
//Add a notification for every incoming request
|
||||
session.downloadKeys(Arrays.asList(userId), false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
|
||||
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
|
||||
val deviceInfo = data.getObject(deviceId, userId)
|
||||
|
||||
if (null == deviceInfo) {
|
||||
Timber.e("## displayKeyShareDialog() : No details found for device $userId:$deviceId")
|
||||
//ignore
|
||||
return
|
||||
}
|
||||
|
||||
if (deviceInfo.isUnknown) {
|
||||
session.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED, deviceId, userId)
|
||||
|
||||
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
|
||||
|
||||
//can we get more info on this device?
|
||||
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
data.devices?.find { it.deviceId == deviceId }?.let {
|
||||
postAlert(context, userId, deviceId, true, deviceInfo, it)
|
||||
} ?: run {
|
||||
postAlert(context, userId, deviceId, true, deviceInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
postAlert(context, userId, deviceId, true, deviceInfo)
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
postAlert(context, userId, deviceId, false, deviceInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
//ignore
|
||||
Timber.e(failure, "## displayKeyShareDialog : downloadKeys")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun postAlert(context: Context,
|
||||
userId: String,
|
||||
deviceId: String,
|
||||
wasNewDevice: Boolean,
|
||||
deviceInfo: MXDeviceInfo?,
|
||||
moreInfo: DeviceInfo? = null) {
|
||||
val deviceName = if (TextUtils.isEmpty(deviceInfo!!.displayName())) deviceInfo.deviceId else deviceInfo.displayName()
|
||||
val dialogText: String?
|
||||
|
||||
if (moreInfo != null) {
|
||||
val lastSeenIp = if (moreInfo.lastSeenIp.isNullOrBlank()) {
|
||||
context.getString(R.string.encryption_information_unknown_ip)
|
||||
} else {
|
||||
moreInfo.lastSeenIp
|
||||
}
|
||||
|
||||
val lastSeenTime = moreInfo.lastSeenTs?.let { ts ->
|
||||
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||
val date = Date(ts)
|
||||
|
||||
val time = dateFormatTime.format(date)
|
||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
|
||||
|
||||
dateFormat.format(date) + ", " + time
|
||||
} ?: "-"
|
||||
|
||||
val lastSeenInfo = context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
||||
dialogText = if (wasNewDevice) {
|
||||
context.getString(R.string.you_added_a_new_device_with_info, deviceName, lastSeenInfo)
|
||||
} else {
|
||||
context.getString(R.string.your_unverified_device_requesting_with_info, deviceName, lastSeenInfo)
|
||||
}
|
||||
} else {
|
||||
dialogText = if (wasNewDevice) {
|
||||
context.getString(R.string.you_added_a_new_device, deviceName)
|
||||
} else {
|
||||
context.getString(R.string.your_unverified_device_requesting, deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val alert = PopupAlertManager.VectorAlert(
|
||||
alertManagerId(deviceId, userId),
|
||||
context.getString(R.string.key_share_request),
|
||||
dialogText,
|
||||
R.drawable.key_small
|
||||
)
|
||||
|
||||
alert.colorRes = R.color.key_share_req_accent_color
|
||||
|
||||
val mappingKey = keyForMap(deviceId, userId)
|
||||
alert.dismissedAction = Runnable {
|
||||
denyAllRequests(mappingKey)
|
||||
}
|
||||
|
||||
alert.addButton(
|
||||
context.getString(R.string.start_verification_short_label),
|
||||
Runnable {
|
||||
alert.weakCurrentActivity?.get()?.let {
|
||||
val intent = SASVerificationActivity.outgoingIntent(it,
|
||||
session.sessionParams.credentials.userId,
|
||||
userId, deviceId)
|
||||
it.startActivity(intent)
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable {
|
||||
shareAllSessions(mappingKey)
|
||||
})
|
||||
|
||||
alert.addButton(context.getString(R.string.ignore_request_short_label), Runnable {
|
||||
denyAllRequests(mappingKey)
|
||||
})
|
||||
|
||||
PopupAlertManager.postVectorAlert(alert)
|
||||
}
|
||||
|
||||
private fun denyAllRequests(mappingKey: String) {
|
||||
alertsToRequests[mappingKey]?.forEach {
|
||||
it.ignore?.run()
|
||||
}
|
||||
alertsToRequests.remove(mappingKey)
|
||||
}
|
||||
|
||||
private fun shareAllSessions(mappingKey: String) {
|
||||
alertsToRequests[mappingKey]?.forEach {
|
||||
it.share?.run()
|
||||
}
|
||||
alertsToRequests.remove(mappingKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage a cancellation request.
|
||||
*
|
||||
* @param request the cancellation request.
|
||||
*/
|
||||
override fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
|
||||
// see if we can find the request in the queue
|
||||
val userId = request.userId
|
||||
val deviceId = request.deviceId
|
||||
val requestId = request.requestId
|
||||
|
||||
if (TextUtils.isEmpty(userId) || TextUtils.isEmpty(deviceId) || TextUtils.isEmpty(requestId)) {
|
||||
Timber.e("## handleKeyRequestCancellation() : invalid parameters")
|
||||
return
|
||||
}
|
||||
|
||||
val alertMgrUniqueKey = alertManagerId(deviceId!!, userId!!)
|
||||
alertsToRequests[alertMgrUniqueKey]?.removeAll {
|
||||
it.deviceId == request.deviceId
|
||||
&& it.userId == request.userId
|
||||
&& it.requestId == request.requestId
|
||||
}
|
||||
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
|
||||
PopupAlertManager.cancelAlert(alertMgrUniqueKey)
|
||||
alertsToRequests.remove(keyForMap(deviceId, userId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
val state = tx.state
|
||||
if (state == SasVerificationTxState.Verified) {
|
||||
//ok it's verified, see if we have key request for that
|
||||
shareAllSessions("${tx.otherDeviceId}${tx.otherUserId}")
|
||||
PopupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
|
||||
//accept related requests
|
||||
shareAllSessions(keyForMap(deviceId, userId))
|
||||
PopupAlertManager.cancelAlert(alertManagerId(deviceId, userId))
|
||||
}
|
||||
|
||||
private fun keyForMap(deviceId: String, userId: String) = "$deviceId$userId"
|
||||
|
||||
private fun alertManagerId(deviceId: String, userId: String) = "ikr_$deviceId$userId"
|
||||
}
|
|
@ -30,10 +30,12 @@ import im.vector.riotredesign.features.popup.PopupAlertManager
|
|||
class IncomingVerificationRequestHandler(val context: Context,
|
||||
private val session: Session) : SasVerificationService.SasVerificationListener {
|
||||
|
||||
fun ensureStarted() {
|
||||
init {
|
||||
session.getSasVerificationService().addListener(this)
|
||||
}
|
||||
|
||||
fun ensureStarted() = Unit
|
||||
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.riotredesign.core.extensions.replaceFragment
|
|||
import im.vector.riotredesign.core.platform.OnBackPressed
|
||||
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import im.vector.riotredesign.features.crypto.keysrequest.KeyRequestHandler
|
||||
import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.riotredesign.features.rageshake.BugReporter
|
||||
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
|
||||
|
@ -60,6 +61,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
|
||||
// TODO Move this elsewhere
|
||||
private val incomingVerificationRequestHandler by inject<IncomingVerificationRequestHandler>()
|
||||
// TODO Move this elsewhere
|
||||
private val keyRequestHandler by inject<KeyRequestHandler>()
|
||||
|
||||
private var progress: ProgressDialog? = null
|
||||
|
||||
|
@ -105,6 +108,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
}
|
||||
|
||||
incomingVerificationRequestHandler.ensureStarted()
|
||||
keyRequestHandler.ensureStarted()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -131,6 +131,6 @@
|
|||
|
||||
<!-- Notification (do not depends on theme -->
|
||||
<color name="notification_accent_color">#368BD6</color>
|
||||
|
||||
<color name="key_share_req_accent_color">#ff812d</color>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue