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 {