diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 7d0173f341..e28ebb31c6 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3383,6 +3383,12 @@
The linking wasn’t completed in the required time.
The request was denied on the other device.
The request failed.
+ 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);
+ The other device is already signed in.
+ The other device must be signed in.
+ The QR code scanned is invalid.
+ The sign in was cancelled on the other device.
+ The homeserver doesn\'t support sign in with QR code.
Open ${app_name} on your other device
Go to Settings -> Security & Privacy -> Show All Sessions
Select \'Show QR code in this device\'
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt
index e467ff06e3..9ad889fca0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt
@@ -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()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt
index 31014e392d..be79569164 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt
@@ -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)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt
index a607dc7f38..18e625d825 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt
@@ -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)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt
index de0aed7efc..5daf906930 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt
@@ -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()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt
index ca7083b297..9a5c5e865a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt
@@ -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 {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt
index 2dd6e7be28..2ef24e9cb7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt
@@ -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")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt
index fec55ffb67..c52b11a322 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt
@@ -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)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt
index ca2a3425cd..fa0e5d8e2a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt
@@ -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")
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt
index 0691e2367e..617d620c27 100644
--- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt
@@ -70,8 +70,14 @@ class QrCodeLoginStatusFragment : VectorBaseFragment 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)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt
index 9d4e5e9870..7f95fad485 100644
--- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt
@@ -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(initialState) {
- val TAG: String = QrCodeLoginViewModel::class.java.simpleName
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory {
override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel
}
- companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+ companion object : MavericksViewModelFactory 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)
+ }
}
}
}