Refactor error handling and report E2EE errors
This commit is contained in:
parent
d616251f26
commit
e01ee619d3
@ -3383,6 +3383,12 @@
|
||||
<string name="qr_code_login_header_failed_timeout_description">The linking wasn’t completed in the required time.</string>
|
||||
<string name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
|
||||
<string name="qr_code_login_header_failed_other_description">The request failed.</string>
|
||||
<string name="qr_code_login_header_failed_e2ee_security_issue_description">A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your intent connection(s); Your device(s);</string>
|
||||
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">The other device is already signed in.</string>
|
||||
<string name="qr_code_login_header_failed_other_device_not_signed_in_description">The other device must be signed in.</string>
|
||||
<string name="qr_code_login_header_failed_invalid_qr_code_description">The QR code scanned is invalid.</string>
|
||||
<string name="qr_code_login_header_failed_user_cancelled_description">The sign in was cancelled on the other device.</string>
|
||||
<string name="qr_code_login_header_failed_homeserver_is_not_supported_description">The homeserver doesn\'t support sign in with QR code.</string>
|
||||
<string name="qr_code_login_new_device_instruction_1">Open ${app_name} on your other device</string>
|
||||
<string name="qr_code_login_new_device_instruction_2">Go to Settings -> Security & Privacy -> Show All Sessions</string>
|
||||
<string name="qr_code_login_new_device_instruction_3">Select \'Show QR code in this device\'</string>
|
||||
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.rendezvous.model.Outcome
|
||||
import org.matrix.android.sdk.api.rendezvous.model.Payload
|
||||
import org.matrix.android.sdk.api.rendezvous.model.PayloadType
|
||||
import org.matrix.android.sdk.api.rendezvous.model.Protocol
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent
|
||||
import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@ -47,10 +48,16 @@ class Rendezvous(
|
||||
companion object {
|
||||
private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value
|
||||
|
||||
fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous {
|
||||
val parsed = MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code) ?: throw RuntimeException("Invalid code")
|
||||
@Throws(RendezvousError::class)
|
||||
fun buildChannelFromCode(code: String): Rendezvous {
|
||||
val parsed = try {
|
||||
// we rely on moshi validating the code and throwing exception if invalid JSON or doesn't
|
||||
MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code)
|
||||
} catch (a: Throwable) {
|
||||
throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode)
|
||||
} ?: throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode)
|
||||
|
||||
val transport = SimpleHttpRendezvousTransport(onCancelled, parsed.rendezvous.transport.uri)
|
||||
val transport = SimpleHttpRendezvousTransport(parsed.rendezvous.transport.uri)
|
||||
|
||||
return Rendezvous(
|
||||
ECDHRendezvousChannel(transport, parsed.rendezvous.key),
|
||||
@ -64,32 +71,30 @@ class Rendezvous(
|
||||
// not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
|
||||
val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE
|
||||
|
||||
private suspend fun areIntentsIncompatible(): Boolean {
|
||||
@Throws(RendezvousError::class)
|
||||
private suspend fun checkCompatibility() {
|
||||
val incompatible = theirIntent == ourIntent
|
||||
|
||||
Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible")
|
||||
|
||||
if (incompatible) {
|
||||
// inform the other side
|
||||
send(Payload(PayloadType.FINISH, intent = ourIntent))
|
||||
val reason = if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) {
|
||||
RendezvousFailureReason.OtherDeviceNotSignedIn
|
||||
if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) {
|
||||
throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn)
|
||||
} else {
|
||||
RendezvousFailureReason.OtherDeviceAlreadySignedIn
|
||||
throw RendezvousError("The other device is already signed in", RendezvousFailureReason.OtherDeviceAlreadySignedIn)
|
||||
}
|
||||
channel.cancel(reason)
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun startAfterScanningCode(): String? {
|
||||
val checksum = channel.connect()
|
||||
|
||||
Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum")
|
||||
|
||||
if (areIntentsIncompatible()) {
|
||||
return null
|
||||
}
|
||||
checkCompatibility()
|
||||
|
||||
// get protocols
|
||||
Timber.tag(TAG).i("Waiting for protocols")
|
||||
@ -97,9 +102,7 @@ class Rendezvous(
|
||||
|
||||
if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) {
|
||||
send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED))
|
||||
Timber.tag(TAG).i("No supported protocol")
|
||||
cancel(RendezvousFailureReason.Unknown)
|
||||
return null
|
||||
throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver)
|
||||
}
|
||||
|
||||
send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN))
|
||||
@ -107,6 +110,7 @@ class Rendezvous(
|
||||
return checksum
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? {
|
||||
Timber.tag(TAG).i("Waiting for login_token")
|
||||
|
||||
@ -115,24 +119,19 @@ class Rendezvous(
|
||||
if (loginToken?.type == PayloadType.FINISH) {
|
||||
when (loginToken.outcome) {
|
||||
Outcome.DECLINED -> {
|
||||
Timber.tag(TAG).i("Login declined by other device")
|
||||
channel.cancel(RendezvousFailureReason.UserDeclined)
|
||||
return null
|
||||
throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined)
|
||||
}
|
||||
Outcome.UNSUPPORTED -> {
|
||||
Timber.tag(TAG).i("Not supported")
|
||||
channel.cancel(RendezvousFailureReason.HomeserverLacksSupport)
|
||||
return null
|
||||
throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver)
|
||||
}
|
||||
else -> {
|
||||
channel.cancel(RendezvousFailureReason.Unknown)
|
||||
return null
|
||||
throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned")
|
||||
val token = loginToken.loginToken ?: throw RuntimeException("No login token returned")
|
||||
val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError)
|
||||
val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError)
|
||||
|
||||
Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver")
|
||||
|
||||
@ -140,6 +139,7 @@ class Rendezvous(
|
||||
return authenticationService.loginUsingQrLoginToken(hsConfig, token)
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun completeVerificationOnNewDevice(session: Session) {
|
||||
val userId = session.myUserId
|
||||
val crypto = session.cryptoService()
|
||||
@ -148,59 +148,77 @@ class Rendezvous(
|
||||
send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey))
|
||||
|
||||
// await confirmation of verification
|
||||
|
||||
val verificationResponse = receive()
|
||||
val verifyingDeviceId = verificationResponse?.verifyingDeviceId ?: throw RuntimeException("No verifying device id returned")
|
||||
val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId)
|
||||
if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) {
|
||||
Timber.tag(TAG).w(
|
||||
"Verifying device $verifyingDeviceId key doesn't match: ${
|
||||
verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})"
|
||||
)
|
||||
throw RuntimeException("Key from verifying device doesn't match")
|
||||
}
|
||||
if (verificationResponse?.outcome == Outcome.VERIFIED) {
|
||||
val verifyingDeviceId = verificationResponse.verifyingDeviceId
|
||||
?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError)
|
||||
val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId)
|
||||
if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) {
|
||||
Timber.tag(TAG).w(
|
||||
"Verifying device $verifyingDeviceId key doesn't match: ${
|
||||
verifyingDeviceFromServer?.fingerprint()
|
||||
} vs ${verificationResponse.verifyingDeviceKey})"
|
||||
)
|
||||
// inform the other side
|
||||
send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
|
||||
throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
|
||||
}
|
||||
|
||||
// set other device as verified
|
||||
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
|
||||
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
|
||||
verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice ->
|
||||
// check master key againt what the homeserver told us
|
||||
crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey ->
|
||||
if (localMasterKey.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) {
|
||||
Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey")
|
||||
// inform the other side
|
||||
send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
|
||||
throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
|
||||
}
|
||||
// set other device as verified
|
||||
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
|
||||
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
|
||||
|
||||
verificationResponse.masterKey ?.let { masterKeyFromVerifyingDevice ->
|
||||
// set master key as trusted
|
||||
crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey ->
|
||||
if (localMasterKey.unpaddedBase64PublicKey == masterKeyFromVerifyingDevice) {
|
||||
Timber.tag(TAG).i("Setting master key as trusted")
|
||||
crypto.crossSigningService().markMyMasterKeyAsTrusted()
|
||||
} else {
|
||||
Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey")
|
||||
throw RuntimeException("Master key from verifying device doesn't match")
|
||||
}
|
||||
} ?: Timber.tag(TAG).i("No local master key")
|
||||
} ?: Timber.tag(TAG).i("No master key given by verifying device")
|
||||
} ?: Timber.tag(TAG).w("No local master key so not verifying")
|
||||
} ?: run {
|
||||
// set other device as verified anyway
|
||||
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
|
||||
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
|
||||
|
||||
// request secrets from the verifying device
|
||||
Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId")
|
||||
Timber.tag(TAG).i("No master key given by verifying device")
|
||||
}
|
||||
|
||||
session.sharedSecretStorageService().let {
|
||||
it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId)
|
||||
it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
|
||||
it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
|
||||
it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId)
|
||||
// request secrets from the verifying device
|
||||
Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId")
|
||||
|
||||
session.sharedSecretStorageService().let {
|
||||
it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId)
|
||||
it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
|
||||
it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
|
||||
it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId)
|
||||
}
|
||||
} else {
|
||||
Timber.tag(TAG).i("Not doing verification")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
private suspend fun receive(): Payload? {
|
||||
val data = channel.receive() ?: return null
|
||||
return adapter.fromJson(data.toString(Charsets.UTF_8))
|
||||
val payload = try {
|
||||
adapter.fromJson(data.toString(Charsets.UTF_8))
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).w(e, "Failed to parse payload")
|
||||
throw RendezvousError("Invalid payload received", RendezvousFailureReason.Unknown)
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
private suspend fun send(payload: Payload) {
|
||||
channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
suspend fun cancel(reason: RendezvousFailureReason) {
|
||||
channel.cancel(reason)
|
||||
}
|
||||
|
||||
suspend fun close() {
|
||||
channel.close()
|
||||
}
|
||||
|
@ -16,8 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.api.rendezvous
|
||||
|
||||
import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
|
||||
/**
|
||||
* Representation of a rendezvous channel such as that described by MSC3903.
|
||||
@ -28,26 +27,25 @@ interface RendezvousChannel {
|
||||
/**
|
||||
* @returns the checksum/confirmation digits to be shown to the user
|
||||
*/
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun connect(): String
|
||||
|
||||
/**
|
||||
* Send a payload via the channel.
|
||||
* @param data payload to send
|
||||
*/
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun send(data: ByteArray)
|
||||
|
||||
/**
|
||||
* Receive a payload from the channel.
|
||||
* @returns the received payload
|
||||
*/
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun receive(): ByteArray?
|
||||
|
||||
/**
|
||||
* @returns a representation of the channel that can be encoded in a QR or similar
|
||||
* @returns closes the channel and cleans up
|
||||
*/
|
||||
suspend fun close()
|
||||
|
||||
// In future we probably want this to be a more generic RendezvousCode but it is suffice for now
|
||||
suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode
|
||||
suspend fun cancel(reason: RendezvousFailureReason)
|
||||
}
|
||||
|
@ -16,16 +16,17 @@
|
||||
|
||||
package org.matrix.android.sdk.api.rendezvous
|
||||
|
||||
enum class RendezvousFailureReason(val value: String, val canRetry: Boolean = true) {
|
||||
UserDeclined("user_declined"),
|
||||
OtherDeviceNotSignedIn("other_device_not_signed_in"),
|
||||
OtherDeviceAlreadySignedIn("other_device_already_signed_in"),
|
||||
Unknown("unknown"),
|
||||
Expired("expired"),
|
||||
UserCancelled("user_cancelled"),
|
||||
InvalidCode("invalid_code"),
|
||||
UnsupportedAlgorithm("unsupported_algorithm", false),
|
||||
DataMismatch("data_mismatch"),
|
||||
UnsupportedTransport("unsupported_transport", false),
|
||||
HomeserverLacksSupport("homeserver_lacks_support", false)
|
||||
enum class RendezvousFailureReason(val canRetry: Boolean = true) {
|
||||
UserDeclined,
|
||||
OtherDeviceNotSignedIn,
|
||||
OtherDeviceAlreadySignedIn,
|
||||
Unknown,
|
||||
Expired,
|
||||
UserCancelled,
|
||||
InvalidCode,
|
||||
UnsupportedAlgorithm(false),
|
||||
UnsupportedTransport(false),
|
||||
UnsupportedHomeserver(false),
|
||||
ProtocolError,
|
||||
E2EESecurityIssue(false)
|
||||
}
|
||||
|
@ -17,13 +17,16 @@
|
||||
package org.matrix.android.sdk.api.rendezvous
|
||||
|
||||
import okhttp3.MediaType
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
|
||||
|
||||
interface RendezvousTransport {
|
||||
var ready: Boolean
|
||||
var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun details(): RendezvousTransportDetails
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun send(contentType: MediaType, data: ByteArray)
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun receive(): ByteArray?
|
||||
suspend fun cancel(reason: RendezvousFailureReason)
|
||||
suspend fun close()
|
||||
}
|
||||
|
@ -89,13 +89,14 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
|
||||
ourPublicKey = Base64.decode(olmSAS!!.publicKey, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
override suspend fun connect(): String {
|
||||
olmSAS ?.let { olmSAS ->
|
||||
val isInitiator = theirPublicKey == null
|
||||
|
||||
if (isInitiator) {
|
||||
Timber.tag(TAG).i("Waiting for other device to send their public key")
|
||||
val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device")
|
||||
val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError)
|
||||
|
||||
if (res.key == null) {
|
||||
throw RendezvousError(
|
||||
@ -137,7 +138,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
|
||||
|
||||
override suspend fun send(data: ByteArray) {
|
||||
if (aesKey == null) {
|
||||
throw RuntimeException("Shared secret not established")
|
||||
throw IllegalStateException("Shared secret not established")
|
||||
}
|
||||
send(encrypt(data))
|
||||
}
|
||||
@ -150,31 +151,12 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
|
||||
|
||||
override suspend fun receive(): ByteArray? {
|
||||
if (aesKey == null) {
|
||||
throw RuntimeException("Shared secret not established")
|
||||
throw IllegalStateException("Shared secret not established")
|
||||
}
|
||||
val payload = receiveAsPayload() ?: return null
|
||||
return decrypt(payload)
|
||||
}
|
||||
|
||||
override suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode {
|
||||
return ECDHRendezvousCode(
|
||||
intent,
|
||||
rendezvous = ECDHRendezvous(
|
||||
transport.details() as SimpleHttpRendezvousTransportDetails,
|
||||
SecureRendezvousChannelAlgorithm.ECDH_V1,
|
||||
key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun cancel(reason: RendezvousFailureReason) {
|
||||
try {
|
||||
transport.cancel(reason)
|
||||
} finally {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun close() {
|
||||
olmSAS ?.let {
|
||||
synchronized(it) {
|
||||
@ -183,6 +165,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
|
||||
olmSAS = null
|
||||
}
|
||||
}
|
||||
transport.close()
|
||||
}
|
||||
|
||||
private fun encrypt(plainText: ByteArray): ECDHPayload {
|
||||
|
@ -26,5 +26,11 @@ enum class Outcome(val value: String) {
|
||||
DECLINED("declined"),
|
||||
|
||||
@Json(name = "unsupported")
|
||||
UNSUPPORTED("unsupported")
|
||||
UNSUPPORTED("unsupported"),
|
||||
|
||||
@Json(name = "verified")
|
||||
VERIFIED("verified"),
|
||||
|
||||
@Json(name = "e2ee_security_error")
|
||||
E2EE_SECURITY_ERROR("e2ee_security_error")
|
||||
}
|
||||
|
@ -18,4 +18,4 @@ package org.matrix.android.sdk.api.rendezvous.model
|
||||
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
|
||||
class RendezvousError(val description: String, val reason: RendezvousFailureReason) : RuntimeException(description)
|
||||
class RendezvousError(val description: String, val reason: RendezvousFailureReason) : Exception(description)
|
||||
|
@ -23,6 +23,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
|
||||
import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails
|
||||
import timber.log.Timber
|
||||
@ -32,7 +33,7 @@ import java.util.Date
|
||||
/**
|
||||
* Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886
|
||||
*/
|
||||
class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?, rendezvousUri: String?) : RendezvousTransport {
|
||||
class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport {
|
||||
companion object {
|
||||
private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value
|
||||
}
|
||||
@ -55,7 +56,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo
|
||||
|
||||
override suspend fun send(contentType: MediaType, data: ByteArray) {
|
||||
if (cancelled) {
|
||||
return
|
||||
throw IllegalStateException("Rendezvous cancelled")
|
||||
}
|
||||
|
||||
val method = if (uri != null) "PUT" else "POST"
|
||||
@ -75,9 +76,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo
|
||||
val response = httpClient.newCall(request.build()).execute()
|
||||
|
||||
if (response.code == 404) {
|
||||
// we set to unknown and the cancel method will rewrite the reason to expired if applicable
|
||||
cancel(RendezvousFailureReason.Unknown)
|
||||
return
|
||||
throw get404Error()
|
||||
}
|
||||
etag = response.header("etag")
|
||||
|
||||
@ -98,12 +97,12 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo
|
||||
}
|
||||
|
||||
override suspend fun receive(): ByteArray? {
|
||||
if (cancelled) {
|
||||
throw IllegalStateException("Rendezvous cancelled")
|
||||
}
|
||||
val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
|
||||
val httpClient = okhttp3.OkHttpClient.Builder().build()
|
||||
while (true) {
|
||||
if (cancelled) {
|
||||
return null
|
||||
}
|
||||
Timber.tag(TAG).i("Polling: $uri after etag $etag")
|
||||
val request = Request.Builder()
|
||||
.url(uri)
|
||||
@ -118,9 +117,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo
|
||||
try {
|
||||
// expired
|
||||
if (response.code == 404) {
|
||||
// we set to unknown and the cancel method will rewrite the reason to expired if applicable
|
||||
cancel(RendezvousFailureReason.Unknown)
|
||||
return null
|
||||
throw get404Error()
|
||||
}
|
||||
|
||||
// rely on server expiring the channel rather than checking ourselves
|
||||
@ -145,31 +142,27 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun cancel(reason: RendezvousFailureReason) {
|
||||
var mappedReason = reason
|
||||
Timber.tag(TAG).i("$expiresAt")
|
||||
if (mappedReason == RendezvousFailureReason.Unknown &&
|
||||
expiresAt != null && Date() > expiresAt
|
||||
) {
|
||||
mappedReason = RendezvousFailureReason.Expired
|
||||
}
|
||||
private fun get404Error(): RendezvousError {
|
||||
return if (expiresAt != null && Date() > expiresAt)
|
||||
RendezvousError("Expired", RendezvousFailureReason.Expired)
|
||||
else
|
||||
RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown)
|
||||
}
|
||||
|
||||
override suspend fun close() {
|
||||
cancelled = true
|
||||
ready = false
|
||||
onCancelled ?.let { it(mappedReason) }
|
||||
|
||||
if (mappedReason == RendezvousFailureReason.UserDeclined) {
|
||||
uri ?.let {
|
||||
try {
|
||||
val httpClient = okhttp3.OkHttpClient.Builder().build()
|
||||
val request = Request.Builder()
|
||||
.url(it)
|
||||
.delete()
|
||||
.build()
|
||||
httpClient.newCall(request).execute()
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).w(e, "Failed to delete channel")
|
||||
}
|
||||
uri ?.let {
|
||||
try {
|
||||
val httpClient = okhttp3.OkHttpClient.Builder().build()
|
||||
val request = Request.Builder()
|
||||
.url(it)
|
||||
.delete()
|
||||
.build()
|
||||
httpClient.newCall(request).execute()
|
||||
} catch (e: Throwable) {
|
||||
Timber.tag(TAG).w(e, "Failed to delete channel")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,8 +70,14 @@ class QrCodeLoginStatusFragment : VectorBaseFragment<FragmentQrCodeLoginStatusBi
|
||||
return when (reason) {
|
||||
RendezvousFailureReason.UnsupportedAlgorithm,
|
||||
RendezvousFailureReason.UnsupportedTransport -> getString(R.string.qr_code_login_header_failed_device_is_not_supported_description)
|
||||
RendezvousFailureReason.UnsupportedHomeserver -> getString(R.string.qr_code_login_header_failed_homeserver_is_not_supported_description)
|
||||
RendezvousFailureReason.Expired -> getString(R.string.qr_code_login_header_failed_timeout_description)
|
||||
RendezvousFailureReason.UserDeclined -> getString(R.string.qr_code_login_header_failed_denied_description)
|
||||
RendezvousFailureReason.E2EESecurityIssue -> getString(R.string.qr_code_login_header_failed_e2ee_security_issue_description)
|
||||
RendezvousFailureReason.OtherDeviceAlreadySignedIn -> getString(R.string.qr_code_login_header_failed_other_device_already_signed_in_description)
|
||||
RendezvousFailureReason.OtherDeviceNotSignedIn -> getString(R.string.qr_code_login_header_failed_other_device_not_signed_in_description)
|
||||
RendezvousFailureReason.InvalidCode -> getString(R.string.qr_code_login_header_failed_invalid_qr_code_description)
|
||||
RendezvousFailureReason.UserCancelled -> getString(R.string.qr_code_login_header_failed_user_cancelled_description)
|
||||
else -> getString(R.string.qr_code_login_header_failed_other_description)
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.rendezvous.Rendezvous
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import timber.log.Timber
|
||||
|
||||
class QrCodeLoginViewModel @AssistedInject constructor(
|
||||
@ -38,14 +39,15 @@ class QrCodeLoginViewModel @AssistedInject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase
|
||||
) : VectorViewModel<QrCodeLoginViewState, QrCodeLoginAction, QrCodeLoginViewEvents>(initialState) {
|
||||
val TAG: String = QrCodeLoginViewModel::class.java.simpleName
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> {
|
||||
override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> by hiltMavericksViewModelFactory()
|
||||
companion object : MavericksViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> by hiltMavericksViewModelFactory() {
|
||||
val TAG: String = QrCodeLoginViewModel::class.java.simpleName
|
||||
}
|
||||
|
||||
override fun handle(action: QrCodeLoginAction) {
|
||||
when (action) {
|
||||
@ -71,9 +73,14 @@ class QrCodeLoginViewModel @AssistedInject constructor(
|
||||
private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) {
|
||||
Timber.tag(TAG).d("Scanned code: ${action.qrCode}")
|
||||
|
||||
val rendezvous = Rendezvous.buildChannelFromCode(action.qrCode) { reason ->
|
||||
Timber.tag(TAG).d("Rendezvous cancelled: $reason")
|
||||
onFailed(reason)
|
||||
val rendezvous = try { Rendezvous.buildChannelFromCode(action.qrCode) } catch (t: Throwable) {
|
||||
Timber.tag(TAG).e(t, "Error occurred during sign in")
|
||||
if (t is RendezvousError) {
|
||||
onFailed(t.reason)
|
||||
} else {
|
||||
onFailed(RendezvousFailureReason.Unknown)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
setState {
|
||||
@ -103,9 +110,13 @@ class QrCodeLoginViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen)
|
||||
}
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(TAG).e(failure, "Error occurred during sign in")
|
||||
onFailed(RendezvousFailureReason.Unknown)
|
||||
} catch (t: Throwable) {
|
||||
Timber.tag(TAG).e(t, "Error occurred during sign in")
|
||||
if (t is RendezvousError) {
|
||||
onFailed(t.reason)
|
||||
} else {
|
||||
onFailed(RendezvousFailureReason.Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user