diff --git a/CHANGES.md b/CHANGES.md
index c778853448..2de2e6e64d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,10 @@
Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md
+Changes in Matrix-SDK v1.5.26 (2023-03-08)
+=========================================
+
+Imported from Element 1.5.26. (https://github.com/vector-im/element-android/releases/tag/v1.5.26)
+
Changes in Matrix-SDK v1.5.25 (2023-02-16)
=========================================
diff --git a/dependencies.gradle b/dependencies.gradle
index a876db4fc5..7120789e10 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -10,8 +10,8 @@ def gradle = "7.4.1"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.8.10"
def kotlinCoroutines = "1.6.4"
-def dagger = "2.44.2"
-def firebaseBom = "31.2.0"
+def dagger = "2.45"
+def firebaseBom = "31.2.1"
def appDistribution = "16.0.0-beta05"
def retrofit = "2.9.0"
def markwon = "4.6.2"
@@ -27,7 +27,7 @@ def jjwt = "0.11.5"
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
// the whole commit which set version 0.16.0-SNAPSHOT
def vanniktechEmoji = "0.16.0-SNAPSHOT"
-def sentry = "6.13.0"
+def sentry = "6.14.0"
// Use 1.6.0 alpha to fix issue with test
def fragment = "1.6.0-alpha04"
// Testing
@@ -50,11 +50,11 @@ ext.libs = [
],
androidx : [
'activity' : "androidx.activity:activity-ktx:1.6.1",
- 'appCompat' : "androidx.appcompat:appcompat:1.6.0",
+ 'appCompat' : "androidx.appcompat:appcompat:1.6.1",
'biometric' : "androidx.biometric:biometric:1.1.0",
'core' : "androidx.core:core-ktx:1.9.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
- 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.5",
+ 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6",
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
'fragmentTestingManifest' : "androidx.fragment:fragment-testing-manifest:$fragment",
@@ -88,7 +88,7 @@ ext.libs = [
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber
- 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.5"
+ 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.6"
],
dagger : [
'dagger' : "com.google.dagger:dagger:$dagger",
@@ -103,7 +103,7 @@ ext.libs = [
],
element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0",
- 'wysiwyg' : "io.element.android:wysiwyg:0.23.0"
+ 'wysiwyg' : "io.element.android:wysiwyg:1.0.0"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",
diff --git a/gradle.properties b/gradle.properties
index a04fbf48e4..1c1b76ba1b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -26,7 +26,7 @@ vector.httpLogLevel=NONE
# Ref: https://github.com/vanniktech/gradle-maven-publish-plugin
GROUP=org.matrix.android
POM_ARTIFACT_ID=matrix-android-sdk2
-VERSION_NAME=1.5.25
+VERSION_NAME=1.5.26
POM_PACKAGING=aar
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index b17fe64e54..187f2acdcb 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -199,7 +199,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
// Video compression
- implementation 'com.otaliastudios:transcoder:0.10.4'
+ implementation 'com.otaliastudios:transcoder:0.10.5'
// Exif data handling
implementation libs.apache.commonsImaging
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt
new file mode 100644
index 0000000000..5b5aad4c51
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2023 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.api.rendezvous
+
+import org.amshove.kluent.invoking
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldBeInstanceOf
+import org.amshove.kluent.shouldThrow
+import org.amshove.kluent.with
+import org.junit.Test
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+import org.matrix.android.sdk.common.CommonTestHelper
+
+class RendezvousTest : InstrumentedTest {
+
+ @Test
+ fun shouldSuccessfullyBuildChannels() = CommonTestHelper.runCryptoTest(context()) { _, _ ->
+ val cases = listOf(
+ // v1:
+ "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
+ "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
+ "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
+ "\"intent\":\"login.reciprocate\"}",
+ // v2:
+ "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256\"," +
+ "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
+ "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
+ "\"intent\":\"login.reciprocate\"}",
+ )
+
+ cases.forEach { input ->
+ Rendezvous.buildChannelFromCode(input).channel shouldBeInstanceOf ECDHRendezvousChannel::class
+ }
+ }
+
+ @Test
+ fun shouldFailToBuildChannelAsUnsupportedAlgorithm() {
+ invoking {
+ Rendezvous.buildChannelFromCode(
+ "{\"rendezvous\":{\"algorithm\":\"bad algo\"," +
+ "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
+ "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
+ "\"intent\":\"login.reciprocate\"}"
+ )
+ } shouldThrow RendezvousError::class with {
+ this.reason shouldBeEqualTo RendezvousFailureReason.UnsupportedAlgorithm
+ }
+ }
+
+ @Test
+ fun shouldFailToBuildChannelAsUnsupportedTransport() {
+ invoking {
+ Rendezvous.buildChannelFromCode(
+ "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
+ "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
+ "{\"type\":\"bad transport\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
+ "\"intent\":\"login.reciprocate\"}"
+ )
+ } shouldThrow RendezvousError::class with {
+ this.reason shouldBeEqualTo RendezvousFailureReason.UnsupportedTransport
+ }
+ }
+
+ @Test
+ fun shouldFailToBuildChannelWithInvalidIntent() {
+ invoking {
+ Rendezvous.buildChannelFromCode(
+ "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
+ "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
+ "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
+ "\"intent\":\"foo\"}"
+ )
+ } shouldThrow RendezvousError::class with {
+ this.reason shouldBeEqualTo RendezvousFailureReason.InvalidCode
+ }
+ }
+
+ @Test
+ fun shouldFailToBuildChannelAsInvalidCode() {
+ val cases = listOf(
+ "{}",
+ "rubbish",
+ ""
+ )
+
+ cases.forEach { input ->
+ invoking {
+ Rendezvous.buildChannelFromCode(input)
+ } shouldThrow RendezvousError::class with {
+ this.reason shouldBeEqualTo RendezvousFailureReason.InvalidCode
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
index e490311b91..c6fab7762f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
@@ -44,7 +44,7 @@ interface AuthenticationService {
/**
* Get a SSO url.
*/
- fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
+ fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String?
/**
* Get the sign in or sign up fallback URL.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt
new file mode 100644
index 0000000000..db2dd870d5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.api.auth
+
+/**
+ * See https://github.com/matrix-org/matrix-spec-proposals/pull/3824
+ */
+enum class SSOAction {
+ LOGIN,
+ REGISTER;
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt
new file mode 100644
index 0000000000..b57472ab7c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 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.api.auth.data
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * https://github.com/matrix-org/matrix-spec-proposals/pull/2965
+ *
+ * {
+ * "issuer": "https://id.server.org",
+ * "account": "https://id.server.org/my-account",
+ * }
+ *
+ * .
+ */
+
+@JsonClass(generateAdapter = true)
+data class DelegatedAuthConfig(
+ @Json(name = "issuer")
+ val issuer: String,
+
+ @Json(name = "account")
+ val accountManagementUrl: String,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
index 5de83033e1..5d737b716b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
@@ -22,6 +22,7 @@ data class LoginFlowResult(
val isLoginAndRegistrationSupported: Boolean,
val homeServerUrl: String,
val isOutdatedHomeserver: Boolean,
+ val hasOidcCompatibilityFlow: Boolean,
val isLogoutDevicesSupported: Boolean,
val isLoginWithQrSupported: Boolean,
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt
index 10c7d51392..95488bd682 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt
@@ -54,5 +54,11 @@ data class WellKnown(
val identityServer: WellKnownBaseConfig? = null,
@Json(name = "m.integrations")
- val integrations: JsonDict? = null
+ val integrations: JsonDict? = null,
+
+ /**
+ * For delegation of auth via OIDC as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
+ */
+ @Json(name = "org.matrix.msc2965.authentication")
+ val unstableDelegatedAuthConfig: DelegatedAuthConfig? = null,
)
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 e5b2d6bf12..28d8230be8 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,8 +26,11 @@ 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.RendezvousCode
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.model.RendezvousTransportType
+import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm
import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
@@ -53,18 +56,37 @@ class Rendezvous(
@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
+ // we first check that the code is valid JSON and has right high-level structure
+ val genericParsed = try {
+ // we rely on moshi validating the code and throwing exception if invalid JSON or algorithm doesn't match
+ MatrixJsonParser.getMoshi().adapter(RendezvousCode::class.java).fromJson(code)
+ } catch (a: Throwable) {
+ throw RendezvousError("Malformed code", RendezvousFailureReason.InvalidCode)
+ } ?: throw RendezvousError("Code is null", RendezvousFailureReason.InvalidCode)
+
+ // then we check that algorithm is supported
+ if (!SecureRendezvousChannelAlgorithm.values().map { it.value }.contains(genericParsed.rendezvous.algorithm)) {
+ throw RendezvousError("Unsupported algorithm", RendezvousFailureReason.UnsupportedAlgorithm)
+ }
+
+ // and, that the transport is supported
+ if (!RendezvousTransportType.values().map { it.value }.contains(genericParsed.rendezvous.transport.type)) {
+ throw RendezvousError("Unsupported transport", RendezvousFailureReason.UnsupportedTransport)
+ }
+
+ // now that we know the overall structure looks sensible, we rely on moshi validating the code and
+ // throwing exception if other parts are invalid
+ val supportedParsed = try {
MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code)
} catch (a: Throwable) {
- throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode)
- } ?: throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode)
+ throw RendezvousError("Malformed ECDH rendezvous code", RendezvousFailureReason.InvalidCode)
+ } ?: throw RendezvousError("ECDH rendezvous code is null", RendezvousFailureReason.InvalidCode)
- val transport = SimpleHttpRendezvousTransport(parsed.rendezvous.transport.uri)
+ val transport = SimpleHttpRendezvousTransport(supportedParsed.rendezvous.transport.uri)
return Rendezvous(
- ECDHRendezvousChannel(transport, parsed.rendezvous.key),
- parsed.intent
+ ECDHRendezvousChannel(transport, supportedParsed.rendezvous.algorithm, supportedParsed.rendezvous.key),
+ supportedParsed.intent
)
}
}
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 c1d6b1b70e..71b22da338 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
@@ -41,7 +41,11 @@ import javax.crypto.spec.SecretKeySpec
* Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903:
* https://github.com/matrix-org/matrix-spec-proposals/pull/3903
*/
-class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?) : RendezvousChannel {
+class ECDHRendezvousChannel(
+ override var transport: RendezvousTransport,
+ private val algorithm: SecureRendezvousChannelAlgorithm,
+ theirPublicKeyBase64: String?,
+) : RendezvousChannel {
companion object {
private const val ALGORITHM_SPEC = "AES/GCM/NoPadding"
private const val KEY_SPEC = "AES"
@@ -53,7 +57,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
val algorithm: SecureRendezvousChannelAlgorithm? = null,
val key: String? = null,
val ciphertext: String? = null,
- val iv: String? = null
+ val iv: String? = null,
)
private val olmSASMutex = Mutex()
@@ -65,10 +69,22 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
init {
theirPublicKeyBase64?.let {
- theirPublicKey = Base64.decode(it, Base64.NO_WRAP)
+ theirPublicKey = decodeBase64(it)
}
olmSAS = OlmSAS()
- ourPublicKey = Base64.decode(olmSAS!!.publicKey, Base64.NO_WRAP)
+ ourPublicKey = decodeBase64(olmSAS!!.publicKey)
+ }
+
+ fun encodeBase64(input: ByteArray?): String? {
+ if (algorithm == SecureRendezvousChannelAlgorithm.ECDH_V2) {
+ return Base64.encodeToString(input, Base64.NO_WRAP or Base64.NO_PADDING)
+ }
+ return Base64.encodeToString(input, Base64.NO_WRAP)
+ }
+
+ fun decodeBase64(input: String?): ByteArray {
+ // for decoding we aren't concerned about padding
+ return Base64.decode(input, Base64.NO_WRAP)
}
@Throws(RendezvousError::class)
@@ -86,25 +102,25 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
RendezvousFailureReason.UnsupportedAlgorithm,
)
}
- theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP)
+ theirPublicKey = decodeBase64(res.key)
} else {
// send our public key unencrypted
Timber.tag(TAG).i("Sending public key")
send(
ECDHPayload(
- algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1,
- key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)
+ algorithm = algorithm,
+ key = encodeBase64(ourPublicKey)
)
)
}
olmSASMutex.withLock {
- sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP))
- sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP))
+ sas.setTheirPublicKey(encodeBase64(theirPublicKey))
+ sas.setTheirPublicKey(encodeBase64(theirPublicKey))
- val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP)
- val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP)
- val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey"
+ val initiatorKey = encodeBase64(if (isInitiator) ourPublicKey else theirPublicKey)
+ val recipientKey = encodeBase64(if (isInitiator) theirPublicKey else ourPublicKey)
+ val aesInfo = "${algorithm.value}|$initiatorKey|$recipientKey"
aesKey = sas.generateShortCode(aesInfo, 32)
@@ -162,20 +178,20 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu
cipherText.addAll(encryptCipher.doFinal().toList())
return ECDHPayload(
- ciphertext = Base64.encodeToString(cipherText.toByteArray(), Base64.NO_WRAP),
- iv = Base64.encodeToString(iv, Base64.NO_WRAP)
+ ciphertext = encodeBase64(cipherText.toByteArray()),
+ iv = encodeBase64(iv)
)
}
private fun decrypt(payload: ECDHPayload): ByteArray {
- val iv = Base64.decode(payload.iv, Base64.NO_WRAP)
+ val iv = decodeBase64(payload.iv)
val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
val ivParameterSpec = IvParameterSpec(iv)
encryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
val plainText = LinkedList()
- plainText.addAll(encryptCipher.update(Base64.decode(payload.ciphertext, Base64.NO_WRAP)).toList())
+ plainText.addAll(encryptCipher.update(decodeBase64(payload.ciphertext)).toList())
plainText.addAll(encryptCipher.doFinal().toList())
return plainText.toByteArray()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Rendezvous.kt
new file mode 100644
index 0000000000..f424f8cab0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Rendezvous.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 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.api.rendezvous.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+open class Rendezvous(
+ val transport: RendezvousTransportDetails,
+ val algorithm: String,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt
new file mode 100644
index 0000000000..ffa8bf6661
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 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.api.rendezvous.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+open class RendezvousCode(
+ open val intent: RendezvousIntent,
+ open val rendezvous: Rendezvous
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt
index 1bde43ab7e..34d96ac64a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt
@@ -20,5 +20,5 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
open class RendezvousTransportDetails(
- val type: RendezvousTransportType
+ val type: String
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt
index 75f0024fda..123e41a5d7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt
@@ -22,5 +22,7 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class SecureRendezvousChannelAlgorithm(val value: String) {
@Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256")
- ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256")
+ ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256"),
+ @Json(name = "org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256")
+ ECDH_V2("org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt
index 049aa8b756..d2342bb9d5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt
@@ -21,4 +21,4 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SimpleHttpRendezvousTransportDetails(
val uri: String
-) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1)
+) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1.name)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index 96e52469c3..4968df775a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -80,6 +80,11 @@ data class HomeServerCapabilities(
* True if the home server supports event redaction with relations.
*/
var canRedactEventWithRelations: Boolean = false,
+
+ /**
+ * External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
+ */
+ val externalAccountManagementUrl: String? = null,
) {
enum class RoomCapabilitySupport {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
index 4f35fb79c3..34581b613a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
@@ -47,6 +47,16 @@ object RuleIds {
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"
+ const val RULE_ID_POLL_START_ONE_TO_ONE = ".m.rule.poll_start_one_to_one"
+ const val RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE = ".org.matrix.msc3930.rule.poll_start_one_to_one"
+ const val RULE_ID_POLL_END_ONE_TO_ONE = ".m.rule.poll_end_one_to_one"
+ const val RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE = ".org.matrix.msc3930.rule.poll_end_one_to_one"
+
+ const val RULE_ID_POLL_START = ".m.rule.poll_start"
+ const val RULE_ID_POLL_START_UNSTABLE = ".org.matrix.msc3930.rule.poll_start"
+ const val RULE_ID_POLL_END = ".m.rule.poll_end"
+ const val RULE_ID_POLL_END_UNSTABLE = ".org.matrix.msc3930.rule.poll_end"
+
// Not documented
const val RULE_ID_FALLBACK = ".m.rule.fallback"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
index 9498ed002c..9287a7828d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
@@ -47,21 +47,14 @@ data class RuleSet(
* @param ruleId a RULE_ID_XX value
* @return the matched bing rule or null it doesn't exist.
*/
- fun findDefaultRule(ruleId: String?): PushRuleAndKind? {
- var result: PushRuleAndKind? = null
- // sanity check
- if (null != ruleId) {
- if (RuleIds.RULE_ID_CONTAIN_USER_NAME == ruleId) {
- result = findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) }
- } else {
- // assume that the ruleId is unique.
- result = findRule(override, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.OVERRIDE) }
- if (null == result) {
- result = findRule(underride, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.UNDERRIDE) }
- }
- }
+ fun findDefaultRule(ruleId: String): PushRuleAndKind? {
+ return if (RuleIds.RULE_ID_CONTAIN_USER_NAME == ruleId) {
+ findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) }
+ } else {
+ // assume that the ruleId is unique.
+ findRule(override, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.OVERRIDE) }
+ ?: findRule(underride, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.UNDERRIDE) }
}
- return result
}
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
index 6e31320b13..9c894ebe28 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
@@ -33,5 +33,9 @@ data class MessageEndPollContent(
override val msgType: String = MessageType.MSGTYPE_POLL_END,
@Json(name = "body") override val body: String = "",
@Json(name = "m.new_content") override val newContent: Content? = null,
- @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null
-) : MessageContent
+ @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
+ @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
+ @Json(name = "m.text") val text: String? = null,
+) : MessageContent {
+ fun getBestText() = text ?: unstableText
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 3aa480094c..a49c20ccbb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.events.model.isLiveLocation
import org.matrix.android.sdk.api.session.events.model.isPoll
import org.matrix.android.sdk.api.session.events.model.isReply
import org.matrix.android.sdk.api.session.events.model.isSticker
+import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
@@ -36,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
@@ -157,7 +159,39 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
}
fun TimelineEvent.getLastEditNewContent(): Content? {
- return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent
+ val lastContent = annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent
+ return if (isReply()) {
+ val previousFormattedBody = root.getClearContent().toModel()?.formattedBody
+ if (previousFormattedBody?.isNotEmpty() == true) {
+ val lastMessageContent = lastContent.toModel()
+ lastMessageContent?.let { ensureCorrectFormattedBodyInTextReply(it, previousFormattedBody) }?.toContent() ?: lastContent
+ } else {
+ lastContent
+ }
+ } else {
+ lastContent
+ }
+}
+
+private const val MX_REPLY_END_TAG = ""
+
+/**
+ * Not every client sends a formatted body in the last edited event since this is not required in the
+ * [Matrix specification](https://spec.matrix.org/v1.4/client-server-api/#applying-mnew_content).
+ * We must ensure there is one so that it is still considered as a reply when rendering the message.
+ */
+private fun ensureCorrectFormattedBodyInTextReply(messageTextContent: MessageTextContent, previousFormattedBody: String): MessageTextContent {
+ return when {
+ messageTextContent.formattedBody.isNullOrEmpty() && previousFormattedBody.contains(MX_REPLY_END_TAG) -> {
+ // take previous formatted body with the new body content
+ val newFormattedBody = previousFormattedBody.replaceAfterLast(MX_REPLY_END_TAG, messageTextContent.body)
+ messageTextContent.copy(
+ formattedBody = newFormattedBody,
+ format = MessageFormat.FORMAT_MATRIX_HTML,
+ )
+ }
+ else -> messageTextContent
+ }
}
private fun TimelineEvent.getLastPollEditNewContent(): Content? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index d9c2afcb40..d1dd0238ba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.LoginType
+import org.matrix.android.sdk.api.auth.SSOAction
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@@ -88,7 +89,7 @@ internal class DefaultAuthenticationService @Inject constructor(
return getLoginFlow(homeServerConnectionConfig)
}
- override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
+ override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String? {
val homeServerUrlBase = getHomeServerUrlBase() ?: return null
return buildString {
@@ -103,6 +104,9 @@ internal class DefaultAuthenticationService @Inject constructor(
// But https://github.com/matrix-org/synapse/issues/5755
appendParamToUrl("device_id", it)
}
+
+ // unstable MSC3824 action param
+ appendParamToUrl("org.matrix.msc3824.action", action.toString())
}
}
@@ -292,12 +296,18 @@ internal class DefaultAuthenticationService @Inject constructor(
val loginFlowResponse = executeRequest(null) {
authAPI.getLoginFlows()
}
+
+ // If an m.login.sso flow is present that is flagged as being for MSC3824 OIDC compatibility then we only return that flow
+ val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibilty == true }
+ val flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows
+
return LoginFlowResult(
- supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
- ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
+ supportedLoginTypes = flows.orEmpty().mapNotNull { it.type },
+ ssoIdentityProviders = flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
homeServerUrl = homeServerUrl,
isOutdatedHomeserver = !versions.isSupportedBySdk(),
+ hasOidcCompatibilityFlow = oidcCompatibilityFlow != null,
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices(),
isLoginWithQrSupported = versions.doesServerSupportQrCodeLogin(),
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
index df10e110d1..971407388c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
@@ -43,6 +43,13 @@ internal data class LoginFlow(
* See MSC #2858
*/
@Json(name = "identity_providers")
- val ssoIdentityProvider: List? = null
+ val ssoIdentityProvider: List? = null,
+ /**
+ * Whether this login flow is preferred for OIDC-aware clients.
+ *
+ * See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824)
+ */
+ @Json(name = "org.matrix.msc3824.delegated_oidc_compatibility")
+ val delegatedOidcCompatibilty: Boolean? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 45bcd792c2..c5ececcddb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo051
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@@ -75,7 +76,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
- schemaVersion = 50L,
+ schemaVersion = 51L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@@ -135,5 +136,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
if (oldVersion < 49) MigrateSessionTo049(realm).perform()
if (oldVersion < 50) MigrateSessionTo050(realm).perform()
+ if (oldVersion < 51) MigrateSessionTo051(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index 83f3e87d05..1c7a0591a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -48,6 +48,7 @@ internal object HomeServerCapabilitiesMapper {
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
canRedactEventWithRelations = entity.canRedactEventWithRelations,
+ externalAccountManagementUrl = entity.externalAccountManagementUrl,
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo051.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo051.kt
new file mode 100644
index 0000000000..3bed97073d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo051.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2023 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.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo051(realm: DynamicRealm) : RealmMigrator(realm, 51) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java)
+ ?.forceRefreshOfHomeServerCapabilities()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 9acdcde7e5..35a5c654de 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -35,6 +35,7 @@ internal open class HomeServerCapabilitiesEntity(
var canUseThreadReadReceiptsAndNotifications: Boolean = false,
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
var canRedactEventWithRelations: Boolean = false,
+ var externalAccountManagementUrl: String? = null,
) : RealmObject() {
companion object
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index 5a6107821d..ec12695ecd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -167,6 +167,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
Timber.v("Extracted integration config : $config")
realm.insertOrUpdate(config)
}
+ homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl
}
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
index 9fe93d8262..3dfac694ed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
@@ -57,6 +57,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
when (event.type) {
in EventType.POLL_START.values,
+ in EventType.POLL_END.values,
in EventType.STATE_ROOM_BEACON_INFO.values,
EventType.MESSAGE,
EventType.REDACTION,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
index 2ff43d6812..ca224cd543 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
@@ -84,7 +84,6 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
val roomId = event.roomId ?: return false
val senderId = event.senderId ?: return false
val targetEventId = event.getRelationContent()?.eventId ?: return false
- val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, targetEventId)
val aggregatedPollSummaryEntity = getAggregatedPollSummaryEntity(realm, annotationsSummaryEntity)
@@ -108,7 +107,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
}
val vote = content.getBestResponse()?.answers?.first() ?: return false
- if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(vote).orFalse()) {
+ val targetPollContent = getPollContent(session, roomId, targetEventId)
+ if (targetPollContent != null && !targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(vote).orFalse()) {
return false
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index b5114ec1dd..c2bdec3596 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -242,7 +242,8 @@ internal class LocalEchoEventFactory @Inject constructor(
relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE,
eventId = eventId
- )
+ ),
+ unstableText = "Ended poll",
)
val localId = LocalEcho.createLocalEchoId()
return Event(
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt
index 766e51a8e5..248c4b322d 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt
@@ -147,6 +147,19 @@ class DefaultPollAggregationProcessorTest {
pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, AN_INVALID_POLL_RESPONSE_EVENT).shouldBeFalse()
}
+ @Test
+ fun `given a poll response event and no existing poll start event, when processing, then is processed and returns true`() {
+ // Given
+ mockRoom(roomId = A_ROOM_ID, eventId = AN_EVENT_ID, hasExistingTimelineEvent = false)
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+
+ // When
+ val result = pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT)
+
+ // Then
+ result.shouldBeTrue()
+ }
+
@Test
fun `given a poll end event, when processing, then is processed and return true`() = runTest {
// Given
@@ -234,11 +247,12 @@ class DefaultPollAggregationProcessorTest {
private fun mockRoom(
roomId: String,
- eventId: String
+ eventId: String,
+ hasExistingTimelineEvent: Boolean = true,
) {
val room = mockk()
every { session.getRoom(roomId) } returns room
- every { room.getTimelineEvent(eventId) } returns A_TIMELINE_EVENT
+ every { room.getTimelineEvent(eventId) } returns if (hasExistingTimelineEvent) A_TIMELINE_EVENT else null
}
private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): PowerLevelsHelper {