mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-23 09:30:50 +03:00
Merge branch 'release/0.18.0'
This commit is contained in:
commit
dec591517c
319 changed files with 2067 additions and 1727 deletions
|
@ -1,95 +0,0 @@
|
||||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
|
||||||
# Last docker plugin version can be found here:
|
|
||||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
|
||||||
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- label: "Compile and run Unit tests"
|
|
||||||
agents:
|
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "medium"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean test --stacktrace"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Compile Android tests"
|
|
||||||
agents:
|
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "medium"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean assembleAndroidTest --stacktrace"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Assemble GPlay Debug version"
|
|
||||||
agents:
|
|
||||||
# We use a xlarge sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "xlarge"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
|
||||||
artifact_paths:
|
|
||||||
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
|
||||||
branches: "!master"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Assemble FDroid Debug version"
|
|
||||||
agents:
|
|
||||||
# We use a xlarge sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "xlarge"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
|
||||||
artifact_paths:
|
|
||||||
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
|
||||||
branches: "!master"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
- label: "Build Google Play unsigned APK"
|
|
||||||
agents:
|
|
||||||
# We use a xlarge sized instance instead of the normal small ones because
|
|
||||||
# gradle build can be memory hungry
|
|
||||||
queue: "xlarge"
|
|
||||||
commands:
|
|
||||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
|
||||||
artifact_paths:
|
|
||||||
- "vector/build/outputs/apk/gplay/release/*.apk"
|
|
||||||
branches: "master"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "runmymind/docker-android-sdk"
|
|
||||||
propagate-environment: true
|
|
||||||
|
|
||||||
# Code quality
|
|
||||||
|
|
||||||
- label: "Code quality"
|
|
||||||
command:
|
|
||||||
- "./tools/check/check_code_quality.sh"
|
|
||||||
|
|
||||||
- label: "ktlint"
|
|
||||||
command:
|
|
||||||
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint"
|
|
||||||
- "./ktlint --android --experimental -v"
|
|
||||||
plugins:
|
|
||||||
- docker#v3.1.0:
|
|
||||||
image: "openjdk"
|
|
||||||
|
|
||||||
# Check that indonesians files are identical.
|
|
||||||
# Due to Android issue, the resource folder must be values-in/, and Weblate export data into values-id/.
|
|
||||||
# If this step fails, it means that Weblate has updated the file in value-id/ so to fix it, copy the file to values-in/
|
|
||||||
- label: "Indonesian"
|
|
||||||
command:
|
|
||||||
- "diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml"
|
|
|
@ -1,9 +1,6 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<option name="RIGHT_MARGIN" value="160" />
|
<option name="RIGHT_MARGIN" value="160" />
|
||||||
<AndroidXmlCodeStyleSettings>
|
|
||||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
|
||||||
</AndroidXmlCodeStyleSettings>
|
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
<value>
|
<value>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<w>msisdn</w>
|
<w>msisdn</w>
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
|
<w>riotx</w>
|
||||||
<w>signin</w>
|
<w>signin</w>
|
||||||
<w>signout</w>
|
<w>signout</w>
|
||||||
<w>signup</w>
|
<w>signup</w>
|
||||||
|
|
|
@ -49,12 +49,12 @@ script:
|
||||||
# Build Android test (assembleAndroidTest) (disabled for now)
|
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||||
# Code quality (lintGplayRelease lintFdroidRelease)
|
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||||
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||||
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
# Done by Buildkite now: - ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||||
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
# Done by Buildkite now: - ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||||
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
||||||
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||||
# Other code quality check
|
# Other code quality check
|
||||||
- ./tools/check/check_code_quality.sh
|
# Done by Buildkite now: - ./tools/check/check_code_quality.sh
|
||||||
- ./tools/travis/check_pr.sh
|
- ./tools/travis/check_pr.sh
|
||||||
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
||||||
- diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
# Done by Buildkite now: - diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
||||||
|
|
28
CHANGES.md
28
CHANGES.md
|
@ -1,3 +1,31 @@
|
||||||
|
Changes in RiotX 0.18.0 (2020-03-11)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Share image and other media from e2e rooms (#677)
|
||||||
|
- Add support for `/plain` command (#12)
|
||||||
|
- Detect spaces in password if user fail to login (#1038)
|
||||||
|
- FTUE: do not display a different color when encrypting message when not in developer mode.
|
||||||
|
- Open room member profile from avatar of the room member state event (#935)
|
||||||
|
- Restore the push rules configuration in the settings
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix crash on attachment preview screen (#1088)
|
||||||
|
- "Share" option is not appearing in encrypted rooms for images (#1031)
|
||||||
|
- Set "image/jpeg" as MIME type of images instead of "image/jpg" (#1075)
|
||||||
|
- Self verification via QR code is failing (#1130)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- PushRuleService.getPushRules() now returns a RuleSet. Use getAllRules() on this object to get all the rules.
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Upgrade ktlint to version 0.36.0
|
||||||
|
- Pipeline file for Buildkite is now hosted on another Github repository: https://github.com/matrix-org/pipelines/blob/master/riotx-android/pipeline.yml
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Restore availability to Chromebooks (#932)
|
||||||
|
- Add a [documentation](./docs/integration_tests.md) to run integration tests
|
||||||
|
|
||||||
Changes in RiotX 0.17.0 (2020-02-27)
|
Changes in RiotX 0.17.0 (2020-02-27)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,8 @@ Make sure the following commands execute without any error:
|
||||||
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
||||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||||
|
|
||||||
|
You should consider adding Unit tests with your PR, and also integration tests (AndroidTest). Please refer to [this document](./docs/integration_tests.md) to install and run the integration test environment.
|
||||||
|
|
||||||
### Internationalisation
|
### Internationalisation
|
||||||
|
|
||||||
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||||
|
|
97
docs/integration_tests.md
Normal file
97
docs/integration_tests.md
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# Integration tests
|
||||||
|
|
||||||
|
Integration tests are useful to ensure that the code works well for any use cases.
|
||||||
|
|
||||||
|
They can also be used as sample on how to use the Matrix SDK.
|
||||||
|
|
||||||
|
In a ideal world, every API of the SDK should be covered by integration tests. For the moment, we have test mainly for the Crypto part, which is the tricky part. But it covers quite a lot of features: accounts creation, login to existing account, send encrypted messages, keys backup, verification, etc.
|
||||||
|
|
||||||
|
The Matrix SDK is able to open multiple sessions, for the same user, of for different users. This way we can test communication between several sessions on a single device.
|
||||||
|
|
||||||
|
## Pre requirements
|
||||||
|
|
||||||
|
Integration tests need a homeserver running on localhost.
|
||||||
|
|
||||||
|
The documentation describes what we do to have one, using [Synapse](https://github.com/matrix-org/synapse/), which is the Matrix reference homeserver.
|
||||||
|
|
||||||
|
## Install and run Synapse
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
- Install virtualenv
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pip install virtualenv
|
||||||
|
```
|
||||||
|
|
||||||
|
- Clone Synapse repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone -b develop https://github.com/matrix-org/synapse.git
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```bash
|
||||||
|
git clone -b develop git@github.com:matrix-org/synapse.git
|
||||||
|
```
|
||||||
|
|
||||||
|
You should have the develop branch cloned by default.
|
||||||
|
|
||||||
|
- Run synapse, from the Synapse folder you just cloned
|
||||||
|
|
||||||
|
```bash
|
||||||
|
virtualenv -p python3 env
|
||||||
|
source env/bin/activate
|
||||||
|
pip install -e .
|
||||||
|
demo/start.sh --no-rate-limit
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, to install the latest Synapse release package (and not a cloned branch) you can run the following instead of `pip install -e .`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install matrix-synapse
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now have 3 running federated Synapse instances 🎉, at http://127.0.0.1:8080/, http://127.0.0.1:8081/ and http://127.0.0.1:8082/, which should display a "It Works! Synapse is running" message.
|
||||||
|
|
||||||
|
## Run the test
|
||||||
|
|
||||||
|
It's recommended to run tests using an Android Emulator and not a real device. First reason for that is that the tests will use http://10.0.2.2:8080 to connect to Synapse, which run locally on your machine.
|
||||||
|
|
||||||
|
You can run all the tests in the `androidTest` folders.
|
||||||
|
|
||||||
|
## Stop Synapse
|
||||||
|
|
||||||
|
To stop Synapse, you can run the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./demo/stop.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can deactivate the virtualenv:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deactivate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshoot
|
||||||
|
|
||||||
|
You'll need python3 to be able to run synapse
|
||||||
|
|
||||||
|
### Android Emulator does cannot reach the homeserver
|
||||||
|
|
||||||
|
Try on the Emulator browser to open "http://10.0.2.2:8080". You should see the "Synapse is running" message.
|
||||||
|
|
||||||
|
### virtualenv command fails
|
||||||
|
|
||||||
|
You can try using
|
||||||
|
```bash
|
||||||
|
python3 -m venv env
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```bash
|
||||||
|
python3 -m virtualenv env
|
||||||
|
```
|
||||||
|
instead of
|
||||||
|
```bash
|
||||||
|
virtualenv -p python3 env
|
||||||
|
```
|
|
@ -126,7 +126,7 @@ dependencies {
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0"
|
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.amshove.kluent.shouldBeTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
class ExtensionsKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testComparingBase64StringWithOrWithoutPadding() {
|
||||||
|
// Without padding
|
||||||
|
"NMJyumnhMic".fromBase64().contentEquals("NMJyumnhMic".fromBase64()).shouldBeTrue()
|
||||||
|
// With padding
|
||||||
|
"NMJyumnhMic".fromBase64().contentEquals("NMJyumnhMic=".fromBase64()).shouldBeTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBadBase64() {
|
||||||
|
"===".fromBase64Safe().shouldBeNull()
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,6 @@ import im.vector.matrix.android.common.CommonTestHelper
|
||||||
import im.vector.matrix.android.common.CryptoTestHelper
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||||
|
@ -279,7 +278,7 @@ class SASTest : InstrumentedTest {
|
||||||
val startMessage = KeyVerificationStart(
|
val startMessage = KeyVerificationStart(
|
||||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
||||||
method = VerificationMethod.SAS.toValue(),
|
method = VerificationMethod.SAS.toValue(),
|
||||||
transactionID = tid,
|
transactionId = tid,
|
||||||
keyAgreementProtocols = protocols,
|
keyAgreementProtocols = protocols,
|
||||||
hashes = hashes,
|
hashes = hashes,
|
||||||
messageAuthenticationCodes = mac,
|
messageAuthenticationCodes = mac,
|
||||||
|
@ -350,16 +349,16 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
var accepted: KeyVerificationAccept? = null
|
var accepted: ValidVerificationInfoAccept? = null
|
||||||
var startReq: KeyVerificationStart? = null
|
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
val at = tx as SASDefaultVerificationTransaction
|
||||||
accepted = at.accepted as? KeyVerificationAccept
|
accepted = at.accepted
|
||||||
startReq = at.startReq as? KeyVerificationStart
|
startReq = at.startReq
|
||||||
aliceAcceptedLatch.countDown()
|
aliceAcceptedLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,13 +383,13 @@ class SASTest : InstrumentedTest {
|
||||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||||
|
|
||||||
// check that agreement is valid
|
// check that agreement is valid
|
||||||
assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
|
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
||||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol))
|
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
||||||
assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash))
|
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
||||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
|
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
||||||
|
|
||||||
accepted!!.shortAuthenticationStrings?.forEach {
|
accepted!!.shortAuthenticationStrings.forEach {
|
||||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
|
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.common.CommonTestHelper
|
||||||
import im.vector.matrix.android.common.CryptoTestHelper
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.common.TestConstants
|
import im.vector.matrix.android.common.TestConstants
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -17,25 +17,24 @@ package im.vector.matrix.android.api.pushrules
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
|
import im.vector.matrix.android.api.pushrules.rest.RuleSet
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
interface PushRuleService {
|
interface PushRuleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the push rules from the server
|
* Fetch the push rules from the server
|
||||||
*/
|
*/
|
||||||
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
||||||
|
|
||||||
// TODO get push rule set
|
fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet
|
||||||
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
|
|
||||||
|
|
||||||
// TODO update rule
|
|
||||||
|
|
||||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun addPushRuleListener(listener: PushRuleListener)
|
fun addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All push rulesets for a user.
|
* All push rulesets for a user.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class GetPushRulesResponse(
|
internal data class GetPushRulesResponse(
|
||||||
|
@ -27,11 +28,11 @@ internal data class GetPushRulesResponse(
|
||||||
* Global rules, account level applying to all devices
|
* Global rules, account level applying to all devices
|
||||||
*/
|
*/
|
||||||
@Json(name = "global")
|
@Json(name = "global")
|
||||||
val global: Ruleset,
|
val global: RuleSet,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device specific rules, apply only to current device
|
* Device specific rules, apply only to current device
|
||||||
*/
|
*/
|
||||||
@Json(name = "device")
|
@Json(name = "device")
|
||||||
val device: Ruleset? = null
|
val device: RuleSet? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,21 +24,27 @@ import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
|
||||||
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PushCondition(
|
data class PushCondition(
|
||||||
/**
|
/**
|
||||||
* Required. The kind of condition to apply.
|
* Required. The kind of condition to apply.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "kind")
|
||||||
val kind: String,
|
val kind: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "key")
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required for event_match conditions.
|
* Required for event_match conditions.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "pattern")
|
||||||
val pattern: String? = null,
|
val pattern: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +53,8 @@ data class PushCondition(
|
||||||
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
||||||
* If no prefix is present, this parameter defaults to ==.
|
* If no prefix is present, this parameter defaults to ==.
|
||||||
*/
|
*/
|
||||||
@Json(name = "is") val iz: String? = null
|
@Json(name = "is")
|
||||||
|
val iz: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun asExecutableCondition(): Condition? {
|
fun asExecutableCondition(): Condition? {
|
||||||
|
|
|
@ -18,31 +18,158 @@ package im.vector.matrix.android.api.pushrules.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.pushrules.Action
|
||||||
|
import im.vector.matrix.android.api.pushrules.getActions
|
||||||
|
import im.vector.matrix.android.api.pushrules.toJson
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PushRule(
|
data class PushRule(
|
||||||
/**
|
/**
|
||||||
* Required. The actions to perform when this rule is matched.
|
* Required. The actions to perform when this rule is matched.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "actions")
|
||||||
val actions: List<Any>,
|
val actions: List<Any>,
|
||||||
/**
|
/**
|
||||||
* Required. Whether this is a default rule, or has been set explicitly.
|
* Required. Whether this is a default rule, or has been set explicitly.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "default")
|
||||||
val default: Boolean? = false,
|
val default: Boolean? = false,
|
||||||
/**
|
/**
|
||||||
* Required. Whether the push rule is enabled or not.
|
* Required. Whether the push rule is enabled or not.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "enabled")
|
||||||
val enabled: Boolean,
|
val enabled: Boolean,
|
||||||
/**
|
/**
|
||||||
* Required. The ID of this rule.
|
* Required. The ID of this rule.
|
||||||
*/
|
*/
|
||||||
@Json(name = "rule_id") val ruleId: String,
|
@Json(name = "rule_id")
|
||||||
|
val ruleId: String,
|
||||||
/**
|
/**
|
||||||
* The conditions that must hold true for an event in order for a rule to be applied to an event
|
* The conditions that must hold true for an event in order for a rule to be applied to an event
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "conditions")
|
||||||
val conditions: List<PushCondition>? = null,
|
val conditions: List<PushCondition>? = null,
|
||||||
/**
|
/**
|
||||||
* The glob-style pattern to match against. Only applicable to content rules.
|
* The glob-style pattern to match against. Only applicable to content rules.
|
||||||
*/
|
*/
|
||||||
|
@Json(name = "pattern")
|
||||||
val pattern: String? = null
|
val pattern: String? = null
|
||||||
)
|
) {
|
||||||
|
/**
|
||||||
|
* Add the default notification sound.
|
||||||
|
*/
|
||||||
|
fun setNotificationSound(): PushRule {
|
||||||
|
return setNotificationSound(ACTION_VALUE_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNotificationSound(): String? {
|
||||||
|
return (getActions().firstOrNull { it is Action.Sound } as? Action.Sound)?.sound
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the notification sound
|
||||||
|
*
|
||||||
|
* @param sound notification sound
|
||||||
|
*/
|
||||||
|
fun setNotificationSound(sound: String): PushRule {
|
||||||
|
return copy(
|
||||||
|
actions = (getActions().filter { it !is Action.Sound } + Action.Sound(sound)).toJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the notification sound
|
||||||
|
*/
|
||||||
|
fun removeNotificationSound(): PushRule {
|
||||||
|
return copy(
|
||||||
|
actions = getActions().filter { it !is Action.Sound }.toJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the highlight status.
|
||||||
|
*
|
||||||
|
* @param highlight the highlight status
|
||||||
|
*/
|
||||||
|
fun setHighlight(highlight: Boolean): PushRule {
|
||||||
|
return copy(
|
||||||
|
actions = (getActions().filter { it !is Action.Highlight } + Action.Highlight(highlight)).toJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the notification status.
|
||||||
|
*
|
||||||
|
* @param notify true to notify
|
||||||
|
*/
|
||||||
|
fun setNotify(notify: Boolean): PushRule {
|
||||||
|
val mutableActions = actions.toMutableList()
|
||||||
|
|
||||||
|
mutableActions.remove(ACTION_DONT_NOTIFY)
|
||||||
|
mutableActions.remove(ACTION_NOTIFY)
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
mutableActions.add(ACTION_NOTIFY)
|
||||||
|
} else {
|
||||||
|
mutableActions.add(ACTION_DONT_NOTIFY)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy(actions = mutableActions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the rule should highlight the event.
|
||||||
|
*
|
||||||
|
* @return true if the rule should play sound
|
||||||
|
*/
|
||||||
|
fun shouldNotify() = actions.contains(ACTION_NOTIFY)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the rule should not highlight the event.
|
||||||
|
*
|
||||||
|
* @return true if the rule should not play sound
|
||||||
|
*/
|
||||||
|
fun shouldNotNotify() = actions.contains(ACTION_DONT_NOTIFY)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Rule id
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
const val RULE_ID_DISABLE_ALL = ".m.rule.master"
|
||||||
|
const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name"
|
||||||
|
const val RULE_ID_CONTAIN_DISPLAY_NAME = ".m.rule.contains_display_name"
|
||||||
|
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
|
||||||
|
const val RULE_ID_INVITE_ME = ".m.rule.invite_for_me"
|
||||||
|
const val RULE_ID_PEOPLE_JOIN_LEAVE = ".m.rule.member_event"
|
||||||
|
const val RULE_ID_CALL = ".m.rule.call"
|
||||||
|
const val RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS = ".m.rule.suppress_notices"
|
||||||
|
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
|
||||||
|
const val RULE_ID_AT_ROOMS = ".m.rule.roomnotif"
|
||||||
|
const val RULE_ID_TOMBSTONE = ".m.rule.tombstone"
|
||||||
|
const val RULE_ID_E2E_ONE_TO_ONE_ROOM = ".m.rule.encrypted_room_one_to_one"
|
||||||
|
const val RULE_ID_E2E_GROUP = ".m.rule.encrypted"
|
||||||
|
const val RULE_ID_REACTION = ".m.rule.reaction"
|
||||||
|
const val RULE_ID_FALLBACK = ".m.rule.fallback"
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Actions
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
const val ACTION_NOTIFY = "notify"
|
||||||
|
const val ACTION_DONT_NOTIFY = "dont_notify"
|
||||||
|
const val ACTION_COALESCE = "coalesce"
|
||||||
|
|
||||||
|
const val ACTION_SET_TWEAK_SOUND_VALUE = "sound"
|
||||||
|
const val ACTION_SET_TWEAK_HIGHLIGHT_VALUE = "highlight"
|
||||||
|
|
||||||
|
const val ACTION_PARAMETER_SET_TWEAK = "set_tweak"
|
||||||
|
const val ACTION_PARAMETER_VALUE = "value"
|
||||||
|
|
||||||
|
const val ACTION_VALUE_DEFAULT = "default"
|
||||||
|
const val ACTION_VALUE_RING = "ring"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.pushrules.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RuleSet(
|
||||||
|
@Json(name = "content")
|
||||||
|
val content: List<PushRule>? = null,
|
||||||
|
@Json(name = "override")
|
||||||
|
val override: List<PushRule>? = null,
|
||||||
|
@Json(name = "room")
|
||||||
|
val room: List<PushRule>? = null,
|
||||||
|
@Json(name = "sender")
|
||||||
|
val sender: List<PushRule>? = null,
|
||||||
|
@Json(name = "underride")
|
||||||
|
val underride: List<PushRule>? = null
|
||||||
|
) {
|
||||||
|
fun getAllRules(): List<PushRule> {
|
||||||
|
// Ref. for the order: https://matrix.org/docs/spec/client_server/latest#push-rules
|
||||||
|
return override.orEmpty() + content.orEmpty() + room.orEmpty() + sender.orEmpty() + underride.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a rule from its ruleID.
|
||||||
|
*
|
||||||
|
* @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 (PushRule.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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a rule from its rule Id.
|
||||||
|
*
|
||||||
|
* @param rules the rules list.
|
||||||
|
* @param ruleId the rule Id.
|
||||||
|
* @return the bing rule if it exists, else null.
|
||||||
|
*/
|
||||||
|
private fun findRule(rules: List<PushRule>?, ruleId: String): PushRule? {
|
||||||
|
return rules?.firstOrNull { it.ruleId == ruleId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PushRuleAndKind(
|
||||||
|
val pushRule: PushRule,
|
||||||
|
val kind: RuleSetKey
|
||||||
|
)
|
|
@ -31,7 +31,7 @@ data class ContentAttachmentData(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val queryUri: String,
|
val queryUri: String,
|
||||||
val path: String,
|
val path: String,
|
||||||
val mimeType: String?,
|
private val mimeType: String?,
|
||||||
val type: Type
|
val type: Type
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
|
@ -41,4 +41,6 @@ data class ContentAttachmentData(
|
||||||
AUDIO,
|
AUDIO,
|
||||||
VIDEO
|
VIDEO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,9 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.extensions.orFalse
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
|
@ -24,17 +23,16 @@ import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores current pending verification requests
|
* Stores current pending verification requests
|
||||||
* TODO We should not expose this whole object to the app. Create an interface
|
|
||||||
*/
|
*/
|
||||||
data class PendingVerificationRequest(
|
data class PendingVerificationRequest(
|
||||||
val ageLocalTs: Long,
|
val ageLocalTs: Long,
|
||||||
val isIncoming: Boolean = false,
|
val isIncoming: Boolean = false,
|
||||||
val localID: String = UUID.randomUUID().toString(),
|
val localId: String = UUID.randomUUID().toString(),
|
||||||
val otherUserId: String,
|
val otherUserId: String,
|
||||||
val roomId: String?,
|
val roomId: String?,
|
||||||
val transactionId: String? = null,
|
val transactionId: String? = null,
|
||||||
val requestInfo: VerificationInfoRequest? = null,
|
val requestInfo: ValidVerificationInfoRequest? = null,
|
||||||
val readyInfo: VerificationInfoReady? = null,
|
val readyInfo: ValidVerificationInfoReady? = null,
|
||||||
val cancelConclusion: CancelCode? = null,
|
val cancelConclusion: CancelCode? = null,
|
||||||
val isSuccessful: Boolean = false,
|
val isSuccessful: Boolean = false,
|
||||||
val handledByOtherSession: Boolean = false,
|
val handledByOtherSession: Boolean = false,
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -13,15 +13,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.api.pushrules.rest
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonClass
|
package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
data class ValidVerificationInfoReady(
|
||||||
internal data class Ruleset(
|
val transactionId: String,
|
||||||
val content: List<PushRule>? = null,
|
val fromDevice: String,
|
||||||
val override: List<PushRule>? = null,
|
val methods: List<String>
|
||||||
val room: List<PushRule>? = null,
|
|
||||||
val sender: List<PushRule>? = null,
|
|
||||||
val underride: List<PushRule>? = null
|
|
||||||
)
|
)
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,16 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.crypto.verification
|
package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
data class ValidVerificationInfoRequest(
|
||||||
|
val transactionId: String,
|
||||||
val supportedVerificationMethods =
|
val fromDevice: String,
|
||||||
listOf(
|
val methods: List<String>,
|
||||||
// RiotX supports SAS verification
|
val timestamp: Long?
|
||||||
VerificationMethod.SAS,
|
)
|
||||||
// RiotX is able to show QR codes
|
|
||||||
VerificationMethod.QR_CODE_SHOW,
|
|
||||||
// RiotX is able to scan QR codes
|
|
||||||
VerificationMethod.QR_CODE_SCAN
|
|
||||||
)
|
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.api.session.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
||||||
|
|
|
@ -34,7 +34,11 @@ interface FileService {
|
||||||
/**
|
/**
|
||||||
* Download file in cache
|
* Download file in cache
|
||||||
*/
|
*/
|
||||||
FOR_INTERNAL_USE
|
FOR_INTERNAL_USE,
|
||||||
|
/**
|
||||||
|
* Download file in file provider path
|
||||||
|
*/
|
||||||
|
FOR_EXTERNAL_SHARE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -51,4 +51,4 @@ data class MessageAudioContent(
|
||||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||||
*/
|
*/
|
||||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||||
) : MessageEncryptedContent
|
) : MessageWithAttachmentContent
|
||||||
|
|
|
@ -57,7 +57,7 @@ data class MessageFileContent(
|
||||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||||
*/
|
*/
|
||||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||||
) : MessageEncryptedContent {
|
) : MessageWithAttachmentContent {
|
||||||
|
|
||||||
fun getMimeType(): String {
|
fun getMimeType(): String {
|
||||||
// Mimetype default to plain text, should not be used
|
// Mimetype default to plain text, should not be used
|
||||||
|
|
|
@ -20,6 +20,6 @@ package im.vector.matrix.android.api.session.room.model.message
|
||||||
/**
|
/**
|
||||||
* A content with image information
|
* A content with image information
|
||||||
*/
|
*/
|
||||||
interface MessageImageInfoContent : MessageEncryptedContent {
|
interface MessageImageInfoContent : MessageWithAttachmentContent {
|
||||||
val info: ImageInfo?
|
val info: ImageInfo?
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class MessageVerificationAcceptContent(
|
internal data class MessageVerificationAcceptContent(
|
||||||
|
@ -34,22 +33,9 @@ internal data class MessageVerificationAcceptContent(
|
||||||
@Json(name = "commitment") override var commitment: String? = null
|
@Json(name = "commitment") override var commitment: String? = null
|
||||||
) : VerificationInfoAccept {
|
) : VerificationInfoAccept {
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank()
|
|
||||||
|| keyAgreementProtocol.isNullOrBlank()
|
|
||||||
|| hash.isNullOrBlank()
|
|
||||||
|| commitment.isNullOrBlank()
|
|
||||||
|| messageAuthenticationCode.isNullOrBlank()
|
|
||||||
|| shortAuthenticationStrings.isNullOrEmpty()) {
|
|
||||||
Timber.e("## received invalid verification request")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
companion object : VerificationInfoAcceptFactory {
|
companion object : VerificationInfoAcceptFactory {
|
||||||
|
|
|
@ -28,21 +28,13 @@ data class MessageVerificationCancelContent(
|
||||||
@Json(name = "code") override val code: String? = null,
|
@Json(name = "code") override val code: String? = null,
|
||||||
@Json(name = "reason") override val reason: String? = null,
|
@Json(name = "reason") override val reason: String? = null,
|
||||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
|
||||||
) : VerificationInfoCancel {
|
) : VerificationInfoCancel {
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent {
|
fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent {
|
||||||
return MessageVerificationCancelContent(
|
return MessageVerificationCancelContent(
|
||||||
|
|
|
@ -25,12 +25,22 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class MessageVerificationDoneContent(
|
internal data class MessageVerificationDoneContent(
|
||||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
) : VerificationInfo {
|
) : VerificationInfo<ValidVerificationDone> {
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
override fun isValid() = transactionID?.isNotEmpty() == true
|
|
||||||
|
|
||||||
override fun toEventContent(): Content? = toContent()
|
override fun toEventContent(): Content? = toContent()
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationDone? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationDone(
|
||||||
|
validTransactionId
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class ValidVerificationDone(
|
||||||
|
val transactionId: String
|
||||||
|
)
|
||||||
|
|
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class MessageVerificationKeyContent(
|
internal data class MessageVerificationKeyContent(
|
||||||
|
@ -33,17 +32,9 @@ internal data class MessageVerificationKeyContent(
|
||||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
) : VerificationInfoKey {
|
) : VerificationInfoKey {
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
|
||||||
Timber.e("## received invalid verification request")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
companion object : VerificationInfoKeyFactory {
|
companion object : VerificationInfoKeyFactory {
|
||||||
|
|
|
@ -30,18 +30,11 @@ internal data class MessageVerificationMacContent(
|
||||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
) : VerificationInfoMac {
|
) : VerificationInfoMac {
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : VerificationInfoMacFactory {
|
companion object : VerificationInfoMacFactory {
|
||||||
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
|
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
|
||||||
return MessageVerificationMacContent(
|
return MessageVerificationMacContent(
|
||||||
|
|
|
@ -30,18 +30,11 @@ internal data class MessageVerificationReadyContent(
|
||||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
) : VerificationInfoReady {
|
) : VerificationInfoReady {
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : MessageVerificationReadyFactory {
|
companion object : MessageVerificationReadyFactory {
|
||||||
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
|
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
|
||||||
return MessageVerificationReadyContent(
|
return MessageVerificationReadyContent(
|
||||||
|
|
|
@ -33,18 +33,10 @@ data class MessageVerificationRequestContent(
|
||||||
@Json(name = "format") val format: String? = null,
|
@Json(name = "format") val format: String? = null,
|
||||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||||
|
// Not parsed, but set after, using the eventId
|
||||||
|
override val transactionId: String? = null
|
||||||
) : MessageContent, VerificationInfoRequest {
|
) : MessageContent, VerificationInfoRequest {
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override val transactionID: String?
|
|
||||||
get() = relatesTo?.eventId
|
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,10 @@ package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
|
||||||
import im.vector.matrix.android.api.session.events.model.toContent
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
|
||||||
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class MessageVerificationStartContent(
|
internal data class MessageVerificationStartContent(
|
||||||
|
@ -39,46 +34,12 @@ internal data class MessageVerificationStartContent(
|
||||||
@Json(name = "secret") override val sharedSecret: String?
|
@Json(name = "secret") override val sharedSecret: String?
|
||||||
) : VerificationInfoStart {
|
) : VerificationInfoStart {
|
||||||
|
|
||||||
override fun toCanonicalJson(): String? {
|
override fun toCanonicalJson(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this)
|
return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val transactionID: String?
|
override val transactionId: String?
|
||||||
get() = relatesTo?.eventId
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
// TODO Move those method to the interface?
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank()
|
|
||||||
|| fromDevice.isNullOrBlank()
|
|
||||||
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
|
||||||
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
|
||||||
Timber.e("## received invalid verification request")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isValidSas(): Boolean {
|
|
||||||
if (keyAgreementProtocols.isNullOrEmpty()
|
|
||||||
|| hashes.isNullOrEmpty()
|
|
||||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
|
||||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
|
||||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
|
||||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
|
||||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isValidReciprocate(): Boolean {
|
|
||||||
if (sharedSecret.isNullOrBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,4 +51,4 @@ data class MessageVideoContent(
|
||||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||||
*/
|
*/
|
||||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||||
) : MessageEncryptedContent
|
) : MessageWithAttachmentContent
|
||||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
/**
|
/**
|
||||||
* Interface for message which can contains an encrypted file
|
* Interface for message which can contains an encrypted file
|
||||||
*/
|
*/
|
||||||
interface MessageEncryptedContent : MessageContent {
|
interface MessageWithAttachmentContent : MessageContent {
|
||||||
/**
|
/**
|
||||||
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
* Required if the file is unencrypted. The URL (typically MXC URI) to the image.
|
||||||
*/
|
*/
|
||||||
|
@ -36,4 +36,4 @@ interface MessageEncryptedContent : MessageContent {
|
||||||
/**
|
/**
|
||||||
* Get the url of the encrypted file or of the file
|
* Get the url of the encrypted file or of the file
|
||||||
*/
|
*/
|
||||||
fun MessageEncryptedContent.getFileUrl() = encryptedFileInfo?.url ?: url
|
fun MessageWithAttachmentContent.getFileUrl() = encryptedFileInfo?.url ?: url
|
|
@ -28,5 +28,7 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
|
||||||
object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
|
object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
|
||||||
object ParsingError : SharedSecretStorageError("parsing Error")
|
object ParsingError : SharedSecretStorageError("parsing Error")
|
||||||
object BadMac : SharedSecretStorageError("Bad mac")
|
object BadMac : SharedSecretStorageError("Bad mac")
|
||||||
|
object BadCipherText : SharedSecretStorageError("Bad cipher text")
|
||||||
|
|
||||||
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
|
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -627,9 +627,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
return runBlocking {
|
return internalDecryptEvent(event, timeline)
|
||||||
internalDecryptEvent(event, timeline)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -657,7 +655,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the MXEventDecryptionResult data, or null in case of error
|
* @return the MXEventDecryptionResult data, or null in case of error
|
||||||
*/
|
*/
|
||||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
val eventContent = event.content
|
val eventContent = event.content
|
||||||
if (eventContent == null) {
|
if (eventContent == null) {
|
||||||
Timber.e("## decryptEvent : empty event content")
|
Timber.e("## decryptEvent : empty event content")
|
||||||
|
|
|
@ -34,7 +34,7 @@ internal interface IMXDecrypting {
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the decryption information, or an error
|
* @return the decryption information, or an error
|
||||||
*/
|
*/
|
||||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a key event.
|
* Handle a key event.
|
||||||
|
|
|
@ -63,7 +63,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||||
*/
|
*/
|
||||||
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
||||||
|
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
// If cross signing is enabled, we don't send request until the keys are trusted
|
// If cross signing is enabled, we don't send request until the keys are trusted
|
||||||
// There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
|
// There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
|
||||||
val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
|
val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
|
||||||
|
|
|
@ -38,7 +38,7 @@ internal class MXOlmDecryption(
|
||||||
private val userId: String)
|
private val userId: String)
|
||||||
: IMXDecrypting {
|
: IMXDecrypting {
|
||||||
|
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
||||||
Timber.e("## decryptEvent() : bad event format")
|
Timber.e("## decryptEvent() : bad event format")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||||
|
|
|
@ -80,7 +80,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
|
|
||||||
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo ->
|
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo ->
|
||||||
privateKeysInfo.master
|
privateKeysInfo.master
|
||||||
?.fromBase64NoPadding()
|
?.fromBase64()
|
||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
|
@ -93,7 +93,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
privateKeysInfo.user
|
privateKeysInfo.user
|
||||||
?.fromBase64NoPadding()
|
?.fromBase64()
|
||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
|
@ -106,7 +106,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
privateKeysInfo.selfSigned
|
privateKeysInfo.selfSigned
|
||||||
?.fromBase64NoPadding()
|
?.fromBase64()
|
||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
|
@ -307,7 +307,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
var userKeyIsTrusted = false
|
var userKeyIsTrusted = false
|
||||||
var selfSignedKeyIsTrusted = false
|
var selfSignedKeyIsTrusted = false
|
||||||
|
|
||||||
masterKeyPrivateKey?.fromBase64NoPadding()
|
masterKeyPrivateKey?.fromBase64()
|
||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
|
@ -324,7 +324,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uskKeyPrivateKey?.fromBase64NoPadding()
|
uskKeyPrivateKey?.fromBase64()
|
||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
|
@ -341,7 +341,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sskPrivateKey?.fromBase64NoPadding()
|
sskPrivateKey?.fromBase64()
|
||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
|
@ -450,7 +450,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
// 1) check if I know the private key
|
// 1) check if I know the private key
|
||||||
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()
|
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()
|
||||||
?.master
|
?.master
|
||||||
?.fromBase64NoPadding()
|
?.fromBase64()
|
||||||
|
|
||||||
var isMaterKeyTrusted = false
|
var isMaterKeyTrusted = false
|
||||||
if (myMasterKey.trustLevel?.locallyVerified == true) {
|
if (myMasterKey.trustLevel?.locallyVerified == true) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import android.util.Base64
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
fun CryptoDeviceInfo.canonicalSignable(): String {
|
fun CryptoDeviceInfo.canonicalSignable(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
|
@ -32,6 +33,18 @@ fun ByteArray.toBase64NoPadding(): String {
|
||||||
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.fromBase64NoPadding(): ByteArray {
|
fun String.fromBase64(): ByteArray {
|
||||||
return Base64.decode(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
return Base64.decode(this, Base64.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source
|
||||||
|
*/
|
||||||
|
fun String.fromBase64Safe(): ByteArray? {
|
||||||
|
return try {
|
||||||
|
Base64.decode(this, Base64.DEFAULT)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.e(throwable, "Unable to decode base64 string")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,21 +19,19 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class KeyVerificationAccept(
|
internal data class KeyVerificationAccept(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* string to identify the transaction.
|
* string to identify the transaction.
|
||||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
||||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id")
|
||||||
override val transactionID: String? = null,
|
override val transactionId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||||
|
@ -67,19 +65,6 @@ internal data class KeyVerificationAccept(
|
||||||
override var commitment: String? = null
|
override var commitment: String? = null
|
||||||
) : SendToDeviceObject, VerificationInfoAccept {
|
) : SendToDeviceObject, VerificationInfoAccept {
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank()
|
|
||||||
|| keyAgreementProtocol.isNullOrBlank()
|
|
||||||
|| hash.isNullOrBlank()
|
|
||||||
|| commitment.isNullOrBlank()
|
|
||||||
|| messageAuthenticationCode.isNullOrBlank()
|
|
||||||
|| shortAuthenticationStrings.isNullOrEmpty()) {
|
|
||||||
Timber.e("## received invalid verification request")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
companion object : VerificationInfoAcceptFactory {
|
companion object : VerificationInfoAcceptFactory {
|
||||||
|
@ -90,7 +75,7 @@ internal data class KeyVerificationAccept(
|
||||||
messageAuthenticationCode: String,
|
messageAuthenticationCode: String,
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
|
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
|
||||||
return KeyVerificationAccept(
|
return KeyVerificationAccept(
|
||||||
transactionID = tid,
|
transactionId = tid,
|
||||||
keyAgreementProtocol = keyAgreementProtocol,
|
keyAgreementProtocol = keyAgreementProtocol,
|
||||||
hash = hash,
|
hash = hash,
|
||||||
commitment = commitment,
|
commitment = commitment,
|
||||||
|
|
|
@ -29,7 +29,7 @@ internal data class KeyVerificationCancel(
|
||||||
* the transaction ID of the verification to cancel
|
* the transaction ID of the verification to cancel
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id")
|
||||||
override val transactionID: String? = null,
|
override val transactionId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* machine-readable reason for cancelling, see #CancelCode
|
* machine-readable reason for cancelling, see #CancelCode
|
||||||
|
@ -53,11 +53,4 @@ internal data class KeyVerificationCancel(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,8 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDon
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class KeyVerificationDone(
|
internal data class KeyVerificationDone(
|
||||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||||
) : SendToDeviceObject, VerificationInfoDone {
|
) : SendToDeviceObject, VerificationInfoDone {
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ internal data class KeyVerificationKey(
|
||||||
/**
|
/**
|
||||||
* the ID of the transaction that the message is part of
|
* the ID of the transaction that the message is part of
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The device’s ephemeral public key, as an unpadded base64 string
|
* The device’s ephemeral public key, as an unpadded base64 string
|
||||||
|
@ -44,11 +44,4 @@ internal data class KeyVerificationKey(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,19 +25,12 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class KeyVerificationMac(
|
internal data class KeyVerificationMac(
|
||||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||||
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
||||||
@Json(name = "keys") override val keys: String? = null
|
@Json(name = "keys") override val keys: String? = null
|
||||||
|
|
||||||
) : SendToDeviceObject, VerificationInfoMac {
|
) : SendToDeviceObject, VerificationInfoMac {
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
||||||
|
|
||||||
companion object : VerificationInfoMacFactory {
|
companion object : VerificationInfoMacFactory {
|
||||||
|
|
|
@ -26,12 +26,8 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRea
|
||||||
internal data class KeyVerificationReady(
|
internal data class KeyVerificationReady(
|
||||||
@Json(name = "from_device") override val fromDevice: String?,
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
@Json(name = "methods") override val methods: List<String>?,
|
@Json(name = "methods") override val methods: List<String>?,
|
||||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||||
) : SendToDeviceObject, VerificationInfoReady {
|
) : SendToDeviceObject, VerificationInfoReady {
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
return !transactionID.isNullOrBlank() && !fromDevice.isNullOrBlank() && !methods.isNullOrEmpty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,8 @@ internal data class KeyVerificationRequest(
|
||||||
@Json(name = "from_device") override val fromDevice: String?,
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
@Json(name = "methods") override val methods: List<String>,
|
@Json(name = "methods") override val methods: List<String>,
|
||||||
@Json(name = "timestamp") override val timestamp: Long?,
|
@Json(name = "timestamp") override val timestamp: Long?,
|
||||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||||
|
|
||||||
) : SendToDeviceObject, VerificationInfoRequest {
|
) : SendToDeviceObject, VerificationInfoRequest {
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,8 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
|
||||||
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by Alice to initiate an interactive key verification.
|
* Sent by Alice to initiate an interactive key verification.
|
||||||
|
@ -29,8 +26,8 @@ import timber.log.Timber
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class KeyVerificationStart(
|
internal data class KeyVerificationStart(
|
||||||
@Json(name = "from_device") override val fromDevice: String? = null,
|
@Json(name = "from_device") override val fromDevice: String? = null,
|
||||||
override val method: String? = null,
|
@Json(name = "method") override val method: String? = null,
|
||||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||||
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
|
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
|
||||||
@Json(name = "hashes") override val hashes: List<String>? = null,
|
@Json(name = "hashes") override val hashes: List<String>? = null,
|
||||||
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>? = null,
|
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>? = null,
|
||||||
|
@ -39,43 +36,9 @@ internal data class KeyVerificationStart(
|
||||||
@Json(name = "secret") override val sharedSecret: String? = null
|
@Json(name = "secret") override val sharedSecret: String? = null
|
||||||
) : SendToDeviceObject, VerificationInfoStart {
|
) : SendToDeviceObject, VerificationInfoStart {
|
||||||
|
|
||||||
override fun toCanonicalJson(): String? {
|
override fun toCanonicalJson(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
|
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Move those method to the interface?
|
|
||||||
override fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank()
|
|
||||||
|| fromDevice.isNullOrBlank()
|
|
||||||
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
|
||||||
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
|
||||||
Timber.e("## received invalid verification request")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isValidSas(): Boolean {
|
|
||||||
if (keyAgreementProtocols.isNullOrEmpty()
|
|
||||||
|| hashes.isNullOrEmpty()
|
|
||||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
|
||||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
|
||||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
|
||||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
|
||||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isValidReciprocate(): Boolean {
|
|
||||||
if (sharedSecret.isNullOrBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
|
||||||
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
|
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
|
||||||
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
|
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
|
||||||
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
|
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding
|
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
|
import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||||
|
@ -268,7 +268,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||||
val ivParameterSpec = IvParameterSpec(iv)
|
val ivParameterSpec = IvParameterSpec(iv)
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||||
// secret are not that big, just do Final
|
// secret are not that big, just do Final
|
||||||
val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64NoPadding())
|
val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64())
|
||||||
require(cipherBytes.isNotEmpty())
|
require(cipherBytes.isNotEmpty())
|
||||||
|
|
||||||
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
|
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
|
||||||
|
@ -295,9 +295,9 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||||
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
||||||
val macKey = pseudoRandomKey.copyOfRange(32, 64)
|
val macKey = pseudoRandomKey.copyOfRange(32, 64)
|
||||||
|
|
||||||
val iv = cipherContent.initializationVector?.fromBase64NoPadding() ?: ByteArray(16)
|
val iv = cipherContent.initializationVector?.fromBase64() ?: ByteArray(16)
|
||||||
|
|
||||||
val cipherRawBytes = cipherContent.ciphertext!!.fromBase64NoPadding()
|
val cipherRawBytes = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText
|
||||||
|
|
||||||
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
|
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||||
val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
|
val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
|
||||||
val digest = mac.doFinal(cipherRawBytes)
|
val digest = mac.doFinal(cipherRawBytes)
|
||||||
|
|
||||||
if (!cipherContent.mac?.fromBase64NoPadding()?.contentEquals(digest).orFalse()) {
|
if (!cipherContent.mac?.fromBase64()?.contentEquals(digest).orFalse()) {
|
||||||
throw SharedSecretStorageError.BadMac
|
throw SharedSecretStorageError.BadMac
|
||||||
} else {
|
} else {
|
||||||
// we are good
|
// we are good
|
||||||
|
|
|
@ -41,22 +41,22 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||||
|
|
||||||
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
||||||
val event = handleEncryption(params)
|
val event = handleEncryption(params)
|
||||||
val localID = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localEchoUpdater.updateSendState(localID, SendState.SENDING)
|
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
||||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localID,
|
localId,
|
||||||
roomId = event.roomId ?: "",
|
roomId = event.roomId ?: "",
|
||||||
content = event.content,
|
content = event.content,
|
||||||
eventType = event.type
|
eventType = event.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
localEchoUpdater.updateSendState(localID, SendState.SENT)
|
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
||||||
return executeRequest.eventId
|
return executeRequest.eventId
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED)
|
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVerificationStart(startReq: VerificationInfoStart) {
|
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
|
||||||
Timber.v("## SAS I: received verification request from state $state")
|
Timber.v("## SAS I: received verification request from state $state")
|
||||||
if (state != VerificationTxState.None) {
|
if (state != VerificationTxState.None) {
|
||||||
Timber.e("## SAS I: received verification request from invalid state")
|
Timber.e("## SAS I: received verification request from invalid state")
|
||||||
|
@ -100,10 +100,10 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||||
|
|
||||||
// Select a key agreement protocol, a hash algorithm, a message authentication code,
|
// Select a key agreement protocol, a hash algorithm, a message authentication code,
|
||||||
// and short authentication string methods out of the lists given in requester's message.
|
// and short authentication string methods out of the lists given in requester's message.
|
||||||
val agreedProtocol = startReq!!.keyAgreementProtocols?.firstOrNull { KNOWN_AGREEMENT_PROTOCOLS.contains(it) }
|
val agreedProtocol = startReq!!.keyAgreementProtocols.firstOrNull { KNOWN_AGREEMENT_PROTOCOLS.contains(it) }
|
||||||
val agreedHash = startReq!!.hashes?.firstOrNull { KNOWN_HASHES.contains(it) }
|
val agreedHash = startReq!!.hashes.firstOrNull { KNOWN_HASHES.contains(it) }
|
||||||
val agreedMac = startReq!!.messageAuthenticationCodes?.firstOrNull { KNOWN_MACS.contains(it) }
|
val agreedMac = startReq!!.messageAuthenticationCodes.firstOrNull { KNOWN_MACS.contains(it) }
|
||||||
val agreedShortCode = startReq!!.shortAuthenticationStrings?.filter { KNOWN_SHORT_CODES.contains(it) }
|
val agreedShortCode = startReq!!.shortAuthenticationStrings.filter { KNOWN_SHORT_CODES.contains(it) }
|
||||||
|
|
||||||
// No common key sharing/hashing/hmac/SAS methods.
|
// No common key sharing/hashing/hmac/SAS methods.
|
||||||
// If a device is unable to complete the verification because the devices are unable to find a common key sharing,
|
// If a device is unable to complete the verification because the devices are unable to find a common key sharing,
|
||||||
|
@ -141,12 +141,12 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doAccept(accept: VerificationInfoAccept) {
|
private fun doAccept(accept: VerificationInfoAccept) {
|
||||||
this.accepted = accept
|
this.accepted = accept.asValidObject()
|
||||||
Timber.v("## SAS incoming accept request id:$transactionId")
|
Timber.v("## SAS incoming accept request id:$transactionId")
|
||||||
|
|
||||||
// The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB,
|
// The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB,
|
||||||
// concatenated with the canonical JSON representation of the content of the m.key.verification.start message
|
// concatenated with the canonical JSON representation of the content of the m.key.verification.start message
|
||||||
val concat = getSAS().publicKey + startReq!!.toCanonicalJson()
|
val concat = getSAS().publicKey + startReq!!.canonicalJson
|
||||||
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
|
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||||
// we need to send this to other device now
|
// we need to send this to other device now
|
||||||
state = VerificationTxState.SendingAccept
|
state = VerificationTxState.SendingAccept
|
||||||
|
@ -158,12 +158,12 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVerificationAccept(accept: VerificationInfoAccept) {
|
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
|
||||||
Timber.v("## SAS invalid message for incoming request id:$transactionId")
|
Timber.v("## SAS invalid message for incoming request id:$transactionId")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
|
||||||
Timber.v("## SAS received key for request id:$transactionId")
|
Timber.v("## SAS received key for request id:$transactionId")
|
||||||
if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) {
|
if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) {
|
||||||
Timber.e("## SAS received key from invalid state $state")
|
Timber.e("## SAS received key from invalid state $state")
|
||||||
|
@ -213,7 +213,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||||
state = VerificationTxState.ShortCodeReady
|
state = VerificationTxState.ShortCodeReady
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationMac(vKey: VerificationInfoMac) {
|
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
||||||
Timber.v("## SAS I: received mac for request id:$transactionId")
|
Timber.v("## SAS I: received mac for request id:$transactionId")
|
||||||
// Check for state?
|
// Check for state?
|
||||||
if (state != VerificationTxState.SendingKey
|
if (state != VerificationTxState.SendingKey
|
||||||
|
@ -226,12 +226,13 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
theirMac = vKey
|
|
||||||
|
theirMac = vMac
|
||||||
|
|
||||||
// Do I have my Mac?
|
// Do I have my Mac?
|
||||||
if (myMac != null) {
|
if (myMac != null) {
|
||||||
// I can check
|
// I can check
|
||||||
verifyMacs()
|
verifyMacs(vMac)
|
||||||
}
|
}
|
||||||
// Wait for ShortCode Accepted
|
// Wait for ShortCode Accepted
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVerificationStart(startReq: VerificationInfoStart) {
|
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
|
||||||
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
|
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
KNOWN_SHORT_CODES
|
KNOWN_SHORT_CODES
|
||||||
)
|
)
|
||||||
|
|
||||||
startReq = startMessage
|
startReq = startMessage.asValidObject() as? ValidVerificationInfoStart.SasVerificationInfoStart
|
||||||
state = VerificationTxState.SendingStart
|
state = VerificationTxState.SendingStart
|
||||||
|
|
||||||
sendToOther(
|
sendToOther(
|
||||||
|
@ -118,7 +118,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
||||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||||
// timestamp = System.currentTimeMillis().toInt(),
|
// timestamp = System.currentTimeMillis().toInt(),
|
||||||
// transactionID = transactionId
|
// transactionId = transactionId
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// sendToOther(
|
// sendToOther(
|
||||||
|
@ -130,7 +130,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
|
|
||||||
override fun onVerificationAccept(accept: VerificationInfoAccept) {
|
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
|
||||||
Timber.v("## SAS O: onVerificationAccept id:$transactionId")
|
Timber.v("## SAS O: onVerificationAccept id:$transactionId")
|
||||||
if (state != VerificationTxState.Started) {
|
if (state != VerificationTxState.Started) {
|
||||||
Timber.e("## SAS O: received accept request from invalid state $state")
|
Timber.e("## SAS O: received accept request from invalid state $state")
|
||||||
|
@ -141,7 +141,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
if (!KNOWN_AGREEMENT_PROTOCOLS.contains(accept.keyAgreementProtocol)
|
if (!KNOWN_AGREEMENT_PROTOCOLS.contains(accept.keyAgreementProtocol)
|
||||||
|| !KNOWN_HASHES.contains(accept.hash)
|
|| !KNOWN_HASHES.contains(accept.hash)
|
||||||
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
||||||
|| accept.shortAuthenticationStrings!!.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
|| accept.shortAuthenticationStrings.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
||||||
Timber.e("## SAS O: received accept request from invalid state")
|
Timber.e("## SAS O: received accept request from invalid state")
|
||||||
cancel(CancelCode.UnknownMethod)
|
cancel(CancelCode.UnknownMethod)
|
||||||
return
|
return
|
||||||
|
@ -167,7 +167,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
|
||||||
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
|
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
|
||||||
if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) {
|
if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) {
|
||||||
Timber.e("## received key from invalid state $state")
|
Timber.e("## received key from invalid state $state")
|
||||||
|
@ -182,7 +182,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
// in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
// in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
||||||
|
|
||||||
// check commitment
|
// check commitment
|
||||||
val concat = vKey.key + startReq!!.toCanonicalJson()
|
val concat = vKey.key + startReq!!.canonicalJson
|
||||||
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||||
|
|
||||||
if (accepted!!.commitment.equals(otherCommitment)) {
|
if (accepted!!.commitment.equals(otherCommitment)) {
|
||||||
|
@ -206,7 +206,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationMac(vKey: VerificationInfoMac) {
|
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
||||||
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
||||||
if (state != VerificationTxState.OnKeyReceived
|
if (state != VerificationTxState.OnKeyReceived
|
||||||
&& state != VerificationTxState.ShortCodeReady
|
&& state != VerificationTxState.ShortCodeReady
|
||||||
|
@ -218,12 +218,12 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
theirMac = vKey
|
theirMac = vMac
|
||||||
|
|
||||||
// Do I have my Mac?
|
// Do I have my Mac?
|
||||||
if (myMac != null) {
|
if (myMac != null) {
|
||||||
// I can check
|
// I can check
|
||||||
verifyMacs()
|
verifyMacs(vMac)
|
||||||
}
|
}
|
||||||
// Wait for ShortCode Accepted
|
// Wait for ShortCode Accepted
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,10 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoReady
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||||
|
@ -45,6 +47,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone
|
||||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||||
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
|
@ -73,7 +76,6 @@ import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.internal.toImmutableList
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -105,7 +107,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
* Map [sender: [PendingVerificationRequest]]
|
* Map [sender: [PendingVerificationRequest]]
|
||||||
* For now we keep all requests (even terminated ones) during the lifetime of the app.
|
* For now we keep all requests (even terminated ones) during the lifetime of the app.
|
||||||
*/
|
*/
|
||||||
private val pendingRequests = HashMap<String, ArrayList<PendingVerificationRequest>>()
|
private val pendingRequests = HashMap<String, MutableList<PendingVerificationRequest>>()
|
||||||
|
|
||||||
// Event received from the sync
|
// Event received from the sync
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
|
@ -268,9 +270,9 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRequestReceived(event: Event) {
|
private fun onRequestReceived(event: Event) {
|
||||||
val requestInfo = event.getClearContent().toModel<KeyVerificationRequest>()!!
|
val validRequestInfo = event.getClearContent().toModel<KeyVerificationRequest>()?.asValidObject()
|
||||||
|
|
||||||
if (!requestInfo.isValid()) {
|
if (validRequestInfo == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid key request")
|
Timber.e("## SAS Received invalid key request")
|
||||||
return
|
return
|
||||||
|
@ -278,7 +280,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
val senderId = event.senderId ?: return
|
val senderId = event.senderId ?: return
|
||||||
|
|
||||||
// We don't want to block here
|
// We don't want to block here
|
||||||
val otherDeviceId = requestInfo.fromDevice ?: return
|
val otherDeviceId = validRequestInfo.fromDevice
|
||||||
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||||
|
@ -287,19 +289,16 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember this request
|
// Remember this request
|
||||||
val requestsForUser = pendingRequests[senderId]
|
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||||
?: ArrayList<PendingVerificationRequest>().also {
|
|
||||||
pendingRequests[event.senderId] = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val pendingVerificationRequest = PendingVerificationRequest(
|
val pendingVerificationRequest = PendingVerificationRequest(
|
||||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||||
isIncoming = true,
|
isIncoming = true,
|
||||||
otherUserId = senderId, // requestInfo.toUserId,
|
otherUserId = senderId, // requestInfo.toUserId,
|
||||||
roomId = null,
|
roomId = null,
|
||||||
transactionId = requestInfo.transactionID,
|
transactionId = validRequestInfo.transactionId,
|
||||||
localID = requestInfo.transactionID!!,
|
localId = validRequestInfo.transactionId,
|
||||||
requestInfo = requestInfo
|
requestInfo = validRequestInfo
|
||||||
)
|
)
|
||||||
requestsForUser.add(pendingVerificationRequest)
|
requestsForUser.add(pendingVerificationRequest)
|
||||||
dispatchRequestAdded(pendingVerificationRequest)
|
dispatchRequestAdded(pendingVerificationRequest)
|
||||||
|
@ -307,10 +306,13 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
|
|
||||||
suspend fun onRoomRequestReceived(event: Event) {
|
suspend fun onRoomRequestReceived(event: Event) {
|
||||||
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
|
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
|
||||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
|
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>() ?: return
|
||||||
?: return
|
val validRequestInfo = requestInfo
|
||||||
|
// copy the EventId to the transactionId
|
||||||
|
.copy(transactionId = event.eventId)
|
||||||
|
.asValidObject() ?: return
|
||||||
|
|
||||||
val senderId = event.senderId ?: return
|
val senderId = event.senderId ?: return
|
||||||
val fromDevice = requestInfo.fromDevice ?: return
|
|
||||||
|
|
||||||
if (requestInfo.toUserId != userId) {
|
if (requestInfo.toUserId != userId) {
|
||||||
// I should ignore this, it's not for me
|
// I should ignore this, it's not for me
|
||||||
|
@ -320,16 +322,13 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
|
|
||||||
// We don't want to block here
|
// We don't want to block here
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
if (checkKeysAreDownloaded(senderId, fromDevice) == null) {
|
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
|
||||||
Timber.e("## SAS Verification device $fromDevice is not known")
|
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember this request
|
// Remember this request
|
||||||
val requestsForUser = pendingRequests[senderId]
|
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||||
?: ArrayList<PendingVerificationRequest>().also {
|
|
||||||
pendingRequests[event.senderId] = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val pendingVerificationRequest = PendingVerificationRequest(
|
val pendingVerificationRequest = PendingVerificationRequest(
|
||||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||||
|
@ -337,8 +336,8 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
otherUserId = senderId, // requestInfo.toUserId,
|
otherUserId = senderId, // requestInfo.toUserId,
|
||||||
roomId = event.roomId,
|
roomId = event.roomId,
|
||||||
transactionId = event.eventId,
|
transactionId = event.eventId,
|
||||||
localID = event.eventId!!,
|
localId = event.eventId!!,
|
||||||
requestInfo = requestInfo
|
requestInfo = validRequestInfo
|
||||||
)
|
)
|
||||||
requestsForUser.add(pendingVerificationRequest)
|
requestsForUser.add(pendingVerificationRequest)
|
||||||
dispatchRequestAdded(pendingVerificationRequest)
|
dispatchRequestAdded(pendingVerificationRequest)
|
||||||
|
@ -362,13 +361,15 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val validStartReq = startReq?.asValidObject()
|
||||||
|
|
||||||
val otherUserId = event.senderId
|
val otherUserId = event.senderId
|
||||||
if (startReq?.isValid()?.not() == true) {
|
if (validStartReq == null) {
|
||||||
Timber.e("## received invalid verification request")
|
Timber.e("## received invalid verification request")
|
||||||
if (startReq.transactionID != null) {
|
if (startReq?.transactionId != null) {
|
||||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||||
.cancelTransaction(
|
.cancelTransaction(
|
||||||
startReq.transactionID ?: "",
|
startReq.transactionId ?: "",
|
||||||
otherUserId!!,
|
otherUserId!!,
|
||||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||||
CancelCode.UnknownMethod
|
CancelCode.UnknownMethod
|
||||||
|
@ -377,14 +378,14 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStart(otherUserId, startReq as VerificationInfoStart) {
|
handleStart(otherUserId, validStartReq) {
|
||||||
it.transport = verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
|
it.transport = verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
|
||||||
}?.let {
|
}?.let {
|
||||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||||
.cancelTransaction(
|
.cancelTransaction(
|
||||||
startReq.transactionID ?: "",
|
validStartReq.transactionId,
|
||||||
otherUserId!!,
|
otherUserId!!,
|
||||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
validStartReq.fromDevice,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -392,16 +393,17 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
|
|
||||||
private suspend fun onStartRequestReceived(event: Event) {
|
private suspend fun onStartRequestReceived(event: Event) {
|
||||||
Timber.e("## SAS received Start request ${event.eventId}")
|
Timber.e("## SAS received Start request ${event.eventId}")
|
||||||
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
val startReq = event.getClearContent().toModel<KeyVerificationStart>()
|
||||||
|
val validStartReq = startReq?.asValidObject()
|
||||||
Timber.v("## SAS received Start request $startReq")
|
Timber.v("## SAS received Start request $startReq")
|
||||||
|
|
||||||
val otherUserId = event.senderId
|
val otherUserId = event.senderId!!
|
||||||
if (!startReq.isValid()) {
|
if (validStartReq == null) {
|
||||||
Timber.e("## SAS received invalid verification request")
|
Timber.e("## SAS received invalid verification request")
|
||||||
if (startReq.transactionID != null) {
|
if (startReq?.transactionId != null) {
|
||||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||||
startReq.transactionID,
|
startReq.transactionId,
|
||||||
otherUserId!!,
|
otherUserId,
|
||||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||||
CancelCode.UnknownMethod
|
CancelCode.UnknownMethod
|
||||||
)
|
)
|
||||||
|
@ -409,13 +411,13 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Download device keys prior to everything
|
// Download device keys prior to everything
|
||||||
handleStart(otherUserId, startReq) {
|
handleStart(otherUserId, validStartReq) {
|
||||||
it.transport = verificationTransportToDeviceFactory.createTransport(it)
|
it.transport = verificationTransportToDeviceFactory.createTransport(it)
|
||||||
}?.let {
|
}?.let {
|
||||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||||
startReq.transactionID ?: "",
|
validStartReq.transactionId,
|
||||||
otherUserId!!,
|
otherUserId,
|
||||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
validStartReq.fromDevice,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -424,18 +426,20 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Return a CancelCode to make the caller cancel the verification. Else return null
|
* Return a CancelCode to make the caller cancel the verification. Else return null
|
||||||
*/
|
*/
|
||||||
private suspend fun handleStart(otherUserId: String?, startReq: VerificationInfoStart, txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
|
private suspend fun handleStart(otherUserId: String?,
|
||||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionID}")
|
startReq: ValidVerificationInfoStart,
|
||||||
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice ?: "") != null) {
|
txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
|
||||||
val tid = startReq.transactionID!!
|
Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}")
|
||||||
|
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice) != null) {
|
||||||
|
val tid = startReq.transactionId
|
||||||
val existing = getExistingTransaction(otherUserId, tid)
|
val existing = getExistingTransaction(otherUserId, tid)
|
||||||
|
|
||||||
when (startReq.method) {
|
when (startReq) {
|
||||||
VERIFICATION_METHOD_SAS -> {
|
is ValidVerificationInfoStart.SasVerificationInfoStart -> {
|
||||||
when (existing) {
|
when (existing) {
|
||||||
is SasVerificationTransaction -> {
|
is SasVerificationTransaction -> {
|
||||||
// should cancel both!
|
// should cancel both!
|
||||||
Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionID}")
|
Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionId}")
|
||||||
existing.cancel(CancelCode.UnexpectedMessage)
|
existing.cancel(CancelCode.UnexpectedMessage)
|
||||||
// Already cancelled, so return null
|
// Already cancelled, so return null
|
||||||
return null
|
return null
|
||||||
|
@ -450,7 +454,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
?.also {
|
?.also {
|
||||||
// Multiple keyshares between two devices:
|
// Multiple keyshares between two devices:
|
||||||
// any two devices may only have at most one key verification in flight at a time.
|
// any two devices may only have at most one key verification in flight at a time.
|
||||||
Timber.v("## SAS onStartRequestReceived - Already a transaction with this user ${startReq.transactionID}")
|
Timber.v("## SAS onStartRequestReceived - Already a transaction with this user ${startReq.transactionId}")
|
||||||
}
|
}
|
||||||
?.forEach {
|
?.forEach {
|
||||||
it.cancel(CancelCode.UnexpectedMessage)
|
it.cancel(CancelCode.UnexpectedMessage)
|
||||||
|
@ -462,12 +466,12 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok we can create a SAS transaction
|
// Ok we can create a SAS transaction
|
||||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionId}")
|
||||||
// If there is a corresponding request, we can auto accept
|
// If there is a corresponding request, we can auto accept
|
||||||
// as we are the one requesting in first place (or we accepted the request)
|
// as we are the one requesting in first place (or we accepted the request)
|
||||||
// I need to check if the pending request was related to this device also
|
// I need to check if the pending request was related to this device also
|
||||||
val autoAccept = getExistingVerificationRequest(otherUserId)?.any {
|
val autoAccept = getExistingVerificationRequest(otherUserId)?.any {
|
||||||
it.transactionId == startReq.transactionID
|
it.transactionId == startReq.transactionId
|
||||||
&& (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId)
|
&& (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId)
|
||||||
}
|
}
|
||||||
?: false
|
?: false
|
||||||
|
@ -479,27 +483,23 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
crossSigningService,
|
crossSigningService,
|
||||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
startReq.transactionID!!,
|
startReq.transactionId,
|
||||||
otherUserId,
|
otherUserId,
|
||||||
autoAccept).also { txConfigure(it) }
|
autoAccept).also { txConfigure(it) }
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
tx.acceptVerificationEvent(otherUserId, startReq)
|
tx.onVerificationStart(startReq)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
VERIFICATION_METHOD_RECIPROCATE -> {
|
is ValidVerificationInfoStart.ReciprocateVerificationInfoStart -> {
|
||||||
// Other user has scanned my QR code
|
// Other user has scanned my QR code
|
||||||
if (existing is DefaultQrCodeVerificationTransaction) {
|
if (existing is DefaultQrCodeVerificationTransaction) {
|
||||||
existing.onStartReceived(startReq)
|
existing.onStartReceived(startReq)
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionID}")
|
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId}")
|
||||||
return CancelCode.UnexpectedMessage
|
return CancelCode.UnexpectedMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
|
||||||
return CancelCode.UnknownMethod
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return CancelCode.UnexpectedMessage
|
return CancelCode.UnexpectedMessage
|
||||||
|
@ -529,24 +529,27 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
// relates_to is in clear in encrypted payload
|
// relates_to is in clear in encrypted payload
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
if (cancelReq == null || cancelReq.isValid().not()) {
|
|
||||||
|
val validCancelReq = cancelReq?.asValidObject()
|
||||||
|
|
||||||
|
if (validCancelReq == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid key request")
|
Timber.e("## SAS Received invalid key request")
|
||||||
// TODO should we cancel?
|
// TODO should we cancel?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getExistingVerificationRequest(event.senderId ?: "", cancelReq.transactionID)?.let {
|
getExistingVerificationRequest(event.senderId ?: "", validCancelReq.transactionId)?.let {
|
||||||
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(cancelReq.code)))
|
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(validCancelReq.code)))
|
||||||
// Should we remove it from the list?
|
// Should we remove it from the list?
|
||||||
}
|
}
|
||||||
handleOnCancel(event.senderId!!, cancelReq)
|
handleOnCancel(event.senderId!!, validCancelReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCancelReceived(event: Event) {
|
private fun onCancelReceived(event: Event) {
|
||||||
Timber.v("## SAS onCancelReceived")
|
Timber.v("## SAS onCancelReceived")
|
||||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!
|
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()?.asValidObject()
|
||||||
|
|
||||||
if (!cancelReq.isValid()) {
|
if (cancelReq == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid cancel request")
|
Timber.e("## SAS Received invalid cancel request")
|
||||||
return
|
return
|
||||||
|
@ -556,11 +559,11 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
handleOnCancel(otherUserId, cancelReq)
|
handleOnCancel(otherUserId, cancelReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOnCancel(otherUserId: String, cancelReq: VerificationInfoCancel) {
|
private fun handleOnCancel(otherUserId: String, cancelReq: ValidVerificationInfoCancel) {
|
||||||
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
Timber.v("## SAS onCancelReceived otherUser: $otherUserId reason: ${cancelReq.reason}")
|
||||||
|
|
||||||
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionId)
|
||||||
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionID!!)
|
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionId)
|
||||||
|
|
||||||
if (existingRequest != null) {
|
if (existingRequest != null) {
|
||||||
// Mark this request as cancelled
|
// Mark this request as cancelled
|
||||||
|
@ -582,30 +585,28 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
?: return
|
?: return
|
||||||
handleAccept(accept, event.senderId!!)
|
|
||||||
|
val validAccept = accept.asValidObject() ?: return
|
||||||
|
|
||||||
|
handleAccept(validAccept, event.senderId!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAcceptReceived(event: Event) {
|
private fun onAcceptReceived(event: Event) {
|
||||||
Timber.d("## SAS Received Accept $event")
|
Timber.d("## SAS Received Accept $event")
|
||||||
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>() ?: return
|
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>()?.asValidObject() ?: return
|
||||||
handleAccept(acceptReq, event.senderId!!)
|
handleAccept(acceptReq, event.senderId!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAccept(acceptReq: VerificationInfoAccept, senderId: String) {
|
private fun handleAccept(acceptReq: ValidVerificationInfoAccept, senderId: String) {
|
||||||
if (!acceptReq.isValid()) {
|
|
||||||
// ignore
|
|
||||||
Timber.e("## SAS Received invalid accept request")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val otherUserId = senderId
|
val otherUserId = senderId
|
||||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, acceptReq.transactionId)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## SAS Received invalid accept request")
|
Timber.e("## SAS Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing is SASDefaultVerificationTransaction) {
|
if (existing is SASDefaultVerificationTransaction) {
|
||||||
existing.acceptVerificationEvent(otherUserId, acceptReq)
|
existing.onVerificationAccept(acceptReq)
|
||||||
} else {
|
} else {
|
||||||
// not other types now
|
// not other types now
|
||||||
}
|
}
|
||||||
|
@ -617,7 +618,8 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
// relates_to is in clear in encrypted payload
|
// relates_to is in clear in encrypted payload
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
if (keyReq == null || keyReq.isValid().not()) {
|
?.asValidObject()
|
||||||
|
if (keyReq == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid key request")
|
Timber.e("## SAS Received invalid key request")
|
||||||
// TODO should we cancel?
|
// TODO should we cancel?
|
||||||
|
@ -627,9 +629,9 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onKeyReceived(event: Event) {
|
private fun onKeyReceived(event: Event) {
|
||||||
val keyReq = event.getClearContent().toModel<KeyVerificationKey>()!!
|
val keyReq = event.getClearContent().toModel<KeyVerificationKey>()?.asValidObject()
|
||||||
|
|
||||||
if (!keyReq.isValid()) {
|
if (keyReq == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid key request")
|
Timber.e("## SAS Received invalid key request")
|
||||||
return
|
return
|
||||||
|
@ -637,16 +639,16 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
handleKeyReceived(event, keyReq)
|
handleKeyReceived(event, keyReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleKeyReceived(event: Event, keyReq: VerificationInfoKey) {
|
private fun handleKeyReceived(event: Event, keyReq: ValidVerificationInfoKey) {
|
||||||
Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq")
|
Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq")
|
||||||
val otherUserId = event.senderId!!
|
val otherUserId = event.senderId!!
|
||||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, keyReq.transactionId)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## SAS Received invalid key request")
|
Timber.e("## SAS Received invalid key request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (existing is SASDefaultVerificationTransaction) {
|
if (existing is SASDefaultVerificationTransaction) {
|
||||||
existing.acceptVerificationEvent(otherUserId, keyReq)
|
existing.onKeyVerificationKey(keyReq)
|
||||||
} else {
|
} else {
|
||||||
// not other types now
|
// not other types now
|
||||||
}
|
}
|
||||||
|
@ -658,7 +660,8 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
// relates_to is in clear in encrypted payload
|
// relates_to is in clear in encrypted payload
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
if (macReq == null || macReq.isValid().not() || event.senderId == null) {
|
?.asValidObject()
|
||||||
|
if (macReq == null || event.senderId == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid mac request")
|
Timber.e("## SAS Received invalid mac request")
|
||||||
// TODO should we cancel?
|
// TODO should we cancel?
|
||||||
|
@ -673,13 +676,14 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
// relates_to is in clear in encrypted payload
|
// relates_to is in clear in encrypted payload
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
if (readyReq == null || readyReq.isValid().not() || event.senderId == null) {
|
?.asValidObject()
|
||||||
|
if (readyReq == null || event.senderId == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid ready request")
|
Timber.e("## SAS Received invalid ready request")
|
||||||
// TODO should we cancel?
|
// TODO should we cancel?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) {
|
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice) == null) {
|
||||||
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
||||||
// TODO cancel?
|
// TODO cancel?
|
||||||
return
|
return
|
||||||
|
@ -691,15 +695,15 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onReadyReceived(event: Event) {
|
private suspend fun onReadyReceived(event: Event) {
|
||||||
val readyReq = event.getClearContent().toModel<KeyVerificationReady>()
|
val readyReq = event.getClearContent().toModel<KeyVerificationReady>()?.asValidObject()
|
||||||
|
|
||||||
if (readyReq == null || readyReq.isValid().not() || event.senderId == null) {
|
if (readyReq == null || event.senderId == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid ready request")
|
Timber.e("## SAS Received invalid ready request")
|
||||||
// TODO should we cancel?
|
// TODO should we cancel?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) {
|
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice) == null) {
|
||||||
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
||||||
// TODO cancel?
|
// TODO cancel?
|
||||||
return
|
return
|
||||||
|
@ -716,8 +720,9 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
// relates_to is in clear in encrypted payload
|
// relates_to is in clear in encrypted payload
|
||||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
)
|
)
|
||||||
|
?.asValidObject()
|
||||||
|
|
||||||
if (doneReq == null || doneReq.isValid().not() || event.senderId == null) {
|
if (doneReq == null || event.senderId == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid Done request")
|
Timber.e("## SAS Received invalid Done request")
|
||||||
// TODO should we cancel?
|
// TODO should we cancel?
|
||||||
|
@ -728,9 +733,9 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMacReceived(event: Event) {
|
private fun onMacReceived(event: Event) {
|
||||||
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!
|
val macReq = event.getClearContent().toModel<KeyVerificationMac>()?.asValidObject()
|
||||||
|
|
||||||
if (!macReq.isValid() || event.senderId == null) {
|
if (macReq == null || event.senderId == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## SAS Received invalid mac request")
|
Timber.e("## SAS Received invalid mac request")
|
||||||
return
|
return
|
||||||
|
@ -738,41 +743,41 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
handleMacReceived(event.senderId, macReq)
|
handleMacReceived(event.senderId, macReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMacReceived(senderId: String, macReq: VerificationInfoMac) {
|
private fun handleMacReceived(senderId: String, macReq: ValidVerificationInfoMac) {
|
||||||
Timber.v("## SAS Received $macReq")
|
Timber.v("## SAS Received $macReq")
|
||||||
val existing = getExistingTransaction(senderId, macReq.transactionID!!)
|
val existing = getExistingTransaction(senderId, macReq.transactionId)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## SAS Received invalid Mac request")
|
Timber.e("## SAS Received invalid Mac request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (existing is SASDefaultVerificationTransaction) {
|
if (existing is SASDefaultVerificationTransaction) {
|
||||||
existing.acceptVerificationEvent(senderId, macReq)
|
existing.onKeyVerificationMac(macReq)
|
||||||
} else {
|
} else {
|
||||||
// not other types known for now
|
// not other types known for now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReadyReceived(senderId: String,
|
private fun handleReadyReceived(senderId: String,
|
||||||
readyReq: VerificationInfoReady,
|
readyReq: ValidVerificationInfoReady,
|
||||||
transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) {
|
transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) {
|
||||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID }
|
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionId }
|
||||||
if (existingRequest == null) {
|
if (existingRequest == null) {
|
||||||
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}")
|
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionId} fromDevice ${readyReq.fromDevice}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val qrCodeData = readyReq.methods
|
val qrCodeData = readyReq.methods
|
||||||
// Check if other user is able to scan QR code
|
// Check if other user is able to scan QR code
|
||||||
?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
|
.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
|
||||||
?.let {
|
?.let {
|
||||||
createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId, readyReq.fromDevice)
|
createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId, readyReq.fromDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readyReq.methods.orEmpty().contains(VERIFICATION_METHOD_RECIPROCATE)) {
|
if (readyReq.methods.contains(VERIFICATION_METHOD_RECIPROCATE)) {
|
||||||
// Create the pending transaction
|
// Create the pending transaction
|
||||||
val tx = DefaultQrCodeVerificationTransaction(
|
val tx = DefaultQrCodeVerificationTransaction(
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
readyReq.transactionID!!,
|
readyReq.transactionId,
|
||||||
senderId,
|
senderId,
|
||||||
readyReq.fromDevice,
|
readyReq.fromDevice,
|
||||||
crossSigningService,
|
crossSigningService,
|
||||||
|
@ -886,10 +891,10 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) {
|
private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) {
|
||||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionID }
|
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId }
|
||||||
if (existingRequest == null) {
|
if (existingRequest == null) {
|
||||||
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionID}")
|
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updatePendingRequest(existingRequest.copy(isSuccessful = true))
|
updatePendingRequest(existingRequest.copy(isSuccessful = true))
|
||||||
|
@ -975,15 +980,12 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
: PendingVerificationRequest {
|
: PendingVerificationRequest {
|
||||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||||
|
|
||||||
val requestsForUser = pendingRequests[otherUserId]
|
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||||
?: ArrayList<PendingVerificationRequest>().also {
|
|
||||||
pendingRequests[otherUserId] = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||||
|
|
||||||
// Cancel existing pending requests?
|
// Cancel existing pending requests?
|
||||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
requestsForUser.toList().forEach { existingRequest ->
|
||||||
existingRequest.transactionId?.let { tid ->
|
existingRequest.transactionId?.let { tid ->
|
||||||
if (!existingRequest.isFinished) {
|
if (!existingRequest.isFinished) {
|
||||||
Timber.d("## SAS, cancelling pending requests to start a new one")
|
Timber.d("## SAS, cancelling pending requests to start a new one")
|
||||||
|
@ -993,13 +995,13 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val localID = localId ?: LocalEcho.createLocalEchoId()
|
val validLocalId = localId ?: LocalEcho.createLocalEchoId()
|
||||||
|
|
||||||
val verificationRequest = PendingVerificationRequest(
|
val verificationRequest = PendingVerificationRequest(
|
||||||
ageLocalTs = System.currentTimeMillis(),
|
ageLocalTs = System.currentTimeMillis(),
|
||||||
isIncoming = false,
|
isIncoming = false,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
localID = localID,
|
localId = validLocalId,
|
||||||
otherUserId = otherUserId
|
otherUserId = otherUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1019,7 +1021,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|
||||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId, null) { syncedId, info ->
|
transport.sendVerificationRequest(methodValues, validLocalId, otherUserId, roomId, null) { syncedId, info ->
|
||||||
// We need to update with the syncedID
|
// We need to update with the syncedID
|
||||||
updatePendingRequest(verificationRequest.copy(
|
updatePendingRequest(verificationRequest.copy(
|
||||||
transactionId = syncedId,
|
transactionId = syncedId,
|
||||||
|
@ -1039,15 +1041,12 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
||||||
|
|
||||||
val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId }
|
val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId }
|
||||||
val requestsForUser = pendingRequests[otherUserId]
|
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||||
?: ArrayList<PendingVerificationRequest>().also {
|
|
||||||
pendingRequests[otherUserId] = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
||||||
|
|
||||||
// Cancel existing pending requests?
|
// Cancel existing pending requests?
|
||||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
requestsForUser.toList().forEach { existingRequest ->
|
||||||
existingRequest.transactionId?.let { tid ->
|
existingRequest.transactionId?.let { tid ->
|
||||||
if (!existingRequest.isFinished) {
|
if (!existingRequest.isFinished) {
|
||||||
Timber.d("## SAS, cancelling pending requests to start a new one")
|
Timber.d("## SAS, cancelling pending requests to start a new one")
|
||||||
|
@ -1059,14 +1058,14 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val localID = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
|
|
||||||
val verificationRequest = PendingVerificationRequest(
|
val verificationRequest = PendingVerificationRequest(
|
||||||
transactionId = localID,
|
transactionId = localId,
|
||||||
ageLocalTs = System.currentTimeMillis(),
|
ageLocalTs = System.currentTimeMillis(),
|
||||||
isIncoming = false,
|
isIncoming = false,
|
||||||
roomId = null,
|
roomId = null,
|
||||||
localID = localID,
|
localId = localId,
|
||||||
otherUserId = otherUserId,
|
otherUserId = otherUserId,
|
||||||
targetDevices = targetDevices
|
targetDevices = targetDevices
|
||||||
)
|
)
|
||||||
|
@ -1087,7 +1086,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|
||||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, info ->
|
transport.sendVerificationRequest(methodValues, localId, otherUserId, null, targetDevices) { _, info ->
|
||||||
// Nothing special to do in to device mode
|
// Nothing special to do in to device mode
|
||||||
updatePendingRequest(verificationRequest.copy(
|
updatePendingRequest(verificationRequest.copy(
|
||||||
// localId stays different
|
// localId stays different
|
||||||
|
@ -1113,13 +1112,10 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePendingRequest(updated: PendingVerificationRequest) {
|
private fun updatePendingRequest(updated: PendingVerificationRequest) {
|
||||||
val requestsForUser = pendingRequests[updated.otherUserId]
|
val requestsForUser = pendingRequests.getOrPut(updated.otherUserId) { mutableListOf() }
|
||||||
?: ArrayList<PendingVerificationRequest>().also {
|
|
||||||
pendingRequests[updated.otherUserId] = it
|
|
||||||
}
|
|
||||||
val index = requestsForUser.indexOfFirst {
|
val index = requestsForUser.indexOfFirst {
|
||||||
it.transactionId == updated.transactionId
|
it.transactionId == updated.transactionId
|
||||||
|| it.transactionId == null && it.localID == updated.localID
|
|| it.transactionId == null && it.localId == updated.localId
|
||||||
}
|
}
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
requestsForUser.removeAt(index)
|
requestsForUser.removeAt(index)
|
||||||
|
@ -1186,7 +1182,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
CancelCode.User,
|
CancelCode.User,
|
||||||
null // TODO handle error?
|
null // TODO handle error?
|
||||||
)
|
)
|
||||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
|
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg.asValidObject()))
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## SAS readyPendingVerificationInDMs Verification not found")
|
Timber.e("## SAS readyPendingVerificationInDMs Verification not found")
|
||||||
|
@ -1214,7 +1210,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
if (methods.isNullOrEmpty()) {
|
if (methods.isNullOrEmpty()) {
|
||||||
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
||||||
// TODO buttons should not be shown in this case?
|
// TODO buttons should not be shown in this case?
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
|
// TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
|
||||||
|
@ -1225,7 +1221,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
existingRequest.requestInfo?.fromDevice ?: "",
|
existingRequest.requestInfo?.fromDevice ?: "",
|
||||||
null // TODO handle error?
|
null // TODO handle error?
|
||||||
)
|
)
|
||||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
|
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg.asValidObject()))
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## SAS readyPendingVerification Verification not found")
|
Timber.e("## SAS readyPendingVerification Verification not found")
|
||||||
|
|
|
@ -15,12 +15,21 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic interactive key verification transaction
|
* Generic interactive key verification transaction
|
||||||
*/
|
*/
|
||||||
internal abstract class DefaultVerificationTransaction(
|
internal abstract class DefaultVerificationTransaction(
|
||||||
|
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
|
private val crossSigningService: CrossSigningService,
|
||||||
|
private val userId: String,
|
||||||
override val transactionId: String,
|
override val transactionId: String,
|
||||||
override val otherUserId: String,
|
override val otherUserId: String,
|
||||||
override var otherDeviceId: String? = null,
|
override var otherDeviceId: String? = null,
|
||||||
|
@ -42,5 +51,50 @@ internal abstract class DefaultVerificationTransaction(
|
||||||
listeners.remove(listener)
|
listeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun acceptVerificationEvent(senderId: String, info: VerificationInfo)
|
protected fun trust(canTrustOtherUserMasterKey: Boolean,
|
||||||
|
toVerifyDeviceIds: List<String>,
|
||||||
|
eventuallyMarkMyMasterKeyAsTrusted: Boolean) {
|
||||||
|
// If not me sign his MSK and upload the signature
|
||||||
|
if (canTrustOtherUserMasterKey) {
|
||||||
|
// we should trust this master key
|
||||||
|
// And check verification MSK -> SSK?
|
||||||
|
if (otherUserId != userId) {
|
||||||
|
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
Timber.e(failure, "## Verification: Failed to trust User $otherUserId")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Notice other master key is mine because other is me
|
||||||
|
if (eventuallyMarkMyMasterKeyAsTrusted) {
|
||||||
|
// Mark my keys as trusted locally
|
||||||
|
crossSigningService.markMyMasterKeyAsTrusted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherUserId == userId) {
|
||||||
|
// If me it's reasonable to sign and upload the device signature
|
||||||
|
// Notice that i might not have the private keys, so may not be able to do it
|
||||||
|
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
Timber.w("## Verification: Failed to sign new device $otherDeviceId, ${failure.localizedMessage}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO what if the otherDevice is not in this list? and should we
|
||||||
|
toVerifyDeviceIds.forEach {
|
||||||
|
setDeviceVerified(otherUserId, it)
|
||||||
|
}
|
||||||
|
transport.done(transactionId)
|
||||||
|
state = VerificationTxState.Verified
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||||
|
// TODO should not override cross sign status
|
||||||
|
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||||
|
userId,
|
||||||
|
deviceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentation
|
import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentation
|
||||||
|
@ -25,7 +25,6 @@ import im.vector.matrix.android.api.session.crypto.verification.SasVerificationT
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||||
|
@ -38,17 +37,25 @@ import timber.log.Timber
|
||||||
* Represents an ongoing short code interactive key verification between two devices.
|
* Represents an ongoing short code interactive key verification between two devices.
|
||||||
*/
|
*/
|
||||||
internal abstract class SASDefaultVerificationTransaction(
|
internal abstract class SASDefaultVerificationTransaction(
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
open val userId: String,
|
open val userId: String,
|
||||||
open val deviceId: String?,
|
open val deviceId: String?,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val crossSigningService: CrossSigningService,
|
crossSigningService: CrossSigningService,
|
||||||
private val deviceFingerprint: String,
|
private val deviceFingerprint: String,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String?,
|
otherDeviceId: String?,
|
||||||
isIncoming: Boolean
|
isIncoming: Boolean
|
||||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), SasVerificationTransaction {
|
) : DefaultVerificationTransaction(
|
||||||
|
setDeviceVerificationAction,
|
||||||
|
crossSigningService,
|
||||||
|
userId,
|
||||||
|
transactionId,
|
||||||
|
otherUserId,
|
||||||
|
otherDeviceId,
|
||||||
|
isIncoming),
|
||||||
|
SasVerificationTransaction {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||||
|
@ -89,15 +96,17 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
|
|
||||||
private var olmSas: OlmSAS? = null
|
private var olmSas: OlmSAS? = null
|
||||||
|
|
||||||
var startReq: VerificationInfoStart? = null
|
// Visible for test
|
||||||
var accepted: VerificationInfoAccept? = null
|
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||||
var otherKey: String? = null
|
// Visible for test
|
||||||
var shortCodeBytes: ByteArray? = null
|
var accepted: ValidVerificationInfoAccept? = null
|
||||||
|
protected var otherKey: String? = null
|
||||||
|
protected var shortCodeBytes: ByteArray? = null
|
||||||
|
|
||||||
var myMac: VerificationInfoMac? = null
|
protected var myMac: ValidVerificationInfoMac? = null
|
||||||
var theirMac: VerificationInfoMac? = null
|
protected var theirMac: ValidVerificationInfoMac? = null
|
||||||
|
|
||||||
fun getSAS(): OlmSAS {
|
protected fun getSAS(): OlmSAS {
|
||||||
if (olmSas == null) olmSas = OlmSAS()
|
if (olmSas == null) olmSas = OlmSAS()
|
||||||
return olmSas!!
|
return olmSas!!
|
||||||
}
|
}
|
||||||
|
@ -177,7 +186,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
|
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
|
||||||
myMac = macMsg
|
myMac = macMsg.asValidObject()
|
||||||
state = VerificationTxState.SendingMac
|
state = VerificationTxState.SendingMac
|
||||||
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, VerificationTxState.MacSent, CancelCode.User) {
|
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, VerificationTxState.MacSent, CancelCode.User) {
|
||||||
if (state == VerificationTxState.SendingMac) {
|
if (state == VerificationTxState.SendingMac) {
|
||||||
|
@ -187,9 +196,8 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do I already have their Mac?
|
// Do I already have their Mac?
|
||||||
if (theirMac != null) {
|
theirMac?.let { verifyMacs(it) }
|
||||||
verifyMacs()
|
// if not wait for it
|
||||||
} // if not wait for it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shortCodeDoesNotMatch() {
|
override fun shortCodeDoesNotMatch() {
|
||||||
|
@ -201,27 +209,15 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
return transport is VerificationTransportToDevice
|
return transport is VerificationTransportToDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
abstract fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart)
|
||||||
when (info) {
|
|
||||||
is VerificationInfoStart -> onVerificationStart(info)
|
|
||||||
is VerificationInfoAccept -> onVerificationAccept(info)
|
|
||||||
is VerificationInfoKey -> onKeyVerificationKey(info)
|
|
||||||
is VerificationInfoMac -> onKeyVerificationMac(info)
|
|
||||||
else -> {
|
|
||||||
// nop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun onVerificationStart(startReq: VerificationInfoStart)
|
abstract fun onVerificationAccept(accept: ValidVerificationInfoAccept)
|
||||||
|
|
||||||
abstract fun onVerificationAccept(accept: VerificationInfoAccept)
|
abstract fun onKeyVerificationKey(vKey: ValidVerificationInfoKey)
|
||||||
|
|
||||||
abstract fun onKeyVerificationKey(vKey: VerificationInfoKey)
|
abstract fun onKeyVerificationMac(vMac: ValidVerificationInfoMac)
|
||||||
|
|
||||||
abstract fun onKeyVerificationMac(vKey: VerificationInfoMac)
|
protected fun verifyMacs(theirMacSafe: ValidVerificationInfoMac) {
|
||||||
|
|
||||||
protected fun verifyMacs() {
|
|
||||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
Timber.v("## SAS verifying macs for id:$transactionId")
|
||||||
state = VerificationTxState.Verifying
|
state = VerificationTxState.Verifying
|
||||||
|
|
||||||
|
@ -232,16 +228,12 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
// as well as the HMAC of the comma-separated, sorted list of the key IDs given in the message.
|
// as well as the HMAC of the comma-separated, sorted list of the key IDs given in the message.
|
||||||
// Bob’s device compares these with the HMAC values given in the m.key.verification.mac message.
|
// Bob’s device compares these with the HMAC values given in the m.key.verification.mac message.
|
||||||
// If everything matches, then consider Alice’s device keys as verified.
|
// If everything matches, then consider Alice’s device keys as verified.
|
||||||
|
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$otherUserId$otherDeviceId$userId$deviceId$transactionId"
|
||||||
|
|
||||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" +
|
val commaSeparatedListOfKeyIds = theirMacSafe.mac.keys.sorted().joinToString(",")
|
||||||
otherUserId + otherDeviceId +
|
|
||||||
userId + deviceId +
|
|
||||||
transactionId
|
|
||||||
|
|
||||||
val commaSeparatedListOfKeyIds = theirMac!!.mac!!.keys.sorted().joinToString(",")
|
|
||||||
|
|
||||||
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
|
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
|
||||||
if (theirMac!!.keys != keyStrings) {
|
if (theirMacSafe.keys != keyStrings) {
|
||||||
// WRONG!
|
// WRONG!
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
|
@ -250,7 +242,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
val verifiedDevices = ArrayList<String>()
|
val verifiedDevices = ArrayList<String>()
|
||||||
|
|
||||||
// cannot be empty because it has been validated
|
// cannot be empty because it has been validated
|
||||||
theirMac!!.mac!!.keys.forEach {
|
theirMacSafe.mac.keys.forEach {
|
||||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||||
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
||||||
if (otherDeviceKey == null) {
|
if (otherDeviceKey == null) {
|
||||||
|
@ -259,7 +251,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
||||||
if (mac != theirMac?.mac?.get(it)) {
|
if (mac != theirMacSafe.mac[it]) {
|
||||||
// WRONG!
|
// WRONG!
|
||||||
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
|
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
|
@ -273,12 +265,12 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
|
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
|
||||||
if (otherCrossSigningMasterKeyPublic != null) {
|
if (otherCrossSigningMasterKeyPublic != null) {
|
||||||
// Did the user signed his master key
|
// Did the user signed his master key
|
||||||
theirMac!!.mac!!.keys.forEach {
|
theirMacSafe.mac.keys.forEach {
|
||||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||||
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
||||||
// Check the signature
|
// Check the signature
|
||||||
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
||||||
if (mac != theirMac?.mac?.get(it)) {
|
if (mac != theirMacSafe.mac.get(it)) {
|
||||||
// WRONG!
|
// WRONG!
|
||||||
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
|
@ -298,40 +290,9 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not me sign his MSK and upload the signature
|
trust(otherMasterKeyIsVerified,
|
||||||
if (otherMasterKeyIsVerified && otherUserId != userId) {
|
verifiedDevices,
|
||||||
// we should trust this master key
|
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false)
|
||||||
// And check verification MSK -> SSK?
|
|
||||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e(failure, "## SAS Verification: Failed to trust User $otherUserId")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherUserId == userId) {
|
|
||||||
// If me it's reasonable to sign and upload the device signature
|
|
||||||
// Notice that i might not have the private keys, so may not be able to do it
|
|
||||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO what if the otherDevice is not in this list? and should we
|
|
||||||
verifiedDevices.forEach {
|
|
||||||
setDeviceVerified(otherUserId, it)
|
|
||||||
}
|
|
||||||
transport.done(transactionId)
|
|
||||||
state = VerificationTxState.Verified
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
|
||||||
// TODO should not override cross sign status
|
|
||||||
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
|
||||||
userId,
|
|
||||||
deviceId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
|
@ -343,11 +304,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun sendToOther(type: String,
|
protected fun <T> sendToOther(type: String,
|
||||||
keyToDevice: VerificationInfo,
|
keyToDevice: VerificationInfo<T>,
|
||||||
nextState: VerificationTxState,
|
nextState: VerificationTxState,
|
||||||
onErrorReason: CancelCode,
|
onErrorReason: CancelCode,
|
||||||
onDone: (() -> Unit)?) {
|
onDone: (() -> Unit)?) {
|
||||||
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,11 +330,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun supportsEmoji(): Boolean {
|
override fun supportsEmoji(): Boolean {
|
||||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI) == true
|
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI).orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun supportsDecimal(): Boolean {
|
override fun supportsDecimal(): Boolean {
|
||||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL) == true
|
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL).orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
|
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
|
||||||
|
@ -386,7 +347,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun macUsingAgreedMethod(message: String, info: String): String? {
|
private fun macUsingAgreedMethod(message: String, info: String): String? {
|
||||||
if (SAS_MAC_SHA256_LONGKDF.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
|
if (SAS_MAC_SHA256_LONGKDF.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
|
||||||
return getSAS().calculateMacLongKdf(message, info)
|
return getSAS().calculateMacLongKdf(message, info)
|
||||||
} else if (SAS_MAC_SHA256.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
|
} else if (SAS_MAC_SHA256.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
|
||||||
|
@ -436,7 +397,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
* For each group of 6 bits, look up the emoji from Appendix A corresponding
|
* For each group of 6 bits, look up the emoji from Appendix A corresponding
|
||||||
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
|
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
|
||||||
*/
|
*/
|
||||||
fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
private fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
||||||
val b0 = byteArray[0].toUnsignedInt()
|
val b0 = byteArray[0].toUnsignedInt()
|
||||||
val b1 = byteArray[1].toUnsignedInt()
|
val b1 = byteArray[1].toUnsignedInt()
|
||||||
val b2 = byteArray[2].toUnsignedInt()
|
val b2 = byteArray[2].toUnsignedInt()
|
||||||
|
|
|
@ -18,18 +18,16 @@ package im.vector.matrix.android.internal.crypto.verification
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject
|
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject
|
||||||
|
|
||||||
interface VerificationInfo {
|
interface VerificationInfo<ValidObjectType> {
|
||||||
fun toEventContent(): Content? = null
|
fun toEventContent(): Content? = null
|
||||||
fun toSendToDeviceObject(): SendToDeviceObject? = null
|
fun toSendToDeviceObject(): SendToDeviceObject? = null
|
||||||
fun isValid(): Boolean
|
|
||||||
|
fun asValidObject(): ValidObjectType?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* String to identify the transaction.
|
* String to identify the transaction.
|
||||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
||||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||||
*/
|
*/
|
||||||
val transactionID: String?
|
val transactionId: String?
|
||||||
|
|
||||||
// TODO Refacto Put the relatesTo here or at least in Message sent in Room parent?
|
|
||||||
// val relatesTo: RelationDefaultContent?
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
internal interface VerificationInfoAccept : VerificationInfo {
|
internal interface VerificationInfoAccept : VerificationInfo<ValidVerificationInfoAccept> {
|
||||||
/**
|
/**
|
||||||
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +41,24 @@ internal interface VerificationInfoAccept : VerificationInfo {
|
||||||
* and the canonical JSON representation of the m.key.verification.start message.
|
* and the canonical JSON representation of the m.key.verification.start message.
|
||||||
*/
|
*/
|
||||||
var commitment: String?
|
var commitment: String?
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoAccept? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validKeyAgreementProtocol = keyAgreementProtocol?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validHash = hash?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validMessageAuthenticationCode = messageAuthenticationCode?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validCommitment = commitment?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationInfoAccept(
|
||||||
|
validTransactionId,
|
||||||
|
validKeyAgreementProtocol,
|
||||||
|
validHash,
|
||||||
|
validMessageAuthenticationCode,
|
||||||
|
validShortAuthenticationStrings,
|
||||||
|
validCommitment
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface VerificationInfoAcceptFactory {
|
internal interface VerificationInfoAcceptFactory {
|
||||||
|
@ -52,3 +70,12 @@ internal interface VerificationInfoAcceptFactory {
|
||||||
messageAuthenticationCode: String,
|
messageAuthenticationCode: String,
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept
|
shortAuthenticationStrings: List<String>): VerificationInfoAccept
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class ValidVerificationInfoAccept(
|
||||||
|
val transactionId: String,
|
||||||
|
val keyAgreementProtocol: String,
|
||||||
|
val hash: String,
|
||||||
|
val messageAuthenticationCode: String,
|
||||||
|
val shortAuthenticationStrings: List<String>,
|
||||||
|
var commitment: String?
|
||||||
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
internal interface VerificationInfoCancel : VerificationInfo {
|
internal interface VerificationInfoCancel : VerificationInfo<ValidVerificationInfoCancel> {
|
||||||
/**
|
/**
|
||||||
* machine-readable reason for cancelling, see [CancelCode]
|
* machine-readable reason for cancelling, see [CancelCode]
|
||||||
*/
|
*/
|
||||||
|
@ -25,4 +25,21 @@ internal interface VerificationInfoCancel : VerificationInfo {
|
||||||
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
||||||
*/
|
*/
|
||||||
val reason: String?
|
val reason: String?
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoCancel? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validCode = code?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationInfoCancel(
|
||||||
|
validTransactionId,
|
||||||
|
validCode,
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class ValidVerificationInfoCancel(
|
||||||
|
val transactionId: String,
|
||||||
|
val code: String,
|
||||||
|
val reason: String?
|
||||||
|
)
|
||||||
|
|
|
@ -15,4 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
interface VerificationInfoDone : VerificationInfo
|
internal interface VerificationInfoDone : VerificationInfo<ValidVerificationInfoDone> {
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoDone? {
|
||||||
|
if (transactionId.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return ValidVerificationInfoDone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object ValidVerificationInfoDone
|
||||||
|
|
|
@ -18,13 +18,28 @@ package im.vector.matrix.android.internal.crypto.verification
|
||||||
/**
|
/**
|
||||||
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
||||||
*/
|
*/
|
||||||
internal interface VerificationInfoKey : VerificationInfo {
|
internal interface VerificationInfoKey : VerificationInfo<ValidVerificationInfoKey> {
|
||||||
/**
|
/**
|
||||||
* The device’s ephemeral public key, as an unpadded base64 string
|
* The device’s ephemeral public key, as an unpadded base64 string
|
||||||
*/
|
*/
|
||||||
val key: String?
|
val key: String?
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoKey? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validKey = key?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationInfoKey(
|
||||||
|
validTransactionId,
|
||||||
|
validKey
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface VerificationInfoKeyFactory {
|
internal interface VerificationInfoKeyFactory {
|
||||||
fun create(tid: String, pubKey: String): VerificationInfoKey
|
fun create(tid: String, pubKey: String): VerificationInfoKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class ValidVerificationInfoKey(
|
||||||
|
val transactionId: String,
|
||||||
|
val key: String
|
||||||
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
internal interface VerificationInfoMac : VerificationInfo {
|
internal interface VerificationInfoMac : VerificationInfo<ValidVerificationInfoMac> {
|
||||||
/**
|
/**
|
||||||
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
|
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
|
||||||
*/
|
*/
|
||||||
|
@ -28,8 +28,26 @@ internal interface VerificationInfoMac : VerificationInfo {
|
||||||
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
|
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
|
||||||
*/
|
*/
|
||||||
val keys: String?
|
val keys: String?
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoMac? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validMac = mac?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validKeys = keys?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationInfoMac(
|
||||||
|
validTransactionId,
|
||||||
|
validMac,
|
||||||
|
validKeys
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface VerificationInfoMacFactory {
|
internal interface VerificationInfoMacFactory {
|
||||||
fun create(tid: String, mac: Map<String, String>, keys: String) : VerificationInfoMac
|
fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class ValidVerificationInfoMac(
|
||||||
|
val transactionId: String,
|
||||||
|
val mac: Map<String, String>,
|
||||||
|
val keys: String
|
||||||
|
)
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoReady
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A new event type is added to the key verification framework: m.key.verification.ready,
|
* A new event type is added to the key verification framework: m.key.verification.ready,
|
||||||
* which may be sent by the target of the m.key.verification.request message, upon receipt of the m.key.verification.request event.
|
* which may be sent by the target of the m.key.verification.request message, upon receipt of the m.key.verification.request event.
|
||||||
|
@ -23,7 +25,7 @@ package im.vector.matrix.android.internal.crypto.verification
|
||||||
* with a m.key.verification.start event instead.
|
* with a m.key.verification.start event instead.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface VerificationInfoReady : VerificationInfo {
|
internal interface VerificationInfoReady : VerificationInfo<ValidVerificationInfoReady> {
|
||||||
/**
|
/**
|
||||||
* The ID of the device that sent the m.key.verification.ready message
|
* The ID of the device that sent the m.key.verification.ready message
|
||||||
*/
|
*/
|
||||||
|
@ -33,6 +35,18 @@ interface VerificationInfoReady : VerificationInfo {
|
||||||
* An array of verification methods that the device supports
|
* An array of verification methods that the device supports
|
||||||
*/
|
*/
|
||||||
val methods: List<String>?
|
val methods: List<String>?
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoReady? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationInfoReady(
|
||||||
|
validTransactionId,
|
||||||
|
validFromDevice,
|
||||||
|
validMethods
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface MessageVerificationReadyFactory {
|
internal interface MessageVerificationReadyFactory {
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
interface VerificationInfoRequest : VerificationInfo {
|
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||||
|
|
||||||
|
internal interface VerificationInfoRequest : VerificationInfo<ValidVerificationInfoRequest> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. The device ID which is initiating the request.
|
* Required. The device ID which is initiating the request.
|
||||||
|
@ -33,4 +35,18 @@ interface VerificationInfoRequest : VerificationInfo {
|
||||||
* the message should be ignored by the receiver.
|
* the message should be ignored by the receiver.
|
||||||
*/
|
*/
|
||||||
val timestamp: Long?
|
val timestamp: Long?
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoRequest? {
|
||||||
|
// FIXME No check on Timestamp?
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return ValidVerificationInfoRequest(
|
||||||
|
validTransactionId,
|
||||||
|
validFromDevice,
|
||||||
|
validMethods,
|
||||||
|
timestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
internal interface VerificationInfoStart : VerificationInfo {
|
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
|
|
||||||
|
internal interface VerificationInfoStart : VerificationInfo<ValidVerificationInfoStart> {
|
||||||
|
|
||||||
val method: String?
|
val method: String?
|
||||||
|
|
||||||
|
@ -57,5 +61,64 @@ internal interface VerificationInfoStart : VerificationInfo {
|
||||||
*/
|
*/
|
||||||
val sharedSecret: String?
|
val sharedSecret: String?
|
||||||
|
|
||||||
fun toCanonicalJson(): String?
|
fun toCanonicalJson(): String
|
||||||
|
|
||||||
|
override fun asValidObject(): ValidVerificationInfoStart? {
|
||||||
|
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
return when (method) {
|
||||||
|
VERIFICATION_METHOD_SAS -> {
|
||||||
|
val validKeyAgreementProtocols = keyAgreementProtocols?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null
|
||||||
|
val validMessageAuthenticationCodes = messageAuthenticationCodes
|
||||||
|
?.takeIf {
|
||||||
|
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||||
|
|| it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
|
||||||
|
}
|
||||||
|
?: return null
|
||||||
|
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null
|
||||||
|
|
||||||
|
ValidVerificationInfoStart.SasVerificationInfoStart(
|
||||||
|
validTransactionId,
|
||||||
|
validFromDevice,
|
||||||
|
validKeyAgreementProtocols,
|
||||||
|
validHashes,
|
||||||
|
validMessageAuthenticationCodes,
|
||||||
|
validShortAuthenticationStrings,
|
||||||
|
canonicalJson = toCanonicalJson()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VERIFICATION_METHOD_RECIPROCATE -> {
|
||||||
|
val validSharedSecret = sharedSecret?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
|
||||||
|
ValidVerificationInfoStart.ReciprocateVerificationInfoStart(
|
||||||
|
validTransactionId,
|
||||||
|
validFromDevice,
|
||||||
|
validSharedSecret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ValidVerificationInfoStart(
|
||||||
|
open val transactionId: String,
|
||||||
|
open val fromDevice: String) {
|
||||||
|
data class SasVerificationInfoStart(
|
||||||
|
override val transactionId: String,
|
||||||
|
override val fromDevice: String,
|
||||||
|
val keyAgreementProtocols: List<String>,
|
||||||
|
val hashes: List<String>,
|
||||||
|
val messageAuthenticationCodes: List<String>,
|
||||||
|
val shortAuthenticationStrings: List<String>,
|
||||||
|
val canonicalJson: String
|
||||||
|
) : ValidVerificationInfoStart(transactionId, fromDevice)
|
||||||
|
|
||||||
|
data class ReciprocateVerificationInfoStart(
|
||||||
|
override val transactionId: String,
|
||||||
|
override val fromDevice: String,
|
||||||
|
val sharedSecret: String
|
||||||
|
) : ValidVerificationInfoStart(transactionId, fromDevice)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
|
|
||||||
|
@ -27,18 +28,18 @@ internal interface VerificationTransport {
|
||||||
/**
|
/**
|
||||||
* Sends a message
|
* Sends a message
|
||||||
*/
|
*/
|
||||||
fun sendToOther(type: String,
|
fun <T> sendToOther(type: String,
|
||||||
verificationInfo: VerificationInfo,
|
verificationInfo: VerificationInfo<T>,
|
||||||
nextState: VerificationTxState,
|
nextState: VerificationTxState,
|
||||||
onErrorReason: CancelCode,
|
onErrorReason: CancelCode,
|
||||||
onDone: (() -> Unit)?)
|
onDone: (() -> Unit)?)
|
||||||
|
|
||||||
fun sendVerificationRequest(supportedMethods: List<String>,
|
fun sendVerificationRequest(supportedMethods: List<String>,
|
||||||
localID: String,
|
localId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
toDevices: List<String>?,
|
toDevices: List<String>?,
|
||||||
callback: (String?, VerificationInfoRequest?) -> Unit)
|
callback: (String?, ValidVerificationInfoRequest?) -> Unit)
|
||||||
|
|
||||||
fun cancelTransaction(transactionId: String,
|
fun cancelTransaction(transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
|
@ -64,7 +65,7 @@ internal interface VerificationTransport {
|
||||||
* Create start for SAS verification
|
* Create start for SAS verification
|
||||||
*/
|
*/
|
||||||
fun createStartForSas(fromDevice: String,
|
fun createStartForSas(fromDevice: String,
|
||||||
transactionID: String,
|
transactionId: String,
|
||||||
keyAgreementProtocols: List<String>,
|
keyAgreementProtocols: List<String>,
|
||||||
hashes: List<String>,
|
hashes: List<String>,
|
||||||
messageAuthenticationCodes: List<String>,
|
messageAuthenticationCodes: List<String>,
|
||||||
|
@ -74,7 +75,7 @@ internal interface VerificationTransport {
|
||||||
* Create start for QR code verification
|
* Create start for QR code verification
|
||||||
*/
|
*/
|
||||||
fun createStartForQrCode(fromDevice: String,
|
fun createStartForQrCode(fromDevice: String,
|
||||||
transactionID: String,
|
transactionId: String,
|
||||||
sharedSecret: String): VerificationInfoStart
|
sharedSecret: String): VerificationInfoStart
|
||||||
|
|
||||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||||
|
|
|
@ -21,8 +21,8 @@ import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.Operation
|
import androidx.work.Operation
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.R
|
import im.vector.matrix.android.R
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
@ -65,16 +65,15 @@ internal class VerificationTransportRoomMessage(
|
||||||
private val userId: String,
|
private val userId: String,
|
||||||
private val userDeviceId: String?,
|
private val userDeviceId: String?,
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val tx: DefaultVerificationTransaction?
|
private val tx: DefaultVerificationTransaction?
|
||||||
) : VerificationTransport {
|
) : VerificationTransport {
|
||||||
|
|
||||||
override fun sendToOther(type: String,
|
override fun <T> sendToOther(type: String,
|
||||||
verificationInfo: VerificationInfo,
|
verificationInfo: VerificationInfo<T>,
|
||||||
nextState: VerificationTxState,
|
nextState: VerificationTxState,
|
||||||
onErrorReason: CancelCode,
|
onErrorReason: CancelCode,
|
||||||
onDone: (() -> Unit)?) {
|
onDone: (() -> Unit)?) {
|
||||||
Timber.d("## SAS sending msg type $type")
|
Timber.d("## SAS sending msg type $type")
|
||||||
Timber.v("## SAS sending msg info $verificationInfo")
|
Timber.v("## SAS sending msg info $verificationInfo")
|
||||||
val event = createEventAndLocalEcho(
|
val event = createEventAndLocalEcho(
|
||||||
|
@ -138,26 +137,33 @@ internal class VerificationTransportRoomMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||||
localID: String,
|
localId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
toDevices: List<String>?,
|
toDevices: List<String>?,
|
||||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
||||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
||||||
// This transport requires a room
|
// This transport requires a room
|
||||||
requireNotNull(roomId)
|
requireNotNull(roomId)
|
||||||
|
|
||||||
|
val validInfo = ValidVerificationInfoRequest(
|
||||||
|
transactionId = "",
|
||||||
|
fromDevice = userDeviceId ?: "",
|
||||||
|
methods = supportedMethods,
|
||||||
|
timestamp = System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
|
||||||
val info = MessageVerificationRequestContent(
|
val info = MessageVerificationRequestContent(
|
||||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||||
fromDevice = userDeviceId ?: "",
|
fromDevice = validInfo.fromDevice,
|
||||||
toUserId = otherUserId,
|
toUserId = otherUserId,
|
||||||
timestamp = System.currentTimeMillis(),
|
timestamp = validInfo.timestamp,
|
||||||
methods = supportedMethods
|
methods = validInfo.methods
|
||||||
)
|
)
|
||||||
val content = info.toContent()
|
val content = info.toContent()
|
||||||
|
|
||||||
val event = createEventAndLocalEcho(
|
val event = createEventAndLocalEcho(
|
||||||
localID,
|
localId,
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
roomId,
|
roomId,
|
||||||
content
|
content
|
||||||
|
@ -192,8 +198,8 @@ internal class VerificationTransportRoomMessage(
|
||||||
?.let { wInfo ->
|
?.let { wInfo ->
|
||||||
if (wInfo.outputData.getBoolean("failed", false)) {
|
if (wInfo.outputData.getBoolean("failed", false)) {
|
||||||
callback(null, null)
|
callback(null, null)
|
||||||
} else if (wInfo.outputData.getString(localID) != null) {
|
} else if (wInfo.outputData.getString(localId) != null) {
|
||||||
callback(wInfo.outputData.getString(localID), info)
|
callback(wInfo.outputData.getString(localId), validInfo)
|
||||||
} else {
|
} else {
|
||||||
callback(null, null)
|
callback(null, null)
|
||||||
}
|
}
|
||||||
|
@ -272,7 +278,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = MessageVerificationMacContent.create(tid, mac, keys)
|
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = MessageVerificationMacContent.create(tid, mac, keys)
|
||||||
|
|
||||||
override fun createStartForSas(fromDevice: String,
|
override fun createStartForSas(fromDevice: String,
|
||||||
transactionID: String,
|
transactionId: String,
|
||||||
keyAgreementProtocols: List<String>,
|
keyAgreementProtocols: List<String>,
|
||||||
hashes: List<String>,
|
hashes: List<String>,
|
||||||
messageAuthenticationCodes: List<String>,
|
messageAuthenticationCodes: List<String>,
|
||||||
|
@ -286,14 +292,14 @@ internal class VerificationTransportRoomMessage(
|
||||||
VERIFICATION_METHOD_SAS,
|
VERIFICATION_METHOD_SAS,
|
||||||
RelationDefaultContent(
|
RelationDefaultContent(
|
||||||
type = RelationType.REFERENCE,
|
type = RelationType.REFERENCE,
|
||||||
eventId = transactionID
|
eventId = transactionId
|
||||||
),
|
),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createStartForQrCode(fromDevice: String,
|
override fun createStartForQrCode(fromDevice: String,
|
||||||
transactionID: String,
|
transactionId: String,
|
||||||
sharedSecret: String): VerificationInfoStart {
|
sharedSecret: String): VerificationInfoStart {
|
||||||
return MessageVerificationStartContent(
|
return MessageVerificationStartContent(
|
||||||
fromDevice,
|
fromDevice,
|
||||||
|
@ -304,7 +310,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
VERIFICATION_METHOD_RECIPROCATE,
|
VERIFICATION_METHOD_RECIPROCATE,
|
||||||
RelationDefaultContent(
|
RelationDefaultContent(
|
||||||
type = RelationType.REFERENCE,
|
type = RelationType.REFERENCE,
|
||||||
eventId = transactionID
|
eventId = transactionId
|
||||||
),
|
),
|
||||||
sharedSecret
|
sharedSecret
|
||||||
)
|
)
|
||||||
|
@ -321,15 +327,15 @@ internal class VerificationTransportRoomMessage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEventAndLocalEcho(localID: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
originServerTs = System.currentTimeMillis(),
|
originServerTs = System.currentTimeMillis(),
|
||||||
senderId = userId,
|
senderId = userId,
|
||||||
eventId = localID,
|
eventId = localId,
|
||||||
type = type,
|
type = type,
|
||||||
content = content,
|
content = content,
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
).also {
|
).also {
|
||||||
localEchoEventFactory.createLocalEcho(it)
|
localEchoEventFactory.createLocalEcho(it)
|
||||||
}
|
}
|
||||||
|
@ -347,7 +353,6 @@ internal class VerificationTransportRoomMessage(
|
||||||
internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val monarchy: Monarchy,
|
|
||||||
@SessionId
|
@SessionId
|
||||||
private val sessionId: String,
|
private val sessionId: String,
|
||||||
@UserId
|
@UserId
|
||||||
|
@ -357,6 +362,6 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory) {
|
private val localEchoEventFactory: LocalEchoEventFactory) {
|
||||||
|
|
||||||
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
|
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
|
||||||
return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, monarchy, localEchoEventFactory, tx)
|
return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, localEchoEventFactory, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
@ -46,28 +47,34 @@ internal class VerificationTransportToDevice(
|
||||||
) : VerificationTransport {
|
) : VerificationTransport {
|
||||||
|
|
||||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||||
localID: String,
|
localId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
toDevices: List<String>?,
|
toDevices: List<String>?,
|
||||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
||||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
val keyReq = KeyVerificationRequest(
|
val validKeyReq = ValidVerificationInfoRequest(
|
||||||
fromDevice = myDeviceId,
|
transactionId = localId,
|
||||||
|
fromDevice = myDeviceId ?: "",
|
||||||
methods = supportedMethods,
|
methods = supportedMethods,
|
||||||
timestamp = System.currentTimeMillis(),
|
timestamp = System.currentTimeMillis()
|
||||||
transactionID = localID
|
)
|
||||||
|
val keyReq = KeyVerificationRequest(
|
||||||
|
fromDevice = validKeyReq.fromDevice,
|
||||||
|
methods = validKeyReq.methods,
|
||||||
|
timestamp = validKeyReq.timestamp,
|
||||||
|
transactionId = validKeyReq.transactionId
|
||||||
)
|
)
|
||||||
toDevices?.forEach {
|
toDevices?.forEach {
|
||||||
contentMap.setObject(otherUserId, it, keyReq)
|
contentMap.setObject(otherUserId, it, keyReq)
|
||||||
}
|
}
|
||||||
sendToDeviceTask
|
sendToDeviceTask
|
||||||
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localID)) {
|
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) {
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
||||||
callback.invoke(localID, keyReq)
|
callback.invoke(localId, validKeyReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
|
@ -103,11 +110,11 @@ internal class VerificationTransportToDevice(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendToOther(type: String,
|
override fun <T> sendToOther(type: String,
|
||||||
verificationInfo: VerificationInfo,
|
verificationInfo: VerificationInfo<T>,
|
||||||
nextState: VerificationTxState,
|
nextState: VerificationTxState,
|
||||||
onErrorReason: CancelCode,
|
onErrorReason: CancelCode,
|
||||||
onDone: (() -> Unit)?) {
|
onDone: (() -> Unit)?) {
|
||||||
Timber.d("## SAS sending msg type $type")
|
Timber.d("## SAS sending msg type $type")
|
||||||
Timber.v("## SAS sending msg info $verificationInfo")
|
Timber.v("## SAS sending msg info $verificationInfo")
|
||||||
val tx = tx ?: return
|
val tx = tx ?: return
|
||||||
|
@ -197,7 +204,7 @@ internal class VerificationTransportToDevice(
|
||||||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = KeyVerificationMac.create(tid, mac, keys)
|
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = KeyVerificationMac.create(tid, mac, keys)
|
||||||
|
|
||||||
override fun createStartForSas(fromDevice: String,
|
override fun createStartForSas(fromDevice: String,
|
||||||
transactionID: String,
|
transactionId: String,
|
||||||
keyAgreementProtocols: List<String>,
|
keyAgreementProtocols: List<String>,
|
||||||
hashes: List<String>,
|
hashes: List<String>,
|
||||||
messageAuthenticationCodes: List<String>,
|
messageAuthenticationCodes: List<String>,
|
||||||
|
@ -205,7 +212,7 @@ internal class VerificationTransportToDevice(
|
||||||
return KeyVerificationStart(
|
return KeyVerificationStart(
|
||||||
fromDevice,
|
fromDevice,
|
||||||
VERIFICATION_METHOD_SAS,
|
VERIFICATION_METHOD_SAS,
|
||||||
transactionID,
|
transactionId,
|
||||||
keyAgreementProtocols,
|
keyAgreementProtocols,
|
||||||
hashes,
|
hashes,
|
||||||
messageAuthenticationCodes,
|
messageAuthenticationCodes,
|
||||||
|
@ -214,12 +221,12 @@ internal class VerificationTransportToDevice(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createStartForQrCode(fromDevice: String,
|
override fun createStartForQrCode(fromDevice: String,
|
||||||
transactionID: String,
|
transactionId: String,
|
||||||
sharedSecret: String): VerificationInfoStart {
|
sharedSecret: String): VerificationInfoStart {
|
||||||
return KeyVerificationStart(
|
return KeyVerificationStart(
|
||||||
fromDevice,
|
fromDevice,
|
||||||
VERIFICATION_METHOD_RECIPROCATE,
|
VERIFICATION_METHOD_RECIPROCATE,
|
||||||
transactionID,
|
transactionId,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -229,7 +236,7 @@ internal class VerificationTransportToDevice(
|
||||||
|
|
||||||
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
||||||
return KeyVerificationReady(
|
return KeyVerificationReady(
|
||||||
transactionID = tid,
|
transactionId = tid,
|
||||||
fromDevice = fromDevice,
|
fromDevice = fromDevice,
|
||||||
methods = methods
|
methods = methods
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,23 +16,22 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64Safe
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationTransaction
|
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationTransaction
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
import im.vector.matrix.android.internal.crypto.verification.ValidVerificationInfoStart
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
|
||||||
import im.vector.matrix.android.internal.util.exhaustive
|
import im.vector.matrix.android.internal.util.exhaustive
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class DefaultQrCodeVerificationTransaction(
|
internal class DefaultQrCodeVerificationTransaction(
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
override val transactionId: String,
|
override val transactionId: String,
|
||||||
override val otherUserId: String,
|
override val otherUserId: String,
|
||||||
override var otherDeviceId: String?,
|
override var otherDeviceId: String?,
|
||||||
|
@ -43,7 +42,15 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val deviceId: String,
|
val deviceId: String,
|
||||||
override val isIncoming: Boolean
|
override val isIncoming: Boolean
|
||||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction {
|
) : DefaultVerificationTransaction(
|
||||||
|
setDeviceVerificationAction,
|
||||||
|
crossSigningService,
|
||||||
|
userId,
|
||||||
|
transactionId,
|
||||||
|
otherUserId,
|
||||||
|
otherDeviceId,
|
||||||
|
isIncoming),
|
||||||
|
QrCodeVerificationTransaction {
|
||||||
|
|
||||||
override val qrCodeText: String?
|
override val qrCodeText: String?
|
||||||
get() = qrCodeData?.toEncodedString()
|
get() = qrCodeData?.toEncodedString()
|
||||||
|
@ -76,72 +83,95 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
// check master key
|
// check master key
|
||||||
|
val myMasterKey = crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey
|
||||||
|
var canTrustOtherUserMasterKey = false
|
||||||
|
|
||||||
|
// Check the other device view of my MSK
|
||||||
when (otherQrCodeData) {
|
when (otherQrCodeData) {
|
||||||
is QrCodeData.VerifyingAnotherUser -> {
|
is QrCodeData.VerifyingAnotherUser -> {
|
||||||
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey
|
// key2 (aka otherUserMasterCrossSigningPublicKey) is what the one displaying the QR code (other user) think my MSK is.
|
||||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
// Let's check that it's correct
|
||||||
|
// If not -> Cancel
|
||||||
|
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != myMasterKey) {
|
||||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
|
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
} else Unit
|
} else Unit
|
||||||
}
|
}
|
||||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
||||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
// key1 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
|
||||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
// Let's check that I see the same MSK
|
||||||
|
// If not -> Cancel
|
||||||
|
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
|
||||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
} else Unit
|
} else {
|
||||||
|
// I can trust the MSK then (i see the same one, and other session tell me it's trusted by him)
|
||||||
|
canTrustOtherUserMasterKey = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
||||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
// key2 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
|
||||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
// Let's check that it's the good one
|
||||||
|
// If not -> Cancel
|
||||||
|
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
|
||||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
} else Unit
|
} else {
|
||||||
|
// Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
|
|
||||||
val toVerifyDeviceIds = mutableListOf<String>()
|
val toVerifyDeviceIds = mutableListOf<String>()
|
||||||
var canTrustOtherUserMasterKey = false
|
|
||||||
|
|
||||||
// Check device key if available
|
// Let's now check the other user/device key material
|
||||||
when (otherQrCodeData) {
|
when (otherQrCodeData) {
|
||||||
is QrCodeData.VerifyingAnotherUser -> {
|
is QrCodeData.VerifyingAnotherUser -> {
|
||||||
|
// key1(aka userMasterCrossSigningPublicKey) is the MSK of the one displaying the QR code (i.e other user)
|
||||||
|
// Let's check that it matches what I think it should be
|
||||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
||||||
!= crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) {
|
!= crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
// It does so i should mark it as trusted
|
||||||
canTrustOtherUserMasterKey = true
|
canTrustOtherUserMasterKey = true
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
||||||
|
// key2 (aka otherDeviceKey) is my current device key in POV of the one displaying the QR code (i.e other device)
|
||||||
|
// Let's check that it's correct
|
||||||
if (otherQrCodeData.otherDeviceKey
|
if (otherQrCodeData.otherDeviceKey
|
||||||
!= cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) {
|
!= cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) {
|
||||||
Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}")
|
Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
} else Unit
|
} else Unit // Nothing special here, we will send a reciprocate start event, and then the other session will trust my device
|
||||||
|
// and thus allow me to request SSSS secret
|
||||||
}
|
}
|
||||||
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
||||||
|
// key1 (aka otherDeviceKey) is the device key of the one displaying the QR code (i.e other device)
|
||||||
|
// Let's check that it matches what I have locally
|
||||||
if (otherQrCodeData.deviceKey
|
if (otherQrCodeData.deviceKey
|
||||||
!= cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) {
|
!= cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) {
|
||||||
Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}")
|
Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
toVerifyDeviceIds.add(otherQrCodeData.deviceKey)
|
// Yes it does -> i should trust it and sign then upload the signature
|
||||||
|
toVerifyDeviceIds.add(otherDeviceId ?: "")
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
|
|
||||||
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
|
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
|
||||||
// Nothing to verify
|
// // Nothing to verify
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -152,10 +182,12 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
start(otherQrCodeData.sharedSecret)
|
start(otherQrCodeData.sharedSecret)
|
||||||
|
|
||||||
// Trust the other user
|
// Trust the other user
|
||||||
trust(canTrustOtherUserMasterKey, toVerifyDeviceIds.distinct())
|
trust(canTrustOtherUserMasterKey,
|
||||||
|
toVerifyDeviceIds.distinct(),
|
||||||
|
eventuallyMarkMyMasterKeyAsTrusted = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(remoteSecret: String) {
|
private fun start(remoteSecret: String) {
|
||||||
if (state != VerificationTxState.None) {
|
if (state != VerificationTxState.None) {
|
||||||
Timber.e("## Verification QR: start verification from invalid state")
|
Timber.e("## Verification QR: start verification from invalid state")
|
||||||
// should I cancel??
|
// should I cancel??
|
||||||
|
@ -177,9 +209,6 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
cancel(CancelCode.User)
|
cancel(CancelCode.User)
|
||||||
}
|
}
|
||||||
|
@ -192,14 +221,14 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
override fun isToDeviceTransport() = false
|
override fun isToDeviceTransport() = false
|
||||||
|
|
||||||
// Other user has scanned our QR code. check that the secret matched, so we can trust him
|
// Other user has scanned our QR code. check that the secret matched, so we can trust him
|
||||||
fun onStartReceived(startReq: VerificationInfoStart) {
|
fun onStartReceived(startReq: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) {
|
||||||
if (qrCodeData == null) {
|
if (qrCodeData == null) {
|
||||||
// Should not happen
|
// Should not happen
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startReq.sharedSecret == qrCodeData.sharedSecret) {
|
if (startReq.sharedSecret.fromBase64Safe()?.contentEquals(qrCodeData.sharedSecret.fromBase64()) == true) {
|
||||||
// Ok, we can trust the other user
|
// Ok, we can trust the other user
|
||||||
// We can only trust the master key in this case
|
// We can only trust the master key in this case
|
||||||
// But first, ask the user for a confirmation
|
// But first, ask the user for a confirmation
|
||||||
|
@ -211,7 +240,21 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun otherUserScannedMyQrCode() {
|
override fun otherUserScannedMyQrCode() {
|
||||||
trust(true, emptyList())
|
when (qrCodeData) {
|
||||||
|
is QrCodeData.VerifyingAnotherUser -> {
|
||||||
|
// Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice's key,
|
||||||
|
trust(true, emptyList(), false)
|
||||||
|
}
|
||||||
|
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
||||||
|
// I now know that I have the correct device key for other session,
|
||||||
|
// and can sign it with the self-signing key and upload the signature
|
||||||
|
trust(false, listOf(otherDeviceId ?: ""), false)
|
||||||
|
}
|
||||||
|
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
||||||
|
// I now know that i can trust my MSK
|
||||||
|
trust(true, emptyList(), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun otherUserDidNotScannedMyQrCode() {
|
override fun otherUserDidNotScannedMyQrCode() {
|
||||||
|
@ -219,46 +262,4 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||||
// At least remove the transaction...
|
// At least remove the transaction...
|
||||||
state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true)
|
state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List<String>) {
|
|
||||||
// If not me sign his MSK and upload the signature
|
|
||||||
if (canTrustOtherUserMasterKey) {
|
|
||||||
if (otherUserId != userId) {
|
|
||||||
// we should trust this master key
|
|
||||||
// And check verification MSK -> SSK?
|
|
||||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Mark my keys as trusted locally
|
|
||||||
crossSigningService.markMyMasterKeyAsTrusted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherUserId == userId) {
|
|
||||||
// If me it's reasonable to sign and upload the device signature
|
|
||||||
// Notice that i might not have the private keys, so may not be able to do it
|
|
||||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO what if the otherDevice is not in this list? and should we
|
|
||||||
toVerifyDeviceIds.forEach {
|
|
||||||
setDeviceVerified(otherUserId, it)
|
|
||||||
}
|
|
||||||
transport.done(transactionId)
|
|
||||||
state = VerificationTxState.Verified
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
|
||||||
// TODO should not override cross sign status
|
|
||||||
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
|
||||||
userId,
|
|
||||||
deviceId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding
|
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||||
|
|
||||||
|
@ -52,15 +52,15 @@ fun QrCodeData.toEncodedString(): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys
|
// Keys
|
||||||
firstKey.fromBase64NoPadding().forEach {
|
firstKey.fromBase64().forEach {
|
||||||
result += it
|
result += it
|
||||||
}
|
}
|
||||||
secondKey.fromBase64NoPadding().forEach {
|
secondKey.fromBase64().forEach {
|
||||||
result += it
|
result += it
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret
|
// Secret
|
||||||
sharedSecret.fromBase64NoPadding().forEach {
|
sharedSecret.fromBase64().forEach {
|
||||||
result += it
|
result += it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,11 +94,11 @@ fun String.toQrCodeData(): QrCodeData? {
|
||||||
val mode = byteArray[cursor].toInt()
|
val mode = byteArray[cursor].toInt()
|
||||||
cursor++
|
cursor++
|
||||||
|
|
||||||
// Get transaction length
|
// Get transaction length, Big Endian format
|
||||||
val bigEndian1 = byteArray[cursor].toUnsignedInt()
|
val msb = byteArray[cursor].toUnsignedInt()
|
||||||
val bigEndian2 = byteArray[cursor + 1].toUnsignedInt()
|
val lsb = byteArray[cursor + 1].toUnsignedInt()
|
||||||
|
|
||||||
val transactionLength = bigEndian1 * 0x0100 + bigEndian2
|
val transactionLength = msb.shl(8) + lsb
|
||||||
|
|
||||||
cursor++
|
cursor++
|
||||||
cursor++
|
cursor++
|
||||||
|
|
|
@ -25,3 +25,7 @@ annotation class SessionFilesDirectory
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class SessionCacheDirectory
|
annotation class SessionCacheDirectory
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class CacheDirectory
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class])
|
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class])
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
|
@ -52,6 +53,9 @@ internal interface MatrixComponent {
|
||||||
|
|
||||||
fun resources(): Resources
|
fun resources(): Resources
|
||||||
|
|
||||||
|
@CacheDirectory
|
||||||
|
fun cacheDir(): File
|
||||||
|
|
||||||
fun olmManager(): OlmManager
|
fun olmManager(): OlmManager
|
||||||
|
|
||||||
fun taskExecutor(): TaskExecutor
|
fun taskExecutor(): TaskExecutor
|
||||||
|
|
|
@ -26,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
|
import java.io.File
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -49,6 +50,13 @@ internal object MatrixModule {
|
||||||
return context.resources
|
return context.resources
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@CacheDirectory
|
||||||
|
fun providesCacheDir(context: Context): File {
|
||||||
|
return context.cacheDir
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
|
|
|
@ -24,11 +24,11 @@ import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
|
import im.vector.matrix.android.internal.di.CacheDirectory
|
||||||
import im.vector.matrix.android.internal.di.SessionCacheDirectory
|
import im.vector.matrix.android.internal.di.SessionCacheDirectory
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.md5
|
|
||||||
import im.vector.matrix.android.internal.util.toCancelable
|
import im.vector.matrix.android.internal.util.toCancelable
|
||||||
import im.vector.matrix.android.internal.util.writeToFile
|
import im.vector.matrix.android.internal.util.writeToFile
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
@ -42,8 +42,10 @@ import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultFileService @Inject constructor(
|
internal class DefaultFileService @Inject constructor(
|
||||||
@SessionCacheDirectory
|
@CacheDirectory
|
||||||
private val cacheDirectory: File,
|
private val cacheDirectory: File,
|
||||||
|
@SessionCacheDirectory
|
||||||
|
private val sessionCacheDirectory: File,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
@Unauthenticated
|
@Unauthenticated
|
||||||
private val okHttpClient: OkHttpClient,
|
private val okHttpClient: OkHttpClient,
|
||||||
|
@ -62,60 +64,50 @@ internal class DefaultFileService @Inject constructor(
|
||||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
return GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
Try {
|
Try {
|
||||||
val folder = getFolder(downloadMode, id)
|
val folder = File(sessionCacheDirectory, "MF")
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdirs()
|
||||||
|
}
|
||||||
File(folder, fileName)
|
File(folder, fileName)
|
||||||
}.flatMap { destFile ->
|
}.flatMap { destFile ->
|
||||||
if (!destFile.exists() || downloadMode == FileService.DownloadMode.TO_EXPORT) {
|
if (!destFile.exists()) {
|
||||||
Try {
|
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
|
||||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(resolvedUrl)
|
.url(resolvedUrl)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = okHttpClient.newCall(request).execute()
|
val response = okHttpClient.newCall(request).execute()
|
||||||
var inputStream = response.body?.byteStream()
|
var inputStream = response.body?.byteStream()
|
||||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||||
if (!response.isSuccessful
|
if (!response.isSuccessful || inputStream == null) {
|
||||||
|| inputStream == null) {
|
return@flatMap Try.Failure(IOException())
|
||||||
throw IOException()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementToDecrypt != null) {
|
|
||||||
Timber.v("## decrypt file")
|
|
||||||
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
|
||||||
?: throw IllegalStateException("Decryption error")
|
|
||||||
}
|
|
||||||
|
|
||||||
writeToFile(inputStream, destFile)
|
|
||||||
destFile
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Try.just(destFile)
|
if (elementToDecrypt != null) {
|
||||||
|
Timber.v("## decrypt file")
|
||||||
|
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||||
|
?: return@flatMap Try.Failure(IllegalStateException("Decryption error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToFile(inputStream, destFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Try.just(copyFile(destFile, downloadMode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foldToCallback(callback)
|
.foldToCallback(callback)
|
||||||
}.toCancelable()
|
}.toCancelable()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFolder(downloadMode: FileService.DownloadMode, id: String): File {
|
private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File {
|
||||||
return when (downloadMode) {
|
return when (downloadMode) {
|
||||||
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
FileService.DownloadMode.TO_EXPORT ->
|
||||||
// Create dir tree (MF stands for Matrix File):
|
file.copyTo(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), file.name), true)
|
||||||
// <cache>/<sessionId>/MF/<md5(id)>/
|
FileService.DownloadMode.FOR_EXTERNAL_SHARE ->
|
||||||
val tmpFolderSession = File(cacheDirectory, "MF")
|
file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true)
|
||||||
File(tmpFolderSession, id.md5())
|
FileService.DownloadMode.FOR_INTERNAL_USE ->
|
||||||
}
|
file
|
||||||
FileService.DownloadMode.TO_EXPORT -> {
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.also { folder ->
|
|
||||||
if (!folder.exists()) {
|
|
||||||
folder.mkdirs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
@ -33,7 +34,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
||||||
val listeners = listeners.getOrPut(key) { ArrayList() }
|
val listeners = listeners.getOrPut(key) { ArrayList() }
|
||||||
listeners.add(updateListener)
|
listeners.add(updateListener)
|
||||||
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
|
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
|
||||||
mainHandler.post { updateListener.onUpdate(currentState) }
|
mainHandler.post {
|
||||||
|
try {
|
||||||
|
updateListener.onUpdate(currentState)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## ContentUploadStateTracker.onUpdate() failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun untrack(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
|
override fun untrack(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
|
||||||
|
@ -79,7 +86,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
||||||
private fun updateState(key: String, state: ContentUploadStateTracker.State) {
|
private fun updateState(key: String, state: ContentUploadStateTracker.State) {
|
||||||
states[key] = state
|
states[key] = state
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
listeners[key]?.forEach { it.onUpdate(state) }
|
listeners[key]?.forEach {
|
||||||
|
try {
|
||||||
|
it.onUpdate(state)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## ContentUploadStateTracker.onUpdate() failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val events: List<Event>,
|
val events: List<Event>,
|
||||||
val attachment: ContentAttachmentData,
|
val attachment: ContentAttachmentData,
|
||||||
val isRoomEncrypted: Boolean,
|
val isEncrypted: Boolean,
|
||||||
val compressBeforeSending: Boolean,
|
val compressBeforeSending: Boolean,
|
||||||
override val lastFailureMessage: String? = null
|
override val lastFailureMessage: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
@ -90,9 +90,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
||||||
return Result.success(
|
return Result.success(
|
||||||
WorkerParamsFactory.toData(params.copy(
|
WorkerParamsFactory.toData(
|
||||||
lastFailureMessage = e.localizedMessage
|
params.copy(
|
||||||
))
|
lastFailureMessage = e.localizedMessage
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.let { originalFile ->
|
.let { originalFile ->
|
||||||
|
@ -136,7 +138,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val contentUploadResponse = if (params.isRoomEncrypted) {
|
val contentUploadResponse = if (params.isEncrypted) {
|
||||||
Timber.v("Encrypt thumbnail")
|
Timber.v("Encrypt thumbnail")
|
||||||
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
|
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
|
||||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
|
val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
|
||||||
|
@ -174,18 +176,18 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
|
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val contentUploadResponse = if (params.isRoomEncrypted) {
|
val contentUploadResponse = if (params.isEncrypted) {
|
||||||
Timber.v("Encrypt file")
|
Timber.v("Encrypt file")
|
||||||
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
|
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
|
||||||
|
|
||||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
|
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType())
|
||||||
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
|
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
|
||||||
|
|
||||||
fileUploader
|
fileUploader
|
||||||
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
|
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
|
||||||
} else {
|
} else {
|
||||||
fileUploader
|
fileUploader
|
||||||
.uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
|
.uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSuccess(params,
|
handleSuccess(params,
|
||||||
|
@ -226,7 +228,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
|
updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isRoomEncrypted)
|
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isEncrypted)
|
||||||
return Result.success(WorkerParamsFactory.toData(sendParams))
|
return Result.success(WorkerParamsFactory.toData(sendParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind
|
||||||
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
||||||
import im.vector.matrix.android.api.pushrules.getActions
|
import im.vector.matrix.android.api.pushrules.getActions
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
|
import im.vector.matrix.android.api.pushrules.rest.RuleSet
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
|
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
|
||||||
|
@ -31,6 +32,7 @@ import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.pushers.AddPushRuleTask
|
import im.vector.matrix.android.internal.session.pushers.AddPushRuleTask
|
||||||
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
|
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
|
||||||
import im.vector.matrix.android.internal.session.pushers.RemovePushRuleTask
|
import im.vector.matrix.android.internal.session.pushers.RemovePushRuleTask
|
||||||
|
import im.vector.matrix.android.internal.session.pushers.UpdatePushRuleActionsTask
|
||||||
import im.vector.matrix.android.internal.session.pushers.UpdatePushRuleEnableStatusTask
|
import im.vector.matrix.android.internal.session.pushers.UpdatePushRuleEnableStatusTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
@ -42,6 +44,7 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||||
private val getPushRulesTask: GetPushRulesTask,
|
private val getPushRulesTask: GetPushRulesTask,
|
||||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||||
private val addPushRuleTask: AddPushRuleTask,
|
private val addPushRuleTask: AddPushRuleTask,
|
||||||
|
private val updatePushRuleActionsTask: UpdatePushRuleActionsTask,
|
||||||
private val removePushRuleTask: RemovePushRuleTask,
|
private val removePushRuleTask: RemovePushRuleTask,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val monarchy: Monarchy
|
private val monarchy: Monarchy
|
||||||
|
@ -55,7 +58,7 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPushRules(scope: String): List<PushRule> {
|
override fun getPushRules(scope: String): RuleSet {
|
||||||
var contentRules: List<PushRule> = emptyList()
|
var contentRules: List<PushRule> = emptyList()
|
||||||
var overrideRules: List<PushRule> = emptyList()
|
var overrideRules: List<PushRule> = emptyList()
|
||||||
var roomRules: List<PushRule> = emptyList()
|
var roomRules: List<PushRule> = emptyList()
|
||||||
|
@ -90,8 +93,13 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ref. for the order: https://matrix.org/docs/spec/client_server/latest#push-rules
|
return RuleSet(
|
||||||
return overrideRules + contentRules + roomRules + senderRules + underrideRules
|
content = contentRules,
|
||||||
|
override = overrideRules,
|
||||||
|
room = roomRules,
|
||||||
|
sender = senderRules,
|
||||||
|
underride = underrideRules
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
@ -111,6 +119,14 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return updatePushRuleActionsTask
|
||||||
|
.configureWith(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
|
override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
return removePushRuleTask
|
return removePushRuleTask
|
||||||
.configureWith(RemovePushRuleTask.Params(kind, pushRule)) {
|
.configureWith(RemovePushRuleTask.Params(kind, pushRule)) {
|
||||||
|
|
|
@ -40,24 +40,35 @@ import im.vector.matrix.android.internal.di.SerializeNulls
|
||||||
* }
|
* }
|
||||||
* </code>
|
* </code>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class JsonPusher(
|
internal data class JsonPusher(
|
||||||
/**
|
/**
|
||||||
* Required. This is a unique identifier for this pusher. See /set for more detail. Max length, 512 bytes.
|
* Required. This is a unique identifier for this pusher. The value you should use for this is the routing or
|
||||||
|
* destination address information for the notification, for example, the APNS token for APNS or the
|
||||||
|
* Registration ID for GCM. If your notification client has no such concept, use any unique identifier.
|
||||||
|
* Max length, 512 bytes.
|
||||||
|
*
|
||||||
|
* If the kind is "email", this is the email address to send notifications to.
|
||||||
*/
|
*/
|
||||||
@Json(name = "pushkey")
|
@Json(name = "pushkey")
|
||||||
val pushKey: String,
|
val pushKey: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. The kind of pusher. "http" is a pusher that sends HTTP pokes.
|
* Required. The kind of pusher to configure.
|
||||||
|
* "http" makes a pusher that sends HTTP pokes.
|
||||||
|
* "email" makes a pusher that emails the user with unread notifications.
|
||||||
|
* null deletes the pusher.
|
||||||
*/
|
*/
|
||||||
@SerializeNulls
|
@SerializeNulls
|
||||||
@Json(name = "kind")
|
@Json(name = "kind")
|
||||||
val kind: String?,
|
val kind: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. This is a reverse-DNS style identifier for the application. Max length, 64 chars.
|
* Required. This is a reverse-DNS style identifier for the application. It is recommended that this end
|
||||||
|
* with the platform, such that different platform versions get different app identifiers.
|
||||||
|
* Max length, 64 chars.
|
||||||
|
*
|
||||||
|
* If the kind is "email", this is "m.email".
|
||||||
*/
|
*/
|
||||||
@Json(name = "app_id")
|
@Json(name = "app_id")
|
||||||
val appId: String,
|
val appId: String,
|
||||||
|
@ -88,15 +99,17 @@ internal data class JsonPusher(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A dictionary of information for the pusher implementation itself.
|
* Required. A dictionary of information for the pusher implementation itself.
|
||||||
|
* If kind is http, this should contain url which is the URL to use to send notifications to.
|
||||||
*/
|
*/
|
||||||
@Json(name = "data")
|
@Json(name = "data")
|
||||||
val data: JsonPusherData? = null,
|
val data: JsonPusherData? = null,
|
||||||
|
|
||||||
// Only used to update add Pusher (body of api request)
|
/**
|
||||||
// Used If true, the homeserver should add another pusher with the given pushkey and App ID in addition
|
* If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others
|
||||||
// to any others with different user IDs.
|
* with different user IDs. Otherwise, the homeserver must remove any other pushers with the same App ID and pushkey
|
||||||
// Otherwise, the homeserver must remove any other pushers with the same App ID and pushkey for different users.
|
* for different users.
|
||||||
// The default is false.
|
* The default is false.
|
||||||
|
*/
|
||||||
@Json(name = "append")
|
@Json(name = "append")
|
||||||
val append: Boolean? = false
|
val append: Boolean? = false
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,12 +22,14 @@ import com.squareup.moshi.JsonClass
|
||||||
internal data class JsonPusherData(
|
internal data class JsonPusherData(
|
||||||
/**
|
/**
|
||||||
* Required if kind is http. The URL to use to send notifications to.
|
* Required if kind is http. The URL to use to send notifications to.
|
||||||
|
* MUST be an HTTPS URL with a path of /_matrix/push/v1/notify.
|
||||||
*/
|
*/
|
||||||
@Json(name = "url")
|
@Json(name = "url")
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The format to use when sending notifications to the Push Gateway.
|
* The format to send notifications in to Push Gateways if the kind is http.
|
||||||
|
* Currently the only format available is 'event_id_only'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "format")
|
@Json(name = "format")
|
||||||
val format: String? = null
|
val format: String? = null
|
||||||
|
|
|
@ -47,6 +47,7 @@ internal interface PushRulesApi {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the ruleID action
|
* Update the ruleID action
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions
|
||||||
*
|
*
|
||||||
* @param kind the notification kind (sender, room...)
|
* @param kind the notification kind (sender, room...)
|
||||||
* @param ruleId the ruleId
|
* @param ruleId the ruleId
|
||||||
|
|
|
@ -72,6 +72,9 @@ internal abstract class PushersModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAddPushRuleTask(task: DefaultAddPushRuleTask): AddPushRuleTask
|
abstract fun bindAddPushRuleTask(task: DefaultAddPushRuleTask): AddPushRuleTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUpdatePushRuleActionTask(task: DefaultUpdatePushRuleActionsTask): UpdatePushRuleActionsTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRemovePushRuleTask(task: DefaultRemovePushRuleTask): RemovePushRuleTask
|
abstract fun bindRemovePushRuleTask(task: DefaultRemovePushRuleTask): RemovePushRuleTask
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.session.pushers
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.pushrules.RuleKind
|
||||||
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface UpdatePushRuleActionsTask : Task<UpdatePushRuleActionsTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val kind: RuleKind,
|
||||||
|
val oldPushRule: PushRule,
|
||||||
|
val newPushRule: PushRule
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
|
||||||
|
private val pushRulesApi: PushRulesApi,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : UpdatePushRuleActionsTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
|
||||||
|
if (params.oldPushRule.enabled != params.newPushRule.enabled) {
|
||||||
|
// First change enabled state
|
||||||
|
executeRequest<Unit>(eventBus) {
|
||||||
|
apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.newPushRule.enabled) {
|
||||||
|
// Also ensure the actions are up to date
|
||||||
|
val body = mapOf("actions" to params.newPushRule.actions)
|
||||||
|
|
||||||
|
executeRequest<Unit>(eventBus) {
|
||||||
|
apiCall = pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ import javax.inject.Inject
|
||||||
* (the transaction ID), this id is used when receiving an event from a sync to check if this event
|
* (the transaction ID), this id is used when receiving an event from a sync to check if this event
|
||||||
* is matching an existing local echo.
|
* is matching an existing local echo.
|
||||||
*
|
*
|
||||||
* The transactionID is used as loc
|
* The transactionId is used as loc
|
||||||
*/
|
*/
|
||||||
internal class LocalEchoEventFactory @Inject constructor(
|
internal class LocalEchoEventFactory @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
|
@ -261,7 +261,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
msgType = MessageType.MSGTYPE_IMAGE,
|
msgType = MessageType.MSGTYPE_IMAGE,
|
||||||
body = attachment.name ?: "image",
|
body = attachment.name ?: "image",
|
||||||
info = ImageInfo(
|
info = ImageInfo(
|
||||||
mimeType = attachment.mimeType,
|
mimeType = attachment.getSafeMimeType(),
|
||||||
width = width?.toInt() ?: 0,
|
width = width?.toInt() ?: 0,
|
||||||
height = height?.toInt() ?: 0,
|
height = height?.toInt() ?: 0,
|
||||||
size = attachment.size.toInt()
|
size = attachment.size.toInt()
|
||||||
|
@ -293,7 +293,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
msgType = MessageType.MSGTYPE_VIDEO,
|
msgType = MessageType.MSGTYPE_VIDEO,
|
||||||
body = attachment.name ?: "video",
|
body = attachment.name ?: "video",
|
||||||
videoInfo = VideoInfo(
|
videoInfo = VideoInfo(
|
||||||
mimeType = attachment.mimeType,
|
mimeType = attachment.getSafeMimeType(),
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
size = attachment.size,
|
size = attachment.size,
|
||||||
|
@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
msgType = MessageType.MSGTYPE_AUDIO,
|
msgType = MessageType.MSGTYPE_AUDIO,
|
||||||
body = attachment.name ?: "audio",
|
body = attachment.name ?: "audio",
|
||||||
audioInfo = AudioInfo(
|
audioInfo = AudioInfo(
|
||||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||||
size = attachment.size
|
size = attachment.size
|
||||||
),
|
),
|
||||||
url = attachment.path
|
url = attachment.path
|
||||||
|
@ -325,7 +325,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
msgType = MessageType.MSGTYPE_FILE,
|
msgType = MessageType.MSGTYPE_FILE,
|
||||||
body = attachment.name ?: "file",
|
body = attachment.name ?: "file",
|
||||||
info = FileInfo(
|
info = FileInfo(
|
||||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() }
|
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }
|
||||||
?: "application/octet-stream",
|
?: "application/octet-stream",
|
||||||
size = attachment.size
|
size = attachment.size
|
||||||
),
|
),
|
||||||
|
@ -335,25 +335,25 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEvent(roomId: String, content: Any? = null): Event {
|
private fun createEvent(roomId: String, content: Any? = null): Event {
|
||||||
val localID = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
originServerTs = dummyOriginServerTs(),
|
originServerTs = dummyOriginServerTs(),
|
||||||
senderId = userId,
|
senderId = userId,
|
||||||
eventId = localID,
|
eventId = localId,
|
||||||
type = EventType.MESSAGE,
|
type = EventType.MESSAGE,
|
||||||
content = content.toContent(),
|
content = content.toContent(),
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createVerificationRequest(roomId: String, fromDevice: String, toUserId: String, methods: List<String>): Event {
|
fun createVerificationRequest(roomId: String, fromDevice: String, toUserId: String, methods: List<String>): Event {
|
||||||
val localID = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
originServerTs = dummyOriginServerTs(),
|
originServerTs = dummyOriginServerTs(),
|
||||||
senderId = userId,
|
senderId = userId,
|
||||||
eventId = localID,
|
eventId = localId,
|
||||||
type = EventType.MESSAGE,
|
type = EventType.MESSAGE,
|
||||||
content = MessageVerificationRequestContent(
|
content = MessageVerificationRequestContent(
|
||||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||||
|
@ -362,7 +362,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
timestamp = System.currentTimeMillis(),
|
timestamp = System.currentTimeMillis(),
|
||||||
methods = methods
|
methods = methods
|
||||||
).toContent(),
|
).toContent(),
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,16 +469,16 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event {
|
fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event {
|
||||||
val localID = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
originServerTs = dummyOriginServerTs(),
|
originServerTs = dummyOriginServerTs(),
|
||||||
senderId = userId,
|
senderId = userId,
|
||||||
eventId = localID,
|
eventId = localId,
|
||||||
type = EventType.REDACTION,
|
type = EventType.REDACTION,
|
||||||
redacts = eventId,
|
redacts = eventId,
|
||||||
content = reason?.let { mapOf("reason" to it).toContent() },
|
content = reason?.let { mapOf("reason" to it).toContent() },
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ internal class SyncResponseHandler @Inject constructor(private val monarchy: Mon
|
||||||
return
|
return
|
||||||
} // nothing on initial sync
|
} // nothing on initial sync
|
||||||
|
|
||||||
val rules = pushRuleService.getPushRules(RuleScope.GLOBAL)
|
val rules = pushRuleService.getPushRules(RuleScope.GLOBAL).getAllRules()
|
||||||
processEventForPushTask.execute(ProcessEventForPushTask.Params(roomsSyncResponse, rules))
|
processEventForPushTask.execute(ProcessEventForPushTask.Params(roomsSyncResponse, rules))
|
||||||
Timber.v("[PushRules] <-- Push task scheduled")
|
Timber.v("[PushRules] <-- Push task scheduled")
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 510 B |
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:paddingBottom="10dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/imageView_icon_and_text"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginLeft="10dp"
|
|
||||||
android:layout_marginRight="10dp"
|
|
||||||
android:src="@drawable/matrix_user" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView_icon_and_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingRight="8dp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
tools:text="A text here" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<ListView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/listView_icon_and_text"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:background="#248801">
|
|
||||||
|
|
||||||
<org.matrix.androidsdk.view.AutoScrollDownListView
|
|
||||||
android:id="@+id/listView_messages"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:background="#190986"
|
|
||||||
android:cacheColorHint="@android:color/transparent"
|
|
||||||
android:childDivider="@android:color/transparent"
|
|
||||||
android:divider="#ffffff"
|
|
||||||
android:dividerHeight="0dp"
|
|
||||||
android:listSelector="@android:color/transparent"
|
|
||||||
android:transcriptMode="normal"
|
|
||||||
tools:layout_height="120dp" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -1,3 +0,0 @@
|
||||||
<resources>
|
|
||||||
|
|
||||||
</resources>
|
|
3
tools/workmanager/dumpsys_debug.sh
Executable file
3
tools/workmanager/dumpsys_debug.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
adb shell dumpsys jobscheduler im.vector.riotx.debug
|
3
tools/workmanager/dumpsys_release.sh
Executable file
3
tools/workmanager/dumpsys_release.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
adb shell dumpsys jobscheduler im.vector.riotx
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 17
|
ext.versionMinor = 18
|
||||||
ext.versionPatch = 0
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
|
@ -296,7 +296,7 @@ dependencies {
|
||||||
implementation 'com.airbnb.android:mvrx:1.3.0'
|
implementation 'com.airbnb.android:mvrx:1.3.0'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
||||||
|
|
|
@ -6,9 +6,15 @@
|
||||||
<issue id="MissingTranslation" severity="warning" />
|
<issue id="MissingTranslation" severity="warning" />
|
||||||
<issue id="TypographyEllipsis" severity="error" />
|
<issue id="TypographyEllipsis" severity="error" />
|
||||||
<issue id="ImpliedQuantity" severity="warning" />
|
<issue id="ImpliedQuantity" severity="warning" />
|
||||||
|
<issue id="IconXmlAndPng" severity="error" />
|
||||||
|
<issue id="IconDipSize" severity="error" />
|
||||||
|
<issue id="IconDuplicatesConfig" severity="error" />
|
||||||
|
<issue id="IconDuplicates" severity="error" />
|
||||||
|
<issue id="IconExpectedSize" severity="error" />
|
||||||
|
|
||||||
<!-- UX -->
|
<!-- UX -->
|
||||||
<issue id="ButtonOrder" severity="error" />
|
<issue id="ButtonOrder" severity="error" />
|
||||||
|
<issue id="TextFields" severity="error" />
|
||||||
|
|
||||||
<!-- Layout -->
|
<!-- Layout -->
|
||||||
<issue id="UnknownIdInLayout" severity="error" />
|
<issue id="UnknownIdInLayout" severity="error" />
|
||||||
|
@ -19,6 +25,7 @@
|
||||||
<issue id="InefficientWeight" severity="error" />
|
<issue id="InefficientWeight" severity="error" />
|
||||||
<issue id="DisableBaselineAlignment" severity="error" />
|
<issue id="DisableBaselineAlignment" severity="error" />
|
||||||
<issue id="ScrollViewSize" severity="error" />
|
<issue id="ScrollViewSize" severity="error" />
|
||||||
|
<issue id="NegativeMargin" severity="error" />
|
||||||
|
|
||||||
<!-- RTL -->
|
<!-- RTL -->
|
||||||
<issue id="RtlEnabled" severity="error" />
|
<issue id="RtlEnabled" severity="error" />
|
||||||
|
@ -30,9 +37,22 @@
|
||||||
<issue id="SetTextI18n" severity="error" />
|
<issue id="SetTextI18n" severity="error" />
|
||||||
<issue id="ViewConstructor" severity="error" />
|
<issue id="ViewConstructor" severity="error" />
|
||||||
<issue id="UseValueOf" severity="error" />
|
<issue id="UseValueOf" severity="error" />
|
||||||
|
<issue id="Recycle" severity="error" />
|
||||||
|
<issue id="KotlinPropertyAccess" severity="error" />
|
||||||
|
|
||||||
<!-- Ignore error from HtmlCompressor lib -->
|
<!-- Ignore error from HtmlCompressor lib -->
|
||||||
<issue id="InvalidPackage">
|
<issue id="InvalidPackage">
|
||||||
<ignore path="**/htmlcompressor-1.4.jar"/>
|
<ignore path="**/htmlcompressor-1.4.jar" />
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
<!-- Manifest -->
|
||||||
|
<issue id="PermissionImpliesUnsupportedChromeOsHardware" severity="error" />
|
||||||
|
|
||||||
|
<!-- Timber -->
|
||||||
|
<!-- This rule is failing on CI because it's marked as unknwown rule id :/-->
|
||||||
|
<!-- <issue id="BinaryOperationInTimber" severity="error" />-->
|
||||||
|
|
||||||
|
<!-- Wording -->
|
||||||
|
<!-- TODO When strings are imported from Weblate, move this to error -->
|
||||||
|
<issue id="Typos" severity="warning" />
|
||||||
</lint>
|
</lint>
|
||||||
|
|
|
@ -114,7 +114,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||||
.setContentText("Content")
|
.setContentText("Content")
|
||||||
// No effect because it's a group summary notif
|
// No effect because it's a group summary notif
|
||||||
.setNumber(33)
|
.setNumber(33)
|
||||||
.setSmallIcon(R.drawable.logo_transparent)
|
.setSmallIcon(R.drawable.ic_status_bar)
|
||||||
// This provocate the badge issue: no badge for group notification
|
// This provocate the badge issue: no badge for group notification
|
||||||
.setGroup("GroupKey")
|
.setGroup("GroupKey")
|
||||||
.setGroupSummary(true)
|
.setGroupSummary(true)
|
||||||
|
@ -147,7 +147,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||||
// For shortcut on long press on launcher icon
|
// For shortcut on long press on launcher icon
|
||||||
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
|
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
|
||||||
.setStyle(messagingStyle1)
|
.setStyle(messagingStyle1)
|
||||||
.setSmallIcon(R.drawable.logo_transparent)
|
.setSmallIcon(R.drawable.ic_status_bar)
|
||||||
.setGroup("GroupKey")
|
.setGroup("GroupKey")
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
@ -159,7 +159,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||||
.setContentTitle("Title 2")
|
.setContentTitle("Title 2")
|
||||||
.setContentText("Content 2")
|
.setContentText("Content 2")
|
||||||
.setStyle(messagingStyle2)
|
.setStyle(messagingStyle2)
|
||||||
.setSmallIcon(R.drawable.logo_transparent)
|
.setSmallIcon(R.drawable.ic_status_bar)
|
||||||
.setGroup("GroupKey")
|
.setGroup("GroupKey")
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ object FcmHelper {
|
||||||
*
|
*
|
||||||
* @param activity the first launch Activity
|
* @param activity the first launch Activity
|
||||||
*/
|
*/
|
||||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
|
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import im.vector.riotx.fdroid.features.settings.troubleshoot.TestAutoStartBoot
|
||||||
import im.vector.riotx.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions
|
import im.vector.riotx.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions
|
||||||
import im.vector.riotx.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
import im.vector.riotx.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestAccountSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestAccountSettings
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestBingRulesSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestPushRulesSettings
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestDeviceSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestDeviceSettings
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestSystemSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestSystemSettings
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -28,7 +28,7 @@ import javax.inject.Inject
|
||||||
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
|
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
|
||||||
private val testAccountSettings: TestAccountSettings,
|
private val testAccountSettings: TestAccountSettings,
|
||||||
private val testDeviceSettings: TestDeviceSettings,
|
private val testDeviceSettings: TestDeviceSettings,
|
||||||
private val testBingRulesSettings: TestBingRulesSettings,
|
private val testPushRulesSettings: TestPushRulesSettings,
|
||||||
private val testAutoStartBoot: TestAutoStartBoot,
|
private val testAutoStartBoot: TestAutoStartBoot,
|
||||||
private val testBackgroundRestrictions: TestBackgroundRestrictions) {
|
private val testBackgroundRestrictions: TestBackgroundRestrictions) {
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
|
||||||
mgr.addTest(testSystemSettings)
|
mgr.addTest(testSystemSettings)
|
||||||
mgr.addTest(testAccountSettings)
|
mgr.addTest(testAccountSettings)
|
||||||
mgr.addTest(testDeviceSettings)
|
mgr.addTest(testDeviceSettings)
|
||||||
mgr.addTest(testBingRulesSettings)
|
mgr.addTest(testPushRulesSettings)
|
||||||
mgr.addTest(testAutoStartBoot)
|
mgr.addTest(testAutoStartBoot)
|
||||||
mgr.addTest(testBackgroundRestrictions)
|
mgr.addTest(testBackgroundRestrictions)
|
||||||
return mgr
|
return mgr
|
||||||
|
|
|
@ -25,13 +25,13 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService
|
import com.google.firebase.messaging.FirebaseMessagingService
|
||||||
import com.google.firebase.messaging.RemoteMessage
|
import com.google.firebase.messaging.RemoteMessage
|
||||||
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.riotx.BuildConfig
|
import im.vector.riotx.BuildConfig
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.extensions.vectorComponent
|
import im.vector.riotx.core.extensions.vectorComponent
|
||||||
import im.vector.riotx.core.preference.BingRule
|
|
||||||
import im.vector.riotx.core.pushers.PushersManager
|
import im.vector.riotx.core.pushers.PushersManager
|
||||||
import im.vector.riotx.features.badge.BadgeProxy
|
import im.vector.riotx.features.badge.BadgeProxy
|
||||||
import im.vector.riotx.features.notifications.NotifiableEventResolver
|
import im.vector.riotx.features.notifications.NotifiableEventResolver
|
||||||
|
@ -196,7 +196,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
description = "",
|
description = "",
|
||||||
type = null,
|
type = null,
|
||||||
timestamp = System.currentTimeMillis(),
|
timestamp = System.currentTimeMillis(),
|
||||||
soundName = BingRule.ACTION_VALUE_DEFAULT,
|
soundName = PushRule.ACTION_VALUE_DEFAULT,
|
||||||
isPushGatewayEvent = true
|
isPushGatewayEvent = true
|
||||||
)
|
)
|
||||||
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)
|
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)
|
||||||
|
|
|
@ -68,7 +68,7 @@ object FcmHelper {
|
||||||
*
|
*
|
||||||
* @param activity the first launch Activity
|
* @param activity the first launch Activity
|
||||||
*/
|
*/
|
||||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
|
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||||
// if (TextUtils.isEmpty(getFcmToken(activity))) {
|
// if (TextUtils.isEmpty(getFcmToken(activity))) {
|
||||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||||
if (checkPlayServices(activity)) {
|
if (checkPlayServices(activity)) {
|
||||||
|
@ -76,7 +76,9 @@ object FcmHelper {
|
||||||
FirebaseInstanceId.getInstance().instanceId
|
FirebaseInstanceId.getInstance().instanceId
|
||||||
.addOnSuccessListener(activity) { instanceIdResult ->
|
.addOnSuccessListener(activity) { instanceIdResult ->
|
||||||
storeFcmToken(activity, instanceIdResult.token)
|
storeFcmToken(activity, instanceIdResult.token)
|
||||||
pushersManager.registerPusherWithFcmKey(instanceIdResult.token)
|
if (registerPusher) {
|
||||||
|
pushersManager.registerPusherWithFcmKey(instanceIdResult.token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.addOnFailureListener(activity) { e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") }
|
.addOnFailureListener(activity) { e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") }
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
|
|
@ -18,8 +18,8 @@ package im.vector.riotx.push.fcm
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.vector.riotx.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
import im.vector.riotx.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestAccountSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestAccountSettings
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestBingRulesSettings
|
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestDeviceSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestDeviceSettings
|
||||||
|
import im.vector.riotx.features.settings.troubleshoot.TestPushRulesSettings
|
||||||
import im.vector.riotx.features.settings.troubleshoot.TestSystemSettings
|
import im.vector.riotx.features.settings.troubleshoot.TestSystemSettings
|
||||||
import im.vector.riotx.gplay.features.settings.troubleshoot.TestFirebaseToken
|
import im.vector.riotx.gplay.features.settings.troubleshoot.TestFirebaseToken
|
||||||
import im.vector.riotx.gplay.features.settings.troubleshoot.TestPlayServices
|
import im.vector.riotx.gplay.features.settings.troubleshoot.TestPlayServices
|
||||||
|
@ -29,7 +29,7 @@ import javax.inject.Inject
|
||||||
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
|
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
|
||||||
private val testAccountSettings: TestAccountSettings,
|
private val testAccountSettings: TestAccountSettings,
|
||||||
private val testDeviceSettings: TestDeviceSettings,
|
private val testDeviceSettings: TestDeviceSettings,
|
||||||
private val testBingRulesSettings: TestBingRulesSettings,
|
private val testBingRulesSettings: TestPushRulesSettings,
|
||||||
private val testPlayServices: TestPlayServices,
|
private val testPlayServices: TestPlayServices,
|
||||||
private val testFirebaseToken: TestFirebaseToken,
|
private val testFirebaseToken: TestFirebaseToken,
|
||||||
private val testTokenRegistration: TestTokenRegistration) {
|
private val testTokenRegistration: TestTokenRegistration) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue