Merge branch 'release/0.18.0'
This commit is contained in:
commit
dec591517c
|
@ -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">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<w>msisdn</w>
|
||||
<w>pbkdf</w>
|
||||
<w>pkcs</w>
|
||||
<w>riotx</w>
|
||||
<w>signin</w>
|
||||
<w>signout</w>
|
||||
<w>signup</w>
|
||||
|
|
|
@ -49,12 +49,12 @@ script:
|
|||
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||
# Done by Buildkite now: - ./gradlew clean assembleGplayRelease lintGplayRelease --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)
|
||||
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||
# 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
|
||||
# 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)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -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.
|
||||
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
|
||||
|
||||
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/).
|
||||
|
|
|
@ -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'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||
|
||||
// FP
|
||||
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.internal.crypto.model.CryptoDeviceInfo
|
||||
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.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||
|
@ -279,7 +278,7 @@ class SASTest : InstrumentedTest {
|
|||
val startMessage = KeyVerificationStart(
|
||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
||||
method = VerificationMethod.SAS.toValue(),
|
||||
transactionID = tid,
|
||||
transactionId = tid,
|
||||
keyAgreementProtocols = protocols,
|
||||
hashes = hashes,
|
||||
messageAuthenticationCodes = mac,
|
||||
|
@ -350,16 +349,16 @@ class SASTest : InstrumentedTest {
|
|||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
var accepted: KeyVerificationAccept? = null
|
||||
var startReq: KeyVerificationStart? = null
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||
val at = tx as SASDefaultVerificationTransaction
|
||||
accepted = at.accepted as? KeyVerificationAccept
|
||||
startReq = at.startReq as? KeyVerificationStart
|
||||
accepted = at.accepted
|
||||
startReq = at.startReq
|
||||
aliceAcceptedLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
@ -384,13 +383,13 @@ class SASTest : InstrumentedTest {
|
|||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||
|
||||
// check that agreement is valid
|
||||
assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
|
||||
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!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
|
||||
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
||||
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!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
||||
|
||||
accepted!!.shortAuthenticationStrings?.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
|
||||
accepted!!.shortAuthenticationStrings.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||
}
|
||||
|
||||
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.TestConstants
|
||||
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.junit.FixMethodOrder
|
||||
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.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.util.Cancelable
|
||||
|
||||
interface PushRuleService {
|
||||
|
||||
/**
|
||||
* Fetch the push rules from the server
|
||||
*/
|
||||
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
||||
|
||||
// TODO get push rule set
|
||||
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
|
||||
|
||||
// TODO update rule
|
||||
fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet
|
||||
|
||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, 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 addPushRuleListener(listener: PushRuleListener)
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
/**
|
||||
* All push rulesets for a user.
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class GetPushRulesResponse(
|
||||
|
@ -27,11 +28,11 @@ internal data class GetPushRulesResponse(
|
|||
* Global rules, account level applying to all devices
|
||||
*/
|
||||
@Json(name = "global")
|
||||
val global: Ruleset,
|
||||
val global: RuleSet,
|
||||
|
||||
/**
|
||||
* Device specific rules, apply only to current 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 timber.log.Timber
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PushCondition(
|
||||
/**
|
||||
* Required. The kind of condition to apply.
|
||||
*/
|
||||
@Json(name = "kind")
|
||||
val kind: String,
|
||||
|
||||
/**
|
||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||
*/
|
||||
@Json(name = "key")
|
||||
val key: String? = null,
|
||||
|
||||
/**
|
||||
* Required for event_match conditions.
|
||||
*/
|
||||
@Json(name = "pattern")
|
||||
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.
|
||||
* 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? {
|
||||
|
|
|
@ -18,31 +18,158 @@ 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.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)
|
||||
data class PushRule(
|
||||
/**
|
||||
* Required. The actions to perform when this rule is matched.
|
||||
*/
|
||||
@Json(name = "actions")
|
||||
val actions: List<Any>,
|
||||
/**
|
||||
* Required. Whether this is a default rule, or has been set explicitly.
|
||||
*/
|
||||
@Json(name = "default")
|
||||
val default: Boolean? = false,
|
||||
/**
|
||||
* Required. Whether the push rule is enabled or not.
|
||||
*/
|
||||
@Json(name = "enabled")
|
||||
val enabled: Boolean,
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Json(name = "conditions")
|
||||
val conditions: List<PushCondition>? = null,
|
||||
/**
|
||||
* The glob-style pattern to match against. Only applicable to content rules.
|
||||
*/
|
||||
@Json(name = "pattern")
|
||||
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 queryUri: String,
|
||||
val path: String,
|
||||
val mimeType: String?,
|
||||
private val mimeType: String?,
|
||||
val type: Type
|
||||
) : Parcelable {
|
||||
|
||||
|
@ -41,4 +41,6 @@ data class ContentAttachmentData(
|
|||
AUDIO,
|
||||
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
|
||||
* 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.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_SHOW
|
||||
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
|
||||
* TODO We should not expose this whole object to the app. Create an interface
|
||||
*/
|
||||
data class PendingVerificationRequest(
|
||||
val ageLocalTs: Long,
|
||||
val isIncoming: Boolean = false,
|
||||
val localID: String = UUID.randomUUID().toString(),
|
||||
val localId: String = UUID.randomUUID().toString(),
|
||||
val otherUserId: String,
|
||||
val roomId: String?,
|
||||
val transactionId: String? = null,
|
||||
val requestInfo: VerificationInfoRequest? = null,
|
||||
val readyInfo: VerificationInfoReady? = null,
|
||||
val requestInfo: ValidVerificationInfoRequest? = null,
|
||||
val readyInfo: ValidVerificationInfoReady? = null,
|
||||
val cancelConclusion: CancelCode? = null,
|
||||
val isSuccessful: 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");
|
||||
* 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
|
||||
* 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,
|
||||
|
@ -13,15 +13,11 @@
|
|||
* 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.JsonClass
|
||||
package im.vector.matrix.android.api.session.crypto.verification
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Ruleset(
|
||||
val content: List<PushRule>? = null,
|
||||
val override: List<PushRule>? = null,
|
||||
val room: List<PushRule>? = null,
|
||||
val sender: List<PushRule>? = null,
|
||||
val underride: List<PushRule>? = null
|
||||
data class ValidVerificationInfoReady(
|
||||
val transactionId: String,
|
||||
val fromDevice: String,
|
||||
val methods: List<String>
|
||||
)
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,16 +14,11 @@
|
|||
* 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
|
||||
|
||||
val supportedVerificationMethods =
|
||||
listOf(
|
||||
// RiotX supports SAS verification
|
||||
VerificationMethod.SAS,
|
||||
// RiotX is able to show QR codes
|
||||
VerificationMethod.QR_CODE_SHOW,
|
||||
// RiotX is able to scan QR codes
|
||||
VerificationMethod.QR_CODE_SCAN
|
||||
)
|
||||
data class ValidVerificationInfoRequest(
|
||||
val transactionId: String,
|
||||
val fromDevice: String,
|
||||
val methods: List<String>,
|
||||
val timestamp: Long?
|
||||
)
|
|
@ -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.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
|
||||
|
|
|
@ -34,7 +34,11 @@ interface FileService {
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||
) : MessageEncryptedContent {
|
||||
) : MessageWithAttachmentContent {
|
||||
|
||||
fun getMimeType(): String {
|
||||
// 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
|
||||
*/
|
||||
interface MessageImageInfoContent : MessageEncryptedContent {
|
||||
interface MessageImageInfoContent : MessageWithAttachmentContent {
|
||||
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.internal.crypto.verification.VerificationInfoAccept
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationAcceptContent(
|
||||
|
@ -34,22 +33,9 @@ internal data class MessageVerificationAcceptContent(
|
|||
@Json(name = "commitment") override var commitment: String? = null
|
||||
) : VerificationInfoAccept {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
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()
|
||||
|
||||
companion object : VerificationInfoAcceptFactory {
|
||||
|
|
|
@ -28,21 +28,13 @@ data class MessageVerificationCancelContent(
|
|||
@Json(name = "code") override val code: String? = null,
|
||||
@Json(name = "reason") override val reason: String? = null,
|
||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
|
||||
) : VerificationInfoCancel {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent {
|
||||
return MessageVerificationCancelContent(
|
||||
|
|
|
@ -25,12 +25,22 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
|||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationDoneContent(
|
||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfo {
|
||||
) : VerificationInfo<ValidVerificationDone> {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun isValid() = transactionID?.isNotEmpty() == true
|
||||
|
||||
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.internal.crypto.verification.VerificationInfoKey
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationKeyContent(
|
||||
|
@ -33,17 +32,9 @@ internal data class MessageVerificationKeyContent(
|
|||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfoKey {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
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()
|
||||
|
||||
companion object : VerificationInfoKeyFactory {
|
||||
|
|
|
@ -30,18 +30,11 @@ internal data class MessageVerificationMacContent(
|
|||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfoMac {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object : VerificationInfoMacFactory {
|
||||
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
|
||||
return MessageVerificationMacContent(
|
||||
|
|
|
@ -30,18 +30,11 @@ internal data class MessageVerificationReadyContent(
|
|||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfoReady {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object : MessageVerificationReadyFactory {
|
||||
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
|
||||
return MessageVerificationReadyContent(
|
||||
|
|
|
@ -33,18 +33,10 @@ data class MessageVerificationRequestContent(
|
|||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "formatted_body") val formattedBody: String? = 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 {
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -17,15 +17,10 @@ package im.vector.matrix.android.api.session.room.model.message
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
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.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.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationStartContent(
|
||||
|
@ -39,46 +34,12 @@ internal data class MessageVerificationStartContent(
|
|||
@Json(name = "secret") override val sharedSecret: String?
|
||||
) : VerificationInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
override fun toCanonicalJson(): String {
|
||||
return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this)
|
||||
}
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
@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 MessageEncryptedContent : MessageContent {
|
||||
interface MessageWithAttachmentContent : MessageContent {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 ParsingError : SharedSecretStorageError("parsing Error")
|
||||
object BadMac : SharedSecretStorageError("Bad mac")
|
||||
object BadCipherText : SharedSecretStorageError("Bad cipher text")
|
||||
|
||||
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
|
||||
}
|
||||
|
|
|
@ -627,9 +627,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*/
|
||||
@Throws(MXCryptoError::class)
|
||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
return runBlocking {
|
||||
internalDecryptEvent(event, timeline)
|
||||
}
|
||||
return 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.
|
||||
* @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
|
||||
if (eventContent == null) {
|
||||
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.
|
||||
* @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.
|
||||
|
|
|
@ -63,7 +63,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
*/
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -38,7 +38,7 @@ internal class MXOlmDecryption(
|
|||
private val userId: String)
|
||||
: 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 {
|
||||
Timber.e("## decryptEvent() : bad event format")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||
|
|
|
@ -80,7 +80,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
|
||||
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo ->
|
||||
privateKeysInfo.master
|
||||
?.fromBase64NoPadding()
|
||||
?.fromBase64()
|
||||
?.let { privateKeySeed ->
|
||||
val pkSigning = OlmPkSigning()
|
||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||
|
@ -93,7 +93,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
privateKeysInfo.user
|
||||
?.fromBase64NoPadding()
|
||||
?.fromBase64()
|
||||
?.let { privateKeySeed ->
|
||||
val pkSigning = OlmPkSigning()
|
||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||
|
@ -106,7 +106,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
privateKeysInfo.selfSigned
|
||||
?.fromBase64NoPadding()
|
||||
?.fromBase64()
|
||||
?.let { privateKeySeed ->
|
||||
val pkSigning = OlmPkSigning()
|
||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||
|
@ -307,7 +307,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
var userKeyIsTrusted = false
|
||||
var selfSignedKeyIsTrusted = false
|
||||
|
||||
masterKeyPrivateKey?.fromBase64NoPadding()
|
||||
masterKeyPrivateKey?.fromBase64()
|
||||
?.let { privateKeySeed ->
|
||||
val pkSigning = OlmPkSigning()
|
||||
try {
|
||||
|
@ -324,7 +324,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
uskKeyPrivateKey?.fromBase64NoPadding()
|
||||
uskKeyPrivateKey?.fromBase64()
|
||||
?.let { privateKeySeed ->
|
||||
val pkSigning = OlmPkSigning()
|
||||
try {
|
||||
|
@ -341,7 +341,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
sskPrivateKey?.fromBase64NoPadding()
|
||||
sskPrivateKey?.fromBase64()
|
||||
?.let { privateKeySeed ->
|
||||
val pkSigning = OlmPkSigning()
|
||||
try {
|
||||
|
@ -450,7 +450,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
// 1) check if I know the private key
|
||||
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()
|
||||
?.master
|
||||
?.fromBase64NoPadding()
|
||||
?.fromBase64()
|
||||
|
||||
var isMaterKeyTrusted = false
|
||||
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.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
fun CryptoDeviceInfo.canonicalSignable(): String {
|
||||
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)
|
||||
}
|
||||
|
||||
fun String.fromBase64NoPadding(): ByteArray {
|
||||
return Base64.decode(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
fun String.fromBase64(): ByteArray {
|
||||
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 im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||
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.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationAccept(
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||
*/
|
||||
@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
|
||||
|
@ -67,19 +65,6 @@ internal data class KeyVerificationAccept(
|
|||
override var commitment: String? = null
|
||||
) : 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
|
||||
|
||||
companion object : VerificationInfoAcceptFactory {
|
||||
|
@ -90,7 +75,7 @@ internal data class KeyVerificationAccept(
|
|||
messageAuthenticationCode: String,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
|
||||
return KeyVerificationAccept(
|
||||
transactionID = tid,
|
||||
transactionId = tid,
|
||||
keyAgreementProtocol = keyAgreementProtocol,
|
||||
hash = hash,
|
||||
commitment = commitment,
|
||||
|
|
|
@ -29,7 +29,7 @@ internal data class KeyVerificationCancel(
|
|||
* the transaction ID of the verification to cancel
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
override val transactionID: String? = null,
|
||||
override val transactionId: String? = null,
|
||||
|
||||
/**
|
||||
* machine-readable reason for cancelling, see #CancelCode
|
||||
|
@ -53,11 +53,4 @@ internal data class KeyVerificationCancel(
|
|||
}
|
||||
|
||||
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)
|
||||
internal data class KeyVerificationDone(
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoDone {
|
||||
|
||||
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
|
||||
*/
|
||||
@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
|
||||
|
@ -44,11 +44,4 @@ internal data class KeyVerificationKey(
|
|||
}
|
||||
|
||||
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)
|
||||
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 = "keys") override val keys: String? = null
|
||||
|
||||
) : SendToDeviceObject, VerificationInfoMac {
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
||||
|
||||
companion object : VerificationInfoMacFactory {
|
||||
|
|
|
@ -26,12 +26,8 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRea
|
|||
internal data class KeyVerificationReady(
|
||||
@Json(name = "from_device") override val fromDevice: 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 {
|
||||
|
||||
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 = "methods") override val methods: List<String>,
|
||||
@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 {
|
||||
|
||||
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.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.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Sent by Alice to initiate an interactive key verification.
|
||||
|
@ -29,8 +26,8 @@ import timber.log.Timber
|
|||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationStart(
|
||||
@Json(name = "from_device") override val fromDevice: String? = null,
|
||||
override val method: String? = null,
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||
@Json(name = "method") override val method: String? = null,
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
|
||||
@Json(name = "hashes") override val hashes: 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
|
||||
) : SendToDeviceObject, VerificationInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
override fun toCanonicalJson(): String {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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.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.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.keysbackup.generatePrivateKeyWithPassword
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||
|
@ -268,7 +268,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||
val ivParameterSpec = IvParameterSpec(iv)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
// secret are not that big, just do Final
|
||||
val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64NoPadding())
|
||||
val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64())
|
||||
require(cipherBytes.isNotEmpty())
|
||||
|
||||
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
|
||||
|
@ -295,9 +295,9 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
||||
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")
|
||||
|
||||
|
@ -314,7 +314,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||
val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
|
||||
val digest = mac.doFinal(cipherRawBytes)
|
||||
|
||||
if (!cipherContent.mac?.fromBase64NoPadding()?.contentEquals(digest).orFalse()) {
|
||||
if (!cipherContent.mac?.fromBase64()?.contentEquals(digest).orFalse()) {
|
||||
throw SharedSecretStorageError.BadMac
|
||||
} else {
|
||||
// we are good
|
||||
|
|
|
@ -41,22 +41,22 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
|
||||
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
||||
val event = handleEncryption(params)
|
||||
val localID = event.eventId!!
|
||||
val localId = event.eventId!!
|
||||
|
||||
try {
|
||||
localEchoUpdater.updateSendState(localID, SendState.SENDING)
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||
apiCall = roomAPI.send(
|
||||
localID,
|
||||
localId,
|
||||
roomId = event.roomId ?: "",
|
||||
content = event.content,
|
||||
eventType = event.type
|
||||
)
|
||||
}
|
||||
localEchoUpdater.updateSendState(localID, SendState.SENT)
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
||||
return executeRequest.eventId
|
||||
} catch (e: Throwable) {
|
||||
localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED)
|
||||
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
||||
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")
|
||||
if (state != VerificationTxState.None) {
|
||||
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,
|
||||
// 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 agreedHash = startReq!!.hashes?.firstOrNull { KNOWN_HASHES.contains(it) }
|
||||
val agreedMac = startReq!!.messageAuthenticationCodes?.firstOrNull { KNOWN_MACS.contains(it) }
|
||||
val agreedShortCode = startReq!!.shortAuthenticationStrings?.filter { KNOWN_SHORT_CODES.contains(it) }
|
||||
val agreedProtocol = startReq!!.keyAgreementProtocols.firstOrNull { KNOWN_AGREEMENT_PROTOCOLS.contains(it) }
|
||||
val agreedHash = startReq!!.hashes.firstOrNull { KNOWN_HASHES.contains(it) }
|
||||
val agreedMac = startReq!!.messageAuthenticationCodes.firstOrNull { KNOWN_MACS.contains(it) }
|
||||
val agreedShortCode = startReq!!.shortAuthenticationStrings.filter { KNOWN_SHORT_CODES.contains(it) }
|
||||
|
||||
// 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,
|
||||
|
@ -141,12 +141,12 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
private fun doAccept(accept: VerificationInfoAccept) {
|
||||
this.accepted = accept
|
||||
this.accepted = accept.asValidObject()
|
||||
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,
|
||||
// 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) ?: ""
|
||||
// we need to send this to other device now
|
||||
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")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
||||
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
|
||||
Timber.v("## SAS received key for request id:$transactionId")
|
||||
if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) {
|
||||
Timber.e("## SAS received key from invalid state $state")
|
||||
|
@ -213,7 +213,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
state = VerificationTxState.ShortCodeReady
|
||||
}
|
||||
|
||||
override fun onKeyVerificationMac(vKey: VerificationInfoMac) {
|
||||
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS I: received mac for request id:$transactionId")
|
||||
// Check for state?
|
||||
if (state != VerificationTxState.SendingKey
|
||||
|
@ -226,12 +226,13 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
theirMac = vKey
|
||||
|
||||
theirMac = vMac
|
||||
|
||||
// Do I have my Mac?
|
||||
if (myMac != null) {
|
||||
// I can check
|
||||
verifyMacs()
|
||||
verifyMacs(vMac)
|
||||
}
|
||||
// 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")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
KNOWN_SHORT_CODES
|
||||
)
|
||||
|
||||
startReq = startMessage
|
||||
startReq = startMessage.asValidObject() as? ValidVerificationInfoStart.SasVerificationInfoStart
|
||||
state = VerificationTxState.SendingStart
|
||||
|
||||
sendToOther(
|
||||
|
@ -118,7 +118,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||
// timestamp = System.currentTimeMillis().toInt(),
|
||||
// transactionID = transactionId
|
||||
// transactionId = transactionId
|
||||
// )
|
||||
//
|
||||
// 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")
|
||||
if (state != VerificationTxState.Started) {
|
||||
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)
|
||||
|| !KNOWN_HASHES.contains(accept.hash)
|
||||
|| !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")
|
||||
cancel(CancelCode.UnknownMethod)
|
||||
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")
|
||||
if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) {
|
||||
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.
|
||||
|
||||
// check commitment
|
||||
val concat = vKey.key + startReq!!.toCanonicalJson()
|
||||
val concat = vKey.key + startReq!!.canonicalJson
|
||||
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||
|
||||
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")
|
||||
if (state != VerificationTxState.OnKeyReceived
|
||||
&& state != VerificationTxState.ShortCodeReady
|
||||
|
@ -218,12 +218,12 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
return
|
||||
}
|
||||
|
||||
theirMac = vKey
|
||||
theirMac = vMac
|
||||
|
||||
// Do I have my Mac?
|
||||
if (myMac != null) {
|
||||
// I can check
|
||||
verifyMacs()
|
||||
verifyMacs(vMac)
|
||||
}
|
||||
// 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.crosssigning.CrossSigningService
|
||||
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.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.VerificationService
|
||||
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.MessageVerificationRequestContent
|
||||
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.MyDeviceInfoHolder
|
||||
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 kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.toImmutableList
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
@ -105,7 +107,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
* Map [sender: [PendingVerificationRequest]]
|
||||
* 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
|
||||
fun onToDeviceEvent(event: Event) {
|
||||
|
@ -268,9 +270,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
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
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
|
@ -278,7 +280,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
val senderId = event.senderId ?: return
|
||||
|
||||
// We don't want to block here
|
||||
val otherDeviceId = requestInfo.fromDevice ?: return
|
||||
val otherDeviceId = validRequestInfo.fromDevice
|
||||
|
||||
GlobalScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||
|
@ -287,19 +289,16 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
// Remember this request
|
||||
val requestsForUser = pendingRequests[senderId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[event.senderId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||
|
||||
val pendingVerificationRequest = PendingVerificationRequest(
|
||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||
isIncoming = true,
|
||||
otherUserId = senderId, // requestInfo.toUserId,
|
||||
roomId = null,
|
||||
transactionId = requestInfo.transactionID,
|
||||
localID = requestInfo.transactionID!!,
|
||||
requestInfo = requestInfo
|
||||
transactionId = validRequestInfo.transactionId,
|
||||
localId = validRequestInfo.transactionId,
|
||||
requestInfo = validRequestInfo
|
||||
)
|
||||
requestsForUser.add(pendingVerificationRequest)
|
||||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
|
@ -307,10 +306,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
suspend fun onRoomRequestReceived(event: Event) {
|
||||
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
|
||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
|
||||
?: return
|
||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>() ?: return
|
||||
val validRequestInfo = requestInfo
|
||||
// copy the EventId to the transactionId
|
||||
.copy(transactionId = event.eventId)
|
||||
.asValidObject() ?: return
|
||||
|
||||
val senderId = event.senderId ?: return
|
||||
val fromDevice = requestInfo.fromDevice ?: return
|
||||
|
||||
if (requestInfo.toUserId != userId) {
|
||||
// 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
|
||||
GlobalScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device $fromDevice is not known")
|
||||
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
|
||||
}
|
||||
}
|
||||
|
||||
// Remember this request
|
||||
val requestsForUser = pendingRequests[senderId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[event.senderId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||
|
||||
val pendingVerificationRequest = PendingVerificationRequest(
|
||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||
|
@ -337,8 +336,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
otherUserId = senderId, // requestInfo.toUserId,
|
||||
roomId = event.roomId,
|
||||
transactionId = event.eventId,
|
||||
localID = event.eventId!!,
|
||||
requestInfo = requestInfo
|
||||
localId = event.eventId!!,
|
||||
requestInfo = validRequestInfo
|
||||
)
|
||||
requestsForUser.add(pendingVerificationRequest)
|
||||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
|
@ -362,13 +361,15 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
|
||||
val validStartReq = startReq?.asValidObject()
|
||||
|
||||
val otherUserId = event.senderId
|
||||
if (startReq?.isValid()?.not() == true) {
|
||||
if (validStartReq == null) {
|
||||
Timber.e("## received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
if (startReq?.transactionId != null) {
|
||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
.cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
startReq.transactionId ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
CancelCode.UnknownMethod
|
||||
|
@ -377,14 +378,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
handleStart(otherUserId, startReq as VerificationInfoStart) {
|
||||
handleStart(otherUserId, validStartReq) {
|
||||
it.transport = verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
|
||||
}?.let {
|
||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
.cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
validStartReq.transactionId,
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
validStartReq.fromDevice,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
@ -392,16 +393,17 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
private suspend fun onStartRequestReceived(event: Event) {
|
||||
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")
|
||||
|
||||
val otherUserId = event.senderId
|
||||
if (!startReq.isValid()) {
|
||||
val otherUserId = event.senderId!!
|
||||
if (validStartReq == null) {
|
||||
Timber.e("## SAS received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
if (startReq?.transactionId != null) {
|
||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID,
|
||||
otherUserId!!,
|
||||
startReq.transactionId,
|
||||
otherUserId,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
CancelCode.UnknownMethod
|
||||
)
|
||||
|
@ -409,13 +411,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
return
|
||||
}
|
||||
// Download device keys prior to everything
|
||||
handleStart(otherUserId, startReq) {
|
||||
handleStart(otherUserId, validStartReq) {
|
||||
it.transport = verificationTransportToDeviceFactory.createTransport(it)
|
||||
}?.let {
|
||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
validStartReq.transactionId,
|
||||
otherUserId,
|
||||
validStartReq.fromDevice,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
@ -424,18 +426,20 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
/**
|
||||
* 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? {
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionID}")
|
||||
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice ?: "") != null) {
|
||||
val tid = startReq.transactionID!!
|
||||
private suspend fun handleStart(otherUserId: String?,
|
||||
startReq: ValidVerificationInfoStart,
|
||||
txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}")
|
||||
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice) != null) {
|
||||
val tid = startReq.transactionId
|
||||
val existing = getExistingTransaction(otherUserId, tid)
|
||||
|
||||
when (startReq.method) {
|
||||
VERIFICATION_METHOD_SAS -> {
|
||||
when (startReq) {
|
||||
is ValidVerificationInfoStart.SasVerificationInfoStart -> {
|
||||
when (existing) {
|
||||
is SasVerificationTransaction -> {
|
||||
// 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)
|
||||
// Already cancelled, so return null
|
||||
return null
|
||||
|
@ -450,7 +454,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
?.also {
|
||||
// Multiple keyshares between two devices:
|
||||
// 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 {
|
||||
it.cancel(CancelCode.UnexpectedMessage)
|
||||
|
@ -462,12 +466,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
val autoAccept = getExistingVerificationRequest(otherUserId)?.any {
|
||||
it.transactionId == startReq.transactionID
|
||||
it.transactionId == startReq.transactionId
|
||||
&& (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId)
|
||||
}
|
||||
?: false
|
||||
|
@ -479,27 +483,23 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
cryptoStore,
|
||||
crossSigningService,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
startReq.transactionID!!,
|
||||
startReq.transactionId,
|
||||
otherUserId,
|
||||
autoAccept).also { txConfigure(it) }
|
||||
addTransaction(tx)
|
||||
tx.acceptVerificationEvent(otherUserId, startReq)
|
||||
tx.onVerificationStart(startReq)
|
||||
return null
|
||||
}
|
||||
VERIFICATION_METHOD_RECIPROCATE -> {
|
||||
is ValidVerificationInfoStart.ReciprocateVerificationInfoStart -> {
|
||||
// Other user has scanned my QR code
|
||||
if (existing is DefaultQrCodeVerificationTransaction) {
|
||||
existing.onStartReceived(startReq)
|
||||
return null
|
||||
} else {
|
||||
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionID}")
|
||||
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId}")
|
||||
return CancelCode.UnexpectedMessage
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
||||
return CancelCode.UnknownMethod
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return CancelCode.UnexpectedMessage
|
||||
|
@ -529,24 +529,27 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (cancelReq == null || cancelReq.isValid().not()) {
|
||||
|
||||
val validCancelReq = cancelReq?.asValidObject()
|
||||
|
||||
if (validCancelReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
// TODO should we cancel?
|
||||
return
|
||||
}
|
||||
getExistingVerificationRequest(event.senderId ?: "", cancelReq.transactionID)?.let {
|
||||
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(cancelReq.code)))
|
||||
getExistingVerificationRequest(event.senderId ?: "", validCancelReq.transactionId)?.let {
|
||||
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(validCancelReq.code)))
|
||||
// Should we remove it from the list?
|
||||
}
|
||||
handleOnCancel(event.senderId!!, cancelReq)
|
||||
handleOnCancel(event.senderId!!, validCancelReq)
|
||||
}
|
||||
|
||||
private fun onCancelReceived(event: Event) {
|
||||
Timber.v("## SAS onCancelReceived")
|
||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!
|
||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()?.asValidObject()
|
||||
|
||||
if (!cancelReq.isValid()) {
|
||||
if (cancelReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid cancel request")
|
||||
return
|
||||
|
@ -556,11 +559,11 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
handleOnCancel(otherUserId, cancelReq)
|
||||
}
|
||||
|
||||
private fun handleOnCancel(otherUserId: String, cancelReq: VerificationInfoCancel) {
|
||||
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
||||
private fun handleOnCancel(otherUserId: String, cancelReq: ValidVerificationInfoCancel) {
|
||||
Timber.v("## SAS onCancelReceived otherUser: $otherUserId reason: ${cancelReq.reason}")
|
||||
|
||||
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
||||
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionID!!)
|
||||
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionId)
|
||||
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionId)
|
||||
|
||||
if (existingRequest != null) {
|
||||
// Mark this request as cancelled
|
||||
|
@ -582,30 +585,28 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
?: return
|
||||
handleAccept(accept, event.senderId!!)
|
||||
|
||||
val validAccept = accept.asValidObject() ?: return
|
||||
|
||||
handleAccept(validAccept, event.senderId!!)
|
||||
}
|
||||
|
||||
private fun onAcceptReceived(event: 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!!)
|
||||
}
|
||||
|
||||
private fun handleAccept(acceptReq: VerificationInfoAccept, senderId: String) {
|
||||
if (!acceptReq.isValid()) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
private fun handleAccept(acceptReq: ValidVerificationInfoAccept, senderId: String) {
|
||||
val otherUserId = senderId
|
||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionId)
|
||||
if (existing == null) {
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
|
||||
if (existing is SASDefaultVerificationTransaction) {
|
||||
existing.acceptVerificationEvent(otherUserId, acceptReq)
|
||||
existing.onVerificationAccept(acceptReq)
|
||||
} else {
|
||||
// not other types now
|
||||
}
|
||||
|
@ -617,7 +618,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (keyReq == null || keyReq.isValid().not()) {
|
||||
?.asValidObject()
|
||||
if (keyReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
// TODO should we cancel?
|
||||
|
@ -627,9 +629,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
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
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
|
@ -637,16 +639,16 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
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")
|
||||
val otherUserId = event.senderId!!
|
||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionId)
|
||||
if (existing == null) {
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
}
|
||||
if (existing is SASDefaultVerificationTransaction) {
|
||||
existing.acceptVerificationEvent(otherUserId, keyReq)
|
||||
existing.onKeyVerificationKey(keyReq)
|
||||
} else {
|
||||
// not other types now
|
||||
}
|
||||
|
@ -658,7 +660,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (macReq == null || macReq.isValid().not() || event.senderId == null) {
|
||||
?.asValidObject()
|
||||
if (macReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid mac request")
|
||||
// TODO should we cancel?
|
||||
|
@ -673,13 +676,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (readyReq == null || readyReq.isValid().not() || event.senderId == null) {
|
||||
?.asValidObject()
|
||||
if (readyReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid ready request")
|
||||
// TODO should we cancel?
|
||||
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")
|
||||
// TODO cancel?
|
||||
return
|
||||
|
@ -691,15 +695,15 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
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
|
||||
Timber.e("## SAS Received invalid ready request")
|
||||
// TODO should we cancel?
|
||||
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")
|
||||
// TODO cancel?
|
||||
return
|
||||
|
@ -716,8 +720,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
?.asValidObject()
|
||||
|
||||
if (doneReq == null || doneReq.isValid().not() || event.senderId == null) {
|
||||
if (doneReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid Done request")
|
||||
// TODO should we cancel?
|
||||
|
@ -728,9 +733,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
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
|
||||
Timber.e("## SAS Received invalid mac request")
|
||||
return
|
||||
|
@ -738,41 +743,41 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
handleMacReceived(event.senderId, macReq)
|
||||
}
|
||||
|
||||
private fun handleMacReceived(senderId: String, macReq: VerificationInfoMac) {
|
||||
private fun handleMacReceived(senderId: String, macReq: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS Received $macReq")
|
||||
val existing = getExistingTransaction(senderId, macReq.transactionID!!)
|
||||
val existing = getExistingTransaction(senderId, macReq.transactionId)
|
||||
if (existing == null) {
|
||||
Timber.e("## SAS Received invalid Mac request")
|
||||
return
|
||||
}
|
||||
if (existing is SASDefaultVerificationTransaction) {
|
||||
existing.acceptVerificationEvent(senderId, macReq)
|
||||
existing.onKeyVerificationMac(macReq)
|
||||
} else {
|
||||
// not other types known for now
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReadyReceived(senderId: String,
|
||||
readyReq: VerificationInfoReady,
|
||||
readyReq: ValidVerificationInfoReady,
|
||||
transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID }
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionId }
|
||||
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
|
||||
}
|
||||
|
||||
val qrCodeData = readyReq.methods
|
||||
// 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 {
|
||||
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
|
||||
val tx = DefaultQrCodeVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
readyReq.transactionID!!,
|
||||
readyReq.transactionId,
|
||||
senderId,
|
||||
readyReq.fromDevice,
|
||||
crossSigningService,
|
||||
|
@ -886,10 +891,10 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionID }
|
||||
private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId }
|
||||
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
|
||||
}
|
||||
updatePendingRequest(existingRequest.copy(isSuccessful = true))
|
||||
|
@ -975,15 +980,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
: PendingVerificationRequest {
|
||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||
|
||||
val requestsForUser = pendingRequests[otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[otherUserId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||
|
||||
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
|
||||
// Cancel existing pending requests?
|
||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||
requestsForUser.toList().forEach { existingRequest ->
|
||||
existingRequest.transactionId?.let { tid ->
|
||||
if (!existingRequest.isFinished) {
|
||||
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(
|
||||
ageLocalTs = System.currentTimeMillis(),
|
||||
isIncoming = false,
|
||||
roomId = roomId,
|
||||
localID = localID,
|
||||
localId = validLocalId,
|
||||
otherUserId = otherUserId
|
||||
)
|
||||
|
||||
|
@ -1019,7 +1021,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
.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
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
transactionId = syncedId,
|
||||
|
@ -1039,15 +1041,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
||||
|
||||
val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId }
|
||||
val requestsForUser = pendingRequests[otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[otherUserId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||
|
||||
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
||||
|
||||
// Cancel existing pending requests?
|
||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||
requestsForUser.toList().forEach { existingRequest ->
|
||||
existingRequest.transactionId?.let { tid ->
|
||||
if (!existingRequest.isFinished) {
|
||||
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(
|
||||
transactionId = localID,
|
||||
transactionId = localId,
|
||||
ageLocalTs = System.currentTimeMillis(),
|
||||
isIncoming = false,
|
||||
roomId = null,
|
||||
localID = localID,
|
||||
localId = localId,
|
||||
otherUserId = otherUserId,
|
||||
targetDevices = targetDevices
|
||||
)
|
||||
|
@ -1087,7 +1086,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
.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
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
// localId stays different
|
||||
|
@ -1113,13 +1112,10 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updatePendingRequest(updated: PendingVerificationRequest) {
|
||||
val requestsForUser = pendingRequests[updated.otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[updated.otherUserId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(updated.otherUserId) { mutableListOf() }
|
||||
val index = requestsForUser.indexOfFirst {
|
||||
it.transactionId == updated.transactionId
|
||||
|| it.transactionId == null && it.localID == updated.localID
|
||||
|| it.transactionId == null && it.localId == updated.localId
|
||||
}
|
||||
if (index != -1) {
|
||||
requestsForUser.removeAt(index)
|
||||
|
@ -1186,7 +1182,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
CancelCode.User,
|
||||
null // TODO handle error?
|
||||
)
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg.asValidObject()))
|
||||
return true
|
||||
} else {
|
||||
Timber.e("## SAS readyPendingVerificationInDMs Verification not found")
|
||||
|
@ -1214,7 +1210,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
if (methods.isNullOrEmpty()) {
|
||||
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
|
||||
}
|
||||
// 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 ?: "",
|
||||
null // TODO handle error?
|
||||
)
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg.asValidObject()))
|
||||
return true
|
||||
} else {
|
||||
Timber.e("## SAS readyPendingVerification Verification not found")
|
||||
|
|
|
@ -15,12 +15,21 @@
|
|||
*/
|
||||
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.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
|
||||
*/
|
||||
internal abstract class DefaultVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val userId: String,
|
||||
override val transactionId: String,
|
||||
override val otherUserId: String,
|
||||
override var otherDeviceId: String? = null,
|
||||
|
@ -42,5 +51,50 @@ internal abstract class DefaultVerificationTransaction(
|
|||
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
|
||||
|
||||
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.verification.CancelCode
|
||||
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.events.model.EventType
|
||||
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.store.IMXCryptoStore
|
||||
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.
|
||||
*/
|
||||
internal abstract class SASDefaultVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
open val userId: String,
|
||||
open val deviceId: String?,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
crossSigningService: CrossSigningService,
|
||||
private val deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String?,
|
||||
isIncoming: Boolean
|
||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), SasVerificationTransaction {
|
||||
) : DefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
crossSigningService,
|
||||
userId,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming),
|
||||
SasVerificationTransaction {
|
||||
|
||||
companion object {
|
||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||
|
@ -89,15 +96,17 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
|
||||
private var olmSas: OlmSAS? = null
|
||||
|
||||
var startReq: VerificationInfoStart? = null
|
||||
var accepted: VerificationInfoAccept? = null
|
||||
var otherKey: String? = null
|
||||
var shortCodeBytes: ByteArray? = null
|
||||
// Visible for test
|
||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||
// Visible for test
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
protected var otherKey: String? = null
|
||||
protected var shortCodeBytes: ByteArray? = null
|
||||
|
||||
var myMac: VerificationInfoMac? = null
|
||||
var theirMac: VerificationInfoMac? = null
|
||||
protected var myMac: ValidVerificationInfoMac? = null
|
||||
protected var theirMac: ValidVerificationInfoMac? = null
|
||||
|
||||
fun getSAS(): OlmSAS {
|
||||
protected fun getSAS(): OlmSAS {
|
||||
if (olmSas == null) olmSas = OlmSAS()
|
||||
return olmSas!!
|
||||
}
|
||||
|
@ -177,7 +186,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
|
||||
myMac = macMsg
|
||||
myMac = macMsg.asValidObject()
|
||||
state = VerificationTxState.SendingMac
|
||||
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, VerificationTxState.MacSent, CancelCode.User) {
|
||||
if (state == VerificationTxState.SendingMac) {
|
||||
|
@ -187,9 +196,8 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
// Do I already have their Mac?
|
||||
if (theirMac != null) {
|
||||
verifyMacs()
|
||||
} // if not wait for it
|
||||
theirMac?.let { verifyMacs(it) }
|
||||
// if not wait for it
|
||||
}
|
||||
|
||||
override fun shortCodeDoesNotMatch() {
|
||||
|
@ -201,27 +209,15 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return transport is VerificationTransportToDevice
|
||||
}
|
||||
|
||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||
when (info) {
|
||||
is VerificationInfoStart -> onVerificationStart(info)
|
||||
is VerificationInfoAccept -> onVerificationAccept(info)
|
||||
is VerificationInfoKey -> onKeyVerificationKey(info)
|
||||
is VerificationInfoMac -> onKeyVerificationMac(info)
|
||||
else -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
abstract fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart)
|
||||
|
||||
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() {
|
||||
protected fun verifyMacs(theirMacSafe: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
||||
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.
|
||||
// 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.
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$otherUserId$otherDeviceId$userId$deviceId$transactionId"
|
||||
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" +
|
||||
otherUserId + otherDeviceId +
|
||||
userId + deviceId +
|
||||
transactionId
|
||||
|
||||
val commaSeparatedListOfKeyIds = theirMac!!.mac!!.keys.sorted().joinToString(",")
|
||||
val commaSeparatedListOfKeyIds = theirMacSafe.mac.keys.sorted().joinToString(",")
|
||||
|
||||
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
|
||||
if (theirMac!!.keys != keyStrings) {
|
||||
if (theirMacSafe.keys != keyStrings) {
|
||||
// WRONG!
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
|
@ -250,7 +242,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
val verifiedDevices = ArrayList<String>()
|
||||
|
||||
// cannot be empty because it has been validated
|
||||
theirMac!!.mac!!.keys.forEach {
|
||||
theirMacSafe.mac.keys.forEach {
|
||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
||||
if (otherDeviceKey == null) {
|
||||
|
@ -259,7 +251,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return@forEach
|
||||
}
|
||||
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
||||
if (mac != theirMac?.mac?.get(it)) {
|
||||
if (mac != theirMacSafe.mac[it]) {
|
||||
// WRONG!
|
||||
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
|
@ -273,12 +265,12 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
|
||||
if (otherCrossSigningMasterKeyPublic != null) {
|
||||
// Did the user signed his master key
|
||||
theirMac!!.mac!!.keys.forEach {
|
||||
theirMacSafe.mac.keys.forEach {
|
||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
||||
// Check the signature
|
||||
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
||||
if (mac != theirMac?.mac?.get(it)) {
|
||||
if (mac != theirMacSafe.mac.get(it)) {
|
||||
// WRONG!
|
||||
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
|
@ -298,40 +290,9 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return
|
||||
}
|
||||
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (otherMasterKeyIsVerified && 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, "## 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)
|
||||
trust(otherMasterKeyIsVerified,
|
||||
verifiedDevices,
|
||||
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false)
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
|
@ -343,11 +304,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
||||
}
|
||||
|
||||
protected fun sendToOther(type: String,
|
||||
keyToDevice: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
protected fun <T> sendToOther(type: String,
|
||||
keyToDevice: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
||||
}
|
||||
|
||||
|
@ -369,11 +330,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
override fun supportsEmoji(): Boolean {
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI) == true
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI).orFalse()
|
||||
}
|
||||
|
||||
override fun supportsDecimal(): Boolean {
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL) == true
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL).orFalse()
|
||||
}
|
||||
|
||||
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
|
||||
|
@ -386,7 +347,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
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()) {
|
||||
return getSAS().calculateMacLongKdf(message, info)
|
||||
} 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
|
||||
* 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 b1 = byteArray[1].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.internal.crypto.model.rest.SendToDeviceObject
|
||||
|
||||
interface VerificationInfo {
|
||||
interface VerificationInfo<ValidObjectType> {
|
||||
fun toEventContent(): Content? = null
|
||||
fun toSendToDeviceObject(): SendToDeviceObject? = null
|
||||
fun isValid(): Boolean
|
||||
|
||||
fun asValidObject(): ValidObjectType?
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||
*/
|
||||
val transactionID: String?
|
||||
|
||||
// TODO Refacto Put the relatesTo here or at least in Message sent in Room parent?
|
||||
// val relatesTo: RelationDefaultContent?
|
||||
val transactionId: String?
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -41,6 +41,24 @@ internal interface VerificationInfoAccept : VerificationInfo {
|
|||
* and the canonical JSON representation of the m.key.verification.start message.
|
||||
*/
|
||||
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 {
|
||||
|
@ -52,3 +70,12 @@ internal interface VerificationInfoAcceptFactory {
|
|||
messageAuthenticationCode: String,
|
||||
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
|
||||
|
||||
internal interface VerificationInfoCancel : VerificationInfo {
|
||||
internal interface VerificationInfoCancel : VerificationInfo<ValidVerificationInfoCancel> {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
|
||||
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.
|
||||
*/
|
||||
internal interface VerificationInfoKey : VerificationInfo {
|
||||
internal interface VerificationInfoKey : VerificationInfo<ValidVerificationInfoKey> {
|
||||
/**
|
||||
* The device’s ephemeral public key, as an unpadded base64 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 {
|
||||
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
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -28,8 +28,26 @@ internal interface VerificationInfoMac : VerificationInfo {
|
|||
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
|
||||
*/
|
||||
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 {
|
||||
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
|
||||
|
||||
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,
|
||||
* 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.
|
||||
*/
|
||||
|
||||
interface VerificationInfoReady : VerificationInfo {
|
||||
internal interface VerificationInfoReady : VerificationInfo<ValidVerificationInfoReady> {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 {
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
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.
|
||||
|
@ -33,4 +35,18 @@ interface VerificationInfoRequest : VerificationInfo {
|
|||
* the message should be ignored by the receiver.
|
||||
*/
|
||||
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
|
||||
|
||||
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?
|
||||
|
||||
|
@ -57,5 +61,64 @@ internal interface VerificationInfoStart : VerificationInfo {
|
|||
*/
|
||||
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
|
||||
|
||||
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.VerificationTxState
|
||||
|
||||
|
@ -27,18 +28,18 @@ internal interface VerificationTransport {
|
|||
/**
|
||||
* Sends a message
|
||||
*/
|
||||
fun sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?)
|
||||
fun <T> sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?)
|
||||
|
||||
fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
localId: String,
|
||||
otherUserId: String,
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit)
|
||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit)
|
||||
|
||||
fun cancelTransaction(transactionId: String,
|
||||
otherUserId: String,
|
||||
|
@ -64,7 +65,7 @@ internal interface VerificationTransport {
|
|||
* Create start for SAS verification
|
||||
*/
|
||||
fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
|
@ -74,7 +75,7 @@ internal interface VerificationTransport {
|
|||
* Create start for QR code verification
|
||||
*/
|
||||
fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
sharedSecret: String): VerificationInfoStart
|
||||
|
||||
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.Operation
|
||||
import androidx.work.WorkInfo
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
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.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
|
@ -65,16 +65,15 @@ internal class VerificationTransportRoomMessage(
|
|||
private val userId: String,
|
||||
private val userDeviceId: String?,
|
||||
private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val tx: DefaultVerificationTransaction?
|
||||
) : VerificationTransport {
|
||||
|
||||
override fun sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
override fun <T> sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
Timber.d("## SAS sending msg type $type")
|
||||
Timber.v("## SAS sending msg info $verificationInfo")
|
||||
val event = createEventAndLocalEcho(
|
||||
|
@ -138,26 +137,33 @@ internal class VerificationTransportRoomMessage(
|
|||
}
|
||||
|
||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
localId: String,
|
||||
otherUserId: String,
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
||||
// This transport requires a room
|
||||
requireNotNull(roomId)
|
||||
|
||||
val validInfo = ValidVerificationInfoRequest(
|
||||
transactionId = "",
|
||||
fromDevice = userDeviceId ?: "",
|
||||
methods = supportedMethods,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val info = MessageVerificationRequestContent(
|
||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||
fromDevice = userDeviceId ?: "",
|
||||
fromDevice = validInfo.fromDevice,
|
||||
toUserId = otherUserId,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
methods = supportedMethods
|
||||
timestamp = validInfo.timestamp,
|
||||
methods = validInfo.methods
|
||||
)
|
||||
val content = info.toContent()
|
||||
|
||||
val event = createEventAndLocalEcho(
|
||||
localID,
|
||||
localId,
|
||||
EventType.MESSAGE,
|
||||
roomId,
|
||||
content
|
||||
|
@ -192,8 +198,8 @@ internal class VerificationTransportRoomMessage(
|
|||
?.let { wInfo ->
|
||||
if (wInfo.outputData.getBoolean("failed", false)) {
|
||||
callback(null, null)
|
||||
} else if (wInfo.outputData.getString(localID) != null) {
|
||||
callback(wInfo.outputData.getString(localID), info)
|
||||
} else if (wInfo.outputData.getString(localId) != null) {
|
||||
callback(wInfo.outputData.getString(localId), validInfo)
|
||||
} else {
|
||||
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 createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
|
@ -286,14 +292,14 @@ internal class VerificationTransportRoomMessage(
|
|||
VERIFICATION_METHOD_SAS,
|
||||
RelationDefaultContent(
|
||||
type = RelationType.REFERENCE,
|
||||
eventId = transactionID
|
||||
eventId = transactionId
|
||||
),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
sharedSecret: String): VerificationInfoStart {
|
||||
return MessageVerificationStartContent(
|
||||
fromDevice,
|
||||
|
@ -304,7 +310,7 @@ internal class VerificationTransportRoomMessage(
|
|||
VERIFICATION_METHOD_RECIPROCATE,
|
||||
RelationDefaultContent(
|
||||
type = RelationType.REFERENCE,
|
||||
eventId = transactionID
|
||||
eventId = transactionId
|
||||
),
|
||||
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(
|
||||
roomId = roomId,
|
||||
originServerTs = System.currentTimeMillis(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = type,
|
||||
content = content,
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
).also {
|
||||
localEchoEventFactory.createLocalEcho(it)
|
||||
}
|
||||
|
@ -347,7 +353,6 @@ internal class VerificationTransportRoomMessage(
|
|||
internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val monarchy: Monarchy,
|
||||
@SessionId
|
||||
private val sessionId: String,
|
||||
@UserId
|
||||
|
@ -357,6 +362,6 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
|||
private val localEchoEventFactory: LocalEchoEventFactory) {
|
||||
|
||||
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
|
||||
|
||||
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.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -46,28 +47,34 @@ internal class VerificationTransportToDevice(
|
|||
) : VerificationTransport {
|
||||
|
||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
localId: String,
|
||||
otherUserId: String,
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
val keyReq = KeyVerificationRequest(
|
||||
fromDevice = myDeviceId,
|
||||
val validKeyReq = ValidVerificationInfoRequest(
|
||||
transactionId = localId,
|
||||
fromDevice = myDeviceId ?: "",
|
||||
methods = supportedMethods,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
transactionID = localID
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
val keyReq = KeyVerificationRequest(
|
||||
fromDevice = validKeyReq.fromDevice,
|
||||
methods = validKeyReq.methods,
|
||||
timestamp = validKeyReq.timestamp,
|
||||
transactionId = validKeyReq.transactionId
|
||||
)
|
||||
toDevices?.forEach {
|
||||
contentMap.setObject(otherUserId, it, keyReq)
|
||||
}
|
||||
sendToDeviceTask
|
||||
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localID)) {
|
||||
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) {
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
||||
callback.invoke(localID, keyReq)
|
||||
callback.invoke(localId, validKeyReq)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
|
@ -103,11 +110,11 @@ internal class VerificationTransportToDevice(
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
override fun <T> sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
Timber.d("## SAS sending msg type $type")
|
||||
Timber.v("## SAS sending msg info $verificationInfo")
|
||||
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 createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
|
@ -205,7 +212,7 @@ internal class VerificationTransportToDevice(
|
|||
return KeyVerificationStart(
|
||||
fromDevice,
|
||||
VERIFICATION_METHOD_SAS,
|
||||
transactionID,
|
||||
transactionId,
|
||||
keyAgreementProtocols,
|
||||
hashes,
|
||||
messageAuthenticationCodes,
|
||||
|
@ -214,12 +221,12 @@ internal class VerificationTransportToDevice(
|
|||
}
|
||||
|
||||
override fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
sharedSecret: String): VerificationInfoStart {
|
||||
return KeyVerificationStart(
|
||||
fromDevice,
|
||||
VERIFICATION_METHOD_RECIPROCATE,
|
||||
transactionID,
|
||||
transactionId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
@ -229,7 +236,7 @@ internal class VerificationTransportToDevice(
|
|||
|
||||
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
||||
return KeyVerificationReady(
|
||||
transactionID = tid,
|
||||
transactionId = tid,
|
||||
fromDevice = fromDevice,
|
||||
methods = methods
|
||||
)
|
||||
|
|
|
@ -16,23 +16,22 @@
|
|||
|
||||
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.verification.CancelCode
|
||||
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.events.model.EventType
|
||||
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.verification.DefaultVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||
import im.vector.matrix.android.internal.crypto.verification.ValidVerificationInfoStart
|
||||
import im.vector.matrix.android.internal.util.exhaustive
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultQrCodeVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
override val transactionId: String,
|
||||
override val otherUserId: String,
|
||||
override var otherDeviceId: String?,
|
||||
|
@ -43,7 +42,15 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
val userId: String,
|
||||
val deviceId: String,
|
||||
override val isIncoming: Boolean
|
||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction {
|
||||
) : DefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
crossSigningService,
|
||||
userId,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming),
|
||||
QrCodeVerificationTransaction {
|
||||
|
||||
override val qrCodeText: String?
|
||||
get() = qrCodeData?.toEncodedString()
|
||||
|
@ -76,72 +83,95 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
}
|
||||
|
||||
// check master key
|
||||
val myMasterKey = crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey
|
||||
var canTrustOtherUserMasterKey = false
|
||||
|
||||
// Check the other device view of my MSK
|
||||
when (otherQrCodeData) {
|
||||
is QrCodeData.VerifyingAnotherUser -> {
|
||||
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey
|
||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
// key2 (aka otherUserMasterCrossSigningPublicKey) is what the one displaying the QR code (other user) think my MSK is.
|
||||
// Let's check that it's correct
|
||||
// If not -> Cancel
|
||||
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != myMasterKey) {
|
||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
} else Unit
|
||||
}
|
||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
// key1 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
|
||||
// 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}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
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 -> {
|
||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
// key2 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
|
||||
// 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}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
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
|
||||
|
||||
val toVerifyDeviceIds = mutableListOf<String>()
|
||||
var canTrustOtherUserMasterKey = false
|
||||
|
||||
// Check device key if available
|
||||
// Let's now check the other user/device key material
|
||||
when (otherQrCodeData) {
|
||||
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
|
||||
!= crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
} else {
|
||||
// It does so i should mark it as trusted
|
||||
canTrustOtherUserMasterKey = true
|
||||
Unit
|
||||
}
|
||||
}
|
||||
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
|
||||
!= cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) {
|
||||
Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
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 -> {
|
||||
// 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
|
||||
!= cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) {
|
||||
Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
} else {
|
||||
toVerifyDeviceIds.add(otherQrCodeData.deviceKey)
|
||||
// Yes it does -> i should trust it and sign then upload the signature
|
||||
toVerifyDeviceIds.add(otherDeviceId ?: "")
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
|
||||
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
|
||||
// Nothing to verify
|
||||
// // Nothing to verify
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
}
|
||||
|
@ -152,10 +182,12 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
start(otherQrCodeData.sharedSecret)
|
||||
|
||||
// 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) {
|
||||
Timber.e("## Verification QR: start verification from invalid state")
|
||||
// should I cancel??
|
||||
|
@ -177,9 +209,6 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
)
|
||||
}
|
||||
|
||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
cancel(CancelCode.User)
|
||||
}
|
||||
|
@ -192,14 +221,14 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
override fun isToDeviceTransport() = false
|
||||
|
||||
// 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) {
|
||||
// Should not happen
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if (startReq.sharedSecret == qrCodeData.sharedSecret) {
|
||||
if (startReq.sharedSecret.fromBase64Safe()?.contentEquals(qrCodeData.sharedSecret.fromBase64()) == true) {
|
||||
// Ok, we can trust the other user
|
||||
// We can only trust the master key in this case
|
||||
// But first, ask the user for a confirmation
|
||||
|
@ -211,7 +240,21 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
}
|
||||
|
||||
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() {
|
||||
|
@ -219,46 +262,4 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
// At least remove the transaction...
|
||||
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
|
||||
|
||||
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.extensions.toUnsignedInt
|
||||
|
||||
|
@ -52,15 +52,15 @@ fun QrCodeData.toEncodedString(): String {
|
|||
}
|
||||
|
||||
// Keys
|
||||
firstKey.fromBase64NoPadding().forEach {
|
||||
firstKey.fromBase64().forEach {
|
||||
result += it
|
||||
}
|
||||
secondKey.fromBase64NoPadding().forEach {
|
||||
secondKey.fromBase64().forEach {
|
||||
result += it
|
||||
}
|
||||
|
||||
// Secret
|
||||
sharedSecret.fromBase64NoPadding().forEach {
|
||||
sharedSecret.fromBase64().forEach {
|
||||
result += it
|
||||
}
|
||||
|
||||
|
@ -94,11 +94,11 @@ fun String.toQrCodeData(): QrCodeData? {
|
|||
val mode = byteArray[cursor].toInt()
|
||||
cursor++
|
||||
|
||||
// Get transaction length
|
||||
val bigEndian1 = byteArray[cursor].toUnsignedInt()
|
||||
val bigEndian2 = byteArray[cursor + 1].toUnsignedInt()
|
||||
// Get transaction length, Big Endian format
|
||||
val msb = byteArray[cursor].toUnsignedInt()
|
||||
val lsb = byteArray[cursor + 1].toUnsignedInt()
|
||||
|
||||
val transactionLength = bigEndian1 * 0x0100 + bigEndian2
|
||||
val transactionLength = msb.shl(8) + lsb
|
||||
|
||||
cursor++
|
||||
cursor++
|
||||
|
|
|
@ -25,3 +25,7 @@ annotation class SessionFilesDirectory
|
|||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
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 okhttp3.OkHttpClient
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.File
|
||||
|
||||
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class])
|
||||
@MatrixScope
|
||||
|
@ -52,6 +53,9 @@ internal interface MatrixComponent {
|
|||
|
||||
fun resources(): Resources
|
||||
|
||||
@CacheDirectory
|
||||
fun cacheDir(): File
|
||||
|
||||
fun olmManager(): OlmManager
|
||||
|
||||
fun taskExecutor(): TaskExecutor
|
||||
|
|
|
@ -26,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@Module
|
||||
|
@ -49,6 +50,13 @@ internal object MatrixModule {
|
|||
return context.resources
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@CacheDirectory
|
||||
fun providesCacheDir(context: Context): File {
|
||||
return context.cacheDir
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@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.internal.crypto.attachments.ElementToDecrypt
|
||||
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.Unauthenticated
|
||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
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.writeToFile
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
@ -42,8 +42,10 @@ import java.io.IOException
|
|||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultFileService @Inject constructor(
|
||||
@SessionCacheDirectory
|
||||
@CacheDirectory
|
||||
private val cacheDirectory: File,
|
||||
@SessionCacheDirectory
|
||||
private val sessionCacheDirectory: File,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
@Unauthenticated
|
||||
private val okHttpClient: OkHttpClient,
|
||||
|
@ -62,60 +64,50 @@ internal class DefaultFileService @Inject constructor(
|
|||
return GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
Try {
|
||||
val folder = getFolder(downloadMode, id)
|
||||
|
||||
val folder = File(sessionCacheDirectory, "MF")
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs()
|
||||
}
|
||||
File(folder, fileName)
|
||||
}.flatMap { destFile ->
|
||||
if (!destFile.exists() || downloadMode == FileService.DownloadMode.TO_EXPORT) {
|
||||
Try {
|
||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
|
||||
if (!destFile.exists()) {
|
||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(resolvedUrl)
|
||||
.build()
|
||||
val request = Request.Builder()
|
||||
.url(resolvedUrl)
|
||||
.build()
|
||||
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
var inputStream = response.body?.byteStream()
|
||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||
if (!response.isSuccessful
|
||||
|| inputStream == null) {
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## decrypt file")
|
||||
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
?: throw IllegalStateException("Decryption error")
|
||||
}
|
||||
|
||||
writeToFile(inputStream, destFile)
|
||||
destFile
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
var inputStream = response.body?.byteStream()
|
||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||
if (!response.isSuccessful || inputStream == null) {
|
||||
return@flatMap Try.Failure(IOException())
|
||||
}
|
||||
} 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)
|
||||
}.toCancelable()
|
||||
}
|
||||
|
||||
private fun getFolder(downloadMode: FileService.DownloadMode, id: String): File {
|
||||
private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File {
|
||||
return when (downloadMode) {
|
||||
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
||||
// Create dir tree (MF stands for Matrix File):
|
||||
// <cache>/<sessionId>/MF/<md5(id)>/
|
||||
val tmpFolderSession = File(cacheDirectory, "MF")
|
||||
File(tmpFolderSession, id.md5())
|
||||
}
|
||||
FileService.DownloadMode.TO_EXPORT -> {
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
FileService.DownloadMode.TO_EXPORT ->
|
||||
file.copyTo(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), file.name), true)
|
||||
FileService.DownloadMode.FOR_EXTERNAL_SHARE ->
|
||||
file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true)
|
||||
FileService.DownloadMode.FOR_INTERNAL_USE ->
|
||||
file
|
||||
}
|
||||
.also { folder ->
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
|
@ -33,7 +34,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
|||
val listeners = listeners.getOrPut(key) { ArrayList() }
|
||||
listeners.add(updateListener)
|
||||
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) {
|
||||
|
@ -79,7 +86,13 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
|||
private fun updateState(key: String, state: ContentUploadStateTracker.State) {
|
||||
states[key] = state
|
||||
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,
|
||||
val events: List<Event>,
|
||||
val attachment: ContentAttachmentData,
|
||||
val isRoomEncrypted: Boolean,
|
||||
val isEncrypted: Boolean,
|
||||
val compressBeforeSending: Boolean,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
@ -90,9 +90,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
Timber.e(e)
|
||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
||||
return Result.success(
|
||||
WorkerParamsFactory.toData(params.copy(
|
||||
lastFailureMessage = e.localizedMessage
|
||||
))
|
||||
WorkerParamsFactory.toData(
|
||||
params.copy(
|
||||
lastFailureMessage = e.localizedMessage
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
.let { originalFile ->
|
||||
|
@ -136,7 +138,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
}
|
||||
|
||||
try {
|
||||
val contentUploadResponse = if (params.isRoomEncrypted) {
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("Encrypt thumbnail")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
|
||||
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
|
||||
|
||||
return try {
|
||||
val contentUploadResponse = if (params.isRoomEncrypted) {
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("Encrypt file")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
|
||||
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType())
|
||||
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
|
||||
|
||||
fileUploader
|
||||
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
|
||||
} else {
|
||||
fileUploader
|
||||
.uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
|
||||
.uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener)
|
||||
}
|
||||
|
||||
handleSuccess(params,
|
||||
|
@ -226,7 +228,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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.getActions
|
||||
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.util.Cancelable
|
||||
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.GetPushRulesTask
|
||||
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.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
|
@ -42,6 +44,7 @@ internal class DefaultPushRuleService @Inject constructor(
|
|||
private val getPushRulesTask: GetPushRulesTask,
|
||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||
private val addPushRuleTask: AddPushRuleTask,
|
||||
private val updatePushRuleActionsTask: UpdatePushRuleActionsTask,
|
||||
private val removePushRuleTask: RemovePushRuleTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy
|
||||
|
@ -55,7 +58,7 @@ internal class DefaultPushRuleService @Inject constructor(
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getPushRules(scope: String): List<PushRule> {
|
||||
override fun getPushRules(scope: String): RuleSet {
|
||||
var contentRules: List<PushRule> = emptyList()
|
||||
var overrideRules: 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 overrideRules + contentRules + roomRules + senderRules + underrideRules
|
||||
return RuleSet(
|
||||
content = contentRules,
|
||||
override = overrideRules,
|
||||
room = roomRules,
|
||||
sender = senderRules,
|
||||
underride = underrideRules
|
||||
)
|
||||
}
|
||||
|
||||
override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
|
@ -111,6 +119,14 @@ internal class DefaultPushRuleService @Inject constructor(
|
|||
.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 {
|
||||
return removePushRuleTask
|
||||
.configureWith(RemovePushRuleTask.Params(kind, pushRule)) {
|
||||
|
|
|
@ -40,24 +40,35 @@ import im.vector.matrix.android.internal.di.SerializeNulls
|
|||
* }
|
||||
* </code>
|
||||
*/
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
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")
|
||||
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
|
||||
@Json(name = "kind")
|
||||
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")
|
||||
val appId: String,
|
||||
|
@ -88,15 +99,17 @@ internal data class JsonPusher(
|
|||
|
||||
/**
|
||||
* 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")
|
||||
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
|
||||
// to any others with different user IDs.
|
||||
// Otherwise, the homeserver must remove any other pushers with the same App ID and pushkey for different users.
|
||||
// The default is false.
|
||||
/**
|
||||
* If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others
|
||||
* with different user IDs. Otherwise, the homeserver must remove any other pushers with the same App ID and pushkey
|
||||
* for different users.
|
||||
* The default is false.
|
||||
*/
|
||||
@Json(name = "append")
|
||||
val append: Boolean? = false
|
||||
)
|
||||
|
|
|
@ -22,12 +22,14 @@ import com.squareup.moshi.JsonClass
|
|||
internal data class JsonPusherData(
|
||||
/**
|
||||
* 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")
|
||||
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")
|
||||
val format: String? = null
|
||||
|
|
|
@ -47,6 +47,7 @@ internal interface PushRulesApi {
|
|||
|
||||
/**
|
||||
* 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 ruleId the ruleId
|
||||
|
|
|
@ -72,6 +72,9 @@ internal abstract class PushersModule {
|
|||
@Binds
|
||||
abstract fun bindAddPushRuleTask(task: DefaultAddPushRuleTask): AddPushRuleTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindUpdatePushRuleActionTask(task: DefaultUpdatePushRuleActionsTask): UpdatePushRuleActionsTask
|
||||
|
||||
@Binds
|
||||
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
|
||||
* is matching an existing local echo.
|
||||
*
|
||||
* The transactionID is used as loc
|
||||
* The transactionId is used as loc
|
||||
*/
|
||||
internal class LocalEchoEventFactory @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
|
@ -261,7 +261,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
msgType = MessageType.MSGTYPE_IMAGE,
|
||||
body = attachment.name ?: "image",
|
||||
info = ImageInfo(
|
||||
mimeType = attachment.mimeType,
|
||||
mimeType = attachment.getSafeMimeType(),
|
||||
width = width?.toInt() ?: 0,
|
||||
height = height?.toInt() ?: 0,
|
||||
size = attachment.size.toInt()
|
||||
|
@ -293,7 +293,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
msgType = MessageType.MSGTYPE_VIDEO,
|
||||
body = attachment.name ?: "video",
|
||||
videoInfo = VideoInfo(
|
||||
mimeType = attachment.mimeType,
|
||||
mimeType = attachment.getSafeMimeType(),
|
||||
width = width,
|
||||
height = height,
|
||||
size = attachment.size,
|
||||
|
@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
msgType = MessageType.MSGTYPE_AUDIO,
|
||||
body = attachment.name ?: "audio",
|
||||
audioInfo = AudioInfo(
|
||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||
size = attachment.size
|
||||
),
|
||||
url = attachment.path
|
||||
|
@ -325,7 +325,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
msgType = MessageType.MSGTYPE_FILE,
|
||||
body = attachment.name ?: "file",
|
||||
info = FileInfo(
|
||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() }
|
||||
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }
|
||||
?: "application/octet-stream",
|
||||
size = attachment.size
|
||||
),
|
||||
|
@ -335,25 +335,25 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
}
|
||||
|
||||
private fun createEvent(roomId: String, content: Any? = null): Event {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
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 {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
content = MessageVerificationRequestContent(
|
||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||
|
@ -362,7 +362,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
timestamp = System.currentTimeMillis(),
|
||||
methods = methods
|
||||
).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 {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = EventType.REDACTION,
|
||||
redacts = eventId,
|
||||
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
|
||||
} // nothing on initial sync
|
||||
|
||||
val rules = pushRuleService.getPushRules(RuleScope.GLOBAL)
|
||||
val rules = pushRuleService.getPushRules(RuleScope.GLOBAL).getAllRules()
|
||||
processEventForPushTask.execute(ProcessEventForPushTask.Params(roomsSyncResponse, rules))
|
||||
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>
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
adb shell dumpsys jobscheduler im.vector.riotx.debug
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
adb shell dumpsys jobscheduler im.vector.riotx
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
|||
}
|
||||
|
||||
ext.versionMajor = 0
|
||||
ext.versionMinor = 17
|
||||
ext.versionMinor = 18
|
||||
ext.versionPatch = 0
|
||||
|
||||
static def getGitTimestamp() {
|
||||
|
@ -296,7 +296,7 @@ dependencies {
|
|||
implementation 'com.airbnb.android:mvrx:1.3.0'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
||||
|
|
|
@ -6,9 +6,15 @@
|
|||
<issue id="MissingTranslation" severity="warning" />
|
||||
<issue id="TypographyEllipsis" severity="error" />
|
||||
<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 -->
|
||||
<issue id="ButtonOrder" severity="error" />
|
||||
<issue id="TextFields" severity="error" />
|
||||
|
||||
<!-- Layout -->
|
||||
<issue id="UnknownIdInLayout" severity="error" />
|
||||
|
@ -19,6 +25,7 @@
|
|||
<issue id="InefficientWeight" severity="error" />
|
||||
<issue id="DisableBaselineAlignment" severity="error" />
|
||||
<issue id="ScrollViewSize" severity="error" />
|
||||
<issue id="NegativeMargin" severity="error" />
|
||||
|
||||
<!-- RTL -->
|
||||
<issue id="RtlEnabled" severity="error" />
|
||||
|
@ -30,9 +37,22 @@
|
|||
<issue id="SetTextI18n" severity="error" />
|
||||
<issue id="ViewConstructor" severity="error" />
|
||||
<issue id="UseValueOf" severity="error" />
|
||||
<issue id="Recycle" severity="error" />
|
||||
<issue id="KotlinPropertyAccess" severity="error" />
|
||||
|
||||
<!-- Ignore error from HtmlCompressor lib -->
|
||||
<issue id="InvalidPackage">
|
||||
<ignore path="**/htmlcompressor-1.4.jar"/>
|
||||
<ignore path="**/htmlcompressor-1.4.jar" />
|
||||
</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>
|
||||
|
|
|
@ -114,7 +114,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
|||
.setContentText("Content")
|
||||
// No effect because it's a group summary notif
|
||||
.setNumber(33)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setSmallIcon(R.drawable.ic_status_bar)
|
||||
// This provocate the badge issue: no badge for group notification
|
||||
.setGroup("GroupKey")
|
||||
.setGroupSummary(true)
|
||||
|
@ -147,7 +147,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
|||
// For shortcut on long press on launcher icon
|
||||
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
|
||||
.setStyle(messagingStyle1)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setSmallIcon(R.drawable.ic_status_bar)
|
||||
.setGroup("GroupKey")
|
||||
.build()
|
||||
)
|
||||
|
@ -159,7 +159,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
|||
.setContentTitle("Title 2")
|
||||
.setContentText("Content 2")
|
||||
.setStyle(messagingStyle2)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setSmallIcon(R.drawable.ic_status_bar)
|
||||
.setGroup("GroupKey")
|
||||
.build()
|
||||
)
|
||||
|
|
|
@ -57,7 +57,7 @@ object FcmHelper {
|
|||
*
|
||||
* @param activity the first launch Activity
|
||||
*/
|
||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
|
||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
// 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.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||
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.TestSystemSettings
|
||||
import javax.inject.Inject
|
||||
|
@ -28,7 +28,7 @@ import javax.inject.Inject
|
|||
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
|
||||
private val testAccountSettings: TestAccountSettings,
|
||||
private val testDeviceSettings: TestDeviceSettings,
|
||||
private val testBingRulesSettings: TestBingRulesSettings,
|
||||
private val testPushRulesSettings: TestPushRulesSettings,
|
||||
private val testAutoStartBoot: TestAutoStartBoot,
|
||||
private val testBackgroundRestrictions: TestBackgroundRestrictions) {
|
||||
|
||||
|
@ -37,7 +37,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
|
|||
mgr.addTest(testSystemSettings)
|
||||
mgr.addTest(testAccountSettings)
|
||||
mgr.addTest(testDeviceSettings)
|
||||
mgr.addTest(testBingRulesSettings)
|
||||
mgr.addTest(testPushRulesSettings)
|
||||
mgr.addTest(testAutoStartBoot)
|
||||
mgr.addTest(testBackgroundRestrictions)
|
||||
return mgr
|
||||
|
|
|
@ -25,13 +25,13 @@ import androidx.lifecycle.Lifecycle
|
|||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
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.events.model.Event
|
||||
import im.vector.riotx.BuildConfig
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
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.features.badge.BadgeProxy
|
||||
import im.vector.riotx.features.notifications.NotifiableEventResolver
|
||||
|
@ -196,7 +196,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
description = "",
|
||||
type = null,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
soundName = BingRule.ACTION_VALUE_DEFAULT,
|
||||
soundName = PushRule.ACTION_VALUE_DEFAULT,
|
||||
isPushGatewayEvent = true
|
||||
)
|
||||
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)
|
||||
|
|
|
@ -68,7 +68,7 @@ object FcmHelper {
|
|||
*
|
||||
* @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))) {
|
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||
if (checkPlayServices(activity)) {
|
||||
|
@ -76,7 +76,9 @@ object FcmHelper {
|
|||
FirebaseInstanceId.getInstance().instanceId
|
||||
.addOnSuccessListener(activity) { instanceIdResult ->
|
||||
storeFcmToken(activity, instanceIdResult.token)
|
||||
pushersManager.registerPusherWithFcmKey(instanceIdResult.token)
|
||||
if (registerPusher) {
|
||||
pushersManager.registerPusherWithFcmKey(instanceIdResult.token)
|
||||
}
|
||||
}
|
||||
.addOnFailureListener(activity) { e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") }
|
||||
} catch (e: Throwable) {
|
||||
|
|
|
@ -18,8 +18,8 @@ package im.vector.riotx.push.fcm
|
|||
import androidx.fragment.app.Fragment
|
||||
import im.vector.riotx.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||
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.TestPushRulesSettings
|
||||
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.TestPlayServices
|
||||
|
@ -29,7 +29,7 @@ import javax.inject.Inject
|
|||
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
|
||||
private val testAccountSettings: TestAccountSettings,
|
||||
private val testDeviceSettings: TestDeviceSettings,
|
||||
private val testBingRulesSettings: TestBingRulesSettings,
|
||||
private val testBingRulesSettings: TestPushRulesSettings,
|
||||
private val testPlayServices: TestPlayServices,
|
||||
private val testFirebaseToken: TestFirebaseToken,
|
||||
private val testTokenRegistration: TestTokenRegistration) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue